summaryrefslogtreecommitdiffstats
path: root/drivers/phy/rockchip
diff options
context:
space:
mode:
authorDapeng Mi <dapeng1.mi@linux.intel.com>2026-09-17 09:52:33 +0800
committerIngo Molnar <mingo@kernel.org>2026-09-22 11:37:46 +0200
commit0102c8c7fdfc4bd700971daff2b94470f4eafae4 (patch)
tree531306c2299f9a269f0a986e29f17ba972180104 /drivers/phy/rockchip
downloadlinux-stable-0102c8c7fdfc4bd700971daff2b94470f4eafae4.tar.gz
linux-stable-0102c8c7fdfc4bd700971daff2b94470f4eafae4.zip
perf/x86/intel: Rename DMR offcore_rsp attribute to offmodule_rspgrafted
DMR introduces Offmodule Response events in place of the legacy Offcore Response events, but it still exposes the inherited offcore_rsp PMU attribute for programming the corresponding MSR data. Rename the DMR PMU attribute to offmodule_rsp so the sysfs interface matches the underlying event name and avoids user & tooling confusion. Signed-off-by: Dapeng Mi <dapeng1.mi@linux.intel.com> Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org> Signed-off-by: Ingo Molnar <mingo@kernel.org> Link: https://patch.msgid.link/20260917015234.981153-12-dapeng1.mi@linux.intel.com
Diffstat (limited to 'drivers/phy/rockchip')
-rw-r--r--drivers/phy/rockchip/Kconfig146
-rw-r--r--drivers/phy/rockchip/Makefile16
-rw-r--r--drivers/phy/rockchip/phy-rockchip-dp.c151
-rw-r--r--drivers/phy/rockchip/phy-rockchip-dphy-rx0.c383
-rw-r--r--drivers/phy/rockchip/phy-rockchip-emmc.c427
-rw-r--r--drivers/phy/rockchip/phy-rockchip-inno-csidphy.c561
-rw-r--r--drivers/phy/rockchip/phy-rockchip-inno-dsidphy.c878
-rw-r--r--drivers/phy/rockchip/phy-rockchip-inno-hdmi.c1485
-rw-r--r--drivers/phy/rockchip/phy-rockchip-inno-usb2.c2399
-rw-r--r--drivers/phy/rockchip/phy-rockchip-naneng-combphy.c1431
-rw-r--r--drivers/phy/rockchip/phy-rockchip-pcie.c338
-rw-r--r--drivers/phy/rockchip/phy-rockchip-samsung-dcphy.c1715
-rw-r--r--drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c2573
-rw-r--r--drivers/phy/rockchip/phy-rockchip-snps-pcie3.c355
-rw-r--r--drivers/phy/rockchip/phy-rockchip-typec.c1225
-rw-r--r--drivers/phy/rockchip/phy-rockchip-usb.c563
-rw-r--r--drivers/phy/rockchip/phy-rockchip-usbdp.c1665
17 files changed, 16311 insertions, 0 deletions
diff --git a/drivers/phy/rockchip/Kconfig b/drivers/phy/rockchip/Kconfig
new file mode 100644
index 000000000..14698571b
--- /dev/null
+++ b/drivers/phy/rockchip/Kconfig
@@ -0,0 +1,146 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Phy drivers for Rockchip platforms
+#
+config PHY_ROCKCHIP_DP
+ tristate "Rockchip Display Port PHY Driver"
+ depends on ARCH_ROCKCHIP && OF
+ select GENERIC_PHY
+ help
+ Enable this to support the Rockchip Display Port PHY.
+
+config PHY_ROCKCHIP_DPHY_RX0
+ tristate "Rockchip MIPI Synopsys DPHY RX0 driver"
+ depends on ARCH_ROCKCHIP || COMPILE_TEST
+ select GENERIC_PHY_MIPI_DPHY
+ select GENERIC_PHY
+ help
+ Enable this to support the Rockchip MIPI Synopsys DPHY RX0
+ associated to the Rockchip ISP module present in RK3399 SoCs.
+
+ To compile this driver as a module, choose M here: the module
+ will be called phy-rockchip-dphy-rx0.
+
+config PHY_ROCKCHIP_EMMC
+ tristate "Rockchip EMMC PHY Driver"
+ depends on ARCH_ROCKCHIP && OF
+ select GENERIC_PHY
+ help
+ Enable this to support the Rockchip EMMC PHY.
+
+config PHY_ROCKCHIP_INNO_HDMI
+ tristate "Rockchip INNO HDMI PHY Driver"
+ depends on (ARCH_ROCKCHIP || COMPILE_TEST) && OF
+ depends on COMMON_CLK
+ depends on HAS_IOMEM
+ select GENERIC_PHY
+ help
+ Enable this to support the Rockchip Innosilicon HDMI PHY.
+
+config PHY_ROCKCHIP_INNO_USB2
+ tristate "Rockchip INNO USB2PHY Driver"
+ depends on (ARCH_ROCKCHIP || COMPILE_TEST) && OF
+ depends on COMMON_CLK
+ depends on EXTCON
+ depends on USB_SUPPORT
+ select GENERIC_PHY
+ select USB_COMMON
+ help
+ Support for Rockchip USB2.0 PHY with Innosilicon IP block.
+
+config PHY_ROCKCHIP_INNO_CSIDPHY
+ tristate "Rockchip Innosilicon MIPI CSI PHY driver"
+ depends on (ARCH_ROCKCHIP || COMPILE_TEST) && OF
+ select GENERIC_PHY
+ select GENERIC_PHY_MIPI_DPHY
+ help
+ Enable this to support the Rockchip MIPI CSI PHY with
+ Innosilicon IP block.
+
+config PHY_ROCKCHIP_INNO_DSIDPHY
+ tristate "Rockchip Innosilicon MIPI/LVDS/TTL PHY driver"
+ depends on (ARCH_ROCKCHIP || COMPILE_TEST) && OF
+ select GENERIC_PHY
+ select GENERIC_PHY_MIPI_DPHY
+ help
+ Enable this to support the Rockchip MIPI/LVDS/TTL PHY with
+ Innosilicon IP block.
+
+config PHY_ROCKCHIP_NANENG_COMBO_PHY
+ tristate "Rockchip NANENG COMBO PHY Driver"
+ depends on ARCH_ROCKCHIP && OF
+ select GENERIC_PHY
+ help
+ Enable this to support the Rockchip PCIe/USB3.0/SATA/QSGMII
+ combo PHY with NaNeng IP block.
+
+config PHY_ROCKCHIP_PCIE
+ tristate "Rockchip PCIe PHY Driver"
+ depends on (ARCH_ROCKCHIP && OF) || COMPILE_TEST
+ depends on HAS_IOMEM
+ select GENERIC_PHY
+ select MFD_SYSCON
+ help
+ Enable this to support the Rockchip PCIe PHY.
+
+config PHY_ROCKCHIP_SAMSUNG_DCPHY
+ tristate "Rockchip Samsung MIPI DCPHY driver"
+ depends on (ARCH_ROCKCHIP || COMPILE_TEST)
+ select GENERIC_PHY
+ select GENERIC_PHY_MIPI_DPHY
+ help
+ Enable this to support the Rockchip MIPI DCPHY with
+ Samsung IP block.
+
+ To compile this driver as a module, choose M here: the module
+ will be called phy-rockchip-samsung-dcphy
+
+config PHY_ROCKCHIP_SAMSUNG_HDPTX
+ tristate "Rockchip Samsung HDMI/eDP Combo PHY driver"
+ depends on (ARCH_ROCKCHIP || COMPILE_TEST) && OF
+ depends on COMMON_CLK
+ depends on HAS_IOMEM
+ select GENERIC_PHY
+ select MFD_SYSCON
+ select RATIONAL
+ help
+ Enable this to support the Rockchip HDMI/eDP Combo PHY
+ with Samsung IP block.
+
+config PHY_ROCKCHIP_SNPS_PCIE3
+ tristate "Rockchip Snps PCIe3 PHY Driver"
+ depends on (ARCH_ROCKCHIP && OF) || COMPILE_TEST
+ depends on HAS_IOMEM
+ select GENERIC_PHY
+ select MFD_SYSCON
+ help
+ Enable this to support the Rockchip snps PCIe3 PHY.
+
+config PHY_ROCKCHIP_TYPEC
+ tristate "Rockchip TYPEC PHY Driver"
+ depends on OF && (ARCH_ROCKCHIP || COMPILE_TEST)
+ select EXTCON
+ select GENERIC_PHY
+ select RESET_CONTROLLER
+ help
+ Enable this to support the Rockchip USB TYPEC PHY.
+
+config PHY_ROCKCHIP_USB
+ tristate "Rockchip USB2 PHY Driver"
+ depends on ARCH_ROCKCHIP && OF
+ select GENERIC_PHY
+ help
+ Enable this to support the Rockchip USB 2.0 PHY.
+
+config PHY_ROCKCHIP_USBDP
+ tristate "Rockchip USBDP COMBO PHY Driver"
+ depends on ARCH_ROCKCHIP && OF
+ depends on TYPEC
+ select GENERIC_PHY
+ select USB_COMMON
+ help
+ Enable this to support the Rockchip USB3.0/DP combo PHY with
+ Samsung IP block. This is required for USB3 support on RK3588.
+
+ To compile this driver as a module, choose M here: the module
+ will be called phy-rockchip-usbdp
diff --git a/drivers/phy/rockchip/Makefile b/drivers/phy/rockchip/Makefile
new file mode 100644
index 000000000..117aaffd0
--- /dev/null
+++ b/drivers/phy/rockchip/Makefile
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_PHY_ROCKCHIP_DP) += phy-rockchip-dp.o
+obj-$(CONFIG_PHY_ROCKCHIP_DPHY_RX0) += phy-rockchip-dphy-rx0.o
+obj-$(CONFIG_PHY_ROCKCHIP_EMMC) += phy-rockchip-emmc.o
+obj-$(CONFIG_PHY_ROCKCHIP_INNO_CSIDPHY) += phy-rockchip-inno-csidphy.o
+obj-$(CONFIG_PHY_ROCKCHIP_INNO_DSIDPHY) += phy-rockchip-inno-dsidphy.o
+obj-$(CONFIG_PHY_ROCKCHIP_INNO_HDMI) += phy-rockchip-inno-hdmi.o
+obj-$(CONFIG_PHY_ROCKCHIP_INNO_USB2) += phy-rockchip-inno-usb2.o
+obj-$(CONFIG_PHY_ROCKCHIP_NANENG_COMBO_PHY) += phy-rockchip-naneng-combphy.o
+obj-$(CONFIG_PHY_ROCKCHIP_PCIE) += phy-rockchip-pcie.o
+obj-$(CONFIG_PHY_ROCKCHIP_SAMSUNG_DCPHY) += phy-rockchip-samsung-dcphy.o
+obj-$(CONFIG_PHY_ROCKCHIP_SAMSUNG_HDPTX) += phy-rockchip-samsung-hdptx.o
+obj-$(CONFIG_PHY_ROCKCHIP_SNPS_PCIE3) += phy-rockchip-snps-pcie3.o
+obj-$(CONFIG_PHY_ROCKCHIP_TYPEC) += phy-rockchip-typec.o
+obj-$(CONFIG_PHY_ROCKCHIP_USB) += phy-rockchip-usb.o
+obj-$(CONFIG_PHY_ROCKCHIP_USBDP) += phy-rockchip-usbdp.o
diff --git a/drivers/phy/rockchip/phy-rockchip-dp.c b/drivers/phy/rockchip/phy-rockchip-dp.c
new file mode 100644
index 000000000..592aa956e
--- /dev/null
+++ b/drivers/phy/rockchip/phy-rockchip-dp.c
@@ -0,0 +1,151 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Rockchip DP PHY driver
+ *
+ * Copyright (C) 2016 FuZhou Rockchip Co., Ltd.
+ * Author: Yakir Yang <ykk@@rock-chips.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define GRF_SOC_CON12 0x0274
+
+#define GRF_EDP_REF_CLK_SEL_INTER_HIWORD_MASK BIT(20)
+#define GRF_EDP_REF_CLK_SEL_INTER BIT(4)
+
+#define GRF_EDP_PHY_SIDDQ_HIWORD_MASK BIT(21)
+#define GRF_EDP_PHY_SIDDQ_ON 0
+#define GRF_EDP_PHY_SIDDQ_OFF BIT(5)
+
+struct rockchip_dp_phy {
+ struct device *dev;
+ struct regmap *grf;
+ struct clk *phy_24m;
+};
+
+static int rockchip_set_phy_state(struct phy *phy, bool enable)
+{
+ struct rockchip_dp_phy *dp = phy_get_drvdata(phy);
+ int ret;
+
+ if (enable) {
+ ret = regmap_write(dp->grf, GRF_SOC_CON12,
+ GRF_EDP_PHY_SIDDQ_HIWORD_MASK |
+ GRF_EDP_PHY_SIDDQ_ON);
+ if (ret < 0) {
+ dev_err(dp->dev, "Can't enable PHY power %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(dp->phy_24m);
+ } else {
+ clk_disable_unprepare(dp->phy_24m);
+
+ ret = regmap_write(dp->grf, GRF_SOC_CON12,
+ GRF_EDP_PHY_SIDDQ_HIWORD_MASK |
+ GRF_EDP_PHY_SIDDQ_OFF);
+ }
+
+ return ret;
+}
+
+static int rockchip_dp_phy_power_on(struct phy *phy)
+{
+ return rockchip_set_phy_state(phy, true);
+}
+
+static int rockchip_dp_phy_power_off(struct phy *phy)
+{
+ return rockchip_set_phy_state(phy, false);
+}
+
+static const struct phy_ops rockchip_dp_phy_ops = {
+ .power_on = rockchip_dp_phy_power_on,
+ .power_off = rockchip_dp_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static int rockchip_dp_phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct phy_provider *phy_provider;
+ struct rockchip_dp_phy *dp;
+ struct phy *phy;
+ int ret;
+
+ if (!np)
+ return -ENODEV;
+
+ if (!dev->parent || !dev->parent->of_node)
+ return -ENODEV;
+
+ dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL);
+ if (!dp)
+ return -ENOMEM;
+
+ dp->dev = dev;
+
+ dp->phy_24m = devm_clk_get(dev, "24m");
+ if (IS_ERR(dp->phy_24m)) {
+ dev_err(dev, "cannot get clock 24m\n");
+ return PTR_ERR(dp->phy_24m);
+ }
+
+ ret = clk_set_rate(dp->phy_24m, 24000000);
+ if (ret < 0) {
+ dev_err(dp->dev, "cannot set clock phy_24m %d\n", ret);
+ return ret;
+ }
+
+ dp->grf = syscon_node_to_regmap(dev->parent->of_node);
+ if (IS_ERR(dp->grf)) {
+ dev_err(dev, "rk3288-dp needs the General Register Files syscon\n");
+ return PTR_ERR(dp->grf);
+ }
+
+ ret = regmap_write(dp->grf, GRF_SOC_CON12, GRF_EDP_REF_CLK_SEL_INTER |
+ GRF_EDP_REF_CLK_SEL_INTER_HIWORD_MASK);
+ if (ret != 0) {
+ dev_err(dp->dev, "Could not config GRF edp ref clk: %d\n", ret);
+ return ret;
+ }
+
+ phy = devm_phy_create(dev, np, &rockchip_dp_phy_ops);
+ if (IS_ERR(phy)) {
+ dev_err(dev, "failed to create phy\n");
+ return PTR_ERR(phy);
+ }
+ phy_set_drvdata(phy, dp);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id rockchip_dp_phy_dt_ids[] = {
+ { .compatible = "rockchip,rk3288-dp-phy" },
+ {}
+};
+
+MODULE_DEVICE_TABLE(of, rockchip_dp_phy_dt_ids);
+
+static struct platform_driver rockchip_dp_phy_driver = {
+ .probe = rockchip_dp_phy_probe,
+ .driver = {
+ .name = "rockchip-dp-phy",
+ .of_match_table = rockchip_dp_phy_dt_ids,
+ },
+};
+
+module_platform_driver(rockchip_dp_phy_driver);
+
+MODULE_AUTHOR("Yakir Yang <ykk@rock-chips.com>");
+MODULE_DESCRIPTION("Rockchip DP PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/rockchip/phy-rockchip-dphy-rx0.c b/drivers/phy/rockchip/phy-rockchip-dphy-rx0.c
new file mode 100644
index 000000000..e6a768bbb
--- /dev/null
+++ b/drivers/phy/rockchip/phy-rockchip-dphy-rx0.c
@@ -0,0 +1,383 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+/*
+ * Rockchip MIPI Synopsys DPHY RX0 driver
+ *
+ * Copyright (C) 2019 Collabora, Ltd.
+ *
+ * Based on:
+ *
+ * drivers/media/platform/rockchip/isp1/mipi_dphy_sy.c
+ * in https://chromium.googlesource.com/chromiumos/third_party/kernel,
+ * chromeos-4.4 branch.
+ *
+ * Copyright (C) 2017 Fuzhou Rockchip Electronics Co., Ltd.
+ * Jacob Chen <jacob2.chen@rock-chips.com>
+ * Shunqian Zheng <zhengsq@rock-chips.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/phy/phy-mipi-dphy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define RK3399_GRF_SOC_CON9 0x6224
+#define RK3399_GRF_SOC_CON21 0x6254
+#define RK3399_GRF_SOC_CON22 0x6258
+#define RK3399_GRF_SOC_CON23 0x625c
+#define RK3399_GRF_SOC_CON24 0x6260
+#define RK3399_GRF_SOC_CON25 0x6264
+#define RK3399_GRF_SOC_STATUS1 0xe2a4
+
+#define CLOCK_LANE_HS_RX_CONTROL 0x34
+#define LANE0_HS_RX_CONTROL 0x44
+#define LANE1_HS_RX_CONTROL 0x54
+#define LANE2_HS_RX_CONTROL 0x84
+#define LANE3_HS_RX_CONTROL 0x94
+#define LANES_THS_SETTLE_CONTROL 0x75
+#define THS_SETTLE_COUNTER_THRESHOLD 0x04
+
+struct hsfreq_range {
+ u16 range_h;
+ u8 cfg_bit;
+};
+
+static const struct hsfreq_range rk3399_mipidphy_hsfreq_ranges[] = {
+ { 89, 0x00 }, { 99, 0x10 }, { 109, 0x20 }, { 129, 0x01 },
+ { 139, 0x11 }, { 149, 0x21 }, { 169, 0x02 }, { 179, 0x12 },
+ { 199, 0x22 }, { 219, 0x03 }, { 239, 0x13 }, { 249, 0x23 },
+ { 269, 0x04 }, { 299, 0x14 }, { 329, 0x05 }, { 359, 0x15 },
+ { 399, 0x25 }, { 449, 0x06 }, { 499, 0x16 }, { 549, 0x07 },
+ { 599, 0x17 }, { 649, 0x08 }, { 699, 0x18 }, { 749, 0x09 },
+ { 799, 0x19 }, { 849, 0x29 }, { 899, 0x39 }, { 949, 0x0a },
+ { 999, 0x1a }, { 1049, 0x2a }, { 1099, 0x3a }, { 1149, 0x0b },
+ { 1199, 0x1b }, { 1249, 0x2b }, { 1299, 0x3b }, { 1349, 0x0c },
+ { 1399, 0x1c }, { 1449, 0x2c }, { 1500, 0x3c }
+};
+
+static const char * const rk3399_mipidphy_clks[] = {
+ "dphy-ref",
+ "dphy-cfg",
+ "grf",
+};
+
+enum dphy_reg_id {
+ GRF_DPHY_RX0_TURNDISABLE = 0,
+ GRF_DPHY_RX0_FORCERXMODE,
+ GRF_DPHY_RX0_FORCETXSTOPMODE,
+ GRF_DPHY_RX0_ENABLE,
+ GRF_DPHY_RX0_TESTCLR,
+ GRF_DPHY_RX0_TESTCLK,
+ GRF_DPHY_RX0_TESTEN,
+ GRF_DPHY_RX0_TESTDIN,
+ GRF_DPHY_RX0_TURNREQUEST,
+ GRF_DPHY_RX0_TESTDOUT,
+ GRF_DPHY_TX0_TURNDISABLE,
+ GRF_DPHY_TX0_FORCERXMODE,
+ GRF_DPHY_TX0_FORCETXSTOPMODE,
+ GRF_DPHY_TX0_TURNREQUEST,
+ GRF_DPHY_TX1RX1_TURNDISABLE,
+ GRF_DPHY_TX1RX1_FORCERXMODE,
+ GRF_DPHY_TX1RX1_FORCETXSTOPMODE,
+ GRF_DPHY_TX1RX1_ENABLE,
+ GRF_DPHY_TX1RX1_MASTERSLAVEZ,
+ GRF_DPHY_TX1RX1_BASEDIR,
+ GRF_DPHY_TX1RX1_ENABLECLK,
+ GRF_DPHY_TX1RX1_TURNREQUEST,
+ GRF_DPHY_RX1_SRC_SEL,
+ /* rk3288 only */
+ GRF_CON_DISABLE_ISP,
+ GRF_CON_ISP_DPHY_SEL,
+ GRF_DSI_CSI_TESTBUS_SEL,
+ GRF_DVP_V18SEL,
+ /* below is for rk3399 only */
+ GRF_DPHY_RX0_CLK_INV_SEL,
+ GRF_DPHY_RX1_CLK_INV_SEL,
+};
+
+struct dphy_reg {
+ u16 offset;
+ u8 mask;
+ u8 shift;
+};
+
+#define PHY_REG(_offset, _width, _shift) \
+ { .offset = _offset, .mask = BIT(_width) - 1, .shift = _shift, }
+
+static const struct dphy_reg rk3399_grf_dphy_regs[] = {
+ [GRF_DPHY_RX0_TURNREQUEST] = PHY_REG(RK3399_GRF_SOC_CON9, 4, 0),
+ [GRF_DPHY_RX0_CLK_INV_SEL] = PHY_REG(RK3399_GRF_SOC_CON9, 1, 10),
+ [GRF_DPHY_RX1_CLK_INV_SEL] = PHY_REG(RK3399_GRF_SOC_CON9, 1, 11),
+ [GRF_DPHY_RX0_ENABLE] = PHY_REG(RK3399_GRF_SOC_CON21, 4, 0),
+ [GRF_DPHY_RX0_FORCERXMODE] = PHY_REG(RK3399_GRF_SOC_CON21, 4, 4),
+ [GRF_DPHY_RX0_FORCETXSTOPMODE] = PHY_REG(RK3399_GRF_SOC_CON21, 4, 8),
+ [GRF_DPHY_RX0_TURNDISABLE] = PHY_REG(RK3399_GRF_SOC_CON21, 4, 12),
+ [GRF_DPHY_TX0_FORCERXMODE] = PHY_REG(RK3399_GRF_SOC_CON22, 4, 0),
+ [GRF_DPHY_TX0_FORCETXSTOPMODE] = PHY_REG(RK3399_GRF_SOC_CON22, 4, 4),
+ [GRF_DPHY_TX0_TURNDISABLE] = PHY_REG(RK3399_GRF_SOC_CON22, 4, 8),
+ [GRF_DPHY_TX0_TURNREQUEST] = PHY_REG(RK3399_GRF_SOC_CON22, 4, 12),
+ [GRF_DPHY_TX1RX1_ENABLE] = PHY_REG(RK3399_GRF_SOC_CON23, 4, 0),
+ [GRF_DPHY_TX1RX1_FORCERXMODE] = PHY_REG(RK3399_GRF_SOC_CON23, 4, 4),
+ [GRF_DPHY_TX1RX1_FORCETXSTOPMODE] = PHY_REG(RK3399_GRF_SOC_CON23, 4, 8),
+ [GRF_DPHY_TX1RX1_TURNDISABLE] = PHY_REG(RK3399_GRF_SOC_CON23, 4, 12),
+ [GRF_DPHY_TX1RX1_TURNREQUEST] = PHY_REG(RK3399_GRF_SOC_CON24, 4, 0),
+ [GRF_DPHY_RX1_SRC_SEL] = PHY_REG(RK3399_GRF_SOC_CON24, 1, 4),
+ [GRF_DPHY_TX1RX1_BASEDIR] = PHY_REG(RK3399_GRF_SOC_CON24, 1, 5),
+ [GRF_DPHY_TX1RX1_ENABLECLK] = PHY_REG(RK3399_GRF_SOC_CON24, 1, 6),
+ [GRF_DPHY_TX1RX1_MASTERSLAVEZ] = PHY_REG(RK3399_GRF_SOC_CON24, 1, 7),
+ [GRF_DPHY_RX0_TESTDIN] = PHY_REG(RK3399_GRF_SOC_CON25, 8, 0),
+ [GRF_DPHY_RX0_TESTEN] = PHY_REG(RK3399_GRF_SOC_CON25, 1, 8),
+ [GRF_DPHY_RX0_TESTCLK] = PHY_REG(RK3399_GRF_SOC_CON25, 1, 9),
+ [GRF_DPHY_RX0_TESTCLR] = PHY_REG(RK3399_GRF_SOC_CON25, 1, 10),
+ [GRF_DPHY_RX0_TESTDOUT] = PHY_REG(RK3399_GRF_SOC_STATUS1, 8, 0),
+};
+
+struct rk_dphy_drv_data {
+ const char * const *clks;
+ unsigned int num_clks;
+ const struct hsfreq_range *hsfreq_ranges;
+ unsigned int num_hsfreq_ranges;
+ const struct dphy_reg *regs;
+};
+
+struct rk_dphy {
+ struct device *dev;
+ struct regmap *grf;
+ struct clk_bulk_data *clks;
+
+ const struct rk_dphy_drv_data *drv_data;
+ struct phy_configure_opts_mipi_dphy config;
+
+ u8 hsfreq;
+};
+
+static inline void rk_dphy_write_grf(struct rk_dphy *priv,
+ unsigned int index, u8 value)
+{
+ const struct dphy_reg *reg = &priv->drv_data->regs[index];
+ /* Update high word */
+ unsigned int val = (value << reg->shift) |
+ (reg->mask << (reg->shift + 16));
+
+ if (WARN_ON(!reg->offset))
+ return;
+ regmap_write(priv->grf, reg->offset, val);
+}
+
+static void rk_dphy_write(struct rk_dphy *priv, u8 test_code, u8 test_data)
+{
+ rk_dphy_write_grf(priv, GRF_DPHY_RX0_TESTDIN, test_code);
+ rk_dphy_write_grf(priv, GRF_DPHY_RX0_TESTEN, 1);
+ /*
+ * With the falling edge on TESTCLK, the TESTDIN[7:0] signal content
+ * is latched internally as the current test code. Test data is
+ * programmed internally by rising edge on TESTCLK.
+ * This code assumes that TESTCLK is already 1.
+ */
+ rk_dphy_write_grf(priv, GRF_DPHY_RX0_TESTCLK, 0);
+ rk_dphy_write_grf(priv, GRF_DPHY_RX0_TESTEN, 0);
+ rk_dphy_write_grf(priv, GRF_DPHY_RX0_TESTDIN, test_data);
+ rk_dphy_write_grf(priv, GRF_DPHY_RX0_TESTCLK, 1);
+}
+
+static void rk_dphy_enable(struct rk_dphy *priv)
+{
+ rk_dphy_write_grf(priv, GRF_DPHY_RX0_FORCERXMODE, 0);
+ rk_dphy_write_grf(priv, GRF_DPHY_RX0_FORCETXSTOPMODE, 0);
+
+ /* Disable lane turn around, which is ignored in receive mode */
+ rk_dphy_write_grf(priv, GRF_DPHY_RX0_TURNREQUEST, 0);
+ rk_dphy_write_grf(priv, GRF_DPHY_RX0_TURNDISABLE, 0xf);
+
+ rk_dphy_write_grf(priv, GRF_DPHY_RX0_ENABLE,
+ GENMASK(priv->config.lanes - 1, 0));
+
+ /* dphy start */
+ rk_dphy_write_grf(priv, GRF_DPHY_RX0_TESTCLK, 1);
+ rk_dphy_write_grf(priv, GRF_DPHY_RX0_TESTCLR, 1);
+ usleep_range(100, 150);
+ rk_dphy_write_grf(priv, GRF_DPHY_RX0_TESTCLR, 0);
+ usleep_range(100, 150);
+
+ /* set clock lane */
+ /* HS hsfreq_range & lane 0 settle bypass */
+ rk_dphy_write(priv, CLOCK_LANE_HS_RX_CONTROL, 0);
+ /* HS RX Control of lane0 */
+ rk_dphy_write(priv, LANE0_HS_RX_CONTROL, priv->hsfreq << 1);
+ /* HS RX Control of lane1 */
+ rk_dphy_write(priv, LANE1_HS_RX_CONTROL, priv->hsfreq << 1);
+ /* HS RX Control of lane2 */
+ rk_dphy_write(priv, LANE2_HS_RX_CONTROL, priv->hsfreq << 1);
+ /* HS RX Control of lane3 */
+ rk_dphy_write(priv, LANE3_HS_RX_CONTROL, priv->hsfreq << 1);
+ /* HS RX Data Lanes Settle State Time Control */
+ rk_dphy_write(priv, LANES_THS_SETTLE_CONTROL,
+ THS_SETTLE_COUNTER_THRESHOLD);
+
+ /* Normal operation */
+ rk_dphy_write(priv, 0x0, 0);
+}
+
+static int rk_dphy_configure(struct phy *phy, union phy_configure_opts *opts)
+{
+ struct rk_dphy *priv = phy_get_drvdata(phy);
+ const struct rk_dphy_drv_data *drv_data = priv->drv_data;
+ struct phy_configure_opts_mipi_dphy *config = &opts->mipi_dphy;
+ unsigned int hsfreq = 0;
+ unsigned int i;
+ u64 data_rate_mbps;
+ int ret;
+
+ /* pass with phy_mipi_dphy_get_default_config (with pixel rate?) */
+ ret = phy_mipi_dphy_config_validate(config);
+ if (ret)
+ return ret;
+
+ data_rate_mbps = div_u64(config->hs_clk_rate, 1000 * 1000);
+
+ dev_dbg(priv->dev, "lanes %d - data_rate_mbps %llu\n",
+ config->lanes, data_rate_mbps);
+ for (i = 0; i < drv_data->num_hsfreq_ranges; i++) {
+ if (drv_data->hsfreq_ranges[i].range_h >= data_rate_mbps) {
+ hsfreq = drv_data->hsfreq_ranges[i].cfg_bit;
+ break;
+ }
+ }
+ if (!hsfreq)
+ return -EINVAL;
+
+ priv->hsfreq = hsfreq;
+ priv->config = *config;
+ return 0;
+}
+
+static int rk_dphy_power_on(struct phy *phy)
+{
+ struct rk_dphy *priv = phy_get_drvdata(phy);
+ int ret;
+
+ ret = clk_bulk_enable(priv->drv_data->num_clks, priv->clks);
+ if (ret)
+ return ret;
+
+ rk_dphy_enable(priv);
+
+ return 0;
+}
+
+static int rk_dphy_power_off(struct phy *phy)
+{
+ struct rk_dphy *priv = phy_get_drvdata(phy);
+
+ rk_dphy_write_grf(priv, GRF_DPHY_RX0_ENABLE, 0);
+ clk_bulk_disable(priv->drv_data->num_clks, priv->clks);
+ return 0;
+}
+
+static int rk_dphy_init(struct phy *phy)
+{
+ struct rk_dphy *priv = phy_get_drvdata(phy);
+
+ return clk_bulk_prepare(priv->drv_data->num_clks, priv->clks);
+}
+
+static int rk_dphy_exit(struct phy *phy)
+{
+ struct rk_dphy *priv = phy_get_drvdata(phy);
+
+ clk_bulk_unprepare(priv->drv_data->num_clks, priv->clks);
+ return 0;
+}
+
+static const struct phy_ops rk_dphy_ops = {
+ .power_on = rk_dphy_power_on,
+ .power_off = rk_dphy_power_off,
+ .init = rk_dphy_init,
+ .exit = rk_dphy_exit,
+ .configure = rk_dphy_configure,
+ .owner = THIS_MODULE,
+};
+
+static const struct rk_dphy_drv_data rk3399_mipidphy_drv_data = {
+ .clks = rk3399_mipidphy_clks,
+ .num_clks = ARRAY_SIZE(rk3399_mipidphy_clks),
+ .hsfreq_ranges = rk3399_mipidphy_hsfreq_ranges,
+ .num_hsfreq_ranges = ARRAY_SIZE(rk3399_mipidphy_hsfreq_ranges),
+ .regs = rk3399_grf_dphy_regs,
+};
+
+static const struct of_device_id rk_dphy_dt_ids[] = {
+ {
+ .compatible = "rockchip,rk3399-mipi-dphy-rx0",
+ .data = &rk3399_mipidphy_drv_data,
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, rk_dphy_dt_ids);
+
+static int rk_dphy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ const struct rk_dphy_drv_data *drv_data;
+ struct phy_provider *phy_provider;
+ struct rk_dphy *priv;
+ struct phy *phy;
+ unsigned int i;
+ int ret;
+
+ if (!dev->parent || !dev->parent->of_node)
+ return -ENODEV;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ priv->dev = dev;
+
+ priv->grf = syscon_node_to_regmap(dev->parent->of_node);
+ if (IS_ERR(priv->grf)) {
+ dev_err(dev, "Can't find GRF syscon\n");
+ return -ENODEV;
+ }
+
+ drv_data = of_device_get_match_data(dev);
+ priv->drv_data = drv_data;
+ priv->clks = devm_kcalloc(&pdev->dev, drv_data->num_clks,
+ sizeof(*priv->clks), GFP_KERNEL);
+ if (!priv->clks)
+ return -ENOMEM;
+ for (i = 0; i < drv_data->num_clks; i++)
+ priv->clks[i].id = drv_data->clks[i];
+ ret = devm_clk_bulk_get(&pdev->dev, drv_data->num_clks, priv->clks);
+ if (ret)
+ return ret;
+
+ phy = devm_phy_create(dev, np, &rk_dphy_ops);
+ if (IS_ERR(phy)) {
+ dev_err(dev, "failed to create phy\n");
+ return PTR_ERR(phy);
+ }
+ phy_set_drvdata(phy, priv);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static struct platform_driver rk_dphy_driver = {
+ .probe = rk_dphy_probe,
+ .driver = {
+ .name = "rockchip-mipi-dphy-rx0",
+ .of_match_table = rk_dphy_dt_ids,
+ },
+};
+module_platform_driver(rk_dphy_driver);
+
+MODULE_AUTHOR("Ezequiel Garcia <ezequiel@collabora.com>");
+MODULE_DESCRIPTION("Rockchip MIPI Synopsys DPHY RX0 driver");
+MODULE_LICENSE("Dual MIT/GPL");
diff --git a/drivers/phy/rockchip/phy-rockchip-emmc.c b/drivers/phy/rockchip/phy-rockchip-emmc.c
new file mode 100644
index 000000000..5187983c5
--- /dev/null
+++ b/drivers/phy/rockchip/phy-rockchip-emmc.c
@@ -0,0 +1,427 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Rockchip emmc PHY driver
+ *
+ * Copyright (C) 2016 Shawn Lin <shawn.lin@rock-chips.com>
+ * Copyright (C) 2016 ROCKCHIP, Inc.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/hw_bitfield.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+/*
+ * The higher 16-bit of this register is used for write protection
+ * only if BIT(x + 16) set to 1 the BIT(x) can be written.
+ */
+#define HIWORD_UPDATE(val, mask, shift) \
+ (FIELD_PREP_WM16((mask) << (shift), (val)))
+
+/* Register definition */
+#define GRF_EMMCPHY_CON0 0x0
+#define GRF_EMMCPHY_CON1 0x4
+#define GRF_EMMCPHY_CON2 0x8
+#define GRF_EMMCPHY_CON3 0xc
+#define GRF_EMMCPHY_CON4 0x10
+#define GRF_EMMCPHY_CON5 0x14
+#define GRF_EMMCPHY_CON6 0x18
+#define GRF_EMMCPHY_STATUS 0x20
+
+#define PHYCTRL_PDB_MASK 0x1
+#define PHYCTRL_PDB_SHIFT 0x0
+#define PHYCTRL_PDB_PWR_ON 0x1
+#define PHYCTRL_PDB_PWR_OFF 0x0
+#define PHYCTRL_ENDLL_MASK 0x1
+#define PHYCTRL_ENDLL_SHIFT 0x1
+#define PHYCTRL_ENDLL_ENABLE 0x1
+#define PHYCTRL_ENDLL_DISABLE 0x0
+#define PHYCTRL_CALDONE_MASK 0x1
+#define PHYCTRL_CALDONE_SHIFT 0x6
+#define PHYCTRL_CALDONE_DONE 0x1
+#define PHYCTRL_CALDONE_GOING 0x0
+#define PHYCTRL_DLLRDY_MASK 0x1
+#define PHYCTRL_DLLRDY_SHIFT 0x5
+#define PHYCTRL_DLLRDY_DONE 0x1
+#define PHYCTRL_DLLRDY_GOING 0x0
+#define PHYCTRL_FREQSEL_200M 0x0
+#define PHYCTRL_FREQSEL_50M 0x1
+#define PHYCTRL_FREQSEL_100M 0x2
+#define PHYCTRL_FREQSEL_150M 0x3
+#define PHYCTRL_FREQSEL_MASK 0x3
+#define PHYCTRL_FREQSEL_SHIFT 0xc
+#define PHYCTRL_DR_MASK 0x7
+#define PHYCTRL_DR_SHIFT 0x4
+#define PHYCTRL_DR_50OHM 0x0
+#define PHYCTRL_DR_33OHM 0x1
+#define PHYCTRL_DR_66OHM 0x2
+#define PHYCTRL_DR_100OHM 0x3
+#define PHYCTRL_DR_40OHM 0x4
+#define PHYCTRL_OTAPDLYENA 0x1
+#define PHYCTRL_OTAPDLYENA_MASK 0x1
+#define PHYCTRL_OTAPDLYENA_SHIFT 0xb
+#define PHYCTRL_OTAPDLYSEL_DEFAULT 0x4
+#define PHYCTRL_OTAPDLYSEL_MAXVALUE 0xf
+#define PHYCTRL_OTAPDLYSEL_MASK 0xf
+#define PHYCTRL_OTAPDLYSEL_SHIFT 0x7
+#define PHYCTRL_REN_STRB_DISABLE 0x0
+#define PHYCTRL_REN_STRB_ENABLE 0x1
+#define PHYCTRL_REN_STRB_MASK 0x1
+#define PHYCTRL_REN_STRB_SHIFT 0x9
+
+#define PHYCTRL_IS_CALDONE(x) \
+ ((((x) >> PHYCTRL_CALDONE_SHIFT) & \
+ PHYCTRL_CALDONE_MASK) == PHYCTRL_CALDONE_DONE)
+#define PHYCTRL_IS_DLLRDY(x) \
+ ((((x) >> PHYCTRL_DLLRDY_SHIFT) & \
+ PHYCTRL_DLLRDY_MASK) == PHYCTRL_DLLRDY_DONE)
+
+struct rockchip_emmc_phy {
+ unsigned int reg_offset;
+ struct regmap *reg_base;
+ struct clk *emmcclk;
+ unsigned int drive_impedance;
+ unsigned int enable_strobe_pulldown;
+ unsigned int output_tapdelay_select;
+};
+
+static int rockchip_emmc_phy_power(struct phy *phy, bool on_off)
+{
+ struct rockchip_emmc_phy *rk_phy = phy_get_drvdata(phy);
+ unsigned int caldone;
+ unsigned int dllrdy;
+ unsigned int freqsel = PHYCTRL_FREQSEL_200M;
+ unsigned long rate;
+ int ret;
+
+ /*
+ * Keep phyctrl_pdb and phyctrl_endll low to allow
+ * initialization of CALIO state M/C DFFs
+ */
+ regmap_write(rk_phy->reg_base,
+ rk_phy->reg_offset + GRF_EMMCPHY_CON6,
+ HIWORD_UPDATE(PHYCTRL_PDB_PWR_OFF,
+ PHYCTRL_PDB_MASK,
+ PHYCTRL_PDB_SHIFT));
+ regmap_write(rk_phy->reg_base,
+ rk_phy->reg_offset + GRF_EMMCPHY_CON6,
+ HIWORD_UPDATE(PHYCTRL_ENDLL_DISABLE,
+ PHYCTRL_ENDLL_MASK,
+ PHYCTRL_ENDLL_SHIFT));
+
+ /* Already finish power_off above */
+ if (on_off == PHYCTRL_PDB_PWR_OFF)
+ return 0;
+
+ rate = clk_get_rate(rk_phy->emmcclk);
+
+ if (rate != 0) {
+ unsigned long ideal_rate;
+ unsigned long diff;
+
+ switch (rate) {
+ case 1 ... 74999999:
+ ideal_rate = 50000000;
+ freqsel = PHYCTRL_FREQSEL_50M;
+ break;
+ case 75000000 ... 124999999:
+ ideal_rate = 100000000;
+ freqsel = PHYCTRL_FREQSEL_100M;
+ break;
+ case 125000000 ... 174999999:
+ ideal_rate = 150000000;
+ freqsel = PHYCTRL_FREQSEL_150M;
+ break;
+ default:
+ ideal_rate = 200000000;
+ break;
+ }
+
+ diff = (rate > ideal_rate) ?
+ rate - ideal_rate : ideal_rate - rate;
+
+ /*
+ * In order for tuning delays to be accurate we need to be
+ * pretty spot on for the DLL range, so warn if we're too
+ * far off. Also warn if we're above the 200 MHz max. Don't
+ * warn for really slow rates since we won't be tuning then.
+ */
+ if ((rate > 50000000 && diff > 15000000) || (rate > 200000000))
+ dev_warn(&phy->dev, "Unsupported rate: %lu\n", rate);
+ }
+
+ /*
+ * According to the user manual, calpad calibration
+ * cycle takes more than 2us without the minimal recommended
+ * value, so we may need a little margin here
+ */
+ udelay(3);
+ regmap_write(rk_phy->reg_base,
+ rk_phy->reg_offset + GRF_EMMCPHY_CON6,
+ HIWORD_UPDATE(PHYCTRL_PDB_PWR_ON,
+ PHYCTRL_PDB_MASK,
+ PHYCTRL_PDB_SHIFT));
+
+ /*
+ * According to the user manual, it asks driver to wait 5us for
+ * calpad busy trimming. However it is documented that this value is
+ * PVT(A.K.A process,voltage and temperature) relevant, so some
+ * failure cases are found which indicates we should be more tolerant
+ * to calpad busy trimming.
+ */
+ ret = regmap_read_poll_timeout(rk_phy->reg_base,
+ rk_phy->reg_offset + GRF_EMMCPHY_STATUS,
+ caldone, PHYCTRL_IS_CALDONE(caldone),
+ 0, 50);
+ if (ret) {
+ pr_err("%s: caldone failed, ret=%d\n", __func__, ret);
+ return ret;
+ }
+
+ /* Set the frequency of the DLL operation */
+ regmap_write(rk_phy->reg_base,
+ rk_phy->reg_offset + GRF_EMMCPHY_CON0,
+ HIWORD_UPDATE(freqsel, PHYCTRL_FREQSEL_MASK,
+ PHYCTRL_FREQSEL_SHIFT));
+
+ /* Turn on the DLL */
+ regmap_write(rk_phy->reg_base,
+ rk_phy->reg_offset + GRF_EMMCPHY_CON6,
+ HIWORD_UPDATE(PHYCTRL_ENDLL_ENABLE,
+ PHYCTRL_ENDLL_MASK,
+ PHYCTRL_ENDLL_SHIFT));
+
+ /*
+ * We turned on the DLL even though the rate was 0 because we the
+ * clock might be turned on later. ...but we can't wait for the DLL
+ * to lock when the rate is 0 because it will never lock with no
+ * input clock.
+ *
+ * Technically we should be checking the lock later when the clock
+ * is turned on, but for now we won't.
+ */
+ if (rate == 0)
+ return 0;
+
+ /*
+ * After enabling analog DLL circuits docs say that we need 10.2 us if
+ * our source clock is at 50 MHz and that lock time scales linearly
+ * with clock speed. If we are powering on the PHY and the card clock
+ * is super slow (like 100 kHZ) this could take as long as 5.1 ms as
+ * per the math: 10.2 us * (50000000 Hz / 100000 Hz) => 5.1 ms
+ * Hopefully we won't be running at 100 kHz, but we should still make
+ * sure we wait long enough.
+ *
+ * NOTE: There appear to be corner cases where the DLL seems to take
+ * extra long to lock for reasons that aren't understood. In some
+ * extreme cases we've seen it take up to over 10ms (!). We'll be
+ * generous and give it 50ms.
+ */
+ ret = regmap_read_poll_timeout(rk_phy->reg_base,
+ rk_phy->reg_offset + GRF_EMMCPHY_STATUS,
+ dllrdy, PHYCTRL_IS_DLLRDY(dllrdy),
+ 0, 50 * USEC_PER_MSEC);
+ if (ret) {
+ pr_err("%s: dllrdy failed. ret=%d\n", __func__, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int rockchip_emmc_phy_init(struct phy *phy)
+{
+ struct rockchip_emmc_phy *rk_phy = phy_get_drvdata(phy);
+ int ret = 0;
+
+ /*
+ * We purposely get the clock here and not in probe to avoid the
+ * circular dependency problem. We expect:
+ * - PHY driver to probe
+ * - SDHCI driver to start probe
+ * - SDHCI driver to register it's clock
+ * - SDHCI driver to get the PHY
+ * - SDHCI driver to init the PHY
+ *
+ * The clock is optional, using clk_get_optional() to get the clock
+ * and do error processing if the return value != NULL
+ *
+ * NOTE: we don't do anything special for EPROBE_DEFER here. Given the
+ * above expected use case, EPROBE_DEFER isn't sensible to expect, so
+ * it's just like any other error.
+ */
+ rk_phy->emmcclk = clk_get_optional(&phy->dev, "emmcclk");
+ if (IS_ERR(rk_phy->emmcclk)) {
+ ret = PTR_ERR(rk_phy->emmcclk);
+ dev_err(&phy->dev, "Error getting emmcclk: %d\n", ret);
+ rk_phy->emmcclk = NULL;
+ }
+
+ return ret;
+}
+
+static int rockchip_emmc_phy_exit(struct phy *phy)
+{
+ struct rockchip_emmc_phy *rk_phy = phy_get_drvdata(phy);
+
+ clk_put(rk_phy->emmcclk);
+
+ return 0;
+}
+
+static int rockchip_emmc_phy_power_off(struct phy *phy)
+{
+ /* Power down emmc phy analog blocks */
+ return rockchip_emmc_phy_power(phy, PHYCTRL_PDB_PWR_OFF);
+}
+
+static int rockchip_emmc_phy_power_on(struct phy *phy)
+{
+ struct rockchip_emmc_phy *rk_phy = phy_get_drvdata(phy);
+
+ /* Drive impedance: from DTS */
+ regmap_write(rk_phy->reg_base,
+ rk_phy->reg_offset + GRF_EMMCPHY_CON6,
+ HIWORD_UPDATE(rk_phy->drive_impedance,
+ PHYCTRL_DR_MASK,
+ PHYCTRL_DR_SHIFT));
+
+ /* Output tap delay: enable */
+ regmap_write(rk_phy->reg_base,
+ rk_phy->reg_offset + GRF_EMMCPHY_CON0,
+ HIWORD_UPDATE(PHYCTRL_OTAPDLYENA,
+ PHYCTRL_OTAPDLYENA_MASK,
+ PHYCTRL_OTAPDLYENA_SHIFT));
+
+ /* Output tap delay */
+ regmap_write(rk_phy->reg_base,
+ rk_phy->reg_offset + GRF_EMMCPHY_CON0,
+ HIWORD_UPDATE(rk_phy->output_tapdelay_select,
+ PHYCTRL_OTAPDLYSEL_MASK,
+ PHYCTRL_OTAPDLYSEL_SHIFT));
+
+ /* Internal pull-down for strobe line */
+ regmap_write(rk_phy->reg_base,
+ rk_phy->reg_offset + GRF_EMMCPHY_CON2,
+ HIWORD_UPDATE(rk_phy->enable_strobe_pulldown,
+ PHYCTRL_REN_STRB_MASK,
+ PHYCTRL_REN_STRB_SHIFT));
+
+ /* Power up emmc phy analog blocks */
+ return rockchip_emmc_phy_power(phy, PHYCTRL_PDB_PWR_ON);
+}
+
+static const struct phy_ops ops = {
+ .init = rockchip_emmc_phy_init,
+ .exit = rockchip_emmc_phy_exit,
+ .power_on = rockchip_emmc_phy_power_on,
+ .power_off = rockchip_emmc_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static u32 convert_drive_impedance_ohm(struct platform_device *pdev, u32 dr_ohm)
+{
+ switch (dr_ohm) {
+ case 100:
+ return PHYCTRL_DR_100OHM;
+ case 66:
+ return PHYCTRL_DR_66OHM;
+ case 50:
+ return PHYCTRL_DR_50OHM;
+ case 40:
+ return PHYCTRL_DR_40OHM;
+ case 33:
+ return PHYCTRL_DR_33OHM;
+ }
+
+ dev_warn(&pdev->dev, "Invalid value %u for drive-impedance-ohm.\n",
+ dr_ohm);
+ return PHYCTRL_DR_50OHM;
+}
+
+static int rockchip_emmc_phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rockchip_emmc_phy *rk_phy;
+ struct phy *generic_phy;
+ struct phy_provider *phy_provider;
+ struct regmap *grf;
+ unsigned int reg_offset;
+ u32 val;
+
+ if (!dev->parent || !dev->parent->of_node)
+ return -ENODEV;
+
+ grf = syscon_node_to_regmap(dev->parent->of_node);
+ if (IS_ERR(grf)) {
+ dev_err(dev, "Missing rockchip,grf property\n");
+ return PTR_ERR(grf);
+ }
+
+ rk_phy = devm_kzalloc(dev, sizeof(*rk_phy), GFP_KERNEL);
+ if (!rk_phy)
+ return -ENOMEM;
+
+ if (of_property_read_u32(dev->of_node, "reg", &reg_offset)) {
+ dev_err(dev, "missing reg property in node %pOFn\n",
+ dev->of_node);
+ return -EINVAL;
+ }
+
+ rk_phy->reg_offset = reg_offset;
+ rk_phy->reg_base = grf;
+ rk_phy->drive_impedance = PHYCTRL_DR_50OHM;
+ rk_phy->enable_strobe_pulldown = PHYCTRL_REN_STRB_DISABLE;
+ rk_phy->output_tapdelay_select = PHYCTRL_OTAPDLYSEL_DEFAULT;
+
+ if (!of_property_read_u32(dev->of_node, "drive-impedance-ohm", &val))
+ rk_phy->drive_impedance = convert_drive_impedance_ohm(pdev, val);
+
+ if (of_property_read_bool(dev->of_node, "rockchip,enable-strobe-pulldown"))
+ rk_phy->enable_strobe_pulldown = PHYCTRL_REN_STRB_ENABLE;
+
+ if (!of_property_read_u32(dev->of_node, "rockchip,output-tapdelay-select", &val)) {
+ if (val <= PHYCTRL_OTAPDLYSEL_MAXVALUE)
+ rk_phy->output_tapdelay_select = val;
+ else
+ dev_err(dev, "output-tapdelay-select exceeds limit, apply default\n");
+ }
+
+ generic_phy = devm_phy_create(dev, dev->of_node, &ops);
+ if (IS_ERR(generic_phy)) {
+ dev_err(dev, "failed to create PHY\n");
+ return PTR_ERR(generic_phy);
+ }
+
+ phy_set_drvdata(generic_phy, rk_phy);
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id rockchip_emmc_phy_dt_ids[] = {
+ { .compatible = "rockchip,rk3399-emmc-phy" },
+ {}
+};
+
+MODULE_DEVICE_TABLE(of, rockchip_emmc_phy_dt_ids);
+
+static struct platform_driver rockchip_emmc_driver = {
+ .probe = rockchip_emmc_phy_probe,
+ .driver = {
+ .name = "rockchip-emmc-phy",
+ .of_match_table = rockchip_emmc_phy_dt_ids,
+ },
+};
+
+module_platform_driver(rockchip_emmc_driver);
+
+MODULE_AUTHOR("Shawn Lin <shawn.lin@rock-chips.com>");
+MODULE_DESCRIPTION("Rockchip EMMC PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/rockchip/phy-rockchip-inno-csidphy.c b/drivers/phy/rockchip/phy-rockchip-inno-csidphy.c
new file mode 100644
index 000000000..d989f1649
--- /dev/null
+++ b/drivers/phy/rockchip/phy-rockchip-inno-csidphy.c
@@ -0,0 +1,561 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Rockchip MIPI RX Innosilicon DPHY driver
+ *
+ * Copyright (C) 2021 Fuzhou Rockchip Electronics Co., Ltd.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/phy/phy.h>
+#include <linux/phy/phy-mipi-dphy.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+
+/* GRF */
+#define RK1808_GRF_PD_VI_CON_OFFSET 0x0430
+
+#define RK3326_GRF_PD_VI_CON_OFFSET 0x0430
+
+#define RK3368_GRF_SOC_CON6_OFFSET 0x0418
+
+#define RK3568_GRF_VI_CON0 0x0340
+#define RK3568_GRF_VI_CON1 0x0344
+
+#define RK3588_CSIDPHY_GRF_CON0 0x0000
+
+/* PHY */
+#define CSIDPHY_CTRL_LANE_ENABLE 0x00
+#define CSIDPHY_CTRL_LANE_ENABLE_CK BIT(6)
+#define CSIDPHY_CTRL_LANE_ENABLE_MASK GENMASK(5, 2)
+#define CSIDPHY_CTRL_LANE_ENABLE_UNDEFINED BIT(0)
+
+/* not present on all variants */
+#define CSIDPHY_CTRL_PWRCTL 0x04
+#define CSIDPHY_CTRL_PWRCTL_UNDEFINED GENMASK(7, 5)
+#define CSIDPHY_CTRL_PWRCTL_SYNCRST BIT(2)
+#define CSIDPHY_CTRL_PWRCTL_LDO_PD BIT(1)
+#define CSIDPHY_CTRL_PWRCTL_PLL_PD BIT(0)
+
+#define CSIDPHY_CTRL_DIG_RST 0x80
+#define CSIDPHY_CTRL_DIG_RST_UNDEFINED 0x1e
+#define CSIDPHY_CTRL_DIG_RST_RESET BIT(0)
+
+/* offset after ths_settle_offset */
+#define CSIDPHY_CLK_THS_SETTLE 0
+#define CSIDPHY_LANE_THS_SETTLE(n) (((n) + 1) * 0x80)
+#define CSIDPHY_THS_SETTLE_MASK GENMASK(6, 0)
+
+/* offset after calib_offset */
+#define CSIDPHY_CLK_CALIB_EN 0
+#define CSIDPHY_LANE_CALIB_EN(n) (((n) + 1) * 0x80)
+#define CSIDPHY_CALIB_EN BIT(7)
+
+/* Configure the count time of the THS-SETTLE by protocol. */
+#define RK1808_CSIDPHY_CLK_WR_THS_SETTLE 0x160
+#define RK3326_CSIDPHY_CLK_WR_THS_SETTLE 0x100
+#define RK3368_CSIDPHY_CLK_WR_THS_SETTLE 0x100
+#define RK3568_CSIDPHY_CLK_WR_THS_SETTLE 0x160
+
+/* Calibration reception enable */
+#define RK1808_CSIDPHY_CLK_CALIB_EN 0x168
+#define RK3568_CSIDPHY_CLK_CALIB_EN 0x168
+
+#define CSIDPHY_LANE_CLK_3_PHASE 0x38
+#define CSIDPHY_CLK_PHASE_MASK GENMASK(6, 4)
+#define CSIDPHY_CLK_PHASE_DEFAULT 3
+
+#define RESETS_MAX 2
+
+/*
+ * The higher 16-bit of this register is used for write protection
+ * only if BIT(x + 16) set to 1 the BIT(x) can be written.
+ */
+#define HIWORD_UPDATE(val, mask, shift) \
+ ((val) << (shift) | (mask) << ((shift) + 16))
+
+#define HZ_TO_MHZ(freq) div_u64(freq, 1000 * 1000)
+
+enum dphy_reg_id {
+ /* rk1808 & rk3326 */
+ GRF_DPHY_CSIPHY_FORCERXMODE,
+ GRF_DPHY_CSIPHY_CLKLANE_EN,
+ GRF_DPHY_CSIPHY_DATALANE_EN,
+};
+
+struct dphy_reg {
+ u32 offset;
+ u32 mask;
+ u32 shift;
+ u8 valid;
+};
+
+#define PHY_REG(_offset, _width, _shift) \
+ { .offset = _offset, .mask = BIT(_width) - 1, .shift = _shift, .valid = 1, }
+
+static const struct dphy_reg rk1808_grf_dphy_regs[] = {
+ [GRF_DPHY_CSIPHY_FORCERXMODE] = PHY_REG(RK1808_GRF_PD_VI_CON_OFFSET, 4, 0),
+ [GRF_DPHY_CSIPHY_CLKLANE_EN] = PHY_REG(RK1808_GRF_PD_VI_CON_OFFSET, 1, 8),
+ [GRF_DPHY_CSIPHY_DATALANE_EN] = PHY_REG(RK1808_GRF_PD_VI_CON_OFFSET, 4, 4),
+};
+
+static const struct dphy_reg rk3326_grf_dphy_regs[] = {
+ [GRF_DPHY_CSIPHY_FORCERXMODE] = PHY_REG(RK3326_GRF_PD_VI_CON_OFFSET, 4, 0),
+ [GRF_DPHY_CSIPHY_CLKLANE_EN] = PHY_REG(RK3326_GRF_PD_VI_CON_OFFSET, 1, 8),
+ [GRF_DPHY_CSIPHY_DATALANE_EN] = PHY_REG(RK3326_GRF_PD_VI_CON_OFFSET, 4, 4),
+};
+
+static const struct dphy_reg rk3368_grf_dphy_regs[] = {
+ [GRF_DPHY_CSIPHY_FORCERXMODE] = PHY_REG(RK3368_GRF_SOC_CON6_OFFSET, 4, 8),
+};
+
+static const struct dphy_reg rk3568_grf_dphy_regs[] = {
+ [GRF_DPHY_CSIPHY_FORCERXMODE] = PHY_REG(RK3568_GRF_VI_CON0, 4, 0),
+ [GRF_DPHY_CSIPHY_DATALANE_EN] = PHY_REG(RK3568_GRF_VI_CON0, 4, 4),
+ [GRF_DPHY_CSIPHY_CLKLANE_EN] = PHY_REG(RK3568_GRF_VI_CON0, 1, 8),
+};
+
+static const struct dphy_reg rk3588_grf_dphy_regs[] = {
+ [GRF_DPHY_CSIPHY_FORCERXMODE] = PHY_REG(RK3588_CSIDPHY_GRF_CON0, 4, 0),
+ [GRF_DPHY_CSIPHY_DATALANE_EN] = PHY_REG(RK3588_CSIDPHY_GRF_CON0, 4, 4),
+ [GRF_DPHY_CSIPHY_CLKLANE_EN] = PHY_REG(RK3588_CSIDPHY_GRF_CON0, 1, 8),
+};
+
+struct hsfreq_range {
+ u32 range_h;
+ u8 cfg_bit;
+};
+
+struct dphy_drv_data {
+ int pwrctl_offset;
+ int ths_settle_offset;
+ int calib_offset;
+ const struct hsfreq_range *hsfreq_ranges;
+ int num_hsfreq_ranges;
+ const struct dphy_reg *grf_regs;
+ const char *const *resets;
+ unsigned int resets_num;
+};
+
+struct rockchip_inno_csidphy {
+ struct device *dev;
+ void __iomem *phy_base;
+ struct clk *pclk;
+ struct regmap *grf;
+ struct reset_control_bulk_data resets[RESETS_MAX];
+ unsigned int resets_num;
+ const struct dphy_drv_data *drv_data;
+ struct phy_configure_opts_mipi_dphy config;
+ u8 hsfreq;
+ int clk_phase;
+};
+
+static inline void write_grf_reg(struct rockchip_inno_csidphy *priv,
+ int index, u8 value)
+{
+ const struct dphy_drv_data *drv_data = priv->drv_data;
+ const struct dphy_reg *reg = &drv_data->grf_regs[index];
+
+ if (reg->valid)
+ regmap_write(priv->grf, reg->offset,
+ HIWORD_UPDATE(value, reg->mask, reg->shift));
+}
+
+/* These tables must be sorted by .range_h ascending. */
+static const struct hsfreq_range rk1808_mipidphy_hsfreq_ranges[] = {
+ { 109, 0x02}, { 149, 0x03}, { 199, 0x06}, { 249, 0x06},
+ { 299, 0x06}, { 399, 0x08}, { 499, 0x0b}, { 599, 0x0e},
+ { 699, 0x10}, { 799, 0x12}, { 999, 0x16}, {1199, 0x1e},
+ {1399, 0x23}, {1599, 0x2d}, {1799, 0x32}, {1999, 0x37},
+ {2199, 0x3c}, {2399, 0x41}, {2500, 0x46}
+};
+
+static const struct hsfreq_range rk3326_mipidphy_hsfreq_ranges[] = {
+ { 109, 0x00}, { 149, 0x01}, { 199, 0x02}, { 249, 0x03},
+ { 299, 0x04}, { 399, 0x05}, { 499, 0x06}, { 599, 0x07},
+ { 699, 0x08}, { 799, 0x09}, { 899, 0x0a}, {1099, 0x0b},
+ {1249, 0x0c}, {1349, 0x0d}, {1500, 0x0e}
+};
+
+static const struct hsfreq_range rk3368_mipidphy_hsfreq_ranges[] = {
+ { 109, 0x00}, { 149, 0x01}, { 199, 0x02}, { 249, 0x03},
+ { 299, 0x04}, { 399, 0x05}, { 499, 0x06}, { 599, 0x07},
+ { 699, 0x08}, { 799, 0x09}, { 899, 0x0a}, {1099, 0x0b},
+ {1249, 0x0c}, {1349, 0x0d}, {1500, 0x0e}
+};
+
+static const char *const rk3368_reset_names[] = {
+ "apb"
+};
+
+static const char *const rk3588_reset_names[] = {
+ "apb",
+ "phy"
+};
+
+static void rockchip_inno_csidphy_ths_settle(struct rockchip_inno_csidphy *priv,
+ int hsfreq, int offset)
+{
+ const struct dphy_drv_data *drv_data = priv->drv_data;
+ u32 val;
+
+ val = readl(priv->phy_base + drv_data->ths_settle_offset + offset);
+ val &= ~CSIDPHY_THS_SETTLE_MASK;
+ val |= hsfreq;
+ writel(val, priv->phy_base + drv_data->ths_settle_offset + offset);
+}
+
+static int rockchip_inno_csidphy_configure(struct phy *phy,
+ union phy_configure_opts *opts)
+{
+ struct rockchip_inno_csidphy *priv = phy_get_drvdata(phy);
+ const struct dphy_drv_data *drv_data = priv->drv_data;
+ struct phy_configure_opts_mipi_dphy *config = &opts->mipi_dphy;
+ unsigned int hsfreq = 0;
+ unsigned int i;
+ u64 data_rate_mbps;
+ int ret;
+
+ /* pass with phy_mipi_dphy_get_default_config (with pixel rate?) */
+ ret = phy_mipi_dphy_config_validate(config);
+ if (ret)
+ return ret;
+
+ data_rate_mbps = HZ_TO_MHZ(config->hs_clk_rate);
+
+ dev_dbg(priv->dev, "lanes %d - data_rate_mbps %llu\n",
+ config->lanes, data_rate_mbps);
+ for (i = 0; i < drv_data->num_hsfreq_ranges; i++) {
+ if (drv_data->hsfreq_ranges[i].range_h >= data_rate_mbps) {
+ hsfreq = drv_data->hsfreq_ranges[i].cfg_bit;
+ break;
+ }
+ }
+ if (!hsfreq)
+ return -EINVAL;
+
+ priv->hsfreq = hsfreq;
+ priv->config = *config;
+ return 0;
+}
+
+static int rockchip_inno_csidphy_power_on(struct phy *phy)
+{
+ struct rockchip_inno_csidphy *priv = phy_get_drvdata(phy);
+ const struct dphy_drv_data *drv_data = priv->drv_data;
+ u64 data_rate_mbps = HZ_TO_MHZ(priv->config.hs_clk_rate);
+ u32 val;
+ int ret, i;
+
+ ret = clk_enable(priv->pclk);
+ if (ret < 0)
+ return ret;
+
+ ret = pm_runtime_resume_and_get(priv->dev);
+ if (ret < 0) {
+ clk_disable(priv->pclk);
+ return ret;
+ }
+
+ /* phy start */
+ if (drv_data->pwrctl_offset >= 0)
+ writel(CSIDPHY_CTRL_PWRCTL_UNDEFINED |
+ CSIDPHY_CTRL_PWRCTL_SYNCRST,
+ priv->phy_base + drv_data->pwrctl_offset);
+
+ /* set data lane num and enable clock lane */
+ val = FIELD_PREP(CSIDPHY_CTRL_LANE_ENABLE_MASK, GENMASK(priv->config.lanes - 1, 0)) |
+ FIELD_PREP(CSIDPHY_CTRL_LANE_ENABLE_CK, 1) |
+ FIELD_PREP(CSIDPHY_CTRL_LANE_ENABLE_UNDEFINED, 1);
+ writel(val, priv->phy_base + CSIDPHY_CTRL_LANE_ENABLE);
+
+ /* Reset dphy analog part */
+ if (drv_data->pwrctl_offset >= 0)
+ writel(CSIDPHY_CTRL_PWRCTL_UNDEFINED,
+ priv->phy_base + drv_data->pwrctl_offset);
+ usleep_range(500, 1000);
+
+ /* Reset dphy digital part */
+ writel(CSIDPHY_CTRL_DIG_RST_UNDEFINED,
+ priv->phy_base + CSIDPHY_CTRL_DIG_RST);
+ writel(CSIDPHY_CTRL_DIG_RST_UNDEFINED + CSIDPHY_CTRL_DIG_RST_RESET,
+ priv->phy_base + CSIDPHY_CTRL_DIG_RST);
+
+ /* not into receive mode/wait stopstate */
+ write_grf_reg(priv, GRF_DPHY_CSIPHY_FORCERXMODE, 0x0);
+
+ /* enable calibration */
+ if (data_rate_mbps > 1500 && drv_data->calib_offset >= 0) {
+ writel(CSIDPHY_CALIB_EN,
+ priv->phy_base + drv_data->calib_offset +
+ CSIDPHY_CLK_CALIB_EN);
+ for (i = 0; i < priv->config.lanes; i++)
+ writel(CSIDPHY_CALIB_EN,
+ priv->phy_base + drv_data->calib_offset +
+ CSIDPHY_LANE_CALIB_EN(i));
+ }
+
+ rockchip_inno_csidphy_ths_settle(priv, priv->hsfreq,
+ CSIDPHY_CLK_THS_SETTLE);
+ for (i = 0; i < priv->config.lanes; i++)
+ rockchip_inno_csidphy_ths_settle(priv, priv->hsfreq,
+ CSIDPHY_LANE_THS_SETTLE(i));
+
+ if (priv->clk_phase >= 0) {
+ val = readl(priv->phy_base + CSIDPHY_LANE_CLK_3_PHASE);
+ val &= ~CSIDPHY_CLK_PHASE_MASK;
+ val |= FIELD_PREP(CSIDPHY_CLK_PHASE_MASK, priv->clk_phase);
+ writel(val, priv->phy_base + CSIDPHY_LANE_CLK_3_PHASE);
+ }
+
+ write_grf_reg(priv, GRF_DPHY_CSIPHY_CLKLANE_EN, 0x1);
+ write_grf_reg(priv, GRF_DPHY_CSIPHY_DATALANE_EN,
+ GENMASK(priv->config.lanes - 1, 0));
+
+ return 0;
+}
+
+static int rockchip_inno_csidphy_power_off(struct phy *phy)
+{
+ struct rockchip_inno_csidphy *priv = phy_get_drvdata(phy);
+ const struct dphy_drv_data *drv_data = priv->drv_data;
+
+ /* disable all lanes */
+ writel(CSIDPHY_CTRL_LANE_ENABLE_UNDEFINED,
+ priv->phy_base + CSIDPHY_CTRL_LANE_ENABLE);
+
+ /* disable pll and ldo */
+ if (drv_data->pwrctl_offset >= 0)
+ writel(CSIDPHY_CTRL_PWRCTL_UNDEFINED |
+ CSIDPHY_CTRL_PWRCTL_LDO_PD |
+ CSIDPHY_CTRL_PWRCTL_PLL_PD,
+ priv->phy_base + drv_data->pwrctl_offset);
+ usleep_range(500, 1000);
+
+ pm_runtime_put(priv->dev);
+ clk_disable(priv->pclk);
+
+ return 0;
+}
+
+static int rockchip_inno_csidphy_init(struct phy *phy)
+{
+ struct rockchip_inno_csidphy *priv = phy_get_drvdata(phy);
+
+ return clk_prepare(priv->pclk);
+}
+
+static int rockchip_inno_csidphy_exit(struct phy *phy)
+{
+ struct rockchip_inno_csidphy *priv = phy_get_drvdata(phy);
+
+ clk_unprepare(priv->pclk);
+
+ return 0;
+}
+
+static const struct phy_ops rockchip_inno_csidphy_ops = {
+ .power_on = rockchip_inno_csidphy_power_on,
+ .power_off = rockchip_inno_csidphy_power_off,
+ .init = rockchip_inno_csidphy_init,
+ .exit = rockchip_inno_csidphy_exit,
+ .configure = rockchip_inno_csidphy_configure,
+ .owner = THIS_MODULE,
+};
+
+static const struct dphy_drv_data rk1808_mipidphy_drv_data = {
+ .pwrctl_offset = -1,
+ .ths_settle_offset = RK1808_CSIDPHY_CLK_WR_THS_SETTLE,
+ .calib_offset = RK1808_CSIDPHY_CLK_CALIB_EN,
+ .hsfreq_ranges = rk1808_mipidphy_hsfreq_ranges,
+ .num_hsfreq_ranges = ARRAY_SIZE(rk1808_mipidphy_hsfreq_ranges),
+ .grf_regs = rk1808_grf_dphy_regs,
+ .resets = rk3368_reset_names,
+ .resets_num = ARRAY_SIZE(rk3368_reset_names),
+};
+
+static const struct dphy_drv_data rk3326_mipidphy_drv_data = {
+ .pwrctl_offset = CSIDPHY_CTRL_PWRCTL,
+ .ths_settle_offset = RK3326_CSIDPHY_CLK_WR_THS_SETTLE,
+ .calib_offset = -1,
+ .hsfreq_ranges = rk3326_mipidphy_hsfreq_ranges,
+ .num_hsfreq_ranges = ARRAY_SIZE(rk3326_mipidphy_hsfreq_ranges),
+ .grf_regs = rk3326_grf_dphy_regs,
+ .resets = rk3368_reset_names,
+ .resets_num = ARRAY_SIZE(rk3368_reset_names),
+};
+
+static const struct dphy_drv_data rk3368_mipidphy_drv_data = {
+ .pwrctl_offset = CSIDPHY_CTRL_PWRCTL,
+ .ths_settle_offset = RK3368_CSIDPHY_CLK_WR_THS_SETTLE,
+ .calib_offset = -1,
+ .hsfreq_ranges = rk3368_mipidphy_hsfreq_ranges,
+ .num_hsfreq_ranges = ARRAY_SIZE(rk3368_mipidphy_hsfreq_ranges),
+ .grf_regs = rk3368_grf_dphy_regs,
+ .resets = rk3368_reset_names,
+ .resets_num = ARRAY_SIZE(rk3368_reset_names),
+};
+
+static const struct dphy_drv_data rk3568_mipidphy_drv_data = {
+ .pwrctl_offset = -1,
+ .ths_settle_offset = RK3568_CSIDPHY_CLK_WR_THS_SETTLE,
+ .calib_offset = RK3568_CSIDPHY_CLK_CALIB_EN,
+ .hsfreq_ranges = rk1808_mipidphy_hsfreq_ranges,
+ .num_hsfreq_ranges = ARRAY_SIZE(rk1808_mipidphy_hsfreq_ranges),
+ .grf_regs = rk3568_grf_dphy_regs,
+ .resets = rk3368_reset_names,
+ .resets_num = ARRAY_SIZE(rk3368_reset_names),
+};
+
+static const struct dphy_drv_data rk3588_mipidphy_drv_data = {
+ .pwrctl_offset = -1,
+ .ths_settle_offset = RK3568_CSIDPHY_CLK_WR_THS_SETTLE,
+ .calib_offset = RK3568_CSIDPHY_CLK_CALIB_EN,
+ .hsfreq_ranges = rk1808_mipidphy_hsfreq_ranges,
+ .num_hsfreq_ranges = ARRAY_SIZE(rk1808_mipidphy_hsfreq_ranges),
+ .grf_regs = rk3588_grf_dphy_regs,
+ .resets = rk3588_reset_names,
+ .resets_num = ARRAY_SIZE(rk3588_reset_names),
+};
+
+static const struct of_device_id rockchip_inno_csidphy_match_id[] = {
+ {
+ .compatible = "rockchip,px30-csi-dphy",
+ .data = &rk3326_mipidphy_drv_data,
+ },
+ {
+ .compatible = "rockchip,rk1808-csi-dphy",
+ .data = &rk1808_mipidphy_drv_data,
+ },
+ {
+ .compatible = "rockchip,rk3326-csi-dphy",
+ .data = &rk3326_mipidphy_drv_data,
+ },
+ {
+ .compatible = "rockchip,rk3368-csi-dphy",
+ .data = &rk3368_mipidphy_drv_data,
+ },
+ {
+ .compatible = "rockchip,rk3568-csi-dphy",
+ .data = &rk3568_mipidphy_drv_data,
+ },
+ {
+ .compatible = "rockchip,rk3588-csi-dphy",
+ .data = &rk3588_mipidphy_drv_data,
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, rockchip_inno_csidphy_match_id);
+
+static int rockchip_inno_csidphy_probe(struct platform_device *pdev)
+{
+ struct rockchip_inno_csidphy *priv;
+ struct device *dev = &pdev->dev;
+ struct phy_provider *phy_provider;
+ struct phy *phy;
+ u32 phase;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = dev;
+ platform_set_drvdata(pdev, priv);
+
+ priv->drv_data = of_device_get_match_data(dev);
+ if (!priv->drv_data) {
+ dev_err(dev, "Can't find device data\n");
+ return -ENODEV;
+ }
+
+ priv->clk_phase = -1;
+ if (device_property_read_u32(dev, "rockchip,clk-lane-phase",
+ &phase) == 0) {
+ if (phase > 7) {
+ dev_warn(dev,
+ "invalid rockchip,clk-lane-phase %u, using default %u\n",
+ phase, CSIDPHY_CLK_PHASE_DEFAULT);
+ phase = CSIDPHY_CLK_PHASE_DEFAULT;
+ }
+ priv->clk_phase = phase;
+ }
+
+ priv->grf = syscon_regmap_lookup_by_phandle(dev->of_node,
+ "rockchip,grf");
+ if (IS_ERR(priv->grf)) {
+ dev_err(dev, "Can't find GRF syscon\n");
+ return PTR_ERR(priv->grf);
+ }
+
+ priv->phy_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->phy_base))
+ return PTR_ERR(priv->phy_base);
+
+ priv->pclk = devm_clk_get(dev, "pclk");
+ if (IS_ERR(priv->pclk)) {
+ dev_err(dev, "failed to get pclk\n");
+ return PTR_ERR(priv->pclk);
+ }
+
+ if (priv->drv_data->resets_num > RESETS_MAX) {
+ dev_err(dev, "invalid number of resets\n");
+ return -EINVAL;
+ }
+ priv->resets_num = priv->drv_data->resets_num;
+ for (unsigned int i = 0; i < priv->resets_num; i++)
+ priv->resets[i].id = priv->drv_data->resets[i];
+ ret = devm_reset_control_bulk_get_exclusive(dev, priv->resets_num,
+ priv->resets);
+ if (ret) {
+ dev_err(dev, "failed to get system reset control\n");
+ return ret;
+ }
+
+ phy = devm_phy_create(dev, NULL, &rockchip_inno_csidphy_ops);
+ if (IS_ERR(phy)) {
+ dev_err(dev, "failed to create phy\n");
+ return PTR_ERR(phy);
+ }
+
+ phy_set_drvdata(phy, priv);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ if (IS_ERR(phy_provider)) {
+ dev_err(dev, "failed to register phy provider\n");
+ return PTR_ERR(phy_provider);
+ }
+
+ pm_runtime_enable(dev);
+
+ return 0;
+}
+
+static void rockchip_inno_csidphy_remove(struct platform_device *pdev)
+{
+ struct rockchip_inno_csidphy *priv = platform_get_drvdata(pdev);
+
+ pm_runtime_disable(priv->dev);
+}
+
+static struct platform_driver rockchip_inno_csidphy_driver = {
+ .driver = {
+ .name = "rockchip-inno-csidphy",
+ .of_match_table = rockchip_inno_csidphy_match_id,
+ },
+ .probe = rockchip_inno_csidphy_probe,
+ .remove = rockchip_inno_csidphy_remove,
+};
+
+module_platform_driver(rockchip_inno_csidphy_driver);
+MODULE_AUTHOR("Heiko Stuebner <heiko.stuebner@theobroma-systems.com>");
+MODULE_DESCRIPTION("Rockchip MIPI Innosilicon CSI-DPHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/rockchip/phy-rockchip-inno-dsidphy.c b/drivers/phy/rockchip/phy-rockchip-inno-dsidphy.c
new file mode 100644
index 000000000..30d5e5ddf
--- /dev/null
+++ b/drivers/phy/rockchip/phy-rockchip-inno-dsidphy.c
@@ -0,0 +1,878 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018 Rockchip Electronics Co. Ltd.
+ *
+ * Author: Wyon Bi <bivvy.bi@rock-chips.com>
+ */
+
+#include <linux/bits.h>
+#include <linux/kernel.h>
+#include <linux/clk.h>
+#include <linux/iopoll.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+#include <linux/time64.h>
+
+#include <linux/phy/phy.h>
+#include <linux/phy/phy-mipi-dphy.h>
+
+#define UPDATE(x, h, l) (((x) << (l)) & GENMASK((h), (l)))
+
+/*
+ * The offset address[7:0] is distributed two parts, one from the bit7 to bit5
+ * is the first address, the other from the bit4 to bit0 is the second address.
+ * when you configure the registers, you must set both of them. The Clock Lane
+ * and Data Lane use the same registers with the same second address, but the
+ * first address is different.
+ */
+#define FIRST_ADDRESS(x) (((x) & 0x7) << 5)
+#define SECOND_ADDRESS(x) (((x) & 0x1f) << 0)
+#define PHY_REG(first, second) (FIRST_ADDRESS(first) | \
+ SECOND_ADDRESS(second))
+
+/* Analog Register Part: reg00 */
+#define BANDGAP_POWER_MASK BIT(7)
+#define BANDGAP_POWER_DOWN BIT(7)
+#define BANDGAP_POWER_ON 0
+#define LANE_EN_MASK GENMASK(6, 2)
+#define LANE_EN_CK BIT(6)
+#define LANE_EN_3 BIT(5)
+#define LANE_EN_2 BIT(4)
+#define LANE_EN_1 BIT(3)
+#define LANE_EN_0 BIT(2)
+#define POWER_WORK_MASK GENMASK(1, 0)
+#define POWER_WORK_ENABLE UPDATE(1, 1, 0)
+#define POWER_WORK_DISABLE UPDATE(2, 1, 0)
+/* Analog Register Part: reg01 */
+#define REG_SYNCRST_MASK BIT(2)
+#define REG_SYNCRST_RESET BIT(2)
+#define REG_SYNCRST_NORMAL 0
+#define REG_LDOPD_MASK BIT(1)
+#define REG_LDOPD_POWER_DOWN BIT(1)
+#define REG_LDOPD_POWER_ON 0
+#define REG_PLLPD_MASK BIT(0)
+#define REG_PLLPD_POWER_DOWN BIT(0)
+#define REG_PLLPD_POWER_ON 0
+/* Analog Register Part: reg03 */
+#define REG_FBDIV_HI_MASK BIT(5)
+#define REG_FBDIV_HI(x) UPDATE((x >> 8), 5, 5)
+#define REG_PREDIV_MASK GENMASK(4, 0)
+#define REG_PREDIV(x) UPDATE(x, 4, 0)
+/* Analog Register Part: reg04 */
+#define REG_FBDIV_LO_MASK GENMASK(7, 0)
+#define REG_FBDIV_LO(x) UPDATE(x, 7, 0)
+/* Analog Register Part: reg05 */
+#define SAMPLE_CLOCK_PHASE_MASK GENMASK(6, 4)
+#define SAMPLE_CLOCK_PHASE(x) UPDATE(x, 6, 4)
+#define CLOCK_LANE_SKEW_PHASE_MASK GENMASK(2, 0)
+#define CLOCK_LANE_SKEW_PHASE(x) UPDATE(x, 2, 0)
+/* Analog Register Part: reg06 */
+#define DATA_LANE_3_SKEW_PHASE_MASK GENMASK(6, 4)
+#define DATA_LANE_3_SKEW_PHASE(x) UPDATE(x, 6, 4)
+#define DATA_LANE_2_SKEW_PHASE_MASK GENMASK(2, 0)
+#define DATA_LANE_2_SKEW_PHASE(x) UPDATE(x, 2, 0)
+/* Analog Register Part: reg07 */
+#define DATA_LANE_1_SKEW_PHASE_MASK GENMASK(6, 4)
+#define DATA_LANE_1_SKEW_PHASE(x) UPDATE(x, 6, 4)
+#define DATA_LANE_0_SKEW_PHASE_MASK GENMASK(2, 0)
+#define DATA_LANE_0_SKEW_PHASE(x) UPDATE(x, 2, 0)
+/* Analog Register Part: reg08 */
+#define PLL_POST_DIV_ENABLE_MASK BIT(5)
+#define PLL_POST_DIV_ENABLE BIT(5)
+#define SAMPLE_CLOCK_DIRECTION_MASK BIT(4)
+#define SAMPLE_CLOCK_DIRECTION_REVERSE BIT(4)
+#define SAMPLE_CLOCK_DIRECTION_FORWARD 0
+#define LOWFRE_EN_MASK BIT(5)
+#define PLL_OUTPUT_FREQUENCY_DIV_BY_1 0
+#define PLL_OUTPUT_FREQUENCY_DIV_BY_2 1
+/* Analog Register Part: reg0b */
+#define CLOCK_LANE_VOD_RANGE_SET_MASK GENMASK(3, 0)
+#define CLOCK_LANE_VOD_RANGE_SET(x) UPDATE(x, 3, 0)
+#define VOD_MIN_RANGE 0x1
+#define VOD_MID_RANGE 0x3
+#define VOD_BIG_RANGE 0x7
+#define VOD_MAX_RANGE 0xf
+/* Analog Register Part: reg18 */
+#define LANE0_PRE_EMPHASIS_ENABLE_MASK BIT(6)
+#define LANE0_PRE_EMPHASIS_ENABLE BIT(6)
+#define LANE0_PRE_EMPHASIS_DISABLE 0
+#define LANE1_PRE_EMPHASIS_ENABLE_MASK BIT(5)
+#define LANE1_PRE_EMPHASIS_ENABLE BIT(5)
+#define LANE1_PRE_EMPHASIS_DISABLE 0
+/* Analog Register Part: reg19 */
+#define PRE_EMPHASIS_RANGE_SET_MASK GENMASK(7, 6)
+#define PRE_EMPHASIS_RANGE_SET(x) UPDATE(x, 7, 6)
+/* Analog Register Part: reg1E */
+#define PLL_MODE_SEL_MASK GENMASK(6, 5)
+#define PLL_MODE_SEL_LVDS_MODE 0
+#define PLL_MODE_SEL_MIPI_MODE BIT(5)
+/* Analog Register Part: reg20 */
+#define LANE0_PRE_EMPHASIS_RANGE_SET_MASK GENMASK(7, 6)
+#define LANE0_PRE_EMPHASIS_RANGE_SET(x) UPDATE(x, 7, 6)
+/* Analog Register Part: reg21 */
+#define LANE1_PRE_EMPHASIS_RANGE_SET_MASK GENMASK(7, 6)
+#define LANE1_PRE_EMPHASIS_RANGE_SET(x) UPDATE(x, 7, 6)
+#define PRE_EMPHASIS_MIN_RANGE 0x0
+#define PRE_EMPHASIS_MID_RANGE 0x1
+#define PRE_EMPHASIS_MAX_RANGE 0x2
+#define PRE_EMPHASIS_RESERVED_RANGE 0x3
+/* Digital Register Part: reg00 */
+#define REG_DIG_RSTN_MASK BIT(0)
+#define REG_DIG_RSTN_NORMAL BIT(0)
+#define REG_DIG_RSTN_RESET 0
+/* Digital Register Part: reg01 */
+#define INVERT_TXCLKESC_MASK BIT(1)
+#define INVERT_TXCLKESC_ENABLE BIT(1)
+#define INVERT_TXCLKESC_DISABLE 0
+#define INVERT_TXBYTECLKHS_MASK BIT(0)
+#define INVERT_TXBYTECLKHS_ENABLE BIT(0)
+#define INVERT_TXBYTECLKHS_DISABLE 0
+/* Clock/Data0/Data1/Data2/Data3 Lane Register Part: reg05 */
+#define T_LPX_CNT_MASK GENMASK(5, 0)
+#define T_LPX_CNT(x) UPDATE(x, 5, 0)
+/* Clock/Data0/Data1/Data2/Data3 Lane Register Part: reg06 */
+#define T_HS_ZERO_CNT_HI_MASK BIT(7)
+#define T_HS_ZERO_CNT_HI(x) UPDATE(x, 7, 7)
+#define T_HS_PREPARE_CNT_MASK GENMASK(6, 0)
+#define T_HS_PREPARE_CNT(x) UPDATE(x, 6, 0)
+/* Clock/Data0/Data1/Data2/Data3 Lane Register Part: reg07 */
+#define T_HS_ZERO_CNT_LO_MASK GENMASK(5, 0)
+#define T_HS_ZERO_CNT_LO(x) UPDATE(x, 5, 0)
+/* Clock/Data0/Data1/Data2/Data3 Lane Register Part: reg08 */
+#define T_HS_TRAIL_CNT_MASK GENMASK(6, 0)
+#define T_HS_TRAIL_CNT(x) UPDATE(x, 6, 0)
+/* Clock/Data0/Data1/Data2/Data3 Lane Register Part: reg09 */
+#define T_HS_EXIT_CNT_LO_MASK GENMASK(4, 0)
+#define T_HS_EXIT_CNT_LO(x) UPDATE(x, 4, 0)
+/* Clock/Data0/Data1/Data2/Data3 Lane Register Part: reg0a */
+#define T_CLK_POST_CNT_LO_MASK GENMASK(3, 0)
+#define T_CLK_POST_CNT_LO(x) UPDATE(x, 3, 0)
+/* Clock/Data0/Data1/Data2/Data3 Lane Register Part: reg0c */
+#define LPDT_TX_PPI_SYNC_MASK BIT(2)
+#define LPDT_TX_PPI_SYNC_ENABLE BIT(2)
+#define LPDT_TX_PPI_SYNC_DISABLE 0
+#define T_WAKEUP_CNT_HI_MASK GENMASK(1, 0)
+#define T_WAKEUP_CNT_HI(x) UPDATE(x, 1, 0)
+/* Clock/Data0/Data1/Data2/Data3 Lane Register Part: reg0d */
+#define T_WAKEUP_CNT_LO_MASK GENMASK(7, 0)
+#define T_WAKEUP_CNT_LO(x) UPDATE(x, 7, 0)
+/* Clock/Data0/Data1/Data2/Data3 Lane Register Part: reg0e */
+#define T_CLK_PRE_CNT_MASK GENMASK(3, 0)
+#define T_CLK_PRE_CNT(x) UPDATE(x, 3, 0)
+/* Clock/Data0/Data1/Data2/Data3 Lane Register Part: reg10 */
+#define T_CLK_POST_CNT_HI_MASK GENMASK(7, 6)
+#define T_CLK_POST_CNT_HI(x) UPDATE(x, 7, 6)
+#define T_TA_GO_CNT_MASK GENMASK(5, 0)
+#define T_TA_GO_CNT(x) UPDATE(x, 5, 0)
+/* Clock/Data0/Data1/Data2/Data3 Lane Register Part: reg11 */
+#define T_HS_EXIT_CNT_HI_MASK BIT(6)
+#define T_HS_EXIT_CNT_HI(x) UPDATE(x, 6, 6)
+#define T_TA_SURE_CNT_MASK GENMASK(5, 0)
+#define T_TA_SURE_CNT(x) UPDATE(x, 5, 0)
+/* Clock/Data0/Data1/Data2/Data3 Lane Register Part: reg12 */
+#define T_TA_WAIT_CNT_MASK GENMASK(5, 0)
+#define T_TA_WAIT_CNT(x) UPDATE(x, 5, 0)
+/* LVDS Register Part: reg00 */
+#define LVDS_DIGITAL_INTERNAL_RESET_MASK BIT(2)
+#define LVDS_DIGITAL_INTERNAL_RESET_DISABLE BIT(2)
+#define LVDS_DIGITAL_INTERNAL_RESET_ENABLE 0
+/* LVDS Register Part: reg01 */
+#define LVDS_DIGITAL_INTERNAL_ENABLE_MASK BIT(7)
+#define LVDS_DIGITAL_INTERNAL_ENABLE BIT(7)
+#define LVDS_DIGITAL_INTERNAL_DISABLE 0
+/* LVDS Register Part: reg03 */
+#define MODE_ENABLE_MASK GENMASK(2, 0)
+#define TTL_MODE_ENABLE BIT(2)
+#define LVDS_MODE_ENABLE BIT(1)
+#define MIPI_MODE_ENABLE BIT(0)
+/* LVDS Register Part: reg0b */
+#define LVDS_LANE_EN_MASK GENMASK(7, 3)
+#define LVDS_DATA_LANE0_EN BIT(7)
+#define LVDS_DATA_LANE1_EN BIT(6)
+#define LVDS_DATA_LANE2_EN BIT(5)
+#define LVDS_DATA_LANE3_EN BIT(4)
+#define LVDS_CLK_LANE_EN BIT(3)
+#define LVDS_PLL_POWER_MASK BIT(2)
+#define LVDS_PLL_POWER_OFF BIT(2)
+#define LVDS_PLL_POWER_ON 0
+#define LVDS_BANDGAP_POWER_MASK BIT(0)
+#define LVDS_BANDGAP_POWER_DOWN BIT(0)
+#define LVDS_BANDGAP_POWER_ON 0
+
+#define DSI_PHY_RSTZ 0xa0
+#define PHY_ENABLECLK BIT(2)
+#define DSI_PHY_STATUS 0xb0
+#define PHY_LOCK BIT(0)
+
+enum phy_max_rate {
+ MAX_1GHZ,
+ MAX_1_5GHZ,
+ MAX_2_5GHZ,
+};
+
+struct inno_video_phy_plat_data {
+ const struct inno_mipi_dphy_timing *inno_mipi_dphy_timing_table;
+ const unsigned int num_timings;
+ enum phy_max_rate max_rate;
+ unsigned int max_lanes;
+};
+
+struct inno_dsidphy {
+ struct device *dev;
+ struct clk *ref_clk;
+ struct clk *pclk_phy;
+ struct clk *pclk_host;
+ const struct inno_video_phy_plat_data *pdata;
+ void __iomem *phy_base;
+ void __iomem *host_base;
+ struct reset_control *rst;
+ enum phy_mode mode;
+ struct phy_configure_opts_mipi_dphy dphy_cfg;
+
+ struct clk *pll_clk;
+ struct {
+ struct clk_hw hw;
+ u8 prediv;
+ u16 fbdiv;
+ unsigned long rate;
+ } pll;
+};
+
+enum {
+ REGISTER_PART_ANALOG,
+ REGISTER_PART_DIGITAL,
+ REGISTER_PART_CLOCK_LANE,
+ REGISTER_PART_DATA0_LANE,
+ REGISTER_PART_DATA1_LANE,
+ REGISTER_PART_DATA2_LANE,
+ REGISTER_PART_DATA3_LANE,
+ REGISTER_PART_LVDS,
+};
+
+struct inno_mipi_dphy_timing {
+ unsigned long rate;
+ u8 lpx;
+ u8 hs_prepare;
+ u8 clk_lane_hs_zero;
+ u8 data_lane_hs_zero;
+ u8 hs_trail;
+};
+
+static const
+struct inno_mipi_dphy_timing inno_mipi_dphy_timing_table_max_1ghz[] = {
+ { 110000000, 0x0, 0x20, 0x16, 0x02, 0x22},
+ { 150000000, 0x0, 0x06, 0x16, 0x03, 0x45},
+ { 200000000, 0x0, 0x18, 0x17, 0x04, 0x0b},
+ { 250000000, 0x0, 0x05, 0x17, 0x05, 0x16},
+ { 300000000, 0x0, 0x51, 0x18, 0x06, 0x2c},
+ { 400000000, 0x0, 0x64, 0x19, 0x07, 0x33},
+ { 500000000, 0x0, 0x20, 0x1b, 0x07, 0x4e},
+ { 600000000, 0x0, 0x6a, 0x1d, 0x08, 0x3a},
+ { 700000000, 0x0, 0x3e, 0x1e, 0x08, 0x6a},
+ { 800000000, 0x0, 0x21, 0x1f, 0x09, 0x29},
+ {1000000000, 0x0, 0x09, 0x20, 0x09, 0x27},
+};
+
+static const
+struct inno_mipi_dphy_timing inno_mipi_dphy_timing_table_max_1_5ghz[] = {
+ { 110, 0x02, 0x7f, 0x16, 0x02, 0x02},
+ { 150, 0x02, 0x7f, 0x16, 0x03, 0x02},
+ { 200, 0x02, 0x7f, 0x17, 0x04, 0x02},
+ { 250, 0x02, 0x7f, 0x17, 0x05, 0x04},
+ { 300, 0x02, 0x7f, 0x18, 0x06, 0x04},
+ { 400, 0x03, 0x7e, 0x19, 0x07, 0x04},
+ { 500, 0x03, 0x7c, 0x1b, 0x07, 0x08},
+ { 600, 0x03, 0x70, 0x1d, 0x08, 0x10},
+ { 700, 0x05, 0x40, 0x1e, 0x08, 0x30},
+ { 800, 0x05, 0x02, 0x1f, 0x09, 0x30},
+ {1000, 0x05, 0x08, 0x20, 0x09, 0x30},
+ {1200, 0x06, 0x03, 0x32, 0x14, 0x0f},
+ {1400, 0x09, 0x03, 0x32, 0x14, 0x0f},
+ {1500, 0x0d, 0x42, 0x36, 0x0e, 0x0f},
+};
+
+static const
+struct inno_mipi_dphy_timing inno_mipi_dphy_timing_table_max_2_5ghz[] = {
+ { 110000000, 0x02, 0x7f, 0x16, 0x02, 0x02},
+ { 150000000, 0x02, 0x7f, 0x16, 0x03, 0x02},
+ { 200000000, 0x02, 0x7f, 0x17, 0x04, 0x02},
+ { 250000000, 0x02, 0x7f, 0x17, 0x05, 0x04},
+ { 300000000, 0x02, 0x7f, 0x18, 0x06, 0x04},
+ { 400000000, 0x03, 0x7e, 0x19, 0x07, 0x04},
+ { 500000000, 0x03, 0x7c, 0x1b, 0x07, 0x08},
+ { 600000000, 0x03, 0x70, 0x1d, 0x08, 0x10},
+ { 700000000, 0x05, 0x40, 0x1e, 0x08, 0x30},
+ { 800000000, 0x05, 0x02, 0x1f, 0x09, 0x30},
+ {1000000000, 0x05, 0x08, 0x20, 0x09, 0x30},
+ {1200000000, 0x06, 0x03, 0x32, 0x14, 0x0f},
+ {1400000000, 0x09, 0x03, 0x32, 0x14, 0x0f},
+ {1600000000, 0x0d, 0x42, 0x36, 0x0e, 0x0f},
+ {1800000000, 0x0e, 0x47, 0x7a, 0x0e, 0x0f},
+ {2000000000, 0x11, 0x64, 0x7a, 0x0e, 0x0b},
+ {2200000000, 0x13, 0x64, 0x7e, 0x15, 0x0b},
+ {2400000000, 0x13, 0x33, 0x7f, 0x15, 0x6a},
+ {2500000000, 0x15, 0x54, 0x7f, 0x15, 0x6a},
+};
+
+static void phy_update_bits(struct inno_dsidphy *inno,
+ u8 first, u8 second, u8 mask, u8 val)
+{
+ u32 reg = PHY_REG(first, second) << 2;
+ unsigned int tmp, orig;
+
+ orig = readl(inno->phy_base + reg);
+ tmp = orig & ~mask;
+ tmp |= val & mask;
+ writel(tmp, inno->phy_base + reg);
+}
+
+static unsigned long inno_dsidphy_pll_calc_rate(struct inno_dsidphy *inno,
+ unsigned long rate)
+{
+ unsigned long prate = clk_get_rate(inno->ref_clk);
+ unsigned long best_freq = 0;
+ unsigned long fref, fout;
+ u8 min_prediv, max_prediv;
+ u8 _prediv, best_prediv = 1;
+ u16 _fbdiv, best_fbdiv = 1;
+ u32 min_delta = UINT_MAX;
+
+ /*
+ * The PLL output frequency can be calculated using a simple formula:
+ * PLL_Output_Frequency = (FREF / PREDIV * FBDIV) / 2
+ * PLL_Output_Frequency: it is equal to DDR-Clock-Frequency * 2
+ */
+ fref = prate / 2;
+ if (rate > 1000000000UL)
+ fout = 1000000000UL;
+ else
+ fout = rate;
+
+ /* 5Mhz < Fref / prediv < 40MHz */
+ min_prediv = DIV_ROUND_UP(fref, 40000000);
+ max_prediv = fref / 5000000;
+
+ for (_prediv = min_prediv; _prediv <= max_prediv; _prediv++) {
+ u64 tmp;
+ u32 delta;
+
+ tmp = (u64)fout * _prediv;
+ do_div(tmp, fref);
+ _fbdiv = tmp;
+
+ /*
+ * The possible settings of feedback divider are
+ * 12, 13, 14, 16, ~ 511
+ */
+ if (_fbdiv == 15)
+ continue;
+
+ if (_fbdiv < 12 || _fbdiv > 511)
+ continue;
+
+ tmp = (u64)_fbdiv * fref;
+ do_div(tmp, _prediv);
+
+ delta = abs(fout - tmp);
+ if (!delta) {
+ best_prediv = _prediv;
+ best_fbdiv = _fbdiv;
+ best_freq = tmp;
+ break;
+ } else if (delta < min_delta) {
+ best_prediv = _prediv;
+ best_fbdiv = _fbdiv;
+ best_freq = tmp;
+ min_delta = delta;
+ }
+ }
+
+ if (best_freq) {
+ inno->pll.prediv = best_prediv;
+ inno->pll.fbdiv = best_fbdiv;
+ inno->pll.rate = best_freq;
+ }
+
+ return best_freq;
+}
+
+static void inno_dsidphy_mipi_mode_enable(struct inno_dsidphy *inno)
+{
+ struct phy_configure_opts_mipi_dphy *cfg = &inno->dphy_cfg;
+ const struct inno_mipi_dphy_timing *timings;
+ u32 t_txbyteclkhs, t_txclkesc;
+ u32 txbyteclkhs, txclkesc, esc_clk_div;
+ u32 hs_exit, clk_post, clk_pre, wakeup, lpx, ta_go, ta_sure, ta_wait;
+ u32 hs_prepare, hs_trail, hs_zero, clk_lane_hs_zero, data_lane_hs_zero;
+ unsigned int i;
+ u32 val;
+
+ timings = inno->pdata->inno_mipi_dphy_timing_table;
+
+ inno_dsidphy_pll_calc_rate(inno, cfg->hs_clk_rate);
+
+ /* Select MIPI mode */
+ phy_update_bits(inno, REGISTER_PART_LVDS, 0x03,
+ MODE_ENABLE_MASK, MIPI_MODE_ENABLE);
+ /* Configure PLL */
+ phy_update_bits(inno, REGISTER_PART_ANALOG, 0x03,
+ REG_PREDIV_MASK, REG_PREDIV(inno->pll.prediv));
+ phy_update_bits(inno, REGISTER_PART_ANALOG, 0x03,
+ REG_FBDIV_HI_MASK, REG_FBDIV_HI(inno->pll.fbdiv));
+ phy_update_bits(inno, REGISTER_PART_ANALOG, 0x04,
+ REG_FBDIV_LO_MASK, REG_FBDIV_LO(inno->pll.fbdiv));
+ if (inno->pdata->max_rate == MAX_2_5GHZ) {
+ phy_update_bits(inno, REGISTER_PART_ANALOG, 0x08,
+ PLL_POST_DIV_ENABLE_MASK, PLL_POST_DIV_ENABLE);
+ phy_update_bits(inno, REGISTER_PART_ANALOG, 0x0b,
+ CLOCK_LANE_VOD_RANGE_SET_MASK,
+ CLOCK_LANE_VOD_RANGE_SET(VOD_MAX_RANGE));
+ } else if (inno->pdata->max_rate == MAX_1_5GHZ) {
+ phy_update_bits(inno, REGISTER_PART_ANALOG, 0x18,
+ LANE0_PRE_EMPHASIS_ENABLE_MASK, LANE0_PRE_EMPHASIS_ENABLE);
+ phy_update_bits(inno, REGISTER_PART_ANALOG, 0x18,
+ LANE1_PRE_EMPHASIS_ENABLE_MASK, LANE1_PRE_EMPHASIS_ENABLE);
+ phy_update_bits(inno, REGISTER_PART_ANALOG, 0x19,
+ PRE_EMPHASIS_RANGE_SET_MASK,
+ PRE_EMPHASIS_RANGE_SET(PRE_EMPHASIS_MID_RANGE));
+ phy_update_bits(inno, REGISTER_PART_ANALOG, 0x1a,
+ LANE0_PRE_EMPHASIS_RANGE_SET_MASK,
+ LANE0_PRE_EMPHASIS_RANGE_SET(PRE_EMPHASIS_MID_RANGE));
+ phy_update_bits(inno, REGISTER_PART_ANALOG, 0x1b,
+ LANE1_PRE_EMPHASIS_RANGE_SET_MASK,
+ LANE1_PRE_EMPHASIS_RANGE_SET(PRE_EMPHASIS_MID_RANGE));
+ phy_update_bits(inno, REGISTER_PART_ANALOG, 0x0b,
+ CLOCK_LANE_VOD_RANGE_SET_MASK,
+ CLOCK_LANE_VOD_RANGE_SET(VOD_MAX_RANGE));
+ }
+ /* Enable PLL and LDO */
+ phy_update_bits(inno, REGISTER_PART_ANALOG, 0x01,
+ REG_LDOPD_MASK | REG_PLLPD_MASK,
+ REG_LDOPD_POWER_ON | REG_PLLPD_POWER_ON);
+ /* Reset analog */
+ phy_update_bits(inno, REGISTER_PART_ANALOG, 0x01,
+ REG_SYNCRST_MASK, REG_SYNCRST_RESET);
+ udelay(1);
+ phy_update_bits(inno, REGISTER_PART_ANALOG, 0x01,
+ REG_SYNCRST_MASK, REG_SYNCRST_NORMAL);
+ /* Reset digital */
+ phy_update_bits(inno, REGISTER_PART_DIGITAL, 0x00,
+ REG_DIG_RSTN_MASK, REG_DIG_RSTN_RESET);
+ udelay(1);
+ phy_update_bits(inno, REGISTER_PART_DIGITAL, 0x00,
+ REG_DIG_RSTN_MASK, REG_DIG_RSTN_NORMAL);
+
+ txbyteclkhs = inno->pll.rate / 8;
+ t_txbyteclkhs = div_u64(PSEC_PER_SEC, txbyteclkhs);
+
+ esc_clk_div = DIV_ROUND_UP(txbyteclkhs, 20000000);
+ txclkesc = txbyteclkhs / esc_clk_div;
+ t_txclkesc = div_u64(PSEC_PER_SEC, txclkesc);
+
+ /*
+ * The value of counter for HS Ths-exit
+ * Ths-exit = Tpin_txbyteclkhs * value
+ */
+ hs_exit = DIV_ROUND_UP(cfg->hs_exit, t_txbyteclkhs);
+ /*
+ * The value of counter for HS Tclk-post
+ * Tclk-post = Tpin_txbyteclkhs * value
+ */
+ clk_post = DIV_ROUND_UP(cfg->clk_post, t_txbyteclkhs);
+ /*
+ * The value of counter for HS Tclk-pre
+ * Tclk-pre = Tpin_txbyteclkhs * value
+ */
+ clk_pre = DIV_ROUND_UP(cfg->clk_pre, BITS_PER_BYTE);
+
+ /*
+ * The value of counter for HS Tta-go
+ * Tta-go for turnaround
+ * Tta-go = Ttxclkesc * value
+ */
+ ta_go = DIV_ROUND_UP(cfg->ta_go, t_txclkesc);
+ /*
+ * The value of counter for HS Tta-sure
+ * Tta-sure for turnaround
+ * Tta-sure = Ttxclkesc * value
+ */
+ ta_sure = DIV_ROUND_UP(cfg->ta_sure, t_txclkesc);
+ /*
+ * The value of counter for HS Tta-wait
+ * Tta-wait for turnaround
+ * Tta-wait = Ttxclkesc * value
+ */
+ ta_wait = DIV_ROUND_UP(cfg->ta_get, t_txclkesc);
+
+ for (i = 0; i < inno->pdata->num_timings; i++)
+ if (inno->pll.rate <= timings[i].rate)
+ break;
+
+ if (i == inno->pdata->num_timings)
+ --i;
+
+ /*
+ * The value of counter for HS Tlpx Time
+ * Tlpx = Tpin_txbyteclkhs * (2 + value)
+ */
+ if (inno->pdata->max_rate == MAX_1GHZ) {
+ lpx = DIV_ROUND_UP(cfg->lpx, t_txbyteclkhs);
+ if (lpx >= 2)
+ lpx -= 2;
+ } else
+ lpx = timings[i].lpx;
+
+ hs_prepare = timings[i].hs_prepare;
+ hs_trail = timings[i].hs_trail;
+ clk_lane_hs_zero = timings[i].clk_lane_hs_zero;
+ data_lane_hs_zero = timings[i].data_lane_hs_zero;
+ wakeup = 0x3ff;
+
+ for (i = REGISTER_PART_CLOCK_LANE; i <= REGISTER_PART_DATA3_LANE; i++) {
+ if (i == REGISTER_PART_CLOCK_LANE)
+ hs_zero = clk_lane_hs_zero;
+ else
+ hs_zero = data_lane_hs_zero;
+
+ phy_update_bits(inno, i, 0x05, T_LPX_CNT_MASK,
+ T_LPX_CNT(lpx));
+ phy_update_bits(inno, i, 0x06, T_HS_PREPARE_CNT_MASK,
+ T_HS_PREPARE_CNT(hs_prepare));
+ if (inno->pdata->max_rate == MAX_2_5GHZ)
+ phy_update_bits(inno, i, 0x06, T_HS_ZERO_CNT_HI_MASK,
+ T_HS_ZERO_CNT_HI(hs_zero >> 6));
+ phy_update_bits(inno, i, 0x07, T_HS_ZERO_CNT_LO_MASK,
+ T_HS_ZERO_CNT_LO(hs_zero));
+ phy_update_bits(inno, i, 0x08, T_HS_TRAIL_CNT_MASK,
+ T_HS_TRAIL_CNT(hs_trail));
+ if (inno->pdata->max_rate == MAX_2_5GHZ)
+ phy_update_bits(inno, i, 0x11, T_HS_EXIT_CNT_HI_MASK,
+ T_HS_EXIT_CNT_HI(hs_exit >> 5));
+ phy_update_bits(inno, i, 0x09, T_HS_EXIT_CNT_LO_MASK,
+ T_HS_EXIT_CNT_LO(hs_exit));
+ if (inno->pdata->max_rate == MAX_2_5GHZ)
+ phy_update_bits(inno, i, 0x10, T_CLK_POST_CNT_HI_MASK,
+ T_CLK_POST_CNT_HI(clk_post >> 4));
+ phy_update_bits(inno, i, 0x0a, T_CLK_POST_CNT_LO_MASK,
+ T_CLK_POST_CNT_LO(clk_post));
+ phy_update_bits(inno, i, 0x0e, T_CLK_PRE_CNT_MASK,
+ T_CLK_PRE_CNT(clk_pre));
+ phy_update_bits(inno, i, 0x0c, T_WAKEUP_CNT_HI_MASK,
+ T_WAKEUP_CNT_HI(wakeup >> 8));
+ phy_update_bits(inno, i, 0x0d, T_WAKEUP_CNT_LO_MASK,
+ T_WAKEUP_CNT_LO(wakeup));
+ phy_update_bits(inno, i, 0x10, T_TA_GO_CNT_MASK,
+ T_TA_GO_CNT(ta_go));
+ phy_update_bits(inno, i, 0x11, T_TA_SURE_CNT_MASK,
+ T_TA_SURE_CNT(ta_sure));
+ phy_update_bits(inno, i, 0x12, T_TA_WAIT_CNT_MASK,
+ T_TA_WAIT_CNT(ta_wait));
+ }
+
+ /* Enable lanes on analog part */
+ switch (inno->pdata->max_lanes) {
+ case 1:
+ val = LANE_EN_0;
+ break;
+ case 2:
+ val = LANE_EN_0 | LANE_EN_1;
+ break;
+ case 3:
+ val = LANE_EN_0 | LANE_EN_1 | LANE_EN_2;
+ break;
+ case 4:
+ default:
+ val = LANE_EN_0 | LANE_EN_1 | LANE_EN_2 | LANE_EN_3;
+ break;
+ }
+
+ phy_update_bits(inno, REGISTER_PART_ANALOG, 0x00,
+ LANE_EN_MASK, LANE_EN_CK | val);
+}
+
+static void inno_dsidphy_lvds_mode_enable(struct inno_dsidphy *inno)
+{
+ u8 prediv = 2;
+ u16 fbdiv = 28;
+
+ /* Sample clock reverse direction */
+ phy_update_bits(inno, REGISTER_PART_ANALOG, 0x08,
+ SAMPLE_CLOCK_DIRECTION_MASK | LOWFRE_EN_MASK,
+ SAMPLE_CLOCK_DIRECTION_REVERSE |
+ PLL_OUTPUT_FREQUENCY_DIV_BY_1);
+
+ /* Select LVDS mode */
+ phy_update_bits(inno, REGISTER_PART_LVDS, 0x03,
+ MODE_ENABLE_MASK, LVDS_MODE_ENABLE);
+ /* Configure PLL */
+ phy_update_bits(inno, REGISTER_PART_ANALOG, 0x03,
+ REG_PREDIV_MASK, REG_PREDIV(prediv));
+ phy_update_bits(inno, REGISTER_PART_ANALOG, 0x03,
+ REG_FBDIV_HI_MASK, REG_FBDIV_HI(fbdiv));
+ phy_update_bits(inno, REGISTER_PART_ANALOG, 0x04,
+ REG_FBDIV_LO_MASK, REG_FBDIV_LO(fbdiv));
+ phy_update_bits(inno, REGISTER_PART_LVDS, 0x08, 0xff, 0xfc);
+ /* Enable PLL and Bandgap */
+ phy_update_bits(inno, REGISTER_PART_LVDS, 0x0b,
+ LVDS_PLL_POWER_MASK | LVDS_BANDGAP_POWER_MASK,
+ LVDS_PLL_POWER_ON | LVDS_BANDGAP_POWER_ON);
+
+ msleep(20);
+
+ /* Select PLL mode */
+ phy_update_bits(inno, REGISTER_PART_ANALOG, 0x1e,
+ PLL_MODE_SEL_MASK, PLL_MODE_SEL_LVDS_MODE);
+
+ /* Reset LVDS digital logic */
+ phy_update_bits(inno, REGISTER_PART_LVDS, 0x00,
+ LVDS_DIGITAL_INTERNAL_RESET_MASK,
+ LVDS_DIGITAL_INTERNAL_RESET_ENABLE);
+ udelay(1);
+ phy_update_bits(inno, REGISTER_PART_LVDS, 0x00,
+ LVDS_DIGITAL_INTERNAL_RESET_MASK,
+ LVDS_DIGITAL_INTERNAL_RESET_DISABLE);
+ /* Enable LVDS digital logic */
+ phy_update_bits(inno, REGISTER_PART_LVDS, 0x01,
+ LVDS_DIGITAL_INTERNAL_ENABLE_MASK,
+ LVDS_DIGITAL_INTERNAL_ENABLE);
+ /* Enable LVDS analog driver */
+ phy_update_bits(inno, REGISTER_PART_LVDS, 0x0b,
+ LVDS_LANE_EN_MASK, LVDS_CLK_LANE_EN |
+ LVDS_DATA_LANE0_EN | LVDS_DATA_LANE1_EN |
+ LVDS_DATA_LANE2_EN | LVDS_DATA_LANE3_EN);
+}
+
+static int inno_dsidphy_power_on(struct phy *phy)
+{
+ struct inno_dsidphy *inno = phy_get_drvdata(phy);
+
+ clk_prepare_enable(inno->pclk_phy);
+ clk_prepare_enable(inno->ref_clk);
+ pm_runtime_get_sync(inno->dev);
+
+ /* Bandgap power on */
+ phy_update_bits(inno, REGISTER_PART_ANALOG, 0x00,
+ BANDGAP_POWER_MASK, BANDGAP_POWER_ON);
+ /* Enable power work */
+ phy_update_bits(inno, REGISTER_PART_ANALOG, 0x00,
+ POWER_WORK_MASK, POWER_WORK_ENABLE);
+
+ switch (inno->mode) {
+ case PHY_MODE_MIPI_DPHY:
+ inno_dsidphy_mipi_mode_enable(inno);
+ break;
+ case PHY_MODE_LVDS:
+ inno_dsidphy_lvds_mode_enable(inno);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int inno_dsidphy_power_off(struct phy *phy)
+{
+ struct inno_dsidphy *inno = phy_get_drvdata(phy);
+
+ phy_update_bits(inno, REGISTER_PART_ANALOG, 0x00, LANE_EN_MASK, 0);
+ phy_update_bits(inno, REGISTER_PART_ANALOG, 0x01,
+ REG_LDOPD_MASK | REG_PLLPD_MASK,
+ REG_LDOPD_POWER_DOWN | REG_PLLPD_POWER_DOWN);
+ phy_update_bits(inno, REGISTER_PART_ANALOG, 0x00,
+ POWER_WORK_MASK, POWER_WORK_DISABLE);
+ phy_update_bits(inno, REGISTER_PART_ANALOG, 0x00,
+ BANDGAP_POWER_MASK, BANDGAP_POWER_DOWN);
+
+ phy_update_bits(inno, REGISTER_PART_LVDS, 0x0b, LVDS_LANE_EN_MASK, 0);
+ phy_update_bits(inno, REGISTER_PART_LVDS, 0x01,
+ LVDS_DIGITAL_INTERNAL_ENABLE_MASK,
+ LVDS_DIGITAL_INTERNAL_DISABLE);
+ phy_update_bits(inno, REGISTER_PART_LVDS, 0x0b,
+ LVDS_PLL_POWER_MASK | LVDS_BANDGAP_POWER_MASK,
+ LVDS_PLL_POWER_OFF | LVDS_BANDGAP_POWER_DOWN);
+
+ pm_runtime_put(inno->dev);
+ clk_disable_unprepare(inno->ref_clk);
+ clk_disable_unprepare(inno->pclk_phy);
+
+ return 0;
+}
+
+static int inno_dsidphy_set_mode(struct phy *phy, enum phy_mode mode,
+ int submode)
+{
+ struct inno_dsidphy *inno = phy_get_drvdata(phy);
+
+ switch (mode) {
+ case PHY_MODE_MIPI_DPHY:
+ case PHY_MODE_LVDS:
+ inno->mode = mode;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int inno_dsidphy_configure(struct phy *phy,
+ union phy_configure_opts *opts)
+{
+ struct inno_dsidphy *inno = phy_get_drvdata(phy);
+ int ret;
+
+ if (inno->mode != PHY_MODE_MIPI_DPHY)
+ return -EINVAL;
+
+ ret = phy_mipi_dphy_config_validate(&opts->mipi_dphy);
+ if (ret)
+ return ret;
+
+ memcpy(&inno->dphy_cfg, &opts->mipi_dphy, sizeof(inno->dphy_cfg));
+
+ return 0;
+}
+
+static const struct phy_ops inno_dsidphy_ops = {
+ .configure = inno_dsidphy_configure,
+ .set_mode = inno_dsidphy_set_mode,
+ .power_on = inno_dsidphy_power_on,
+ .power_off = inno_dsidphy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static const struct inno_video_phy_plat_data max_1ghz_video_phy_plat_data = {
+ .inno_mipi_dphy_timing_table = inno_mipi_dphy_timing_table_max_1ghz,
+ .num_timings = ARRAY_SIZE(inno_mipi_dphy_timing_table_max_1ghz),
+ .max_rate = MAX_1GHZ,
+ .max_lanes = 4,
+};
+
+static const struct inno_video_phy_plat_data max_1_5ghz_video_phy_plat_data = {
+ .inno_mipi_dphy_timing_table = inno_mipi_dphy_timing_table_max_1_5ghz,
+ .num_timings = ARRAY_SIZE(inno_mipi_dphy_timing_table_max_1_5ghz),
+ .max_rate = MAX_1_5GHZ,
+ .max_lanes = 2,
+};
+
+static const struct inno_video_phy_plat_data max_2_5ghz_video_phy_plat_data = {
+ .inno_mipi_dphy_timing_table = inno_mipi_dphy_timing_table_max_2_5ghz,
+ .num_timings = ARRAY_SIZE(inno_mipi_dphy_timing_table_max_2_5ghz),
+ .max_rate = MAX_2_5GHZ,
+ .max_lanes = 4,
+};
+
+static int inno_dsidphy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct inno_dsidphy *inno;
+ struct phy_provider *phy_provider;
+ struct phy *phy;
+ int ret;
+
+ inno = devm_kzalloc(dev, sizeof(*inno), GFP_KERNEL);
+ if (!inno)
+ return -ENOMEM;
+
+ inno->dev = dev;
+ inno->pdata = of_device_get_match_data(inno->dev);
+ platform_set_drvdata(pdev, inno);
+
+ inno->phy_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(inno->phy_base))
+ return PTR_ERR(inno->phy_base);
+
+ inno->ref_clk = devm_clk_get(dev, "ref");
+ if (IS_ERR(inno->ref_clk)) {
+ ret = PTR_ERR(inno->ref_clk);
+ dev_err(dev, "failed to get ref clock: %d\n", ret);
+ return ret;
+ }
+
+ inno->pclk_phy = devm_clk_get(dev, "pclk");
+ if (IS_ERR(inno->pclk_phy)) {
+ ret = PTR_ERR(inno->pclk_phy);
+ dev_err(dev, "failed to get phy pclk: %d\n", ret);
+ return ret;
+ }
+
+ inno->rst = devm_reset_control_get(dev, "apb");
+ if (IS_ERR(inno->rst)) {
+ ret = PTR_ERR(inno->rst);
+ dev_err(dev, "failed to get system reset control: %d\n", ret);
+ return ret;
+ }
+
+ phy = devm_phy_create(dev, NULL, &inno_dsidphy_ops);
+ if (IS_ERR(phy)) {
+ ret = PTR_ERR(phy);
+ dev_err(dev, "failed to create phy: %d\n", ret);
+ return ret;
+ }
+
+ phy_set_drvdata(phy, inno);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ if (IS_ERR(phy_provider)) {
+ ret = PTR_ERR(phy_provider);
+ dev_err(dev, "failed to register phy provider: %d\n", ret);
+ return ret;
+ }
+
+ pm_runtime_enable(dev);
+
+ return 0;
+}
+
+static void inno_dsidphy_remove(struct platform_device *pdev)
+{
+ struct inno_dsidphy *inno = platform_get_drvdata(pdev);
+
+ pm_runtime_disable(inno->dev);
+}
+
+static const struct of_device_id inno_dsidphy_of_match[] = {
+ {
+ .compatible = "rockchip,px30-dsi-dphy",
+ .data = &max_1ghz_video_phy_plat_data,
+ }, {
+ .compatible = "rockchip,rk3128-dsi-dphy",
+ .data = &max_1ghz_video_phy_plat_data,
+ }, {
+ .compatible = "rockchip,rk3368-dsi-dphy",
+ .data = &max_1ghz_video_phy_plat_data,
+ }, {
+ .compatible = "rockchip,rk3506-dsi-dphy",
+ .data = &max_1_5ghz_video_phy_plat_data,
+ }, {
+ .compatible = "rockchip,rk3568-dsi-dphy",
+ .data = &max_2_5ghz_video_phy_plat_data,
+ }, {
+ .compatible = "rockchip,rv1126-dsi-dphy",
+ .data = &max_2_5ghz_video_phy_plat_data,
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, inno_dsidphy_of_match);
+
+static struct platform_driver inno_dsidphy_driver = {
+ .driver = {
+ .name = "inno-dsidphy",
+ .of_match_table = of_match_ptr(inno_dsidphy_of_match),
+ },
+ .probe = inno_dsidphy_probe,
+ .remove = inno_dsidphy_remove,
+};
+module_platform_driver(inno_dsidphy_driver);
+
+MODULE_AUTHOR("Wyon Bi <bivvy.bi@rock-chips.com>");
+MODULE_DESCRIPTION("Innosilicon MIPI/LVDS/TTL Video Combo PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/rockchip/phy-rockchip-inno-hdmi.c b/drivers/phy/rockchip/phy-rockchip-inno-hdmi.c
new file mode 100644
index 000000000..c3d257a0f
--- /dev/null
+++ b/drivers/phy/rockchip/phy-rockchip-inno-hdmi.c
@@ -0,0 +1,1485 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2017 Rockchip Electronics Co. Ltd.
+ *
+ * Author: Zheng Yang <zhengyang@rock-chips.com>
+ * Heiko Stuebner <heiko@sntech.de>
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/phy/phy.h>
+#include <linux/slab.h>
+
+#define UPDATE(x, h, l) (((x) << (l)) & GENMASK((h), (l)))
+
+/* REG: 0x00 */
+#define RK3228_PRE_PLL_REFCLK_SEL_PCLK BIT(0)
+/* REG: 0x01 */
+#define RK3228_BYPASS_RXSENSE_EN BIT(2)
+#define RK3228_BYPASS_PWRON_EN BIT(1)
+#define RK3228_BYPASS_PLLPD_EN BIT(0)
+/* REG: 0x02 */
+#define RK3228_BYPASS_PDATA_EN BIT(4)
+#define RK3228_PDATAEN_DISABLE BIT(0)
+/* REG: 0x03 */
+#define RK3228_BYPASS_AUTO_TERM_RES_CAL BIT(7)
+#define RK3228_AUTO_TERM_RES_CAL_SPEED_14_8(x) UPDATE(x, 6, 0)
+/* REG: 0x04 */
+#define RK3228_AUTO_TERM_RES_CAL_SPEED_7_0(x) UPDATE(x, 7, 0)
+/* REG: 0xaa */
+#define RK3228_POST_PLL_CTRL_MANUAL BIT(0)
+/* REG: 0xe0 */
+#define RK3228_POST_PLL_POWER_DOWN BIT(5)
+#define RK3228_PRE_PLL_POWER_DOWN BIT(4)
+#define RK3228_RXSENSE_CLK_CH_ENABLE BIT(3)
+#define RK3228_RXSENSE_DATA_CH2_ENABLE BIT(2)
+#define RK3228_RXSENSE_DATA_CH1_ENABLE BIT(1)
+#define RK3228_RXSENSE_DATA_CH0_ENABLE BIT(0)
+/* REG: 0xe1 */
+#define RK3228_BANDGAP_ENABLE BIT(4)
+#define RK3228_TMDS_DRIVER_ENABLE GENMASK(3, 0)
+/* REG: 0xe2 */
+#define RK3228_PRE_PLL_FB_DIV_8_MASK BIT(7)
+#define RK3228_PRE_PLL_FB_DIV_8(x) UPDATE((x) >> 8, 7, 7)
+#define RK3228_PCLK_VCO_DIV_5_MASK BIT(5)
+#define RK3228_PCLK_VCO_DIV_5(x) UPDATE(x, 5, 5)
+#define RK3228_PRE_PLL_PRE_DIV_MASK GENMASK(4, 0)
+#define RK3228_PRE_PLL_PRE_DIV(x) UPDATE(x, 4, 0)
+/* REG: 0xe3 */
+#define RK3228_PRE_PLL_FB_DIV_7_0(x) UPDATE(x, 7, 0)
+/* REG: 0xe4 */
+#define RK3228_PRE_PLL_PCLK_DIV_B_MASK GENMASK(6, 5)
+#define RK3228_PRE_PLL_PCLK_DIV_B_SHIFT 5
+#define RK3228_PRE_PLL_PCLK_DIV_B(x) UPDATE(x, 6, 5)
+#define RK3228_PRE_PLL_PCLK_DIV_A_MASK GENMASK(4, 0)
+#define RK3228_PRE_PLL_PCLK_DIV_A(x) UPDATE(x, 4, 0)
+/* REG: 0xe5 */
+#define RK3228_PRE_PLL_PCLK_DIV_C_MASK GENMASK(6, 5)
+#define RK3228_PRE_PLL_PCLK_DIV_C(x) UPDATE(x, 6, 5)
+#define RK3228_PRE_PLL_PCLK_DIV_D_MASK GENMASK(4, 0)
+#define RK3228_PRE_PLL_PCLK_DIV_D(x) UPDATE(x, 4, 0)
+/* REG: 0xe6 */
+#define RK3228_PRE_PLL_TMDSCLK_DIV_C_MASK GENMASK(5, 4)
+#define RK3228_PRE_PLL_TMDSCLK_DIV_C(x) UPDATE(x, 5, 4)
+#define RK3228_PRE_PLL_TMDSCLK_DIV_A_MASK GENMASK(3, 2)
+#define RK3228_PRE_PLL_TMDSCLK_DIV_A(x) UPDATE(x, 3, 2)
+#define RK3228_PRE_PLL_TMDSCLK_DIV_B_MASK GENMASK(1, 0)
+#define RK3228_PRE_PLL_TMDSCLK_DIV_B(x) UPDATE(x, 1, 0)
+/* REG: 0xe8 */
+#define RK3228_PRE_PLL_LOCK_STATUS BIT(0)
+/* REG: 0xe9 */
+#define RK3228_POST_PLL_POST_DIV_ENABLE UPDATE(3, 7, 6)
+#define RK3228_POST_PLL_PRE_DIV_MASK GENMASK(4, 0)
+#define RK3228_POST_PLL_PRE_DIV(x) UPDATE(x, 4, 0)
+/* REG: 0xea */
+#define RK3228_POST_PLL_FB_DIV_7_0(x) UPDATE(x, 7, 0)
+/* REG: 0xeb */
+#define RK3228_POST_PLL_FB_DIV_8_MASK BIT(7)
+#define RK3228_POST_PLL_FB_DIV_8(x) UPDATE((x) >> 8, 7, 7)
+#define RK3228_POST_PLL_POST_DIV_MASK GENMASK(5, 4)
+#define RK3228_POST_PLL_POST_DIV(x) UPDATE(x, 5, 4)
+#define RK3228_POST_PLL_LOCK_STATUS BIT(0)
+/* REG: 0xee */
+#define RK3228_TMDS_CH_TA_ENABLE GENMASK(7, 4)
+/* REG: 0xef */
+#define RK3228_TMDS_CLK_CH_TA(x) UPDATE(x, 7, 6)
+#define RK3228_TMDS_DATA_CH2_TA(x) UPDATE(x, 5, 4)
+#define RK3228_TMDS_DATA_CH1_TA(x) UPDATE(x, 3, 2)
+#define RK3228_TMDS_DATA_CH0_TA(x) UPDATE(x, 1, 0)
+/* REG: 0xf0 */
+#define RK3228_TMDS_DATA_CH2_PRE_EMPHASIS_MASK GENMASK(5, 4)
+#define RK3228_TMDS_DATA_CH2_PRE_EMPHASIS(x) UPDATE(x, 5, 4)
+#define RK3228_TMDS_DATA_CH1_PRE_EMPHASIS_MASK GENMASK(3, 2)
+#define RK3228_TMDS_DATA_CH1_PRE_EMPHASIS(x) UPDATE(x, 3, 2)
+#define RK3228_TMDS_DATA_CH0_PRE_EMPHASIS_MASK GENMASK(1, 0)
+#define RK3228_TMDS_DATA_CH0_PRE_EMPHASIS(x) UPDATE(x, 1, 0)
+/* REG: 0xf1 */
+#define RK3228_TMDS_CLK_CH_OUTPUT_SWING(x) UPDATE(x, 7, 4)
+#define RK3228_TMDS_DATA_CH2_OUTPUT_SWING(x) UPDATE(x, 3, 0)
+/* REG: 0xf2 */
+#define RK3228_TMDS_DATA_CH1_OUTPUT_SWING(x) UPDATE(x, 7, 4)
+#define RK3228_TMDS_DATA_CH0_OUTPUT_SWING(x) UPDATE(x, 3, 0)
+
+/* REG: 0x01 */
+#define RK3328_BYPASS_RXSENSE_EN BIT(2)
+#define RK3328_BYPASS_POWERON_EN BIT(1)
+#define RK3328_BYPASS_PLLPD_EN BIT(0)
+/* REG: 0x02 */
+#define RK3328_INT_POL_HIGH BIT(7)
+#define RK3328_BYPASS_PDATA_EN BIT(4)
+#define RK3328_PDATA_EN BIT(0)
+/* REG:0x05 */
+#define RK3328_INT_TMDS_CLK(x) UPDATE(x, 7, 4)
+#define RK3328_INT_TMDS_D2(x) UPDATE(x, 3, 0)
+/* REG:0x07 */
+#define RK3328_INT_TMDS_D1(x) UPDATE(x, 7, 4)
+#define RK3328_INT_TMDS_D0(x) UPDATE(x, 3, 0)
+/* for all RK3328_INT_TMDS_*, ESD_DET as defined in 0xc8-0xcb */
+#define RK3328_INT_AGND_LOW_PULSE_LOCKED BIT(3)
+#define RK3328_INT_RXSENSE_LOW_PULSE_LOCKED BIT(2)
+#define RK3328_INT_VSS_AGND_ESD_DET BIT(1)
+#define RK3328_INT_AGND_VSS_ESD_DET BIT(0)
+/* REG: 0xa0 */
+#define RK3328_PCLK_VCO_DIV_5_MASK BIT(1)
+#define RK3328_PCLK_VCO_DIV_5(x) UPDATE(x, 1, 1)
+#define RK3328_PRE_PLL_POWER_DOWN BIT(0)
+/* REG: 0xa1 */
+#define RK3328_PRE_PLL_PRE_DIV_MASK GENMASK(5, 0)
+#define RK3328_PRE_PLL_PRE_DIV(x) UPDATE(x, 5, 0)
+/* REG: 0xa2 */
+/* unset means center spread */
+#define RK3328_SPREAD_SPECTRUM_MOD_DOWN BIT(7)
+#define RK3328_SPREAD_SPECTRUM_MOD_DISABLE BIT(6)
+#define RK3328_PRE_PLL_FRAC_DIV_DISABLE UPDATE(3, 5, 4)
+#define RK3328_PRE_PLL_FB_DIV_11_8_MASK GENMASK(3, 0)
+#define RK3328_PRE_PLL_FB_DIV_11_8(x) UPDATE((x) >> 8, 3, 0)
+/* REG: 0xa3 */
+#define RK3328_PRE_PLL_FB_DIV_7_0(x) UPDATE(x, 7, 0)
+/* REG: 0xa4*/
+#define RK3328_PRE_PLL_TMDSCLK_DIV_C_MASK GENMASK(1, 0)
+#define RK3328_PRE_PLL_TMDSCLK_DIV_C(x) UPDATE(x, 1, 0)
+#define RK3328_PRE_PLL_TMDSCLK_DIV_B_MASK GENMASK(3, 2)
+#define RK3328_PRE_PLL_TMDSCLK_DIV_B(x) UPDATE(x, 3, 2)
+#define RK3328_PRE_PLL_TMDSCLK_DIV_A_MASK GENMASK(5, 4)
+#define RK3328_PRE_PLL_TMDSCLK_DIV_A(x) UPDATE(x, 5, 4)
+/* REG: 0xa5 */
+#define RK3328_PRE_PLL_PCLK_DIV_B_SHIFT 5
+#define RK3328_PRE_PLL_PCLK_DIV_B_MASK GENMASK(6, 5)
+#define RK3328_PRE_PLL_PCLK_DIV_B(x) UPDATE(x, 6, 5)
+#define RK3328_PRE_PLL_PCLK_DIV_A_MASK GENMASK(4, 0)
+#define RK3328_PRE_PLL_PCLK_DIV_A(x) UPDATE(x, 4, 0)
+/* REG: 0xa6 */
+#define RK3328_PRE_PLL_PCLK_DIV_C_SHIFT 5
+#define RK3328_PRE_PLL_PCLK_DIV_C_MASK GENMASK(6, 5)
+#define RK3328_PRE_PLL_PCLK_DIV_C(x) UPDATE(x, 6, 5)
+#define RK3328_PRE_PLL_PCLK_DIV_D_MASK GENMASK(4, 0)
+#define RK3328_PRE_PLL_PCLK_DIV_D(x) UPDATE(x, 4, 0)
+/* REG: 0xa9 */
+#define RK3328_PRE_PLL_LOCK_STATUS BIT(0)
+/* REG: 0xaa */
+#define RK3328_POST_PLL_POST_DIV_ENABLE GENMASK(3, 2)
+#define RK3328_POST_PLL_REFCLK_SEL_TMDS BIT(1)
+#define RK3328_POST_PLL_POWER_DOWN BIT(0)
+/* REG:0xab */
+#define RK3328_POST_PLL_FB_DIV_8(x) UPDATE((x) >> 8, 7, 7)
+#define RK3328_POST_PLL_PRE_DIV(x) UPDATE(x, 4, 0)
+/* REG: 0xac */
+#define RK3328_POST_PLL_FB_DIV_7_0(x) UPDATE(x, 7, 0)
+/* REG: 0xad */
+#define RK3328_POST_PLL_POST_DIV_MASK GENMASK(1, 0)
+#define RK3328_POST_PLL_POST_DIV_2 0x0
+#define RK3328_POST_PLL_POST_DIV_4 0x1
+#define RK3328_POST_PLL_POST_DIV_8 0x3
+/* REG: 0xaf */
+#define RK3328_POST_PLL_LOCK_STATUS BIT(0)
+/* REG: 0xb0 */
+#define RK3328_BANDGAP_ENABLE BIT(2)
+/* REG: 0xb2 */
+#define RK3328_TMDS_CLK_DRIVER_EN BIT(3)
+#define RK3328_TMDS_D2_DRIVER_EN BIT(2)
+#define RK3328_TMDS_D1_DRIVER_EN BIT(1)
+#define RK3328_TMDS_D0_DRIVER_EN BIT(0)
+#define RK3328_TMDS_DRIVER_ENABLE (RK3328_TMDS_CLK_DRIVER_EN | \
+ RK3328_TMDS_D2_DRIVER_EN | \
+ RK3328_TMDS_D1_DRIVER_EN | \
+ RK3328_TMDS_D0_DRIVER_EN)
+/* REG:0xc5 */
+#define RK3328_BYPASS_TERM_RESISTOR_CALIB BIT(7)
+#define RK3328_TERM_RESISTOR_CALIB_SPEED_14_8(x) UPDATE((x) >> 8, 6, 0)
+/* REG:0xc6 */
+#define RK3328_TERM_RESISTOR_CALIB_SPEED_7_0(x) UPDATE(x, 7, 0)
+/* REG:0xc7 */
+#define RK3328_TERM_RESISTOR_50 UPDATE(0, 2, 1)
+#define RK3328_TERM_RESISTOR_62_5 UPDATE(1, 2, 1)
+#define RK3328_TERM_RESISTOR_75 UPDATE(2, 2, 1)
+#define RK3328_TERM_RESISTOR_100 UPDATE(3, 2, 1)
+/* REG 0xc8 - 0xcb */
+#define RK3328_ESD_DETECT_MASK GENMASK(7, 6)
+#define RK3328_ESD_DETECT_340MV (0x0 << 6)
+#define RK3328_ESD_DETECT_280MV (0x1 << 6)
+#define RK3328_ESD_DETECT_260MV (0x2 << 6)
+#define RK3328_ESD_DETECT_240MV (0x3 << 6)
+/* resistors can be used in parallel */
+#define RK3328_TMDS_TERM_RESIST_MASK GENMASK(5, 0)
+#define RK3328_TMDS_TERM_RESIST_75 BIT(5)
+#define RK3328_TMDS_TERM_RESIST_150 BIT(4)
+#define RK3328_TMDS_TERM_RESIST_300 BIT(3)
+#define RK3328_TMDS_TERM_RESIST_600 BIT(2)
+#define RK3328_TMDS_TERM_RESIST_1000 BIT(1)
+#define RK3328_TMDS_TERM_RESIST_2000 BIT(0)
+/* REG: 0xd1 */
+#define RK3328_PRE_PLL_FRAC_DIV_23_16(x) UPDATE((x) >> 16, 7, 0)
+/* REG: 0xd2 */
+#define RK3328_PRE_PLL_FRAC_DIV_15_8(x) UPDATE((x) >> 8, 7, 0)
+/* REG: 0xd3 */
+#define RK3328_PRE_PLL_FRAC_DIV_7_0(x) UPDATE(x, 7, 0)
+
+struct inno_hdmi_phy_drv_data;
+
+struct inno_hdmi_phy {
+ struct device *dev;
+ struct regmap *regmap;
+ int irq;
+
+ struct phy *phy;
+ struct clk *sysclk;
+ struct clk *refoclk;
+ struct clk *refpclk;
+
+ /* platform data */
+ const struct inno_hdmi_phy_drv_data *plat_data;
+ int chip_version;
+
+ /* clk provider */
+ struct clk_hw hw;
+ struct clk *phyclk;
+ unsigned long pixclock;
+ unsigned long tmdsclock;
+ unsigned long opts_tmds_char_rate;
+};
+
+struct pre_pll_config {
+ unsigned long pixclock;
+ unsigned long tmdsclock;
+ u8 prediv;
+ u16 fbdiv;
+ u8 tmds_div_a;
+ u8 tmds_div_b;
+ u8 tmds_div_c;
+ u8 pclk_div_a;
+ u8 pclk_div_b;
+ u8 pclk_div_c;
+ u8 pclk_div_d;
+ u8 vco_div_5_en;
+ u32 fracdiv;
+};
+
+struct post_pll_config {
+ unsigned long tmdsclock;
+ u8 prediv;
+ u16 fbdiv;
+ u8 postdiv;
+ u8 version;
+};
+
+struct phy_config {
+ unsigned long tmdsclock;
+ u8 regs[14];
+};
+
+struct inno_hdmi_phy_ops {
+ int (*init)(struct inno_hdmi_phy *inno);
+ int (*power_on)(struct inno_hdmi_phy *inno,
+ const struct post_pll_config *cfg,
+ const struct phy_config *phy_cfg);
+ void (*power_off)(struct inno_hdmi_phy *inno);
+};
+
+struct inno_hdmi_phy_drv_data {
+ const struct inno_hdmi_phy_ops *ops;
+ const struct clk_ops *clk_ops;
+ const struct phy_config *phy_cfg_table;
+};
+
+static const struct pre_pll_config pre_pll_cfg_table[] = {
+ { 25175000, 25175000, 3, 125, 3, 1, 1, 1, 3, 3, 4, 0, 0xe00000},
+ { 25175000, 31468750, 1, 41, 0, 3, 3, 1, 3, 3, 4, 0, 0xf5554f},
+ { 27000000, 27000000, 1, 36, 0, 3, 3, 1, 2, 3, 4, 0, 0x0},
+ { 27000000, 33750000, 1, 45, 0, 3, 3, 1, 3, 3, 4, 0, 0x0},
+ { 31500000, 31500000, 1, 42, 0, 3, 3, 1, 2, 3, 4, 0, 0x0},
+ { 31500000, 39375000, 1, 105, 1, 3, 3, 10, 0, 3, 4, 0, 0x0},
+ { 33750000, 33750000, 1, 45, 0, 3, 3, 1, 2, 3, 4, 0, 0x0},
+ { 33750000, 42187500, 1, 169, 2, 3, 3, 15, 0, 3, 4, 0, 0x0},
+ { 35500000, 35500000, 1, 71, 2, 2, 2, 6, 0, 3, 4, 0, 0x0},
+ { 35500000, 44375000, 1, 74, 3, 1, 1, 25, 0, 1, 1, 0, 0x0},
+ { 36000000, 36000000, 1, 36, 2, 1, 1, 1, 1, 3, 4, 0, 0x0},
+ { 36000000, 45000000, 1, 45, 2, 1, 1, 15, 0, 1, 1, 0, 0x0},
+ { 40000000, 40000000, 1, 40, 2, 1, 1, 1, 1, 3, 4, 0, 0x0},
+ { 40000000, 50000000, 1, 50, 2, 1, 1, 15, 0, 1, 1, 0, 0x0},
+ { 49500000, 49500000, 1, 66, 0, 3, 3, 1, 2, 3, 4, 0, 0x0},
+ { 49500000, 61875000, 1, 165, 1, 3, 3, 10, 0, 3, 4, 0, 0x0},
+ { 50000000, 50000000, 1, 50, 2, 1, 1, 1, 1, 3, 4, 0, 0x0},
+ { 50000000, 62500000, 1, 125, 2, 2, 2, 15, 0, 2, 2, 0, 0x0},
+ { 54000000, 54000000, 1, 36, 0, 2, 2, 1, 0, 3, 4, 0, 0x0},
+ { 54000000, 67500000, 1, 45, 0, 2, 2, 1, 3, 2, 2, 0, 0x0},
+ { 56250000, 56250000, 1, 75, 0, 3, 3, 1, 2, 3, 4, 0, 0x0},
+ { 56250000, 70312500, 1, 117, 3, 1, 1, 25, 0, 1, 1, 0, 0x0},
+ { 59341000, 59341000, 1, 118, 2, 2, 2, 6, 0, 3, 4, 0, 0xae978d},
+ { 59341000, 74176250, 2, 148, 2, 1, 1, 15, 0, 1, 1, 0, 0x5a3d70},
+ { 59400000, 59400000, 1, 99, 3, 1, 1, 1, 3, 3, 4, 0, 0x0},
+ { 59400000, 74250000, 1, 99, 0, 3, 3, 1, 3, 3, 4, 0, 0x0},
+ { 65000000, 65000000, 1, 65, 2, 1, 1, 1, 1, 3, 4, 0, 0x0},
+ { 65000000, 81250000, 3, 325, 0, 3, 3, 1, 3, 3, 4, 0, 0x0},
+ { 68250000, 68250000, 1, 91, 0, 3, 3, 1, 2, 3, 4, 0, 0x0},
+ { 68250000, 85312500, 1, 142, 3, 1, 1, 25, 0, 1, 1, 0, 0x0},
+ { 71000000, 71000000, 1, 71, 2, 1, 1, 1, 1, 3, 4, 0, 0x0},
+ { 71000000, 88750000, 3, 355, 0, 3, 3, 1, 3, 3, 4, 0, 0x0},
+ { 72000000, 72000000, 1, 36, 2, 0, 0, 1, 1, 2, 2, 0, 0x0},
+ { 72000000, 90000000, 1, 60, 0, 2, 2, 1, 3, 2, 2, 0, 0x0},
+ { 73250000, 73250000, 3, 293, 0, 3, 3, 1, 2, 3, 4, 0, 0x0},
+ { 73250000, 91562500, 1, 61, 0, 2, 2, 1, 3, 2, 2, 0, 0x0},
+ { 74176000, 74176000, 1, 37, 2, 0, 0, 1, 1, 2, 2, 0, 0x16872b},
+ { 74176000, 92720000, 2, 185, 2, 1, 1, 15, 0, 1, 1, 0, 0x70a3d7},
+ { 74250000, 74250000, 1, 99, 0, 3, 3, 1, 2, 3, 4, 0, 0x0},
+ { 74250000, 92812500, 4, 495, 0, 3, 3, 1, 3, 3, 4, 0, 0x0},
+ { 75000000, 75000000, 1, 50, 0, 2, 2, 1, 0, 3, 4, 0, 0x0},
+ { 75000000, 93750000, 1, 125, 0, 3, 3, 1, 3, 3, 4, 0, 0x0},
+ { 78750000, 78750000, 1, 105, 0, 3, 3, 1, 2, 3, 4, 0, 0x0},
+ { 78750000, 98437500, 1, 164, 3, 1, 1, 25, 0, 1, 1, 0, 0x0},
+ { 79500000, 79500000, 1, 53, 0, 2, 2, 1, 0, 3, 4, 0, 0x0},
+ { 79500000, 99375000, 1, 199, 2, 2, 2, 15, 0, 2, 2, 0, 0x0},
+ { 83500000, 83500000, 2, 167, 2, 1, 1, 1, 1, 3, 4, 0, 0x0},
+ { 83500000, 104375000, 1, 104, 2, 1, 1, 15, 0, 1, 1, 0, 0x600000},
+ { 85500000, 85500000, 1, 57, 0, 2, 2, 1, 0, 3, 4, 0, 0x0},
+ { 85500000, 106875000, 1, 178, 3, 1, 1, 25, 0, 1, 1, 0, 0x0},
+ { 85750000, 85750000, 3, 343, 0, 3, 3, 1, 2, 3, 4, 0, 0x0},
+ { 85750000, 107187500, 1, 143, 0, 3, 3, 1, 3, 3, 4, 0, 0x0},
+ { 88750000, 88750000, 3, 355, 0, 3, 3, 1, 2, 3, 4, 0, 0x0},
+ { 88750000, 110937500, 1, 110, 2, 1, 1, 15, 0, 1, 1, 0, 0xf00000},
+ { 94500000, 94500000, 1, 63, 0, 2, 2, 1, 0, 3, 4, 0, 0x0},
+ { 94500000, 118125000, 1, 197, 3, 1, 1, 25, 0, 1, 1, 0, 0x0},
+ {101000000, 101000000, 1, 101, 2, 1, 1, 1, 1, 3, 4, 0, 0x0},
+ {101000000, 126250000, 1, 42, 0, 1, 1, 1, 3, 1, 1, 0, 0x0},
+ {102250000, 102250000, 4, 409, 2, 1, 1, 1, 1, 3, 4, 0, 0x0},
+ {102250000, 127812500, 1, 128, 2, 1, 1, 15, 0, 1, 1, 0, 0x0},
+ {106500000, 106500000, 1, 71, 0, 2, 2, 1, 0, 3, 4, 0, 0x0},
+ {106500000, 133125000, 1, 133, 2, 1, 1, 15, 0, 1, 1, 0, 0x0},
+ {108000000, 108000000, 1, 36, 0, 1, 1, 1, 0, 2, 2, 0, 0x0},
+ {108000000, 135000000, 1, 45, 0, 1, 1, 1, 3, 1, 1, 0, 0x0},
+ {115500000, 115500000, 1, 77, 0, 2, 2, 1, 0, 3, 4, 0, 0x0},
+ {115500000, 144375000, 1, 48, 0, 1, 1, 1, 3, 1, 1, 0, 0x0},
+ {117500000, 117500000, 2, 235, 2, 1, 1, 1, 1, 3, 4, 0, 0x0},
+ {117500000, 146875000, 1, 49, 0, 1, 1, 1, 3, 1, 1, 0, 0x0},
+ {119000000, 119000000, 1, 119, 2, 1, 1, 1, 1, 3, 4, 0, 0x0},
+ {119000000, 148750000, 3, 148, 0, 1, 1, 1, 3, 1, 1, 0, 0xc00000},
+ {121750000, 121750000, 4, 487, 2, 1, 1, 1, 1, 3, 4, 0, 0x0},
+ {121750000, 152187500, 1, 203, 0, 3, 3, 1, 3, 3, 4, 0, 0x0},
+ {122500000, 122500000, 2, 245, 2, 1, 1, 1, 1, 3, 4, 0, 0x0},
+ {122500000, 153125000, 1, 51, 0, 1, 1, 1, 3, 1, 1, 0, 0x0},
+ {135000000, 135000000, 1, 45, 0, 1, 1, 1, 0, 2, 2, 0, 0x0},
+ {135000000, 168750000, 1, 169, 2, 1, 1, 15, 0, 1, 1, 0, 0x0},
+ {136750000, 136750000, 1, 68, 2, 0, 0, 1, 1, 2, 2, 0, 0x600000},
+ {136750000, 170937500, 1, 113, 0, 2, 2, 1, 3, 2, 2, 0, 0xf5554f},
+ {140250000, 140250000, 2, 187, 0, 2, 2, 1, 0, 3, 4, 0, 0x0},
+ {140250000, 175312500, 1, 117, 0, 2, 2, 1, 3, 2, 2, 0, 0x0},
+ {146250000, 146250000, 2, 195, 0, 2, 2, 1, 0, 3, 4, 0, 0x0},
+ {146250000, 182812500, 1, 61, 0, 1, 1, 1, 3, 1, 1, 0, 0x0},
+ {148250000, 148250000, 3, 222, 2, 0, 0, 1, 1, 2, 2, 0, 0x600000},
+ {148250000, 185312500, 1, 123, 0, 2, 2, 1, 3, 2, 2, 0, 0x8aaab0},
+ {148352000, 148352000, 2, 148, 2, 0, 0, 1, 1, 2, 2, 0, 0x5a1cac},
+ {148352000, 185440000, 3, 185, 0, 1, 1, 1, 3, 1, 1, 0, 0x70a3d7},
+ {148500000, 148500000, 1, 99, 0, 2, 2, 1, 0, 3, 4, 0, 0x0},
+ {148500000, 185625000, 4, 495, 0, 2, 2, 1, 3, 2, 2, 0, 0x0},
+ {154000000, 154000000, 1, 77, 2, 0, 0, 1, 1, 2, 2, 0, 0x0},
+ {154000000, 192500000, 1, 64, 0, 1, 1, 1, 3, 1, 1, 0, 0x0},
+ {156000000, 156000000, 1, 52, 0, 1, 1, 1, 0, 2, 2, 0, 0x0},
+ {156000000, 195000000, 1, 65, 0, 1, 1, 1, 3, 1, 1, 0, 0x0},
+ {156750000, 156750000, 2, 209, 0, 2, 2, 1, 0, 3, 4, 0, 0x0},
+ {156750000, 195937500, 1, 196, 2, 1, 1, 15, 0, 1, 1, 0, 0x0},
+ {157000000, 157000000, 2, 157, 2, 0, 0, 1, 1, 2, 2, 0, 0x0},
+ {157000000, 196250000, 1, 131, 0, 2, 2, 1, 3, 2, 2, 0, 0x0},
+ {157500000, 157500000, 1, 105, 0, 2, 2, 1, 0, 3, 4, 0, 0x0},
+ {157500000, 196875000, 1, 197, 2, 1, 1, 15, 0, 1, 1, 0, 0x0},
+ {162000000, 162000000, 1, 54, 0, 1, 1, 1, 0, 2, 2, 0, 0x0},
+ {162000000, 202500000, 2, 135, 0, 1, 1, 1, 3, 1, 1, 0, 0x0},
+ {175500000, 175500000, 1, 117, 0, 2, 2, 1, 0, 3, 4, 0, 0x0},
+ {175500000, 219375000, 1, 73, 0, 1, 1, 1, 3, 1, 1, 0, 0x0},
+ {179500000, 179500000, 3, 359, 0, 2, 2, 1, 0, 3, 4, 0, 0x0},
+ {179500000, 224375000, 1, 75, 0, 1, 1, 1, 3, 1, 1, 0, 0x0},
+ {182750000, 182750000, 1, 91, 2, 0, 0, 1, 1, 2, 2, 0, 0x600000},
+ {182750000, 228437500, 1, 152, 0, 2, 2, 1, 3, 2, 2, 0, 0x4aaab0},
+ {182750000, 228437500, 1, 152, 0, 2, 2, 1, 3, 2, 2, 0, 0x4aaab0},
+ {187000000, 187000000, 2, 187, 2, 0, 0, 1, 1, 2, 2, 0, 0x0},
+ {187000000, 233750000, 1, 39, 0, 0, 0, 1, 3, 0, 0, 1, 0x0},
+ {187250000, 187250000, 3, 280, 2, 0, 0, 1, 1, 2, 2, 0, 0xe00000},
+ {187250000, 234062500, 1, 156, 0, 2, 2, 1, 3, 2, 2, 0, 0xaaab0},
+ {189000000, 189000000, 1, 63, 0, 1, 1, 1, 0, 2, 2, 0, 0x0},
+ {189000000, 236250000, 1, 79, 0, 1, 1, 1, 3, 1, 1, 0, 0x0},
+ {193250000, 193250000, 3, 289, 2, 0, 0, 1, 1, 2, 2, 0, 0xe00000},
+ {193250000, 241562500, 1, 161, 0, 2, 2, 1, 3, 2, 2, 0, 0xaaab0},
+ {202500000, 202500000, 2, 135, 0, 1, 1, 1, 0, 2, 2, 0, 0x0},
+ {202500000, 253125000, 1, 169, 0, 2, 2, 1, 3, 2, 2, 0, 0x0},
+ {204750000, 204750000, 4, 273, 0, 1, 1, 1, 0, 2, 2, 0, 0x0},
+ {204750000, 255937500, 1, 171, 0, 2, 2, 1, 3, 2, 2, 0, 0x0},
+ {208000000, 208000000, 1, 104, 2, 0, 0, 1, 1, 2, 2, 0, 0x0},
+ {208000000, 260000000, 1, 173, 0, 2, 2, 1, 3, 2, 2, 0, 0x0},
+ {214750000, 214750000, 1, 107, 2, 0, 0, 1, 1, 2, 2, 0, 0x600000},
+ {214750000, 268437500, 1, 178, 0, 2, 2, 1, 3, 2, 2, 0, 0xf5554f},
+ {218250000, 218250000, 4, 291, 0, 1, 1, 1, 0, 2, 2, 0, 0x0},
+ {218250000, 272812500, 1, 91, 0, 1, 1, 1, 3, 1, 1, 0, 0x0},
+ {229500000, 229500000, 2, 153, 0, 1, 1, 1, 0, 2, 2, 0, 0x0},
+ {229500000, 286875000, 1, 191, 0, 2, 2, 1, 3, 2, 2, 0, 0x0},
+ {234000000, 234000000, 1, 39, 0, 0, 0, 1, 0, 1, 1, 0, 0x0},
+ {234000000, 292500000, 1, 195, 0, 2, 2, 1, 3, 2, 2, 0, 0x0},
+ {241500000, 241500000, 2, 161, 0, 1, 1, 1, 0, 2, 2, 0, 0x0},
+ {241500000, 301875000, 1, 201, 0, 2, 2, 1, 3, 2, 2, 0, 0x0},
+ {245250000, 245250000, 4, 327, 0, 1, 1, 1, 0, 2, 2, 0, 0x0},
+ {245250000, 306562500, 1, 51, 0, 0, 0, 1, 3, 0, 0, 1, 0x0},
+ {245500000, 245500000, 4, 491, 2, 0, 0, 1, 1, 2, 2, 0, 0x0},
+ {245500000, 306875000, 1, 51, 0, 0, 0, 1, 3, 0, 0, 1, 0x0},
+ {261000000, 261000000, 1, 87, 0, 1, 1, 1, 0, 2, 2, 0, 0x0},
+ {261000000, 326250000, 1, 109, 0, 1, 1, 1, 3, 1, 1, 0, 0x0},
+ {268250000, 268250000, 9, 402, 0, 0, 0, 1, 0, 1, 1, 0, 0x600000},
+ {268250000, 335312500, 1, 111, 0, 1, 1, 1, 3, 1, 1, 0, 0xc5554f},
+ {268500000, 268500000, 2, 179, 0, 1, 1, 1, 0, 2, 2, 0, 0x0},
+ {268500000, 335625000, 1, 56, 0, 0, 0, 1, 3, 0, 0, 1, 0x0},
+ {281250000, 281250000, 4, 375, 0, 1, 1, 1, 0, 2, 2, 0, 0x0},
+ {281250000, 351562500, 1, 117, 0, 3, 1, 1, 3, 1, 1, 0, 0x0},
+ {288000000, 288000000, 1, 48, 0, 0, 0, 1, 0, 1, 1, 0, 0x0},
+ {288000000, 360000000, 1, 60, 0, 2, 0, 1, 3, 0, 0, 1, 0x0},
+ {296703000, 296703000, 1, 49, 0, 0, 0, 1, 0, 1, 1, 0, 0x7353f7},
+ {296703000, 370878750, 1, 123, 0, 3, 1, 1, 3, 1, 1, 0, 0xa051eb},
+ {297000000, 297000000, 1, 99, 0, 1, 1, 1, 0, 2, 2, 0, 0x0},
+ {297000000, 371250000, 4, 495, 0, 3, 1, 1, 3, 1, 1, 0, 0x0},
+ {312250000, 312250000, 9, 468, 0, 0, 0, 1, 0, 1, 1, 0, 0x600000},
+ {312250000, 390312500, 1, 130, 0, 3, 1, 1, 3, 1, 1, 0, 0x1aaab0},
+ {317000000, 317000000, 3, 317, 0, 1, 1, 1, 0, 2, 2, 0, 0x0},
+ {317000000, 396250000, 1, 66, 0, 2, 0, 1, 3, 0, 0, 1, 0x0},
+ {319750000, 319750000, 3, 159, 0, 0, 0, 1, 0, 1, 1, 0, 0xe00000},
+ {319750000, 399687500, 3, 199, 0, 2, 0, 1, 3, 0, 0, 1, 0xd80000},
+ {333250000, 333250000, 9, 499, 0, 0, 0, 1, 0, 1, 1, 0, 0xe00000},
+ {333250000, 416562500, 1, 138, 0, 3, 1, 1, 3, 1, 1, 0, 0xdaaab0},
+ {348500000, 348500000, 9, 522, 0, 2, 0, 1, 0, 1, 1, 0, 0xc00000},
+ {348500000, 435625000, 1, 145, 0, 3, 1, 1, 3, 1, 1, 0, 0x35554f},
+ {356500000, 356500000, 9, 534, 0, 2, 0, 1, 0, 1, 1, 0, 0xc00000},
+ {356500000, 445625000, 1, 148, 0, 3, 1, 1, 3, 1, 1, 0, 0x8aaab0},
+ {380500000, 380500000, 9, 570, 0, 2, 0, 1, 0, 1, 1, 0, 0xc00000},
+ {380500000, 475625000, 1, 158, 0, 3, 1, 1, 3, 1, 1, 0, 0x8aaab0},
+ {443250000, 443250000, 1, 73, 0, 2, 0, 1, 0, 1, 1, 0, 0xe00000},
+ {443250000, 554062500, 1, 92, 0, 2, 0, 1, 3, 0, 0, 1, 0x580000},
+ {505250000, 505250000, 9, 757, 0, 2, 0, 1, 0, 1, 1, 0, 0xe00000},
+ {552750000, 552750000, 3, 276, 0, 2, 0, 1, 0, 1, 1, 0, 0x600000},
+ {593407000, 296703500, 3, 296, 0, 1, 1, 1, 0, 1, 1, 0, 0xb41893},
+ {593407000, 370879375, 4, 494, 0, 3, 1, 1, 3, 0, 0, 1, 0x817e4a},
+ {593407000, 593407000, 3, 296, 0, 2, 0, 1, 0, 1, 1, 0, 0xb41893},
+ {594000000, 297000000, 1, 99, 0, 1, 1, 1, 0, 1, 1, 0, 0x0},
+ {594000000, 371250000, 4, 495, 0, 3, 1, 1, 3, 0, 0, 1, 0x0},
+ {594000000, 594000000, 1, 99, 0, 2, 0, 1, 0, 1, 1, 0, 0x0},
+ { /* sentinel */ }
+};
+
+static const struct post_pll_config post_pll_cfg_table[] = {
+ {33750000, 1, 40, 8, 1},
+ {33750000, 1, 80, 8, 2},
+ {74250000, 1, 40, 8, 1},
+ {74250000, 18, 80, 8, 2},
+ {148500000, 2, 40, 4, 3},
+ {297000000, 4, 40, 2, 3},
+ {594000000, 8, 40, 1, 3},
+ { /* sentinel */ }
+};
+
+/* phy tuning values for an undocumented set of registers */
+static const struct phy_config rk3228_phy_cfg[] = {
+ { 165000000, {
+ 0xaa, 0x00, 0x44, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00,
+ },
+ }, {
+ 340000000, {
+ 0xaa, 0x15, 0x6a, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00,
+ },
+ }, {
+ 594000000, {
+ 0xaa, 0x15, 0x7a, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00,
+ },
+ }, { /* sentinel */ },
+};
+
+/* phy tuning values for an undocumented set of registers */
+static const struct phy_config rk3328_phy_cfg[] = {
+ { 165000000, {
+ 0x07, 0x0a, 0x0a, 0x0a, 0x00, 0x00, 0x08, 0x08, 0x08,
+ 0x00, 0xac, 0xcc, 0xcc, 0xcc,
+ },
+ }, {
+ 340000000, {
+ 0x0b, 0x0d, 0x0d, 0x0d, 0x07, 0x15, 0x08, 0x08, 0x08,
+ 0x3f, 0xac, 0xcc, 0xcd, 0xdd,
+ },
+ }, {
+ 594000000, {
+ 0x10, 0x1a, 0x1a, 0x1a, 0x07, 0x15, 0x08, 0x08, 0x08,
+ 0x00, 0xac, 0xcc, 0xcc, 0xcc,
+ },
+ }, { /* sentinel */ },
+};
+
+static inline struct inno_hdmi_phy *to_inno_hdmi_phy(struct clk_hw *hw)
+{
+ return container_of(hw, struct inno_hdmi_phy, hw);
+}
+
+/*
+ * The register description of the IP block does not use any distinct names
+ * but instead the databook simply numbers the registers in one-increments.
+ * As the registers are obviously 32bit sized, the inno_* functions
+ * translate the databook register names to the actual registers addresses.
+ */
+static inline void inno_write(struct inno_hdmi_phy *inno, u32 reg, u8 val)
+{
+ regmap_write(inno->regmap, reg * 4, val);
+}
+
+static inline u8 inno_read(struct inno_hdmi_phy *inno, u32 reg)
+{
+ u32 val;
+
+ regmap_read(inno->regmap, reg * 4, &val);
+
+ return val;
+}
+
+static inline void inno_update_bits(struct inno_hdmi_phy *inno, u8 reg,
+ u8 mask, u8 val)
+{
+ regmap_update_bits(inno->regmap, reg * 4, mask, val);
+}
+
+#define inno_poll(inno, reg, val, cond, sleep_us, timeout_us) \
+ regmap_read_poll_timeout((inno)->regmap, (reg) * 4, val, cond, \
+ sleep_us, timeout_us)
+
+static unsigned long inno_hdmi_phy_get_tmdsclk(struct inno_hdmi_phy *inno,
+ unsigned long rate)
+{
+ if (inno->opts_tmds_char_rate)
+ return inno->opts_tmds_char_rate;
+
+ return rate;
+}
+
+static irqreturn_t inno_hdmi_phy_rk3328_hardirq(int irq, void *dev_id)
+{
+ struct inno_hdmi_phy *inno = dev_id;
+ int intr_stat1, intr_stat2, intr_stat3;
+
+ intr_stat1 = inno_read(inno, 0x04);
+ intr_stat2 = inno_read(inno, 0x06);
+ intr_stat3 = inno_read(inno, 0x08);
+
+ if (intr_stat1)
+ inno_write(inno, 0x04, intr_stat1);
+ if (intr_stat2)
+ inno_write(inno, 0x06, intr_stat2);
+ if (intr_stat3)
+ inno_write(inno, 0x08, intr_stat3);
+
+ if (intr_stat1 || intr_stat2 || intr_stat3)
+ return IRQ_WAKE_THREAD;
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t inno_hdmi_phy_rk3328_irq(int irq, void *dev_id)
+{
+ struct inno_hdmi_phy *inno = dev_id;
+
+ inno_update_bits(inno, 0x02, RK3328_PDATA_EN, 0);
+ usleep_range(10, 20);
+ inno_update_bits(inno, 0x02, RK3328_PDATA_EN, RK3328_PDATA_EN);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * phy_validate() is expected to be called from encoder atomic_check(), before
+ * the hdmiphy pixel clock is known. Without knowing the actual pixel clock, we
+ * cannot do full validation of the configuration. Instead, we do a simple check
+ * that the pre-pll table contains an entry for the requested TMDS char rate.
+ */
+static int inno_hdmi_phy_validate(struct phy *phy, enum phy_mode mode,
+ int submode, union phy_configure_opts *opts)
+{
+ const struct pre_pll_config *cfg = pre_pll_cfg_table;
+ unsigned long tmdsclock;
+
+ if (!(mode == PHY_MODE_HDMI && submode == PHY_HDMI_MODE_TMDS))
+ return -EINVAL;
+
+ if (!opts)
+ return -EINVAL;
+
+ if (!opts->hdmi.tmds_char_rate || opts->hdmi.tmds_char_rate > 594000000)
+ return -EINVAL;
+
+ tmdsclock = opts->hdmi.tmds_char_rate;
+ for (; cfg->pixclock != 0; cfg++)
+ if (cfg->tmdsclock == tmdsclock)
+ return 0;
+
+ return -EINVAL;
+}
+
+/*
+ * phy_configure() is expected to be called from encoder atomic_set_mode(),
+ * before the hdmiphy pixel clock is known. Store the requested TMDS character
+ * rate, so that it can be used later in power_on() and/or set_rate() when the
+ * pixel clock is known.
+ */
+static int inno_hdmi_phy_configure(struct phy *phy,
+ union phy_configure_opts *opts)
+{
+ struct inno_hdmi_phy *inno = phy_get_drvdata(phy);
+ int ret;
+
+ ret = inno_hdmi_phy_validate(phy, phy_get_mode(phy),
+ PHY_HDMI_MODE_TMDS, opts);
+ if (ret)
+ return ret;
+
+ inno->opts_tmds_char_rate = opts->hdmi.tmds_char_rate;
+
+ return 0;
+}
+
+static int inno_hdmi_phy_power_on(struct phy *phy)
+{
+ struct inno_hdmi_phy *inno = phy_get_drvdata(phy);
+ const struct post_pll_config *cfg = post_pll_cfg_table;
+ const struct phy_config *phy_cfg = inno->plat_data->phy_cfg_table;
+ unsigned long tmdsclock = inno_hdmi_phy_get_tmdsclk(inno,
+ inno->pixclock);
+ int ret;
+
+ if (!tmdsclock) {
+ dev_err(inno->dev, "TMDS clock is zero!\n");
+ return -EINVAL;
+ }
+
+ if (!inno->plat_data->ops->power_on)
+ return -EINVAL;
+
+ for (; cfg->tmdsclock != 0; cfg++)
+ if (tmdsclock <= cfg->tmdsclock &&
+ cfg->version & inno->chip_version)
+ break;
+
+ for (; phy_cfg->tmdsclock != 0; phy_cfg++)
+ if (tmdsclock <= phy_cfg->tmdsclock)
+ break;
+
+ if (cfg->tmdsclock == 0 || phy_cfg->tmdsclock == 0)
+ return -EINVAL;
+
+ dev_dbg(inno->dev, "Inno HDMI PHY Power On\n");
+
+ inno->plat_data->clk_ops->set_rate(&inno->hw, inno->pixclock, 24000000);
+
+ ret = clk_prepare_enable(inno->phyclk);
+ if (ret)
+ return ret;
+
+ ret = inno->plat_data->ops->power_on(inno, cfg, phy_cfg);
+ if (ret) {
+ clk_disable_unprepare(inno->phyclk);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int inno_hdmi_phy_power_off(struct phy *phy)
+{
+ struct inno_hdmi_phy *inno = phy_get_drvdata(phy);
+
+ if (!inno->plat_data->ops->power_off)
+ return -EINVAL;
+
+ inno->plat_data->ops->power_off(inno);
+
+ clk_disable_unprepare(inno->phyclk);
+
+ inno->tmdsclock = 0;
+
+ dev_dbg(inno->dev, "Inno HDMI PHY Power Off\n");
+
+ return 0;
+}
+
+static const struct phy_ops inno_hdmi_phy_ops = {
+ .owner = THIS_MODULE,
+ .power_on = inno_hdmi_phy_power_on,
+ .power_off = inno_hdmi_phy_power_off,
+ .configure = inno_hdmi_phy_configure,
+ .validate = inno_hdmi_phy_validate,
+};
+
+static const
+struct pre_pll_config *inno_hdmi_phy_get_pre_pll_cfg(struct inno_hdmi_phy *inno,
+ unsigned long rate)
+{
+ const struct pre_pll_config *cfg = pre_pll_cfg_table;
+ unsigned long tmdsclock = inno_hdmi_phy_get_tmdsclk(inno, rate);
+
+ for (; cfg->pixclock != 0; cfg++)
+ if (cfg->pixclock == rate && cfg->tmdsclock == tmdsclock)
+ break;
+
+ if (cfg->pixclock == 0)
+ return ERR_PTR(-EINVAL);
+
+ return cfg;
+}
+
+static int inno_hdmi_phy_rk3228_clk_is_prepared(struct clk_hw *hw)
+{
+ struct inno_hdmi_phy *inno = to_inno_hdmi_phy(hw);
+ u8 status;
+
+ status = inno_read(inno, 0xe0) & RK3228_PRE_PLL_POWER_DOWN;
+ return status ? 0 : 1;
+}
+
+static int inno_hdmi_phy_rk3228_clk_prepare(struct clk_hw *hw)
+{
+ struct inno_hdmi_phy *inno = to_inno_hdmi_phy(hw);
+
+ inno_update_bits(inno, 0xe0, RK3228_PRE_PLL_POWER_DOWN, 0);
+ return 0;
+}
+
+static void inno_hdmi_phy_rk3228_clk_unprepare(struct clk_hw *hw)
+{
+ struct inno_hdmi_phy *inno = to_inno_hdmi_phy(hw);
+
+ inno_update_bits(inno, 0xe0, RK3228_PRE_PLL_POWER_DOWN,
+ RK3228_PRE_PLL_POWER_DOWN);
+}
+
+static
+unsigned long inno_hdmi_phy_rk3228_clk_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct inno_hdmi_phy *inno = to_inno_hdmi_phy(hw);
+ u8 nd, no_a, no_b, no_d;
+ u64 vco;
+ u16 nf;
+
+ nd = inno_read(inno, 0xe2) & RK3228_PRE_PLL_PRE_DIV_MASK;
+ nf = (inno_read(inno, 0xe2) & RK3228_PRE_PLL_FB_DIV_8_MASK) << 1;
+ nf |= inno_read(inno, 0xe3);
+ vco = parent_rate * nf;
+
+ if (inno_read(inno, 0xe2) & RK3228_PCLK_VCO_DIV_5_MASK) {
+ do_div(vco, nd * 5);
+ } else {
+ no_a = inno_read(inno, 0xe4) & RK3228_PRE_PLL_PCLK_DIV_A_MASK;
+ if (!no_a)
+ no_a = 1;
+ no_b = inno_read(inno, 0xe4) & RK3228_PRE_PLL_PCLK_DIV_B_MASK;
+ no_b >>= RK3228_PRE_PLL_PCLK_DIV_B_SHIFT;
+ no_b += 2;
+ no_d = inno_read(inno, 0xe5) & RK3228_PRE_PLL_PCLK_DIV_D_MASK;
+
+ do_div(vco, (nd * (no_a == 1 ? no_b : no_a) * no_d * 2));
+ }
+
+ inno->pixclock = vco;
+
+ dev_dbg(inno->dev, "%s rate %lu\n", __func__, inno->pixclock);
+
+ return vco;
+}
+
+static int inno_hdmi_phy_rk3228_clk_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ const struct pre_pll_config *cfg = pre_pll_cfg_table;
+
+ req->rate = (req->rate / 1000) * 1000;
+
+ for (; cfg->pixclock != 0; cfg++)
+ if (cfg->pixclock == req->rate && !cfg->fracdiv)
+ break;
+
+ if (cfg->pixclock == 0)
+ return -EINVAL;
+
+ req->rate = cfg->pixclock;
+
+ return 0;
+}
+
+static int inno_hdmi_phy_rk3228_clk_set_rate(struct clk_hw *hw,
+ unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct inno_hdmi_phy *inno = to_inno_hdmi_phy(hw);
+ const struct pre_pll_config *cfg;
+ unsigned long tmdsclock = inno_hdmi_phy_get_tmdsclk(inno, rate);
+ u32 v;
+ int ret;
+
+ dev_dbg(inno->dev, "%s rate %lu tmdsclk %lu\n",
+ __func__, rate, tmdsclock);
+
+ if (inno->pixclock == rate && inno->tmdsclock == tmdsclock)
+ return 0;
+
+ cfg = inno_hdmi_phy_get_pre_pll_cfg(inno, rate);
+ if (IS_ERR(cfg))
+ return PTR_ERR(cfg);
+
+ /* Power down PRE-PLL */
+ inno_update_bits(inno, 0xe0, RK3228_PRE_PLL_POWER_DOWN,
+ RK3228_PRE_PLL_POWER_DOWN);
+
+ inno_update_bits(inno, 0xe2, RK3228_PRE_PLL_FB_DIV_8_MASK |
+ RK3228_PCLK_VCO_DIV_5_MASK |
+ RK3228_PRE_PLL_PRE_DIV_MASK,
+ RK3228_PRE_PLL_FB_DIV_8(cfg->fbdiv) |
+ RK3228_PCLK_VCO_DIV_5(cfg->vco_div_5_en) |
+ RK3228_PRE_PLL_PRE_DIV(cfg->prediv));
+ inno_write(inno, 0xe3, RK3228_PRE_PLL_FB_DIV_7_0(cfg->fbdiv));
+ inno_update_bits(inno, 0xe4, RK3228_PRE_PLL_PCLK_DIV_B_MASK |
+ RK3228_PRE_PLL_PCLK_DIV_A_MASK,
+ RK3228_PRE_PLL_PCLK_DIV_B(cfg->pclk_div_b) |
+ RK3228_PRE_PLL_PCLK_DIV_A(cfg->pclk_div_a));
+ inno_update_bits(inno, 0xe5, RK3228_PRE_PLL_PCLK_DIV_C_MASK |
+ RK3228_PRE_PLL_PCLK_DIV_D_MASK,
+ RK3228_PRE_PLL_PCLK_DIV_C(cfg->pclk_div_c) |
+ RK3228_PRE_PLL_PCLK_DIV_D(cfg->pclk_div_d));
+ inno_update_bits(inno, 0xe6, RK3228_PRE_PLL_TMDSCLK_DIV_C_MASK |
+ RK3228_PRE_PLL_TMDSCLK_DIV_A_MASK |
+ RK3228_PRE_PLL_TMDSCLK_DIV_B_MASK,
+ RK3228_PRE_PLL_TMDSCLK_DIV_C(cfg->tmds_div_c) |
+ RK3228_PRE_PLL_TMDSCLK_DIV_A(cfg->tmds_div_a) |
+ RK3228_PRE_PLL_TMDSCLK_DIV_B(cfg->tmds_div_b));
+
+ /* Power up PRE-PLL */
+ inno_update_bits(inno, 0xe0, RK3228_PRE_PLL_POWER_DOWN, 0);
+
+ /* Wait for Pre-PLL lock */
+ ret = inno_poll(inno, 0xe8, v, v & RK3228_PRE_PLL_LOCK_STATUS,
+ 100, 100000);
+ if (ret) {
+ dev_err(inno->dev, "Pre-PLL locking failed\n");
+ return ret;
+ }
+
+ inno->pixclock = rate;
+ inno->tmdsclock = tmdsclock;
+
+ return 0;
+}
+
+static const struct clk_ops inno_hdmi_phy_rk3228_clk_ops = {
+ .prepare = inno_hdmi_phy_rk3228_clk_prepare,
+ .unprepare = inno_hdmi_phy_rk3228_clk_unprepare,
+ .is_prepared = inno_hdmi_phy_rk3228_clk_is_prepared,
+ .recalc_rate = inno_hdmi_phy_rk3228_clk_recalc_rate,
+ .determine_rate = inno_hdmi_phy_rk3228_clk_determine_rate,
+ .set_rate = inno_hdmi_phy_rk3228_clk_set_rate,
+};
+
+static int inno_hdmi_phy_rk3328_clk_is_prepared(struct clk_hw *hw)
+{
+ struct inno_hdmi_phy *inno = to_inno_hdmi_phy(hw);
+ u8 status;
+
+ status = inno_read(inno, 0xa0) & RK3328_PRE_PLL_POWER_DOWN;
+ return status ? 0 : 1;
+}
+
+static int inno_hdmi_phy_rk3328_clk_prepare(struct clk_hw *hw)
+{
+ struct inno_hdmi_phy *inno = to_inno_hdmi_phy(hw);
+
+ inno_update_bits(inno, 0xa0, RK3328_PRE_PLL_POWER_DOWN, 0);
+ return 0;
+}
+
+static void inno_hdmi_phy_rk3328_clk_unprepare(struct clk_hw *hw)
+{
+ struct inno_hdmi_phy *inno = to_inno_hdmi_phy(hw);
+
+ inno_update_bits(inno, 0xa0, RK3328_PRE_PLL_POWER_DOWN,
+ RK3328_PRE_PLL_POWER_DOWN);
+}
+
+static
+unsigned long inno_hdmi_phy_rk3328_clk_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct inno_hdmi_phy *inno = to_inno_hdmi_phy(hw);
+ unsigned long frac;
+ u8 nd, no_a, no_b, no_d;
+ u64 vco;
+ u16 nf;
+
+ nd = inno_read(inno, 0xa1) & RK3328_PRE_PLL_PRE_DIV_MASK;
+ nf = ((inno_read(inno, 0xa2) & RK3328_PRE_PLL_FB_DIV_11_8_MASK) << 8);
+ nf |= inno_read(inno, 0xa3);
+ vco = parent_rate * nf;
+
+ if (!(inno_read(inno, 0xa2) & RK3328_PRE_PLL_FRAC_DIV_DISABLE)) {
+ frac = inno_read(inno, 0xd3) |
+ (inno_read(inno, 0xd2) << 8) |
+ (inno_read(inno, 0xd1) << 16);
+ vco += DIV_ROUND_CLOSEST(parent_rate * frac, (1 << 24));
+ }
+
+ if (inno_read(inno, 0xa0) & RK3328_PCLK_VCO_DIV_5_MASK) {
+ do_div(vco, nd * 5);
+ } else {
+ no_a = inno_read(inno, 0xa5) & RK3328_PRE_PLL_PCLK_DIV_A_MASK;
+ no_b = inno_read(inno, 0xa5) & RK3328_PRE_PLL_PCLK_DIV_B_MASK;
+ no_b >>= RK3328_PRE_PLL_PCLK_DIV_B_SHIFT;
+ no_b += 2;
+ no_d = inno_read(inno, 0xa6) & RK3328_PRE_PLL_PCLK_DIV_D_MASK;
+
+ do_div(vco, (nd * (no_a == 1 ? no_b : no_a) * no_d * 2));
+ }
+
+ inno->pixclock = DIV_ROUND_CLOSEST((unsigned long)vco, 1000) * 1000;
+
+ dev_dbg(inno->dev, "%s rate %lu vco %llu\n",
+ __func__, inno->pixclock, vco);
+
+ return inno->pixclock;
+}
+
+static int inno_hdmi_phy_rk3328_clk_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ const struct pre_pll_config *cfg = pre_pll_cfg_table;
+
+ req->rate = (req->rate / 1000) * 1000;
+
+ for (; cfg->pixclock != 0; cfg++)
+ if (cfg->pixclock == req->rate)
+ break;
+
+ if (cfg->pixclock == 0)
+ return -EINVAL;
+
+ req->rate = cfg->pixclock;
+
+ return 0;
+}
+
+static int inno_hdmi_phy_rk3328_clk_set_rate(struct clk_hw *hw,
+ unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct inno_hdmi_phy *inno = to_inno_hdmi_phy(hw);
+ const struct pre_pll_config *cfg;
+ unsigned long tmdsclock = inno_hdmi_phy_get_tmdsclk(inno, rate);
+ u32 val;
+ int ret;
+
+ dev_dbg(inno->dev, "%s rate %lu tmdsclk %lu\n",
+ __func__, rate, tmdsclock);
+
+ if (inno->pixclock == rate && inno->tmdsclock == tmdsclock)
+ return 0;
+
+ cfg = inno_hdmi_phy_get_pre_pll_cfg(inno, rate);
+ if (IS_ERR(cfg))
+ return PTR_ERR(cfg);
+
+ inno_update_bits(inno, 0xa0, RK3328_PRE_PLL_POWER_DOWN,
+ RK3328_PRE_PLL_POWER_DOWN);
+
+ /* Configure pre-pll */
+ inno_update_bits(inno, 0xa0, RK3328_PCLK_VCO_DIV_5_MASK,
+ RK3328_PCLK_VCO_DIV_5(cfg->vco_div_5_en));
+ inno_write(inno, 0xa1, RK3328_PRE_PLL_PRE_DIV(cfg->prediv));
+
+ val = RK3328_SPREAD_SPECTRUM_MOD_DISABLE;
+ if (!cfg->fracdiv)
+ val |= RK3328_PRE_PLL_FRAC_DIV_DISABLE;
+ inno_write(inno, 0xa2, RK3328_PRE_PLL_FB_DIV_11_8(cfg->fbdiv) | val);
+ inno_write(inno, 0xa3, RK3328_PRE_PLL_FB_DIV_7_0(cfg->fbdiv));
+ inno_write(inno, 0xa5, RK3328_PRE_PLL_PCLK_DIV_A(cfg->pclk_div_a) |
+ RK3328_PRE_PLL_PCLK_DIV_B(cfg->pclk_div_b));
+ inno_write(inno, 0xa6, RK3328_PRE_PLL_PCLK_DIV_C(cfg->pclk_div_c) |
+ RK3328_PRE_PLL_PCLK_DIV_D(cfg->pclk_div_d));
+ inno_write(inno, 0xa4, RK3328_PRE_PLL_TMDSCLK_DIV_C(cfg->tmds_div_c) |
+ RK3328_PRE_PLL_TMDSCLK_DIV_A(cfg->tmds_div_a) |
+ RK3328_PRE_PLL_TMDSCLK_DIV_B(cfg->tmds_div_b));
+ inno_write(inno, 0xd3, RK3328_PRE_PLL_FRAC_DIV_7_0(cfg->fracdiv));
+ inno_write(inno, 0xd2, RK3328_PRE_PLL_FRAC_DIV_15_8(cfg->fracdiv));
+ inno_write(inno, 0xd1, RK3328_PRE_PLL_FRAC_DIV_23_16(cfg->fracdiv));
+
+ inno_update_bits(inno, 0xa0, RK3328_PRE_PLL_POWER_DOWN, 0);
+
+ /* Wait for Pre-PLL lock */
+ ret = inno_poll(inno, 0xa9, val, val & RK3328_PRE_PLL_LOCK_STATUS,
+ 1000, 10000);
+ if (ret) {
+ dev_err(inno->dev, "Pre-PLL locking failed\n");
+ return ret;
+ }
+
+ inno->pixclock = rate;
+ inno->tmdsclock = tmdsclock;
+
+ return 0;
+}
+
+static const struct clk_ops inno_hdmi_phy_rk3328_clk_ops = {
+ .prepare = inno_hdmi_phy_rk3328_clk_prepare,
+ .unprepare = inno_hdmi_phy_rk3328_clk_unprepare,
+ .is_prepared = inno_hdmi_phy_rk3328_clk_is_prepared,
+ .recalc_rate = inno_hdmi_phy_rk3328_clk_recalc_rate,
+ .determine_rate = inno_hdmi_phy_rk3328_clk_determine_rate,
+ .set_rate = inno_hdmi_phy_rk3328_clk_set_rate,
+};
+
+static int inno_hdmi_phy_clk_register(struct inno_hdmi_phy *inno)
+{
+ struct device *dev = inno->dev;
+ struct device_node *np = dev->of_node;
+ struct clk_init_data init;
+ const char *parent_name;
+ int ret;
+
+ parent_name = __clk_get_name(inno->refoclk);
+
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+ init.flags = 0;
+ init.name = "pin_hd20_pclk";
+ init.ops = inno->plat_data->clk_ops;
+
+ /* optional override of the clock name */
+ of_property_read_string(np, "clock-output-names", &init.name);
+
+ inno->hw.init = &init;
+
+ inno->phyclk = devm_clk_register(dev, &inno->hw);
+ if (IS_ERR(inno->phyclk)) {
+ ret = PTR_ERR(inno->phyclk);
+ dev_err(dev, "failed to register clock: %d\n", ret);
+ return ret;
+ }
+
+ ret = of_clk_add_provider(np, of_clk_src_simple_get, inno->phyclk);
+ if (ret) {
+ dev_err(dev, "failed to register clock provider: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int inno_hdmi_phy_rk3228_init(struct inno_hdmi_phy *inno)
+{
+ /*
+ * Use phy internal register control
+ * rxsense/poweron/pllpd/pdataen signal.
+ */
+ inno_write(inno, 0x01, RK3228_BYPASS_RXSENSE_EN |
+ RK3228_BYPASS_PWRON_EN |
+ RK3228_BYPASS_PLLPD_EN);
+ inno_update_bits(inno, 0x02, RK3228_BYPASS_PDATA_EN,
+ RK3228_BYPASS_PDATA_EN);
+
+ /* manual power down post-PLL */
+ inno_update_bits(inno, 0xaa, RK3228_POST_PLL_CTRL_MANUAL,
+ RK3228_POST_PLL_CTRL_MANUAL);
+
+ inno->chip_version = 1;
+
+ return 0;
+}
+
+static int
+inno_hdmi_phy_rk3228_power_on(struct inno_hdmi_phy *inno,
+ const struct post_pll_config *cfg,
+ const struct phy_config *phy_cfg)
+{
+ int ret;
+ u32 v;
+
+ inno_update_bits(inno, 0x02, RK3228_PDATAEN_DISABLE,
+ RK3228_PDATAEN_DISABLE);
+ inno_update_bits(inno, 0xe0, RK3228_PRE_PLL_POWER_DOWN |
+ RK3228_POST_PLL_POWER_DOWN,
+ RK3228_PRE_PLL_POWER_DOWN |
+ RK3228_POST_PLL_POWER_DOWN);
+
+ /* Post-PLL update */
+ inno_update_bits(inno, 0xe9, RK3228_POST_PLL_PRE_DIV_MASK,
+ RK3228_POST_PLL_PRE_DIV(cfg->prediv));
+ inno_update_bits(inno, 0xeb, RK3228_POST_PLL_FB_DIV_8_MASK,
+ RK3228_POST_PLL_FB_DIV_8(cfg->fbdiv));
+ inno_write(inno, 0xea, RK3228_POST_PLL_FB_DIV_7_0(cfg->fbdiv));
+
+ if (cfg->postdiv == 1) {
+ inno_update_bits(inno, 0xe9, RK3228_POST_PLL_POST_DIV_ENABLE,
+ 0);
+ } else {
+ int div = cfg->postdiv / 2 - 1;
+
+ inno_update_bits(inno, 0xe9, RK3228_POST_PLL_POST_DIV_ENABLE,
+ RK3228_POST_PLL_POST_DIV_ENABLE);
+ inno_update_bits(inno, 0xeb, RK3228_POST_PLL_POST_DIV_MASK,
+ RK3228_POST_PLL_POST_DIV(div));
+ }
+
+ for (v = 0; v < 4; v++)
+ inno_write(inno, 0xef + v, phy_cfg->regs[v]);
+
+ inno_update_bits(inno, 0xe0, RK3228_PRE_PLL_POWER_DOWN |
+ RK3228_POST_PLL_POWER_DOWN, 0);
+ inno_update_bits(inno, 0xe1, RK3228_BANDGAP_ENABLE,
+ RK3228_BANDGAP_ENABLE);
+ inno_update_bits(inno, 0xe1, RK3228_TMDS_DRIVER_ENABLE,
+ RK3228_TMDS_DRIVER_ENABLE);
+
+ /* Wait for post PLL lock */
+ ret = inno_poll(inno, 0xeb, v, v & RK3228_POST_PLL_LOCK_STATUS,
+ 100, 100000);
+ if (ret) {
+ dev_err(inno->dev, "Post-PLL locking failed\n");
+ return ret;
+ }
+
+ if (cfg->tmdsclock > 340000000)
+ msleep(100);
+
+ inno_update_bits(inno, 0x02, RK3228_PDATAEN_DISABLE, 0);
+ return 0;
+}
+
+static void inno_hdmi_phy_rk3228_power_off(struct inno_hdmi_phy *inno)
+{
+ inno_update_bits(inno, 0xe1, RK3228_TMDS_DRIVER_ENABLE, 0);
+ inno_update_bits(inno, 0xe1, RK3228_BANDGAP_ENABLE, 0);
+ inno_update_bits(inno, 0xe0, RK3228_POST_PLL_POWER_DOWN,
+ RK3228_POST_PLL_POWER_DOWN);
+}
+
+static const struct inno_hdmi_phy_ops rk3228_hdmi_phy_ops = {
+ .init = inno_hdmi_phy_rk3228_init,
+ .power_on = inno_hdmi_phy_rk3228_power_on,
+ .power_off = inno_hdmi_phy_rk3228_power_off,
+};
+
+static int inno_hdmi_phy_rk3328_init(struct inno_hdmi_phy *inno)
+{
+ struct nvmem_cell *cell;
+ unsigned char *efuse_buf;
+ size_t len;
+
+ /*
+ * Use phy internal register control
+ * rxsense/poweron/pllpd/pdataen signal.
+ */
+ inno_write(inno, 0x01, RK3328_BYPASS_RXSENSE_EN |
+ RK3328_BYPASS_POWERON_EN |
+ RK3328_BYPASS_PLLPD_EN);
+ inno_write(inno, 0x02, RK3328_INT_POL_HIGH | RK3328_BYPASS_PDATA_EN |
+ RK3328_PDATA_EN);
+
+ /* Disable phy irq */
+ inno_write(inno, 0x05, 0);
+ inno_write(inno, 0x07, 0);
+
+ /* try to read the chip-version */
+ inno->chip_version = 1;
+ cell = nvmem_cell_get(inno->dev, "cpu-version");
+ if (IS_ERR(cell)) {
+ if (PTR_ERR(cell) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+
+ return 0;
+ }
+
+ efuse_buf = nvmem_cell_read(cell, &len);
+ nvmem_cell_put(cell);
+
+ if (IS_ERR(efuse_buf))
+ return 0;
+ if (len == 1)
+ inno->chip_version = efuse_buf[0] + 1;
+ kfree(efuse_buf);
+
+ return 0;
+}
+
+static int
+inno_hdmi_phy_rk3328_power_on(struct inno_hdmi_phy *inno,
+ const struct post_pll_config *cfg,
+ const struct phy_config *phy_cfg)
+{
+ int ret;
+ u32 v;
+
+ inno_update_bits(inno, 0x02, RK3328_PDATA_EN, 0);
+ inno_update_bits(inno, 0xaa, RK3328_POST_PLL_POWER_DOWN,
+ RK3328_POST_PLL_POWER_DOWN);
+
+ inno_write(inno, 0xac, RK3328_POST_PLL_FB_DIV_7_0(cfg->fbdiv));
+ if (cfg->postdiv == 1) {
+ inno_write(inno, 0xab, RK3328_POST_PLL_FB_DIV_8(cfg->fbdiv) |
+ RK3328_POST_PLL_PRE_DIV(cfg->prediv));
+ inno_write(inno, 0xaa, RK3328_POST_PLL_REFCLK_SEL_TMDS |
+ RK3328_POST_PLL_POWER_DOWN);
+ } else {
+ v = (cfg->postdiv / 2) - 1;
+ v &= RK3328_POST_PLL_POST_DIV_MASK;
+ inno_write(inno, 0xad, v);
+ inno_write(inno, 0xab, RK3328_POST_PLL_FB_DIV_8(cfg->fbdiv) |
+ RK3328_POST_PLL_PRE_DIV(cfg->prediv));
+ inno_write(inno, 0xaa, RK3328_POST_PLL_POST_DIV_ENABLE |
+ RK3328_POST_PLL_REFCLK_SEL_TMDS |
+ RK3328_POST_PLL_POWER_DOWN);
+ }
+
+ for (v = 0; v < 14; v++)
+ inno_write(inno, 0xb5 + v, phy_cfg->regs[v]);
+
+ /* set ESD detection threshold for TMDS CLK, D2, D1 and D0 */
+ for (v = 0; v < 4; v++)
+ inno_update_bits(inno, 0xc8 + v, RK3328_ESD_DETECT_MASK,
+ RK3328_ESD_DETECT_340MV);
+
+ if (phy_cfg->tmdsclock > 340000000) {
+ /* Set termination resistor to 100ohm */
+ v = clk_get_rate(inno->sysclk) / 100000;
+ inno_write(inno, 0xc5, RK3328_TERM_RESISTOR_CALIB_SPEED_14_8(v)
+ | RK3328_BYPASS_TERM_RESISTOR_CALIB);
+ inno_write(inno, 0xc6, RK3328_TERM_RESISTOR_CALIB_SPEED_7_0(v));
+ inno_write(inno, 0xc7, RK3328_TERM_RESISTOR_100);
+ inno_update_bits(inno, 0xc5,
+ RK3328_BYPASS_TERM_RESISTOR_CALIB, 0);
+ } else {
+ inno_write(inno, 0xc5, RK3328_BYPASS_TERM_RESISTOR_CALIB);
+
+ /* clk termination resistor is 50ohm (parallel resistors) */
+ if (phy_cfg->tmdsclock > 165000000)
+ inno_update_bits(inno, 0xc8,
+ RK3328_TMDS_TERM_RESIST_MASK,
+ RK3328_TMDS_TERM_RESIST_75 |
+ RK3328_TMDS_TERM_RESIST_150);
+
+ /* data termination resistor for D2, D1 and D0 is 150ohm */
+ for (v = 0; v < 3; v++)
+ inno_update_bits(inno, 0xc9 + v,
+ RK3328_TMDS_TERM_RESIST_MASK,
+ RK3328_TMDS_TERM_RESIST_150);
+ }
+
+ inno_update_bits(inno, 0xaa, RK3328_POST_PLL_POWER_DOWN, 0);
+ inno_update_bits(inno, 0xb0, RK3328_BANDGAP_ENABLE,
+ RK3328_BANDGAP_ENABLE);
+ inno_update_bits(inno, 0xb2, RK3328_TMDS_DRIVER_ENABLE,
+ RK3328_TMDS_DRIVER_ENABLE);
+
+ /* Wait for post PLL lock */
+ ret = inno_poll(inno, 0xaf, v, v & RK3328_POST_PLL_LOCK_STATUS,
+ 1000, 10000);
+ if (ret) {
+ dev_err(inno->dev, "Post-PLL locking failed\n");
+ return ret;
+ }
+
+ if (phy_cfg->tmdsclock > 340000000)
+ msleep(100);
+
+ inno_update_bits(inno, 0x02, RK3328_PDATA_EN, RK3328_PDATA_EN);
+
+ /* Enable PHY IRQ */
+ inno_write(inno, 0x05, RK3328_INT_TMDS_CLK(RK3328_INT_VSS_AGND_ESD_DET)
+ | RK3328_INT_TMDS_D2(RK3328_INT_VSS_AGND_ESD_DET));
+ inno_write(inno, 0x07, RK3328_INT_TMDS_D1(RK3328_INT_VSS_AGND_ESD_DET)
+ | RK3328_INT_TMDS_D0(RK3328_INT_VSS_AGND_ESD_DET));
+ return 0;
+}
+
+static void inno_hdmi_phy_rk3328_power_off(struct inno_hdmi_phy *inno)
+{
+ inno_update_bits(inno, 0xb2, RK3328_TMDS_DRIVER_ENABLE, 0);
+ inno_update_bits(inno, 0xb0, RK3328_BANDGAP_ENABLE, 0);
+ inno_update_bits(inno, 0xaa, RK3328_POST_PLL_POWER_DOWN,
+ RK3328_POST_PLL_POWER_DOWN);
+
+ /* Disable PHY IRQ */
+ inno_write(inno, 0x05, 0);
+ inno_write(inno, 0x07, 0);
+}
+
+static const struct inno_hdmi_phy_ops rk3328_hdmi_phy_ops = {
+ .init = inno_hdmi_phy_rk3328_init,
+ .power_on = inno_hdmi_phy_rk3328_power_on,
+ .power_off = inno_hdmi_phy_rk3328_power_off,
+};
+
+static const struct inno_hdmi_phy_drv_data rk3228_hdmi_phy_drv_data = {
+ .ops = &rk3228_hdmi_phy_ops,
+ .clk_ops = &inno_hdmi_phy_rk3228_clk_ops,
+ .phy_cfg_table = rk3228_phy_cfg,
+};
+
+static const struct inno_hdmi_phy_drv_data rk3328_hdmi_phy_drv_data = {
+ .ops = &rk3328_hdmi_phy_ops,
+ .clk_ops = &inno_hdmi_phy_rk3328_clk_ops,
+ .phy_cfg_table = rk3328_phy_cfg,
+};
+
+static const struct regmap_config inno_hdmi_phy_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = 0x400,
+};
+
+static void inno_hdmi_phy_action(void *data)
+{
+ struct inno_hdmi_phy *inno = data;
+
+ clk_disable_unprepare(inno->refpclk);
+ clk_disable_unprepare(inno->sysclk);
+}
+
+static int inno_hdmi_phy_probe(struct platform_device *pdev)
+{
+ struct inno_hdmi_phy *inno;
+ struct phy_provider *phy_provider;
+ void __iomem *regs;
+ int ret;
+
+ inno = devm_kzalloc(&pdev->dev, sizeof(*inno), GFP_KERNEL);
+ if (!inno)
+ return -ENOMEM;
+
+ inno->dev = &pdev->dev;
+
+ inno->plat_data = of_device_get_match_data(inno->dev);
+ if (!inno->plat_data || !inno->plat_data->ops)
+ return -EINVAL;
+
+ regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ inno->sysclk = devm_clk_get(inno->dev, "sysclk");
+ if (IS_ERR(inno->sysclk)) {
+ ret = PTR_ERR(inno->sysclk);
+ dev_err(inno->dev, "failed to get sysclk: %d\n", ret);
+ return ret;
+ }
+
+ inno->refpclk = devm_clk_get(inno->dev, "refpclk");
+ if (IS_ERR(inno->refpclk)) {
+ ret = PTR_ERR(inno->refpclk);
+ dev_err(inno->dev, "failed to get ref clock: %d\n", ret);
+ return ret;
+ }
+
+ inno->refoclk = devm_clk_get(inno->dev, "refoclk");
+ if (IS_ERR(inno->refoclk)) {
+ ret = PTR_ERR(inno->refoclk);
+ dev_err(inno->dev, "failed to get oscillator-ref clock: %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(inno->sysclk);
+ if (ret) {
+ dev_err(inno->dev, "Cannot enable inno phy sysclk: %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Refpclk needs to be on, on at least the rk3328 for still
+ * unknown reasons.
+ */
+ ret = clk_prepare_enable(inno->refpclk);
+ if (ret) {
+ dev_err(inno->dev, "failed to enable refpclk\n");
+ clk_disable_unprepare(inno->sysclk);
+ return ret;
+ }
+
+ ret = devm_add_action_or_reset(inno->dev, inno_hdmi_phy_action,
+ inno);
+ if (ret)
+ return ret;
+
+ inno->regmap = devm_regmap_init_mmio(inno->dev, regs,
+ &inno_hdmi_phy_regmap_config);
+ if (IS_ERR(inno->regmap))
+ return PTR_ERR(inno->regmap);
+
+ /* only the newer rk3328 hdmiphy has an interrupt */
+ inno->irq = platform_get_irq(pdev, 0);
+ if (inno->irq > 0) {
+ ret = devm_request_threaded_irq(inno->dev, inno->irq,
+ inno_hdmi_phy_rk3328_hardirq,
+ inno_hdmi_phy_rk3328_irq,
+ IRQF_SHARED,
+ dev_name(inno->dev), inno);
+ if (ret)
+ return ret;
+ }
+
+ inno->phy = devm_phy_create(inno->dev, NULL, &inno_hdmi_phy_ops);
+ if (IS_ERR(inno->phy)) {
+ dev_err(inno->dev, "failed to create HDMI PHY\n");
+ return PTR_ERR(inno->phy);
+ }
+
+ phy_set_drvdata(inno->phy, inno);
+ phy_set_mode_ext(inno->phy, PHY_MODE_HDMI, PHY_HDMI_MODE_TMDS);
+
+ if (inno->plat_data->ops->init) {
+ ret = inno->plat_data->ops->init(inno);
+ if (ret)
+ return ret;
+ }
+
+ ret = inno_hdmi_phy_clk_register(inno);
+ if (ret)
+ return ret;
+
+ phy_provider = devm_of_phy_provider_register(inno->dev,
+ of_phy_simple_xlate);
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static void inno_hdmi_phy_remove(struct platform_device *pdev)
+{
+ of_clk_del_provider(pdev->dev.of_node);
+}
+
+static const struct of_device_id inno_hdmi_phy_of_match[] = {
+ {
+ .compatible = "rockchip,rk3228-hdmi-phy",
+ .data = &rk3228_hdmi_phy_drv_data
+ }, {
+ .compatible = "rockchip,rk3328-hdmi-phy",
+ .data = &rk3328_hdmi_phy_drv_data
+ }, { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, inno_hdmi_phy_of_match);
+
+static struct platform_driver inno_hdmi_phy_driver = {
+ .probe = inno_hdmi_phy_probe,
+ .remove = inno_hdmi_phy_remove,
+ .driver = {
+ .name = "inno-hdmi-phy",
+ .of_match_table = inno_hdmi_phy_of_match,
+ },
+};
+module_platform_driver(inno_hdmi_phy_driver);
+
+MODULE_AUTHOR("Zheng Yang <zhengyang@rock-chips.com>");
+MODULE_DESCRIPTION("Innosilion HDMI 2.0 Transmitter PHY Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c
new file mode 100644
index 000000000..7d8a533f2
--- /dev/null
+++ b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c
@@ -0,0 +1,2399 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Rockchip USB2.0 PHY with Innosilicon IP block driver
+ *
+ * Copyright (C) 2016 Fuzhou Rockchip Electronics Co., Ltd
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/extcon-provider.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/gpio/consumer.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/mfd/syscon.h>
+#include <linux/usb/of.h>
+#include <linux/usb/otg.h>
+
+#define BIT_WRITEABLE_SHIFT 16
+#define SCHEDULE_DELAY (60 * HZ)
+#define OTG_SCHEDULE_DELAY (2 * HZ)
+
+struct rockchip_usb2phy;
+
+enum rockchip_usb2phy_port_id {
+ USB2PHY_PORT_OTG,
+ USB2PHY_PORT_HOST,
+ USB2PHY_NUM_PORTS,
+};
+
+enum rockchip_usb2phy_host_state {
+ PHY_STATE_HS_ONLINE = 0,
+ PHY_STATE_DISCONNECT = 1,
+ PHY_STATE_CONNECT = 2,
+ PHY_STATE_FS_LS_ONLINE = 4,
+};
+
+/**
+ * enum usb_chg_state - Different states involved in USB charger detection.
+ * @USB_CHG_STATE_UNDEFINED: USB charger is not connected or detection
+ * process is not yet started.
+ * @USB_CHG_STATE_WAIT_FOR_DCD: Waiting for Data pins contact.
+ * @USB_CHG_STATE_DCD_DONE: Data pin contact is detected.
+ * @USB_CHG_STATE_PRIMARY_DONE: Primary detection is completed (Detects
+ * between SDP and DCP/CDP).
+ * @USB_CHG_STATE_SECONDARY_DONE: Secondary detection is completed (Detects
+ * between DCP and CDP).
+ * @USB_CHG_STATE_DETECTED: USB charger type is determined.
+ */
+enum usb_chg_state {
+ USB_CHG_STATE_UNDEFINED = 0,
+ USB_CHG_STATE_WAIT_FOR_DCD,
+ USB_CHG_STATE_DCD_DONE,
+ USB_CHG_STATE_PRIMARY_DONE,
+ USB_CHG_STATE_SECONDARY_DONE,
+ USB_CHG_STATE_DETECTED,
+};
+
+static const unsigned int rockchip_usb2phy_extcon_cable[] = {
+ EXTCON_USB,
+ EXTCON_USB_HOST,
+ EXTCON_CHG_USB_SDP,
+ EXTCON_CHG_USB_CDP,
+ EXTCON_CHG_USB_DCP,
+ EXTCON_CHG_USB_SLOW,
+ EXTCON_NONE,
+};
+
+struct usb2phy_reg {
+ unsigned int offset;
+ unsigned int bitend;
+ unsigned int bitstart;
+ unsigned int disable;
+ unsigned int enable;
+};
+
+/**
+ * struct rockchip_chg_det_reg - usb charger detect registers
+ * @cp_det: charging port detected successfully.
+ * @dcp_det: dedicated charging port detected successfully.
+ * @dp_det: assert data pin connect successfully.
+ * @idm_sink_en: open dm sink curren.
+ * @idp_sink_en: open dp sink current.
+ * @idp_src_en: open dm source current.
+ * @rdm_pdwn_en: open dm pull down resistor.
+ * @vdm_src_en: open dm voltage source.
+ * @vdp_src_en: open dp voltage source.
+ * @opmode: utmi operational mode.
+ */
+struct rockchip_chg_det_reg {
+ struct usb2phy_reg cp_det;
+ struct usb2phy_reg dcp_det;
+ struct usb2phy_reg dp_det;
+ struct usb2phy_reg idm_sink_en;
+ struct usb2phy_reg idp_sink_en;
+ struct usb2phy_reg idp_src_en;
+ struct usb2phy_reg rdm_pdwn_en;
+ struct usb2phy_reg vdm_src_en;
+ struct usb2phy_reg vdp_src_en;
+ struct usb2phy_reg opmode;
+};
+
+/**
+ * struct rockchip_usb2phy_port_cfg - usb-phy port configuration.
+ * @phy_sus: phy suspend register.
+ * @bvalid_det_en: vbus valid rise detection enable register.
+ * @bvalid_det_st: vbus valid rise detection status register.
+ * @bvalid_det_clr: vbus valid rise detection clear register.
+ * @disfall_en: host disconnect fall edge detection enable.
+ * @disfall_st: host disconnect fall edge detection state.
+ * @disfall_clr: host disconnect fall edge detection clear.
+ * @disrise_en: host disconnect rise edge detection enable.
+ * @disrise_st: host disconnect rise edge detection state.
+ * @disrise_clr: host disconnect rise edge detection clear.
+ * @idfall_det_en: id detection enable register, falling edge
+ * @idfall_det_st: id detection state register, falling edge
+ * @idfall_det_clr: id detection clear register, falling edge
+ * @idrise_det_en: id detection enable register, rising edge
+ * @idrise_det_st: id detection state register, rising edge
+ * @idrise_det_clr: id detection clear register, rising edge
+ * @ls_det_en: linestate detection enable register.
+ * @ls_det_st: linestate detection state register.
+ * @ls_det_clr: linestate detection clear register.
+ * @utmi_avalid: utmi vbus avalid status register.
+ * @utmi_bvalid: utmi vbus bvalid status register.
+ * @utmi_id: utmi id state register.
+ * @utmi_ls: utmi linestate state register.
+ * @utmi_hstdet: utmi host disconnect register.
+ */
+struct rockchip_usb2phy_port_cfg {
+ struct usb2phy_reg phy_sus;
+ struct usb2phy_reg bvalid_det_en;
+ struct usb2phy_reg bvalid_det_st;
+ struct usb2phy_reg bvalid_det_clr;
+ struct usb2phy_reg disfall_en;
+ struct usb2phy_reg disfall_st;
+ struct usb2phy_reg disfall_clr;
+ struct usb2phy_reg disrise_en;
+ struct usb2phy_reg disrise_st;
+ struct usb2phy_reg disrise_clr;
+ struct usb2phy_reg idfall_det_en;
+ struct usb2phy_reg idfall_det_st;
+ struct usb2phy_reg idfall_det_clr;
+ struct usb2phy_reg idrise_det_en;
+ struct usb2phy_reg idrise_det_st;
+ struct usb2phy_reg idrise_det_clr;
+ struct usb2phy_reg ls_det_en;
+ struct usb2phy_reg ls_det_st;
+ struct usb2phy_reg ls_det_clr;
+ struct usb2phy_reg utmi_avalid;
+ struct usb2phy_reg utmi_bvalid;
+ struct usb2phy_reg utmi_id;
+ struct usb2phy_reg utmi_ls;
+ struct usb2phy_reg utmi_hstdet;
+};
+
+/**
+ * struct rockchip_usb2phy_cfg - usb-phy configuration.
+ * @reg: the address offset of grf for usb-phy config.
+ * @num_ports: specify how many ports that the phy has.
+ * @phy_tuning: phy default parameters tuning.
+ * @clkout_ctl: register to enable output clk of phy, when set in GRF
+ * @clkout_ctl_phy: register to enable output clk of phy, when set inside phy
+ * @port_cfgs: usb-phy port configurations.
+ * @chg_det: charger detection registers.
+ */
+struct rockchip_usb2phy_cfg {
+ unsigned int reg;
+ unsigned int num_ports;
+ int (*phy_tuning)(struct rockchip_usb2phy *rphy);
+ struct usb2phy_reg clkout_ctl;
+ struct usb2phy_reg clkout_ctl_phy;
+ const struct rockchip_usb2phy_port_cfg port_cfgs[USB2PHY_NUM_PORTS];
+ const struct rockchip_chg_det_reg chg_det;
+};
+
+/**
+ * struct rockchip_usb2phy_port - usb-phy port data.
+ * @phy: generic phy.
+ * @port_id: flag for otg port or host port.
+ * @suspended: phy suspended flag.
+ * @vbus_attached: otg device vbus status.
+ * @host_disconnect: usb host disconnect status.
+ * @bvalid_irq: IRQ number assigned for vbus valid rise detection.
+ * @id_irq: IRQ number assigned for ID pin detection.
+ * @ls_irq: IRQ number assigned for linestate detection.
+ * @otg_mux_irq: IRQ number which multiplex otg-id/otg-bvalid/linestate
+ * irqs to one irq in otg-port.
+ * @mutex: for register updating in sm_work.
+ * @chg_work: charge detect work.
+ * @otg_sm_work: OTG state machine work.
+ * @sm_work: HOST state machine work.
+ * @port_cfg: port register configuration, assigned by driver data.
+ * @event_nb: hold event notification callback.
+ * @state: define OTG enumeration states before device reset.
+ * @mode: the dr_mode of the controller.
+ */
+struct rockchip_usb2phy_port {
+ struct phy *phy;
+ unsigned int port_id;
+ bool suspended;
+ bool vbus_attached;
+ bool host_disconnect;
+ int bvalid_irq;
+ int id_irq;
+ int ls_irq;
+ int otg_mux_irq;
+ struct mutex mutex;
+ struct delayed_work chg_work;
+ struct delayed_work otg_sm_work;
+ struct delayed_work sm_work;
+ const struct rockchip_usb2phy_port_cfg *port_cfg;
+ struct notifier_block event_nb;
+ enum usb_otg_state state;
+ enum usb_dr_mode mode;
+};
+
+/**
+ * struct rockchip_usb2phy - usb2.0 phy driver data.
+ * @dev: pointer to device.
+ * @grf: General Register Files regmap.
+ * @phy_base: USB PHY regmap.
+ * @clks: array of phy input clocks.
+ * @clk480m: clock struct of phy output clk.
+ * @clk480m_hw: clock struct of phy output clk management.
+ * @num_clks: number of phy input clocks.
+ * @phy_reset: phy reset control.
+ * @chg_state: states involved in USB charger detection.
+ * @chg_type: USB charger types.
+ * @dcd_retries: The retry count used to track Data contact
+ * detection process.
+ * @edev: extcon device for notification registration
+ * @irq: muxed interrupt for single irq configuration
+ * @phy_cfg: phy register configuration, assigned by driver data.
+ * @ports: phy port instance.
+ */
+struct rockchip_usb2phy {
+ struct device *dev;
+ struct regmap *grf;
+ struct regmap *phy_base;
+ struct clk_bulk_data *clks;
+ struct clk *clk480m;
+ struct clk_hw clk480m_hw;
+ int num_clks;
+ struct reset_control *phy_reset;
+ enum usb_chg_state chg_state;
+ enum power_supply_type chg_type;
+ u8 dcd_retries;
+ struct extcon_dev *edev;
+ int irq;
+ const struct rockchip_usb2phy_cfg *phy_cfg;
+ struct rockchip_usb2phy_port ports[USB2PHY_NUM_PORTS];
+};
+
+static inline int property_enable(struct regmap *base,
+ const struct usb2phy_reg *reg, bool en)
+{
+ unsigned int val, mask, tmp;
+
+ tmp = en ? reg->enable : reg->disable;
+ mask = GENMASK(reg->bitend, reg->bitstart);
+ val = (tmp << reg->bitstart) | (mask << BIT_WRITEABLE_SHIFT);
+
+ return regmap_write(base, reg->offset, val);
+}
+
+static inline bool property_enabled(struct regmap *base,
+ const struct usb2phy_reg *reg)
+{
+ int ret;
+ unsigned int tmp, orig;
+ unsigned int mask = GENMASK(reg->bitend, reg->bitstart);
+
+ ret = regmap_read(base, reg->offset, &orig);
+ if (ret)
+ return false;
+
+ tmp = (orig & mask) >> reg->bitstart;
+ return tmp != reg->disable;
+}
+
+static int rockchip_usb2phy_reset(struct rockchip_usb2phy *rphy)
+{
+ int ret;
+
+ ret = reset_control_assert(rphy->phy_reset);
+ if (ret)
+ return ret;
+
+ udelay(10);
+
+ ret = reset_control_deassert(rphy->phy_reset);
+ if (ret)
+ return ret;
+
+ usleep_range(100, 200);
+
+ return 0;
+}
+
+static void rockchip_usb2phy_clk_bulk_disable(void *data)
+{
+ struct rockchip_usb2phy *rphy = data;
+
+ clk_bulk_disable_unprepare(rphy->num_clks, rphy->clks);
+}
+
+static void
+rockchip_usb2phy_clk480m_clkout_ctl(struct clk_hw *hw, struct regmap **base,
+ const struct usb2phy_reg **clkout_ctl)
+{
+ struct rockchip_usb2phy *rphy =
+ container_of(hw, struct rockchip_usb2phy, clk480m_hw);
+
+ if (rphy->phy_cfg->clkout_ctl_phy.enable) {
+ *base = rphy->phy_base;
+ *clkout_ctl = &rphy->phy_cfg->clkout_ctl_phy;
+ } else {
+ *base = rphy->grf;
+ *clkout_ctl = &rphy->phy_cfg->clkout_ctl;
+ }
+}
+
+static int rockchip_usb2phy_clk480m_prepare(struct clk_hw *hw)
+{
+ const struct usb2phy_reg *clkout_ctl;
+ struct regmap *base;
+ int ret;
+
+ rockchip_usb2phy_clk480m_clkout_ctl(hw, &base, &clkout_ctl);
+
+ /* turn on 480m clk output if it is off */
+ if (!property_enabled(base, clkout_ctl)) {
+ ret = property_enable(base, clkout_ctl, true);
+ if (ret)
+ return ret;
+
+ /* waiting for the clk become stable */
+ usleep_range(1200, 1300);
+ }
+
+ return 0;
+}
+
+static void rockchip_usb2phy_clk480m_unprepare(struct clk_hw *hw)
+{
+ const struct usb2phy_reg *clkout_ctl;
+ struct regmap *base;
+
+ rockchip_usb2phy_clk480m_clkout_ctl(hw, &base, &clkout_ctl);
+
+ /* turn off 480m clk output */
+ property_enable(base, clkout_ctl, false);
+}
+
+static int rockchip_usb2phy_clk480m_prepared(struct clk_hw *hw)
+{
+ const struct usb2phy_reg *clkout_ctl;
+ struct regmap *base;
+
+ rockchip_usb2phy_clk480m_clkout_ctl(hw, &base, &clkout_ctl);
+
+ return property_enabled(base, clkout_ctl);
+}
+
+static unsigned long
+rockchip_usb2phy_clk480m_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ return 480000000;
+}
+
+static const struct clk_ops rockchip_usb2phy_clkout_ops = {
+ .prepare = rockchip_usb2phy_clk480m_prepare,
+ .unprepare = rockchip_usb2phy_clk480m_unprepare,
+ .is_prepared = rockchip_usb2phy_clk480m_prepared,
+ .recalc_rate = rockchip_usb2phy_clk480m_recalc_rate,
+};
+
+static void rockchip_usb2phy_clk480m_unregister(void *data)
+{
+ struct rockchip_usb2phy *rphy = data;
+
+ of_clk_del_provider(rphy->dev->of_node);
+ clk_unregister(rphy->clk480m);
+}
+
+static int
+rockchip_usb2phy_clk480m_register(struct rockchip_usb2phy *rphy)
+{
+ struct device_node *node = rphy->dev->of_node;
+ struct clk_init_data init;
+ struct clk *refclk = NULL;
+ const char *clk_name;
+ int i;
+ int ret = 0;
+
+ init.flags = 0;
+ init.name = "clk_usbphy_480m";
+ init.ops = &rockchip_usb2phy_clkout_ops;
+
+ /* optional override of the clockname */
+ of_property_read_string(node, "clock-output-names", &init.name);
+
+ for (i = 0; i < rphy->num_clks; i++) {
+ if (!strncmp(rphy->clks[i].id, "phyclk", 6)) {
+ refclk = rphy->clks[i].clk;
+ break;
+ }
+ }
+
+ if (!IS_ERR(refclk)) {
+ clk_name = __clk_get_name(refclk);
+ init.parent_names = &clk_name;
+ init.num_parents = 1;
+ } else {
+ init.parent_names = NULL;
+ init.num_parents = 0;
+ }
+
+ rphy->clk480m_hw.init = &init;
+
+ /* register the clock */
+ rphy->clk480m = clk_register(rphy->dev, &rphy->clk480m_hw);
+ if (IS_ERR(rphy->clk480m)) {
+ ret = PTR_ERR(rphy->clk480m);
+ goto err_ret;
+ }
+
+ ret = of_clk_add_provider(node, of_clk_src_simple_get, rphy->clk480m);
+ if (ret < 0)
+ goto err_clk_provider;
+
+ return devm_add_action_or_reset(rphy->dev, rockchip_usb2phy_clk480m_unregister, rphy);
+
+err_clk_provider:
+ clk_unregister(rphy->clk480m);
+err_ret:
+ return ret;
+}
+
+static int rockchip_usb2phy_extcon_register(struct rockchip_usb2phy *rphy)
+{
+ struct device_node *node = rphy->dev->of_node;
+ struct extcon_dev *edev;
+ int ret;
+
+ if (of_property_present(node, "extcon")) {
+ edev = extcon_get_edev_by_phandle(rphy->dev, 0);
+ if (IS_ERR(edev))
+ return dev_err_probe(rphy->dev, PTR_ERR(edev),
+ "invalid or missing extcon\n");
+ } else {
+ /* Initialize extcon device */
+ edev = devm_extcon_dev_allocate(rphy->dev,
+ rockchip_usb2phy_extcon_cable);
+
+ if (IS_ERR(edev))
+ return dev_err_probe(rphy->dev, PTR_ERR(edev),
+ "failed to allocate extcon device\n");
+
+ ret = devm_extcon_dev_register(rphy->dev, edev);
+ if (ret)
+ return dev_err_probe(rphy->dev, ret,
+ "failed to register extcon device\n");
+ }
+
+ rphy->edev = edev;
+
+ return 0;
+}
+
+static int rockchip_usb2phy_enable_host_disc_irq(struct rockchip_usb2phy *rphy,
+ struct rockchip_usb2phy_port *rport,
+ bool en)
+{
+ int ret;
+
+ ret = property_enable(rphy->grf, &rport->port_cfg->disfall_clr, true);
+ if (ret)
+ return ret;
+
+ ret = property_enable(rphy->grf, &rport->port_cfg->disfall_en, en);
+ if (ret)
+ return ret;
+
+ ret = property_enable(rphy->grf, &rport->port_cfg->disrise_clr, true);
+ if (ret)
+ return ret;
+
+ return property_enable(rphy->grf, &rport->port_cfg->disrise_en, en);
+}
+
+static int rockchip_usb2phy_init(struct phy *phy)
+{
+ struct rockchip_usb2phy_port *rport = phy_get_drvdata(phy);
+ struct rockchip_usb2phy *rphy = dev_get_drvdata(phy->dev.parent);
+ int ret = 0;
+
+ mutex_lock(&rport->mutex);
+
+ if (rport->port_id == USB2PHY_PORT_OTG) {
+ if (rport->mode != USB_DR_MODE_HOST &&
+ rport->mode != USB_DR_MODE_UNKNOWN) {
+ /* clear bvalid status and enable bvalid detect irq */
+ ret = property_enable(rphy->grf,
+ &rport->port_cfg->bvalid_det_clr,
+ true);
+ if (ret)
+ goto out;
+
+ ret = property_enable(rphy->grf,
+ &rport->port_cfg->bvalid_det_en,
+ true);
+ if (ret)
+ goto out;
+
+ /* clear id status and enable id detect irqs */
+ ret = property_enable(rphy->grf,
+ &rport->port_cfg->idfall_det_clr,
+ true);
+ if (ret)
+ goto out;
+
+ ret = property_enable(rphy->grf,
+ &rport->port_cfg->idrise_det_clr,
+ true);
+ if (ret)
+ goto out;
+
+ ret = property_enable(rphy->grf,
+ &rport->port_cfg->idfall_det_en,
+ true);
+ if (ret)
+ goto out;
+
+ ret = property_enable(rphy->grf,
+ &rport->port_cfg->idrise_det_en,
+ true);
+ if (ret)
+ goto out;
+
+ schedule_delayed_work(&rport->otg_sm_work,
+ OTG_SCHEDULE_DELAY * 3);
+ } else {
+ /* If OTG works in host only mode, do nothing. */
+ dev_dbg(&rport->phy->dev, "mode %d\n", rport->mode);
+ }
+ } else if (rport->port_id == USB2PHY_PORT_HOST) {
+ if (rport->port_cfg->disfall_en.offset) {
+ rport->host_disconnect = true;
+ ret = rockchip_usb2phy_enable_host_disc_irq(rphy, rport, true);
+ if (ret) {
+ dev_err(rphy->dev, "failed to enable disconnect irq\n");
+ goto out;
+ }
+ }
+
+ /* clear linestate and enable linestate detect irq */
+ ret = property_enable(rphy->grf,
+ &rport->port_cfg->ls_det_clr, true);
+ if (ret)
+ goto out;
+
+ ret = property_enable(rphy->grf,
+ &rport->port_cfg->ls_det_en, true);
+ if (ret)
+ goto out;
+
+ schedule_delayed_work(&rport->sm_work, SCHEDULE_DELAY);
+ }
+
+out:
+ mutex_unlock(&rport->mutex);
+ return ret;
+}
+
+static int rockchip_usb2phy_power_on(struct phy *phy)
+{
+ struct rockchip_usb2phy_port *rport = phy_get_drvdata(phy);
+ struct rockchip_usb2phy *rphy = dev_get_drvdata(phy->dev.parent);
+ int ret;
+
+ dev_dbg(&rport->phy->dev, "port power on\n");
+
+ if (!rport->suspended)
+ return 0;
+
+ ret = clk_prepare_enable(rphy->clk480m);
+ if (ret)
+ return ret;
+
+ ret = property_enable(rphy->grf, &rport->port_cfg->phy_sus, false);
+ if (ret) {
+ clk_disable_unprepare(rphy->clk480m);
+ return ret;
+ }
+
+ /*
+ * For rk3588, it needs to reset phy when exit from
+ * suspend mode with common_on_n 1'b1(aka REFCLK_LOGIC,
+ * Bias, and PLL blocks are powered down) for lower
+ * power consumption. If you don't want to reset phy,
+ * please keep the common_on_n 1'b0 to set these blocks
+ * remain powered.
+ */
+ ret = rockchip_usb2phy_reset(rphy);
+ if (ret)
+ return ret;
+
+ /* waiting for the utmi_clk to become stable */
+ usleep_range(1500, 2000);
+
+ rport->suspended = false;
+ return 0;
+}
+
+static int rockchip_usb2phy_power_off(struct phy *phy)
+{
+ struct rockchip_usb2phy_port *rport = phy_get_drvdata(phy);
+ struct rockchip_usb2phy *rphy = dev_get_drvdata(phy->dev.parent);
+ int ret;
+
+ dev_dbg(&rport->phy->dev, "port power off\n");
+
+ if (rport->suspended)
+ return 0;
+
+ ret = property_enable(rphy->grf, &rport->port_cfg->phy_sus, true);
+ if (ret)
+ return ret;
+
+ rport->suspended = true;
+ clk_disable_unprepare(rphy->clk480m);
+
+ return 0;
+}
+
+static int rockchip_usb2phy_exit(struct phy *phy)
+{
+ struct rockchip_usb2phy_port *rport = phy_get_drvdata(phy);
+
+ if (rport->port_id == USB2PHY_PORT_OTG &&
+ rport->mode != USB_DR_MODE_HOST &&
+ rport->mode != USB_DR_MODE_UNKNOWN) {
+ cancel_delayed_work_sync(&rport->otg_sm_work);
+ cancel_delayed_work_sync(&rport->chg_work);
+ } else if (rport->port_id == USB2PHY_PORT_HOST)
+ cancel_delayed_work_sync(&rport->sm_work);
+
+ return 0;
+}
+
+static const struct phy_ops rockchip_usb2phy_ops = {
+ .init = rockchip_usb2phy_init,
+ .exit = rockchip_usb2phy_exit,
+ .power_on = rockchip_usb2phy_power_on,
+ .power_off = rockchip_usb2phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static void rockchip_usb2phy_otg_sm_work(struct work_struct *work)
+{
+ struct rockchip_usb2phy_port *rport =
+ container_of(work, struct rockchip_usb2phy_port,
+ otg_sm_work.work);
+ struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent);
+ static unsigned int cable;
+ unsigned long delay;
+ bool vbus_attach, sch_work, notify_charger;
+
+ vbus_attach = property_enabled(rphy->grf,
+ &rport->port_cfg->utmi_bvalid);
+
+ sch_work = false;
+ notify_charger = false;
+ delay = OTG_SCHEDULE_DELAY;
+ dev_dbg(&rport->phy->dev, "%s otg sm work\n",
+ usb_otg_state_string(rport->state));
+
+ switch (rport->state) {
+ case OTG_STATE_UNDEFINED:
+ rport->state = OTG_STATE_B_IDLE;
+ if (!vbus_attach)
+ rockchip_usb2phy_power_off(rport->phy);
+ fallthrough;
+ case OTG_STATE_B_IDLE:
+ if (extcon_get_state(rphy->edev, EXTCON_USB_HOST) > 0) {
+ dev_dbg(&rport->phy->dev, "usb otg host connect\n");
+ rport->state = OTG_STATE_A_HOST;
+ rockchip_usb2phy_power_on(rport->phy);
+ return;
+ } else if (vbus_attach) {
+ dev_dbg(&rport->phy->dev, "vbus_attach\n");
+ switch (rphy->chg_state) {
+ case USB_CHG_STATE_UNDEFINED:
+ schedule_delayed_work(&rport->chg_work, 0);
+ return;
+ case USB_CHG_STATE_DETECTED:
+ switch (rphy->chg_type) {
+ case POWER_SUPPLY_TYPE_USB:
+ dev_dbg(&rport->phy->dev, "sdp cable is connected\n");
+ rockchip_usb2phy_power_on(rport->phy);
+ rport->state = OTG_STATE_B_PERIPHERAL;
+ notify_charger = true;
+ sch_work = true;
+ cable = EXTCON_CHG_USB_SDP;
+ break;
+ case POWER_SUPPLY_TYPE_USB_DCP:
+ dev_dbg(&rport->phy->dev, "dcp cable is connected\n");
+ rockchip_usb2phy_power_off(rport->phy);
+ notify_charger = true;
+ sch_work = true;
+ cable = EXTCON_CHG_USB_DCP;
+ break;
+ case POWER_SUPPLY_TYPE_USB_CDP:
+ dev_dbg(&rport->phy->dev, "cdp cable is connected\n");
+ rockchip_usb2phy_power_on(rport->phy);
+ rport->state = OTG_STATE_B_PERIPHERAL;
+ notify_charger = true;
+ sch_work = true;
+ cable = EXTCON_CHG_USB_CDP;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ } else {
+ notify_charger = true;
+ rphy->chg_state = USB_CHG_STATE_UNDEFINED;
+ rphy->chg_type = POWER_SUPPLY_TYPE_UNKNOWN;
+ }
+
+ if (rport->vbus_attached != vbus_attach) {
+ rport->vbus_attached = vbus_attach;
+
+ if (notify_charger && rphy->edev) {
+ extcon_set_state_sync(rphy->edev,
+ cable, vbus_attach);
+ if (cable == EXTCON_CHG_USB_SDP)
+ extcon_set_state_sync(rphy->edev,
+ EXTCON_USB,
+ vbus_attach);
+ }
+ }
+ break;
+ case OTG_STATE_B_PERIPHERAL:
+ if (!vbus_attach) {
+ dev_dbg(&rport->phy->dev, "usb disconnect\n");
+ rphy->chg_state = USB_CHG_STATE_UNDEFINED;
+ rphy->chg_type = POWER_SUPPLY_TYPE_UNKNOWN;
+ rport->state = OTG_STATE_B_IDLE;
+ delay = 0;
+ rockchip_usb2phy_power_off(rport->phy);
+ }
+ sch_work = true;
+ break;
+ case OTG_STATE_A_HOST:
+ if (extcon_get_state(rphy->edev, EXTCON_USB_HOST) == 0) {
+ dev_dbg(&rport->phy->dev, "usb otg host disconnect\n");
+ rport->state = OTG_STATE_B_IDLE;
+ rockchip_usb2phy_power_off(rport->phy);
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (sch_work)
+ schedule_delayed_work(&rport->otg_sm_work, delay);
+}
+
+static const char *chg_to_string(enum power_supply_type chg_type)
+{
+ switch (chg_type) {
+ case POWER_SUPPLY_TYPE_USB:
+ return "USB_SDP_CHARGER";
+ case POWER_SUPPLY_TYPE_USB_DCP:
+ return "USB_DCP_CHARGER";
+ case POWER_SUPPLY_TYPE_USB_CDP:
+ return "USB_CDP_CHARGER";
+ default:
+ return "INVALID_CHARGER";
+ }
+}
+
+static void rockchip_chg_enable_dcd(struct rockchip_usb2phy *rphy,
+ bool en)
+{
+ property_enable(rphy->grf, &rphy->phy_cfg->chg_det.rdm_pdwn_en, en);
+ property_enable(rphy->grf, &rphy->phy_cfg->chg_det.idp_src_en, en);
+}
+
+static void rockchip_chg_enable_primary_det(struct rockchip_usb2phy *rphy,
+ bool en)
+{
+ property_enable(rphy->grf, &rphy->phy_cfg->chg_det.vdp_src_en, en);
+ property_enable(rphy->grf, &rphy->phy_cfg->chg_det.idm_sink_en, en);
+}
+
+static void rockchip_chg_enable_secondary_det(struct rockchip_usb2phy *rphy,
+ bool en)
+{
+ property_enable(rphy->grf, &rphy->phy_cfg->chg_det.vdm_src_en, en);
+ property_enable(rphy->grf, &rphy->phy_cfg->chg_det.idp_sink_en, en);
+}
+
+#define CHG_DCD_POLL_TIME (100 * HZ / 1000)
+#define CHG_DCD_MAX_RETRIES 6
+#define CHG_PRIMARY_DET_TIME (40 * HZ / 1000)
+#define CHG_SECONDARY_DET_TIME (40 * HZ / 1000)
+static void rockchip_chg_detect_work(struct work_struct *work)
+{
+ struct rockchip_usb2phy_port *rport =
+ container_of(work, struct rockchip_usb2phy_port, chg_work.work);
+ struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent);
+ bool is_dcd, tmout, vout, vbus_attach;
+ unsigned long delay;
+
+ vbus_attach = property_enabled(rphy->grf, &rport->port_cfg->utmi_bvalid);
+
+ dev_dbg(&rport->phy->dev, "chg detection work state = %d\n",
+ rphy->chg_state);
+ switch (rphy->chg_state) {
+ case USB_CHG_STATE_UNDEFINED:
+ if (!rport->suspended && !vbus_attach)
+ rockchip_usb2phy_power_off(rport->phy);
+ /* put the controller in non-driving mode */
+ if (!vbus_attach)
+ property_enable(rphy->grf, &rphy->phy_cfg->chg_det.opmode, false);
+ /* Start DCD processing stage 1 */
+ rockchip_chg_enable_dcd(rphy, true);
+ rphy->chg_state = USB_CHG_STATE_WAIT_FOR_DCD;
+ rphy->dcd_retries = 0;
+ delay = CHG_DCD_POLL_TIME;
+ break;
+ case USB_CHG_STATE_WAIT_FOR_DCD:
+ /* get data contact detection status */
+ is_dcd = property_enabled(rphy->grf,
+ &rphy->phy_cfg->chg_det.dp_det);
+ tmout = ++rphy->dcd_retries == CHG_DCD_MAX_RETRIES;
+ /* stage 2 */
+ if (is_dcd || tmout) {
+ /* stage 4 */
+ /* Turn off DCD circuitry */
+ rockchip_chg_enable_dcd(rphy, false);
+ /* Voltage Source on DP, Probe on DM */
+ rockchip_chg_enable_primary_det(rphy, true);
+ delay = CHG_PRIMARY_DET_TIME;
+ rphy->chg_state = USB_CHG_STATE_DCD_DONE;
+ } else {
+ /* stage 3 */
+ delay = CHG_DCD_POLL_TIME;
+ }
+ break;
+ case USB_CHG_STATE_DCD_DONE:
+ vout = property_enabled(rphy->grf,
+ &rphy->phy_cfg->chg_det.cp_det);
+ rockchip_chg_enable_primary_det(rphy, false);
+ if (vout) {
+ /* Voltage Source on DM, Probe on DP */
+ rockchip_chg_enable_secondary_det(rphy, true);
+ delay = CHG_SECONDARY_DET_TIME;
+ rphy->chg_state = USB_CHG_STATE_PRIMARY_DONE;
+ } else {
+ if (rphy->dcd_retries == CHG_DCD_MAX_RETRIES) {
+ /* floating charger found */
+ rphy->chg_type = POWER_SUPPLY_TYPE_USB_DCP;
+ rphy->chg_state = USB_CHG_STATE_DETECTED;
+ delay = 0;
+ } else {
+ rphy->chg_type = POWER_SUPPLY_TYPE_USB;
+ rphy->chg_state = USB_CHG_STATE_DETECTED;
+ delay = 0;
+ }
+ }
+ break;
+ case USB_CHG_STATE_PRIMARY_DONE:
+ vout = property_enabled(rphy->grf,
+ &rphy->phy_cfg->chg_det.dcp_det);
+ /* Turn off voltage source */
+ rockchip_chg_enable_secondary_det(rphy, false);
+ if (vout)
+ rphy->chg_type = POWER_SUPPLY_TYPE_USB_DCP;
+ else
+ rphy->chg_type = POWER_SUPPLY_TYPE_USB_CDP;
+ fallthrough;
+ case USB_CHG_STATE_SECONDARY_DONE:
+ rphy->chg_state = USB_CHG_STATE_DETECTED;
+ fallthrough;
+ case USB_CHG_STATE_DETECTED:
+ /* put the controller in normal mode */
+ if (!vbus_attach)
+ property_enable(rphy->grf, &rphy->phy_cfg->chg_det.opmode, true);
+ rockchip_usb2phy_otg_sm_work(&rport->otg_sm_work.work);
+ dev_dbg(&rport->phy->dev, "charger = %s\n",
+ chg_to_string(rphy->chg_type));
+ return;
+ default:
+ return;
+ }
+
+ schedule_delayed_work(&rport->chg_work, delay);
+}
+
+/*
+ * The function manage host-phy port state and suspend/resume phy port
+ * to save power.
+ *
+ * we rely on utmi_linestate and utmi_hostdisconnect to identify whether
+ * devices is disconnect or not. Besides, we do not need care it is FS/LS
+ * disconnected or HS disconnected, actually, we just only need get the
+ * device is disconnected at last through rearm the delayed work,
+ * to suspend the phy port in _PHY_STATE_DISCONNECT_ case.
+ *
+ * NOTE: It may invoke *phy_powr_off or *phy_power_on which will invoke
+ * some clk related APIs, so do not invoke it from interrupt context directly.
+ */
+static void rockchip_usb2phy_sm_work(struct work_struct *work)
+{
+ struct rockchip_usb2phy_port *rport =
+ container_of(work, struct rockchip_usb2phy_port, sm_work.work);
+ struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent);
+ unsigned int sh, ul, uhd, state;
+ unsigned int ul_mask, uhd_mask;
+ int ret;
+
+ mutex_lock(&rport->mutex);
+
+ ret = regmap_read(rphy->grf, rport->port_cfg->utmi_ls.offset, &ul);
+ if (ret < 0)
+ goto next_schedule;
+
+ ul_mask = GENMASK(rport->port_cfg->utmi_ls.bitend,
+ rport->port_cfg->utmi_ls.bitstart);
+
+ if (rport->port_cfg->utmi_hstdet.offset) {
+ ret = regmap_read(rphy->grf, rport->port_cfg->utmi_hstdet.offset, &uhd);
+ if (ret < 0)
+ goto next_schedule;
+
+ uhd_mask = GENMASK(rport->port_cfg->utmi_hstdet.bitend,
+ rport->port_cfg->utmi_hstdet.bitstart);
+
+ sh = rport->port_cfg->utmi_hstdet.bitend -
+ rport->port_cfg->utmi_hstdet.bitstart + 1;
+ /* stitch on utmi_ls and utmi_hstdet as phy state */
+ state = ((uhd & uhd_mask) >> rport->port_cfg->utmi_hstdet.bitstart) |
+ (((ul & ul_mask) >> rport->port_cfg->utmi_ls.bitstart) << sh);
+ } else {
+ state = ((ul & ul_mask) >> rport->port_cfg->utmi_ls.bitstart) << 1 |
+ rport->host_disconnect;
+ }
+
+ switch (state) {
+ case PHY_STATE_HS_ONLINE:
+ dev_dbg(&rport->phy->dev, "HS online\n");
+ break;
+ case PHY_STATE_FS_LS_ONLINE:
+ /*
+ * For FS/LS device, the online state share with connect state
+ * from utmi_ls and utmi_hstdet register, so we distinguish
+ * them via suspended flag.
+ *
+ * Plus, there are two cases, one is D- Line pull-up, and D+
+ * line pull-down, the state is 4; another is D+ line pull-up,
+ * and D- line pull-down, the state is 2.
+ */
+ if (!rport->suspended) {
+ /* D- line pull-up, D+ line pull-down */
+ dev_dbg(&rport->phy->dev, "FS/LS online\n");
+ break;
+ }
+ fallthrough;
+ case PHY_STATE_CONNECT:
+ if (rport->suspended) {
+ dev_dbg(&rport->phy->dev, "Connected\n");
+ rockchip_usb2phy_power_on(rport->phy);
+ rport->suspended = false;
+ } else {
+ /* D+ line pull-up, D- line pull-down */
+ dev_dbg(&rport->phy->dev, "FS/LS online\n");
+ }
+ break;
+ case PHY_STATE_DISCONNECT:
+ if (!rport->suspended) {
+ dev_dbg(&rport->phy->dev, "Disconnected\n");
+ rockchip_usb2phy_power_off(rport->phy);
+ rport->suspended = true;
+ }
+
+ /*
+ * activate the linestate detection to get the next device
+ * plug-in irq.
+ */
+ property_enable(rphy->grf, &rport->port_cfg->ls_det_clr, true);
+ property_enable(rphy->grf, &rport->port_cfg->ls_det_en, true);
+
+ /*
+ * we don't need to rearm the delayed work when the phy port
+ * is suspended.
+ */
+ mutex_unlock(&rport->mutex);
+ return;
+ default:
+ dev_dbg(&rport->phy->dev, "unknown phy state\n");
+ break;
+ }
+
+next_schedule:
+ mutex_unlock(&rport->mutex);
+ schedule_delayed_work(&rport->sm_work, SCHEDULE_DELAY);
+}
+
+static irqreturn_t rockchip_usb2phy_linestate_irq(int irq, void *data)
+{
+ struct rockchip_usb2phy_port *rport = data;
+ struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent);
+
+ if (!property_enabled(rphy->grf, &rport->port_cfg->ls_det_st))
+ return IRQ_NONE;
+
+ mutex_lock(&rport->mutex);
+
+ /* disable linestate detect irq and clear its status */
+ property_enable(rphy->grf, &rport->port_cfg->ls_det_en, false);
+ property_enable(rphy->grf, &rport->port_cfg->ls_det_clr, true);
+
+ mutex_unlock(&rport->mutex);
+
+ /*
+ * In this case for host phy port, a new device is plugged in,
+ * meanwhile, if the phy port is suspended, we need rearm the work to
+ * resume it and mange its states; otherwise, we do nothing about that.
+ */
+ if (rport->suspended && rport->port_id == USB2PHY_PORT_HOST)
+ rockchip_usb2phy_sm_work(&rport->sm_work.work);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t rockchip_usb2phy_bvalid_irq(int irq, void *data)
+{
+ struct rockchip_usb2phy_port *rport = data;
+ struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent);
+
+ if (!property_enabled(rphy->grf, &rport->port_cfg->bvalid_det_st))
+ return IRQ_NONE;
+
+ /* clear bvalid detect irq pending status */
+ property_enable(rphy->grf, &rport->port_cfg->bvalid_det_clr, true);
+
+ rockchip_usb2phy_otg_sm_work(&rport->otg_sm_work.work);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t rockchip_usb2phy_id_irq(int irq, void *data)
+{
+ struct rockchip_usb2phy_port *rport = data;
+ struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent);
+ bool id;
+
+ if (!property_enabled(rphy->grf, &rport->port_cfg->idfall_det_st) &&
+ !property_enabled(rphy->grf, &rport->port_cfg->idrise_det_st))
+ return IRQ_NONE;
+
+ /* clear id detect irq pending status */
+ if (property_enabled(rphy->grf, &rport->port_cfg->idfall_det_st))
+ property_enable(rphy->grf, &rport->port_cfg->idfall_det_clr, true);
+
+ if (property_enabled(rphy->grf, &rport->port_cfg->idrise_det_st))
+ property_enable(rphy->grf, &rport->port_cfg->idrise_det_clr, true);
+
+ id = property_enabled(rphy->grf, &rport->port_cfg->utmi_id);
+ extcon_set_state_sync(rphy->edev, EXTCON_USB_HOST, !id);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t rockchip_usb2phy_otg_mux_irq(int irq, void *data)
+{
+ irqreturn_t ret = IRQ_NONE;
+
+ ret |= rockchip_usb2phy_bvalid_irq(irq, data);
+ ret |= rockchip_usb2phy_id_irq(irq, data);
+
+ return ret;
+}
+
+static irqreturn_t rockchip_usb2phy_host_disc_irq(int irq, void *data)
+{
+ struct rockchip_usb2phy_port *rport = data;
+ struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent);
+
+ if (!property_enabled(rphy->grf, &rport->port_cfg->disfall_st) &&
+ !property_enabled(rphy->grf, &rport->port_cfg->disrise_st))
+ return IRQ_NONE;
+
+ mutex_lock(&rport->mutex);
+
+ /* clear disconnect fall or rise detect irq pending status */
+ if (property_enabled(rphy->grf, &rport->port_cfg->disfall_st)) {
+ property_enable(rphy->grf, &rport->port_cfg->disfall_clr, true);
+ rport->host_disconnect = false;
+ } else if (property_enabled(rphy->grf, &rport->port_cfg->disrise_st)) {
+ property_enable(rphy->grf, &rport->port_cfg->disrise_clr, true);
+ rport->host_disconnect = true;
+ }
+
+ mutex_unlock(&rport->mutex);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t rockchip_usb2phy_irq(int irq, void *data)
+{
+ struct rockchip_usb2phy *rphy = data;
+ struct rockchip_usb2phy_port *rport;
+ irqreturn_t ret = IRQ_NONE;
+ unsigned int index;
+
+ for (index = 0; index < rphy->phy_cfg->num_ports; index++) {
+ rport = &rphy->ports[index];
+ if (!rport->phy)
+ continue;
+
+ if (rport->port_id == USB2PHY_PORT_HOST &&
+ rport->port_cfg->disfall_en.offset)
+ ret |= rockchip_usb2phy_host_disc_irq(irq, rport);
+
+ switch (rport->port_id) {
+ case USB2PHY_PORT_OTG:
+ if (rport->mode != USB_DR_MODE_HOST &&
+ rport->mode != USB_DR_MODE_UNKNOWN)
+ ret |= rockchip_usb2phy_otg_mux_irq(irq, rport);
+ break;
+ case USB2PHY_PORT_HOST:
+ ret |= rockchip_usb2phy_linestate_irq(irq, rport);
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static int rockchip_usb2phy_port_irq_init(struct rockchip_usb2phy *rphy,
+ struct rockchip_usb2phy_port *rport,
+ struct device_node *child_np)
+{
+ int ret;
+
+ /*
+ * If the usb2 phy used combined irq for otg and host port,
+ * don't need to init otg and host port irq separately.
+ */
+ if (rphy->irq > 0)
+ return 0;
+
+ switch (rport->port_id) {
+ case USB2PHY_PORT_HOST:
+ rport->ls_irq = of_irq_get_byname(child_np, "linestate");
+ if (rport->ls_irq < 0) {
+ dev_err(rphy->dev, "no linestate irq provided\n");
+ return rport->ls_irq;
+ }
+
+ ret = devm_request_threaded_irq(rphy->dev, rport->ls_irq, NULL,
+ rockchip_usb2phy_linestate_irq,
+ IRQF_ONESHOT,
+ "rockchip_usb2phy", rport);
+ if (ret) {
+ dev_err(rphy->dev, "failed to request linestate irq handle\n");
+ return ret;
+ }
+ break;
+ case USB2PHY_PORT_OTG:
+ /*
+ * Some SoCs use one interrupt with otg-id/otg-bvalid/linestate
+ * interrupts muxed together, so probe the otg-mux interrupt first,
+ * if not found, then look for the regular interrupts one by one.
+ */
+ rport->otg_mux_irq = of_irq_get_byname(child_np, "otg-mux");
+ if (rport->otg_mux_irq > 0) {
+ ret = devm_request_threaded_irq(rphy->dev, rport->otg_mux_irq,
+ NULL,
+ rockchip_usb2phy_otg_mux_irq,
+ IRQF_ONESHOT,
+ "rockchip_usb2phy_otg",
+ rport);
+ if (ret) {
+ dev_err(rphy->dev,
+ "failed to request otg-mux irq handle\n");
+ return ret;
+ }
+ } else {
+ rport->bvalid_irq = of_irq_get_byname(child_np, "otg-bvalid");
+ if (rport->bvalid_irq < 0) {
+ dev_err(rphy->dev, "no vbus valid irq provided\n");
+ ret = rport->bvalid_irq;
+ return ret;
+ }
+
+ ret = devm_request_threaded_irq(rphy->dev, rport->bvalid_irq,
+ NULL,
+ rockchip_usb2phy_bvalid_irq,
+ IRQF_ONESHOT,
+ "rockchip_usb2phy_bvalid",
+ rport);
+ if (ret) {
+ dev_err(rphy->dev,
+ "failed to request otg-bvalid irq handle\n");
+ return ret;
+ }
+
+ rport->id_irq = of_irq_get_byname(child_np, "otg-id");
+ if (rport->id_irq < 0) {
+ dev_err(rphy->dev, "no otg-id irq provided\n");
+ ret = rport->id_irq;
+ return ret;
+ }
+
+ ret = devm_request_threaded_irq(rphy->dev, rport->id_irq,
+ NULL,
+ rockchip_usb2phy_id_irq,
+ IRQF_ONESHOT,
+ "rockchip_usb2phy_id",
+ rport);
+ if (ret) {
+ dev_err(rphy->dev,
+ "failed to request otg-id irq handle\n");
+ return ret;
+ }
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int rockchip_usb2phy_host_port_init(struct rockchip_usb2phy *rphy,
+ struct rockchip_usb2phy_port *rport,
+ struct device_node *child_np)
+{
+ int ret;
+
+ rport->port_id = USB2PHY_PORT_HOST;
+ rport->port_cfg = &rphy->phy_cfg->port_cfgs[USB2PHY_PORT_HOST];
+ rport->suspended = true;
+
+ mutex_init(&rport->mutex);
+ INIT_DELAYED_WORK(&rport->sm_work, rockchip_usb2phy_sm_work);
+
+ ret = rockchip_usb2phy_port_irq_init(rphy, rport, child_np);
+ if (ret) {
+ dev_err(rphy->dev, "failed to setup host irq\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int rockchip_otg_event(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct rockchip_usb2phy_port *rport =
+ container_of(nb, struct rockchip_usb2phy_port, event_nb);
+
+ schedule_delayed_work(&rport->otg_sm_work, OTG_SCHEDULE_DELAY);
+
+ return NOTIFY_DONE;
+}
+
+static int rockchip_usb2phy_otg_port_init(struct rockchip_usb2phy *rphy,
+ struct rockchip_usb2phy_port *rport,
+ struct device_node *child_np)
+{
+ int ret, id;
+
+ rport->port_id = USB2PHY_PORT_OTG;
+ rport->port_cfg = &rphy->phy_cfg->port_cfgs[USB2PHY_PORT_OTG];
+ rport->state = OTG_STATE_UNDEFINED;
+
+ /*
+ * set suspended flag to true, but actually don't
+ * put phy in suspend mode, it aims to enable usb
+ * phy and clock in power_on() called by usb controller
+ * driver during probe.
+ */
+ rport->suspended = true;
+ rport->vbus_attached = false;
+
+ mutex_init(&rport->mutex);
+
+ rport->mode = of_usb_get_dr_mode_by_phy(child_np, -1);
+ if (rport->mode == USB_DR_MODE_HOST ||
+ rport->mode == USB_DR_MODE_UNKNOWN) {
+ ret = 0;
+ goto out;
+ }
+
+ INIT_DELAYED_WORK(&rport->chg_work, rockchip_chg_detect_work);
+ INIT_DELAYED_WORK(&rport->otg_sm_work, rockchip_usb2phy_otg_sm_work);
+
+ ret = rockchip_usb2phy_port_irq_init(rphy, rport, child_np);
+ if (ret) {
+ dev_err(rphy->dev, "failed to init irq for host port\n");
+ goto out;
+ }
+
+ if (!IS_ERR(rphy->edev)) {
+ rport->event_nb.notifier_call = rockchip_otg_event;
+
+ ret = devm_extcon_register_notifier(rphy->dev, rphy->edev,
+ EXTCON_USB_HOST, &rport->event_nb);
+ if (ret) {
+ dev_err(rphy->dev, "register USB HOST notifier failed\n");
+ goto out;
+ }
+
+ if (!of_property_present(rphy->dev->of_node, "extcon")) {
+ /* do initial sync of usb state */
+ id = property_enabled(rphy->grf, &rport->port_cfg->utmi_id);
+ extcon_set_state_sync(rphy->edev, EXTCON_USB_HOST, !id);
+ }
+ }
+
+out:
+ return ret;
+}
+
+static int rockchip_usb2phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct device_node *child_np;
+ struct phy_provider *provider;
+ struct rockchip_usb2phy *rphy;
+ const struct rockchip_usb2phy_cfg *phy_cfgs;
+ unsigned int reg;
+ int index = 0, ret;
+
+ rphy = devm_kzalloc(dev, sizeof(*rphy), GFP_KERNEL);
+ if (!rphy)
+ return -ENOMEM;
+
+ if (!dev->parent || !dev->parent->of_node ||
+ of_property_present(np, "rockchip,usbgrf")) {
+ rphy->phy_base = device_node_to_regmap(np);
+ if (IS_ERR(rphy->phy_base))
+ return PTR_ERR(rphy->phy_base);
+ rphy->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,usbgrf");
+ } else {
+ rphy->grf = syscon_node_to_regmap(dev->parent->of_node);
+ rphy->phy_base = rphy->grf;
+ }
+ if (IS_ERR(rphy->grf))
+ return PTR_ERR(rphy->grf);
+
+ if (of_property_read_u32_index(np, "reg", 0, &reg)) {
+ dev_err(dev, "the reg property is not assigned in %pOFn node\n", np);
+ return -EINVAL;
+ }
+
+ /* support address_cells=2 */
+ if (of_property_count_u32_elems(np, "reg") > 2 && reg == 0) {
+ if (of_property_read_u32_index(np, "reg", 1, &reg)) {
+ dev_err(dev, "the reg property is not assigned in %pOFn node\n", np);
+ return -EINVAL;
+ }
+ }
+
+ rphy->dev = dev;
+ phy_cfgs = device_get_match_data(dev);
+ rphy->chg_state = USB_CHG_STATE_UNDEFINED;
+ rphy->chg_type = POWER_SUPPLY_TYPE_UNKNOWN;
+ rphy->irq = platform_get_irq_optional(pdev, 0);
+ platform_set_drvdata(pdev, rphy);
+
+ if (!phy_cfgs)
+ return dev_err_probe(dev, -EINVAL, "phy configs are not assigned!\n");
+
+ ret = rockchip_usb2phy_extcon_register(rphy);
+ if (ret)
+ return ret;
+
+ /* find a proper config that can be matched with the DT */
+ do {
+ if (phy_cfgs[index].reg == reg) {
+ rphy->phy_cfg = &phy_cfgs[index];
+ break;
+ }
+
+ ++index;
+ } while (phy_cfgs[index].reg);
+
+ if (!rphy->phy_cfg) {
+ dev_err(dev, "could not find phy config for reg=0x%08x\n", reg);
+ return -EINVAL;
+ }
+
+ rphy->phy_reset = devm_reset_control_get_optional(dev, "phy");
+ if (IS_ERR(rphy->phy_reset))
+ return PTR_ERR(rphy->phy_reset);
+
+ ret = devm_clk_bulk_get_all(dev, &rphy->clks);
+ if (ret == -EPROBE_DEFER)
+ return dev_err_probe(&pdev->dev, -EPROBE_DEFER,
+ "failed to get phy clock\n");
+
+ /* Clocks are optional */
+ rphy->num_clks = ret < 0 ? 0 : ret;
+
+ ret = rockchip_usb2phy_clk480m_register(rphy);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to register 480m output clock\n");
+
+ ret = clk_bulk_prepare_enable(rphy->num_clks, rphy->clks);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to enable phy clock\n");
+
+ ret = devm_add_action_or_reset(dev, rockchip_usb2phy_clk_bulk_disable, rphy);
+ if (ret)
+ return ret;
+
+ if (rphy->phy_cfg->phy_tuning) {
+ ret = rphy->phy_cfg->phy_tuning(rphy);
+ if (ret)
+ return ret;
+ }
+
+ index = 0;
+ for_each_available_child_of_node(np, child_np) {
+ struct rockchip_usb2phy_port *rport = &rphy->ports[index];
+ struct phy *phy;
+
+ /* This driver aims to support both otg-port and host-port */
+ if (!of_node_name_eq(child_np, "host-port") &&
+ !of_node_name_eq(child_np, "otg-port"))
+ goto next_child;
+
+ phy = devm_phy_create(dev, child_np, &rockchip_usb2phy_ops);
+ if (IS_ERR(phy)) {
+ ret = dev_err_probe(dev, PTR_ERR(phy), "failed to create phy\n");
+ goto put_child;
+ }
+
+ rport->phy = phy;
+ phy_set_drvdata(rport->phy, rport);
+
+ /* initialize otg/host port separately */
+ if (of_node_name_eq(child_np, "host-port")) {
+ ret = rockchip_usb2phy_host_port_init(rphy, rport, child_np);
+ if (ret)
+ goto put_child;
+ } else {
+ ret = rockchip_usb2phy_otg_port_init(rphy, rport, child_np);
+ if (ret)
+ goto put_child;
+ }
+
+next_child:
+ /* to prevent out of boundary */
+ if (++index >= rphy->phy_cfg->num_ports) {
+ of_node_put(child_np);
+ break;
+ }
+ }
+
+ provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+
+ if (rphy->irq > 0) {
+ ret = devm_request_threaded_irq(rphy->dev, rphy->irq, NULL,
+ rockchip_usb2phy_irq,
+ IRQF_ONESHOT,
+ "rockchip_usb2phy",
+ rphy);
+ if (ret) {
+ dev_err_probe(rphy->dev, ret, "failed to request usb2phy irq handle\n");
+ return ret;
+ }
+ }
+
+ return PTR_ERR_OR_ZERO(provider);
+
+put_child:
+ of_node_put(child_np);
+ return ret;
+}
+
+static int rk3128_usb2phy_tuning(struct rockchip_usb2phy *rphy)
+{
+ /* Turn off differential receiver in suspend mode */
+ return regmap_write_bits(rphy->grf, 0x298,
+ BIT(2) << BIT_WRITEABLE_SHIFT | BIT(2),
+ BIT(2) << BIT_WRITEABLE_SHIFT | 0);
+}
+
+static int rk3528_usb2phy_tuning(struct rockchip_usb2phy *rphy)
+{
+ int ret;
+
+ /* Turn off otg port differential receiver in suspend mode */
+ ret = regmap_write(rphy->phy_base, 0x30, BIT(18) | 0x0000);
+ if (ret)
+ return ret;
+
+ /* Turn off host port differential receiver in suspend mode */
+ ret = regmap_write(rphy->phy_base, 0x430, BIT(18) | 0x0000);
+ if (ret)
+ return ret;
+
+ /* Set otg port HS eye height to 400mv (default is 450mv) */
+ ret = regmap_write(rphy->phy_base, 0x30, GENMASK(22, 20) | 0x0000);
+ if (ret)
+ return ret;
+
+ /* Set host port HS eye height to 400mv (default is 450mv) */
+ ret = regmap_write(rphy->phy_base, 0x430, GENMASK(22, 20) | 0x0000);
+ if (ret)
+ return ret;
+
+ /* Choose the Tx fs/ls data as linestate from TX driver for otg port */
+ ret = regmap_write(rphy->phy_base, 0x94, GENMASK(22, 19) | 0x0018);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int rk3576_usb2phy_tuning(struct rockchip_usb2phy *rphy)
+{
+ int ret;
+ u32 reg = rphy->phy_cfg->reg;
+
+ /* Deassert SIDDQ to power on analog block */
+ ret = regmap_write(rphy->grf, reg + 0x0010, GENMASK(29, 29) | 0x0000);
+ if (ret)
+ return ret;
+
+ /* Do reset after exit IDDQ mode */
+ ret = rockchip_usb2phy_reset(rphy);
+ if (ret)
+ return ret;
+
+ /* HS DC Voltage Level Adjustment 4'b1001 : +5.89% */
+ ret |= regmap_write(rphy->grf, reg + 0x000c, GENMASK(27, 24) | 0x0900);
+
+ /* HS Transmitter Pre-Emphasis Current Control 2'b10 : 2x */
+ ret |= regmap_write(rphy->grf, reg + 0x0010, GENMASK(20, 19) | 0x0010);
+
+ return ret;
+}
+
+static int rk3588_usb2phy_tuning(struct rockchip_usb2phy *rphy)
+{
+ int ret;
+ bool usb3otg = false;
+ /*
+ * utmi_termselect = 1'b1 (en FS terminations)
+ * utmi_xcvrselect = 2'b01 (FS transceiver)
+ */
+ int suspend_cfg = 0x14;
+
+ if (rphy->phy_cfg->reg == 0x0000 || rphy->phy_cfg->reg == 0x4000) {
+ /* USB2 config for USB3_0 and USB3_1 */
+ suspend_cfg |= 0x01; /* utmi_opmode = 2'b01 (no-driving) */
+ usb3otg = true;
+ } else if (rphy->phy_cfg->reg == 0x8000 || rphy->phy_cfg->reg == 0xc000) {
+ /* USB2 config for USB2_0 and USB2_1 */
+ suspend_cfg |= 0x00; /* utmi_opmode = 2'b00 (normal) */
+ } else {
+ return -EINVAL;
+ }
+
+ /* Deassert SIDDQ to power on analog block */
+ ret = regmap_write(rphy->grf, 0x0008, GENMASK(29, 29) | 0x0000);
+ if (ret)
+ return ret;
+
+ /* Do reset after exit IDDQ mode */
+ ret = rockchip_usb2phy_reset(rphy);
+ if (ret)
+ return ret;
+
+ /* suspend configuration */
+ ret |= regmap_write(rphy->grf, 0x000c, GENMASK(20, 16) | suspend_cfg);
+
+ /* HS DC Voltage Level Adjustment 4'b1001 : +5.89% */
+ ret |= regmap_write(rphy->grf, 0x0004, GENMASK(27, 24) | 0x0900);
+
+ /* HS Transmitter Pre-Emphasis Current Control 2'b10 : 2x */
+ ret |= regmap_write(rphy->grf, 0x0008, GENMASK(20, 19) | 0x0010);
+
+ if (!usb3otg)
+ return ret;
+
+ /* Pullup iddig pin for USB3_0 OTG mode */
+ ret |= regmap_write(rphy->grf, 0x0010, GENMASK(17, 16) | 0x0003);
+
+ return ret;
+}
+
+static const struct rockchip_usb2phy_cfg rk3036_phy_cfgs[] = {
+ {
+ .reg = 0x17c,
+ .num_ports = 2,
+ .phy_tuning = rk3128_usb2phy_tuning,
+ .clkout_ctl = { 0x017c, 11, 11, 1, 0 },
+ .port_cfgs = {
+ [USB2PHY_PORT_OTG] = {
+ .phy_sus = { 0x017c, 8, 0, 0, 0x1d1 },
+ .bvalid_det_en = { 0x017c, 14, 14, 0, 1 },
+ .bvalid_det_st = { 0x017c, 15, 15, 0, 1 },
+ .bvalid_det_clr = { 0x017c, 15, 15, 0, 1 },
+ .ls_det_en = { 0x017c, 12, 12, 0, 1 },
+ .ls_det_st = { 0x017c, 13, 13, 0, 1 },
+ .ls_det_clr = { 0x017c, 13, 13, 0, 1 },
+ .utmi_bvalid = { 0x014c, 8, 8, 0, 1 },
+ .utmi_id = { 0x014c, 11, 11, 0, 1 },
+ .utmi_ls = { 0x014c, 10, 9, 0, 1 },
+
+ },
+ [USB2PHY_PORT_HOST] = {
+ .phy_sus = { 0x0194, 8, 0, 0, 0x1d1 },
+ .ls_det_en = { 0x0194, 14, 14, 0, 1 },
+ .ls_det_st = { 0x0194, 15, 15, 0, 1 },
+ .ls_det_clr = { 0x0194, 15, 15, 0, 1 }
+ }
+ },
+ },
+ { /* sentinel */ }
+};
+
+static const struct rockchip_usb2phy_cfg rk3128_phy_cfgs[] = {
+ {
+ .reg = 0x17c,
+ .num_ports = 2,
+ .phy_tuning = rk3128_usb2phy_tuning,
+ .clkout_ctl = { 0x0190, 15, 15, 1, 0 },
+ .port_cfgs = {
+ [USB2PHY_PORT_OTG] = {
+ .phy_sus = { 0x017c, 8, 0, 0, 0x1d1 },
+ .bvalid_det_en = { 0x017c, 14, 14, 0, 1 },
+ .bvalid_det_st = { 0x017c, 15, 15, 0, 1 },
+ .bvalid_det_clr = { 0x017c, 15, 15, 0, 1 },
+ .idfall_det_en = { 0x01a0, 2, 2, 0, 1 },
+ .idfall_det_st = { 0x01a0, 3, 3, 0, 1 },
+ .idfall_det_clr = { 0x01a0, 3, 3, 0, 1 },
+ .idrise_det_en = { 0x01a0, 0, 0, 0, 1 },
+ .idrise_det_st = { 0x01a0, 1, 1, 0, 1 },
+ .idrise_det_clr = { 0x01a0, 1, 1, 0, 1 },
+ .ls_det_en = { 0x017c, 12, 12, 0, 1 },
+ .ls_det_st = { 0x017c, 13, 13, 0, 1 },
+ .ls_det_clr = { 0x017c, 13, 13, 0, 1 },
+ .utmi_bvalid = { 0x014c, 5, 5, 0, 1 },
+ .utmi_id = { 0x014c, 8, 8, 0, 1 },
+ .utmi_ls = { 0x014c, 7, 6, 0, 1 },
+ },
+ [USB2PHY_PORT_HOST] = {
+ .phy_sus = { 0x0194, 8, 0, 0, 0x1d1 },
+ .ls_det_en = { 0x0194, 14, 14, 0, 1 },
+ .ls_det_st = { 0x0194, 15, 15, 0, 1 },
+ .ls_det_clr = { 0x0194, 15, 15, 0, 1 }
+ }
+ },
+ .chg_det = {
+ .opmode = { 0x017c, 3, 0, 5, 1 },
+ .cp_det = { 0x02c0, 6, 6, 0, 1 },
+ .dcp_det = { 0x02c0, 5, 5, 0, 1 },
+ .dp_det = { 0x02c0, 7, 7, 0, 1 },
+ .idm_sink_en = { 0x0184, 8, 8, 0, 1 },
+ .idp_sink_en = { 0x0184, 7, 7, 0, 1 },
+ .idp_src_en = { 0x0184, 9, 9, 0, 1 },
+ .rdm_pdwn_en = { 0x0184, 10, 10, 0, 1 },
+ .vdm_src_en = { 0x0184, 12, 12, 0, 1 },
+ .vdp_src_en = { 0x0184, 11, 11, 0, 1 },
+ },
+ },
+ { /* sentinel */ }
+};
+
+static const struct rockchip_usb2phy_cfg rk3228_phy_cfgs[] = {
+ {
+ .reg = 0x760,
+ .num_ports = 2,
+ .clkout_ctl = { 0x0768, 4, 4, 1, 0 },
+ .port_cfgs = {
+ [USB2PHY_PORT_OTG] = {
+ .phy_sus = { 0x0760, 15, 0, 0, 0x1d1 },
+ .bvalid_det_en = { 0x0680, 3, 3, 0, 1 },
+ .bvalid_det_st = { 0x0690, 3, 3, 0, 1 },
+ .bvalid_det_clr = { 0x06a0, 3, 3, 0, 1 },
+ .idfall_det_en = { 0x0680, 6, 6, 0, 1 },
+ .idfall_det_st = { 0x0690, 6, 6, 0, 1 },
+ .idfall_det_clr = { 0x06a0, 6, 6, 0, 1 },
+ .idrise_det_en = { 0x0680, 5, 5, 0, 1 },
+ .idrise_det_st = { 0x0690, 5, 5, 0, 1 },
+ .idrise_det_clr = { 0x06a0, 5, 5, 0, 1 },
+ .ls_det_en = { 0x0680, 2, 2, 0, 1 },
+ .ls_det_st = { 0x0690, 2, 2, 0, 1 },
+ .ls_det_clr = { 0x06a0, 2, 2, 0, 1 },
+ .utmi_bvalid = { 0x0480, 4, 4, 0, 1 },
+ .utmi_id = { 0x0480, 1, 1, 0, 1 },
+ .utmi_ls = { 0x0480, 3, 2, 0, 1 },
+ },
+ [USB2PHY_PORT_HOST] = {
+ .phy_sus = { 0x0764, 15, 0, 0, 0x1d1 },
+ .ls_det_en = { 0x0680, 4, 4, 0, 1 },
+ .ls_det_st = { 0x0690, 4, 4, 0, 1 },
+ .ls_det_clr = { 0x06a0, 4, 4, 0, 1 }
+ }
+ },
+ .chg_det = {
+ .opmode = { 0x0760, 3, 0, 5, 1 },
+ .cp_det = { 0x0884, 4, 4, 0, 1 },
+ .dcp_det = { 0x0884, 3, 3, 0, 1 },
+ .dp_det = { 0x0884, 5, 5, 0, 1 },
+ .idm_sink_en = { 0x0768, 8, 8, 0, 1 },
+ .idp_sink_en = { 0x0768, 7, 7, 0, 1 },
+ .idp_src_en = { 0x0768, 9, 9, 0, 1 },
+ .rdm_pdwn_en = { 0x0768, 10, 10, 0, 1 },
+ .vdm_src_en = { 0x0768, 12, 12, 0, 1 },
+ .vdp_src_en = { 0x0768, 11, 11, 0, 1 },
+ },
+ },
+ {
+ .reg = 0x800,
+ .num_ports = 2,
+ .clkout_ctl = { 0x0808, 4, 4, 1, 0 },
+ .port_cfgs = {
+ [USB2PHY_PORT_OTG] = {
+ .phy_sus = { 0x800, 15, 0, 0, 0x1d1 },
+ .ls_det_en = { 0x0684, 0, 0, 0, 1 },
+ .ls_det_st = { 0x0694, 0, 0, 0, 1 },
+ .ls_det_clr = { 0x06a4, 0, 0, 0, 1 }
+ },
+ [USB2PHY_PORT_HOST] = {
+ .phy_sus = { 0x804, 15, 0, 0, 0x1d1 },
+ .ls_det_en = { 0x0684, 1, 1, 0, 1 },
+ .ls_det_st = { 0x0694, 1, 1, 0, 1 },
+ .ls_det_clr = { 0x06a4, 1, 1, 0, 1 }
+ }
+ },
+ },
+ { /* sentinel */ }
+};
+
+static const struct rockchip_usb2phy_cfg rk3308_phy_cfgs[] = {
+ {
+ .reg = 0x100,
+ .num_ports = 2,
+ .clkout_ctl = { 0x108, 4, 4, 1, 0 },
+ .port_cfgs = {
+ [USB2PHY_PORT_OTG] = {
+ .phy_sus = { 0x0100, 8, 0, 0, 0x1d1 },
+ .bvalid_det_en = { 0x3020, 3, 2, 0, 3 },
+ .bvalid_det_st = { 0x3024, 3, 2, 0, 3 },
+ .bvalid_det_clr = { 0x3028, 3, 2, 0, 3 },
+ .idfall_det_en = { 0x3020, 5, 5, 0, 1 },
+ .idfall_det_st = { 0x3024, 5, 5, 0, 1 },
+ .idfall_det_clr = { 0x3028, 5, 5, 0, 1 },
+ .idrise_det_en = { 0x3020, 4, 4, 0, 1 },
+ .idrise_det_st = { 0x3024, 4, 4, 0, 1 },
+ .idrise_det_clr = { 0x3028, 4, 4, 0, 1 },
+ .ls_det_en = { 0x3020, 0, 0, 0, 1 },
+ .ls_det_st = { 0x3024, 0, 0, 0, 1 },
+ .ls_det_clr = { 0x3028, 0, 0, 0, 1 },
+ .utmi_avalid = { 0x0120, 10, 10, 0, 1 },
+ .utmi_bvalid = { 0x0120, 9, 9, 0, 1 },
+ .utmi_id = { 0x0120, 6, 6, 0, 1 },
+ .utmi_ls = { 0x0120, 5, 4, 0, 1 },
+ },
+ [USB2PHY_PORT_HOST] = {
+ .phy_sus = { 0x0104, 8, 0, 0, 0x1d1 },
+ .ls_det_en = { 0x3020, 1, 1, 0, 1 },
+ .ls_det_st = { 0x3024, 1, 1, 0, 1 },
+ .ls_det_clr = { 0x3028, 1, 1, 0, 1 },
+ .utmi_ls = { 0x0120, 17, 16, 0, 1 },
+ .utmi_hstdet = { 0x0120, 19, 19, 0, 1 }
+ }
+ },
+ .chg_det = {
+ .opmode = { 0x0100, 3, 0, 5, 1 },
+ .cp_det = { 0x0120, 24, 24, 0, 1 },
+ .dcp_det = { 0x0120, 23, 23, 0, 1 },
+ .dp_det = { 0x0120, 25, 25, 0, 1 },
+ .idm_sink_en = { 0x0108, 8, 8, 0, 1 },
+ .idp_sink_en = { 0x0108, 7, 7, 0, 1 },
+ .idp_src_en = { 0x0108, 9, 9, 0, 1 },
+ .rdm_pdwn_en = { 0x0108, 10, 10, 0, 1 },
+ .vdm_src_en = { 0x0108, 12, 12, 0, 1 },
+ .vdp_src_en = { 0x0108, 11, 11, 0, 1 },
+ },
+ },
+ { /* sentinel */ }
+};
+
+static const struct rockchip_usb2phy_cfg rk3328_phy_cfgs[] = {
+ {
+ .reg = 0x100,
+ .num_ports = 2,
+ .clkout_ctl = { 0x108, 4, 4, 1, 0 },
+ .port_cfgs = {
+ [USB2PHY_PORT_OTG] = {
+ .phy_sus = { 0x0100, 15, 0, 0, 0x1d1 },
+ .bvalid_det_en = { 0x0110, 3, 2, 0, 3 },
+ .bvalid_det_st = { 0x0114, 3, 2, 0, 3 },
+ .bvalid_det_clr = { 0x0118, 3, 2, 0, 3 },
+ .idfall_det_en = { 0x0110, 5, 5, 0, 1 },
+ .idfall_det_st = { 0x0114, 5, 5, 0, 1 },
+ .idfall_det_clr = { 0x0118, 5, 5, 0, 1 },
+ .idrise_det_en = { 0x0110, 4, 4, 0, 1 },
+ .idrise_det_st = { 0x0114, 4, 4, 0, 1 },
+ .idrise_det_clr = { 0x0118, 4, 4, 0, 1 },
+ .ls_det_en = { 0x0110, 0, 0, 0, 1 },
+ .ls_det_st = { 0x0114, 0, 0, 0, 1 },
+ .ls_det_clr = { 0x0118, 0, 0, 0, 1 },
+ .utmi_avalid = { 0x0120, 10, 10, 0, 1 },
+ .utmi_bvalid = { 0x0120, 9, 9, 0, 1 },
+ .utmi_id = { 0x0120, 6, 6, 0, 1 },
+ .utmi_ls = { 0x0120, 5, 4, 0, 1 },
+ },
+ [USB2PHY_PORT_HOST] = {
+ .phy_sus = { 0x104, 15, 0, 0, 0x1d1 },
+ .ls_det_en = { 0x110, 1, 1, 0, 1 },
+ .ls_det_st = { 0x114, 1, 1, 0, 1 },
+ .ls_det_clr = { 0x118, 1, 1, 0, 1 },
+ .utmi_ls = { 0x120, 17, 16, 0, 1 },
+ .utmi_hstdet = { 0x120, 19, 19, 0, 1 }
+ }
+ },
+ .chg_det = {
+ .opmode = { 0x0100, 3, 0, 5, 1 },
+ .cp_det = { 0x0120, 24, 24, 0, 1 },
+ .dcp_det = { 0x0120, 23, 23, 0, 1 },
+ .dp_det = { 0x0120, 25, 25, 0, 1 },
+ .idm_sink_en = { 0x0108, 8, 8, 0, 1 },
+ .idp_sink_en = { 0x0108, 7, 7, 0, 1 },
+ .idp_src_en = { 0x0108, 9, 9, 0, 1 },
+ .rdm_pdwn_en = { 0x0108, 10, 10, 0, 1 },
+ .vdm_src_en = { 0x0108, 12, 12, 0, 1 },
+ .vdp_src_en = { 0x0108, 11, 11, 0, 1 },
+ },
+ },
+ { /* sentinel */ }
+};
+
+static const struct rockchip_usb2phy_cfg rk3366_phy_cfgs[] = {
+ {
+ .reg = 0x700,
+ .num_ports = 2,
+ .clkout_ctl = { 0x0724, 15, 15, 1, 0 },
+ .port_cfgs = {
+ [USB2PHY_PORT_HOST] = {
+ .phy_sus = { 0x0728, 15, 0, 0, 0x1d1 },
+ .ls_det_en = { 0x0680, 4, 4, 0, 1 },
+ .ls_det_st = { 0x0690, 4, 4, 0, 1 },
+ .ls_det_clr = { 0x06a0, 4, 4, 0, 1 },
+ .utmi_ls = { 0x049c, 14, 13, 0, 1 },
+ .utmi_hstdet = { 0x049c, 12, 12, 0, 1 }
+ }
+ },
+ },
+ { /* sentinel */ }
+};
+
+static const struct rockchip_usb2phy_cfg rk3399_phy_cfgs[] = {
+ {
+ .reg = 0xe450,
+ .num_ports = 2,
+ .clkout_ctl = { 0xe450, 4, 4, 1, 0 },
+ .port_cfgs = {
+ [USB2PHY_PORT_OTG] = {
+ .phy_sus = { 0xe454, 1, 0, 2, 1 },
+ .bvalid_det_en = { 0xe3c0, 3, 3, 0, 1 },
+ .bvalid_det_st = { 0xe3e0, 3, 3, 0, 1 },
+ .bvalid_det_clr = { 0xe3d0, 3, 3, 0, 1 },
+ .idfall_det_en = { 0xe3c0, 5, 5, 0, 1 },
+ .idfall_det_st = { 0xe3e0, 5, 5, 0, 1 },
+ .idfall_det_clr = { 0xe3d0, 5, 5, 0, 1 },
+ .idrise_det_en = { 0xe3c0, 4, 4, 0, 1 },
+ .idrise_det_st = { 0xe3e0, 4, 4, 0, 1 },
+ .idrise_det_clr = { 0xe3d0, 4, 4, 0, 1 },
+ .utmi_avalid = { 0xe2ac, 7, 7, 0, 1 },
+ .utmi_bvalid = { 0xe2ac, 12, 12, 0, 1 },
+ .utmi_id = { 0xe2ac, 8, 8, 0, 1 },
+ },
+ [USB2PHY_PORT_HOST] = {
+ .phy_sus = { 0xe458, 1, 0, 0x2, 0x1 },
+ .ls_det_en = { 0xe3c0, 6, 6, 0, 1 },
+ .ls_det_st = { 0xe3e0, 6, 6, 0, 1 },
+ .ls_det_clr = { 0xe3d0, 6, 6, 0, 1 },
+ .utmi_ls = { 0xe2ac, 22, 21, 0, 1 },
+ .utmi_hstdet = { 0xe2ac, 23, 23, 0, 1 }
+ }
+ },
+ .chg_det = {
+ .opmode = { 0xe454, 3, 0, 5, 1 },
+ .cp_det = { 0xe2ac, 2, 2, 0, 1 },
+ .dcp_det = { 0xe2ac, 1, 1, 0, 1 },
+ .dp_det = { 0xe2ac, 0, 0, 0, 1 },
+ .idm_sink_en = { 0xe450, 8, 8, 0, 1 },
+ .idp_sink_en = { 0xe450, 7, 7, 0, 1 },
+ .idp_src_en = { 0xe450, 9, 9, 0, 1 },
+ .rdm_pdwn_en = { 0xe450, 10, 10, 0, 1 },
+ .vdm_src_en = { 0xe450, 12, 12, 0, 1 },
+ .vdp_src_en = { 0xe450, 11, 11, 0, 1 },
+ },
+ },
+ {
+ .reg = 0xe460,
+ .num_ports = 2,
+ .clkout_ctl = { 0xe460, 4, 4, 1, 0 },
+ .port_cfgs = {
+ [USB2PHY_PORT_OTG] = {
+ .phy_sus = { 0xe464, 1, 0, 2, 1 },
+ .bvalid_det_en = { 0xe3c0, 8, 8, 0, 1 },
+ .bvalid_det_st = { 0xe3e0, 8, 8, 0, 1 },
+ .bvalid_det_clr = { 0xe3d0, 8, 8, 0, 1 },
+ .idfall_det_en = { 0xe3c0, 10, 10, 0, 1 },
+ .idfall_det_st = { 0xe3e0, 10, 10, 0, 1 },
+ .idfall_det_clr = { 0xe3d0, 10, 10, 0, 1 },
+ .idrise_det_en = { 0xe3c0, 9, 9, 0, 1 },
+ .idrise_det_st = { 0xe3e0, 9, 9, 0, 1 },
+ .idrise_det_clr = { 0xe3d0, 9, 9, 0, 1 },
+ .utmi_avalid = { 0xe2ac, 10, 10, 0, 1 },
+ .utmi_bvalid = { 0xe2ac, 16, 16, 0, 1 },
+ .utmi_id = { 0xe2ac, 11, 11, 0, 1 },
+ },
+ [USB2PHY_PORT_HOST] = {
+ .phy_sus = { 0xe468, 1, 0, 0x2, 0x1 },
+ .ls_det_en = { 0xe3c0, 11, 11, 0, 1 },
+ .ls_det_st = { 0xe3e0, 11, 11, 0, 1 },
+ .ls_det_clr = { 0xe3d0, 11, 11, 0, 1 },
+ .utmi_ls = { 0xe2ac, 26, 25, 0, 1 },
+ .utmi_hstdet = { 0xe2ac, 27, 27, 0, 1 }
+ }
+ },
+ },
+ { /* sentinel */ }
+};
+
+static const struct rockchip_usb2phy_cfg rk3528_phy_cfgs[] = {
+ {
+ .reg = 0xffdf0000,
+ .num_ports = 2,
+ .phy_tuning = rk3528_usb2phy_tuning,
+ .clkout_ctl_phy = { 0x041c, 7, 2, 0, 0x27 },
+ .port_cfgs = {
+ [USB2PHY_PORT_OTG] = {
+ .phy_sus = { 0x004c, 8, 0, 0, 0x1d1 },
+ .bvalid_det_en = { 0x0074, 3, 2, 0, 3 },
+ .bvalid_det_st = { 0x0078, 3, 2, 0, 3 },
+ .bvalid_det_clr = { 0x007c, 3, 2, 0, 3 },
+ .idfall_det_en = { 0x0074, 5, 5, 0, 1 },
+ .idfall_det_st = { 0x0078, 5, 5, 0, 1 },
+ .idfall_det_clr = { 0x007c, 5, 5, 0, 1 },
+ .idrise_det_en = { 0x0074, 4, 4, 0, 1 },
+ .idrise_det_st = { 0x0078, 4, 4, 0, 1 },
+ .idrise_det_clr = { 0x007c, 4, 4, 0, 1 },
+ .ls_det_en = { 0x0074, 0, 0, 0, 1 },
+ .ls_det_st = { 0x0078, 0, 0, 0, 1 },
+ .ls_det_clr = { 0x007c, 0, 0, 0, 1 },
+ .utmi_avalid = { 0x006c, 1, 1, 0, 1 },
+ .utmi_bvalid = { 0x006c, 0, 0, 0, 1 },
+ .utmi_id = { 0x006c, 6, 6, 0, 1 },
+ .utmi_ls = { 0x006c, 5, 4, 0, 1 },
+ },
+ [USB2PHY_PORT_HOST] = {
+ .phy_sus = { 0x005c, 8, 0, 0x1d2, 0x1d1 },
+ .ls_det_en = { 0x0090, 0, 0, 0, 1 },
+ .ls_det_st = { 0x0094, 0, 0, 0, 1 },
+ .ls_det_clr = { 0x0098, 0, 0, 0, 1 },
+ .utmi_ls = { 0x006c, 13, 12, 0, 1 },
+ .utmi_hstdet = { 0x006c, 15, 15, 0, 1 },
+ }
+ },
+ .chg_det = {
+ .opmode = { 0x004c, 3, 0, 5, 1 },
+ .cp_det = { 0x006c, 19, 19, 0, 1 },
+ .dcp_det = { 0x006c, 18, 18, 0, 1 },
+ .dp_det = { 0x006c, 20, 20, 0, 1 },
+ .idm_sink_en = { 0x0058, 1, 1, 0, 1 },
+ .idp_sink_en = { 0x0058, 0, 0, 0, 1 },
+ .idp_src_en = { 0x0058, 2, 2, 0, 1 },
+ .rdm_pdwn_en = { 0x0058, 3, 3, 0, 1 },
+ .vdm_src_en = { 0x0058, 5, 5, 0, 1 },
+ .vdp_src_en = { 0x0058, 4, 4, 0, 1 },
+ },
+ },
+ { /* sentinel */ }
+};
+
+static const struct rockchip_usb2phy_cfg rk3562_phy_cfgs[] = {
+ {
+ .reg = 0xff740000,
+ .num_ports = 2,
+ .clkout_ctl = { 0x0108, 4, 4, 1, 0 },
+ .port_cfgs = {
+ [USB2PHY_PORT_OTG] = {
+ .phy_sus = { 0x0100, 8, 0, 0, 0x1d1 },
+ .bvalid_det_en = { 0x0110, 2, 2, 0, 1 },
+ .bvalid_det_st = { 0x0114, 2, 2, 0, 1 },
+ .bvalid_det_clr = { 0x0118, 2, 2, 0, 1 },
+ .idfall_det_en = { 0x0110, 5, 5, 0, 1 },
+ .idfall_det_st = { 0x0114, 5, 5, 0, 1 },
+ .idfall_det_clr = { 0x0118, 5, 5, 0, 1 },
+ .idrise_det_en = { 0x0110, 4, 4, 0, 1 },
+ .idrise_det_st = { 0x0114, 4, 4, 0, 1 },
+ .idrise_det_clr = { 0x0118, 4, 4, 0, 1 },
+ .ls_det_en = { 0x0110, 0, 0, 0, 1 },
+ .ls_det_st = { 0x0114, 0, 0, 0, 1 },
+ .ls_det_clr = { 0x0118, 0, 0, 0, 1 },
+ .utmi_avalid = { 0x0120, 10, 10, 0, 1 },
+ .utmi_bvalid = { 0x0120, 9, 9, 0, 1 },
+ .utmi_ls = { 0x0120, 5, 4, 0, 1 },
+ },
+ [USB2PHY_PORT_HOST] = {
+ .phy_sus = { 0x0104, 8, 0, 0x1d2, 0x1d1 },
+ .ls_det_en = { 0x0110, 1, 1, 0, 1 },
+ .ls_det_st = { 0x0114, 1, 1, 0, 1 },
+ .ls_det_clr = { 0x0118, 1, 1, 0, 1 },
+ .utmi_ls = { 0x0120, 17, 16, 0, 1 },
+ .utmi_hstdet = { 0x0120, 19, 19, 0, 1 }
+ }
+ },
+ .chg_det = {
+ .cp_det = { 0x0120, 24, 24, 0, 1 },
+ .dcp_det = { 0x0120, 23, 23, 0, 1 },
+ .dp_det = { 0x0120, 25, 25, 0, 1 },
+ .idm_sink_en = { 0x0108, 8, 8, 0, 1 },
+ .idp_sink_en = { 0x0108, 7, 7, 0, 1 },
+ .idp_src_en = { 0x0108, 9, 9, 0, 1 },
+ .rdm_pdwn_en = { 0x0108, 10, 10, 0, 1 },
+ .vdm_src_en = { 0x0108, 12, 12, 0, 1 },
+ .vdp_src_en = { 0x0108, 11, 11, 0, 1 },
+ },
+ },
+ { /* sentinel */ }
+};
+
+static const struct rockchip_usb2phy_cfg rk3568_phy_cfgs[] = {
+ {
+ .reg = 0xfe8a0000,
+ .num_ports = 2,
+ .clkout_ctl = { 0x0008, 4, 4, 1, 0 },
+ .port_cfgs = {
+ [USB2PHY_PORT_OTG] = {
+ .phy_sus = { 0x0000, 8, 0, 0, 0x1d1 },
+ .bvalid_det_en = { 0x0080, 3, 2, 0, 3 },
+ .bvalid_det_st = { 0x0084, 3, 2, 0, 3 },
+ .bvalid_det_clr = { 0x0088, 3, 2, 0, 3 },
+ .idfall_det_en = { 0x0080, 5, 5, 0, 1 },
+ .idfall_det_st = { 0x0084, 5, 5, 0, 1 },
+ .idfall_det_clr = { 0x0088, 5, 5, 0, 1 },
+ .idrise_det_en = { 0x0080, 4, 4, 0, 1 },
+ .idrise_det_st = { 0x0084, 4, 4, 0, 1 },
+ .idrise_det_clr = { 0x0088, 4, 4, 0, 1 },
+ .utmi_avalid = { 0x00c0, 10, 10, 0, 1 },
+ .utmi_bvalid = { 0x00c0, 9, 9, 0, 1 },
+ .utmi_id = { 0x00c0, 6, 6, 0, 1 },
+ },
+ [USB2PHY_PORT_HOST] = {
+ /* Select suspend control from controller */
+ .phy_sus = { 0x0004, 8, 0, 0x1d2, 0x1d2 },
+ .ls_det_en = { 0x0080, 1, 1, 0, 1 },
+ .ls_det_st = { 0x0084, 1, 1, 0, 1 },
+ .ls_det_clr = { 0x0088, 1, 1, 0, 1 },
+ .utmi_ls = { 0x00c0, 17, 16, 0, 1 },
+ .utmi_hstdet = { 0x00c0, 19, 19, 0, 1 }
+ }
+ },
+ .chg_det = {
+ .opmode = { 0x0000, 3, 0, 5, 1 },
+ .cp_det = { 0x00c0, 24, 24, 0, 1 },
+ .dcp_det = { 0x00c0, 23, 23, 0, 1 },
+ .dp_det = { 0x00c0, 25, 25, 0, 1 },
+ .idm_sink_en = { 0x0008, 8, 8, 0, 1 },
+ .idp_sink_en = { 0x0008, 7, 7, 0, 1 },
+ .idp_src_en = { 0x0008, 9, 9, 0, 1 },
+ .rdm_pdwn_en = { 0x0008, 10, 10, 0, 1 },
+ .vdm_src_en = { 0x0008, 12, 12, 0, 1 },
+ .vdp_src_en = { 0x0008, 11, 11, 0, 1 },
+ },
+ },
+ {
+ .reg = 0xfe8b0000,
+ .num_ports = 2,
+ .clkout_ctl = { 0x0008, 4, 4, 1, 0 },
+ .port_cfgs = {
+ [USB2PHY_PORT_OTG] = {
+ .phy_sus = { 0x0000, 8, 0, 0x1d2, 0x1d1 },
+ .ls_det_en = { 0x0080, 0, 0, 0, 1 },
+ .ls_det_st = { 0x0084, 0, 0, 0, 1 },
+ .ls_det_clr = { 0x0088, 0, 0, 0, 1 },
+ .utmi_ls = { 0x00c0, 5, 4, 0, 1 },
+ .utmi_hstdet = { 0x00c0, 7, 7, 0, 1 }
+ },
+ [USB2PHY_PORT_HOST] = {
+ .phy_sus = { 0x0004, 8, 0, 0x1d2, 0x1d1 },
+ .ls_det_en = { 0x0080, 1, 1, 0, 1 },
+ .ls_det_st = { 0x0084, 1, 1, 0, 1 },
+ .ls_det_clr = { 0x0088, 1, 1, 0, 1 },
+ .utmi_ls = { 0x00c0, 17, 16, 0, 1 },
+ .utmi_hstdet = { 0x00c0, 19, 19, 0, 1 }
+ }
+ },
+ },
+ { /* sentinel */ }
+};
+
+static const struct rockchip_usb2phy_cfg rk3576_phy_cfgs[] = {
+ {
+ .reg = 0x0,
+ .num_ports = 1,
+ .phy_tuning = rk3576_usb2phy_tuning,
+ .clkout_ctl = { 0x0008, 0, 0, 1, 0 },
+ .port_cfgs = {
+ [USB2PHY_PORT_OTG] = {
+ .phy_sus = { 0x0000, 8, 0, 0, 0x1d1 },
+ .bvalid_det_en = { 0x00c0, 1, 1, 0, 1 },
+ .bvalid_det_st = { 0x00c4, 1, 1, 0, 1 },
+ .bvalid_det_clr = { 0x00c8, 1, 1, 0, 1 },
+ .ls_det_en = { 0x00c0, 0, 0, 0, 1 },
+ .ls_det_st = { 0x00c4, 0, 0, 0, 1 },
+ .ls_det_clr = { 0x00c8, 0, 0, 0, 1 },
+ .disfall_en = { 0x00c0, 6, 6, 0, 1 },
+ .disfall_st = { 0x00c4, 6, 6, 0, 1 },
+ .disfall_clr = { 0x00c8, 6, 6, 0, 1 },
+ .disrise_en = { 0x00c0, 5, 5, 0, 1 },
+ .disrise_st = { 0x00c4, 5, 5, 0, 1 },
+ .disrise_clr = { 0x00c8, 5, 5, 0, 1 },
+ .utmi_avalid = { 0x0080, 1, 1, 0, 1 },
+ .utmi_bvalid = { 0x0080, 0, 0, 0, 1 },
+ .utmi_ls = { 0x0080, 5, 4, 0, 1 },
+ }
+ },
+ .chg_det = {
+ .cp_det = { 0x0080, 8, 8, 0, 1 },
+ .dcp_det = { 0x0080, 8, 8, 0, 1 },
+ .dp_det = { 0x0080, 9, 9, 1, 0 },
+ .idm_sink_en = { 0x0010, 5, 5, 1, 0 },
+ .idp_sink_en = { 0x0010, 5, 5, 0, 1 },
+ .idp_src_en = { 0x0010, 14, 14, 0, 1 },
+ .rdm_pdwn_en = { 0x0010, 14, 14, 0, 1 },
+ .vdm_src_en = { 0x0010, 7, 6, 0, 3 },
+ .vdp_src_en = { 0x0010, 7, 6, 0, 3 },
+ },
+ },
+ {
+ .reg = 0x2000,
+ .num_ports = 1,
+ .phy_tuning = rk3576_usb2phy_tuning,
+ .clkout_ctl = { 0x2008, 0, 0, 1, 0 },
+ .port_cfgs = {
+ [USB2PHY_PORT_OTG] = {
+ .phy_sus = { 0x2000, 8, 0, 0, 0x1d1 },
+ .bvalid_det_en = { 0x20c0, 1, 1, 0, 1 },
+ .bvalid_det_st = { 0x20c4, 1, 1, 0, 1 },
+ .bvalid_det_clr = { 0x20c8, 1, 1, 0, 1 },
+ .ls_det_en = { 0x20c0, 0, 0, 0, 1 },
+ .ls_det_st = { 0x20c4, 0, 0, 0, 1 },
+ .ls_det_clr = { 0x20c8, 0, 0, 0, 1 },
+ .disfall_en = { 0x20c0, 6, 6, 0, 1 },
+ .disfall_st = { 0x20c4, 6, 6, 0, 1 },
+ .disfall_clr = { 0x20c8, 6, 6, 0, 1 },
+ .disrise_en = { 0x20c0, 5, 5, 0, 1 },
+ .disrise_st = { 0x20c4, 5, 5, 0, 1 },
+ .disrise_clr = { 0x20c8, 5, 5, 0, 1 },
+ .utmi_avalid = { 0x2080, 1, 1, 0, 1 },
+ .utmi_bvalid = { 0x2080, 0, 0, 0, 1 },
+ .utmi_ls = { 0x2080, 5, 4, 0, 1 },
+ }
+ },
+ .chg_det = {
+ .cp_det = { 0x2080, 8, 8, 0, 1 },
+ .dcp_det = { 0x2080, 8, 8, 0, 1 },
+ .dp_det = { 0x2080, 9, 9, 1, 0 },
+ .idm_sink_en = { 0x2010, 5, 5, 1, 0 },
+ .idp_sink_en = { 0x2010, 5, 5, 0, 1 },
+ .idp_src_en = { 0x2010, 14, 14, 0, 1 },
+ .rdm_pdwn_en = { 0x2010, 14, 14, 0, 1 },
+ .vdm_src_en = { 0x2010, 7, 6, 0, 3 },
+ .vdp_src_en = { 0x2010, 7, 6, 0, 3 },
+ },
+ },
+ { /* sentinel */ }
+};
+
+static const struct rockchip_usb2phy_cfg rk3588_phy_cfgs[] = {
+ {
+ .reg = 0x0000,
+ .num_ports = 1,
+ .phy_tuning = rk3588_usb2phy_tuning,
+ .clkout_ctl = { 0x0000, 0, 0, 1, 0 },
+ .port_cfgs = {
+ [USB2PHY_PORT_OTG] = {
+ .phy_sus = { 0x000c, 11, 11, 0, 1 },
+ .bvalid_det_en = { 0x0080, 1, 1, 0, 1 },
+ .bvalid_det_st = { 0x0084, 1, 1, 0, 1 },
+ .bvalid_det_clr = { 0x0088, 1, 1, 0, 1 },
+ .ls_det_en = { 0x0080, 0, 0, 0, 1 },
+ .ls_det_st = { 0x0084, 0, 0, 0, 1 },
+ .ls_det_clr = { 0x0088, 0, 0, 0, 1 },
+ .disfall_en = { 0x0080, 6, 6, 0, 1 },
+ .disfall_st = { 0x0084, 6, 6, 0, 1 },
+ .disfall_clr = { 0x0088, 6, 6, 0, 1 },
+ .disrise_en = { 0x0080, 5, 5, 0, 1 },
+ .disrise_st = { 0x0084, 5, 5, 0, 1 },
+ .disrise_clr = { 0x0088, 5, 5, 0, 1 },
+ .utmi_avalid = { 0x00c0, 7, 7, 0, 1 },
+ .utmi_bvalid = { 0x00c0, 6, 6, 0, 1 },
+ .utmi_ls = { 0x00c0, 10, 9, 0, 1 },
+ }
+ },
+ .chg_det = {
+ .cp_det = { 0x00c0, 0, 0, 0, 1 },
+ .dcp_det = { 0x00c0, 0, 0, 0, 1 },
+ .dp_det = { 0x00c0, 1, 1, 1, 0 },
+ .idm_sink_en = { 0x0008, 5, 5, 1, 0 },
+ .idp_sink_en = { 0x0008, 5, 5, 0, 1 },
+ .idp_src_en = { 0x0008, 14, 14, 0, 1 },
+ .rdm_pdwn_en = { 0x0008, 14, 14, 0, 1 },
+ .vdm_src_en = { 0x0008, 7, 6, 0, 3 },
+ .vdp_src_en = { 0x0008, 7, 6, 0, 3 },
+ },
+ },
+ {
+ .reg = 0x4000,
+ .num_ports = 1,
+ .phy_tuning = rk3588_usb2phy_tuning,
+ .clkout_ctl = { 0x0000, 0, 0, 1, 0 },
+ .port_cfgs = {
+ [USB2PHY_PORT_OTG] = {
+ .phy_sus = { 0x000c, 11, 11, 0, 1 },
+ .bvalid_det_en = { 0x0080, 1, 1, 0, 1 },
+ .bvalid_det_st = { 0x0084, 1, 1, 0, 1 },
+ .bvalid_det_clr = { 0x0088, 1, 1, 0, 1 },
+ .ls_det_en = { 0x0080, 0, 0, 0, 1 },
+ .ls_det_st = { 0x0084, 0, 0, 0, 1 },
+ .ls_det_clr = { 0x0088, 0, 0, 0, 1 },
+ .disfall_en = { 0x0080, 6, 6, 0, 1 },
+ .disfall_st = { 0x0084, 6, 6, 0, 1 },
+ .disfall_clr = { 0x0088, 6, 6, 0, 1 },
+ .disrise_en = { 0x0080, 5, 5, 0, 1 },
+ .disrise_st = { 0x0084, 5, 5, 0, 1 },
+ .disrise_clr = { 0x0088, 5, 5, 0, 1 },
+ .utmi_avalid = { 0x00c0, 7, 7, 0, 1 },
+ .utmi_bvalid = { 0x00c0, 6, 6, 0, 1 },
+ .utmi_ls = { 0x00c0, 10, 9, 0, 1 },
+ }
+ },
+ .chg_det = {
+ .cp_det = { 0x00c0, 0, 0, 0, 1 },
+ .dcp_det = { 0x00c0, 0, 0, 0, 1 },
+ .dp_det = { 0x00c0, 1, 1, 1, 0 },
+ .idm_sink_en = { 0x0008, 5, 5, 1, 0 },
+ .idp_sink_en = { 0x0008, 5, 5, 0, 1 },
+ .idp_src_en = { 0x0008, 14, 14, 0, 1 },
+ .rdm_pdwn_en = { 0x0008, 14, 14, 0, 1 },
+ .vdm_src_en = { 0x0008, 7, 6, 0, 3 },
+ .vdp_src_en = { 0x0008, 7, 6, 0, 3 },
+ },
+ },
+ {
+ .reg = 0x8000,
+ .num_ports = 1,
+ .phy_tuning = rk3588_usb2phy_tuning,
+ .clkout_ctl = { 0x0000, 0, 0, 1, 0 },
+ .port_cfgs = {
+ [USB2PHY_PORT_HOST] = {
+ .phy_sus = { 0x0008, 2, 2, 0, 1 },
+ .ls_det_en = { 0x0080, 0, 0, 0, 1 },
+ .ls_det_st = { 0x0084, 0, 0, 0, 1 },
+ .ls_det_clr = { 0x0088, 0, 0, 0, 1 },
+ .disfall_en = { 0x0080, 6, 6, 0, 1 },
+ .disfall_st = { 0x0084, 6, 6, 0, 1 },
+ .disfall_clr = { 0x0088, 6, 6, 0, 1 },
+ .disrise_en = { 0x0080, 5, 5, 0, 1 },
+ .disrise_st = { 0x0084, 5, 5, 0, 1 },
+ .disrise_clr = { 0x0088, 5, 5, 0, 1 },
+ .utmi_ls = { 0x00c0, 10, 9, 0, 1 },
+ }
+ },
+ },
+ {
+ .reg = 0xc000,
+ .num_ports = 1,
+ .phy_tuning = rk3588_usb2phy_tuning,
+ .clkout_ctl = { 0x0000, 0, 0, 1, 0 },
+ .port_cfgs = {
+ [USB2PHY_PORT_HOST] = {
+ .phy_sus = { 0x0008, 2, 2, 0, 1 },
+ .ls_det_en = { 0x0080, 0, 0, 0, 1 },
+ .ls_det_st = { 0x0084, 0, 0, 0, 1 },
+ .ls_det_clr = { 0x0088, 0, 0, 0, 1 },
+ .disfall_en = { 0x0080, 6, 6, 0, 1 },
+ .disfall_st = { 0x0084, 6, 6, 0, 1 },
+ .disfall_clr = { 0x0088, 6, 6, 0, 1 },
+ .disrise_en = { 0x0080, 5, 5, 0, 1 },
+ .disrise_st = { 0x0084, 5, 5, 0, 1 },
+ .disrise_clr = { 0x0088, 5, 5, 0, 1 },
+ .utmi_ls = { 0x00c0, 10, 9, 0, 1 },
+ }
+ },
+ },
+ { /* sentinel */ }
+};
+
+static const struct rockchip_usb2phy_cfg rv1108_phy_cfgs[] = {
+ {
+ .reg = 0x100,
+ .num_ports = 2,
+ .clkout_ctl = { 0x108, 4, 4, 1, 0 },
+ .port_cfgs = {
+ [USB2PHY_PORT_OTG] = {
+ .phy_sus = { 0x0100, 15, 0, 0, 0x1d1 },
+ .bvalid_det_en = { 0x0680, 3, 3, 0, 1 },
+ .bvalid_det_st = { 0x0690, 3, 3, 0, 1 },
+ .bvalid_det_clr = { 0x06a0, 3, 3, 0, 1 },
+ .ls_det_en = { 0x0680, 2, 2, 0, 1 },
+ .ls_det_st = { 0x0690, 2, 2, 0, 1 },
+ .ls_det_clr = { 0x06a0, 2, 2, 0, 1 },
+ .utmi_bvalid = { 0x0804, 10, 10, 0, 1 },
+ .utmi_ls = { 0x0804, 13, 12, 0, 1 },
+ },
+ [USB2PHY_PORT_HOST] = {
+ .phy_sus = { 0x0104, 15, 0, 0, 0x1d1 },
+ .ls_det_en = { 0x0680, 4, 4, 0, 1 },
+ .ls_det_st = { 0x0690, 4, 4, 0, 1 },
+ .ls_det_clr = { 0x06a0, 4, 4, 0, 1 },
+ .utmi_ls = { 0x0804, 9, 8, 0, 1 },
+ .utmi_hstdet = { 0x0804, 7, 7, 0, 1 }
+ }
+ },
+ .chg_det = {
+ .opmode = { 0x0100, 3, 0, 5, 1 },
+ .cp_det = { 0x0804, 1, 1, 0, 1 },
+ .dcp_det = { 0x0804, 0, 0, 0, 1 },
+ .dp_det = { 0x0804, 2, 2, 0, 1 },
+ .idm_sink_en = { 0x0108, 8, 8, 0, 1 },
+ .idp_sink_en = { 0x0108, 7, 7, 0, 1 },
+ .idp_src_en = { 0x0108, 9, 9, 0, 1 },
+ .rdm_pdwn_en = { 0x0108, 10, 10, 0, 1 },
+ .vdm_src_en = { 0x0108, 12, 12, 0, 1 },
+ .vdp_src_en = { 0x0108, 11, 11, 0, 1 },
+ },
+ },
+ { /* sentinel */ }
+};
+
+static const struct of_device_id rockchip_usb2phy_dt_match[] = {
+ { .compatible = "rockchip,px30-usb2phy", .data = &rk3328_phy_cfgs },
+ { .compatible = "rockchip,rk3036-usb2phy", .data = &rk3036_phy_cfgs },
+ { .compatible = "rockchip,rk3128-usb2phy", .data = &rk3128_phy_cfgs },
+ { .compatible = "rockchip,rk3228-usb2phy", .data = &rk3228_phy_cfgs },
+ { .compatible = "rockchip,rk3308-usb2phy", .data = &rk3308_phy_cfgs },
+ { .compatible = "rockchip,rk3328-usb2phy", .data = &rk3328_phy_cfgs },
+ { .compatible = "rockchip,rk3366-usb2phy", .data = &rk3366_phy_cfgs },
+ { .compatible = "rockchip,rk3399-usb2phy", .data = &rk3399_phy_cfgs },
+ { .compatible = "rockchip,rk3528-usb2phy", .data = &rk3528_phy_cfgs },
+ { .compatible = "rockchip,rk3562-usb2phy", .data = &rk3562_phy_cfgs },
+ { .compatible = "rockchip,rk3568-usb2phy", .data = &rk3568_phy_cfgs },
+ { .compatible = "rockchip,rk3576-usb2phy", .data = &rk3576_phy_cfgs },
+ { .compatible = "rockchip,rk3588-usb2phy", .data = &rk3588_phy_cfgs },
+ { .compatible = "rockchip,rv1108-usb2phy", .data = &rv1108_phy_cfgs },
+ {}
+};
+MODULE_DEVICE_TABLE(of, rockchip_usb2phy_dt_match);
+
+static struct platform_driver rockchip_usb2phy_driver = {
+ .probe = rockchip_usb2phy_probe,
+ .driver = {
+ .name = "rockchip-usb2phy",
+ .of_match_table = rockchip_usb2phy_dt_match,
+ },
+};
+module_platform_driver(rockchip_usb2phy_driver);
+
+MODULE_AUTHOR("Frank Wang <frank.wang@rock-chips.com>");
+MODULE_DESCRIPTION("Rockchip USB2.0 PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c b/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c
new file mode 100644
index 000000000..7843356a4
--- /dev/null
+++ b/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c
@@ -0,0 +1,1431 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Rockchip PIPE USB3.0 PCIE SATA Combo Phy driver
+ *
+ * Copyright (C) 2021 Rockchip Electronics Co., Ltd.
+ */
+
+#include <dt-bindings/phy/phy.h>
+#include <linux/clk.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/units.h>
+
+#define BIT_WRITEABLE_SHIFT 16
+#define REF_CLOCK_24MHz (24 * HZ_PER_MHZ)
+#define REF_CLOCK_25MHz (25 * HZ_PER_MHZ)
+#define REF_CLOCK_100MHz (100 * HZ_PER_MHZ)
+
+/* RK3528 COMBO PHY REG */
+#define RK3528_PHYREG5 0x14
+#define RK3528_PHYREG5_GATE_TX_PCK_SEL BIT(3)
+#define RK3528_PHYREG5_GATE_TX_PCK_DLY_PLL_OFF BIT(3)
+#define RK3528_PHYREG6 0x18
+#define RK3528_PHYREG6_PLL_KVCO GENMASK(12, 10)
+#define RK3528_PHYREG6_PLL_KVCO_VALUE 0x2
+#define RK3528_PHYREG6_SSC_DIR GENMASK(5, 4)
+#define RK3528_PHYREG6_SSC_UPWARD 0
+#define RK3528_PHYREG6_SSC_DOWNWARD 1
+
+#define RK3528_PHYREG40 0x100
+#define RK3528_PHYREG40_SSC_EN BIT(20)
+#define RK3528_PHYREG40_SSC_CNT GENMASK(10, 0)
+#define RK3528_PHYREG40_SSC_CNT_VALUE 0x17d
+
+#define RK3528_PHYREG42 0x108
+#define RK3528_PHYREG42_CKDRV_CLK_SEL BIT(29)
+#define RK3528_PHYREG42_CKDRV_CLK_PLL 0
+#define RK3528_PHYREG42_CKDRV_CLK_CKRCV 1
+#define RK3528_PHYREG42_PLL_LPF_R1_ADJ GENMASK(10, 7)
+#define RK3528_PHYREG42_PLL_LPF_R1_ADJ_VALUE 0x9
+#define RK3528_PHYREG42_PLL_CHGPUMP_CUR_ADJ GENMASK(6, 4)
+#define RK3528_PHYREG42_PLL_CHGPUMP_CUR_ADJ_VALUE 0x7
+#define RK3528_PHYREG42_PLL_KVCO_ADJ GENMASK(2, 0)
+#define RK3528_PHYREG42_PLL_KVCO_ADJ_VALUE 0x0
+
+#define RK3528_PHYREG80 0x200
+#define RK3528_PHYREG80_CTLE_EN BIT(17)
+
+#define RK3528_PHYREG81 0x204
+#define RK3528_PHYREG81_CDR_PHASE_PATH_GAIN_2X BIT(5)
+#define RK3528_PHYREG81_SLEW_RATE_CTRL GENMASK(2, 0)
+#define RK3528_PHYREG81_SLEW_RATE_CTRL_SLOW 0x7
+
+#define RK3528_PHYREG83 0x20c
+#define RK3528_PHYREG83_RX_SQUELCH GENMASK(2, 0)
+#define RK3528_PHYREG83_RX_SQUELCH_VALUE 0x6
+
+#define RK3528_PHYREG86 0x218
+#define RK3528_PHYREG86_RTERM_DET_CLK_EN BIT(14)
+
+/* RK3568 COMBO PHY REG */
+#define RK3568_PHYREG6 0x14
+#define RK3568_PHYREG6_PLL_DIV_MASK GENMASK(7, 6)
+#define RK3568_PHYREG6_PLL_DIV_SHIFT 6
+#define RK3568_PHYREG6_PLL_DIV_2 1
+
+#define RK3568_PHYREG7 0x18
+#define RK3568_PHYREG7_TX_RTERM_MASK GENMASK(7, 4)
+#define RK3568_PHYREG7_TX_RTERM_SHIFT 4
+#define RK3568_PHYREG7_TX_RTERM_50OHM 8
+#define RK3568_PHYREG7_RX_RTERM_MASK GENMASK(3, 0)
+#define RK3568_PHYREG7_RX_RTERM_SHIFT 0
+#define RK3568_PHYREG7_RX_RTERM_44OHM 15
+
+#define RK3568_PHYREG8 0x1C
+#define RK3568_PHYREG8_SSC_EN BIT(4)
+
+#define RK3568_PHYREG11 0x28
+#define RK3568_PHYREG11_SU_TRIM_0_7 0xF0
+
+#define RK3568_PHYREG12 0x2C
+#define RK3568_PHYREG12_PLL_LPF_ADJ_VALUE 4
+
+#define RK3568_PHYREG13 0x30
+#define RK3568_PHYREG13_RESISTER_MASK GENMASK(5, 4)
+#define RK3568_PHYREG13_RESISTER_SHIFT 0x4
+#define RK3568_PHYREG13_RESISTER_HIGH_Z 3
+#define RK3568_PHYREG13_CKRCV_AMP0 BIT(7)
+
+#define RK3568_PHYREG14 0x34
+#define RK3568_PHYREG14_CKRCV_AMP1 BIT(0)
+
+#define RK3568_PHYREG15 0x38
+#define RK3568_PHYREG15_CTLE_EN BIT(0)
+#define RK3568_PHYREG15_SSC_CNT_MASK GENMASK(7, 6)
+#define RK3568_PHYREG15_SSC_CNT_SHIFT 6
+#define RK3568_PHYREG15_SSC_CNT_VALUE 1
+
+#define RK3568_PHYREG16 0x3C
+#define RK3568_PHYREG16_SSC_CNT_VALUE 0x5f
+
+#define RK3568_PHYREG18 0x44
+#define RK3568_PHYREG18_PLL_LOOP 0x32
+
+#define RK3568_PHYREG30 0x74
+#define RK3568_PHYREG30_GATE_TX_PCK_SEL BIT(7)
+#define RK3568_PHYREG30_GATE_TX_PCK_DLY_PLL_OFF BIT(7)
+
+#define RK3568_PHYREG32 0x7C
+#define RK3568_PHYREG32_SSC_MASK GENMASK(7, 4)
+#define RK3568_PHYREG32_SSC_DIR_MASK GENMASK(5, 4)
+#define RK3568_PHYREG32_SSC_DIR_SHIFT 4
+#define RK3568_PHYREG32_SSC_UPWARD 0
+#define RK3568_PHYREG32_SSC_DOWNWARD 1
+#define RK3568_PHYREG32_SSC_OFFSET_MASK GENMASK(7, 6)
+#define RK3568_PHYREG32_SSC_OFFSET_SHIFT 6
+#define RK3568_PHYREG32_SSC_OFFSET_500PPM 1
+
+#define RK3568_PHYREG33 0x80
+#define RK3568_PHYREG33_PLL_SSC_CTRL BIT(5)
+#define RK3568_PHYREG33_PLL_KVCO_MASK GENMASK(4, 2)
+#define RK3568_PHYREG33_PLL_KVCO_SHIFT 2
+#define RK3568_PHYREG33_PLL_KVCO_VALUE 2
+#define RK3576_PHYREG33_PLL_KVCO_VALUE 4
+
+/* RK3588 COMBO PHY registers */
+#define RK3588_PHYREG27 0x6C
+#define RK3588_PHYREG27_RX_TRIM 0x4C
+
+/* RK3576 COMBO PHY registers */
+#define RK3576_PHYREG10 0x24
+#define RK3576_PHYREG10_SSC_PCM_MASK GENMASK(3, 0)
+#define RK3576_PHYREG10_SSC_PCM_3500PPM 7
+
+#define RK3576_PHYREG17 0x40
+
+#define RK3576_PHYREG21 0x50
+#define RK3576_PHYREG21_RX_SQUELCH_VAL 0x0D
+
+#define RK3576_PHYREG30 0x74
+
+struct rockchip_combphy_priv;
+
+struct combphy_reg {
+ u16 offset;
+ u16 bitend;
+ u16 bitstart;
+ u16 disable;
+ u16 enable;
+};
+
+struct rockchip_combphy_grfcfg {
+ struct combphy_reg pcie_mode_set;
+ struct combphy_reg usb_mode_set;
+ struct combphy_reg sgmii_mode_set;
+ struct combphy_reg qsgmii_mode_set;
+ struct combphy_reg pipe_rxterm_set;
+ struct combphy_reg pipe_txelec_set;
+ struct combphy_reg pipe_txcomp_set;
+ struct combphy_reg pipe_clk_24m;
+ struct combphy_reg pipe_clk_25m;
+ struct combphy_reg pipe_clk_100m;
+ struct combphy_reg pipe_phymode_sel;
+ struct combphy_reg pipe_rate_sel;
+ struct combphy_reg pipe_rxterm_sel;
+ struct combphy_reg pipe_txelec_sel;
+ struct combphy_reg pipe_txcomp_sel;
+ struct combphy_reg pipe_clk_ext;
+ struct combphy_reg pipe_sel_usb;
+ struct combphy_reg pipe_sel_qsgmii;
+ struct combphy_reg pipe_phy_status;
+ struct combphy_reg con0_for_pcie;
+ struct combphy_reg con1_for_pcie;
+ struct combphy_reg con2_for_pcie;
+ struct combphy_reg con3_for_pcie;
+ struct combphy_reg con0_for_sata;
+ struct combphy_reg con1_for_sata;
+ struct combphy_reg con2_for_sata;
+ struct combphy_reg con3_for_sata;
+ struct combphy_reg pipe_con0_for_sata;
+ struct combphy_reg pipe_con1_for_sata;
+ struct combphy_reg pipe_xpcs_phy_ready;
+ struct combphy_reg pipe_pcie1l0_sel;
+ struct combphy_reg pipe_pcie1l1_sel;
+ struct combphy_reg u3otg0_port_en;
+ struct combphy_reg u3otg1_port_en;
+};
+
+struct rockchip_combphy_cfg {
+ unsigned int num_phys;
+ unsigned int phy_ids[3];
+ const struct rockchip_combphy_grfcfg *grfcfg;
+ int (*combphy_cfg)(struct rockchip_combphy_priv *priv);
+};
+
+struct rockchip_combphy_priv {
+ u8 type;
+ int id;
+ void __iomem *mmio;
+ int num_clks;
+ struct clk_bulk_data *clks;
+ struct device *dev;
+ struct regmap *pipe_grf;
+ struct regmap *phy_grf;
+ struct phy *phy;
+ struct reset_control *phy_rst;
+ const struct rockchip_combphy_cfg *cfg;
+ bool enable_ssc;
+ bool ext_refclk;
+ struct clk *refclk;
+};
+
+static void rockchip_combphy_updatel(struct rockchip_combphy_priv *priv,
+ int mask, int val, int reg)
+{
+ unsigned int temp;
+
+ temp = readl(priv->mmio + reg);
+ temp = (temp & ~(mask)) | val;
+ writel(temp, priv->mmio + reg);
+}
+
+static int rockchip_combphy_param_write(struct regmap *base,
+ const struct combphy_reg *reg, bool en)
+{
+ u32 val, mask, tmp;
+
+ tmp = en ? reg->enable : reg->disable;
+ mask = GENMASK(reg->bitend, reg->bitstart);
+ val = (tmp << reg->bitstart) | (mask << BIT_WRITEABLE_SHIFT);
+
+ return regmap_write(base, reg->offset, val);
+}
+
+static u32 rockchip_combphy_is_ready(struct rockchip_combphy_priv *priv)
+{
+ const struct rockchip_combphy_grfcfg *cfg = priv->cfg->grfcfg;
+ u32 mask, val;
+
+ mask = GENMASK(cfg->pipe_phy_status.bitend,
+ cfg->pipe_phy_status.bitstart);
+
+ regmap_read(priv->phy_grf, cfg->pipe_phy_status.offset, &val);
+ val = (val & mask) >> cfg->pipe_phy_status.bitstart;
+
+ return val;
+}
+
+static int rockchip_combphy_init(struct phy *phy)
+{
+ struct rockchip_combphy_priv *priv = phy_get_drvdata(phy);
+ const struct rockchip_combphy_grfcfg *cfg = priv->cfg->grfcfg;
+ u32 val;
+ int ret;
+
+ ret = clk_bulk_prepare_enable(priv->num_clks, priv->clks);
+ if (ret) {
+ dev_err(priv->dev, "failed to enable clks\n");
+ return ret;
+ }
+
+ switch (priv->type) {
+ case PHY_TYPE_PCIE:
+ case PHY_TYPE_USB3:
+ case PHY_TYPE_SATA:
+ case PHY_TYPE_SGMII:
+ case PHY_TYPE_QSGMII:
+ if (priv->cfg->combphy_cfg)
+ ret = priv->cfg->combphy_cfg(priv);
+ break;
+ default:
+ dev_err(priv->dev, "incompatible PHY type\n");
+ ret = -EINVAL;
+ break;
+ }
+
+ if (ret) {
+ dev_err(priv->dev, "failed to init phy for phy type %x\n", priv->type);
+ goto err_clk;
+ }
+
+ ret = reset_control_deassert(priv->phy_rst);
+ if (ret)
+ goto err_clk;
+
+ if (priv->type == PHY_TYPE_USB3) {
+ ret = readx_poll_timeout_atomic(rockchip_combphy_is_ready,
+ priv, val,
+ val == cfg->pipe_phy_status.enable,
+ 10, 1000);
+ if (ret)
+ dev_warn(priv->dev, "wait phy status ready timeout\n");
+ }
+
+ return 0;
+
+err_clk:
+ clk_bulk_disable_unprepare(priv->num_clks, priv->clks);
+
+ return ret;
+}
+
+static int rockchip_combphy_exit(struct phy *phy)
+{
+ struct rockchip_combphy_priv *priv = phy_get_drvdata(phy);
+
+ clk_bulk_disable_unprepare(priv->num_clks, priv->clks);
+ reset_control_assert(priv->phy_rst);
+
+ return 0;
+}
+
+static const struct phy_ops rockchip_combphy_ops = {
+ .init = rockchip_combphy_init,
+ .exit = rockchip_combphy_exit,
+ .owner = THIS_MODULE,
+};
+
+static struct phy *rockchip_combphy_xlate(struct device *dev, const struct of_phandle_args *args)
+{
+ struct rockchip_combphy_priv *priv = dev_get_drvdata(dev);
+
+ if (args->args_count != 1) {
+ dev_err(dev, "invalid number of arguments\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (priv->type != PHY_NONE && priv->type != args->args[0])
+ dev_warn(dev, "phy type select %d overwriting type %d\n",
+ args->args[0], priv->type);
+
+ priv->type = args->args[0];
+
+ return priv->phy;
+}
+
+static int rockchip_combphy_parse_dt(struct device *dev, struct rockchip_combphy_priv *priv)
+{
+ int i;
+
+ priv->num_clks = devm_clk_bulk_get_all(dev, &priv->clks);
+ if (priv->num_clks < 1)
+ return -EINVAL;
+
+ priv->refclk = NULL;
+ for (i = 0; i < priv->num_clks; i++) {
+ if (!strncmp(priv->clks[i].id, "ref", 3)) {
+ priv->refclk = priv->clks[i].clk;
+ break;
+ }
+ }
+
+ if (!priv->refclk) {
+ dev_err(dev, "no refclk found\n");
+ return -EINVAL;
+ }
+
+ priv->pipe_grf = syscon_regmap_lookup_by_phandle(dev->of_node, "rockchip,pipe-grf");
+ if (IS_ERR(priv->pipe_grf)) {
+ dev_err(dev, "failed to find peri_ctrl pipe-grf regmap\n");
+ return PTR_ERR(priv->pipe_grf);
+ }
+
+ priv->phy_grf = syscon_regmap_lookup_by_phandle(dev->of_node, "rockchip,pipe-phy-grf");
+ if (IS_ERR(priv->phy_grf)) {
+ dev_err(dev, "failed to find peri_ctrl pipe-phy-grf regmap\n");
+ return PTR_ERR(priv->phy_grf);
+ }
+
+ priv->enable_ssc = device_property_present(dev, "rockchip,enable-ssc");
+
+ priv->ext_refclk = device_property_present(dev, "rockchip,ext-refclk");
+
+ priv->phy_rst = devm_reset_control_get_exclusive(dev, "phy");
+ /* fallback to old behaviour */
+ if (PTR_ERR(priv->phy_rst) == -ENOENT)
+ priv->phy_rst = devm_reset_control_array_get_exclusive(dev);
+ if (IS_ERR(priv->phy_rst))
+ return dev_err_probe(dev, PTR_ERR(priv->phy_rst), "failed to get phy reset\n");
+
+ return 0;
+}
+
+static int rockchip_combphy_probe(struct platform_device *pdev)
+{
+ struct phy_provider *phy_provider;
+ struct device *dev = &pdev->dev;
+ struct rockchip_combphy_priv *priv;
+ const struct rockchip_combphy_cfg *phy_cfg;
+ struct resource *res;
+ int ret, id;
+
+ phy_cfg = of_device_get_match_data(dev);
+ if (!phy_cfg) {
+ dev_err(dev, "no OF match data provided\n");
+ return -EINVAL;
+ }
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->mmio = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(priv->mmio)) {
+ ret = PTR_ERR(priv->mmio);
+ return ret;
+ }
+
+ /* find the phy-id from the io address */
+ priv->id = -ENODEV;
+ for (id = 0; id < phy_cfg->num_phys; id++) {
+ if (res->start == phy_cfg->phy_ids[id]) {
+ priv->id = id;
+ break;
+ }
+ }
+
+ priv->dev = dev;
+ priv->type = PHY_NONE;
+ priv->cfg = phy_cfg;
+
+ ret = rockchip_combphy_parse_dt(dev, priv);
+ if (ret)
+ return ret;
+
+ ret = reset_control_assert(priv->phy_rst);
+ if (ret) {
+ dev_err(dev, "failed to reset phy\n");
+ return ret;
+ }
+
+ priv->phy = devm_phy_create(dev, NULL, &rockchip_combphy_ops);
+ if (IS_ERR(priv->phy)) {
+ dev_err(dev, "failed to create combphy\n");
+ return PTR_ERR(priv->phy);
+ }
+
+ dev_set_drvdata(dev, priv);
+ phy_set_drvdata(priv->phy, priv);
+
+ phy_provider = devm_of_phy_provider_register(dev, rockchip_combphy_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static void rk_combphy_common_cfg_ssc(struct rockchip_combphy_priv *priv, unsigned long rate)
+{
+ struct device_node *np = priv->dev->of_node;
+ u32 val;
+
+ /* Set SSC downward spread spectrum for PCIe and USB3 */
+ if (priv->type == PHY_TYPE_PCIE || priv->type == PHY_TYPE_USB3) {
+ val = FIELD_PREP(RK3568_PHYREG32_SSC_MASK, RK3568_PHYREG32_SSC_DOWNWARD);
+ rockchip_combphy_updatel(priv, RK3568_PHYREG32_SSC_MASK, val, RK3568_PHYREG32);
+ }
+
+ /* Set SSC downward spread spectrum +500ppm for SATA in 100MHz */
+ if (priv->type == PHY_TYPE_SATA && rate == REF_CLOCK_100MHz) {
+ val = FIELD_PREP(RK3568_PHYREG32_SSC_DIR_MASK,
+ RK3568_PHYREG32_SSC_DOWNWARD);
+ val |= FIELD_PREP(RK3568_PHYREG32_SSC_OFFSET_MASK,
+ RK3568_PHYREG32_SSC_OFFSET_500PPM);
+ rockchip_combphy_updatel(priv, RK3568_PHYREG32_SSC_MASK, val,
+ RK3568_PHYREG32);
+ }
+
+ if (!priv->enable_ssc)
+ return;
+
+ /* Enable SSC */
+ val = readl(priv->mmio + RK3568_PHYREG8);
+ val |= RK3568_PHYREG8_SSC_EN;
+ writel(val, priv->mmio + RK3568_PHYREG8);
+
+ /* Some SoCs need tuning PCIe SSC instead of default configuration in 24MHz */
+ if (!of_device_is_compatible(np, "rockchip,rk3588-naneng-combphy") &&
+ !of_device_is_compatible(np, "rockchip,rk3576-naneng-combphy"))
+ return;
+
+ /* PLL control SSC module period should be set if need tuning */
+ val = readl(priv->mmio + RK3568_PHYREG33);
+ val |= RK3568_PHYREG33_PLL_SSC_CTRL;
+ writel(val, priv->mmio + RK3568_PHYREG33);
+
+ if (priv->type == PHY_TYPE_PCIE && rate == REF_CLOCK_24MHz) {
+ /* Set PLL loop divider */
+ writel(0x00, priv->mmio + RK3576_PHYREG17);
+ writel(RK3568_PHYREG18_PLL_LOOP, priv->mmio + RK3568_PHYREG18);
+
+ /* Set up rx_pck invert and rx msb to disable */
+ writel(0x00, priv->mmio + RK3588_PHYREG27);
+
+ /*
+ * Set up SU adjust signal:
+ * su_trim[7:0], PLL KVCO adjust bits[2:0] to min
+ * su_trim[15:8], PLL LPF R1 adujst bits[9:7]=3'b101
+ * su_trim[23:16], CKRCV adjust
+ * su_trim[31:24], CKDRV adjust
+ */
+ writel(0x90, priv->mmio + RK3568_PHYREG11);
+ writel(0x02, priv->mmio + RK3568_PHYREG12);
+ writel(0x08, priv->mmio + RK3568_PHYREG13);
+ writel(0x57, priv->mmio + RK3568_PHYREG14);
+ writel(0x40, priv->mmio + RK3568_PHYREG15);
+
+ writel(RK3568_PHYREG16_SSC_CNT_VALUE, priv->mmio + RK3568_PHYREG16);
+
+ val = FIELD_PREP(RK3568_PHYREG33_PLL_KVCO_MASK,
+ RK3576_PHYREG33_PLL_KVCO_VALUE);
+ writel(val, priv->mmio + RK3568_PHYREG33);
+ }
+}
+
+static int rk3528_combphy_cfg(struct rockchip_combphy_priv *priv)
+{
+ const struct rockchip_combphy_grfcfg *cfg = priv->cfg->grfcfg;
+ unsigned long rate;
+ u32 val;
+
+ /* Set SSC downward spread spectrum */
+ val = FIELD_PREP(RK3528_PHYREG6_SSC_DIR, RK3528_PHYREG6_SSC_DOWNWARD);
+ rockchip_combphy_updatel(priv, RK3528_PHYREG6_SSC_DIR, val, RK3528_PHYREG6);
+
+ switch (priv->type) {
+ case PHY_TYPE_PCIE:
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->con0_for_pcie, true);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->con1_for_pcie, true);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->con2_for_pcie, true);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->con3_for_pcie, true);
+ break;
+ case PHY_TYPE_USB3:
+ /* Enable adaptive CTLE for USB3.0 Rx */
+ rockchip_combphy_updatel(priv, RK3528_PHYREG80_CTLE_EN, RK3528_PHYREG80_CTLE_EN,
+ RK3528_PHYREG80);
+
+ /* Set slow slew rate control for PI */
+ val = FIELD_PREP(RK3528_PHYREG81_SLEW_RATE_CTRL,
+ RK3528_PHYREG81_SLEW_RATE_CTRL_SLOW);
+ rockchip_combphy_updatel(priv, RK3528_PHYREG81_SLEW_RATE_CTRL, val,
+ RK3528_PHYREG81);
+
+ /* Set CDR phase path with 2x gain */
+ rockchip_combphy_updatel(priv, RK3528_PHYREG81_CDR_PHASE_PATH_GAIN_2X,
+ RK3528_PHYREG81_CDR_PHASE_PATH_GAIN_2X, RK3528_PHYREG81);
+
+ /* Set Rx squelch input filler bandwidth */
+ val = FIELD_PREP(RK3528_PHYREG83_RX_SQUELCH, RK3528_PHYREG83_RX_SQUELCH_VALUE);
+ rockchip_combphy_updatel(priv, RK3528_PHYREG83_RX_SQUELCH, val, RK3528_PHYREG83);
+
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_txcomp_sel, false);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_txelec_sel, false);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->usb_mode_set, true);
+ rockchip_combphy_param_write(priv->pipe_grf, &cfg->u3otg0_port_en, true);
+ break;
+ default:
+ dev_err(priv->dev, "incompatible PHY type\n");
+ return -EINVAL;
+ }
+
+ rate = clk_get_rate(priv->refclk);
+
+ switch (rate) {
+ case REF_CLOCK_24MHz:
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_clk_24m, true);
+ if (priv->type == PHY_TYPE_USB3) {
+ /* Set ssc_cnt[10:0]=00101111101 & 31.5KHz */
+ val = FIELD_PREP(RK3528_PHYREG40_SSC_CNT, RK3528_PHYREG40_SSC_CNT_VALUE);
+ rockchip_combphy_updatel(priv, RK3528_PHYREG40_SSC_CNT, val,
+ RK3528_PHYREG40);
+ } else if (priv->type == PHY_TYPE_PCIE) {
+ /* tx_trim[14]=1, Enable the counting clock of the rterm detect */
+ rockchip_combphy_updatel(priv, RK3528_PHYREG86_RTERM_DET_CLK_EN,
+ RK3528_PHYREG86_RTERM_DET_CLK_EN, RK3528_PHYREG86);
+ }
+ break;
+ case REF_CLOCK_100MHz:
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_clk_100m, true);
+ if (priv->type == PHY_TYPE_PCIE) {
+ /* Gate_tx_pck_sel length select for L1ss support */
+ rockchip_combphy_updatel(priv, RK3528_PHYREG5_GATE_TX_PCK_SEL,
+ RK3528_PHYREG5_GATE_TX_PCK_DLY_PLL_OFF, RK3528_PHYREG5);
+
+ /* PLL KVCO tuning fine */
+ val = FIELD_PREP(RK3528_PHYREG6_PLL_KVCO, RK3528_PHYREG6_PLL_KVCO_VALUE);
+ rockchip_combphy_updatel(priv, RK3528_PHYREG6_PLL_KVCO, val,
+ RK3528_PHYREG6);
+
+ /* su_trim[6:4]=111, [10:7]=1001, [2:0]=000, swing 650mv */
+ writel(0x570804f0, priv->mmio + RK3528_PHYREG42);
+ }
+ break;
+ default:
+ dev_err(priv->dev, "Unsupported rate: %lu\n", rate);
+ return -EINVAL;
+ }
+
+ if (priv->ext_refclk) {
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_clk_ext, true);
+
+ if (priv->type == PHY_TYPE_PCIE && rate == REF_CLOCK_100MHz) {
+ val = FIELD_PREP(RK3528_PHYREG42_CKDRV_CLK_SEL,
+ RK3528_PHYREG42_CKDRV_CLK_CKRCV);
+ val |= FIELD_PREP(RK3528_PHYREG42_PLL_LPF_R1_ADJ,
+ RK3528_PHYREG42_PLL_LPF_R1_ADJ_VALUE);
+ val |= FIELD_PREP(RK3528_PHYREG42_PLL_CHGPUMP_CUR_ADJ,
+ RK3528_PHYREG42_PLL_CHGPUMP_CUR_ADJ_VALUE);
+ val |= FIELD_PREP(RK3528_PHYREG42_PLL_KVCO_ADJ,
+ RK3528_PHYREG42_PLL_KVCO_ADJ_VALUE);
+ rockchip_combphy_updatel(priv,
+ RK3528_PHYREG42_CKDRV_CLK_SEL |
+ RK3528_PHYREG42_PLL_LPF_R1_ADJ |
+ RK3528_PHYREG42_PLL_CHGPUMP_CUR_ADJ |
+ RK3528_PHYREG42_PLL_KVCO_ADJ,
+ val, RK3528_PHYREG42);
+
+ val = FIELD_PREP(RK3528_PHYREG6_PLL_KVCO, RK3528_PHYREG6_PLL_KVCO_VALUE);
+ rockchip_combphy_updatel(priv, RK3528_PHYREG6_PLL_KVCO, val,
+ RK3528_PHYREG6);
+ }
+ }
+
+ if (priv->type == PHY_TYPE_PCIE && priv->enable_ssc)
+ rockchip_combphy_updatel(priv, RK3528_PHYREG40_SSC_EN,
+ RK3528_PHYREG40_SSC_EN, RK3528_PHYREG40);
+
+ return 0;
+}
+
+static const struct rockchip_combphy_grfcfg rk3528_combphy_grfcfgs = {
+ /* pipe-phy-grf */
+ .pcie_mode_set = { 0x0000, 5, 0, 0x00, 0x11 },
+ .usb_mode_set = { 0x0000, 5, 0, 0x00, 0x04 },
+ .pipe_rxterm_set = { 0x0000, 12, 12, 0x00, 0x01 },
+ .pipe_txelec_set = { 0x0004, 1, 1, 0x00, 0x01 },
+ .pipe_txcomp_set = { 0x0004, 4, 4, 0x00, 0x01 },
+ .pipe_clk_24m = { 0x0004, 14, 13, 0x00, 0x00 },
+ .pipe_clk_100m = { 0x0004, 14, 13, 0x00, 0x02 },
+ .pipe_rxterm_sel = { 0x0008, 8, 8, 0x00, 0x01 },
+ .pipe_txelec_sel = { 0x0008, 12, 12, 0x00, 0x01 },
+ .pipe_txcomp_sel = { 0x0008, 15, 15, 0x00, 0x01 },
+ .pipe_clk_ext = { 0x000c, 9, 8, 0x02, 0x01 },
+ .pipe_phy_status = { 0x0034, 6, 6, 0x01, 0x00 },
+ .con0_for_pcie = { 0x0000, 15, 0, 0x00, 0x110 },
+ .con1_for_pcie = { 0x0004, 15, 0, 0x00, 0x00 },
+ .con2_for_pcie = { 0x0008, 15, 0, 0x00, 0x101 },
+ .con3_for_pcie = { 0x000c, 15, 0, 0x00, 0x0200 },
+ /* pipe-grf */
+ .u3otg0_port_en = { 0x0044, 15, 0, 0x0181, 0x1100 },
+};
+
+static const struct rockchip_combphy_cfg rk3528_combphy_cfgs = {
+ .num_phys = 1,
+ .phy_ids = {
+ 0xffdc0000,
+ },
+ .grfcfg = &rk3528_combphy_grfcfgs,
+ .combphy_cfg = rk3528_combphy_cfg,
+};
+
+static int rk3562_combphy_cfg(struct rockchip_combphy_priv *priv)
+{
+ const struct rockchip_combphy_grfcfg *cfg = priv->cfg->grfcfg;
+ unsigned long rate;
+ u32 val;
+
+ switch (priv->type) {
+ case PHY_TYPE_PCIE:
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->con0_for_pcie, true);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->con1_for_pcie, true);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->con2_for_pcie, true);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->con3_for_pcie, true);
+ break;
+ case PHY_TYPE_USB3:
+ /* Enable adaptive CTLE for USB3.0 Rx */
+ rockchip_combphy_updatel(priv, RK3568_PHYREG15_CTLE_EN,
+ RK3568_PHYREG15_CTLE_EN, RK3568_PHYREG15);
+
+ /* Set PLL KVCO fine tuning signals */
+ rockchip_combphy_updatel(priv, RK3568_PHYREG33_PLL_KVCO_MASK,
+ BIT(3), RK3568_PHYREG33);
+
+ /* Set PLL LPF R1 to su_trim[10:7]=1001 */
+ writel(RK3568_PHYREG12_PLL_LPF_ADJ_VALUE, priv->mmio + RK3568_PHYREG12);
+
+ /* Set PLL input clock divider 1/2 */
+ val = FIELD_PREP(RK3568_PHYREG6_PLL_DIV_MASK, RK3568_PHYREG6_PLL_DIV_2);
+ rockchip_combphy_updatel(priv, RK3568_PHYREG6_PLL_DIV_MASK, val, RK3568_PHYREG6);
+
+ /* Set PLL loop divider */
+ writel(RK3568_PHYREG18_PLL_LOOP, priv->mmio + RK3568_PHYREG18);
+
+ /* Set PLL KVCO to min and set PLL charge pump current to max */
+ writel(RK3568_PHYREG11_SU_TRIM_0_7, priv->mmio + RK3568_PHYREG11);
+
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_sel_usb, true);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_txcomp_sel, false);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_txelec_sel, false);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->usb_mode_set, true);
+ break;
+ default:
+ dev_err(priv->dev, "incompatible PHY type\n");
+ return -EINVAL;
+ }
+
+ rate = clk_get_rate(priv->refclk);
+
+ switch (rate) {
+ case REF_CLOCK_24MHz:
+ if (priv->type == PHY_TYPE_USB3) {
+ /* Set ssc_cnt[9:0]=0101111101 & 31.5KHz */
+ val = FIELD_PREP(RK3568_PHYREG15_SSC_CNT_MASK,
+ RK3568_PHYREG15_SSC_CNT_VALUE);
+ rockchip_combphy_updatel(priv, RK3568_PHYREG15_SSC_CNT_MASK,
+ val, RK3568_PHYREG15);
+
+ writel(RK3568_PHYREG16_SSC_CNT_VALUE, priv->mmio + RK3568_PHYREG16);
+ }
+ break;
+ case REF_CLOCK_25MHz:
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_clk_25m, true);
+ break;
+ case REF_CLOCK_100MHz:
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_clk_100m, true);
+ if (priv->type == PHY_TYPE_PCIE) {
+ /* Gate_tx_pck_sel length select for L1ss support */
+ rockchip_combphy_updatel(priv, RK3568_PHYREG30_GATE_TX_PCK_SEL,
+ RK3568_PHYREG30_GATE_TX_PCK_DLY_PLL_OFF,
+ RK3568_PHYREG30);
+ /* PLL KVCO tuning fine */
+ val = FIELD_PREP(RK3568_PHYREG33_PLL_KVCO_MASK,
+ RK3568_PHYREG33_PLL_KVCO_VALUE);
+ rockchip_combphy_updatel(priv, RK3568_PHYREG33_PLL_KVCO_MASK,
+ val, RK3568_PHYREG33);
+
+ /* Enable controlling random jitter, aka RMJ */
+ writel(0x4, priv->mmio + RK3568_PHYREG12);
+
+ val = RK3568_PHYREG6_PLL_DIV_2 << RK3568_PHYREG6_PLL_DIV_SHIFT;
+ rockchip_combphy_updatel(priv, RK3568_PHYREG6_PLL_DIV_MASK,
+ val, RK3568_PHYREG6);
+
+ writel(0x32, priv->mmio + RK3568_PHYREG18);
+ writel(0xf0, priv->mmio + RK3568_PHYREG11);
+ }
+ break;
+ default:
+ dev_err(priv->dev, "Unsupported rate: %lu\n", rate);
+ return -EINVAL;
+ }
+
+ if (priv->ext_refclk) {
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_clk_ext, true);
+ if (priv->type == PHY_TYPE_PCIE && rate == REF_CLOCK_100MHz) {
+ val = RK3568_PHYREG13_RESISTER_HIGH_Z << RK3568_PHYREG13_RESISTER_SHIFT;
+ val |= RK3568_PHYREG13_CKRCV_AMP0;
+ rockchip_combphy_updatel(priv, RK3568_PHYREG13_RESISTER_MASK, val,
+ RK3568_PHYREG13);
+
+ val = readl(priv->mmio + RK3568_PHYREG14);
+ val |= RK3568_PHYREG14_CKRCV_AMP1;
+ writel(val, priv->mmio + RK3568_PHYREG14);
+ }
+ }
+
+ rk_combphy_common_cfg_ssc(priv, rate);
+
+ return 0;
+}
+
+static const struct rockchip_combphy_grfcfg rk3562_combphy_grfcfgs = {
+ /* pipe-phy-grf */
+ .pcie_mode_set = { 0x0000, 5, 0, 0x00, 0x11 },
+ .usb_mode_set = { 0x0000, 5, 0, 0x00, 0x04 },
+ .pipe_rxterm_set = { 0x0000, 12, 12, 0x00, 0x01 },
+ .pipe_txelec_set = { 0x0004, 1, 1, 0x00, 0x01 },
+ .pipe_txcomp_set = { 0x0004, 4, 4, 0x00, 0x01 },
+ .pipe_clk_25m = { 0x0004, 14, 13, 0x00, 0x01 },
+ .pipe_clk_100m = { 0x0004, 14, 13, 0x00, 0x02 },
+ .pipe_phymode_sel = { 0x0008, 1, 1, 0x00, 0x01 },
+ .pipe_rate_sel = { 0x0008, 2, 2, 0x00, 0x01 },
+ .pipe_rxterm_sel = { 0x0008, 8, 8, 0x00, 0x01 },
+ .pipe_txelec_sel = { 0x0008, 12, 12, 0x00, 0x01 },
+ .pipe_txcomp_sel = { 0x0008, 15, 15, 0x00, 0x01 },
+ .pipe_clk_ext = { 0x000c, 9, 8, 0x02, 0x01 },
+ .pipe_sel_usb = { 0x000c, 14, 13, 0x00, 0x01 },
+ .pipe_phy_status = { 0x0034, 6, 6, 0x01, 0x00 },
+ .con0_for_pcie = { 0x0000, 15, 0, 0x00, 0x1000 },
+ .con1_for_pcie = { 0x0004, 15, 0, 0x00, 0x0000 },
+ .con2_for_pcie = { 0x0008, 15, 0, 0x00, 0x0101 },
+ .con3_for_pcie = { 0x000c, 15, 0, 0x00, 0x0200 },
+};
+
+static const struct rockchip_combphy_cfg rk3562_combphy_cfgs = {
+ .num_phys = 1,
+ .phy_ids = {
+ 0xff750000
+ },
+ .grfcfg = &rk3562_combphy_grfcfgs,
+ .combphy_cfg = rk3562_combphy_cfg,
+};
+
+static int rk3568_combphy_cfg(struct rockchip_combphy_priv *priv)
+{
+ const struct rockchip_combphy_grfcfg *cfg = priv->cfg->grfcfg;
+ unsigned long rate;
+ u32 val;
+
+ switch (priv->type) {
+ case PHY_TYPE_PCIE:
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->con0_for_pcie, true);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->con1_for_pcie, true);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->con2_for_pcie, true);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->con3_for_pcie, true);
+ break;
+
+ case PHY_TYPE_USB3:
+ /* Enable adaptive CTLE for USB3.0 Rx. */
+ val = readl(priv->mmio + RK3568_PHYREG15);
+ val |= RK3568_PHYREG15_CTLE_EN;
+ writel(val, priv->mmio + RK3568_PHYREG15);
+
+ /* Set PLL KVCO fine tuning signals. */
+ val = RK3568_PHYREG33_PLL_KVCO_VALUE << RK3568_PHYREG33_PLL_KVCO_SHIFT;
+ rockchip_combphy_updatel(priv, RK3568_PHYREG33_PLL_KVCO_MASK, val, RK3568_PHYREG33);
+
+ /* Enable controlling random jitter. */
+ writel(RK3568_PHYREG12_PLL_LPF_ADJ_VALUE, priv->mmio + RK3568_PHYREG12);
+
+ /* Set PLL input clock divider 1/2. */
+ rockchip_combphy_updatel(priv, RK3568_PHYREG6_PLL_DIV_MASK,
+ RK3568_PHYREG6_PLL_DIV_2 << RK3568_PHYREG6_PLL_DIV_SHIFT,
+ RK3568_PHYREG6);
+
+ writel(RK3568_PHYREG18_PLL_LOOP, priv->mmio + RK3568_PHYREG18);
+ writel(RK3568_PHYREG11_SU_TRIM_0_7, priv->mmio + RK3568_PHYREG11);
+
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_sel_usb, true);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_txcomp_sel, false);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_txelec_sel, false);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->usb_mode_set, true);
+ switch (priv->id) {
+ case 0:
+ rockchip_combphy_param_write(priv->pipe_grf, &cfg->u3otg0_port_en, true);
+ break;
+ case 1:
+ rockchip_combphy_param_write(priv->pipe_grf, &cfg->u3otg1_port_en, true);
+ break;
+ }
+ break;
+
+ case PHY_TYPE_SATA:
+ /* Enable adaptive CTLE for SATA Rx. */
+ val = readl(priv->mmio + RK3568_PHYREG15);
+ val |= RK3568_PHYREG15_CTLE_EN;
+ writel(val, priv->mmio + RK3568_PHYREG15);
+ /*
+ * Set tx_rterm=50ohm and rx_rterm=44ohm for SATA.
+ * 0: 60ohm, 8: 50ohm 15: 44ohm (by step abort 1ohm)
+ */
+ val = RK3568_PHYREG7_TX_RTERM_50OHM << RK3568_PHYREG7_TX_RTERM_SHIFT;
+ val |= RK3568_PHYREG7_RX_RTERM_44OHM << RK3568_PHYREG7_RX_RTERM_SHIFT;
+ writel(val, priv->mmio + RK3568_PHYREG7);
+
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->con0_for_sata, true);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->con1_for_sata, true);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->con2_for_sata, true);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->con3_for_sata, true);
+ rockchip_combphy_param_write(priv->pipe_grf, &cfg->pipe_con0_for_sata, true);
+ break;
+
+ case PHY_TYPE_SGMII:
+ rockchip_combphy_param_write(priv->pipe_grf, &cfg->pipe_xpcs_phy_ready, true);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_phymode_sel, true);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_sel_qsgmii, true);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->sgmii_mode_set, true);
+ break;
+
+ case PHY_TYPE_QSGMII:
+ rockchip_combphy_param_write(priv->pipe_grf, &cfg->pipe_xpcs_phy_ready, true);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_phymode_sel, true);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_rate_sel, true);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_sel_qsgmii, true);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->qsgmii_mode_set, true);
+ break;
+
+ default:
+ dev_err(priv->dev, "incompatible PHY type\n");
+ return -EINVAL;
+ }
+
+ rate = clk_get_rate(priv->refclk);
+
+ switch (rate) {
+ case REF_CLOCK_24MHz:
+ if (priv->type == PHY_TYPE_USB3 || priv->type == PHY_TYPE_SATA) {
+ /* Set ssc_cnt[9:0]=0101111101 & 31.5KHz. */
+ val = RK3568_PHYREG15_SSC_CNT_VALUE << RK3568_PHYREG15_SSC_CNT_SHIFT;
+ rockchip_combphy_updatel(priv, RK3568_PHYREG15_SSC_CNT_MASK,
+ val, RK3568_PHYREG15);
+
+ writel(RK3568_PHYREG16_SSC_CNT_VALUE, priv->mmio + RK3568_PHYREG16);
+ }
+ break;
+
+ case REF_CLOCK_25MHz:
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_clk_25m, true);
+ break;
+
+ case REF_CLOCK_100MHz:
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_clk_100m, true);
+ if (priv->type == PHY_TYPE_PCIE) {
+ /* PLL KVCO fine tuning. */
+ val = RK3568_PHYREG33_PLL_KVCO_VALUE << RK3568_PHYREG33_PLL_KVCO_SHIFT;
+ rockchip_combphy_updatel(priv, RK3568_PHYREG33_PLL_KVCO_MASK,
+ val, RK3568_PHYREG33);
+
+ /* Enable controlling random jitter. */
+ writel(RK3568_PHYREG12_PLL_LPF_ADJ_VALUE, priv->mmio + RK3568_PHYREG12);
+
+ val = RK3568_PHYREG6_PLL_DIV_2 << RK3568_PHYREG6_PLL_DIV_SHIFT;
+ rockchip_combphy_updatel(priv, RK3568_PHYREG6_PLL_DIV_MASK,
+ val, RK3568_PHYREG6);
+
+ writel(RK3568_PHYREG18_PLL_LOOP, priv->mmio + RK3568_PHYREG18);
+ writel(RK3568_PHYREG11_SU_TRIM_0_7, priv->mmio + RK3568_PHYREG11);
+ }
+ break;
+
+ default:
+ dev_err(priv->dev, "unsupported rate: %lu\n", rate);
+ return -EINVAL;
+ }
+
+ if (priv->ext_refclk) {
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_clk_ext, true);
+ if (priv->type == PHY_TYPE_PCIE && rate == REF_CLOCK_100MHz) {
+ val = RK3568_PHYREG13_RESISTER_HIGH_Z << RK3568_PHYREG13_RESISTER_SHIFT;
+ val |= RK3568_PHYREG13_CKRCV_AMP0;
+ rockchip_combphy_updatel(priv, RK3568_PHYREG13_RESISTER_MASK, val,
+ RK3568_PHYREG13);
+
+ val = readl(priv->mmio + RK3568_PHYREG14);
+ val |= RK3568_PHYREG14_CKRCV_AMP1;
+ writel(val, priv->mmio + RK3568_PHYREG14);
+ }
+ }
+
+ rk_combphy_common_cfg_ssc(priv, rate);
+
+ return 0;
+}
+
+static const struct rockchip_combphy_grfcfg rk3568_combphy_grfcfgs = {
+ /* pipe-phy-grf */
+ .pcie_mode_set = { 0x0000, 5, 0, 0x00, 0x11 },
+ .usb_mode_set = { 0x0000, 5, 0, 0x00, 0x04 },
+ .sgmii_mode_set = { 0x0000, 5, 0, 0x00, 0x01 },
+ .qsgmii_mode_set = { 0x0000, 5, 0, 0x00, 0x21 },
+ .pipe_rxterm_set = { 0x0000, 12, 12, 0x00, 0x01 },
+ .pipe_txelec_set = { 0x0004, 1, 1, 0x00, 0x01 },
+ .pipe_txcomp_set = { 0x0004, 4, 4, 0x00, 0x01 },
+ .pipe_clk_25m = { 0x0004, 14, 13, 0x00, 0x01 },
+ .pipe_clk_100m = { 0x0004, 14, 13, 0x00, 0x02 },
+ .pipe_phymode_sel = { 0x0008, 1, 1, 0x00, 0x01 },
+ .pipe_rate_sel = { 0x0008, 2, 2, 0x00, 0x01 },
+ .pipe_rxterm_sel = { 0x0008, 8, 8, 0x00, 0x01 },
+ .pipe_txelec_sel = { 0x0008, 12, 12, 0x00, 0x01 },
+ .pipe_txcomp_sel = { 0x0008, 15, 15, 0x00, 0x01 },
+ .pipe_clk_ext = { 0x000c, 9, 8, 0x02, 0x01 },
+ .pipe_sel_usb = { 0x000c, 14, 13, 0x00, 0x01 },
+ .pipe_sel_qsgmii = { 0x000c, 15, 13, 0x00, 0x07 },
+ .pipe_phy_status = { 0x0034, 6, 6, 0x01, 0x00 },
+ .con0_for_pcie = { 0x0000, 15, 0, 0x00, 0x1000 },
+ .con1_for_pcie = { 0x0004, 15, 0, 0x00, 0x0000 },
+ .con2_for_pcie = { 0x0008, 15, 0, 0x00, 0x0101 },
+ .con3_for_pcie = { 0x000c, 15, 0, 0x00, 0x0200 },
+ .con0_for_sata = { 0x0000, 15, 0, 0x00, 0x0119 },
+ .con1_for_sata = { 0x0004, 15, 0, 0x00, 0x0040 },
+ .con2_for_sata = { 0x0008, 15, 0, 0x00, 0x80c3 },
+ .con3_for_sata = { 0x000c, 15, 0, 0x00, 0x4407 },
+ /* pipe-grf */
+ .pipe_con0_for_sata = { 0x0000, 15, 0, 0x00, 0x2220 },
+ .pipe_xpcs_phy_ready = { 0x0040, 2, 2, 0x00, 0x01 },
+ .u3otg0_port_en = { 0x0104, 15, 0, 0x0181, 0x1100 },
+ .u3otg1_port_en = { 0x0144, 15, 0, 0x0181, 0x1100 },
+};
+
+static const struct rockchip_combphy_cfg rk3568_combphy_cfgs = {
+ .num_phys = 3,
+ .phy_ids = {
+ 0xfe820000,
+ 0xfe830000,
+ 0xfe840000,
+ },
+ .grfcfg = &rk3568_combphy_grfcfgs,
+ .combphy_cfg = rk3568_combphy_cfg,
+};
+
+static int rk3576_combphy_cfg(struct rockchip_combphy_priv *priv)
+{
+ const struct rockchip_combphy_grfcfg *cfg = priv->cfg->grfcfg;
+ unsigned long rate;
+ u32 val;
+
+ switch (priv->type) {
+ case PHY_TYPE_PCIE:
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->con0_for_pcie, true);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->con1_for_pcie, true);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->con2_for_pcie, true);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->con3_for_pcie, true);
+ break;
+
+ case PHY_TYPE_USB3:
+ /* Enable adaptive CTLE for USB3.0 Rx */
+ val = readl(priv->mmio + RK3568_PHYREG15);
+ val |= RK3568_PHYREG15_CTLE_EN;
+ writel(val, priv->mmio + RK3568_PHYREG15);
+
+ /* Set PLL KVCO fine tuning signals */
+ rockchip_combphy_updatel(priv, RK3568_PHYREG33_PLL_KVCO_MASK, BIT(3),
+ RK3568_PHYREG33);
+
+ /* Set PLL LPF R1 to su_trim[10:7]=1001 */
+ writel(RK3568_PHYREG12_PLL_LPF_ADJ_VALUE, priv->mmio + RK3568_PHYREG12);
+
+ /* Set PLL input clock divider 1/2 */
+ val = FIELD_PREP(RK3568_PHYREG6_PLL_DIV_MASK, RK3568_PHYREG6_PLL_DIV_2);
+ rockchip_combphy_updatel(priv, RK3568_PHYREG6_PLL_DIV_MASK, val, RK3568_PHYREG6);
+
+ /* Set PLL loop divider */
+ writel(RK3568_PHYREG18_PLL_LOOP, priv->mmio + RK3568_PHYREG18);
+
+ /* Set PLL KVCO to min and set PLL charge pump current to max */
+ writel(RK3568_PHYREG11_SU_TRIM_0_7, priv->mmio + RK3568_PHYREG11);
+
+ /* Set Rx squelch input filler bandwidth */
+ writel(RK3576_PHYREG21_RX_SQUELCH_VAL, priv->mmio + RK3576_PHYREG21);
+
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_txcomp_sel, false);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_txelec_sel, false);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->usb_mode_set, true);
+ break;
+
+ case PHY_TYPE_SATA:
+ /* Enable adaptive CTLE for SATA Rx */
+ val = readl(priv->mmio + RK3568_PHYREG15);
+ val |= RK3568_PHYREG15_CTLE_EN;
+ writel(val, priv->mmio + RK3568_PHYREG15);
+
+ /* Set tx_rterm = 50 ohm and rx_rterm = 43.5 ohm */
+ val = RK3568_PHYREG7_TX_RTERM_50OHM << RK3568_PHYREG7_TX_RTERM_SHIFT;
+ val |= RK3568_PHYREG7_RX_RTERM_44OHM << RK3568_PHYREG7_RX_RTERM_SHIFT;
+ writel(val, priv->mmio + RK3568_PHYREG7);
+
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->con0_for_sata, true);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->con1_for_sata, true);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->con2_for_sata, true);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->con3_for_sata, true);
+ rockchip_combphy_param_write(priv->pipe_grf, &cfg->pipe_con0_for_sata, true);
+ rockchip_combphy_param_write(priv->pipe_grf, &cfg->pipe_con1_for_sata, true);
+ break;
+
+ default:
+ dev_err(priv->dev, "incompatible PHY type\n");
+ return -EINVAL;
+ }
+
+ rate = clk_get_rate(priv->refclk);
+
+ switch (rate) {
+ case REF_CLOCK_24MHz:
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_clk_24m, true);
+ if (priv->type == PHY_TYPE_USB3 || priv->type == PHY_TYPE_SATA) {
+ /* Set ssc_cnt[9:0]=0101111101 & 31.5KHz */
+ val = FIELD_PREP(RK3568_PHYREG15_SSC_CNT_MASK,
+ RK3568_PHYREG15_SSC_CNT_VALUE);
+ rockchip_combphy_updatel(priv, RK3568_PHYREG15_SSC_CNT_MASK,
+ val, RK3568_PHYREG15);
+
+ writel(RK3568_PHYREG16_SSC_CNT_VALUE, priv->mmio + RK3568_PHYREG16);
+ } else if (priv->type == PHY_TYPE_PCIE) {
+ /* PLL KVCO tuning fine */
+ val = FIELD_PREP(RK3568_PHYREG33_PLL_KVCO_MASK,
+ RK3576_PHYREG33_PLL_KVCO_VALUE);
+ rockchip_combphy_updatel(priv, RK3568_PHYREG33_PLL_KVCO_MASK,
+ val, RK3568_PHYREG33);
+
+ /* Set up rx_pck invert and rx msb to disable */
+ writel(0x00, priv->mmio + RK3588_PHYREG27);
+
+ /*
+ * Set up SU adjust signal:
+ * su_trim[7:0], PLL KVCO adjust bits[2:0] to min
+ * su_trim[15:8], PLL LPF R1 adujst bits[9:7]=3'b011
+ * su_trim[31:24], CKDRV adjust
+ */
+ writel(0x90, priv->mmio + RK3568_PHYREG11);
+ writel(0x02, priv->mmio + RK3568_PHYREG12);
+ writel(0x57, priv->mmio + RK3568_PHYREG14);
+
+ writel(RK3568_PHYREG16_SSC_CNT_VALUE, priv->mmio + RK3568_PHYREG16);
+ }
+ break;
+
+ case REF_CLOCK_25MHz:
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_clk_25m, true);
+ break;
+
+ case REF_CLOCK_100MHz:
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_clk_100m, true);
+ if (priv->type == PHY_TYPE_PCIE) {
+ /* gate_tx_pck_sel length select work for L1SS */
+ writel(0xc0, priv->mmio + RK3576_PHYREG30);
+
+ /* PLL KVCO tuning fine */
+ val = FIELD_PREP(RK3568_PHYREG33_PLL_KVCO_MASK,
+ RK3576_PHYREG33_PLL_KVCO_VALUE);
+ rockchip_combphy_updatel(priv, RK3568_PHYREG33_PLL_KVCO_MASK,
+ val, RK3568_PHYREG33);
+
+ /* Set up rx_trim: PLL LPF C1 85pf R1 1.25kohm */
+ writel(0x4c, priv->mmio + RK3588_PHYREG27);
+
+ /*
+ * Set up SU adjust signal:
+ * su_trim[7:0], PLL KVCO adjust bits[2:0] to min
+ * su_trim[15:8], bypass PLL loop divider code, and
+ * PLL LPF R1 adujst bits[9:7]=3'b101
+ * su_trim[23:16], CKRCV adjust
+ * su_trim[31:24], CKDRV adjust
+ */
+ writel(0x90, priv->mmio + RK3568_PHYREG11);
+ writel(0x43, priv->mmio + RK3568_PHYREG12);
+ writel(0x88, priv->mmio + RK3568_PHYREG13);
+ writel(0x56, priv->mmio + RK3568_PHYREG14);
+ } else if (priv->type == PHY_TYPE_SATA) {
+ /* ssc ppm adjust to 3500ppm */
+ rockchip_combphy_updatel(priv, RK3576_PHYREG10_SSC_PCM_MASK,
+ RK3576_PHYREG10_SSC_PCM_3500PPM,
+ RK3576_PHYREG10);
+ }
+ break;
+
+ default:
+ dev_err(priv->dev, "Unsupported rate: %lu\n", rate);
+ return -EINVAL;
+ }
+
+ if (priv->ext_refclk) {
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_clk_ext, true);
+ if (priv->type == PHY_TYPE_PCIE && rate == REF_CLOCK_100MHz) {
+ val = FIELD_PREP(RK3568_PHYREG33_PLL_KVCO_MASK,
+ RK3576_PHYREG33_PLL_KVCO_VALUE);
+ rockchip_combphy_updatel(priv, RK3568_PHYREG33_PLL_KVCO_MASK,
+ val, RK3568_PHYREG33);
+
+ /* Set up rx_trim: PLL LPF C1 85pf R1 2.5kohm */
+ writel(0x0c, priv->mmio + RK3588_PHYREG27);
+
+ /*
+ * Set up SU adjust signal:
+ * su_trim[7:0], PLL KVCO adjust bits[2:0] to min
+ * su_trim[15:8], bypass PLL loop divider code, and
+ * PLL LPF R1 adujst bits[9:7]=3'b101.
+ * su_trim[23:16], CKRCV adjust
+ * su_trim[31:24], CKDRV adjust
+ */
+ writel(0x90, priv->mmio + RK3568_PHYREG11);
+ writel(0x43, priv->mmio + RK3568_PHYREG12);
+ writel(0x88, priv->mmio + RK3568_PHYREG13);
+ writel(0x56, priv->mmio + RK3568_PHYREG14);
+ }
+ }
+
+ rk_combphy_common_cfg_ssc(priv, rate);
+
+ return 0;
+}
+
+static const struct rockchip_combphy_grfcfg rk3576_combphy_grfcfgs = {
+ /* pipe-phy-grf */
+ .pcie_mode_set = { 0x0000, 5, 0, 0x00, 0x11 },
+ .usb_mode_set = { 0x0000, 5, 0, 0x00, 0x04 },
+ .pipe_rxterm_set = { 0x0000, 12, 12, 0x00, 0x01 },
+ .pipe_txelec_set = { 0x0004, 1, 1, 0x00, 0x01 },
+ .pipe_txcomp_set = { 0x0004, 4, 4, 0x00, 0x01 },
+ .pipe_clk_24m = { 0x0004, 14, 13, 0x00, 0x00 },
+ .pipe_clk_25m = { 0x0004, 14, 13, 0x00, 0x01 },
+ .pipe_clk_100m = { 0x0004, 14, 13, 0x00, 0x02 },
+ .pipe_phymode_sel = { 0x0008, 1, 1, 0x00, 0x01 },
+ .pipe_rate_sel = { 0x0008, 2, 2, 0x00, 0x01 },
+ .pipe_rxterm_sel = { 0x0008, 8, 8, 0x00, 0x01 },
+ .pipe_txelec_sel = { 0x0008, 12, 12, 0x00, 0x01 },
+ .pipe_txcomp_sel = { 0x0008, 15, 15, 0x00, 0x01 },
+ .pipe_clk_ext = { 0x000c, 9, 8, 0x02, 0x01 },
+ .pipe_phy_status = { 0x0034, 6, 6, 0x01, 0x00 },
+ .con0_for_pcie = { 0x0000, 15, 0, 0x00, 0x1000 },
+ .con1_for_pcie = { 0x0004, 15, 0, 0x00, 0x0000 },
+ .con2_for_pcie = { 0x0008, 15, 0, 0x00, 0x0101 },
+ .con3_for_pcie = { 0x000c, 15, 0, 0x00, 0x0200 },
+ .con0_for_sata = { 0x0000, 15, 0, 0x00, 0x0129 },
+ .con1_for_sata = { 0x0004, 15, 0, 0x00, 0x0000 },
+ .con2_for_sata = { 0x0008, 15, 0, 0x00, 0x80c1 },
+ .con3_for_sata = { 0x000c, 15, 0, 0x00, 0x0407 },
+ /* php-grf */
+ .pipe_con0_for_sata = { 0x001C, 2, 0, 0x00, 0x2 },
+ .pipe_con1_for_sata = { 0x0020, 2, 0, 0x00, 0x2 },
+};
+
+static const struct rockchip_combphy_cfg rk3576_combphy_cfgs = {
+ .num_phys = 2,
+ .phy_ids = {
+ 0x2b050000,
+ 0x2b060000
+ },
+ .grfcfg = &rk3576_combphy_grfcfgs,
+ .combphy_cfg = rk3576_combphy_cfg,
+};
+
+static int rk3588_combphy_cfg(struct rockchip_combphy_priv *priv)
+{
+ const struct rockchip_combphy_grfcfg *cfg = priv->cfg->grfcfg;
+ unsigned long rate;
+ u32 val;
+
+ switch (priv->type) {
+ case PHY_TYPE_PCIE:
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->con0_for_pcie, true);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->con1_for_pcie, true);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->con2_for_pcie, true);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->con3_for_pcie, true);
+ switch (priv->id) {
+ case 1:
+ rockchip_combphy_param_write(priv->pipe_grf, &cfg->pipe_pcie1l0_sel, true);
+ break;
+ case 2:
+ rockchip_combphy_param_write(priv->pipe_grf, &cfg->pipe_pcie1l1_sel, true);
+ break;
+ }
+ break;
+ case PHY_TYPE_USB3:
+ /* Enable adaptive CTLE for USB3.0 Rx. */
+ val = readl(priv->mmio + RK3568_PHYREG15);
+ val |= RK3568_PHYREG15_CTLE_EN;
+ writel(val, priv->mmio + RK3568_PHYREG15);
+
+ /* Set PLL KVCO fine tuning signals. */
+ val = RK3568_PHYREG33_PLL_KVCO_VALUE << RK3568_PHYREG33_PLL_KVCO_SHIFT;
+ rockchip_combphy_updatel(priv, RK3568_PHYREG33_PLL_KVCO_MASK, val, RK3568_PHYREG33);
+
+ /* Enable controlling random jitter. */
+ writel(RK3568_PHYREG12_PLL_LPF_ADJ_VALUE, priv->mmio + RK3568_PHYREG12);
+
+ /* Set PLL input clock divider 1/2. */
+ rockchip_combphy_updatel(priv, RK3568_PHYREG6_PLL_DIV_MASK,
+ RK3568_PHYREG6_PLL_DIV_2 << RK3568_PHYREG6_PLL_DIV_SHIFT,
+ RK3568_PHYREG6);
+
+ writel(RK3568_PHYREG18_PLL_LOOP, priv->mmio + RK3568_PHYREG18);
+ writel(RK3568_PHYREG11_SU_TRIM_0_7, priv->mmio + RK3568_PHYREG11);
+
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_txcomp_sel, false);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_txelec_sel, false);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->usb_mode_set, true);
+ break;
+ case PHY_TYPE_SATA:
+ /* Enable adaptive CTLE for SATA Rx. */
+ val = readl(priv->mmio + RK3568_PHYREG15);
+ val |= RK3568_PHYREG15_CTLE_EN;
+ writel(val, priv->mmio + RK3568_PHYREG15);
+ /*
+ * Set tx_rterm=50ohm and rx_rterm=44ohm for SATA.
+ * 0: 60ohm, 8: 50ohm 15: 44ohm (by step abort 1ohm)
+ */
+ val = RK3568_PHYREG7_TX_RTERM_50OHM << RK3568_PHYREG7_TX_RTERM_SHIFT;
+ val |= RK3568_PHYREG7_RX_RTERM_44OHM << RK3568_PHYREG7_RX_RTERM_SHIFT;
+ writel(val, priv->mmio + RK3568_PHYREG7);
+
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->con0_for_sata, true);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->con1_for_sata, true);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->con2_for_sata, true);
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->con3_for_sata, true);
+ rockchip_combphy_param_write(priv->pipe_grf, &cfg->pipe_con0_for_sata, true);
+ rockchip_combphy_param_write(priv->pipe_grf, &cfg->pipe_con1_for_sata, true);
+ break;
+ case PHY_TYPE_SGMII:
+ case PHY_TYPE_QSGMII:
+ default:
+ dev_err(priv->dev, "incompatible PHY type\n");
+ return -EINVAL;
+ }
+
+ rate = clk_get_rate(priv->refclk);
+
+ switch (rate) {
+ case REF_CLOCK_24MHz:
+ if (priv->type == PHY_TYPE_USB3 || priv->type == PHY_TYPE_SATA) {
+ /* Set ssc_cnt[9:0]=0101111101 & 31.5KHz. */
+ val = RK3568_PHYREG15_SSC_CNT_VALUE << RK3568_PHYREG15_SSC_CNT_SHIFT;
+ rockchip_combphy_updatel(priv, RK3568_PHYREG15_SSC_CNT_MASK,
+ val, RK3568_PHYREG15);
+
+ writel(RK3568_PHYREG16_SSC_CNT_VALUE, priv->mmio + RK3568_PHYREG16);
+ }
+ break;
+
+ case REF_CLOCK_25MHz:
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_clk_25m, true);
+ break;
+ case REF_CLOCK_100MHz:
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_clk_100m, true);
+ if (priv->type == PHY_TYPE_PCIE) {
+ /* PLL KVCO fine tuning. */
+ val = 4 << RK3568_PHYREG33_PLL_KVCO_SHIFT;
+ rockchip_combphy_updatel(priv, RK3568_PHYREG33_PLL_KVCO_MASK,
+ val, RK3568_PHYREG33);
+
+ /* Enable controlling random jitter. */
+ writel(RK3568_PHYREG12_PLL_LPF_ADJ_VALUE, priv->mmio + RK3568_PHYREG12);
+
+ /* Set up rx_trim: PLL LPF C1 85pf R1 1.25kohm */
+ writel(RK3588_PHYREG27_RX_TRIM, priv->mmio + RK3588_PHYREG27);
+
+ /* Set up su_trim: */
+ writel(RK3568_PHYREG11_SU_TRIM_0_7, priv->mmio + RK3568_PHYREG11);
+ }
+ break;
+ default:
+ dev_err(priv->dev, "Unsupported rate: %lu\n", rate);
+ return -EINVAL;
+ }
+
+ if (priv->ext_refclk) {
+ rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_clk_ext, true);
+ if (priv->type == PHY_TYPE_PCIE && rate == REF_CLOCK_100MHz) {
+ val = RK3568_PHYREG13_RESISTER_HIGH_Z << RK3568_PHYREG13_RESISTER_SHIFT;
+ val |= RK3568_PHYREG13_CKRCV_AMP0;
+ rockchip_combphy_updatel(priv, RK3568_PHYREG13_RESISTER_MASK, val,
+ RK3568_PHYREG13);
+
+ val = readl(priv->mmio + RK3568_PHYREG14);
+ val |= RK3568_PHYREG14_CKRCV_AMP1;
+ writel(val, priv->mmio + RK3568_PHYREG14);
+ }
+ }
+
+ rk_combphy_common_cfg_ssc(priv, rate);
+
+ return 0;
+}
+
+static const struct rockchip_combphy_grfcfg rk3588_combphy_grfcfgs = {
+ /* pipe-phy-grf */
+ .pcie_mode_set = { 0x0000, 5, 0, 0x00, 0x11 },
+ .usb_mode_set = { 0x0000, 5, 0, 0x00, 0x04 },
+ .pipe_rxterm_set = { 0x0000, 12, 12, 0x00, 0x01 },
+ .pipe_txelec_set = { 0x0004, 1, 1, 0x00, 0x01 },
+ .pipe_txcomp_set = { 0x0004, 4, 4, 0x00, 0x01 },
+ .pipe_clk_25m = { 0x0004, 14, 13, 0x00, 0x01 },
+ .pipe_clk_100m = { 0x0004, 14, 13, 0x00, 0x02 },
+ .pipe_rxterm_sel = { 0x0008, 8, 8, 0x00, 0x01 },
+ .pipe_txelec_sel = { 0x0008, 12, 12, 0x00, 0x01 },
+ .pipe_txcomp_sel = { 0x0008, 15, 15, 0x00, 0x01 },
+ .pipe_clk_ext = { 0x000c, 9, 8, 0x02, 0x01 },
+ .pipe_phy_status = { 0x0034, 6, 6, 0x01, 0x00 },
+ .con0_for_pcie = { 0x0000, 15, 0, 0x00, 0x1000 },
+ .con1_for_pcie = { 0x0004, 15, 0, 0x00, 0x0000 },
+ .con2_for_pcie = { 0x0008, 15, 0, 0x00, 0x0101 },
+ .con3_for_pcie = { 0x000c, 15, 0, 0x00, 0x0200 },
+ .con0_for_sata = { 0x0000, 15, 0, 0x00, 0x0129 },
+ .con1_for_sata = { 0x0004, 15, 0, 0x00, 0x0000 },
+ .con2_for_sata = { 0x0008, 15, 0, 0x00, 0x80c1 },
+ .con3_for_sata = { 0x000c, 15, 0, 0x00, 0x0407 },
+ /* pipe-grf */
+ .pipe_con0_for_sata = { 0x0000, 11, 5, 0x00, 0x22 },
+ .pipe_con1_for_sata = { 0x0000, 2, 0, 0x00, 0x2 },
+ .pipe_pcie1l0_sel = { 0x0100, 0, 0, 0x01, 0x0 },
+ .pipe_pcie1l1_sel = { 0x0100, 1, 1, 0x01, 0x0 },
+};
+
+static const struct rockchip_combphy_cfg rk3588_combphy_cfgs = {
+ .num_phys = 3,
+ .phy_ids = {
+ 0xfee00000,
+ 0xfee10000,
+ 0xfee20000,
+ },
+ .grfcfg = &rk3588_combphy_grfcfgs,
+ .combphy_cfg = rk3588_combphy_cfg,
+};
+
+static const struct of_device_id rockchip_combphy_of_match[] = {
+ {
+ .compatible = "rockchip,rk3528-naneng-combphy",
+ .data = &rk3528_combphy_cfgs,
+ },
+ {
+ .compatible = "rockchip,rk3562-naneng-combphy",
+ .data = &rk3562_combphy_cfgs,
+ },
+ {
+ .compatible = "rockchip,rk3568-naneng-combphy",
+ .data = &rk3568_combphy_cfgs,
+ },
+ {
+ .compatible = "rockchip,rk3576-naneng-combphy",
+ .data = &rk3576_combphy_cfgs,
+ },
+ {
+ .compatible = "rockchip,rk3588-naneng-combphy",
+ .data = &rk3588_combphy_cfgs,
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(of, rockchip_combphy_of_match);
+
+static struct platform_driver rockchip_combphy_driver = {
+ .probe = rockchip_combphy_probe,
+ .driver = {
+ .name = "rockchip-naneng-combphy",
+ .of_match_table = rockchip_combphy_of_match,
+ },
+};
+module_platform_driver(rockchip_combphy_driver);
+
+MODULE_DESCRIPTION("Rockchip NANENG COMBPHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/rockchip/phy-rockchip-pcie.c b/drivers/phy/rockchip/phy-rockchip-pcie.c
new file mode 100644
index 000000000..126306c01
--- /dev/null
+++ b/drivers/phy/rockchip/phy-rockchip-pcie.c
@@ -0,0 +1,338 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Rockchip PCIe PHY driver
+ *
+ * Copyright (C) 2016 Shawn Lin <shawn.lin@rock-chips.com>
+ * Copyright (C) 2016 ROCKCHIP, Inc.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/hw_bitfield.h>
+#include <linux/io.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+
+
+#define PHY_MAX_LANE_NUM 4
+#define PHY_CFG_DATA_MASK GENMASK(10, 7)
+#define PHY_CFG_ADDR_MASK GENMASK(6, 1)
+#define PHY_CFG_WR_ENABLE 1
+#define PHY_CFG_WR_DISABLE 0
+#define PHY_CFG_WR_MASK BIT(0)
+#define PHY_CFG_PLL_LOCK 0x10
+#define PHY_CFG_CLK_TEST 0x10
+#define PHY_CFG_CLK_SCC 0x12
+#define PHY_CFG_SEPE_RATE BIT(3)
+#define PHY_CFG_PLL_100M BIT(3)
+#define PHY_PLL_LOCKED BIT(9)
+#define PHY_PLL_OUTPUT BIT(10)
+#define PHY_LANE_A_STATUS 0x30
+#define PHY_LANE_B_STATUS 0x31
+#define PHY_LANE_C_STATUS 0x32
+#define PHY_LANE_D_STATUS 0x33
+#define PHY_LANE_RX_DET_SHIFT 11
+#define PHY_LANE_RX_DET_TH 0x1
+#define PHY_LANE_IDLE_OFF 0x1
+#define PHY_LANE_IDLE_MASK BIT(3)
+
+struct rockchip_pcie_data {
+ unsigned int pcie_conf;
+ unsigned int pcie_status;
+ unsigned int pcie_laneoff;
+};
+
+struct rockchip_pcie_phy {
+ const struct rockchip_pcie_data *phy_data;
+ struct regmap *reg_base;
+ struct phy_pcie_instance {
+ struct phy *phy;
+ u32 index;
+ } phys[PHY_MAX_LANE_NUM];
+ struct mutex pcie_mutex;
+ struct reset_control *phy_rst;
+ struct clk *clk_pciephy_ref;
+ int pwr_cnt;
+ int init_cnt;
+};
+
+static struct rockchip_pcie_phy *to_pcie_phy(struct phy_pcie_instance *inst)
+{
+ return container_of(inst, struct rockchip_pcie_phy,
+ phys[inst->index]);
+}
+
+static struct phy *rockchip_pcie_phy_of_xlate(struct device *dev,
+ const struct of_phandle_args *args)
+{
+ struct rockchip_pcie_phy *rk_phy = dev_get_drvdata(dev);
+
+ if (args->args_count == 0)
+ return rk_phy->phys[0].phy;
+
+ if (WARN_ON(args->args[0] >= PHY_MAX_LANE_NUM))
+ return ERR_PTR(-ENODEV);
+
+ return rk_phy->phys[args->args[0]].phy;
+}
+
+
+static inline void phy_wr_cfg(struct rockchip_pcie_phy *rk_phy,
+ u32 addr, u32 data)
+{
+ regmap_write(rk_phy->reg_base, rk_phy->phy_data->pcie_conf,
+ FIELD_PREP_WM16(PHY_CFG_DATA_MASK, data) |
+ FIELD_PREP_WM16(PHY_CFG_ADDR_MASK, addr));
+ udelay(1);
+ regmap_write(rk_phy->reg_base, rk_phy->phy_data->pcie_conf,
+ FIELD_PREP_WM16(PHY_CFG_WR_MASK, PHY_CFG_WR_ENABLE));
+ udelay(1);
+ regmap_write(rk_phy->reg_base, rk_phy->phy_data->pcie_conf,
+ FIELD_PREP_WM16(PHY_CFG_WR_MASK, PHY_CFG_WR_DISABLE));
+}
+
+static int rockchip_pcie_phy_power_off(struct phy *phy)
+{
+ struct phy_pcie_instance *inst = phy_get_drvdata(phy);
+ struct rockchip_pcie_phy *rk_phy = to_pcie_phy(inst);
+ int err = 0;
+
+ guard(mutex)(&rk_phy->pcie_mutex);
+
+ regmap_write(rk_phy->reg_base, rk_phy->phy_data->pcie_laneoff,
+ FIELD_PREP_WM16(PHY_LANE_IDLE_MASK,
+ PHY_LANE_IDLE_OFF) << inst->index);
+
+ if (--rk_phy->pwr_cnt) {
+ return 0;
+ }
+
+ err = reset_control_assert(rk_phy->phy_rst);
+ if (err) {
+ dev_err(&phy->dev, "assert phy_rst err %d\n", err);
+ rk_phy->pwr_cnt++;
+ regmap_write(rk_phy->reg_base, rk_phy->phy_data->pcie_laneoff,
+ FIELD_PREP_WM16(PHY_LANE_IDLE_MASK,
+ !PHY_LANE_IDLE_OFF) << inst->index);
+ return err;
+ }
+
+ return err;
+}
+
+static int rockchip_pcie_phy_power_on(struct phy *phy)
+{
+ struct phy_pcie_instance *inst = phy_get_drvdata(phy);
+ struct rockchip_pcie_phy *rk_phy = to_pcie_phy(inst);
+ int err = 0;
+ u32 status;
+
+ guard(mutex)(&rk_phy->pcie_mutex);
+
+ regmap_write(rk_phy->reg_base, rk_phy->phy_data->pcie_laneoff,
+ FIELD_PREP_WM16(PHY_LANE_IDLE_MASK,
+ !PHY_LANE_IDLE_OFF) << inst->index);
+
+ if (rk_phy->pwr_cnt++) {
+ return 0;
+ }
+
+ err = reset_control_deassert(rk_phy->phy_rst);
+ if (err) {
+ dev_err(&phy->dev, "deassert phy_rst err %d\n", err);
+ rk_phy->pwr_cnt--;
+ return err;
+ }
+
+ regmap_write(rk_phy->reg_base, rk_phy->phy_data->pcie_conf,
+ FIELD_PREP_WM16(PHY_CFG_ADDR_MASK, PHY_CFG_PLL_LOCK));
+
+ /*
+ * No documented timeout value for phy operation below,
+ * so we make it large enough here. And we use loop-break
+ * method which should not be harmful.
+ */
+ err = regmap_read_poll_timeout(rk_phy->reg_base,
+ rk_phy->phy_data->pcie_status,
+ status,
+ status & PHY_PLL_LOCKED,
+ 200, 100000);
+ if (err) {
+ dev_err(&phy->dev, "pll lock timeout!\n");
+ goto err_pll_lock;
+ }
+
+ phy_wr_cfg(rk_phy, PHY_CFG_CLK_TEST, PHY_CFG_SEPE_RATE);
+ phy_wr_cfg(rk_phy, PHY_CFG_CLK_SCC, PHY_CFG_PLL_100M);
+
+ err = regmap_read_poll_timeout(rk_phy->reg_base,
+ rk_phy->phy_data->pcie_status,
+ status,
+ !(status & PHY_PLL_OUTPUT),
+ 200, 100000);
+ if (err) {
+ dev_err(&phy->dev, "pll output enable timeout!\n");
+ goto err_pll_lock;
+ }
+
+ regmap_write(rk_phy->reg_base, rk_phy->phy_data->pcie_conf,
+ FIELD_PREP_WM16(PHY_CFG_ADDR_MASK, PHY_CFG_PLL_LOCK));
+
+ err = regmap_read_poll_timeout(rk_phy->reg_base,
+ rk_phy->phy_data->pcie_status,
+ status,
+ status & PHY_PLL_LOCKED,
+ 200, 100000);
+ if (err) {
+ dev_err(&phy->dev, "pll relock timeout!\n");
+ goto err_pll_lock;
+ }
+
+ return err;
+
+err_pll_lock:
+ reset_control_assert(rk_phy->phy_rst);
+ rk_phy->pwr_cnt--;
+ return err;
+}
+
+static int rockchip_pcie_phy_init(struct phy *phy)
+{
+ struct phy_pcie_instance *inst = phy_get_drvdata(phy);
+ struct rockchip_pcie_phy *rk_phy = to_pcie_phy(inst);
+ int err = 0;
+
+ guard(mutex)(&rk_phy->pcie_mutex);
+
+ if (rk_phy->init_cnt++) {
+ return 0;
+ }
+
+ err = reset_control_assert(rk_phy->phy_rst);
+ if (err) {
+ dev_err(&phy->dev, "assert phy_rst err %d\n", err);
+ rk_phy->init_cnt--;
+ return err;
+ }
+
+ return err;
+}
+
+static int rockchip_pcie_phy_exit(struct phy *phy)
+{
+ struct phy_pcie_instance *inst = phy_get_drvdata(phy);
+ struct rockchip_pcie_phy *rk_phy = to_pcie_phy(inst);
+
+ guard(mutex)(&rk_phy->pcie_mutex);
+
+ if (--rk_phy->init_cnt)
+ goto err_init_cnt;
+
+err_init_cnt:
+ return 0;
+}
+
+static const struct phy_ops ops = {
+ .init = rockchip_pcie_phy_init,
+ .exit = rockchip_pcie_phy_exit,
+ .power_on = rockchip_pcie_phy_power_on,
+ .power_off = rockchip_pcie_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static const struct rockchip_pcie_data rk3399_pcie_data = {
+ .pcie_conf = 0xe220,
+ .pcie_status = 0xe2a4,
+ .pcie_laneoff = 0xe214,
+};
+
+static const struct of_device_id rockchip_pcie_phy_dt_ids[] = {
+ {
+ .compatible = "rockchip,rk3399-pcie-phy",
+ .data = &rk3399_pcie_data,
+ },
+ {}
+};
+
+MODULE_DEVICE_TABLE(of, rockchip_pcie_phy_dt_ids);
+
+static int rockchip_pcie_phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rockchip_pcie_phy *rk_phy;
+ struct phy_provider *phy_provider;
+ struct regmap *grf;
+ int i;
+ u32 phy_num;
+
+ grf = syscon_node_to_regmap(dev->parent->of_node);
+ if (IS_ERR(grf)) {
+ dev_err(dev, "Cannot find GRF syscon\n");
+ return PTR_ERR(grf);
+ }
+
+ rk_phy = devm_kzalloc(dev, sizeof(*rk_phy), GFP_KERNEL);
+ if (!rk_phy)
+ return -ENOMEM;
+
+ rk_phy->phy_data = device_get_match_data(&pdev->dev);
+ if (!rk_phy->phy_data)
+ return -EINVAL;
+
+ rk_phy->reg_base = grf;
+
+ mutex_init(&rk_phy->pcie_mutex);
+
+ rk_phy->phy_rst = devm_reset_control_get(dev, "phy");
+ if (IS_ERR(rk_phy->phy_rst))
+ return dev_err_probe(&pdev->dev, PTR_ERR(rk_phy->phy_rst),
+ "missing phy property for reset controller\n");
+
+ rk_phy->clk_pciephy_ref = devm_clk_get_enabled(dev, "refclk");
+ if (IS_ERR(rk_phy->clk_pciephy_ref))
+ return dev_err_probe(&pdev->dev, PTR_ERR(rk_phy->clk_pciephy_ref),
+ "failed to get phyclk\n");
+
+ /* parse #phy-cells to see if it's legacy PHY model */
+ if (of_property_read_u32(dev->of_node, "#phy-cells", &phy_num))
+ return -ENOENT;
+
+ phy_num = (phy_num == 0) ? 1 : PHY_MAX_LANE_NUM;
+ dev_dbg(dev, "phy number is %d\n", phy_num);
+
+ for (i = 0; i < phy_num; i++) {
+ rk_phy->phys[i].phy = devm_phy_create(dev, dev->of_node, &ops);
+ if (IS_ERR(rk_phy->phys[i].phy)) {
+ dev_err(dev, "failed to create PHY%d\n", i);
+ return PTR_ERR(rk_phy->phys[i].phy);
+ }
+ rk_phy->phys[i].index = i;
+ phy_set_drvdata(rk_phy->phys[i].phy, &rk_phy->phys[i]);
+ }
+
+ platform_set_drvdata(pdev, rk_phy);
+ phy_provider = devm_of_phy_provider_register(dev,
+ rockchip_pcie_phy_of_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static struct platform_driver rockchip_pcie_driver = {
+ .probe = rockchip_pcie_phy_probe,
+ .driver = {
+ .name = "rockchip-pcie-phy",
+ .of_match_table = rockchip_pcie_phy_dt_ids,
+ },
+};
+
+module_platform_driver(rockchip_pcie_driver);
+
+MODULE_AUTHOR("Shawn Lin <shawn.lin@rock-chips.com>");
+MODULE_DESCRIPTION("Rockchip PCIe PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-dcphy.c b/drivers/phy/rockchip/phy-rockchip-samsung-dcphy.c
new file mode 100644
index 000000000..18f5f582d
--- /dev/null
+++ b/drivers/phy/rockchip/phy-rockchip-samsung-dcphy.c
@@ -0,0 +1,1715 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2025 Rockchip Electronics Co.Ltd
+ * Author:
+ * Guochun Huang <hero.huang@rock-chips.com>
+ */
+
+#include <dt-bindings/phy/phy.h>
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/hw_bitfield.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+
+#define BIAS_CON0 0x0000
+#define I_RES_CNTL_MASK GENMASK(6, 4)
+#define I_RES_CNTL(x) FIELD_PREP(I_RES_CNTL_MASK, x)
+#define I_RES_059_2UA I_RES_CNTL(0)
+#define I_RES_100_2UA I_RES_CNTL(1)
+#define I_RES_094_2UA I_RES_CNTL(2)
+#define I_RES_113_8UA I_RES_CNTL(3)
+#define I_RES_089_7UA I_RES_CNTL(4)
+#define I_RES_111_8UA I_RES_CNTL(5)
+#define I_RES_108_2UA I_RES_CNTL(6)
+#define I_RES_120_8UA I_RES_CNTL(7)
+#define I_DEV_SEL_MASK GENMASK(1, 0)
+#define I_DEV_SEL(x) FIELD_PREP(I_DEV_SEL_MASK, x)
+#define I_DEV_DIV_6 I_DEV_SEL(0)
+#define I_DEV_DIV_12 I_DEV_SEL(1)
+#define I_DEV_DIV_20 I_DEV_SEL(2)
+#define I_DEV_DIV_40 I_DEV_SEL(3)
+
+#define BIAS_CON1 0x0004
+#define I_VBG_SEL_MASK GENMASK(9, 8)
+#define I_VBG_SEL(x) FIELD_PREP(I_VBG_SEL_MASK, x)
+#define I_VBG_SEL_780MV I_VBG_SEL(0)
+#define I_VBG_SEL_820MV I_VBG_SEL(1)
+#define I_VBG_SEL_860MV I_VBG_SEL(2)
+#define I_VBG_SEL_900MV I_VBG_SEL(3)
+#define I_BGR_VREF_SEL_MASK GENMASK(5, 4)
+#define I_BGR_VREF_SEL(x) FIELD_PREP(I_BGR_VREF_SEL_MASK, x)
+#define I_BGR_VREF_810MV I_BGR_VREF_SEL(0)
+#define I_BGR_VREF_820MV I_BGR_VREF_SEL(1)
+#define I_BGR_VREF_830MV I_BGR_VREF_SEL(2)
+#define I_BGR_VREF_840MV I_BGR_VREF_SEL(3)
+#define I_LADDER_SEL_MASK GENMASK(2, 0)
+#define I_LADDER_SEL(x) FIELD_PREP(I_LADDER_SEL_MASK, x)
+#define I_LADDER_1_00V I_LADDER_SEL(0)
+#define I_LADDER_0_96V I_LADDER_SEL(1)
+#define I_LADDER_0_92V I_LADDER_SEL(2)
+#define I_LADDER_0_88V I_LADDER_SEL(3)
+#define I_LADDER_0_84V I_LADDER_SEL(4)
+#define I_LADDER_0_80V I_LADDER_SEL(5)
+#define I_LADDER_0_76V I_LADDER_SEL(6)
+#define I_LADDER_0_72V I_LADDER_SEL(7)
+
+/*
+ * Voltage corrections around reference voltages
+ * The selection between the 400-based or 200-based values for REG_400M
+ * is done by the hw depending on I_MUX below being 400MV or 200MV.
+ */
+#define BIAS_CON2 0x0008
+#define REG_325M_MASK GENMASK(14, 12)
+#define REG_325M(x) FIELD_PREP(REG_325M_MASK, x)
+#define REG_325M_295MV REG_325M(0)
+#define REG_325M_305MV REG_325M(1)
+#define REG_325M_315MV REG_325M(2)
+#define REG_325M_325MV REG_325M(3)
+#define REG_325M_335MV REG_325M(4)
+#define REG_325M_345MV REG_325M(5)
+#define REG_325M_355MV REG_325M(6)
+#define REG_325M_365MV REG_325M(7)
+#define REG_LP_400M_MASK GENMASK(10, 8)
+#define REG_LP_400M(x) FIELD_PREP(REG_LP_400M_MASK, x)
+#define REG_LP_400M_380MV REG_LP_400M(0)
+#define REG_LP_400M_390MV REG_LP_400M(1)
+#define REG_LP_400M_400MV REG_LP_400M(2)
+#define REG_LP_400M_410MV REG_LP_400M(3)
+#define REG_LP_400M_420MV REG_LP_400M(4)
+#define REG_LP_400M_430MV REG_LP_400M(5)
+#define REG_LP_400M_440MV REG_LP_400M(6)
+#define REG_LP_400M_450MV REG_LP_400M(7)
+#define REG_400M_MASK GENMASK(6, 4)
+#define REG_400M(x) FIELD_PREP(REG_400M_MASK, x)
+#define REG_400M_380MV REG_400M(0)
+#define REG_400M_390MV REG_400M(1)
+#define REG_400M_400MV REG_400M(2)
+#define REG_400M_410MV REG_400M(3)
+#define REG_400M_420MV REG_400M(4)
+#define REG_400M_430MV REG_400M(5)
+#define REG_400M_440MV REG_400M(6)
+#define REG_400M_450MV REG_400M(7)
+#define REG_400M_230MV REG_400M(0)
+#define REG_400M_220MV REG_400M(1)
+#define REG_400M_210MV REG_400M(2)
+#define REG_400M_200MV REG_400M(3)
+#define REG_400M_190MV REG_400M(4)
+#define REG_400M_180MV REG_400M(5)
+#define REG_400M_170MV REG_400M(6)
+#define REG_400M_160MV REG_400M(7)
+#define REG_645M_MASK GENMASK(2, 0)
+#define REG_645M(x) FIELD_PREP(REG_645M_MASK, x)
+#define REG_645M_605MV REG_645M(0)
+#define REG_645M_625MV REG_645M(1)
+#define REG_645M_635MV REG_645M(2)
+#define REG_645M_645MV REG_645M(3)
+#define REG_645M_655MV REG_645M(4)
+#define REG_645M_665MV REG_645M(5)
+#define REG_645M_685MV REG_645M(6)
+#define REG_645M_725MV REG_645M(7)
+
+#define BIAS_CON4 0x0010
+#define I_MUX_SEL_MASK GENMASK(6, 5)
+#define I_MUX_SEL(x) FIELD_PREP(I_MUX_SEL_MASK, x)
+#define I_MUX_400MV I_MUX_SEL(0)
+#define I_MUX_200MV I_MUX_SEL(1)
+#define I_MUX_530MV I_MUX_SEL(2)
+
+#define PLL_CON0 0x0100
+#define PLL_EN BIT(12)
+#define S_MASK GENMASK(10, 8)
+#define S(x) FIELD_PREP(S_MASK, x)
+#define P_MASK GENMASK(5, 0)
+#define P(x) FIELD_PREP(P_MASK, x)
+#define PLL_CON1 0x0104
+#define PLL_CON2 0x0108
+#define M_MASK GENMASK(9, 0)
+#define M(x) FIELD_PREP(M_MASK, x)
+#define PLL_CON3 0x010c
+#define MRR_MASK GENMASK(13, 8)
+#define MRR(x) FIELD_PREP(MRR_MASK, x)
+#define MFR_MASK GENMASK(7, 0)
+#define MFR(x) FIELD_PREP(MFR_MASK, x)
+#define PLL_CON4 0x0110
+#define SSCG_EN BIT(11)
+#define PLL_CON5 0x0114
+#define RESET_N_SEL BIT(10)
+#define PLL_ENABLE_SEL BIT(8)
+#define PLL_CON6 0x0118
+#define PLL_CON7 0x011c
+#define PLL_LOCK_CNT(x) FIELD_PREP(GENMASK(15, 0), x)
+#define PLL_CON8 0x0120
+#define PLL_STB_CNT(x) FIELD_PREP(GENMASK(15, 0), x)
+#define PLL_STAT0 0x0140
+#define PLL_LOCK BIT(0)
+
+#define DPHY_MC_GNR_CON0 0x0300
+#define PHY_READY BIT(1)
+#define PHY_ENABLE BIT(0)
+#define DPHY_MC_GNR_CON1 0x0304
+#define T_PHY_READY(x) FIELD_PREP(GENMASK(15, 0), x)
+#define DPHY_MC_ANA_CON0 0x0308
+#define EDGE_CON(x) FIELD_PREP(GENMASK(14, 12), x)
+#define EDGE_CON_DIR(x) FIELD_PREP(BIT(9), x)
+#define EDGE_CON_EN BIT(8)
+#define RES_UP(x) FIELD_PREP(GENMASK(7, 4), x)
+#define RES_DN(x) FIELD_PREP(GENMASK(3, 0), x)
+#define DPHY_MC_ANA_CON1 0x030c
+#define DPHY_MC_ANA_CON2 0x0310
+#define HS_VREG_AMP_ICON(x) FIELD_PREP(GENMASK(1, 0), x)
+#define DPHY_MC_TIME_CON0 0x0330
+#define HSTX_CLK_SEL BIT(12)
+#define T_LPX(x) FIELD_PREP(GENMASK(11, 4), x)
+#define DPHY_MC_TIME_CON1 0x0334
+#define T_CLK_ZERO(x) FIELD_PREP(GENMASK(15, 8), x)
+#define T_CLK_PREPARE(x) FIELD_PREP(GENMASK(7, 0), x)
+#define DPHY_MC_TIME_CON2 0x0338
+#define T_HS_EXIT(x) FIELD_PREP(GENMASK(15, 8), x)
+#define T_CLK_TRAIL(x) FIELD_PREP(GENMASK(7, 0), x)
+#define DPHY_MC_TIME_CON3 0x033c
+#define T_CLK_POST(x) FIELD_PREP(GENMASK(7, 0), x)
+#define DPHY_MC_TIME_CON4 0x0340
+#define T_ULPS_EXIT(x) FIELD_PREP(GENMASK(9, 0), x)
+#define DPHY_MC_DESKEW_CON0 0x0350
+#define SKEW_CAL_RUN_TIME(x) FIELD_PREP(GENMASK(15, 12), x)
+
+#define SKEW_CAL_INIT_RUN_TIME(x) FIELD_PREP(GENMASK(11, 8), x)
+#define SKEW_CAL_INIT_WAIT_TIME(x) FIELD_PREP(GENMASK(7, 4), x)
+#define SKEW_CAL_EN BIT(0)
+
+#define COMBO_MD0_GNR_CON0 0x0400
+#define COMBO_MD0_GNR_CON1 0x0404
+#define COMBO_MD0_ANA_CON0 0x0408
+#define COMBO_MD0_ANA_CON1 0x040c
+#define COMBO_MD0_ANA_CON2 0x0410
+
+#define COMBO_MD0_TIME_CON0 0x0430
+#define COMBO_MD0_TIME_CON1 0x0434
+#define COMBO_MD0_TIME_CON2 0x0438
+#define COMBO_MD0_TIME_CON3 0x043c
+#define COMBO_MD0_TIME_CON4 0x0440
+#define COMBO_MD0_DATA_CON0 0x0444
+
+#define COMBO_MD1_GNR_CON0 0x0500
+#define COMBO_MD1_GNR_CON1 0x0504
+#define COMBO_MD1_ANA_CON0 0x0508
+#define COMBO_MD1_ANA_CON1 0x050c
+#define COMBO_MD1_ANA_CON2 0x0510
+#define COMBO_MD1_TIME_CON0 0x0530
+#define COMBO_MD1_TIME_CON1 0x0534
+#define COMBO_MD1_TIME_CON2 0x0538
+#define COMBO_MD1_TIME_CON3 0x053c
+#define COMBO_MD1_TIME_CON4 0x0540
+#define COMBO_MD1_DATA_CON0 0x0544
+
+#define COMBO_MD2_GNR_CON0 0x0600
+#define COMBO_MD2_GNR_CON1 0x0604
+#define COMBO_MD2_ANA_CON0 0X0608
+#define COMBO_MD2_ANA_CON1 0X060c
+#define COMBO_MD2_ANA_CON2 0X0610
+#define COMBO_MD2_TIME_CON0 0x0630
+#define COMBO_MD2_TIME_CON1 0x0634
+#define COMBO_MD2_TIME_CON2 0x0638
+#define COMBO_MD2_TIME_CON3 0x063c
+#define COMBO_MD2_TIME_CON4 0x0640
+#define COMBO_MD2_DATA_CON0 0x0644
+
+#define DPHY_MD3_GNR_CON0 0x0700
+#define DPHY_MD3_GNR_CON1 0x0704
+#define DPHY_MD3_ANA_CON0 0X0708
+#define DPHY_MD3_ANA_CON1 0X070c
+#define DPHY_MD3_ANA_CON2 0X0710
+#define DPHY_MD3_TIME_CON0 0x0730
+#define DPHY_MD3_TIME_CON1 0x0734
+#define DPHY_MD3_TIME_CON2 0x0738
+#define DPHY_MD3_TIME_CON3 0x073c
+#define DPHY_MD3_TIME_CON4 0x0740
+#define DPHY_MD3_DATA_CON0 0x0744
+
+#define T_LP_EXIT_SKEW(x) FIELD_PREP(GENMASK(3, 2), x)
+#define T_LP_ENTRY_SKEW(x) FIELD_PREP(GENMASK(1, 0), x)
+#define T_HS_ZERO(x) FIELD_PREP(GENMASK(15, 8), x)
+#define T_HS_PREPARE(x) FIELD_PREP(GENMASK(7, 0), x)
+#define T_HS_EXIT(x) FIELD_PREP(GENMASK(15, 8), x)
+#define T_HS_TRAIL(x) FIELD_PREP(GENMASK(7, 0), x)
+#define T_TA_GET(x) FIELD_PREP(GENMASK(7, 4), x)
+#define T_TA_GO(x) FIELD_PREP(GENMASK(3, 0), x)
+
+/* MIPI_CDPHY_GRF registers */
+#define MIPI_DCPHY_GRF_CON0 0x0000
+#define S_CPHY_MODE FIELD_PREP_WM16(BIT(3), 1)
+#define M_CPHY_MODE FIELD_PREP_WM16(BIT(0), 1)
+
+enum hs_drv_res_ohm {
+ STRENGTH_30_OHM = 0x8,
+ STRENGTH_31_2_OHM,
+ STRENGTH_32_5_OHM,
+ STRENGTH_34_OHM,
+ STRENGTH_35_5_OHM,
+ STRENGTH_37_OHM,
+ STRENGTH_39_OHM,
+ STRENGTH_41_OHM,
+ STRENGTH_43_OHM = 0x0,
+ STRENGTH_46_OHM,
+ STRENGTH_49_OHM,
+ STRENGTH_52_OHM,
+ STRENGTH_56_OHM,
+ STRENGTH_60_OHM,
+ STRENGTH_66_OHM,
+ STRENGTH_73_OHM,
+};
+
+struct hs_drv_res_cfg {
+ enum hs_drv_res_ohm clk_hs_drv_up_ohm;
+ enum hs_drv_res_ohm clk_hs_drv_down_ohm;
+ enum hs_drv_res_ohm data_hs_drv_up_ohm;
+ enum hs_drv_res_ohm data_hs_drv_down_ohm;
+};
+
+struct samsung_mipi_dcphy_plat_data {
+ const struct hs_drv_res_cfg *dphy_hs_drv_res_cfg;
+ u32 dphy_tx_max_lane_kbps;
+};
+
+struct samsung_mipi_dcphy {
+ struct device *dev;
+ struct clk *ref_clk;
+ struct clk *pclk;
+ struct regmap *regmap;
+ struct regmap *grf_regmap;
+ struct reset_control *m_phy_rst;
+ struct reset_control *s_phy_rst;
+ struct reset_control *apb_rst;
+ struct reset_control *grf_apb_rst;
+ unsigned int lanes;
+ struct phy *phy;
+ u8 type;
+
+ const struct samsung_mipi_dcphy_plat_data *pdata;
+ struct {
+ unsigned long long rate;
+ u8 prediv;
+ u16 fbdiv;
+ long dsm;
+ u8 scaler;
+
+ bool ssc_en;
+ u8 mfr;
+ u8 mrr;
+ } pll;
+};
+
+struct samsung_mipi_dphy_timing {
+ unsigned int max_lane_mbps;
+ u8 clk_prepare;
+ u8 clk_zero;
+ u8 clk_post;
+ u8 clk_trail_eot;
+ u8 hs_prepare;
+ u8 hs_zero;
+ u8 hs_trail_eot;
+ u8 lpx;
+ u8 hs_exit;
+ u8 hs_settle;
+};
+
+/*
+ * Timing values taken from rk3588 vendor kernel.
+ * Not documented in hw documentation.
+ */
+static const
+struct samsung_mipi_dphy_timing samsung_mipi_dphy_timing_table[] = {
+ {6500, 32, 117, 31, 28, 30, 56, 27, 24, 44, 37},
+ {6490, 32, 116, 31, 28, 30, 56, 27, 24, 44, 37},
+ {6480, 32, 116, 31, 28, 30, 56, 27, 24, 44, 37},
+ {6470, 32, 116, 31, 28, 30, 56, 27, 24, 44, 37},
+ {6460, 32, 116, 31, 28, 30, 56, 27, 24, 44, 37},
+ {6450, 32, 115, 31, 28, 30, 56, 27, 24, 44, 37},
+ {6440, 32, 115, 31, 28, 30, 56, 27, 24, 44, 37},
+ {6430, 31, 116, 31, 28, 30, 55, 27, 24, 44, 37},
+ {6420, 31, 116, 31, 28, 30, 55, 27, 24, 44, 37},
+ {6410, 31, 116, 31, 27, 30, 55, 27, 24, 44, 37},
+ {6400, 31, 115, 30, 27, 30, 55, 27, 23, 43, 36},
+ {6390, 31, 115, 30, 27, 30, 55, 27, 23, 43, 36},
+ {6380, 31, 115, 30, 27, 30, 55, 27, 23, 43, 36},
+ {6370, 31, 115, 30, 27, 30, 55, 26, 23, 43, 36},
+ {6360, 31, 114, 30, 27, 30, 54, 26, 23, 43, 36},
+ {6350, 31, 114, 30, 27, 30, 54, 26, 23, 43, 36},
+ {6340, 31, 114, 30, 27, 30, 54, 26, 23, 43, 36},
+ {6330, 31, 114, 30, 27, 30, 54, 26, 23, 43, 36},
+ {6320, 31, 113, 30, 27, 30, 54, 26, 23, 43, 36},
+ {6310, 31, 113, 30, 27, 30, 54, 26, 23, 43, 36},
+ {6300, 31, 113, 30, 27, 30, 54, 26, 23, 43, 36},
+ {6290, 31, 113, 30, 27, 29, 54, 26, 23, 43, 36},
+ {6280, 31, 112, 30, 27, 29, 54, 26, 23, 43, 36},
+ {6270, 31, 112, 30, 27, 29, 54, 26, 23, 43, 36},
+ {6260, 31, 112, 30, 27, 29, 54, 26, 23, 43, 36},
+ {6250, 31, 112, 30, 27, 29, 54, 26, 23, 42, 36},
+ {6240, 30, 113, 30, 27, 29, 54, 26, 23, 42, 36},
+ {6230, 30, 112, 30, 27, 29, 54, 26, 23, 42, 35},
+ {6220, 30, 112, 30, 27, 29, 53, 26, 23, 42, 35},
+ {6210, 30, 112, 30, 27, 29, 53, 26, 23, 42, 35},
+ {6200, 30, 112, 29, 27, 29, 53, 26, 23, 42, 35},
+ {6190, 30, 111, 29, 27, 29, 53, 26, 23, 42, 35},
+ {6180, 30, 111, 29, 27, 29, 53, 26, 23, 42, 35},
+ {6170, 30, 111, 29, 26, 29, 53, 26, 23, 42, 35},
+ {6160, 30, 111, 29, 26, 29, 53, 26, 23, 42, 35},
+ {6150, 30, 110, 29, 26, 29, 53, 26, 23, 42, 35},
+ {6140, 30, 110, 29, 26, 29, 52, 26, 23, 42, 35},
+ {6130, 30, 110, 29, 26, 29, 52, 25, 22, 42, 35},
+ {6120, 30, 110, 29, 26, 29, 52, 25, 22, 42, 35},
+ {6110, 30, 110, 29, 26, 29, 52, 25, 22, 42, 35},
+ {6100, 30, 109, 29, 26, 29, 52, 25, 22, 41, 35},
+ {6090, 30, 109, 29, 26, 29, 52, 25, 22, 41, 35},
+ {6080, 30, 109, 29, 26, 28, 53, 25, 22, 41, 35},
+ {6070, 30, 109, 29, 26, 28, 52, 25, 22, 41, 34},
+ {6060, 30, 108, 29, 26, 28, 52, 25, 22, 41, 34},
+ {6050, 30, 108, 29, 26, 28, 52, 25, 22, 41, 34},
+ {6040, 29, 109, 29, 26, 28, 52, 25, 22, 41, 34},
+ {6030, 29, 109, 29, 26, 28, 52, 25, 22, 41, 34},
+ {6020, 29, 108, 29, 26, 28, 52, 25, 22, 41, 34},
+ {6010, 29, 108, 29, 26, 28, 52, 25, 22, 41, 34},
+ {6000, 29, 108, 28, 26, 28, 51, 25, 22, 41, 34},
+ {5990, 29, 108, 28, 26, 28, 51, 25, 22, 41, 34},
+ {5980, 29, 107, 28, 26, 28, 51, 25, 22, 41, 34},
+ {5970, 29, 107, 28, 26, 28, 51, 25, 22, 41, 34},
+ {5960, 29, 107, 28, 26, 28, 51, 25, 22, 40, 34},
+ {5950, 29, 107, 28, 26, 28, 51, 25, 22, 40, 34},
+ {5940, 29, 107, 28, 25, 28, 51, 25, 22, 40, 34},
+ {5930, 29, 106, 28, 25, 28, 50, 25, 22, 40, 34},
+ {5920, 29, 106, 28, 25, 28, 50, 25, 22, 40, 34},
+ {5910, 29, 106, 28, 25, 28, 50, 25, 22, 40, 34},
+ {5900, 29, 106, 28, 25, 28, 50, 24, 22, 40, 33},
+ {5890, 29, 105, 28, 25, 28, 50, 24, 22, 40, 33},
+ {5880, 29, 105, 28, 25, 28, 50, 24, 22, 40, 33},
+ {5870, 29, 105, 28, 25, 27, 51, 24, 22, 40, 33},
+ {5860, 29, 105, 28, 25, 27, 51, 24, 21, 40, 33},
+ {5850, 29, 104, 28, 25, 27, 50, 24, 21, 40, 33},
+ {5840, 28, 105, 28, 25, 27, 50, 24, 21, 40, 33},
+ {5830, 28, 105, 28, 25, 27, 50, 24, 21, 40, 33},
+ {5820, 28, 105, 28, 25, 27, 50, 24, 21, 40, 33},
+ {5810, 28, 104, 28, 25, 27, 50, 24, 21, 39, 33},
+ {5800, 28, 104, 27, 25, 27, 50, 24, 21, 39, 33},
+ {5790, 28, 104, 27, 25, 27, 50, 24, 21, 39, 33},
+ {5780, 28, 104, 27, 25, 27, 49, 24, 21, 39, 33},
+ {5770, 28, 104, 27, 25, 27, 49, 24, 21, 39, 33},
+ {5760, 28, 103, 27, 25, 27, 49, 24, 21, 39, 33},
+ {5750, 28, 103, 27, 25, 27, 49, 24, 21, 39, 33},
+ {5740, 28, 103, 27, 25, 27, 49, 24, 21, 39, 33},
+ {5730, 28, 103, 27, 25, 27, 49, 24, 21, 39, 32},
+ {5720, 28, 102, 27, 25, 27, 49, 24, 21, 39, 32},
+ {5710, 28, 102, 27, 25, 27, 48, 24, 21, 39, 32},
+ {5700, 28, 102, 27, 24, 27, 48, 24, 21, 39, 32},
+ {5690, 28, 102, 27, 24, 27, 48, 24, 21, 39, 32},
+ {5680, 28, 101, 27, 24, 27, 48, 24, 21, 39, 32},
+ {5670, 28, 101, 27, 24, 27, 48, 23, 21, 38, 32},
+ {5660, 28, 101, 27, 24, 26, 49, 23, 21, 38, 32},
+ {5650, 28, 101, 27, 24, 26, 49, 23, 21, 38, 32},
+ {5640, 27, 101, 27, 24, 26, 48, 23, 21, 38, 32},
+ {5630, 27, 101, 27, 24, 26, 48, 23, 21, 38, 32},
+ {5620, 27, 101, 27, 24, 26, 48, 23, 21, 38, 32},
+ {5610, 27, 101, 27, 24, 26, 48, 23, 21, 38, 32},
+ {5600, 27, 101, 26, 24, 26, 48, 23, 20, 38, 32},
+ {5590, 27, 100, 26, 24, 26, 48, 23, 20, 38, 32},
+ {5580, 27, 100, 26, 24, 26, 48, 23, 20, 38, 32},
+ {5570, 27, 100, 26, 24, 26, 48, 23, 20, 38, 31},
+ {5560, 27, 100, 26, 24, 26, 47, 23, 20, 38, 31},
+ {5550, 27, 99, 26, 24, 26, 47, 23, 20, 38, 31},
+ {5540, 27, 99, 26, 24, 26, 47, 23, 20, 38, 31},
+ {5530, 27, 99, 26, 24, 26, 47, 23, 20, 38, 31},
+ {5520, 27, 99, 26, 24, 26, 47, 23, 20, 37, 31},
+ {5510, 27, 98, 26, 24, 26, 47, 23, 20, 37, 31},
+ {5500, 27, 98, 26, 24, 26, 47, 23, 20, 37, 31},
+ {5490, 27, 98, 26, 24, 26, 46, 23, 20, 37, 31},
+ {5480, 27, 98, 26, 24, 26, 46, 23, 20, 37, 31},
+ {5470, 27, 97, 26, 23, 26, 46, 23, 20, 37, 31},
+ {5460, 27, 97, 26, 23, 26, 46, 23, 20, 37, 31},
+ {5450, 27, 97, 26, 23, 25, 47, 23, 20, 37, 31},
+ {5440, 26, 98, 26, 23, 25, 47, 23, 20, 37, 31},
+ {5430, 26, 98, 26, 23, 25, 47, 22, 20, 37, 31},
+ {5420, 26, 97, 26, 23, 25, 46, 22, 20, 37, 31},
+ {5410, 26, 97, 26, 23, 25, 46, 22, 20, 37, 31},
+ {5400, 26, 97, 25, 23, 25, 46, 22, 20, 37, 30},
+ {5390, 26, 97, 25, 23, 25, 46, 22, 20, 37, 30},
+ {5380, 26, 96, 25, 23, 25, 46, 22, 20, 36, 30},
+ {5370, 26, 96, 25, 23, 25, 46, 22, 20, 36, 30},
+ {5360, 26, 96, 25, 23, 25, 46, 22, 20, 36, 30},
+ {5350, 26, 96, 25, 23, 25, 46, 22, 20, 36, 30},
+ {5340, 26, 95, 25, 23, 25, 45, 22, 20, 36, 30},
+ {5330, 26, 95, 25, 23, 25, 45, 22, 19, 36, 30},
+ {5320, 26, 95, 25, 23, 25, 45, 22, 19, 36, 30},
+ {5310, 26, 95, 25, 23, 25, 45, 22, 19, 36, 30},
+ {5300, 26, 95, 25, 23, 25, 45, 22, 19, 36, 30},
+ {5290, 26, 94, 25, 23, 25, 45, 22, 19, 36, 30},
+ {5280, 26, 94, 25, 23, 25, 45, 22, 19, 36, 30},
+ {5270, 26, 94, 25, 23, 25, 44, 22, 19, 36, 30},
+ {5260, 26, 94, 25, 23, 25, 44, 22, 19, 36, 30},
+ {5250, 25, 94, 25, 23, 24, 45, 22, 19, 36, 30},
+ {5240, 25, 94, 25, 23, 24, 45, 22, 19, 36, 29},
+ {5230, 25, 94, 25, 22, 24, 45, 22, 19, 35, 29},
+ {5220, 25, 94, 25, 22, 24, 45, 22, 19, 35, 29},
+ {5210, 25, 93, 25, 22, 24, 45, 22, 19, 35, 29},
+ {5200, 25, 93, 24, 22, 24, 44, 21, 19, 35, 29},
+ {5190, 25, 93, 24, 22, 24, 44, 21, 19, 35, 29},
+ {5180, 25, 93, 24, 22, 24, 44, 21, 19, 35, 29},
+ {5170, 25, 92, 24, 22, 24, 44, 21, 19, 35, 29},
+ {5160, 25, 92, 24, 22, 24, 44, 21, 19, 35, 29},
+ {5150, 25, 92, 24, 22, 24, 44, 21, 19, 35, 29},
+ {5140, 25, 92, 24, 22, 24, 44, 21, 19, 35, 29},
+ {5130, 25, 92, 24, 22, 24, 43, 21, 19, 35, 29},
+ {5120, 25, 91, 24, 22, 24, 43, 21, 19, 35, 29},
+ {5110, 25, 91, 24, 22, 24, 43, 21, 19, 35, 29},
+ {5100, 25, 91, 24, 22, 24, 43, 21, 19, 35, 29},
+ {5090, 25, 91, 24, 22, 24, 43, 21, 19, 34, 29},
+ {5080, 25, 90, 24, 22, 24, 43, 21, 19, 34, 29},
+ {5070, 25, 90, 24, 22, 24, 43, 21, 19, 34, 28},
+ {5060, 25, 90, 24, 22, 24, 43, 21, 18, 34, 28},
+ {5050, 24, 91, 24, 22, 24, 42, 21, 18, 34, 28},
+ {5040, 24, 90, 24, 22, 23, 43, 21, 18, 34, 28},
+ {5030, 24, 90, 24, 22, 23, 43, 21, 18, 34, 28},
+ {5020, 24, 90, 24, 22, 23, 43, 21, 18, 34, 28},
+ {5010, 24, 90, 24, 22, 23, 43, 21, 18, 34, 28},
+ {5000, 24, 89, 23, 21, 23, 43, 21, 18, 34, 28},
+ {4990, 24, 89, 23, 21, 23, 43, 21, 18, 34, 28},
+ {4980, 24, 89, 23, 21, 23, 42, 21, 18, 34, 28},
+ {4970, 24, 89, 23, 21, 23, 42, 21, 18, 34, 28},
+ {4960, 24, 89, 23, 21, 23, 42, 20, 18, 34, 28},
+ {4950, 24, 88, 23, 21, 23, 42, 20, 18, 34, 28},
+ {4940, 24, 88, 23, 21, 23, 42, 20, 18, 33, 28},
+ {4930, 24, 88, 23, 21, 23, 42, 20, 18, 33, 28},
+ {4920, 24, 88, 23, 21, 23, 42, 20, 18, 33, 28},
+ {4910, 24, 87, 23, 21, 23, 41, 20, 18, 33, 28},
+ {4900, 24, 87, 23, 21, 23, 41, 20, 18, 33, 27},
+ {4890, 24, 87, 23, 21, 23, 41, 20, 18, 33, 27},
+ {4880, 24, 87, 23, 21, 23, 41, 20, 18, 33, 27},
+ {4870, 24, 86, 23, 21, 23, 41, 20, 18, 33, 27},
+ {4860, 24, 86, 23, 21, 23, 41, 20, 18, 33, 27},
+ {4850, 23, 87, 23, 21, 23, 41, 20, 18, 33, 27},
+ {4840, 23, 87, 23, 21, 23, 40, 20, 18, 33, 27},
+ {4830, 23, 86, 23, 21, 22, 41, 20, 18, 33, 27},
+ {4820, 23, 86, 23, 21, 22, 41, 20, 18, 33, 27},
+ {4810, 23, 86, 23, 21, 22, 41, 20, 18, 33, 27},
+ {4800, 23, 86, 22, 21, 22, 41, 20, 17, 32, 27},
+ {4790, 23, 86, 22, 21, 22, 41, 20, 17, 32, 27},
+ {4780, 23, 85, 22, 21, 22, 41, 20, 17, 32, 27},
+ {4770, 23, 85, 22, 21, 22, 41, 20, 17, 32, 27},
+ {4760, 23, 85, 22, 20, 22, 40, 20, 17, 32, 27},
+ {4750, 23, 85, 22, 20, 22, 40, 20, 17, 32, 27},
+ {4740, 23, 84, 22, 20, 22, 40, 20, 17, 32, 26},
+ {4730, 23, 84, 22, 20, 22, 40, 19, 17, 32, 26},
+ {4720, 23, 84, 22, 20, 22, 40, 19, 17, 32, 26},
+ {4710, 23, 84, 22, 20, 22, 40, 19, 17, 32, 26},
+ {4700, 23, 83, 22, 20, 22, 40, 19, 17, 32, 26},
+ {4690, 23, 83, 22, 20, 22, 39, 19, 17, 32, 26},
+ {4680, 23, 83, 22, 20, 22, 39, 19, 17, 32, 26},
+ {4670, 23, 83, 22, 20, 22, 39, 19, 17, 32, 26},
+ {4660, 23, 82, 22, 20, 22, 39, 19, 17, 32, 26},
+ {4650, 22, 83, 22, 20, 22, 39, 19, 17, 31, 26},
+ {4640, 22, 83, 22, 20, 22, 39, 19, 17, 31, 26},
+ {4630, 22, 83, 22, 20, 22, 39, 19, 17, 31, 26},
+ {4620, 22, 83, 22, 20, 21, 39, 19, 17, 31, 26},
+ {4610, 22, 82, 22, 20, 21, 39, 19, 17, 31, 26},
+ {4600, 22, 82, 21, 20, 21, 39, 19, 17, 31, 26},
+ {4590, 22, 82, 21, 20, 21, 39, 19, 17, 31, 26},
+ {4580, 22, 82, 21, 20, 21, 39, 19, 17, 31, 26},
+ {4570, 22, 81, 21, 20, 21, 39, 19, 17, 31, 25},
+ {4560, 22, 81, 21, 20, 21, 39, 19, 17, 31, 25},
+ {4550, 22, 81, 21, 20, 21, 38, 19, 17, 31, 25},
+ {4540, 22, 81, 21, 20, 21, 38, 19, 17, 31, 25},
+ {4530, 22, 80, 21, 19, 21, 38, 19, 16, 31, 25},
+ {4520, 22, 80, 21, 19, 21, 38, 19, 16, 31, 25},
+ {4510, 22, 80, 21, 19, 21, 38, 19, 16, 31, 25},
+ {4500, 22, 80, 21, 19, 21, 38, 19, 16, 30, 25},
+ {4490, 22, 80, 21, 19, 21, 38, 18, 16, 30, 25},
+ {4480, 22, 79, 21, 19, 21, 38, 18, 16, 30, 25},
+ {4470, 22, 79, 21, 19, 21, 37, 18, 16, 30, 25},
+ {4460, 22, 79, 21, 19, 21, 37, 18, 16, 30, 25},
+ {4450, 21, 80, 21, 19, 21, 37, 18, 16, 30, 25},
+ {4440, 21, 79, 21, 19, 21, 37, 18, 16, 30, 25},
+ {4430, 21, 79, 21, 19, 21, 37, 18, 16, 30, 25},
+ {4420, 21, 79, 21, 19, 21, 37, 18, 16, 30, 25},
+ {4410, 21, 79, 21, 19, 20, 38, 18, 16, 30, 25},
+ {4400, 21, 78, 20, 19, 20, 37, 18, 16, 30, 24},
+ {4390, 21, 78, 20, 19, 20, 37, 18, 16, 30, 24},
+ {4380, 21, 78, 20, 19, 20, 37, 18, 16, 30, 24},
+ {4370, 21, 78, 20, 19, 20, 37, 18, 16, 30, 24},
+ {4360, 21, 77, 20, 19, 20, 37, 18, 16, 29, 24},
+ {4350, 21, 77, 20, 19, 20, 37, 18, 16, 29, 24},
+ {4340, 21, 77, 20, 19, 20, 37, 18, 16, 29, 24},
+ {4330, 21, 77, 20, 19, 20, 36, 18, 16, 29, 24},
+ {4320, 21, 77, 20, 19, 20, 36, 18, 16, 29, 24},
+ {4310, 21, 76, 20, 19, 20, 36, 18, 16, 29, 24},
+ {4300, 21, 76, 20, 18, 20, 36, 18, 16, 29, 24},
+ {4290, 21, 76, 20, 18, 20, 36, 18, 16, 29, 24},
+ {4280, 21, 76, 20, 18, 20, 36, 18, 16, 29, 24},
+ {4270, 21, 75, 20, 18, 20, 36, 18, 16, 29, 24},
+ {4260, 21, 75, 20, 18, 20, 35, 17, 15, 29, 24},
+ {4250, 20, 76, 20, 18, 20, 35, 17, 15, 29, 24},
+ {4240, 20, 76, 20, 18, 20, 35, 17, 15, 29, 23},
+ {4230, 20, 75, 20, 18, 20, 35, 17, 15, 29, 23},
+ {4220, 20, 75, 20, 18, 20, 35, 17, 15, 29, 23},
+ {4210, 20, 75, 20, 18, 20, 35, 17, 15, 28, 23},
+ {4200, 20, 75, 19, 18, 19, 36, 17, 15, 28, 23},
+ {4190, 20, 74, 19, 18, 19, 36, 17, 15, 28, 23},
+ {4180, 20, 74, 19, 18, 19, 35, 17, 15, 28, 23},
+ {4170, 20, 74, 19, 18, 19, 35, 17, 15, 28, 23},
+ {4160, 20, 74, 19, 18, 19, 35, 17, 15, 28, 23},
+ {4150, 20, 74, 19, 18, 19, 35, 17, 15, 28, 23},
+ {4140, 20, 73, 19, 18, 19, 35, 17, 15, 28, 23},
+ {4130, 20, 73, 19, 18, 19, 35, 17, 15, 28, 23},
+ {4120, 20, 73, 19, 18, 19, 35, 17, 15, 28, 23},
+ {4110, 20, 73, 19, 18, 19, 34, 17, 15, 28, 23},
+ {4100, 20, 72, 19, 18, 19, 34, 17, 15, 28, 23},
+ {4090, 20, 72, 19, 18, 19, 34, 17, 15, 28, 23},
+ {4080, 20, 72, 19, 18, 19, 34, 17, 15, 28, 23},
+ {4070, 20, 72, 19, 18, 19, 34, 17, 15, 27, 22},
+ {4060, 19, 72, 19, 17, 19, 34, 17, 15, 27, 22},
+ {4050, 19, 72, 19, 17, 19, 34, 17, 15, 27, 22},
+ {4040, 19, 72, 19, 17, 19, 33, 17, 15, 27, 22},
+ {4030, 19, 72, 19, 17, 19, 33, 17, 15, 27, 22},
+ {4020, 19, 71, 19, 17, 19, 33, 16, 15, 27, 22},
+ {4010, 19, 71, 19, 17, 19, 33, 16, 15, 27, 22},
+ {4000, 19, 71, 18, 17, 19, 33, 16, 14, 27, 22},
+ {3990, 19, 71, 18, 17, 18, 34, 16, 14, 27, 22},
+ {3980, 19, 71, 18, 17, 18, 34, 16, 14, 27, 22},
+ {3970, 19, 70, 18, 17, 18, 33, 16, 14, 27, 22},
+ {3960, 19, 70, 18, 17, 18, 33, 16, 14, 27, 22},
+ {3950, 19, 70, 18, 17, 18, 33, 16, 14, 27, 22},
+ {3940, 19, 70, 18, 17, 18, 33, 16, 14, 27, 22},
+ {3930, 19, 69, 18, 17, 18, 33, 16, 14, 27, 22},
+ {3920, 19, 69, 18, 17, 18, 33, 16, 14, 26, 22},
+ {3910, 19, 69, 18, 17, 18, 33, 16, 14, 26, 22},
+ {3900, 19, 69, 18, 17, 18, 33, 16, 14, 26, 21},
+ {3890, 19, 68, 18, 17, 18, 32, 16, 14, 26, 21},
+ {3880, 19, 68, 18, 17, 18, 32, 16, 14, 26, 21},
+ {3870, 19, 68, 18, 17, 18, 32, 16, 14, 26, 21},
+ {3860, 18, 69, 18, 17, 18, 32, 16, 14, 26, 21},
+ {3850, 18, 68, 18, 17, 18, 32, 16, 14, 26, 21},
+ {3840, 18, 68, 18, 17, 18, 32, 16, 14, 26, 21},
+ {3830, 18, 68, 18, 16, 18, 32, 16, 14, 26, 21},
+ {3820, 18, 68, 18, 16, 18, 31, 16, 14, 26, 21},
+ {3810, 18, 68, 18, 16, 18, 31, 16, 14, 26, 21},
+ {3800, 18, 67, 17, 16, 18, 31, 16, 14, 26, 21},
+ {3790, 18, 67, 17, 16, 17, 32, 15, 14, 26, 21},
+ {3780, 18, 67, 17, 16, 17, 32, 15, 14, 25, 21},
+ {3770, 18, 67, 17, 16, 17, 32, 15, 14, 25, 21},
+ {3760, 18, 66, 17, 16, 17, 32, 15, 14, 25, 21},
+ {3750, 18, 66, 17, 16, 17, 31, 15, 14, 25, 21},
+ {3740, 18, 66, 17, 16, 17, 31, 15, 14, 25, 20},
+ {3730, 18, 66, 17, 16, 17, 31, 15, 13, 25, 20},
+ {3720, 18, 65, 17, 16, 17, 31, 15, 13, 25, 20},
+ {3710, 18, 65, 17, 16, 17, 31, 15, 13, 25, 20},
+ {3700, 18, 65, 17, 16, 17, 31, 15, 13, 25, 20},
+ {3690, 18, 65, 17, 16, 17, 31, 15, 13, 25, 20},
+ {3680, 18, 64, 17, 16, 17, 31, 15, 13, 25, 20},
+ {3670, 18, 64, 17, 16, 17, 30, 15, 13, 25, 20},
+ {3660, 17, 65, 17, 16, 17, 30, 15, 13, 25, 20},
+ {3650, 17, 65, 17, 16, 17, 30, 15, 13, 25, 20},
+ {3640, 17, 65, 17, 16, 17, 30, 15, 13, 25, 20},
+ {3630, 17, 64, 17, 16, 17, 30, 15, 13, 24, 20},
+ {3620, 17, 64, 17, 16, 17, 30, 15, 13, 24, 20},
+ {3610, 17, 64, 17, 16, 17, 30, 15, 13, 24, 20},
+ {3600, 17, 64, 16, 16, 17, 29, 15, 13, 24, 20},
+ {3590, 17, 63, 16, 15, 17, 29, 15, 13, 24, 20},
+ {3580, 17, 63, 16, 15, 16, 30, 15, 13, 24, 20},
+ {3570, 17, 63, 16, 15, 16, 30, 15, 13, 24, 19},
+ {3560, 17, 63, 16, 15, 16, 30, 14, 13, 24, 19},
+ {3550, 17, 62, 16, 15, 16, 30, 14, 13, 24, 19},
+ {3540, 17, 62, 16, 15, 16, 30, 14, 13, 24, 19},
+ {3530, 17, 62, 16, 15, 16, 29, 14, 13, 24, 19},
+ {3520, 17, 62, 16, 15, 16, 29, 14, 13, 24, 19},
+ {3510, 17, 62, 16, 15, 16, 29, 14, 13, 24, 19},
+ {3500, 17, 61, 16, 15, 16, 29, 14, 13, 24, 19},
+ {3490, 17, 61, 16, 15, 16, 29, 14, 13, 23, 19},
+ {3480, 17, 61, 16, 15, 16, 29, 14, 13, 23, 19},
+ {3470, 17, 61, 16, 15, 16, 29, 14, 13, 23, 19},
+ {3460, 16, 61, 16, 15, 16, 28, 14, 12, 23, 19},
+ {3450, 16, 61, 16, 15, 16, 28, 14, 12, 23, 19},
+ {3440, 16, 61, 16, 15, 16, 28, 14, 12, 23, 19},
+ {3430, 16, 61, 16, 15, 16, 28, 14, 12, 23, 19},
+ {3420, 16, 60, 16, 15, 16, 28, 14, 12, 23, 19},
+ {3410, 16, 60, 16, 15, 16, 28, 14, 12, 23, 18},
+ {3400, 16, 60, 15, 15, 16, 28, 14, 12, 23, 18},
+ {3390, 16, 60, 15, 15, 16, 28, 14, 12, 23, 18},
+ {3380, 16, 59, 15, 15, 16, 27, 14, 12, 23, 18},
+ {3370, 16, 59, 15, 15, 15, 28, 14, 12, 23, 18},
+ {3360, 16, 59, 15, 14, 15, 28, 14, 12, 23, 18},
+ {3350, 16, 59, 15, 14, 15, 28, 14, 12, 23, 18},
+ {3340, 16, 59, 15, 14, 15, 28, 14, 12, 22, 18},
+ {3330, 16, 58, 15, 14, 15, 28, 14, 12, 22, 18},
+ {3320, 16, 58, 15, 14, 15, 28, 13, 12, 22, 18},
+ {3310, 16, 58, 15, 14, 15, 27, 13, 12, 22, 18},
+ {3300, 16, 58, 15, 14, 15, 27, 13, 12, 22, 18},
+ {3290, 16, 57, 15, 14, 15, 27, 13, 12, 22, 18},
+ {3280, 16, 57, 15, 14, 15, 27, 13, 12, 22, 18},
+ {3270, 16, 57, 15, 14, 15, 27, 13, 12, 22, 18},
+ {3260, 15, 58, 15, 14, 15, 27, 13, 12, 22, 18},
+ {3250, 15, 57, 15, 14, 15, 27, 13, 12, 22, 18},
+ {3240, 15, 57, 15, 14, 15, 26, 13, 12, 22, 17},
+ {3230, 15, 57, 15, 14, 15, 26, 13, 12, 22, 17},
+ {3220, 15, 57, 15, 14, 15, 26, 13, 12, 22, 17},
+ {3210, 15, 56, 15, 14, 15, 26, 13, 12, 22, 17},
+ {3200, 15, 56, 14, 14, 15, 26, 13, 11, 21, 17},
+ {3190, 15, 56, 14, 14, 15, 26, 13, 11, 21, 17},
+ {3180, 15, 56, 14, 14, 15, 26, 13, 11, 21, 17},
+ {3170, 15, 56, 14, 14, 15, 25, 13, 11, 21, 17},
+ {3160, 15, 55, 14, 14, 14, 26, 13, 11, 21, 17},
+ {3150, 15, 55, 14, 14, 14, 26, 13, 11, 21, 17},
+ {3140, 15, 55, 14, 14, 14, 26, 13, 11, 21, 17},
+ {3130, 15, 55, 14, 14, 14, 26, 13, 11, 21, 17},
+ {3120, 15, 54, 14, 13, 14, 26, 13, 11, 21, 17},
+ {3110, 15, 54, 14, 13, 14, 26, 13, 11, 21, 17},
+ {3100, 15, 54, 14, 13, 14, 26, 13, 11, 21, 17},
+ {3090, 15, 54, 14, 13, 14, 25, 12, 11, 21, 17},
+ {3080, 15, 53, 14, 13, 14, 25, 12, 11, 21, 17},
+ {3070, 14, 54, 14, 13, 14, 25, 12, 11, 21, 16},
+ {3060, 14, 54, 14, 13, 14, 25, 12, 11, 21, 16},
+ {3050, 14, 54, 14, 13, 14, 25, 12, 11, 20, 16},
+ {3040, 14, 53, 14, 13, 14, 25, 12, 11, 20, 16},
+ {3030, 14, 53, 14, 13, 14, 25, 12, 11, 20, 16},
+ {3020, 14, 53, 14, 13, 14, 24, 12, 11, 20, 16},
+ {3010, 14, 53, 14, 13, 14, 24, 12, 11, 20, 16},
+ {3000, 14, 53, 13, 13, 14, 24, 12, 11, 20, 16},
+ {2990, 14, 52, 13, 13, 14, 24, 12, 11, 20, 16},
+ {2980, 14, 52, 13, 13, 14, 24, 12, 11, 20, 16},
+ {2970, 14, 52, 13, 13, 14, 24, 12, 11, 20, 16},
+ {2960, 14, 52, 13, 13, 14, 24, 12, 11, 20, 16},
+ {2950, 14, 51, 13, 13, 13, 24, 12, 11, 20, 16},
+ {2940, 14, 51, 13, 13, 13, 24, 12, 11, 20, 16},
+ {2930, 14, 51, 13, 13, 13, 24, 12, 10, 20, 16},
+ {2920, 14, 51, 13, 13, 13, 24, 12, 10, 20, 16},
+ {2910, 14, 50, 13, 13, 13, 24, 12, 10, 20, 15},
+ {2900, 14, 50, 13, 13, 13, 24, 12, 10, 19, 15},
+ {2890, 14, 50, 13, 12, 13, 24, 12, 10, 19, 15},
+ {2880, 14, 50, 13, 12, 13, 23, 12, 10, 19, 15},
+ {2870, 13, 50, 13, 12, 13, 23, 12, 10, 19, 15},
+ {2860, 13, 50, 13, 12, 13, 23, 12, 10, 19, 15},
+ {2850, 13, 50, 13, 12, 13, 23, 11, 10, 19, 15},
+ {2840, 13, 50, 13, 12, 13, 23, 11, 10, 19, 15},
+ {2830, 13, 50, 13, 12, 13, 23, 11, 10, 19, 15},
+ {2820, 13, 49, 13, 12, 13, 23, 11, 10, 19, 15},
+ {2810, 13, 49, 13, 12, 13, 23, 11, 10, 19, 15},
+ {2800, 13, 49, 12, 12, 13, 22, 11, 10, 19, 15},
+ {2790, 13, 49, 12, 12, 13, 22, 11, 10, 19, 15},
+ {2780, 13, 48, 12, 12, 13, 22, 11, 10, 19, 15},
+ {2770, 13, 48, 12, 12, 13, 22, 11, 10, 19, 15},
+ {2760, 13, 48, 12, 12, 13, 22, 11, 10, 18, 15},
+ {2750, 13, 48, 12, 12, 13, 22, 11, 10, 18, 15},
+ {2740, 13, 47, 12, 12, 12, 23, 11, 10, 18, 14},
+ {2730, 13, 47, 12, 12, 12, 22, 11, 10, 18, 14},
+ {2720, 13, 47, 12, 12, 12, 22, 11, 10, 18, 14},
+ {2710, 13, 47, 12, 12, 12, 22, 11, 10, 18, 14},
+ {2700, 13, 47, 12, 12, 12, 22, 11, 10, 18, 14},
+ {2690, 13, 46, 12, 12, 12, 22, 11, 10, 18, 14},
+ {2680, 13, 46, 12, 12, 12, 22, 11, 10, 18, 14},
+ {2670, 12, 47, 12, 12, 12, 22, 11, 10, 18, 14},
+ {2660, 12, 47, 12, 12, 12, 21, 11, 9, 18, 14},
+ {2650, 12, 46, 12, 11, 12, 21, 11, 9, 18, 14},
+ {2640, 12, 46, 12, 11, 12, 21, 11, 9, 18, 14},
+ {2630, 12, 46, 12, 11, 12, 21, 11, 9, 18, 14},
+ {2620, 12, 46, 12, 11, 12, 21, 10, 9, 18, 14},
+ {2610, 12, 45, 12, 11, 12, 21, 10, 9, 17, 14},
+ {2600, 12, 45, 11, 11, 12, 21, 10, 9, 17, 14},
+ {2590, 12, 45, 11, 11, 12, 20, 10, 9, 17, 14},
+ {2580, 12, 45, 11, 11, 12, 20, 10, 9, 17, 14},
+ {2570, 12, 44, 11, 11, 12, 20, 10, 9, 17, 13},
+ {2560, 12, 44, 11, 11, 12, 20, 10, 9, 17, 13},
+ {2550, 12, 44, 11, 11, 12, 20, 10, 9, 17, 13},
+ {2540, 12, 44, 11, 11, 11, 21, 10, 9, 17, 13},
+ {2530, 12, 44, 11, 11, 11, 21, 10, 9, 17, 13},
+ {2520, 12, 43, 11, 11, 11, 21, 10, 9, 17, 13},
+ {2510, 12, 43, 11, 11, 11, 20, 10, 9, 17, 13},
+ {2500, 12, 43, 11, 11, 11, 20, 10, 9, 17, 13},
+ {2490, 12, 43, 11, 11, 11, 20, 10, 9, 17, 13},
+ {2480, 12, 42, 11, 11, 11, 20, 10, 9, 17, 13},
+ {2470, 11, 43, 11, 11, 11, 20, 10, 9, 16, 13},
+ {2460, 11, 43, 11, 11, 11, 20, 10, 9, 16, 13},
+ {2450, 11, 43, 11, 11, 11, 20, 10, 9, 16, 13},
+ {2440, 11, 42, 11, 11, 11, 19, 10, 9, 16, 13},
+ {2430, 11, 42, 11, 11, 11, 19, 10, 9, 16, 13},
+ {2420, 11, 42, 11, 10, 11, 19, 10, 9, 16, 13},
+ {2410, 11, 42, 11, 10, 11, 19, 10, 9, 16, 12},
+ {2400, 11, 41, 10, 10, 11, 19, 10, 8, 16, 12},
+ {2390, 11, 41, 10, 10, 11, 19, 10, 8, 16, 12},
+ {2380, 11, 41, 10, 10, 11, 19, 9, 8, 16, 12},
+ {2370, 11, 41, 10, 10, 11, 18, 9, 8, 16, 12},
+ {2360, 11, 41, 10, 10, 11, 18, 9, 8, 16, 12},
+ {2350, 11, 40, 10, 10, 11, 18, 9, 8, 16, 12},
+ {2340, 11, 40, 10, 10, 11, 18, 9, 8, 16, 12},
+ {2330, 11, 40, 10, 10, 10, 19, 9, 8, 16, 12},
+ {2320, 11, 40, 10, 10, 10, 19, 9, 8, 15, 12},
+ {2310, 11, 39, 10, 10, 10, 19, 9, 8, 15, 12},
+ {2300, 11, 39, 10, 10, 10, 18, 9, 8, 15, 12},
+ {2290, 11, 39, 10, 10, 10, 18, 9, 8, 15, 12},
+ {2280, 11, 39, 10, 10, 10, 18, 9, 8, 15, 12},
+ {2270, 10, 39, 10, 10, 10, 18, 9, 8, 15, 12},
+ {2260, 10, 39, 10, 10, 10, 18, 9, 8, 15, 12},
+ {2250, 10, 39, 10, 10, 10, 18, 9, 8, 15, 12},
+ {2240, 10, 39, 10, 10, 10, 18, 9, 8, 15, 11},
+ {2230, 10, 38, 10, 10, 10, 18, 9, 8, 15, 11},
+ {2220, 10, 38, 10, 10, 10, 17, 9, 8, 15, 11},
+ {2210, 10, 38, 10, 10, 10, 17, 9, 8, 15, 11},
+ {2200, 10, 38, 9, 10, 10, 17, 9, 8, 15, 11},
+ {2190, 10, 38, 9, 9, 10, 17, 9, 8, 15, 11},
+ {2180, 10, 37, 9, 9, 10, 17, 9, 8, 14, 11},
+ {2170, 10, 37, 9, 9, 10, 17, 9, 8, 14, 11},
+ {2160, 10, 37, 9, 9, 10, 17, 9, 8, 14, 11},
+ {2150, 10, 37, 9, 9, 10, 16, 8, 8, 14, 11},
+ {2140, 10, 36, 9, 9, 10, 16, 8, 8, 14, 11},
+ {2130, 10, 36, 9, 9, 10, 16, 8, 7, 14, 11},
+ {2120, 10, 36, 9, 9, 9, 17, 8, 7, 14, 11},
+ {2110, 10, 36, 9, 9, 9, 17, 8, 7, 14, 11},
+ {2100, 10, 35, 9, 9, 9, 17, 8, 7, 14, 11},
+ {2090, 10, 35, 9, 9, 9, 17, 8, 7, 14, 11},
+ {2080, 9, 36, 9, 9, 9, 16, 8, 7, 14, 11},
+ {2070, 9, 36, 9, 9, 9, 16, 8, 7, 14, 10},
+ {2060, 9, 35, 9, 9, 9, 16, 8, 7, 14, 10},
+ {2050, 9, 35, 9, 9, 9, 16, 8, 7, 14, 10},
+ {2040, 9, 35, 9, 9, 9, 16, 8, 7, 14, 10},
+ {2030, 9, 35, 9, 9, 9, 16, 8, 7, 13, 10},
+ {2020, 9, 35, 9, 9, 9, 16, 8, 7, 13, 10},
+ {2010, 9, 34, 9, 9, 9, 15, 8, 7, 13, 10},
+ {2000, 9, 34, 8, 9, 9, 15, 8, 7, 13, 10},
+ {1990, 9, 34, 8, 9, 9, 15, 8, 7, 13, 10},
+ {1980, 9, 34, 8, 9, 9, 15, 8, 7, 13, 10},
+ {1970, 9, 33, 8, 9, 9, 15, 8, 7, 13, 10},
+ {1960, 9, 33, 8, 9, 9, 15, 8, 7, 13, 10},
+ {1950, 9, 33, 8, 8, 9, 15, 8, 7, 13, 10},
+ {1940, 9, 33, 8, 8, 9, 15, 8, 7, 13, 10},
+ {1930, 9, 32, 8, 8, 9, 14, 8, 7, 13, 10},
+ {1920, 9, 32, 8, 8, 9, 14, 8, 7, 13, 10},
+ {1910, 9, 32, 8, 8, 8, 15, 7, 7, 13, 9},
+ {1900, 9, 32, 8, 8, 8, 15, 7, 7, 13, 9},
+ {1890, 9, 31, 8, 8, 8, 15, 7, 7, 12, 9},
+ {1880, 8, 32, 8, 8, 8, 15, 7, 7, 12, 9},
+ {1870, 8, 32, 8, 8, 8, 15, 7, 7, 12, 9},
+ {1860, 8, 32, 8, 8, 8, 14, 7, 6, 12, 9},
+ {1850, 8, 32, 8, 8, 8, 14, 7, 6, 12, 9},
+ {1840, 8, 31, 8, 8, 8, 14, 7, 6, 12, 9},
+ {1830, 8, 31, 8, 8, 8, 14, 7, 6, 12, 9},
+ {1820, 8, 31, 8, 8, 8, 14, 7, 6, 12, 9},
+ {1810, 8, 31, 8, 8, 8, 14, 7, 6, 12, 9},
+ {1800, 8, 30, 7, 8, 8, 14, 7, 6, 12, 9},
+ {1790, 8, 30, 7, 8, 8, 13, 7, 6, 12, 9},
+ {1780, 8, 30, 7, 8, 8, 13, 7, 6, 12, 9},
+ {1770, 8, 30, 7, 8, 8, 13, 7, 6, 12, 9},
+ {1760, 8, 29, 7, 8, 8, 13, 7, 6, 12, 9},
+ {1750, 8, 29, 7, 8, 8, 13, 7, 6, 12, 9},
+ {1740, 8, 29, 7, 8, 8, 13, 7, 6, 11, 8},
+ {1730, 8, 29, 7, 8, 8, 13, 7, 6, 11, 8},
+ {1720, 8, 29, 7, 7, 8, 13, 7, 6, 11, 8},
+ {1710, 8, 28, 7, 7, 8, 12, 7, 6, 11, 8},
+ {1700, 8, 28, 7, 7, 7, 13, 7, 6, 11, 8},
+ {1690, 8, 28, 7, 7, 7, 13, 7, 6, 11, 8},
+ {1680, 7, 29, 7, 7, 7, 13, 6, 6, 11, 8},
+ {1670, 7, 28, 7, 7, 7, 13, 6, 6, 11, 8},
+ {1660, 7, 28, 7, 7, 7, 13, 6, 6, 11, 8},
+ {1650, 7, 28, 7, 7, 7, 13, 6, 6, 11, 8},
+ {1640, 7, 28, 7, 7, 7, 12, 6, 6, 11, 8},
+ {1630, 7, 27, 7, 7, 7, 12, 6, 6, 11, 8},
+ {1620, 7, 27, 7, 7, 7, 12, 6, 6, 11, 8},
+ {1610, 7, 27, 7, 7, 7, 12, 6, 6, 11, 8},
+ {1600, 7, 27, 6, 7, 7, 12, 6, 5, 10, 8},
+ {1590, 7, 26, 6, 7, 7, 12, 6, 5, 10, 8},
+ {1580, 7, 26, 6, 7, 7, 12, 6, 5, 10, 7},
+ {1570, 7, 26, 6, 7, 7, 11, 6, 5, 10, 7},
+ {1560, 7, 26, 6, 7, 7, 11, 6, 5, 10, 7},
+ {1550, 7, 26, 6, 7, 7, 11, 6, 5, 10, 7},
+ {1540, 7, 25, 6, 7, 7, 11, 6, 5, 10, 7},
+ {1530, 7, 25, 6, 7, 7, 11, 6, 5, 10, 7},
+ {1520, 7, 25, 6, 7, 7, 11, 6, 5, 10, 7},
+ {1510, 7, 25, 6, 7, 7, 11, 6, 5, 10, 7},
+ {1500, 7, 24, 6, 7, 7, 10, 6, 5, 10, 7},
+ {1490, 59, 25, 6, 77, 59, 10, 70, 44, 9, 73},
+ {1480, 59, 24, 6, 76, 58, 10, 70, 44, 9, 73},
+ {1470, 58, 24, 6, 76, 58, 10, 69, 44, 9, 72},
+ {1460, 58, 24, 6, 76, 58, 10, 69, 43, 9, 72},
+ {1450, 58, 24, 6, 75, 57, 10, 68, 43, 9, 71},
+ {1440, 57, 24, 6, 75, 57, 10, 68, 43, 9, 71},
+ {1430, 57, 23, 6, 75, 57, 10, 68, 43, 8, 70},
+ {1420, 56, 23, 6, 74, 57, 9, 67, 43, 8, 70},
+ {1410, 56, 23, 6, 74, 57, 9, 67, 43, 8, 69},
+ {1400, 56, 23, 5, 74, 55, 9, 67, 41, 8, 69},
+ {1390, 55, 23, 5, 73, 55, 9, 66, 41, 8, 68},
+ {1380, 55, 23, 5, 73, 54, 9, 66, 41, 8, 68},
+ {1370, 54, 22, 5, 72, 54, 9, 66, 41, 8, 67},
+ {1360, 54, 22, 5, 72, 54, 9, 65, 40, 8, 67},
+ {1350, 54, 22, 5, 72, 53, 9, 65, 40, 8, 66},
+ {1340, 53, 22, 5, 71, 53, 9, 65, 40, 8, 66},
+ {1330, 53, 22, 5, 71, 53, 9, 64, 39, 8, 65},
+ {1320, 52, 22, 5, 71, 53, 8, 64, 40, 8, 65},
+ {1310, 52, 21, 5, 70, 53, 8, 64, 40, 8, 64},
+ {1300, 51, 21, 5, 70, 51, 8, 63, 38, 8, 64},
+ {1290, 51, 21, 5, 70, 51, 8, 63, 38, 7, 64},
+ {1280, 51, 21, 5, 69, 51, 8, 63, 38, 7, 63},
+ {1270, 50, 21, 5, 69, 50, 8, 62, 38, 7, 63},
+ {1260, 50, 20, 5, 69, 50, 8, 62, 37, 7, 62},
+ {1250, 49, 20, 5, 68, 49, 8, 62, 37, 7, 62},
+ {1240, 49, 20, 5, 68, 49, 8, 61, 37, 7, 61},
+ {1230, 49, 20, 5, 68, 49, 8, 61, 36, 7, 61},
+ {1220, 48, 20, 5, 67, 48, 8, 61, 36, 7, 60},
+ {1210, 48, 19, 5, 67, 48, 7, 60, 36, 7, 60},
+ {1200, 49, 19, 4, 67, 49, 7, 60, 36, 7, 59},
+ {1190, 48, 19, 4, 66, 48, 7, 60, 36, 7, 59},
+ {1180, 48, 19, 4, 66, 48, 7, 59, 36, 7, 58},
+ {1170, 46, 19, 4, 66, 46, 7, 59, 35, 7, 58},
+ {1160, 46, 18, 4, 65, 46, 7, 59, 34, 7, 57},
+ {1150, 45, 18, 4, 65, 46, 7, 58, 34, 7, 57},
+ {1140, 45, 18, 4, 65, 45, 7, 58, 34, 6, 56},
+ {1130, 45, 18, 4, 64, 45, 7, 58, 33, 6, 56},
+ {1120, 44, 18, 4, 64, 44, 7, 57, 33, 6, 55},
+ {1110, 44, 18, 4, 64, 44, 7, 57, 33, 6, 55},
+ {1100, 43, 17, 4, 63, 44, 6, 57, 32, 6, 54},
+ {1090, 43, 17, 4, 63, 44, 6, 56, 33, 6, 54},
+ {1080, 43, 17, 4, 63, 44, 6, 56, 33, 6, 53},
+ {1070, 42, 17, 4, 62, 44, 6, 56, 33, 6, 53},
+ {1060, 42, 17, 4, 62, 42, 6, 55, 31, 6, 52},
+ {1050, 41, 17, 4, 62, 42, 6, 55, 31, 6, 52},
+ {1040, 41, 16, 4, 61, 41, 6, 54, 31, 6, 52},
+ {1030, 41, 16, 4, 61, 41, 6, 54, 30, 6, 51},
+ {1020, 40, 16, 4, 61, 41, 6, 54, 30, 6, 51},
+ {1010, 40, 16, 4, 60, 40, 6, 53, 30, 6, 50},
+ {1000, 39, 16, 3, 60, 40, 6, 53, 29, 5, 50},
+ { 990, 39, 15, 3, 60, 39, 6, 53, 29, 5, 49},
+ { 980, 39, 15, 3, 59, 39, 5, 52, 29, 5, 49},
+ { 970, 38, 15, 3, 59, 39, 5, 52, 29, 5, 48},
+ { 960, 38, 15, 3, 59, 39, 5, 52, 29, 5, 48},
+ { 950, 37, 15, 3, 58, 39, 5, 51, 29, 5, 47},
+ { 940, 37, 14, 3, 58, 39, 5, 51, 29, 5, 47},
+ { 930, 37, 14, 3, 57, 37, 5, 51, 27, 5, 46},
+ { 920, 36, 14, 3, 57, 37, 5, 50, 27, 5, 46},
+ { 910, 36, 14, 3, 57, 36, 5, 50, 27, 5, 45},
+ { 900, 35, 14, 3, 56, 36, 5, 50, 26, 5, 45},
+ { 890, 35, 14, 3, 56, 36, 5, 49, 26, 5, 44},
+ { 880, 35, 13, 3, 56, 35, 5, 49, 26, 5, 44},
+ { 870, 34, 13, 3, 55, 35, 4, 49, 26, 5, 43},
+ { 860, 34, 13, 3, 55, 35, 4, 48, 25, 5, 43},
+ { 850, 33, 13, 3, 55, 35, 4, 48, 26, 4, 42},
+ { 840, 33, 13, 3, 54, 35, 4, 48, 26, 4, 42},
+ { 830, 33, 12, 3, 54, 33, 4, 47, 24, 4, 41},
+ { 820, 32, 12, 3, 54, 33, 4, 47, 24, 4, 41},
+ { 810, 32, 12, 3, 53, 33, 4, 47, 24, 4, 40},
+ { 800, 31, 12, 2, 53, 32, 4, 46, 23, 4, 40},
+ { 790, 31, 12, 2, 53, 32, 4, 46, 23, 4, 39},
+ { 780, 30, 12, 2, 52, 31, 4, 46, 23, 4, 39},
+ { 770, 30, 11, 2, 52, 31, 4, 45, 23, 4, 39},
+ { 760, 30, 11, 2, 52, 31, 3, 45, 22, 4, 38},
+ { 750, 29, 11, 2, 51, 30, 3, 45, 22, 4, 38},
+ { 740, 29, 11, 2, 51, 30, 3, 44, 22, 4, 37},
+ { 730, 28, 11, 2, 51, 31, 3, 44, 22, 4, 37},
+ { 720, 28, 10, 2, 50, 30, 3, 44, 22, 4, 36},
+ { 710, 28, 10, 2, 50, 30, 3, 43, 22, 4, 36},
+ { 700, 27, 10, 2, 50, 28, 3, 43, 20, 3, 35},
+ { 690, 27, 10, 2, 49, 28, 3, 43, 20, 3, 35},
+ { 680, 26, 10, 2, 49, 28, 3, 42, 20, 3, 34},
+ { 670, 26, 10, 2, 49, 27, 3, 42, 20, 3, 34},
+ { 660, 26, 9, 2, 48, 27, 3, 42, 19, 3, 33},
+ { 650, 25, 9, 2, 48, 26, 3, 41, 19, 3, 33},
+ { 640, 25, 9, 2, 48, 26, 2, 41, 19, 3, 32},
+ { 630, 24, 9, 2, 47, 26, 2, 40, 18, 3, 32},
+ { 620, 24, 9, 2, 47, 26, 2, 40, 19, 3, 31},
+ { 610, 24, 8, 2, 47, 26, 2, 40, 19, 3, 31},
+ { 600, 23, 8, 1, 46, 26, 2, 39, 18, 3, 30},
+ { 590, 23, 8, 1, 46, 24, 2, 39, 17, 3, 30},
+ { 580, 22, 8, 1, 46, 24, 2, 39, 17, 3, 29},
+ { 570, 22, 8, 1, 45, 23, 2, 38, 17, 3, 29},
+ { 560, 22, 7, 1, 45, 23, 2, 38, 16, 2, 28},
+ { 550, 21, 7, 1, 45, 23, 2, 38, 16, 2, 28},
+ { 540, 21, 7, 1, 44, 22, 2, 37, 16, 2, 27},
+ { 530, 20, 7, 1, 44, 22, 1, 37, 15, 2, 27},
+ { 520, 20, 7, 1, 43, 21, 1, 37, 15, 2, 27},
+ { 510, 20, 6, 1, 43, 21, 1, 36, 15, 2, 26},
+ { 500, 19, 6, 1, 43, 22, 1, 36, 15, 2, 26},
+ { 490, 19, 6, 1, 42, 21, 1, 36, 15, 2, 25},
+ { 480, 18, 6, 1, 42, 21, 1, 35, 15, 2, 25},
+ { 470, 18, 6, 1, 42, 21, 1, 35, 15, 2, 24},
+ { 460, 18, 6, 1, 41, 19, 1, 35, 13, 2, 24},
+ { 450, 17, 5, 1, 41, 19, 1, 34, 13, 2, 23},
+ { 440, 17, 5, 1, 41, 18, 1, 34, 13, 2, 23},
+ { 430, 16, 5, 1, 40, 18, 0, 34, 12, 2, 22},
+ { 420, 16, 5, 1, 40, 18, 0, 33, 12, 2, 22},
+ { 410, 16, 5, 1, 40, 17, 0, 33, 12, 1, 21},
+ { 400, 15, 5, 0, 39, 17, 0, 33, 11, 1, 21},
+ { 390, 15, 4, 0, 39, 17, 0, 32, 12, 1, 20},
+ { 380, 14, 4, 0, 39, 17, 0, 32, 12, 1, 20},
+ { 370, 14, 4, 0, 38, 17, 0, 32, 12, 1, 19},
+ { 360, 14, 4, 0, 38, 15, 0, 31, 10, 1, 19},
+ { 350, 13, 4, 0, 38, 15, 0, 31, 10, 1, 18},
+ { 340, 13, 3, 0, 37, 15, 0, 31, 10, 1, 18},
+ { 330, 12, 3, 0, 37, 14, 0, 30, 9, 1, 17},
+ { 320, 12, 3, 0, 37, 14, 0, 30, 9, 1, 17},
+ { 310, 12, 3, 0, 36, 13, 0, 30, 9, 1, 16},
+ { 300, 11, 3, 0, 36, 13, 0, 29, 8, 1, 16},
+ { 290, 11, 2, 0, 36, 13, 0, 29, 8, 1, 15},
+ { 280, 10, 2, 0, 35, 12, 0, 29, 8, 1, 15},
+ { 270, 10, 2, 0, 35, 12, 0, 28, 8, 0, 14},
+ { 260, 9, 2, 0, 35, 12, 0, 28, 8, 0, 14},
+ { 250, 9, 2, 0, 34, 12, 0, 28, 8, 0, 14},
+ { 240, 9, 2, 0, 34, 12, 0, 27, 8, 0, 13},
+ { 230, 8, 1, 0, 34, 10, 0, 27, 6, 0, 13},
+ { 220, 8, 1, 0, 33, 10, 0, 27, 6, 0, 12},
+ { 210, 7, 1, 0, 33, 10, 0, 26, 6, 0, 12},
+ { 200, 7, 1, 0, 33, 9, 0, 26, 5, 0, 11},
+ { 190, 7, 1, 0, 32, 9, 0, 25, 5, 0, 11},
+ { 180, 6, 1, 0, 32, 8, 0, 25, 5, 0, 10},
+ { 170, 6, 0, 0, 32, 8, 0, 25, 5, 0, 10},
+ { 160, 5, 0, 0, 31, 8, 0, 24, 4, 0, 9},
+ { 150, 5, 0, 0, 31, 8, 0, 24, 5, 0, 9},
+ { 140, 5, 0, 0, 31, 8, 0, 24, 5, 0, 8},
+ { 130, 4, 0, 0, 30, 6, 0, 23, 3, 0, 8},
+ { 120, 4, 0, 0, 30, 6, 0, 23, 3, 0, 7},
+ { 110, 3, 0, 0, 30, 6, 0, 23, 3, 0, 7},
+ { 100, 3, 0, 0, 29, 5, 0, 22, 2, 0, 6},
+ { 90, 3, 0, 0, 29, 5, 0, 22, 2, 0, 6},
+ { 80, 2, 0, 0, 28, 5, 0, 22, 2, 0, 5},
+};
+
+static void samsung_mipi_dcphy_bias_block_enable(struct samsung_mipi_dcphy *samsung)
+{
+ regmap_write(samsung->regmap, BIAS_CON0, I_DEV_DIV_6 | I_RES_100_2UA);
+ regmap_write(samsung->regmap, BIAS_CON1, I_VBG_SEL_820MV | I_BGR_VREF_820MV |
+ I_LADDER_1_00V);
+ regmap_write(samsung->regmap, BIAS_CON2, REG_325M_325MV | REG_LP_400M_400MV |
+ REG_400M_400MV | REG_645M_645MV);
+
+ /* default output voltage select:
+ * dphy: 400mv
+ * cphy: 530mv
+ */
+ regmap_update_bits(samsung->regmap, BIAS_CON4,
+ I_MUX_SEL_MASK, I_MUX_400MV);
+}
+
+static void samsung_mipi_dphy_lane_enable(struct samsung_mipi_dcphy *samsung)
+{
+ regmap_write(samsung->regmap, DPHY_MC_GNR_CON1, T_PHY_READY(0x2000));
+ regmap_update_bits(samsung->regmap, DPHY_MC_GNR_CON0,
+ PHY_ENABLE, PHY_ENABLE);
+
+ switch (samsung->lanes) {
+ case 4:
+ regmap_write(samsung->regmap, DPHY_MD3_GNR_CON1,
+ T_PHY_READY(0x2000));
+ regmap_update_bits(samsung->regmap, DPHY_MD3_GNR_CON0,
+ PHY_ENABLE, PHY_ENABLE);
+ fallthrough;
+ case 3:
+ regmap_write(samsung->regmap, COMBO_MD2_GNR_CON1,
+ T_PHY_READY(0x2000));
+ regmap_update_bits(samsung->regmap, COMBO_MD2_GNR_CON0,
+ PHY_ENABLE, PHY_ENABLE);
+ fallthrough;
+ case 2:
+ regmap_write(samsung->regmap, COMBO_MD1_GNR_CON1,
+ T_PHY_READY(0x2000));
+ regmap_update_bits(samsung->regmap, COMBO_MD1_GNR_CON0,
+ PHY_ENABLE, PHY_ENABLE);
+ fallthrough;
+ case 1:
+ default:
+ regmap_write(samsung->regmap, COMBO_MD0_GNR_CON1,
+ T_PHY_READY(0x2000));
+ regmap_update_bits(samsung->regmap, COMBO_MD0_GNR_CON0,
+ PHY_ENABLE, PHY_ENABLE);
+ break;
+ }
+}
+
+static void samsung_mipi_dphy_lane_disable(struct samsung_mipi_dcphy *samsung)
+{
+ switch (samsung->lanes) {
+ case 4:
+ regmap_update_bits(samsung->regmap, DPHY_MD3_GNR_CON0,
+ PHY_ENABLE, 0);
+ fallthrough;
+ case 3:
+ regmap_update_bits(samsung->regmap, COMBO_MD2_GNR_CON0,
+ PHY_ENABLE, 0);
+ fallthrough;
+ case 2:
+ regmap_update_bits(samsung->regmap, COMBO_MD1_GNR_CON0,
+ PHY_ENABLE, 0);
+ fallthrough;
+ case 1:
+ default:
+ regmap_update_bits(samsung->regmap, COMBO_MD0_GNR_CON0,
+ PHY_ENABLE, 0);
+ break;
+ }
+
+ regmap_update_bits(samsung->regmap, DPHY_MC_GNR_CON0, PHY_ENABLE, 0);
+}
+
+static void samsung_mipi_dcphy_pll_configure(struct samsung_mipi_dcphy *samsung)
+{
+ regmap_update_bits(samsung->regmap, PLL_CON0, S_MASK | P_MASK,
+ S(samsung->pll.scaler) | P(samsung->pll.prediv));
+
+ if (samsung->pll.dsm < 0) {
+ u16 dsm_tmp;
+
+ /* Using opposite number subtraction to find complement */
+ dsm_tmp = abs(samsung->pll.dsm);
+ dsm_tmp = dsm_tmp - 1;
+ dsm_tmp ^= 0xffff;
+ regmap_write(samsung->regmap, PLL_CON1, dsm_tmp);
+ } else {
+ regmap_write(samsung->regmap, PLL_CON1, samsung->pll.dsm);
+ }
+
+ regmap_update_bits(samsung->regmap, PLL_CON2,
+ M_MASK, M(samsung->pll.fbdiv));
+
+ if (samsung->pll.ssc_en) {
+ regmap_write(samsung->regmap, PLL_CON3,
+ MRR(samsung->pll.mrr) | MFR(samsung->pll.mfr));
+ regmap_update_bits(samsung->regmap, PLL_CON4, SSCG_EN, SSCG_EN);
+ }
+
+ regmap_write(samsung->regmap, PLL_CON5, RESET_N_SEL | PLL_ENABLE_SEL);
+ regmap_write(samsung->regmap, PLL_CON7, PLL_LOCK_CNT(0xf000));
+ regmap_write(samsung->regmap, PLL_CON8, PLL_STB_CNT(0xf000));
+}
+
+static int samsung_mipi_dcphy_pll_enable(struct samsung_mipi_dcphy *samsung)
+{
+ u32 sts;
+ int ret;
+
+ regmap_update_bits(samsung->regmap, PLL_CON0, PLL_EN, PLL_EN);
+
+ ret = regmap_read_poll_timeout(samsung->regmap, PLL_STAT0,
+ sts, (sts & PLL_LOCK), 1000, 20000);
+ if (ret < 0)
+ dev_err(samsung->dev, "DC-PHY pll failed to lock\n");
+
+ return ret;
+}
+
+static void samsung_mipi_dcphy_pll_disable(struct samsung_mipi_dcphy *samsung)
+{
+ regmap_update_bits(samsung->regmap, PLL_CON0, PLL_EN, 0);
+}
+
+static const struct samsung_mipi_dphy_timing *
+samsung_mipi_dphy_get_timing(struct samsung_mipi_dcphy *samsung)
+{
+ const struct samsung_mipi_dphy_timing *timings;
+ unsigned int num_timings;
+ unsigned int lane_mbps = div64_ul(samsung->pll.rate, USEC_PER_SEC);
+ unsigned int i;
+
+ timings = samsung_mipi_dphy_timing_table;
+ num_timings = ARRAY_SIZE(samsung_mipi_dphy_timing_table);
+
+ for (i = num_timings; i > 1; i--)
+ if (lane_mbps <= timings[i - 1].max_lane_mbps)
+ break;
+
+ return &timings[i - 1];
+}
+
+static unsigned long
+samsung_mipi_dcphy_pll_round_rate(struct samsung_mipi_dcphy *samsung,
+ unsigned long prate, unsigned long rate,
+ u8 *prediv, u16 *fbdiv, int *dsm, u8 *scaler)
+{
+ u32 max_fout = samsung->pdata->dphy_tx_max_lane_kbps;
+ u64 best_freq = 0;
+ u64 fin, fvco, fout;
+ u8 min_prediv, max_prediv;
+ u8 _prediv, best_prediv = 1;
+ u16 _fbdiv, best_fbdiv = 1;
+ u8 _scaler, best_scaler = 0;
+ u32 min_delta = UINT_MAX;
+ long _dsm, best_dsm = 0;
+
+ if (!prate) {
+ dev_err(samsung->dev, "parent rate of PLL can not be zero\n");
+ return 0;
+ }
+
+ /*
+ * The PLL output frequency can be calculated using a simple formula:
+ * Fvco = ((m+k/65536) x 2 x Fin) / p
+ * Fout = ((m+k/65536) x 2 x Fin) / (p x 2^s)
+ */
+ fin = div64_ul(prate, MSEC_PER_SEC);
+
+ while (!best_freq) {
+ fout = div64_ul(rate, MSEC_PER_SEC);
+ if (fout > max_fout)
+ fout = max_fout;
+
+ /* 0 ≤ S[2:0] ≤ 6 */
+ for (_scaler = 0; _scaler < 7; _scaler++) {
+ fvco = fout << _scaler;
+
+ /*
+ * 2600MHz ≤ FVCO ≤ 6600MHz
+ */
+ if (fvco < 2600 * MSEC_PER_SEC || fvco > 6600 * MSEC_PER_SEC)
+ continue;
+
+ /* 6MHz ≤ Fref(Fin / p) ≤ 30MHz */
+ min_prediv = DIV_ROUND_UP_ULL(fin, 30 * MSEC_PER_SEC);
+ max_prediv = DIV_ROUND_CLOSEST_ULL(fin, 6 * MSEC_PER_SEC);
+
+ for (_prediv = min_prediv; _prediv <= max_prediv; _prediv++) {
+ u64 delta, tmp;
+
+ _fbdiv = DIV_ROUND_CLOSEST_ULL(fvco * _prediv, 2 * fin);
+
+ /* 64 ≤ M[9:0] ≤ 1023 */
+ if (_fbdiv < 64 || _fbdiv > 1023)
+ continue;
+
+ /* -32767 ≤ K[15:0] ≤ 32767 */
+ _dsm = ((_prediv * fvco) - (2 * _fbdiv * fin));
+ _dsm = DIV_ROUND_UP_ULL(_dsm << 15, fin);
+ if (abs(_dsm) > 32767)
+ continue;
+
+ tmp = DIV_ROUND_CLOSEST_ULL((_fbdiv * fin * 2 * 1000), _prediv);
+ tmp += DIV_ROUND_CLOSEST_ULL((_dsm * fin * 1000), _prediv << 15);
+
+ delta = abs(fvco * MSEC_PER_SEC - tmp);
+ if (delta < min_delta) {
+ best_prediv = _prediv;
+ best_fbdiv = _fbdiv;
+ best_dsm = _dsm;
+ best_scaler = _scaler;
+ min_delta = delta;
+ best_freq = DIV_ROUND_CLOSEST_ULL(tmp, 1000) * MSEC_PER_SEC;
+ }
+ }
+ }
+
+ rate += 100 * MSEC_PER_SEC;
+ }
+
+ *prediv = best_prediv;
+ *fbdiv = best_fbdiv;
+ *dsm = (int)best_dsm & 0xffff;
+ *scaler = best_scaler;
+ dev_dbg(samsung->dev, "p: %d, m: %d, dsm:%ld, scaler: %d\n",
+ best_prediv, best_fbdiv, best_dsm, best_scaler);
+
+ return best_freq >> best_scaler;
+}
+
+static void
+samsung_mipi_dphy_clk_lane_timing_init(struct samsung_mipi_dcphy *samsung)
+{
+ const struct samsung_mipi_dphy_timing *timing;
+ unsigned int lane_hs_rate = div64_ul(samsung->pll.rate, USEC_PER_SEC);
+ u32 val, res_up, res_down;
+
+ timing = samsung_mipi_dphy_get_timing(samsung);
+ regmap_write(samsung->regmap, DPHY_MC_GNR_CON0, 0xf000);
+
+ /*
+ * The Drive-Strength / Voltage-Amplitude is adjusted by setting
+ * the Driver-Up Resistor and Driver-Down Resistor.
+ */
+ res_up = samsung->pdata->dphy_hs_drv_res_cfg->clk_hs_drv_up_ohm;
+ res_down = samsung->pdata->dphy_hs_drv_res_cfg->clk_hs_drv_down_ohm;
+ val = EDGE_CON(7) | EDGE_CON_DIR(0) | EDGE_CON_EN |
+ RES_UP(res_up) | RES_DN(res_down);
+ regmap_write(samsung->regmap, DPHY_MC_ANA_CON0, val);
+
+ if (lane_hs_rate >= 4500)
+ regmap_write(samsung->regmap, DPHY_MC_ANA_CON1, 0x0001);
+
+ val = 0;
+ /*
+ * Divide-by-2 Clock from Serial Clock. Use this when data rate is under
+ * 1500Mbps, otherwise divide-by-16 Clock from Serial Clock
+ */
+ if (lane_hs_rate < 1500)
+ val = HSTX_CLK_SEL;
+
+ val |= T_LPX(timing->lpx);
+ /* T_LP_EXIT_SKEW/T_LP_ENTRY_SKEW unconfig */
+ regmap_write(samsung->regmap, DPHY_MC_TIME_CON0, val);
+
+ val = T_CLK_ZERO(timing->clk_zero) | T_CLK_PREPARE(timing->clk_prepare);
+ regmap_write(samsung->regmap, DPHY_MC_TIME_CON1, val);
+
+ val = T_HS_EXIT(timing->hs_exit) | T_CLK_TRAIL(timing->clk_trail_eot);
+ regmap_write(samsung->regmap, DPHY_MC_TIME_CON2, val);
+
+ val = T_CLK_POST(timing->clk_post);
+ regmap_write(samsung->regmap, DPHY_MC_TIME_CON3, val);
+
+ /* Escape Clock is 20.00MHz */
+ regmap_write(samsung->regmap, DPHY_MC_TIME_CON4, 0x1f4);
+
+ /*
+ * skew calibration should be off, if the operation data rate is
+ * under 1.5Gbps or equal to 1.5Gbps.
+ */
+ if (lane_hs_rate > 1500)
+ regmap_write(samsung->regmap, DPHY_MC_DESKEW_CON0, 0x9cb1);
+}
+
+static void
+samsung_mipi_dphy_data_lane_timing_init(struct samsung_mipi_dcphy *samsung)
+{
+ const struct samsung_mipi_dphy_timing *timing;
+ unsigned int lane_hs_rate = div64_ul(samsung->pll.rate, USEC_PER_SEC);
+ u32 val, res_up, res_down;
+
+ timing = samsung_mipi_dphy_get_timing(samsung);
+
+ /*
+ * The Drive-Strength / Voltage-Amplitude is adjusted by adjusting the
+ * Driver-Up Resistor and Driver-Down Resistor.
+ */
+ res_up = samsung->pdata->dphy_hs_drv_res_cfg->data_hs_drv_up_ohm;
+ res_down = samsung->pdata->dphy_hs_drv_res_cfg->data_hs_drv_down_ohm;
+ val = EDGE_CON(7) | EDGE_CON_DIR(0) | EDGE_CON_EN |
+ RES_UP(res_up) | RES_DN(res_down);
+ regmap_write(samsung->regmap, COMBO_MD0_ANA_CON0, val);
+ regmap_write(samsung->regmap, COMBO_MD1_ANA_CON0, val);
+ regmap_write(samsung->regmap, COMBO_MD2_ANA_CON0, val);
+ regmap_write(samsung->regmap, DPHY_MD3_ANA_CON0, val);
+
+ if (lane_hs_rate >= 4500) {
+ regmap_write(samsung->regmap, COMBO_MD0_ANA_CON1, 0x0001);
+ regmap_write(samsung->regmap, COMBO_MD1_ANA_CON1, 0x0001);
+ regmap_write(samsung->regmap, COMBO_MD2_ANA_CON1, 0x0001);
+ regmap_write(samsung->regmap, DPHY_MD3_ANA_CON1, 0x0001);
+ }
+
+ val = 0;
+ /*
+ * Divide-by-2 Clock from Serial Clock. Use this when data rate is under
+ * 1500Mbps, otherwise divide-by-16 Clock from Serial Clock
+ */
+ if (lane_hs_rate < 1500)
+ val = HSTX_CLK_SEL;
+
+ val |= T_LPX(timing->lpx);
+ /* T_LP_EXIT_SKEW/T_LP_ENTRY_SKEW unconfig */
+ regmap_write(samsung->regmap, COMBO_MD0_TIME_CON0, val);
+ regmap_write(samsung->regmap, COMBO_MD1_TIME_CON0, val);
+ regmap_write(samsung->regmap, COMBO_MD2_TIME_CON0, val);
+ regmap_write(samsung->regmap, DPHY_MD3_TIME_CON0, val);
+
+ val = T_HS_ZERO(timing->hs_zero) | T_HS_PREPARE(timing->hs_prepare);
+ regmap_write(samsung->regmap, COMBO_MD0_TIME_CON1, val);
+ regmap_write(samsung->regmap, COMBO_MD1_TIME_CON1, val);
+ regmap_write(samsung->regmap, COMBO_MD2_TIME_CON1, val);
+ regmap_write(samsung->regmap, DPHY_MD3_TIME_CON1, val);
+
+ val = T_HS_EXIT(timing->hs_exit) | T_HS_TRAIL(timing->hs_trail_eot);
+ regmap_write(samsung->regmap, COMBO_MD0_TIME_CON2, val);
+ regmap_write(samsung->regmap, COMBO_MD1_TIME_CON2, val);
+ regmap_write(samsung->regmap, COMBO_MD2_TIME_CON2, val);
+ regmap_write(samsung->regmap, DPHY_MD3_TIME_CON2, val);
+
+ /* TTA-GET/TTA-GO Timing Counter register use default value */
+ val = T_TA_GET(0x3) | T_TA_GO(0x0);
+ regmap_write(samsung->regmap, COMBO_MD0_TIME_CON3, val);
+ regmap_write(samsung->regmap, COMBO_MD1_TIME_CON3, val);
+ regmap_write(samsung->regmap, COMBO_MD2_TIME_CON3, val);
+ regmap_write(samsung->regmap, DPHY_MD3_TIME_CON3, val);
+
+ /* Escape Clock is 20.00MHz */
+ regmap_write(samsung->regmap, COMBO_MD0_TIME_CON4, 0x1f4);
+ regmap_write(samsung->regmap, COMBO_MD1_TIME_CON4, 0x1f4);
+ regmap_write(samsung->regmap, COMBO_MD2_TIME_CON4, 0x1f4);
+ regmap_write(samsung->regmap, DPHY_MD3_TIME_CON4, 0x1f4);
+}
+
+static int samsung_mipi_dphy_power_on(struct samsung_mipi_dcphy *samsung)
+{
+ int ret;
+
+ reset_control_assert(samsung->m_phy_rst);
+
+ samsung_mipi_dcphy_bias_block_enable(samsung);
+ samsung_mipi_dcphy_pll_configure(samsung);
+ samsung_mipi_dphy_clk_lane_timing_init(samsung);
+ samsung_mipi_dphy_data_lane_timing_init(samsung);
+ ret = samsung_mipi_dcphy_pll_enable(samsung);
+ if (ret < 0)
+ return ret;
+
+ samsung_mipi_dphy_lane_enable(samsung);
+
+ reset_control_deassert(samsung->m_phy_rst);
+
+ /* The TSKEWCAL maximum is 100 µsec
+ * at initial calibration.
+ */
+ usleep_range(100, 110);
+
+ return 0;
+}
+
+static int samsung_mipi_dcphy_power_on(struct phy *phy)
+{
+ struct samsung_mipi_dcphy *samsung = phy_get_drvdata(phy);
+
+ reset_control_assert(samsung->apb_rst);
+ udelay(1);
+ reset_control_deassert(samsung->apb_rst);
+
+ switch (samsung->type) {
+ case PHY_TYPE_DPHY:
+ return samsung_mipi_dphy_power_on(samsung);
+ default:
+ /* CPHY part to be implemented later */
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int samsung_mipi_dcphy_power_off(struct phy *phy)
+{
+ struct samsung_mipi_dcphy *samsung = phy_get_drvdata(phy);
+
+ switch (samsung->type) {
+ case PHY_TYPE_DPHY:
+ samsung_mipi_dphy_lane_disable(samsung);
+ break;
+ default:
+ /* CPHY part to be implemented later */
+ return -EOPNOTSUPP;
+ }
+
+ samsung_mipi_dcphy_pll_disable(samsung);
+
+ return 0;
+}
+
+static int
+samsung_mipi_dcphy_pll_ssc_modulation_calc(struct samsung_mipi_dcphy *samsung,
+ u8 *mfr, u8 *mrr)
+{
+ unsigned long fin = div64_ul(clk_get_rate(samsung->ref_clk), MSEC_PER_SEC);
+ u16 prediv = samsung->pll.prediv;
+ u16 fbdiv = samsung->pll.fbdiv;
+ u16 min_mfr, max_mfr;
+ u16 _mfr, best_mfr = 0;
+ u16 mr, _mrr, best_mrr = 0;
+
+ /* 20KHz ≤ MF ≤ 150KHz */
+ max_mfr = DIV_ROUND_UP(fin, (20 * prediv) << 5);
+ min_mfr = div64_ul(fin, ((150 * prediv) << 5));
+ /*0 ≤ mfr ≤ 255 */
+ if (max_mfr > 256)
+ max_mfr = 256;
+
+ for (_mfr = min_mfr; _mfr < max_mfr; _mfr++) {
+ /* 1 ≤ mrr ≤ 31 */
+ for (_mrr = 1; _mrr < 32; _mrr++) {
+ mr = DIV_ROUND_UP(_mfr * _mrr * 100, fbdiv << 6);
+ /* 0 ≤ MR ≤ 5% */
+ if (mr > 5)
+ continue;
+
+ if (_mfr * _mrr < 513) {
+ best_mfr = _mfr;
+ best_mrr = _mrr;
+ break;
+ }
+ }
+ }
+
+ if (best_mrr) {
+ *mfr = best_mfr & 0xff;
+ *mrr = best_mrr & 0x3f;
+ } else {
+ dev_err(samsung->dev, "failed to calc ssc parameter mfr and mrr\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void
+samsung_mipi_dcphy_pll_calc_rate(struct samsung_mipi_dcphy *samsung,
+ unsigned long long rate)
+{
+ unsigned long prate = clk_get_rate(samsung->ref_clk);
+ unsigned long fout;
+ u8 scaler = 0, mfr = 0, mrr = 0;
+ u16 fbdiv = 0;
+ u8 prediv = 1;
+ int dsm = 0;
+ int ret;
+
+ fout = samsung_mipi_dcphy_pll_round_rate(samsung, prate, rate,
+ &prediv, &fbdiv, &dsm,
+ &scaler);
+
+ dev_dbg(samsung->dev, "%s: fin=%lu, req_rate=%llu\n",
+ __func__, prate, rate);
+ dev_dbg(samsung->dev, "%s: fout=%lu, prediv=%u, fbdiv=%u\n",
+ __func__, fout, prediv, fbdiv);
+
+ samsung->pll.prediv = prediv;
+ samsung->pll.fbdiv = fbdiv;
+ samsung->pll.dsm = dsm;
+ samsung->pll.scaler = scaler;
+ samsung->pll.rate = fout;
+
+ /*
+ * All DPHY 2.0 compliant Transmitters shall support SSC operating above
+ * 2.5 Gbps
+ */
+ if (fout > 2500000000LL) {
+ ret = samsung_mipi_dcphy_pll_ssc_modulation_calc(samsung,
+ &mfr, &mrr);
+ if (!ret) {
+ samsung->pll.ssc_en = true;
+ samsung->pll.mfr = mfr;
+ samsung->pll.mrr = mrr;
+ }
+ }
+}
+
+static int samsung_mipi_dcphy_configure(struct phy *phy,
+ union phy_configure_opts *opts)
+{
+ struct samsung_mipi_dcphy *samsung = phy_get_drvdata(phy);
+ unsigned long long target_rate = opts->mipi_dphy.hs_clk_rate;
+
+ samsung->lanes = opts->mipi_dphy.lanes > 4 ? 4 : opts->mipi_dphy.lanes;
+
+ samsung_mipi_dcphy_pll_calc_rate(samsung, target_rate);
+ opts->mipi_dphy.hs_clk_rate = samsung->pll.rate;
+
+ return 0;
+}
+
+static int samsung_mipi_dcphy_init(struct phy *phy)
+{
+ struct samsung_mipi_dcphy *samsung = phy_get_drvdata(phy);
+
+ return pm_runtime_resume_and_get(samsung->dev);
+}
+
+static int samsung_mipi_dcphy_exit(struct phy *phy)
+{
+ struct samsung_mipi_dcphy *samsung = phy_get_drvdata(phy);
+
+ pm_runtime_put(samsung->dev);
+
+ return 0;
+}
+
+static const struct phy_ops samsung_mipi_dcphy_ops = {
+ .configure = samsung_mipi_dcphy_configure,
+ .power_on = samsung_mipi_dcphy_power_on,
+ .power_off = samsung_mipi_dcphy_power_off,
+ .init = samsung_mipi_dcphy_init,
+ .exit = samsung_mipi_dcphy_exit,
+ .owner = THIS_MODULE,
+};
+
+static const struct regmap_config samsung_mipi_dcphy_regmap_config = {
+ .name = "dcphy",
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = 0xfffc,
+};
+
+static struct phy *samsung_mipi_dcphy_xlate(struct device *dev,
+ const struct of_phandle_args *args)
+{
+ struct samsung_mipi_dcphy *samsung = dev_get_drvdata(dev);
+
+ if (args->args_count != 1) {
+ dev_err(dev, "invalid number of arguments\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (samsung->type != PHY_NONE && samsung->type != args->args[0])
+ dev_warn(dev, "phy type select %d overwriting type %d\n",
+ args->args[0], samsung->type);
+
+ samsung->type = args->args[0];
+
+ return samsung->phy;
+}
+
+static int samsung_mipi_dcphy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct samsung_mipi_dcphy *samsung;
+ struct phy_provider *phy_provider;
+ struct resource *res;
+ void __iomem *regs;
+ int ret;
+
+ samsung = devm_kzalloc(dev, sizeof(*samsung), GFP_KERNEL);
+ if (!samsung)
+ return -ENOMEM;
+
+ samsung->dev = dev;
+ samsung->pdata = device_get_match_data(dev);
+ platform_set_drvdata(pdev, samsung);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ samsung->regmap = devm_regmap_init_mmio(dev, regs,
+ &samsung_mipi_dcphy_regmap_config);
+ if (IS_ERR(samsung->regmap))
+ return dev_err_probe(dev, PTR_ERR(samsung->regmap), "Failed to init regmap\n");
+
+ samsung->grf_regmap = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
+ if (IS_ERR(samsung->grf_regmap))
+ return dev_err_probe(dev, PTR_ERR(samsung->grf_regmap),
+ "Unable to get rockchip,grf\n");
+
+ samsung->ref_clk = devm_clk_get(dev, "ref");
+ if (IS_ERR(samsung->ref_clk))
+ return dev_err_probe(dev, PTR_ERR(samsung->ref_clk),
+ "Failed to get reference clock\n");
+
+ samsung->pclk = devm_clk_get(dev, "pclk");
+ if (IS_ERR(samsung->pclk))
+ return dev_err_probe(dev, PTR_ERR(samsung->pclk), "Failed to get pclk\n");
+
+ samsung->m_phy_rst = devm_reset_control_get(dev, "m_phy");
+ if (IS_ERR(samsung->m_phy_rst))
+ return dev_err_probe(dev, PTR_ERR(samsung->m_phy_rst),
+ "Failed to get system m_phy_rst control\n");
+
+ samsung->s_phy_rst = devm_reset_control_get(dev, "s_phy");
+ if (IS_ERR(samsung->s_phy_rst))
+ return dev_err_probe(dev, PTR_ERR(samsung->s_phy_rst),
+ "Failed to get system s_phy_rst control\n");
+
+ samsung->apb_rst = devm_reset_control_get(dev, "apb");
+ if (IS_ERR(samsung->apb_rst))
+ return dev_err_probe(dev, PTR_ERR(samsung->apb_rst),
+ "Failed to get system apb_rst control\n");
+
+ samsung->grf_apb_rst = devm_reset_control_get(dev, "grf");
+ if (IS_ERR(samsung->grf_apb_rst))
+ return dev_err_probe(dev, PTR_ERR(samsung->grf_apb_rst),
+ "Failed to get system grf_apb_rst control\n");
+
+ samsung->phy = devm_phy_create(dev, NULL, &samsung_mipi_dcphy_ops);
+ if (IS_ERR(samsung->phy))
+ return dev_err_probe(dev, PTR_ERR(samsung->phy), "Failed to create MIPI DC-PHY\n");
+
+ phy_set_drvdata(samsung->phy, samsung);
+
+ ret = devm_pm_runtime_enable(dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to enable runtime PM\n");
+
+ phy_provider = devm_of_phy_provider_register(dev, samsung_mipi_dcphy_xlate);
+ if (IS_ERR(phy_provider))
+ return dev_err_probe(dev, PTR_ERR(phy_provider),
+ "Failed to register phy provider\n");
+
+ return 0;
+}
+
+static __maybe_unused int samsung_mipi_dcphy_runtime_suspend(struct device *dev)
+{
+ struct samsung_mipi_dcphy *samsung = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(samsung->ref_clk);
+ clk_disable_unprepare(samsung->pclk);
+
+ return 0;
+}
+
+static __maybe_unused int samsung_mipi_dcphy_runtime_resume(struct device *dev)
+{
+ struct samsung_mipi_dcphy *samsung = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(samsung->pclk);
+ if (ret) {
+ dev_err(samsung->dev, "Failed to enable pclk, %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(samsung->ref_clk);
+ if (ret) {
+ dev_err(samsung->dev, "Failed to enable reference clock, %d\n", ret);
+ clk_disable_unprepare(samsung->pclk);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops samsung_mipi_dcphy_pm_ops = {
+ SET_RUNTIME_PM_OPS(samsung_mipi_dcphy_runtime_suspend,
+ samsung_mipi_dcphy_runtime_resume, NULL)
+};
+
+static const struct hs_drv_res_cfg rk3576_dphy_hs_drv_res_cfg = {
+ .clk_hs_drv_up_ohm = STRENGTH_52_OHM,
+ .clk_hs_drv_down_ohm = STRENGTH_52_OHM,
+ .data_hs_drv_up_ohm = STRENGTH_39_OHM,
+ .data_hs_drv_down_ohm = STRENGTH_39_OHM,
+};
+
+static const struct hs_drv_res_cfg rk3588_dphy_hs_drv_res_cfg = {
+ .clk_hs_drv_up_ohm = STRENGTH_34_OHM,
+ .clk_hs_drv_down_ohm = STRENGTH_34_OHM,
+ .data_hs_drv_up_ohm = STRENGTH_43_OHM,
+ .data_hs_drv_down_ohm = STRENGTH_43_OHM,
+};
+
+static const struct samsung_mipi_dcphy_plat_data rk3576_samsung_mipi_dcphy_plat_data = {
+ .dphy_hs_drv_res_cfg = &rk3576_dphy_hs_drv_res_cfg,
+ .dphy_tx_max_lane_kbps = 2500000L,
+};
+
+static const struct samsung_mipi_dcphy_plat_data rk3588_samsung_mipi_dcphy_plat_data = {
+ .dphy_hs_drv_res_cfg = &rk3588_dphy_hs_drv_res_cfg,
+ .dphy_tx_max_lane_kbps = 4500000L,
+};
+
+static const struct of_device_id samsung_mipi_dcphy_of_match[] = {
+ {
+ .compatible = "rockchip,rk3576-mipi-dcphy",
+ .data = &rk3576_samsung_mipi_dcphy_plat_data,
+ }, {
+ .compatible = "rockchip,rk3588-mipi-dcphy",
+ .data = &rk3588_samsung_mipi_dcphy_plat_data,
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, samsung_mipi_dcphy_of_match);
+
+static struct platform_driver samsung_mipi_dcphy_driver = {
+ .driver = {
+ .name = "samsung-mipi-dcphy",
+ .of_match_table = samsung_mipi_dcphy_of_match,
+ .pm = &samsung_mipi_dcphy_pm_ops,
+ },
+ .probe = samsung_mipi_dcphy_probe,
+};
+module_platform_driver(samsung_mipi_dcphy_driver);
+
+MODULE_AUTHOR("Guochun Huang <hero.huang@rock-chips.com>");
+MODULE_DESCRIPTION("Samsung MIPI DCPHY Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
new file mode 100644
index 000000000..24ed0d7eb
--- /dev/null
+++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
@@ -0,0 +1,2573 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2021-2022 Rockchip Electronics Co., Ltd.
+ * Copyright (c) 2024-2026 Collabora Ltd.
+ *
+ * Author: Algea Cao <algea.cao@rock-chips.com>
+ * Author: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
+ */
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/hw_bitfield.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/rational.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+
+#define GRF_HDPTX_CON0 0x00
+#define LC_REF_CLK_SEL BIT(11)
+#define HDPTX_I_PLL_EN BIT(7)
+#define HDPTX_I_BIAS_EN BIT(6)
+#define HDPTX_I_BGR_EN BIT(5)
+#define HDPTX_MODE_SEL BIT(0)
+#define GRF_HDPTX_STATUS 0x80
+#define HDPTX_O_PLL_LOCK_DONE BIT(3)
+#define HDPTX_O_PHY_CLK_RDY BIT(2)
+#define HDPTX_O_PHY_RDY BIT(1)
+#define HDPTX_O_SB_RDY BIT(0)
+
+#define HDPTX_REG(_n, _min, _max) \
+ ( \
+ BUILD_BUG_ON_ZERO((0x##_n) < (0x##_min)) + \
+ BUILD_BUG_ON_ZERO((0x##_n) > (0x##_max)) + \
+ ((0x##_n) * 4) \
+ )
+
+#define CMN_REG(n) HDPTX_REG(n, 0000, 00a7)
+#define SB_REG(n) HDPTX_REG(n, 0100, 0129)
+#define LNTOP_REG(n) HDPTX_REG(n, 0200, 0229)
+#define LANE_REG(n) HDPTX_REG(n, 0300, 062d)
+
+/* CMN_REG(0008) */
+#define OVRD_LCPLL_EN_MASK BIT(7)
+#define LCPLL_EN_MASK BIT(6)
+#define LCPLL_LCVCO_MODE_EN_MASK BIT(4)
+/* CMN_REG(001e) */
+#define LCPLL_PI_EN_MASK BIT(5)
+#define LCPLL_100M_CLK_EN_MASK BIT(0)
+/* CMN_REG(0022) */
+#define ANA_LCPLL_PMS_PDIV_MASK GENMASK(7, 4)
+#define ANA_LCPLL_PMS_REFDIV_MASK GENMASK(3, 0)
+/* CMN_REG(0023) */
+#define LCPLL_PMS_SDIV_RBR_MASK GENMASK(7, 4)
+#define LCPLL_PMS_SDIV_HBR_MASK GENMASK(3, 0)
+/* CMN_REG(0025) */
+#define LCPLL_PMS_IQDIV_RSTN_MASK BIT(4)
+/* CMN_REG(0028) */
+#define LCPLL_SDC_FRAC_EN_MASK BIT(2)
+#define LCPLL_SDC_FRAC_RSTN_MASK BIT(0)
+/* CMN_REG(002d) */
+#define LCPLL_SDC_N_MASK GENMASK(3, 1)
+/* CMN_REG(002e) */
+#define LCPLL_SDC_NUMBERATOR_MASK GENMASK(5, 0)
+/* CMN_REG(002f) */
+#define LCPLL_SDC_DENOMINATOR_MASK GENMASK(7, 2)
+#define LCPLL_SDC_NDIV_RSTN_MASK BIT(0)
+/* CMN_REG(003c) */
+#define ANA_LCPLL_RESERVED7_MASK BIT(7)
+/* CMN_REG(003d) */
+#define OVRD_ROPLL_EN_MASK BIT(7)
+#define ROPLL_EN_MASK BIT(6)
+#define ROPLL_LCVCO_EN_MASK BIT(4)
+/* CMN_REG(0046) */
+#define ROPLL_ANA_CPP_CTRL_COARSE_MASK GENMASK(7, 4)
+#define ROPLL_ANA_CPP_CTRL_FINE_MASK GENMASK(3, 0)
+/* CMN_REG(0047) */
+#define ROPLL_ANA_LPF_C_SEL_COARSE_MASK GENMASK(5, 3)
+#define ROPLL_ANA_LPF_C_SEL_FINE_MASK GENMASK(2, 0)
+/* CMN_REG(004e) */
+#define ROPLL_PI_EN_MASK BIT(5)
+/* CMN_REG(0051) */
+#define ROPLL_PMS_MDIV_MASK GENMASK(7, 0)
+/* CMN_REG(0055) */
+#define ROPLL_PMS_MDIV_AFC_MASK GENMASK(7, 0)
+/* CMN_REG(0059) */
+#define ANA_ROPLL_PMS_PDIV_MASK GENMASK(7, 4)
+#define ANA_ROPLL_PMS_REFDIV_MASK GENMASK(3, 0)
+/* CMN_REG(005a) */
+#define ROPLL_PMS_SDIV_RBR_MASK GENMASK(7, 4)
+#define ROPLL_PMS_SDIV_HBR_MASK GENMASK(3, 0)
+/* CMN_REG(005b) */
+#define ROPLL_PMS_SDIV_HBR2_MASK GENMASK(7, 4)
+/* CMN_REG(005c) */
+#define ROPLL_PMS_IQDIV_RSTN_MASK BIT(5)
+/* CMN_REG(005e) */
+#define ROPLL_SDM_EN_MASK BIT(6)
+#define OVRD_ROPLL_SDM_RSTN_MASK BIT(5)
+#define ROPLL_SDM_RSTN_MASK BIT(4)
+#define ROPLL_SDC_FRAC_EN_RBR_MASK BIT(3)
+#define ROPLL_SDC_FRAC_EN_HBR_MASK BIT(2)
+#define ROPLL_SDC_FRAC_EN_HBR2_MASK BIT(1)
+#define ROPLL_SDM_FRAC_EN_HBR3_MASK BIT(0)
+/* CMN_REG(005f) */
+#define OVRD_ROPLL_SDC_RSTN_MASK BIT(5)
+#define ROPLL_SDC_RSTN_MASK BIT(4)
+/* CMN_REG(0060) */
+#define ROPLL_SDM_DENOMINATOR_MASK GENMASK(7, 0)
+/* CMN_REG(0064) */
+#define ROPLL_SDM_NUM_SIGN_RBR_MASK BIT(3)
+#define ROPLL_SDM_NUM_SIGN_HBR_MASK BIT(2)
+#define ROPLL_SDM_NUM_SIGN_HBR2_MASK BIT(1)
+/* CMN_REG(0065) */
+#define ROPLL_SDM_NUM_MASK GENMASK(7, 0)
+/* CMN_REG(0069) */
+#define ROPLL_SDC_N_RBR_MASK GENMASK(2, 0)
+/* CMN_REG(006a) */
+#define ROPLL_SDC_N_HBR_MASK GENMASK(5, 3)
+#define ROPLL_SDC_N_HBR2_MASK GENMASK(2, 0)
+/* CMN_REG(006b) */
+#define ROPLL_SDC_N_HBR3_MASK GENMASK(3, 1)
+/* CMN_REG(006c) */
+#define ROPLL_SDC_NUM_MASK GENMASK(5, 0)
+/* cmn_reg0070 */
+#define ROPLL_SDC_DENO_MASK GENMASK(5, 0)
+/* CMN_REG(0074) */
+#define OVRD_ROPLL_SDC_NDIV_RSTN_MASK BIT(3)
+#define ROPLL_SDC_NDIV_RSTN_MASK BIT(2)
+#define OVRD_ROPLL_SSC_EN_MASK BIT(1)
+#define ROPLL_SSC_EN_MASK BIT(0)
+/* CMN_REG(0075) */
+#define ANA_ROPLL_SSC_FM_DEVIATION_MASK GENMASK(5, 0)
+/* CMN_REG(0076) */
+#define ANA_ROPLL_SSC_FM_FREQ_MASK GENMASK(6, 2)
+/* CMN_REG(0077) */
+#define ANA_ROPLL_SSC_CLK_DIV_SEL_MASK GENMASK(6, 3)
+/* CMN_REG(0081) */
+#define OVRD_PLL_CD_CLK_EN_MASK BIT(8)
+#define ANA_PLL_CD_TX_SER_RATE_SEL_MASK BIT(3)
+#define ANA_PLL_CD_HSCLK_WEST_EN_MASK BIT(1)
+#define ANA_PLL_CD_HSCLK_EAST_EN_MASK BIT(0)
+/* CMN_REG(0082) */
+#define ANA_PLL_CD_VREG_GAIN_CTRL_MASK GENMASK(3, 0)
+/* CMN_REG(0083) */
+#define ANA_PLL_CD_VREG_ICTRL_MASK GENMASK(6, 5)
+/* CMN_REG(0084) */
+#define PLL_LCRO_CLK_SEL_MASK BIT(5)
+/* CMN_REG(0085) */
+#define ANA_PLL_SYNC_LOSS_DET_MODE_MASK GENMASK(1, 0)
+/* CMN_REG(0086) */
+#define PLL_PCG_POSTDIV_SEL_MASK GENMASK(7, 4)
+#define PLL_PCG_CLK_SEL_MASK GENMASK(3, 1)
+#define PLL_PCG_CLK_EN_MASK BIT(0)
+/* CMN_REG(0087) */
+#define ANA_PLL_FRL_MODE_EN_MASK BIT(3)
+#define ANA_PLL_TX_HS_CLK_EN_MASK BIT(2)
+/* CMN_REG(0089) */
+#define LCPLL_ALONE_MODE_MASK BIT(1)
+/* CMN_REG(0095) */
+#define DP_TX_LINK_BW_MASK GENMASK(1, 0)
+/* CMN_REG(0097) */
+#define DIG_CLK_SEL_MASK BIT(1)
+#define LCPLL_REF BIT(1)
+#define ROPLL_REF 0
+/* CMN_REG(0099) */
+#define SSC_EN_MASK GENMASK(7, 6)
+#define CMN_ROPLL_ALONE_MODE_MASK BIT(2)
+#define ROPLL_ALONE_MODE BIT(2)
+/* CMN_REG(009a) */
+#define HS_SPEED_SEL_MASK BIT(0)
+#define DIV_10_CLOCK BIT(0)
+/* CMN_REG(009b) */
+#define LS_SPEED_SEL_MASK BIT(4)
+#define LINK_SYMBOL_CLOCK BIT(4)
+#define LINK_SYMBOL_CLOCK1_2 0
+
+/* SB_REG(0102) */
+#define OVRD_SB_RXTERM_EN_MASK BIT(5)
+#define SB_RXTERM_EN_MASK BIT(4)
+#define ANA_SB_RXTERM_OFFSP_MASK GENMASK(3, 0)
+/* SB_REG(0103) */
+#define ANA_SB_RXTERM_OFFSN_MASK GENMASK(6, 3)
+#define OVRD_SB_RX_RESCAL_DONE_MASK BIT(1)
+#define SB_RX_RESCAL_DONE_MASK BIT(0)
+/* SB_REG(0104) */
+#define OVRD_SB_EN_MASK BIT(5)
+#define SB_EN_MASK BIT(4)
+#define OVRD_SB_AUX_EN_MASK BIT(1)
+#define SB_AUX_EN_MASK BIT(0)
+/* SB_REG(0105) */
+#define OVRD_SB_EARC_CMDC_EN_MASK BIT(6)
+#define SB_EARC_CMDC_EN_MASK BIT(5)
+#define ANA_SB_TX_HLVL_PROG_MASK GENMASK(2, 0)
+/* SB_REG(0106) */
+#define ANA_SB_TX_LLVL_PROG_MASK GENMASK(6, 4)
+/* SB_REG(0109) */
+#define ANA_SB_DMRX_AFC_DIV_RATIO_MASK GENMASK(2, 0)
+/* SB_REG(010d) */
+#define ANA_SB_DMRX_LPBK_DATA_MASK BIT(4)
+/* SB_REG(010f) */
+#define OVRD_SB_VREG_EN_MASK BIT(7)
+#define SB_VREG_EN_MASK BIT(6)
+#define OVRD_SB_VREG_LPF_BYPASS_MASK BIT(5)
+#define SB_VREG_LPF_BYPASS_MASK BIT(4)
+#define ANA_SB_VREG_GAIN_CTRL_MASK GENMASK(3, 0)
+/* SB_REG(0110) */
+#define ANA_SB_VREG_OUT_SEL_MASK BIT(1)
+#define ANA_SB_VREG_REF_SEL_MASK BIT(0)
+/* SB_REG(0113) */
+#define SB_RX_RCAL_OPT_CODE_MASK GENMASK(5, 4)
+#define SB_RX_RTERM_CTRL_MASK GENMASK(3, 0)
+/* SB_REG(0114) */
+#define SB_TG_SB_EN_DELAY_TIME_MASK GENMASK(5, 3)
+#define SB_TG_RXTERM_EN_DELAY_TIME_MASK GENMASK(2, 0)
+/* SB_REG(0115) */
+#define SB_READY_DELAY_TIME_MASK GENMASK(5, 3)
+#define SB_TG_OSC_EN_DELAY_TIME_MASK GENMASK(2, 0)
+/* SB_REG(0116) */
+#define AFC_RSTN_DELAY_TIME_MASK GENMASK(6, 4)
+/* SB_REG(0117) */
+#define FAST_PULSE_TIME_MASK GENMASK(3, 0)
+/* SB_REG(0118) */
+#define SB_TG_EARC_DMRX_RECVRD_CLK_CNT_MASK GENMASK(7, 0)
+/* SB_REG(011a) */
+#define SB_TG_CNT_RUN_NO_7_0_MASK GENMASK(7, 0)
+/* SB_REG(011b) */
+#define SB_EARC_SIG_DET_BYPASS_MASK BIT(4)
+#define SB_AFC_TOL_MASK GENMASK(3, 0)
+/* SB_REG(011c) */
+#define SB_AFC_STB_NUM_MASK GENMASK(3, 0)
+/* SB_REG(011d) */
+#define SB_TG_OSC_CNT_MIN_MASK GENMASK(7, 0)
+/* SB_REG(011e) */
+#define SB_TG_OSC_CNT_MAX_MASK GENMASK(7, 0)
+/* SB_REG(011f) */
+#define SB_PWM_AFC_CTRL_MASK GENMASK(7, 2)
+#define SB_RCAL_RSTN_MASK BIT(1)
+/* SB_REG(0120) */
+#define SB_AUX_EN_IN_MASK BIT(7)
+#define SB_EARC_EN_MASK BIT(1)
+#define SB_EARC_AFC_EN_MASK BIT(2)
+/* SB_REG(0123) */
+#define OVRD_SB_READY_MASK BIT(5)
+#define SB_READY_MASK BIT(4)
+
+/* LNTOP_REG(0200) */
+#define PROTOCOL_SEL_MASK BIT(2)
+#define HDMI_MODE BIT(2)
+#define HDMI_TMDS_FRL_SEL BIT(1)
+/* LNTOP_REG(0206) */
+#define DATA_BUS_WIDTH_MASK GENMASK(2, 1)
+#define DATA_BUS_WIDTH_SEL_MASK BIT(0)
+#define DATA_BUS_36_40 BIT(0)
+/* LNTOP_REG(0207) */
+#define LANE_EN_MASK 0xf
+#define ALL_LANE_EN 0xf
+
+/* LANE_REG(0301) */
+#define OVRD_LN_TX_DRV_EI_EN_MASK BIT(7)
+#define LN_TX_DRV_EI_EN_MASK BIT(6)
+/* LANE_REG(0303) */
+#define OVRD_LN_TX_DRV_LVL_CTRL_MASK BIT(5)
+#define LN_TX_DRV_LVL_CTRL_MASK GENMASK(4, 0)
+/* LANE_REG(0304) */
+#define OVRD_LN_TX_DRV_POST_LVL_CTRL_MASK BIT(4)
+#define LN_TX_DRV_POST_LVL_CTRL_MASK GENMASK(3, 0)
+/* LANE_REG(0305) */
+#define OVRD_LN_TX_DRV_PRE_LVL_CTRL_MASK BIT(6)
+#define LN_TX_DRV_PRE_LVL_CTRL_MASK GENMASK(5, 2)
+/* LANE_REG(0306) */
+#define LN_ANA_TX_DRV_IDRV_IDN_CTRL_MASK GENMASK(7, 5)
+#define LN_ANA_TX_DRV_IDRV_IUP_CTRL_MASK GENMASK(4, 2)
+#define LN_ANA_TX_DRV_ACCDRV_EN_MASK BIT(0)
+/* LANE_REG(0307) */
+#define LN_ANA_TX_DRV_ACCDRV_POL_SEL_MASK BIT(6)
+#define LN_ANA_TX_DRV_ACCDRV_CTRL_MASK GENMASK(5, 3)
+/* LANE_REG(030a) */
+#define LN_ANA_TX_JEQ_EN_MASK BIT(4)
+#define LN_TX_JEQ_EVEN_CTRL_RBR_MASK GENMASK(3, 0)
+/* LANE_REG(030b) */
+#define LN_TX_JEQ_EVEN_CTRL_HBR_MASK GENMASK(7, 4)
+#define LN_TX_JEQ_EVEN_CTRL_HBR2_MASK GENMASK(3, 0)
+/* LANE_REG(030c) */
+#define LN_TX_JEQ_ODD_CTRL_RBR_MASK GENMASK(3, 0)
+/* LANE_REG(030d) */
+#define LN_TX_JEQ_ODD_CTRL_HBR_MASK GENMASK(7, 4)
+#define LN_TX_JEQ_ODD_CTRL_HBR2_MASK GENMASK(3, 0)
+/* LANE_REG(0310) */
+#define LN_ANA_TX_SYNC_LOSS_DET_MODE_MASK GENMASK(1, 0)
+/* LANE_REG(0311) */
+#define LN_TX_SER_40BIT_EN_RBR_MASK BIT(3)
+#define LN_TX_SER_40BIT_EN_HBR_MASK BIT(2)
+#define LN_TX_SER_40BIT_EN_HBR2_MASK BIT(1)
+/* LANE_REG(0312) */
+#define LN0_TX_SER_RATE_SEL_RBR_MASK BIT(5)
+#define LN0_TX_SER_RATE_SEL_HBR_MASK BIT(4)
+#define LN0_TX_SER_RATE_SEL_HBR2_MASK BIT(3)
+#define LN0_TX_SER_RATE_SEL_HBR3_MASK BIT(2)
+/* LANE_REG(0316) */
+#define LN_ANA_TX_SER_VREG_GAIN_CTRL_MASK GENMASK(3, 0)
+/* LANE_REG(031B) */
+#define LN_ANA_TX_RESERVED_MASK GENMASK(7, 0)
+/* LANE_REG(031e) */
+#define LN_POLARITY_INV_MASK BIT(2)
+#define LN_LANE_MODE_MASK BIT(1)
+
+/* LANE_REG(0412) */
+#define LN1_TX_SER_RATE_SEL_RBR_MASK BIT(5)
+#define LN1_TX_SER_RATE_SEL_HBR_MASK BIT(4)
+#define LN1_TX_SER_RATE_SEL_HBR2_MASK BIT(3)
+#define LN1_TX_SER_RATE_SEL_HBR3_MASK BIT(2)
+
+/* LANE_REG(0512) */
+#define LN2_TX_SER_RATE_SEL_RBR_MASK BIT(5)
+#define LN2_TX_SER_RATE_SEL_HBR_MASK BIT(4)
+#define LN2_TX_SER_RATE_SEL_HBR2_MASK BIT(3)
+#define LN2_TX_SER_RATE_SEL_HBR3_MASK BIT(2)
+
+/* LANE_REG(0612) */
+#define LN3_TX_SER_RATE_SEL_RBR_MASK BIT(5)
+#define LN3_TX_SER_RATE_SEL_HBR_MASK BIT(4)
+#define LN3_TX_SER_RATE_SEL_HBR2_MASK BIT(3)
+#define LN3_TX_SER_RATE_SEL_HBR3_MASK BIT(2)
+
+#define HDMI14_MAX_RATE 340000000
+#define HDMI20_MAX_RATE 600000000
+#define FRL_3G3L_RATE 900000000
+#define FRL_6G3L_RATE 1800000000
+#define FRL_8G4L_RATE 3200000000
+
+enum dp_link_rate {
+ DP_BW_RBR,
+ DP_BW_HBR,
+ DP_BW_HBR2,
+};
+
+struct lcpll_config {
+ unsigned long long rate;
+ u8 lcvco_mode_en;
+ u8 pi_en;
+ u8 clk_en_100m;
+ u8 pms_mdiv;
+ u8 pms_mdiv_afc;
+ u8 pms_pdiv;
+ u8 pms_refdiv;
+ u8 pms_sdiv;
+ u8 sdm_deno;
+ u8 sdm_num_sign;
+ u8 sdm_num;
+ u8 sdc_n;
+};
+
+struct ropll_config {
+ unsigned long long rate;
+ u8 pms_mdiv;
+ u8 pms_mdiv_afc;
+ u8 pms_pdiv;
+ u8 pms_refdiv;
+ u8 pms_sdiv;
+ u8 sdm_en;
+ u8 sdm_deno;
+ u8 sdm_num_sign;
+ u8 sdm_num;
+ u8 sdc_n;
+ u8 sdc_num;
+ u8 sdc_deno;
+};
+
+struct tx_drv_ctrl {
+ u8 tx_drv_lvl_ctrl;
+ u8 tx_drv_post_lvl_ctrl;
+ u8 ana_tx_drv_idrv_idn_ctrl;
+ u8 ana_tx_drv_idrv_iup_ctrl;
+ u8 ana_tx_drv_accdrv_en;
+ u8 ana_tx_drv_accdrv_ctrl;
+ u8 tx_drv_pre_lvl_ctrl;
+ u8 ana_tx_jeq_en;
+ u8 tx_jeq_even_ctrl;
+ u8 tx_jeq_odd_ctrl;
+};
+
+enum rk_hdptx_reset {
+ RST_APB = 0,
+ RST_INIT,
+ RST_CMN,
+ RST_LANE,
+ RST_MAX
+};
+
+#define MAX_HDPTX_PHY_NUM 2
+
+struct rk_hdptx_phy_cfg {
+ unsigned int num_phys;
+ unsigned int phy_ids[MAX_HDPTX_PHY_NUM];
+};
+
+struct rk_hdptx_hdmi_cfg {
+ enum phy_hdmi_mode mode;
+ unsigned long long rate;
+ unsigned int bpc;
+};
+
+struct rk_hdptx_phy {
+ struct device *dev;
+ struct regmap *regmap;
+ struct regmap *grf;
+
+ int phy_id;
+ struct phy *phy;
+ struct rk_hdptx_hdmi_cfg hdmi_cfg;
+ struct clk_bulk_data *clks;
+ int nr_clks;
+ struct reset_control_bulk_data rsts[RST_MAX];
+
+ /* clk provider */
+ struct clk_hw hw;
+ bool pll_config_dirty;
+
+ atomic_t usage_count;
+
+ /* used for dp mode */
+ unsigned int link_rate;
+ unsigned int lanes;
+};
+
+static const struct lcpll_config rk_hdptx_frl_lcpll_cfg[] = {
+ /* | pms | sdm | */
+ /* rate, lcen, pien, cken, mdiv, mdafc, pdiv, rdiv, sdiv, deno, nsig, num, sdcn, */
+ { 4800000000ULL, 1, 0, 0, 125, 125, 1, 1, 0, 1, 0, 0, 2, },
+ { 4000000000ULL, 1, 1, 0, 104, 104, 1, 1, 0, 9, 0, 1, 1, },
+ { 2400000000ULL, 1, 0, 0, 125, 125, 1, 1, 1, 1, 0, 0, 2, },
+ { 1800000000ULL, 1, 0, 0, 125, 125, 1, 1, 1, 1, 0, 0, 2, },
+ { 900000000ULL, 1, 0, 0, 125, 125, 1, 1, 3, 1, 0, 0, 2, },
+};
+
+static const struct ropll_config rk_hdptx_tmds_ropll_cfg[] = {
+ /* | pms | sdm | sdc | */
+ /* rate, mdiv, mdafc, pdiv, rdiv, sdiv, en, deno, nsig, num, n, num, deno, */
+ { 594000000ULL, 124, 124, 1, 1, 0, 1, 62, 1, 16, 5, 0, 1, },
+ { 461101250ULL, 97, 97, 1, 1, 0, 1, 71, 1, 53, 2, 6, 35, },
+ { 371250000ULL, 155, 155, 1, 1, 1, 1, 62, 1, 16, 5, 0, 1, },
+ { 297000000ULL, 124, 124, 1, 1, 1, 1, 62, 1, 16, 5, 0, 1, },
+ { 185625000ULL, 155, 155, 1, 1, 3, 1, 62, 1, 16, 5, 0, 1, },
+ { 162000000ULL, 135, 135, 1, 1, 3, 0, 4, 0, 3, 5, 5, 16, },
+ { 154000000ULL, 193, 193, 1, 1, 5, 1, 193, 1, 32, 2, 1, 1, },
+ { 148500000ULL, 123, 123, 1, 1, 3, 1, 4, 0, 3, 5, 5, 16, },
+ { 146250000ULL, 122, 122, 1, 1, 3, 1, 244, 1, 16, 2, 1, 1, },
+ { 119000000ULL, 149, 149, 1, 1, 5, 1, 149, 1, 16, 2, 1, 1, },
+ { 108000000ULL, 135, 135, 1, 1, 5, 0, 9, 0, 5, 0, 20, 24, },
+ { 106500000ULL, 89, 89, 1, 1, 3, 1, 89, 1, 16, 1, 0, 1, },
+ { 92812500ULL, 155, 155, 1, 1, 7, 1, 62, 1, 16, 5, 0, 1, },
+ { 85500000ULL, 214, 214, 1, 1, 11, 1, 214, 1, 16, 2, 1, 1, },
+ { 83500000ULL, 105, 105, 1, 1, 5, 1, 42, 1, 16, 1, 0, 1, },
+ { 74250000ULL, 124, 124, 1, 1, 7, 1, 62, 1, 16, 5, 0, 1, },
+ { 65000000ULL, 162, 162, 1, 1, 11, 1, 54, 0, 16, 4, 1, 1, },
+ { 50250000ULL, 84, 84, 1, 1, 7, 1, 11, 1, 4, 5, 4, 11, },
+ { 40000000ULL, 100, 100, 1, 1, 11, 0, 9, 0, 5, 0, 20, 24, },
+ { 33750000ULL, 112, 112, 1, 1, 15, 1, 2, 0, 1, 5, 1, 1, },
+ { 27000000ULL, 90, 90, 1, 1, 15, 0, 9, 0, 5, 0, 20, 24, },
+ { 25175000ULL, 84, 84, 1, 1, 15, 1, 168, 1, 16, 4, 1, 1, },
+};
+
+static const struct reg_sequence rk_hdptx_common_cmn_init_seq[] = {
+ REG_SEQ0(CMN_REG(0009), 0x0c),
+ REG_SEQ0(CMN_REG(000a), 0x83),
+ REG_SEQ0(CMN_REG(000b), 0x06),
+ REG_SEQ0(CMN_REG(000c), 0x20),
+ REG_SEQ0(CMN_REG(000d), 0xb8),
+ REG_SEQ0(CMN_REG(000e), 0x0f),
+ REG_SEQ0(CMN_REG(000f), 0x0f),
+ REG_SEQ0(CMN_REG(0010), 0x04),
+ REG_SEQ0(CMN_REG(0012), 0x26),
+ REG_SEQ0(CMN_REG(0013), 0x22),
+ REG_SEQ0(CMN_REG(0014), 0x24),
+ REG_SEQ0(CMN_REG(0015), 0x77),
+ REG_SEQ0(CMN_REG(0016), 0x08),
+ REG_SEQ0(CMN_REG(0018), 0x04),
+ REG_SEQ0(CMN_REG(0019), 0x48),
+ REG_SEQ0(CMN_REG(001a), 0x01),
+ REG_SEQ0(CMN_REG(001b), 0x00),
+ REG_SEQ0(CMN_REG(001c), 0x01),
+ REG_SEQ0(CMN_REG(001d), 0x64),
+ REG_SEQ0(CMN_REG(001f), 0x00),
+ REG_SEQ0(CMN_REG(0029), 0x01),
+ REG_SEQ0(CMN_REG(0035), 0x00),
+ REG_SEQ0(CMN_REG(0038), 0x00),
+ REG_SEQ0(CMN_REG(0039), 0x00),
+ REG_SEQ0(CMN_REG(003a), 0x00),
+ REG_SEQ0(CMN_REG(003b), 0x00),
+ REG_SEQ0(CMN_REG(003c), 0x80),
+ REG_SEQ0(CMN_REG(003e), 0x0c),
+ REG_SEQ0(CMN_REG(003f), 0x83),
+ REG_SEQ0(CMN_REG(0040), 0x06),
+ REG_SEQ0(CMN_REG(0041), 0x20),
+ REG_SEQ0(CMN_REG(0043), 0x00),
+ REG_SEQ0(CMN_REG(0044), 0x46),
+ REG_SEQ0(CMN_REG(0045), 0x24),
+ REG_SEQ0(CMN_REG(0047), 0x00),
+ REG_SEQ0(CMN_REG(0049), 0xfa),
+ REG_SEQ0(CMN_REG(004a), 0x08),
+ REG_SEQ0(CMN_REG(004b), 0x00),
+ REG_SEQ0(CMN_REG(004c), 0x01),
+ REG_SEQ0(CMN_REG(004d), 0x64),
+ REG_SEQ0(CMN_REG(004f), 0x00),
+ REG_SEQ0(CMN_REG(0050), 0x00),
+ REG_SEQ0(CMN_REG(005f), 0x01),
+ REG_SEQ0(CMN_REG(0075), 0x20),
+ REG_SEQ0(CMN_REG(0076), 0x30),
+ REG_SEQ0(CMN_REG(0077), 0x08),
+ REG_SEQ0(CMN_REG(0078), 0x0c),
+ REG_SEQ0(CMN_REG(0079), 0x00),
+ REG_SEQ0(CMN_REG(007b), 0x00),
+ REG_SEQ0(CMN_REG(007c), 0x00),
+ REG_SEQ0(CMN_REG(007d), 0x00),
+ REG_SEQ0(CMN_REG(007e), 0x00),
+ REG_SEQ0(CMN_REG(007f), 0x00),
+ REG_SEQ0(CMN_REG(0080), 0x00),
+ REG_SEQ0(CMN_REG(0082), 0x04),
+ REG_SEQ0(CMN_REG(0083), 0x24),
+ REG_SEQ0(CMN_REG(0084), 0x20),
+ REG_SEQ0(CMN_REG(0085), 0x03),
+ REG_SEQ0(CMN_REG(008a), 0x55),
+ REG_SEQ0(CMN_REG(008b), 0x25),
+ REG_SEQ0(CMN_REG(008c), 0x2c),
+ REG_SEQ0(CMN_REG(008d), 0x22),
+ REG_SEQ0(CMN_REG(008e), 0x14),
+ REG_SEQ0(CMN_REG(008f), 0x20),
+ REG_SEQ0(CMN_REG(0090), 0x00),
+ REG_SEQ0(CMN_REG(0091), 0x00),
+ REG_SEQ0(CMN_REG(0092), 0x00),
+ REG_SEQ0(CMN_REG(0093), 0x00),
+ REG_SEQ0(CMN_REG(009a), 0x11),
+};
+
+static const struct reg_sequence rk_hdptx_frl_lcpll_cmn_init_seq[] = {
+ REG_SEQ0(CMN_REG(0011), 0x00),
+ REG_SEQ0(CMN_REG(0017), 0x00),
+ REG_SEQ0(CMN_REG(0025), 0x10),
+ REG_SEQ0(CMN_REG(0026), 0x53),
+ REG_SEQ0(CMN_REG(0027), 0x01),
+ REG_SEQ0(CMN_REG(0028), 0x0d),
+ REG_SEQ0(CMN_REG(002e), 0x02),
+ REG_SEQ0(CMN_REG(002f), 0x0d),
+ REG_SEQ0(CMN_REG(0030), 0x00),
+ REG_SEQ0(CMN_REG(0031), 0x20),
+ REG_SEQ0(CMN_REG(0032), 0x30),
+ REG_SEQ0(CMN_REG(0033), 0x0b),
+ REG_SEQ0(CMN_REG(0034), 0x23),
+ REG_SEQ0(CMN_REG(003d), 0x00),
+ REG_SEQ0(CMN_REG(0042), 0xb8),
+ REG_SEQ0(CMN_REG(0046), 0xff),
+ REG_SEQ0(CMN_REG(0048), 0x44),
+ REG_SEQ0(CMN_REG(004e), 0x14),
+ REG_SEQ0(CMN_REG(0051), 0x00),
+ REG_SEQ0(CMN_REG(0055), 0x00),
+ REG_SEQ0(CMN_REG(0059), 0x11),
+ REG_SEQ0(CMN_REG(005a), 0x03),
+ REG_SEQ0(CMN_REG(005c), 0x05),
+ REG_SEQ0(CMN_REG(005d), 0x0c),
+ REG_SEQ0(CMN_REG(005e), 0x07),
+ REG_SEQ0(CMN_REG(0060), 0x01),
+ REG_SEQ0(CMN_REG(0064), 0x07),
+ REG_SEQ0(CMN_REG(0065), 0x00),
+ REG_SEQ0(CMN_REG(0069), 0x00),
+ REG_SEQ0(CMN_REG(006b), 0x04),
+ REG_SEQ0(CMN_REG(006c), 0x00),
+ REG_SEQ0(CMN_REG(0070), 0x01),
+ REG_SEQ0(CMN_REG(0073), 0x30),
+ REG_SEQ0(CMN_REG(0074), 0x00),
+ REG_SEQ0(CMN_REG(0081), 0x09),
+ REG_SEQ0(CMN_REG(0086), 0x01),
+ REG_SEQ0(CMN_REG(0087), 0x0c),
+ REG_SEQ0(CMN_REG(0089), 0x02),
+ REG_SEQ0(CMN_REG(0095), 0x00),
+ REG_SEQ0(CMN_REG(0097), 0x00),
+ REG_SEQ0(CMN_REG(0099), 0x00),
+ REG_SEQ0(CMN_REG(009b), 0x10),
+};
+
+static const struct reg_sequence rk_hdptx_frl_lcpll_ropll_cmn_init_seq[] = {
+ REG_SEQ0(CMN_REG(0008), 0xd0),
+ REG_SEQ0(CMN_REG(0011), 0x00),
+ REG_SEQ0(CMN_REG(0017), 0x00),
+ REG_SEQ0(CMN_REG(001e), 0x35),
+ REG_SEQ0(CMN_REG(0020), 0x6b),
+ REG_SEQ0(CMN_REG(0021), 0x6b),
+ REG_SEQ0(CMN_REG(0022), 0x11),
+ REG_SEQ0(CMN_REG(0024), 0x00),
+ REG_SEQ0(CMN_REG(0025), 0x10),
+ REG_SEQ0(CMN_REG(0026), 0x53),
+ REG_SEQ0(CMN_REG(0027), 0x15),
+ REG_SEQ0(CMN_REG(0028), 0x0d),
+ REG_SEQ0(CMN_REG(002a), 0x09),
+ REG_SEQ0(CMN_REG(002b), 0x01),
+ REG_SEQ0(CMN_REG(002c), 0x02),
+ REG_SEQ0(CMN_REG(002d), 0x02),
+ REG_SEQ0(CMN_REG(002e), 0x0d),
+ REG_SEQ0(CMN_REG(002f), 0x61),
+ REG_SEQ0(CMN_REG(0030), 0x00),
+ REG_SEQ0(CMN_REG(0031), 0x20),
+ REG_SEQ0(CMN_REG(0032), 0x30),
+ REG_SEQ0(CMN_REG(0033), 0x0b),
+ REG_SEQ0(CMN_REG(0034), 0x23),
+ REG_SEQ0(CMN_REG(0037), 0x00),
+ REG_SEQ0(CMN_REG(003d), 0xc0),
+ REG_SEQ0(CMN_REG(0042), 0xb8),
+ REG_SEQ0(CMN_REG(0046), 0xff),
+ REG_SEQ0(CMN_REG(0048), 0x44),
+ REG_SEQ0(CMN_REG(004e), 0x14),
+ REG_SEQ0(CMN_REG(0054), 0x19),
+ REG_SEQ0(CMN_REG(0058), 0x19),
+ REG_SEQ0(CMN_REG(0059), 0x11),
+ REG_SEQ0(CMN_REG(005b), 0x30),
+ REG_SEQ0(CMN_REG(005c), 0x25),
+ REG_SEQ0(CMN_REG(005d), 0x14),
+ REG_SEQ0(CMN_REG(005e), 0x0e),
+ REG_SEQ0(CMN_REG(0063), 0x01),
+ REG_SEQ0(CMN_REG(0064), 0x0e),
+ REG_SEQ0(CMN_REG(0068), 0x00),
+ REG_SEQ0(CMN_REG(0069), 0x02),
+ REG_SEQ0(CMN_REG(006b), 0x00),
+ REG_SEQ0(CMN_REG(006f), 0x00),
+ REG_SEQ0(CMN_REG(0073), 0x02),
+ REG_SEQ0(CMN_REG(0074), 0x00),
+ REG_SEQ0(CMN_REG(007a), 0x00),
+ REG_SEQ0(CMN_REG(0081), 0x09),
+ REG_SEQ0(CMN_REG(0086), 0x11),
+ REG_SEQ0(CMN_REG(0087), 0x0c),
+ REG_SEQ0(CMN_REG(0089), 0x00),
+ REG_SEQ0(CMN_REG(0095), 0x03),
+ REG_SEQ0(CMN_REG(0097), 0x00),
+ REG_SEQ0(CMN_REG(0099), 0x00),
+ REG_SEQ0(CMN_REG(009b), 0x10),
+ REG_SEQ0(CMN_REG(009e), 0x03),
+ REG_SEQ0(CMN_REG(009f), 0xff),
+ REG_SEQ0(CMN_REG(00a0), 0x60),
+};
+
+static const struct reg_sequence rk_hdptx_tmds_cmn_init_seq[] = {
+ REG_SEQ0(CMN_REG(0008), 0x00),
+ REG_SEQ0(CMN_REG(0011), 0x01),
+ REG_SEQ0(CMN_REG(0017), 0x20),
+ REG_SEQ0(CMN_REG(001e), 0x14),
+ REG_SEQ0(CMN_REG(0020), 0x00),
+ REG_SEQ0(CMN_REG(0021), 0x00),
+ REG_SEQ0(CMN_REG(0022), 0x11),
+ REG_SEQ0(CMN_REG(0023), 0x00),
+ REG_SEQ0(CMN_REG(0024), 0x00),
+ REG_SEQ0(CMN_REG(0025), 0x53),
+ REG_SEQ0(CMN_REG(0026), 0x00),
+ REG_SEQ0(CMN_REG(0027), 0x00),
+ REG_SEQ0(CMN_REG(0028), 0x01),
+ REG_SEQ0(CMN_REG(002a), 0x00),
+ REG_SEQ0(CMN_REG(002b), 0x00),
+ REG_SEQ0(CMN_REG(002c), 0x00),
+ REG_SEQ0(CMN_REG(002d), 0x00),
+ REG_SEQ0(CMN_REG(002e), 0x04),
+ REG_SEQ0(CMN_REG(002f), 0x00),
+ REG_SEQ0(CMN_REG(0030), 0x20),
+ REG_SEQ0(CMN_REG(0031), 0x30),
+ REG_SEQ0(CMN_REG(0032), 0x0b),
+ REG_SEQ0(CMN_REG(0033), 0x23),
+ REG_SEQ0(CMN_REG(0034), 0x00),
+ REG_SEQ0(CMN_REG(003d), 0x40),
+ REG_SEQ0(CMN_REG(0042), 0x78),
+ REG_SEQ0(CMN_REG(0046), 0xdd),
+ REG_SEQ0(CMN_REG(0048), 0x11),
+ REG_SEQ0(CMN_REG(004e), 0x34),
+ REG_SEQ0(CMN_REG(005c), 0x25),
+ REG_SEQ0(CMN_REG(005d), 0x0c),
+ REG_SEQ0(CMN_REG(005e), 0x4f),
+ REG_SEQ0(CMN_REG(006b), 0x04),
+ REG_SEQ0(CMN_REG(0073), 0x30),
+ REG_SEQ0(CMN_REG(0074), 0x04),
+ REG_SEQ0(CMN_REG(0081), 0x01),
+ REG_SEQ0(CMN_REG(0086), 0x01),
+ REG_SEQ0(CMN_REG(0087), 0x04),
+ REG_SEQ0(CMN_REG(0089), 0x00),
+ REG_SEQ0(CMN_REG(0095), 0x00),
+ REG_SEQ0(CMN_REG(0097), 0x02),
+ REG_SEQ0(CMN_REG(0099), 0x04),
+ REG_SEQ0(CMN_REG(009b), 0x00),
+};
+
+static const struct reg_sequence rk_hdptx_common_sb_init_seq[] = {
+ REG_SEQ0(SB_REG(0114), 0x00),
+ REG_SEQ0(SB_REG(0115), 0x00),
+ REG_SEQ0(SB_REG(0116), 0x00),
+ REG_SEQ0(SB_REG(0117), 0x00),
+};
+
+static const struct reg_sequence rk_hdptx_frl_lntop_init_seq[] = {
+ REG_SEQ0(LNTOP_REG(0200), 0x04),
+ REG_SEQ0(LNTOP_REG(0201), 0x00),
+ REG_SEQ0(LNTOP_REG(0202), 0x00),
+ REG_SEQ0(LNTOP_REG(0203), 0xf0),
+ REG_SEQ0(LNTOP_REG(0204), 0xff),
+ REG_SEQ0(LNTOP_REG(0205), 0xff),
+ REG_SEQ0(LNTOP_REG(0206), 0x05),
+};
+
+static const struct reg_sequence rk_hdptx_tmds_lntop_highbr_seq[] = {
+ REG_SEQ0(LNTOP_REG(0201), 0x00),
+ REG_SEQ0(LNTOP_REG(0202), 0x00),
+ REG_SEQ0(LNTOP_REG(0203), 0x0f),
+ REG_SEQ0(LNTOP_REG(0204), 0xff),
+ REG_SEQ0(LNTOP_REG(0205), 0xff),
+};
+
+static const struct reg_sequence rk_hdptx_tmds_lntop_lowbr_seq[] = {
+ REG_SEQ0(LNTOP_REG(0201), 0x07),
+ REG_SEQ0(LNTOP_REG(0202), 0xc1),
+ REG_SEQ0(LNTOP_REG(0203), 0xf0),
+ REG_SEQ0(LNTOP_REG(0204), 0x7c),
+ REG_SEQ0(LNTOP_REG(0205), 0x1f),
+};
+
+static const struct reg_sequence rk_hdptx_common_lane_init_seq[] = {
+ REG_SEQ0(LANE_REG(0303), 0x0c),
+ REG_SEQ0(LANE_REG(0307), 0x20),
+ REG_SEQ0(LANE_REG(030a), 0x17),
+ REG_SEQ0(LANE_REG(030b), 0x77),
+ REG_SEQ0(LANE_REG(030c), 0x77),
+ REG_SEQ0(LANE_REG(030d), 0x77),
+ REG_SEQ0(LANE_REG(030e), 0x38),
+ REG_SEQ0(LANE_REG(0310), 0x03),
+ REG_SEQ0(LANE_REG(0311), 0x0f),
+ REG_SEQ0(LANE_REG(0316), 0x02),
+ REG_SEQ0(LANE_REG(031b), 0x01),
+ REG_SEQ0(LANE_REG(031f), 0x15),
+ REG_SEQ0(LANE_REG(0320), 0xa0),
+ REG_SEQ0(LANE_REG(0403), 0x0c),
+ REG_SEQ0(LANE_REG(0407), 0x20),
+ REG_SEQ0(LANE_REG(040a), 0x17),
+ REG_SEQ0(LANE_REG(040b), 0x77),
+ REG_SEQ0(LANE_REG(040c), 0x77),
+ REG_SEQ0(LANE_REG(040d), 0x77),
+ REG_SEQ0(LANE_REG(040e), 0x38),
+ REG_SEQ0(LANE_REG(0410), 0x03),
+ REG_SEQ0(LANE_REG(0411), 0x0f),
+ REG_SEQ0(LANE_REG(0416), 0x02),
+ REG_SEQ0(LANE_REG(041b), 0x01),
+ REG_SEQ0(LANE_REG(041f), 0x15),
+ REG_SEQ0(LANE_REG(0420), 0xa0),
+ REG_SEQ0(LANE_REG(0503), 0x0c),
+ REG_SEQ0(LANE_REG(0507), 0x20),
+ REG_SEQ0(LANE_REG(050a), 0x17),
+ REG_SEQ0(LANE_REG(050b), 0x77),
+ REG_SEQ0(LANE_REG(050c), 0x77),
+ REG_SEQ0(LANE_REG(050d), 0x77),
+ REG_SEQ0(LANE_REG(050e), 0x38),
+ REG_SEQ0(LANE_REG(0510), 0x03),
+ REG_SEQ0(LANE_REG(0511), 0x0f),
+ REG_SEQ0(LANE_REG(0516), 0x02),
+ REG_SEQ0(LANE_REG(051b), 0x01),
+ REG_SEQ0(LANE_REG(051f), 0x15),
+ REG_SEQ0(LANE_REG(0520), 0xa0),
+ REG_SEQ0(LANE_REG(0603), 0x0c),
+ REG_SEQ0(LANE_REG(0607), 0x20),
+ REG_SEQ0(LANE_REG(060a), 0x17),
+ REG_SEQ0(LANE_REG(060b), 0x77),
+ REG_SEQ0(LANE_REG(060c), 0x77),
+ REG_SEQ0(LANE_REG(060d), 0x77),
+ REG_SEQ0(LANE_REG(060e), 0x38),
+ REG_SEQ0(LANE_REG(0610), 0x03),
+ REG_SEQ0(LANE_REG(0611), 0x0f),
+ REG_SEQ0(LANE_REG(0616), 0x02),
+ REG_SEQ0(LANE_REG(061b), 0x01),
+ REG_SEQ0(LANE_REG(061f), 0x15),
+ REG_SEQ0(LANE_REG(0620), 0xa0),
+};
+
+static const struct reg_sequence rk_hdptx_frl_lane_init_seq[] = {
+ REG_SEQ0(LANE_REG(0312), 0x3c),
+ REG_SEQ0(LANE_REG(0412), 0x3c),
+ REG_SEQ0(LANE_REG(0512), 0x3c),
+ REG_SEQ0(LANE_REG(0612), 0x3c),
+ REG_SEQ0(LANE_REG(0303), 0x2f),
+ REG_SEQ0(LANE_REG(0403), 0x2f),
+ REG_SEQ0(LANE_REG(0503), 0x2f),
+ REG_SEQ0(LANE_REG(0603), 0x2f),
+ REG_SEQ0(LANE_REG(0305), 0x03),
+ REG_SEQ0(LANE_REG(0405), 0x03),
+ REG_SEQ0(LANE_REG(0505), 0x03),
+ REG_SEQ0(LANE_REG(0605), 0x03),
+ REG_SEQ0(LANE_REG(0306), 0xfc),
+ REG_SEQ0(LANE_REG(0406), 0xfc),
+ REG_SEQ0(LANE_REG(0506), 0xfc),
+ REG_SEQ0(LANE_REG(0606), 0xfc),
+ REG_SEQ0(LANE_REG(0305), 0x4f),
+ REG_SEQ0(LANE_REG(0405), 0x4f),
+ REG_SEQ0(LANE_REG(0505), 0x4f),
+ REG_SEQ0(LANE_REG(0605), 0x4f),
+ REG_SEQ0(LANE_REG(0304), 0x14),
+ REG_SEQ0(LANE_REG(0404), 0x14),
+ REG_SEQ0(LANE_REG(0504), 0x14),
+ REG_SEQ0(LANE_REG(0604), 0x14),
+ /* Keep Inter-Pair Skew in the limits */
+ REG_SEQ0(LANE_REG(031e), 0x02),
+ REG_SEQ0(LANE_REG(041e), 0x02),
+ REG_SEQ0(LANE_REG(051e), 0x02),
+ REG_SEQ0(LANE_REG(061e), 0x02),
+};
+
+static const struct reg_sequence rk_hdptx_tmds_lane_init_seq[] = {
+ REG_SEQ0(LANE_REG(0312), 0x00),
+ REG_SEQ0(LANE_REG(0412), 0x00),
+ REG_SEQ0(LANE_REG(0512), 0x00),
+ REG_SEQ0(LANE_REG(0612), 0x00),
+ REG_SEQ0(LANE_REG(0303), 0x2f),
+ REG_SEQ0(LANE_REG(0403), 0x2f),
+ REG_SEQ0(LANE_REG(0503), 0x2f),
+ REG_SEQ0(LANE_REG(0603), 0x2f),
+ REG_SEQ0(LANE_REG(0305), 0x03),
+ REG_SEQ0(LANE_REG(0405), 0x03),
+ REG_SEQ0(LANE_REG(0505), 0x03),
+ REG_SEQ0(LANE_REG(0605), 0x03),
+ REG_SEQ0(LANE_REG(0306), 0x1c),
+ REG_SEQ0(LANE_REG(0406), 0x1c),
+ REG_SEQ0(LANE_REG(0506), 0x1c),
+ REG_SEQ0(LANE_REG(0606), 0x1c),
+ /* Keep Inter-Pair Skew in the limits */
+ REG_SEQ0(LANE_REG(031e), 0x02),
+ REG_SEQ0(LANE_REG(041e), 0x02),
+ REG_SEQ0(LANE_REG(051e), 0x02),
+ REG_SEQ0(LANE_REG(061e), 0x0a),
+};
+
+static struct tx_drv_ctrl tx_drv_ctrl_rbr[4][4] = {
+ /* voltage swing 0, pre-emphasis 0->3 */
+ {
+ { 0x2, 0x0, 0x4, 0x6, 0x0, 0x4, 0x1, 0x1, 0x7, 0x7 },
+ { 0x4, 0x3, 0x4, 0x6, 0x0, 0x4, 0x0, 0x1, 0x7, 0x7 },
+ { 0x7, 0x6, 0x4, 0x6, 0x0, 0x4, 0x0, 0x1, 0x7, 0x7 },
+ { 0xd, 0xc, 0x7, 0x7, 0x1, 0x7, 0x0, 0x1, 0x7, 0x7 },
+ },
+
+ /* voltage swing 1, pre-emphasis 0->2 */
+ {
+ { 0x4, 0x0, 0x4, 0x6, 0x0, 0x4, 0x1, 0x1, 0x7, 0x7 },
+ { 0x9, 0x5, 0x4, 0x6, 0x0, 0x4, 0x0, 0x1, 0x7, 0x7 },
+ { 0xc, 0x8, 0x7, 0x7, 0x1, 0x7, 0x0, 0x1, 0x7, 0x7 },
+ },
+
+ /* voltage swing 2, pre-emphasis 0->1 */
+ {
+ { 0x8, 0x0, 0x4, 0x6, 0x0, 0x4, 0x1, 0x1, 0x7, 0x7 },
+ { 0xc, 0x5, 0x7, 0x7, 0x1, 0x7, 0x0, 0x1, 0x7, 0x7 },
+ },
+
+ /* voltage swing 3, pre-emphasis 0 */
+ {
+ { 0xb, 0x0, 0x7, 0x7, 0x1, 0x4, 0x1, 0x1, 0x7, 0x7 },
+ }
+};
+
+static struct tx_drv_ctrl tx_drv_ctrl_hbr[4][4] = {
+ /* voltage swing 0, pre-emphasis 0->3 */
+ {
+ { 0x2, 0x0, 0x4, 0x6, 0x0, 0x4, 0x1, 0x1, 0x7, 0x7 },
+ { 0x5, 0x4, 0x4, 0x6, 0x0, 0x4, 0x0, 0x1, 0x7, 0x7 },
+ { 0x9, 0x8, 0x4, 0x6, 0x0, 0x4, 0x0, 0x1, 0x7, 0x7 },
+ { 0xd, 0xc, 0x7, 0x7, 0x1, 0x7, 0x0, 0x1, 0x7, 0x7 },
+ },
+
+ /* voltage swing 1, pre-emphasis 0->2 */
+ {
+ { 0x6, 0x1, 0x4, 0x6, 0x0, 0x4, 0x1, 0x1, 0x7, 0x7 },
+ { 0xa, 0x6, 0x4, 0x6, 0x0, 0x4, 0x0, 0x1, 0x7, 0x7 },
+ { 0xc, 0x8, 0x7, 0x7, 0x1, 0x7, 0x0, 0x1, 0x7, 0x7 },
+ },
+
+ /* voltage swing 2, pre-emphasis 0->1 */
+ {
+ { 0x9, 0x1, 0x4, 0x6, 0x0, 0x4, 0x1, 0x1, 0x7, 0x7 },
+ { 0xd, 0x6, 0x7, 0x7, 0x1, 0x7, 0x0, 0x1, 0x7, 0x7 },
+ },
+
+ /* voltage swing 3, pre-emphasis 0 */
+ {
+ { 0xc, 0x1, 0x7, 0x7, 0x1, 0x4, 0x1, 0x1, 0x7, 0x7 },
+ }
+};
+
+static struct tx_drv_ctrl tx_drv_ctrl_hbr2[4][4] = {
+ /* voltage swing 0, pre-emphasis 0->3 */
+ {
+ { 0x2, 0x1, 0x4, 0x6, 0x0, 0x4, 0x0, 0x1, 0x7, 0x7 },
+ { 0x5, 0x4, 0x4, 0x6, 0x0, 0x4, 0x0, 0x1, 0x7, 0x7 },
+ { 0x9, 0x8, 0x4, 0x6, 0x1, 0x4, 0x0, 0x1, 0x7, 0x7 },
+ { 0xd, 0xc, 0x7, 0x7, 0x1, 0x7, 0x0, 0x1, 0x7, 0x7 },
+ },
+
+ /* voltage swing 1, pre-emphasis 0->2 */
+ {
+ { 0x6, 0x1, 0x4, 0x6, 0x0, 0x4, 0x1, 0x1, 0x7, 0x7 },
+ { 0xb, 0x7, 0x4, 0x6, 0x0, 0x4, 0x0, 0x1, 0x7, 0x7 },
+ { 0xd, 0x9, 0x7, 0x7, 0x1, 0x7, 0x0, 0x1, 0x7, 0x7 },
+ },
+
+ /* voltage swing 2, pre-emphasis 0->1 */
+ {
+ { 0x8, 0x1, 0x4, 0x6, 0x0, 0x4, 0x1, 0x1, 0x7, 0x7 },
+ { 0xc, 0x6, 0x7, 0x7, 0x1, 0x7, 0x0, 0x1, 0x7, 0x7 },
+ },
+
+ /* voltage swing 3, pre-emphasis 0 */
+ {
+ { 0xb, 0x0, 0x7, 0x7, 0x1, 0x4, 0x1, 0x1, 0x7, 0x7 },
+ }
+};
+
+static bool rk_hdptx_phy_is_rw_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case 0x0000 ... 0x029c: /* CMN Register */
+ case 0x0400 ... 0x04a4: /* Sideband Register */
+ case 0x0800 ... 0x08a4: /* Lane Top Register */
+ case 0x0c00 ... 0x0cb4: /* Lane 0 Register */
+ case 0x1000 ... 0x10b4: /* Lane 1 Register */
+ case 0x1400 ... 0x14b4: /* Lane 2 Register */
+ case 0x1800 ... 0x18b4: /* Lane 3 Register */
+ return true;
+ }
+
+ return false;
+}
+
+static const struct regmap_config rk_hdptx_phy_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .writeable_reg = rk_hdptx_phy_is_rw_reg,
+ .readable_reg = rk_hdptx_phy_is_rw_reg,
+ .max_register = 0x18b4,
+};
+
+#define rk_hdptx_multi_reg_write(hdptx, seq) \
+ regmap_multi_reg_write((hdptx)->regmap, seq, ARRAY_SIZE(seq))
+
+static void rk_hdptx_pre_power_up(struct rk_hdptx_phy *hdptx)
+{
+ u32 val;
+
+ reset_control_assert(hdptx->rsts[RST_APB].rstc);
+ usleep_range(20, 25);
+ reset_control_deassert(hdptx->rsts[RST_APB].rstc);
+
+ reset_control_assert(hdptx->rsts[RST_LANE].rstc);
+ reset_control_assert(hdptx->rsts[RST_CMN].rstc);
+ reset_control_assert(hdptx->rsts[RST_INIT].rstc);
+
+ val = (FIELD_PREP_WM16(HDPTX_I_PLL_EN, 0) |
+ FIELD_PREP_WM16(HDPTX_I_BIAS_EN, 0) |
+ FIELD_PREP_WM16(HDPTX_I_BGR_EN, 0));
+ regmap_write(hdptx->grf, GRF_HDPTX_CON0, val);
+}
+
+static int rk_hdptx_post_enable_lane(struct rk_hdptx_phy *hdptx)
+{
+ u32 val;
+ int ret;
+
+ reset_control_deassert(hdptx->rsts[RST_LANE].rstc);
+
+ val = (FIELD_PREP_WM16(HDPTX_I_BIAS_EN, 1) |
+ FIELD_PREP_WM16(HDPTX_I_BGR_EN, 1));
+ regmap_write(hdptx->grf, GRF_HDPTX_CON0, val);
+
+ /* 3 lanes FRL mode */
+ if (hdptx->hdmi_cfg.rate == FRL_6G3L_RATE ||
+ hdptx->hdmi_cfg.rate == FRL_3G3L_RATE)
+ regmap_write(hdptx->regmap, LNTOP_REG(0207), 0x07);
+ else
+ regmap_write(hdptx->regmap, LNTOP_REG(0207), 0x0f);
+
+ ret = regmap_read_poll_timeout(hdptx->grf, GRF_HDPTX_STATUS, val,
+ (val & HDPTX_O_PHY_RDY) &&
+ (val & HDPTX_O_PLL_LOCK_DONE),
+ 100, 5000);
+ if (ret) {
+ dev_err(hdptx->dev, "Failed to get PHY lane lock: %d\n", ret);
+ return ret;
+ }
+
+ dev_dbg(hdptx->dev, "PHY lane locked\n");
+
+ return 0;
+}
+
+static int rk_hdptx_post_enable_pll(struct rk_hdptx_phy *hdptx)
+{
+ u32 val;
+ int ret;
+
+ val = (FIELD_PREP_WM16(HDPTX_I_BIAS_EN, 1) |
+ FIELD_PREP_WM16(HDPTX_I_BGR_EN, 1));
+ regmap_write(hdptx->grf, GRF_HDPTX_CON0, val);
+
+ usleep_range(10, 15);
+ reset_control_deassert(hdptx->rsts[RST_INIT].rstc);
+
+ usleep_range(10, 15);
+ regmap_write(hdptx->grf, GRF_HDPTX_CON0, FIELD_PREP_WM16(HDPTX_I_PLL_EN, 1));
+
+ usleep_range(10, 15);
+ reset_control_deassert(hdptx->rsts[RST_CMN].rstc);
+
+ ret = regmap_read_poll_timeout(hdptx->grf, GRF_HDPTX_STATUS, val,
+ val & HDPTX_O_PHY_CLK_RDY, 20, 400);
+ if (ret) {
+ dev_err(hdptx->dev, "Failed to get PHY clk ready: %d\n", ret);
+ return ret;
+ }
+
+ dev_dbg(hdptx->dev, "PHY clk ready\n");
+
+ return 0;
+}
+
+static void rk_hdptx_phy_disable(struct rk_hdptx_phy *hdptx)
+{
+ u32 val;
+
+ reset_control_assert(hdptx->rsts[RST_APB].rstc);
+ usleep_range(20, 30);
+ reset_control_deassert(hdptx->rsts[RST_APB].rstc);
+
+ regmap_write(hdptx->regmap, LNTOP_REG(0207), 0x0);
+ regmap_write(hdptx->regmap, LANE_REG(0300), 0x82);
+ regmap_write(hdptx->regmap, SB_REG(010f), 0xc1);
+ regmap_write(hdptx->regmap, SB_REG(0110), 0x1);
+ regmap_write(hdptx->regmap, LANE_REG(0301), 0x80);
+ regmap_write(hdptx->regmap, LANE_REG(0401), 0x80);
+ regmap_write(hdptx->regmap, LANE_REG(0501), 0x80);
+ regmap_write(hdptx->regmap, LANE_REG(0601), 0x80);
+
+ reset_control_assert(hdptx->rsts[RST_LANE].rstc);
+ reset_control_assert(hdptx->rsts[RST_CMN].rstc);
+ reset_control_assert(hdptx->rsts[RST_INIT].rstc);
+
+ val = (FIELD_PREP_WM16(HDPTX_I_PLL_EN, 0) |
+ FIELD_PREP_WM16(HDPTX_I_BIAS_EN, 0) |
+ FIELD_PREP_WM16(HDPTX_I_BGR_EN, 0));
+ regmap_write(hdptx->grf, GRF_HDPTX_CON0, val);
+}
+
+static bool rk_hdptx_phy_clk_pll_calc(unsigned long long rate,
+ struct ropll_config *cfg)
+{
+ const unsigned int fout = div_u64(rate, 200), fref = 24000;
+ unsigned long k = 0, lc, k_sub, lc_sub;
+ unsigned int fvco, sdc;
+ u32 mdiv, sdiv, n = 8;
+
+ if (fout > 0xfffffff)
+ return false;
+
+ for (sdiv = 16; sdiv >= 1; sdiv--) {
+ if (sdiv % 2 && sdiv != 1)
+ continue;
+
+ fvco = fout * sdiv;
+
+ if (fvco < 2000000 || fvco > 4000000)
+ continue;
+
+ mdiv = DIV_ROUND_UP(fvco, fref);
+ if (mdiv < 20 || mdiv > 255)
+ continue;
+
+ if (fref * mdiv - fvco) {
+ for (sdc = 264000; sdc <= 750000; sdc += fref)
+ if (sdc * n > fref * mdiv)
+ break;
+
+ if (sdc > 750000)
+ continue;
+
+ rational_best_approximation(fref * mdiv - fvco,
+ sdc / 16,
+ GENMASK(6, 0),
+ GENMASK(7, 0),
+ &k, &lc);
+
+ rational_best_approximation(sdc * n - fref * mdiv,
+ sdc,
+ GENMASK(6, 0),
+ GENMASK(7, 0),
+ &k_sub, &lc_sub);
+ }
+
+ break;
+ }
+
+ if (sdiv < 1)
+ return false;
+
+ if (cfg) {
+ cfg->pms_mdiv = mdiv;
+ cfg->pms_mdiv_afc = mdiv;
+ cfg->pms_pdiv = 1;
+ cfg->pms_refdiv = 1;
+ cfg->pms_sdiv = sdiv - 1;
+
+ cfg->sdm_en = k > 0 ? 1 : 0;
+ if (cfg->sdm_en) {
+ cfg->sdm_deno = lc;
+ cfg->sdm_num_sign = 1;
+ cfg->sdm_num = k;
+ cfg->sdc_n = n - 3;
+ cfg->sdc_num = k_sub;
+ cfg->sdc_deno = lc_sub;
+ }
+ }
+
+ return true;
+}
+
+static int rk_hdptx_frl_lcpll_cmn_config(struct rk_hdptx_phy *hdptx)
+{
+ const struct lcpll_config *cfg = NULL;
+ int i;
+
+ dev_dbg(hdptx->dev, "%s rate=%llu\n", __func__, hdptx->hdmi_cfg.rate);
+
+ for (i = 0; i < ARRAY_SIZE(rk_hdptx_frl_lcpll_cfg); i++) {
+ if (hdptx->hdmi_cfg.rate == rk_hdptx_frl_lcpll_cfg[i].rate) {
+ cfg = &rk_hdptx_frl_lcpll_cfg[i];
+ break;
+ }
+ }
+
+ if (!cfg) {
+ dev_err(hdptx->dev, "%s cannot find pll cfg for rate=%llu\n",
+ __func__, hdptx->hdmi_cfg.rate);
+ return -EINVAL;
+ }
+
+ rk_hdptx_pre_power_up(hdptx);
+
+ regmap_write(hdptx->grf, GRF_HDPTX_CON0, FIELD_PREP_WM16(LC_REF_CLK_SEL, 0));
+
+ rk_hdptx_multi_reg_write(hdptx, rk_hdptx_common_cmn_init_seq);
+ rk_hdptx_multi_reg_write(hdptx, rk_hdptx_frl_lcpll_cmn_init_seq);
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(0008),
+ LCPLL_EN_MASK | LCPLL_LCVCO_MODE_EN_MASK,
+ FIELD_PREP(LCPLL_EN_MASK, 1) |
+ FIELD_PREP(LCPLL_LCVCO_MODE_EN_MASK, cfg->lcvco_mode_en));
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(001e),
+ LCPLL_PI_EN_MASK | LCPLL_100M_CLK_EN_MASK,
+ FIELD_PREP(LCPLL_PI_EN_MASK, cfg->pi_en) |
+ FIELD_PREP(LCPLL_100M_CLK_EN_MASK, cfg->clk_en_100m));
+
+ regmap_write(hdptx->regmap, CMN_REG(0020), cfg->pms_mdiv);
+ regmap_write(hdptx->regmap, CMN_REG(0021), cfg->pms_mdiv_afc);
+ regmap_write(hdptx->regmap, CMN_REG(0022),
+ FIELD_PREP(ANA_LCPLL_PMS_PDIV_MASK, cfg->pms_pdiv) |
+ FIELD_PREP(ANA_LCPLL_PMS_REFDIV_MASK, cfg->pms_refdiv));
+ regmap_write(hdptx->regmap, CMN_REG(0023),
+ FIELD_PREP(LCPLL_PMS_SDIV_RBR_MASK, cfg->pms_sdiv) |
+ FIELD_PREP(LCPLL_PMS_SDIV_HBR_MASK, cfg->pms_sdiv));
+ regmap_write(hdptx->regmap, CMN_REG(002a), cfg->sdm_deno);
+ regmap_write(hdptx->regmap, CMN_REG(002b), cfg->sdm_num_sign);
+ regmap_write(hdptx->regmap, CMN_REG(002c), cfg->sdm_num);
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(002d), LCPLL_SDC_N_MASK,
+ FIELD_PREP(LCPLL_SDC_N_MASK, cfg->sdc_n));
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(0086), PLL_PCG_POSTDIV_SEL_MASK,
+ FIELD_PREP(PLL_PCG_POSTDIV_SEL_MASK, cfg->pms_sdiv));
+ regmap_update_bits(hdptx->regmap, CMN_REG(0086), PLL_PCG_CLK_SEL_MASK,
+ FIELD_PREP(PLL_PCG_CLK_SEL_MASK, (hdptx->hdmi_cfg.bpc - 8) >> 1));
+
+ return rk_hdptx_post_enable_pll(hdptx);
+}
+
+static int rk_hdptx_frl_lcpll_ropll_cmn_config(struct rk_hdptx_phy *hdptx)
+{
+ dev_dbg(hdptx->dev, "%s rate=%llu\n", __func__, hdptx->hdmi_cfg.rate);
+
+ rk_hdptx_pre_power_up(hdptx);
+
+ /* ROPLL input reference clock from LCPLL (cascade mode) */
+ regmap_write(hdptx->grf, GRF_HDPTX_CON0, FIELD_PREP_WM16(LC_REF_CLK_SEL, 1));
+
+ rk_hdptx_multi_reg_write(hdptx, rk_hdptx_common_cmn_init_seq);
+ rk_hdptx_multi_reg_write(hdptx, rk_hdptx_frl_lcpll_ropll_cmn_init_seq);
+
+ return rk_hdptx_post_enable_pll(hdptx);
+}
+
+static int rk_hdptx_tmds_ropll_cmn_config(struct rk_hdptx_phy *hdptx)
+{
+ const struct ropll_config *cfg = NULL;
+ struct ropll_config rc = {0};
+ int i;
+
+ if (!hdptx->hdmi_cfg.rate)
+ return 0;
+
+ for (i = 0; i < ARRAY_SIZE(rk_hdptx_tmds_ropll_cfg); i++)
+ if (hdptx->hdmi_cfg.rate == rk_hdptx_tmds_ropll_cfg[i].rate) {
+ cfg = &rk_hdptx_tmds_ropll_cfg[i];
+ break;
+ }
+
+ if (!cfg) {
+ if (!rk_hdptx_phy_clk_pll_calc(hdptx->hdmi_cfg.rate, &rc)) {
+ dev_err(hdptx->dev, "%s cannot find pll cfg for rate=%llu\n",
+ __func__, hdptx->hdmi_cfg.rate);
+ return -EINVAL;
+ }
+
+ cfg = &rc;
+ }
+
+ dev_dbg(hdptx->dev, "%s rate=%llu mdiv=%u sdiv=%u sdm_en=%u k_sign=%u k=%u lc=%u\n",
+ __func__, hdptx->hdmi_cfg.rate, cfg->pms_mdiv, cfg->pms_sdiv + 1,
+ cfg->sdm_en, cfg->sdm_num_sign, cfg->sdm_num, cfg->sdm_deno);
+
+ rk_hdptx_pre_power_up(hdptx);
+
+ regmap_write(hdptx->grf, GRF_HDPTX_CON0, FIELD_PREP_WM16(LC_REF_CLK_SEL, 0));
+
+ rk_hdptx_multi_reg_write(hdptx, rk_hdptx_common_cmn_init_seq);
+ rk_hdptx_multi_reg_write(hdptx, rk_hdptx_tmds_cmn_init_seq);
+
+ regmap_write(hdptx->regmap, CMN_REG(0051), cfg->pms_mdiv);
+ regmap_write(hdptx->regmap, CMN_REG(0055), cfg->pms_mdiv_afc);
+ regmap_write(hdptx->regmap, CMN_REG(0059),
+ FIELD_PREP(ANA_ROPLL_PMS_PDIV_MASK, cfg->pms_pdiv) |
+ FIELD_PREP(ANA_ROPLL_PMS_REFDIV_MASK, cfg->pms_refdiv));
+ regmap_write(hdptx->regmap, CMN_REG(005a),
+ FIELD_PREP(ROPLL_PMS_SDIV_RBR_MASK, cfg->pms_sdiv));
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(005e), ROPLL_SDM_EN_MASK,
+ FIELD_PREP(ROPLL_SDM_EN_MASK, cfg->sdm_en));
+ if (!cfg->sdm_en)
+ regmap_update_bits(hdptx->regmap, CMN_REG(005e), 0xf, 0);
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(0064), ROPLL_SDM_NUM_SIGN_RBR_MASK,
+ FIELD_PREP(ROPLL_SDM_NUM_SIGN_RBR_MASK, cfg->sdm_num_sign));
+
+ regmap_write(hdptx->regmap, CMN_REG(0060), cfg->sdm_deno);
+ regmap_write(hdptx->regmap, CMN_REG(0065), cfg->sdm_num);
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(0069), ROPLL_SDC_N_RBR_MASK,
+ FIELD_PREP(ROPLL_SDC_N_RBR_MASK, cfg->sdc_n));
+
+ regmap_write(hdptx->regmap, CMN_REG(006c), cfg->sdc_num);
+ regmap_write(hdptx->regmap, CMN_REG(0070), cfg->sdc_deno);
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(0086), PLL_PCG_POSTDIV_SEL_MASK,
+ FIELD_PREP(PLL_PCG_POSTDIV_SEL_MASK, cfg->pms_sdiv));
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(0086), PLL_PCG_CLK_SEL_MASK,
+ FIELD_PREP(PLL_PCG_CLK_SEL_MASK, (hdptx->hdmi_cfg.bpc - 8) >> 1));
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(0086), PLL_PCG_CLK_EN_MASK,
+ FIELD_PREP(PLL_PCG_CLK_EN_MASK, 0x1));
+
+ return rk_hdptx_post_enable_pll(hdptx);
+}
+
+static int rk_hdptx_pll_cmn_config(struct rk_hdptx_phy *hdptx)
+{
+ int ret;
+
+ if (hdptx->hdmi_cfg.rate <= HDMI20_MAX_RATE)
+ ret = rk_hdptx_tmds_ropll_cmn_config(hdptx);
+ else if (hdptx->hdmi_cfg.rate == FRL_8G4L_RATE)
+ ret = rk_hdptx_frl_lcpll_ropll_cmn_config(hdptx);
+ else
+ ret = rk_hdptx_frl_lcpll_cmn_config(hdptx);
+
+ if (!ret)
+ hdptx->pll_config_dirty = false;
+
+ return ret;
+}
+
+static int rk_hdptx_frl_lcpll_mode_config(struct rk_hdptx_phy *hdptx)
+{
+ rk_hdptx_multi_reg_write(hdptx, rk_hdptx_common_sb_init_seq);
+ rk_hdptx_multi_reg_write(hdptx, rk_hdptx_frl_lntop_init_seq);
+
+ rk_hdptx_multi_reg_write(hdptx, rk_hdptx_common_lane_init_seq);
+ rk_hdptx_multi_reg_write(hdptx, rk_hdptx_frl_lane_init_seq);
+
+ return rk_hdptx_post_enable_lane(hdptx);
+}
+
+static int rk_hdptx_tmds_ropll_mode_config(struct rk_hdptx_phy *hdptx)
+{
+ rk_hdptx_multi_reg_write(hdptx, rk_hdptx_common_sb_init_seq);
+
+ regmap_write(hdptx->regmap, LNTOP_REG(0200), 0x06);
+
+ if (hdptx->hdmi_cfg.rate > HDMI14_MAX_RATE) {
+ /* For 1/40 bitrate clk */
+ rk_hdptx_multi_reg_write(hdptx, rk_hdptx_tmds_lntop_highbr_seq);
+ } else {
+ /* For 1/10 bitrate clk */
+ rk_hdptx_multi_reg_write(hdptx, rk_hdptx_tmds_lntop_lowbr_seq);
+ }
+
+ regmap_write(hdptx->regmap, LNTOP_REG(0206), 0x07);
+
+ rk_hdptx_multi_reg_write(hdptx, rk_hdptx_common_lane_init_seq);
+ rk_hdptx_multi_reg_write(hdptx, rk_hdptx_tmds_lane_init_seq);
+
+ return rk_hdptx_post_enable_lane(hdptx);
+}
+
+static void rk_hdptx_dp_reset(struct rk_hdptx_phy *hdptx)
+{
+ reset_control_assert(hdptx->rsts[RST_LANE].rstc);
+ reset_control_assert(hdptx->rsts[RST_CMN].rstc);
+ reset_control_assert(hdptx->rsts[RST_INIT].rstc);
+
+ reset_control_assert(hdptx->rsts[RST_APB].rstc);
+ usleep_range(10, 15);
+ reset_control_deassert(hdptx->rsts[RST_APB].rstc);
+
+ regmap_update_bits(hdptx->regmap, LANE_REG(0301),
+ OVRD_LN_TX_DRV_EI_EN_MASK | LN_TX_DRV_EI_EN_MASK,
+ FIELD_PREP(OVRD_LN_TX_DRV_EI_EN_MASK, 1) |
+ FIELD_PREP(LN_TX_DRV_EI_EN_MASK, 0));
+ regmap_update_bits(hdptx->regmap, LANE_REG(0401),
+ OVRD_LN_TX_DRV_EI_EN_MASK | LN_TX_DRV_EI_EN_MASK,
+ FIELD_PREP(OVRD_LN_TX_DRV_EI_EN_MASK, 1) |
+ FIELD_PREP(LN_TX_DRV_EI_EN_MASK, 0));
+ regmap_update_bits(hdptx->regmap, LANE_REG(0501),
+ OVRD_LN_TX_DRV_EI_EN_MASK | LN_TX_DRV_EI_EN_MASK,
+ FIELD_PREP(OVRD_LN_TX_DRV_EI_EN_MASK, 1) |
+ FIELD_PREP(LN_TX_DRV_EI_EN_MASK, 0));
+ regmap_update_bits(hdptx->regmap, LANE_REG(0601),
+ OVRD_LN_TX_DRV_EI_EN_MASK | LN_TX_DRV_EI_EN_MASK,
+ FIELD_PREP(OVRD_LN_TX_DRV_EI_EN_MASK, 1) |
+ FIELD_PREP(LN_TX_DRV_EI_EN_MASK, 0));
+
+ regmap_write(hdptx->grf, GRF_HDPTX_CON0,
+ FIELD_PREP_WM16(HDPTX_I_PLL_EN, 0) |
+ FIELD_PREP_WM16(HDPTX_I_BIAS_EN, 0) |
+ FIELD_PREP_WM16(HDPTX_I_BGR_EN, 0));
+}
+
+static int rk_hdptx_phy_consumer_get(struct rk_hdptx_phy *hdptx)
+{
+ enum phy_mode mode = phy_get_mode(hdptx->phy);
+ u32 status;
+ int ret;
+
+ if (atomic_inc_return(&hdptx->usage_count) > 1)
+ return 0;
+
+ ret = regmap_read(hdptx->grf, GRF_HDPTX_STATUS, &status);
+ if (ret) {
+ atomic_dec(&hdptx->usage_count);
+ return ret;
+ }
+
+ if (mode == PHY_MODE_DP) {
+ rk_hdptx_dp_reset(hdptx);
+ } else {
+ /*
+ * Ignore PLL config errors at this point as pll_config_dirty
+ * was not reset and, therefore, operation will be retried.
+ */
+ rk_hdptx_pll_cmn_config(hdptx);
+ }
+
+ return 0;
+}
+
+static int rk_hdptx_phy_consumer_put(struct rk_hdptx_phy *hdptx, bool force)
+{
+ enum phy_mode mode = phy_get_mode(hdptx->phy);
+ u32 status;
+ int ret;
+
+ ret = atomic_dec_return(&hdptx->usage_count);
+ if (ret > 0)
+ return 0;
+
+ if (ret < 0) {
+ dev_warn(hdptx->dev, "Usage count underflow!\n");
+ ret = -EINVAL;
+ } else {
+ ret = regmap_read(hdptx->grf, GRF_HDPTX_STATUS, &status);
+ if (!ret) {
+ if (status & HDPTX_O_PLL_LOCK_DONE) {
+ if (mode == PHY_MODE_DP)
+ rk_hdptx_dp_reset(hdptx);
+ else
+ rk_hdptx_phy_disable(hdptx);
+ }
+ return 0;
+ } else if (force) {
+ return 0;
+ }
+ }
+
+ atomic_inc(&hdptx->usage_count);
+ return ret;
+}
+
+static void rk_hdptx_dp_pll_init(struct rk_hdptx_phy *hdptx)
+{
+ regmap_update_bits(hdptx->regmap, CMN_REG(003c), ANA_LCPLL_RESERVED7_MASK,
+ FIELD_PREP(ANA_LCPLL_RESERVED7_MASK, 0x1));
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(0046),
+ ROPLL_ANA_CPP_CTRL_COARSE_MASK | ROPLL_ANA_CPP_CTRL_FINE_MASK,
+ FIELD_PREP(ROPLL_ANA_CPP_CTRL_COARSE_MASK, 0xe) |
+ FIELD_PREP(ROPLL_ANA_CPP_CTRL_FINE_MASK, 0xe));
+ regmap_update_bits(hdptx->regmap, CMN_REG(0047),
+ ROPLL_ANA_LPF_C_SEL_COARSE_MASK |
+ ROPLL_ANA_LPF_C_SEL_FINE_MASK,
+ FIELD_PREP(ROPLL_ANA_LPF_C_SEL_COARSE_MASK, 0x4) |
+ FIELD_PREP(ROPLL_ANA_LPF_C_SEL_FINE_MASK, 0x4));
+
+ regmap_write(hdptx->regmap, CMN_REG(0051), FIELD_PREP(ROPLL_PMS_MDIV_MASK, 0x87));
+ regmap_write(hdptx->regmap, CMN_REG(0052), FIELD_PREP(ROPLL_PMS_MDIV_MASK, 0x71));
+ regmap_write(hdptx->regmap, CMN_REG(0053), FIELD_PREP(ROPLL_PMS_MDIV_MASK, 0x71));
+
+ regmap_write(hdptx->regmap, CMN_REG(0055),
+ FIELD_PREP(ROPLL_PMS_MDIV_AFC_MASK, 0x87));
+ regmap_write(hdptx->regmap, CMN_REG(0056),
+ FIELD_PREP(ROPLL_PMS_MDIV_AFC_MASK, 0x71));
+ regmap_write(hdptx->regmap, CMN_REG(0057),
+ FIELD_PREP(ROPLL_PMS_MDIV_AFC_MASK, 0x71));
+
+ regmap_write(hdptx->regmap, CMN_REG(0059),
+ FIELD_PREP(ANA_ROPLL_PMS_PDIV_MASK, 0x1) |
+ FIELD_PREP(ANA_ROPLL_PMS_REFDIV_MASK, 0x1));
+ regmap_write(hdptx->regmap, CMN_REG(005a),
+ FIELD_PREP(ROPLL_PMS_SDIV_RBR_MASK, 0x3) |
+ FIELD_PREP(ROPLL_PMS_SDIV_HBR_MASK, 0x1));
+ regmap_update_bits(hdptx->regmap, CMN_REG(005b), ROPLL_PMS_SDIV_HBR2_MASK,
+ FIELD_PREP(ROPLL_PMS_SDIV_HBR2_MASK, 0x0));
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(005e), ROPLL_SDM_EN_MASK,
+ FIELD_PREP(ROPLL_SDM_EN_MASK, 0x1));
+ regmap_update_bits(hdptx->regmap, CMN_REG(005e),
+ OVRD_ROPLL_SDM_RSTN_MASK | ROPLL_SDM_RSTN_MASK,
+ FIELD_PREP(OVRD_ROPLL_SDM_RSTN_MASK, 0x1) |
+ FIELD_PREP(ROPLL_SDM_RSTN_MASK, 0x1));
+ regmap_update_bits(hdptx->regmap, CMN_REG(005e), ROPLL_SDC_FRAC_EN_RBR_MASK,
+ FIELD_PREP(ROPLL_SDC_FRAC_EN_RBR_MASK, 0x1));
+ regmap_update_bits(hdptx->regmap, CMN_REG(005e), ROPLL_SDC_FRAC_EN_HBR_MASK,
+ FIELD_PREP(ROPLL_SDC_FRAC_EN_HBR_MASK, 0x1));
+ regmap_update_bits(hdptx->regmap, CMN_REG(005e), ROPLL_SDC_FRAC_EN_HBR2_MASK,
+ FIELD_PREP(ROPLL_SDC_FRAC_EN_HBR2_MASK, 0x1));
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(005f),
+ OVRD_ROPLL_SDC_RSTN_MASK | ROPLL_SDC_RSTN_MASK,
+ FIELD_PREP(OVRD_ROPLL_SDC_RSTN_MASK, 0x1) |
+ FIELD_PREP(ROPLL_SDC_RSTN_MASK, 0x1));
+ regmap_write(hdptx->regmap, CMN_REG(0060),
+ FIELD_PREP(ROPLL_SDM_DENOMINATOR_MASK, 0x21));
+ regmap_write(hdptx->regmap, CMN_REG(0061),
+ FIELD_PREP(ROPLL_SDM_DENOMINATOR_MASK, 0x27));
+ regmap_write(hdptx->regmap, CMN_REG(0062),
+ FIELD_PREP(ROPLL_SDM_DENOMINATOR_MASK, 0x27));
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(0064),
+ ROPLL_SDM_NUM_SIGN_RBR_MASK |
+ ROPLL_SDM_NUM_SIGN_HBR_MASK |
+ ROPLL_SDM_NUM_SIGN_HBR2_MASK,
+ FIELD_PREP(ROPLL_SDM_NUM_SIGN_RBR_MASK, 0x0) |
+ FIELD_PREP(ROPLL_SDM_NUM_SIGN_HBR_MASK, 0x1) |
+ FIELD_PREP(ROPLL_SDM_NUM_SIGN_HBR2_MASK, 0x1));
+ regmap_write(hdptx->regmap, CMN_REG(0065),
+ FIELD_PREP(ROPLL_SDM_NUM_MASK, 0x0));
+ regmap_write(hdptx->regmap, CMN_REG(0066),
+ FIELD_PREP(ROPLL_SDM_NUM_MASK, 0xd));
+ regmap_write(hdptx->regmap, CMN_REG(0067),
+ FIELD_PREP(ROPLL_SDM_NUM_MASK, 0xd));
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(0069), ROPLL_SDC_N_RBR_MASK,
+ FIELD_PREP(ROPLL_SDC_N_RBR_MASK, 0x2));
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(006a),
+ ROPLL_SDC_N_HBR_MASK | ROPLL_SDC_N_HBR2_MASK,
+ FIELD_PREP(ROPLL_SDC_N_HBR_MASK, 0x1) |
+ FIELD_PREP(ROPLL_SDC_N_HBR2_MASK, 0x1));
+
+ regmap_write(hdptx->regmap, CMN_REG(006c),
+ FIELD_PREP(ROPLL_SDC_NUM_MASK, 0x3));
+ regmap_write(hdptx->regmap, CMN_REG(006d),
+ FIELD_PREP(ROPLL_SDC_NUM_MASK, 0x7));
+ regmap_write(hdptx->regmap, CMN_REG(006e),
+ FIELD_PREP(ROPLL_SDC_NUM_MASK, 0x7));
+
+ regmap_write(hdptx->regmap, CMN_REG(0070),
+ FIELD_PREP(ROPLL_SDC_DENO_MASK, 0x8));
+ regmap_write(hdptx->regmap, CMN_REG(0071),
+ FIELD_PREP(ROPLL_SDC_DENO_MASK, 0x18));
+ regmap_write(hdptx->regmap, CMN_REG(0072),
+ FIELD_PREP(ROPLL_SDC_DENO_MASK, 0x18));
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(0074),
+ OVRD_ROPLL_SDC_NDIV_RSTN_MASK | ROPLL_SDC_NDIV_RSTN_MASK,
+ FIELD_PREP(OVRD_ROPLL_SDC_NDIV_RSTN_MASK, 0x1) |
+ FIELD_PREP(ROPLL_SDC_NDIV_RSTN_MASK, 0x1));
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(0077), ANA_ROPLL_SSC_CLK_DIV_SEL_MASK,
+ FIELD_PREP(ANA_ROPLL_SSC_CLK_DIV_SEL_MASK, 0x1));
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(0081), ANA_PLL_CD_TX_SER_RATE_SEL_MASK,
+ FIELD_PREP(ANA_PLL_CD_TX_SER_RATE_SEL_MASK, 0x0));
+ regmap_update_bits(hdptx->regmap, CMN_REG(0081),
+ ANA_PLL_CD_HSCLK_EAST_EN_MASK | ANA_PLL_CD_HSCLK_WEST_EN_MASK,
+ FIELD_PREP(ANA_PLL_CD_HSCLK_EAST_EN_MASK, 0x1) |
+ FIELD_PREP(ANA_PLL_CD_HSCLK_WEST_EN_MASK, 0x0));
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(0082), ANA_PLL_CD_VREG_GAIN_CTRL_MASK,
+ FIELD_PREP(ANA_PLL_CD_VREG_GAIN_CTRL_MASK, 0x4));
+ regmap_update_bits(hdptx->regmap, CMN_REG(0083), ANA_PLL_CD_VREG_ICTRL_MASK,
+ FIELD_PREP(ANA_PLL_CD_VREG_ICTRL_MASK, 0x1));
+ regmap_update_bits(hdptx->regmap, CMN_REG(0084), PLL_LCRO_CLK_SEL_MASK,
+ FIELD_PREP(PLL_LCRO_CLK_SEL_MASK, 0x1));
+ regmap_update_bits(hdptx->regmap, CMN_REG(0085), ANA_PLL_SYNC_LOSS_DET_MODE_MASK,
+ FIELD_PREP(ANA_PLL_SYNC_LOSS_DET_MODE_MASK, 0x3));
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(0087), ANA_PLL_TX_HS_CLK_EN_MASK,
+ FIELD_PREP(ANA_PLL_TX_HS_CLK_EN_MASK, 0x1));
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(0097), DIG_CLK_SEL_MASK,
+ FIELD_PREP(DIG_CLK_SEL_MASK, 0x1));
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(0099), CMN_ROPLL_ALONE_MODE_MASK,
+ FIELD_PREP(CMN_ROPLL_ALONE_MODE_MASK, 0x1));
+ regmap_update_bits(hdptx->regmap, CMN_REG(009a), HS_SPEED_SEL_MASK,
+ FIELD_PREP(HS_SPEED_SEL_MASK, 0x1));
+ regmap_update_bits(hdptx->regmap, CMN_REG(009b), LS_SPEED_SEL_MASK,
+ FIELD_PREP(LS_SPEED_SEL_MASK, 0x1));
+}
+
+static int rk_hdptx_dp_aux_init(struct rk_hdptx_phy *hdptx)
+{
+ u32 status;
+ int ret;
+
+ regmap_update_bits(hdptx->regmap, SB_REG(0102), ANA_SB_RXTERM_OFFSP_MASK,
+ FIELD_PREP(ANA_SB_RXTERM_OFFSP_MASK, 0x3));
+ regmap_update_bits(hdptx->regmap, SB_REG(0103), ANA_SB_RXTERM_OFFSN_MASK,
+ FIELD_PREP(ANA_SB_RXTERM_OFFSN_MASK, 0x3));
+ regmap_update_bits(hdptx->regmap, SB_REG(0104), SB_AUX_EN_MASK,
+ FIELD_PREP(SB_AUX_EN_MASK, 0x1));
+ regmap_update_bits(hdptx->regmap, SB_REG(0105), ANA_SB_TX_HLVL_PROG_MASK,
+ FIELD_PREP(ANA_SB_TX_HLVL_PROG_MASK, 0x7));
+ regmap_update_bits(hdptx->regmap, SB_REG(0106), ANA_SB_TX_LLVL_PROG_MASK,
+ FIELD_PREP(ANA_SB_TX_LLVL_PROG_MASK, 0x7));
+
+ regmap_update_bits(hdptx->regmap, SB_REG(010d), ANA_SB_DMRX_LPBK_DATA_MASK,
+ FIELD_PREP(ANA_SB_DMRX_LPBK_DATA_MASK, 0x1));
+
+ regmap_update_bits(hdptx->regmap, SB_REG(010f), ANA_SB_VREG_GAIN_CTRL_MASK,
+ FIELD_PREP(ANA_SB_VREG_GAIN_CTRL_MASK, 0x0));
+ regmap_update_bits(hdptx->regmap, SB_REG(0110),
+ ANA_SB_VREG_OUT_SEL_MASK | ANA_SB_VREG_REF_SEL_MASK,
+ FIELD_PREP(ANA_SB_VREG_OUT_SEL_MASK, 0x1) |
+ FIELD_PREP(ANA_SB_VREG_REF_SEL_MASK, 0x1));
+
+ regmap_update_bits(hdptx->regmap, SB_REG(0113),
+ SB_RX_RCAL_OPT_CODE_MASK | SB_RX_RTERM_CTRL_MASK,
+ FIELD_PREP(SB_RX_RCAL_OPT_CODE_MASK, 0x1) |
+ FIELD_PREP(SB_RX_RTERM_CTRL_MASK, 0x3));
+ regmap_update_bits(hdptx->regmap, SB_REG(0114),
+ SB_TG_SB_EN_DELAY_TIME_MASK | SB_TG_RXTERM_EN_DELAY_TIME_MASK,
+ FIELD_PREP(SB_TG_SB_EN_DELAY_TIME_MASK, 0x2) |
+ FIELD_PREP(SB_TG_RXTERM_EN_DELAY_TIME_MASK, 0x2));
+ regmap_update_bits(hdptx->regmap, SB_REG(0115),
+ SB_READY_DELAY_TIME_MASK | SB_TG_OSC_EN_DELAY_TIME_MASK,
+ FIELD_PREP(SB_READY_DELAY_TIME_MASK, 0x2) |
+ FIELD_PREP(SB_TG_OSC_EN_DELAY_TIME_MASK, 0x2));
+ regmap_update_bits(hdptx->regmap, SB_REG(0116),
+ AFC_RSTN_DELAY_TIME_MASK,
+ FIELD_PREP(AFC_RSTN_DELAY_TIME_MASK, 0x2));
+ regmap_update_bits(hdptx->regmap, SB_REG(0117),
+ FAST_PULSE_TIME_MASK,
+ FIELD_PREP(FAST_PULSE_TIME_MASK, 0x4));
+ regmap_update_bits(hdptx->regmap, SB_REG(0118),
+ SB_TG_EARC_DMRX_RECVRD_CLK_CNT_MASK,
+ FIELD_PREP(SB_TG_EARC_DMRX_RECVRD_CLK_CNT_MASK, 0xa));
+
+ regmap_update_bits(hdptx->regmap, SB_REG(011a), SB_TG_CNT_RUN_NO_7_0_MASK,
+ FIELD_PREP(SB_TG_CNT_RUN_NO_7_0_MASK, 0x3));
+ regmap_update_bits(hdptx->regmap, SB_REG(011b),
+ SB_EARC_SIG_DET_BYPASS_MASK | SB_AFC_TOL_MASK,
+ FIELD_PREP(SB_EARC_SIG_DET_BYPASS_MASK, 0x1) |
+ FIELD_PREP(SB_AFC_TOL_MASK, 0x3));
+ regmap_update_bits(hdptx->regmap, SB_REG(011c), SB_AFC_STB_NUM_MASK,
+ FIELD_PREP(SB_AFC_STB_NUM_MASK, 0x4));
+ regmap_update_bits(hdptx->regmap, SB_REG(011d), SB_TG_OSC_CNT_MIN_MASK,
+ FIELD_PREP(SB_TG_OSC_CNT_MIN_MASK, 0x67));
+ regmap_update_bits(hdptx->regmap, SB_REG(011e), SB_TG_OSC_CNT_MAX_MASK,
+ FIELD_PREP(SB_TG_OSC_CNT_MAX_MASK, 0x6a));
+ regmap_update_bits(hdptx->regmap, SB_REG(011f), SB_PWM_AFC_CTRL_MASK,
+ FIELD_PREP(SB_PWM_AFC_CTRL_MASK, 0x5));
+ regmap_update_bits(hdptx->regmap, SB_REG(011f), SB_RCAL_RSTN_MASK,
+ FIELD_PREP(SB_RCAL_RSTN_MASK, 0x1));
+ regmap_update_bits(hdptx->regmap, SB_REG(0120), SB_AUX_EN_IN_MASK,
+ FIELD_PREP(SB_AUX_EN_IN_MASK, 0x1));
+
+ regmap_update_bits(hdptx->regmap, SB_REG(0102), OVRD_SB_RXTERM_EN_MASK,
+ FIELD_PREP(OVRD_SB_RXTERM_EN_MASK, 0x1));
+ regmap_update_bits(hdptx->regmap, SB_REG(0103), OVRD_SB_RX_RESCAL_DONE_MASK,
+ FIELD_PREP(OVRD_SB_RX_RESCAL_DONE_MASK, 0x1));
+ regmap_update_bits(hdptx->regmap, SB_REG(0104), OVRD_SB_EN_MASK,
+ FIELD_PREP(OVRD_SB_EN_MASK, 0x1));
+ regmap_update_bits(hdptx->regmap, SB_REG(0104), OVRD_SB_AUX_EN_MASK,
+ FIELD_PREP(OVRD_SB_AUX_EN_MASK, 0x1));
+
+ regmap_update_bits(hdptx->regmap, SB_REG(010f), OVRD_SB_VREG_EN_MASK,
+ FIELD_PREP(OVRD_SB_VREG_EN_MASK, 0x1));
+
+ regmap_write(hdptx->grf, GRF_HDPTX_CON0,
+ FIELD_PREP_WM16(HDPTX_I_BGR_EN, 1) |
+ FIELD_PREP_WM16(HDPTX_I_BIAS_EN, 1));
+ usleep_range(20, 25);
+
+ reset_control_deassert(hdptx->rsts[RST_INIT].rstc);
+ usleep_range(20, 25);
+ reset_control_deassert(hdptx->rsts[RST_CMN].rstc);
+ usleep_range(20, 25);
+
+ regmap_update_bits(hdptx->regmap, SB_REG(0103), OVRD_SB_RX_RESCAL_DONE_MASK,
+ FIELD_PREP(OVRD_SB_RX_RESCAL_DONE_MASK, 0x1));
+ usleep_range(100, 110);
+ regmap_update_bits(hdptx->regmap, SB_REG(0104), SB_EN_MASK,
+ FIELD_PREP(SB_EN_MASK, 0x1));
+ usleep_range(100, 110);
+ regmap_update_bits(hdptx->regmap, SB_REG(0102), SB_RXTERM_EN_MASK,
+ FIELD_PREP(SB_RXTERM_EN_MASK, 0x1));
+ usleep_range(20, 25);
+ regmap_update_bits(hdptx->regmap, SB_REG(010f), SB_VREG_EN_MASK,
+ FIELD_PREP(SB_VREG_EN_MASK, 0x1));
+ usleep_range(20, 25);
+ regmap_update_bits(hdptx->regmap, SB_REG(0104), SB_AUX_EN_MASK,
+ FIELD_PREP(SB_AUX_EN_MASK, 0x1));
+ usleep_range(100, 110);
+
+ ret = regmap_read_poll_timeout(hdptx->grf, GRF_HDPTX_STATUS,
+ status, FIELD_GET(HDPTX_O_SB_RDY, status),
+ 50, 1000);
+ if (ret) {
+ dev_err(hdptx->dev, "Failed to get phy sb ready: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int rk_hdptx_phy_power_on(struct phy *phy)
+{
+ struct rk_hdptx_phy *hdptx = phy_get_drvdata(phy);
+ enum phy_mode mode = phy_get_mode(phy);
+ int ret, lane;
+
+ ret = rk_hdptx_phy_consumer_get(hdptx);
+ if (ret)
+ return ret;
+
+ if (mode == PHY_MODE_DP) {
+ regmap_write(hdptx->grf, GRF_HDPTX_CON0,
+ FIELD_PREP_WM16(HDPTX_MODE_SEL, 1));
+
+ for (lane = 0; lane < 4; lane++) {
+ regmap_update_bits(hdptx->regmap, LANE_REG(031e) + 0x400 * lane,
+ LN_POLARITY_INV_MASK | LN_LANE_MODE_MASK,
+ FIELD_PREP(LN_POLARITY_INV_MASK, 0) |
+ FIELD_PREP(LN_LANE_MODE_MASK, 1));
+ }
+
+ regmap_update_bits(hdptx->regmap, LNTOP_REG(0200), PROTOCOL_SEL_MASK,
+ FIELD_PREP(PROTOCOL_SEL_MASK, 0x0));
+ regmap_update_bits(hdptx->regmap, LNTOP_REG(0206), DATA_BUS_WIDTH_MASK,
+ FIELD_PREP(DATA_BUS_WIDTH_MASK, 0x1));
+ regmap_update_bits(hdptx->regmap, LNTOP_REG(0206), DATA_BUS_WIDTH_SEL_MASK,
+ FIELD_PREP(DATA_BUS_WIDTH_SEL_MASK, 0x0));
+
+ rk_hdptx_dp_pll_init(hdptx);
+
+ ret = rk_hdptx_dp_aux_init(hdptx);
+ } else {
+ dev_dbg(hdptx->dev, "%s rate=%llu bpc=%u\n", __func__,
+ hdptx->hdmi_cfg.rate, hdptx->hdmi_cfg.bpc);
+
+ if (hdptx->pll_config_dirty)
+ ret = rk_hdptx_pll_cmn_config(hdptx);
+
+ if (!ret) {
+ regmap_write(hdptx->grf, GRF_HDPTX_CON0,
+ FIELD_PREP_WM16(HDPTX_MODE_SEL, 0));
+
+ if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL)
+ ret = rk_hdptx_frl_lcpll_mode_config(hdptx);
+ else
+ ret = rk_hdptx_tmds_ropll_mode_config(hdptx);
+ }
+ }
+
+ if (ret)
+ rk_hdptx_phy_consumer_put(hdptx, true);
+
+ return ret;
+}
+
+static int rk_hdptx_phy_power_off(struct phy *phy)
+{
+ struct rk_hdptx_phy *hdptx = phy_get_drvdata(phy);
+
+ return rk_hdptx_phy_consumer_put(hdptx, false);
+}
+
+static int rk_hdptx_phy_verify_hdmi_config(struct rk_hdptx_phy *hdptx,
+ struct phy_configure_opts_hdmi *hdmi_in,
+ struct rk_hdptx_hdmi_cfg *hdmi_out)
+{
+ int i;
+
+ if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL) {
+ unsigned long long frl_rate = 100000000ULL * hdmi_in->frl.lanes *
+ hdmi_in->frl.rate_per_lane;
+
+ switch (hdmi_in->frl.rate_per_lane) {
+ case 3:
+ case 6:
+ case 8:
+ case 10:
+ case 12:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (!hdmi_in->frl.lanes || hdmi_in->frl.lanes > 4)
+ return -EINVAL;
+
+ if (frl_rate != FRL_8G4L_RATE) {
+ for (i = 0; i < ARRAY_SIZE(rk_hdptx_frl_lcpll_cfg); i++)
+ if (frl_rate == rk_hdptx_frl_lcpll_cfg[i].rate)
+ break;
+ if (i == ARRAY_SIZE(rk_hdptx_frl_lcpll_cfg))
+ return -EINVAL;
+ }
+
+ if (hdmi_out)
+ hdmi_out->rate = frl_rate;
+ } else {
+ if (!hdmi_in->tmds_char_rate || hdmi_in->tmds_char_rate > HDMI20_MAX_RATE)
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(rk_hdptx_tmds_ropll_cfg); i++)
+ if (hdmi_in->tmds_char_rate == rk_hdptx_tmds_ropll_cfg[i].rate)
+ break;
+
+ if (i == ARRAY_SIZE(rk_hdptx_tmds_ropll_cfg) &&
+ !rk_hdptx_phy_clk_pll_calc(hdmi_in->tmds_char_rate, NULL))
+ return -EINVAL;
+
+ if (hdmi_out)
+ hdmi_out->rate = hdmi_in->tmds_char_rate;
+ }
+
+ switch (hdmi_in->bpc) {
+ case 0:
+ case 8:
+ case 10:
+ case 12:
+ case 16:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (hdmi_out)
+ hdmi_out->bpc = hdmi_in->bpc ?: 8;
+
+ return 0;
+}
+
+static int rk_hdptx_phy_verify_dp_config(struct rk_hdptx_phy *hdptx,
+ struct phy_configure_opts_dp *dp)
+{
+ int i;
+
+ if (dp->set_rate) {
+ switch (dp->link_rate) {
+ case 1620:
+ case 2700:
+ case 5400:
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ if (dp->set_lanes) {
+ switch (dp->lanes) {
+ case 1:
+ case 2:
+ case 4:
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ if (dp->set_voltages) {
+ for (i = 0; i < hdptx->lanes; i++) {
+ if (dp->voltage[i] > 3 || dp->pre[i] > 3)
+ return -EINVAL;
+
+ if (dp->voltage[i] + dp->pre[i] > 3)
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int rk_hdptx_phy_set_rate(struct rk_hdptx_phy *hdptx,
+ struct phy_configure_opts_dp *dp)
+{
+ u32 bw, status;
+ int ret;
+
+ regmap_write(hdptx->grf, GRF_HDPTX_CON0, FIELD_PREP_WM16(HDPTX_I_PLL_EN, 0));
+
+ switch (dp->link_rate) {
+ case 1620:
+ bw = DP_BW_RBR;
+ break;
+ case 2700:
+ bw = DP_BW_HBR;
+ break;
+ case 5400:
+ bw = DP_BW_HBR2;
+ break;
+ default:
+ return -EINVAL;
+ }
+ hdptx->link_rate = dp->link_rate;
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(0008), OVRD_LCPLL_EN_MASK | LCPLL_EN_MASK,
+ FIELD_PREP(OVRD_LCPLL_EN_MASK, 0x1) |
+ FIELD_PREP(LCPLL_EN_MASK, 0x0));
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(003d), OVRD_ROPLL_EN_MASK | ROPLL_EN_MASK,
+ FIELD_PREP(OVRD_ROPLL_EN_MASK, 0x1) |
+ FIELD_PREP(ROPLL_EN_MASK, 0x1));
+
+ if (dp->ssc) {
+ regmap_update_bits(hdptx->regmap, CMN_REG(0074),
+ OVRD_ROPLL_SSC_EN_MASK | ROPLL_SSC_EN_MASK,
+ FIELD_PREP(OVRD_ROPLL_SSC_EN_MASK, 0x1) |
+ FIELD_PREP(ROPLL_SSC_EN_MASK, 0x1));
+ regmap_write(hdptx->regmap, CMN_REG(0075),
+ FIELD_PREP(ANA_ROPLL_SSC_FM_DEVIATION_MASK, 0xc));
+ regmap_update_bits(hdptx->regmap, CMN_REG(0076),
+ ANA_ROPLL_SSC_FM_FREQ_MASK,
+ FIELD_PREP(ANA_ROPLL_SSC_FM_FREQ_MASK, 0x1f));
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(0099), SSC_EN_MASK,
+ FIELD_PREP(SSC_EN_MASK, 0x2));
+ } else {
+ regmap_update_bits(hdptx->regmap, CMN_REG(0074),
+ OVRD_ROPLL_SSC_EN_MASK | ROPLL_SSC_EN_MASK,
+ FIELD_PREP(OVRD_ROPLL_SSC_EN_MASK, 0x1) |
+ FIELD_PREP(ROPLL_SSC_EN_MASK, 0x0));
+ regmap_write(hdptx->regmap, CMN_REG(0075),
+ FIELD_PREP(ANA_ROPLL_SSC_FM_DEVIATION_MASK, 0x20));
+ regmap_update_bits(hdptx->regmap, CMN_REG(0076),
+ ANA_ROPLL_SSC_FM_FREQ_MASK,
+ FIELD_PREP(ANA_ROPLL_SSC_FM_FREQ_MASK, 0xc));
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(0099), SSC_EN_MASK,
+ FIELD_PREP(SSC_EN_MASK, 0x0));
+ }
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(0095), DP_TX_LINK_BW_MASK,
+ FIELD_PREP(DP_TX_LINK_BW_MASK, bw));
+
+ regmap_write(hdptx->grf, GRF_HDPTX_CON0, FIELD_PREP_WM16(HDPTX_I_PLL_EN, 1));
+
+ ret = regmap_read_poll_timeout(hdptx->grf, GRF_HDPTX_STATUS,
+ status, FIELD_GET(HDPTX_O_PLL_LOCK_DONE, status),
+ 50, 1000);
+ if (ret) {
+ dev_err(hdptx->dev, "Failed to get phy pll lock: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int rk_hdptx_phy_set_lanes(struct rk_hdptx_phy *hdptx,
+ struct phy_configure_opts_dp *dp)
+{
+ hdptx->lanes = dp->lanes;
+
+ regmap_update_bits(hdptx->regmap, LNTOP_REG(0207), LANE_EN_MASK,
+ FIELD_PREP(LANE_EN_MASK, GENMASK(hdptx->lanes - 1, 0)));
+
+ return 0;
+}
+
+static void rk_hdptx_phy_set_voltage(struct rk_hdptx_phy *hdptx,
+ struct phy_configure_opts_dp *dp,
+ u8 lane)
+{
+ const struct tx_drv_ctrl *ctrl;
+ u32 offset = lane * 0x400;
+
+ switch (hdptx->link_rate) {
+ case 1620:
+ ctrl = &tx_drv_ctrl_rbr[dp->voltage[lane]][dp->pre[lane]];
+ regmap_update_bits(hdptx->regmap, LANE_REG(030a) + offset,
+ LN_TX_JEQ_EVEN_CTRL_RBR_MASK,
+ FIELD_PREP(LN_TX_JEQ_EVEN_CTRL_RBR_MASK,
+ ctrl->tx_jeq_even_ctrl));
+ regmap_update_bits(hdptx->regmap, LANE_REG(030c) + offset,
+ LN_TX_JEQ_ODD_CTRL_RBR_MASK,
+ FIELD_PREP(LN_TX_JEQ_ODD_CTRL_RBR_MASK,
+ ctrl->tx_jeq_odd_ctrl));
+ regmap_update_bits(hdptx->regmap, LANE_REG(0311) + offset,
+ LN_TX_SER_40BIT_EN_RBR_MASK,
+ FIELD_PREP(LN_TX_SER_40BIT_EN_RBR_MASK, 0x1));
+ break;
+ case 2700:
+ ctrl = &tx_drv_ctrl_hbr[dp->voltage[lane]][dp->pre[lane]];
+ regmap_update_bits(hdptx->regmap, LANE_REG(030b) + offset,
+ LN_TX_JEQ_EVEN_CTRL_HBR_MASK,
+ FIELD_PREP(LN_TX_JEQ_EVEN_CTRL_HBR_MASK,
+ ctrl->tx_jeq_even_ctrl));
+ regmap_update_bits(hdptx->regmap, LANE_REG(030d) + offset,
+ LN_TX_JEQ_ODD_CTRL_HBR_MASK,
+ FIELD_PREP(LN_TX_JEQ_ODD_CTRL_HBR_MASK,
+ ctrl->tx_jeq_odd_ctrl));
+ regmap_update_bits(hdptx->regmap, LANE_REG(0311) + offset,
+ LN_TX_SER_40BIT_EN_HBR_MASK,
+ FIELD_PREP(LN_TX_SER_40BIT_EN_HBR_MASK, 0x1));
+ break;
+ case 5400:
+ default:
+ ctrl = &tx_drv_ctrl_hbr2[dp->voltage[lane]][dp->pre[lane]];
+ regmap_update_bits(hdptx->regmap, LANE_REG(030b) + offset,
+ LN_TX_JEQ_EVEN_CTRL_HBR2_MASK,
+ FIELD_PREP(LN_TX_JEQ_EVEN_CTRL_HBR2_MASK,
+ ctrl->tx_jeq_even_ctrl));
+ regmap_update_bits(hdptx->regmap, LANE_REG(030d) + offset,
+ LN_TX_JEQ_ODD_CTRL_HBR2_MASK,
+ FIELD_PREP(LN_TX_JEQ_ODD_CTRL_HBR2_MASK,
+ ctrl->tx_jeq_odd_ctrl));
+ regmap_update_bits(hdptx->regmap, LANE_REG(0311) + offset,
+ LN_TX_SER_40BIT_EN_HBR2_MASK,
+ FIELD_PREP(LN_TX_SER_40BIT_EN_HBR2_MASK, 0x1));
+ break;
+ }
+
+ regmap_update_bits(hdptx->regmap, LANE_REG(0303) + offset,
+ OVRD_LN_TX_DRV_LVL_CTRL_MASK | LN_TX_DRV_LVL_CTRL_MASK,
+ FIELD_PREP(OVRD_LN_TX_DRV_LVL_CTRL_MASK, 0x1) |
+ FIELD_PREP(LN_TX_DRV_LVL_CTRL_MASK,
+ ctrl->tx_drv_lvl_ctrl));
+ regmap_update_bits(hdptx->regmap, LANE_REG(0304) + offset,
+ OVRD_LN_TX_DRV_POST_LVL_CTRL_MASK |
+ LN_TX_DRV_POST_LVL_CTRL_MASK,
+ FIELD_PREP(OVRD_LN_TX_DRV_POST_LVL_CTRL_MASK, 0x1) |
+ FIELD_PREP(LN_TX_DRV_POST_LVL_CTRL_MASK,
+ ctrl->tx_drv_post_lvl_ctrl));
+ regmap_update_bits(hdptx->regmap, LANE_REG(0305) + offset,
+ OVRD_LN_TX_DRV_PRE_LVL_CTRL_MASK |
+ LN_TX_DRV_PRE_LVL_CTRL_MASK,
+ FIELD_PREP(OVRD_LN_TX_DRV_PRE_LVL_CTRL_MASK, 0x1) |
+ FIELD_PREP(LN_TX_DRV_PRE_LVL_CTRL_MASK,
+ ctrl->tx_drv_pre_lvl_ctrl));
+ regmap_update_bits(hdptx->regmap, LANE_REG(0306) + offset,
+ LN_ANA_TX_DRV_IDRV_IDN_CTRL_MASK |
+ LN_ANA_TX_DRV_IDRV_IUP_CTRL_MASK |
+ LN_ANA_TX_DRV_ACCDRV_EN_MASK,
+ FIELD_PREP(LN_ANA_TX_DRV_IDRV_IDN_CTRL_MASK,
+ ctrl->ana_tx_drv_idrv_idn_ctrl) |
+ FIELD_PREP(LN_ANA_TX_DRV_IDRV_IUP_CTRL_MASK,
+ ctrl->ana_tx_drv_idrv_iup_ctrl) |
+ FIELD_PREP(LN_ANA_TX_DRV_ACCDRV_EN_MASK,
+ ctrl->ana_tx_drv_accdrv_en));
+ regmap_update_bits(hdptx->regmap, LANE_REG(0307) + offset,
+ LN_ANA_TX_DRV_ACCDRV_POL_SEL_MASK |
+ LN_ANA_TX_DRV_ACCDRV_CTRL_MASK,
+ FIELD_PREP(LN_ANA_TX_DRV_ACCDRV_POL_SEL_MASK, 0x1) |
+ FIELD_PREP(LN_ANA_TX_DRV_ACCDRV_CTRL_MASK,
+ ctrl->ana_tx_drv_accdrv_ctrl));
+
+ regmap_update_bits(hdptx->regmap, LANE_REG(030a) + offset,
+ LN_ANA_TX_JEQ_EN_MASK,
+ FIELD_PREP(LN_ANA_TX_JEQ_EN_MASK, ctrl->ana_tx_jeq_en));
+
+ regmap_update_bits(hdptx->regmap, LANE_REG(0310) + offset,
+ LN_ANA_TX_SYNC_LOSS_DET_MODE_MASK,
+ FIELD_PREP(LN_ANA_TX_SYNC_LOSS_DET_MODE_MASK, 0x3));
+
+ regmap_update_bits(hdptx->regmap, LANE_REG(0316) + offset,
+ LN_ANA_TX_SER_VREG_GAIN_CTRL_MASK,
+ FIELD_PREP(LN_ANA_TX_SER_VREG_GAIN_CTRL_MASK, 0x2));
+
+ regmap_update_bits(hdptx->regmap, LANE_REG(031b) + offset,
+ LN_ANA_TX_RESERVED_MASK,
+ FIELD_PREP(LN_ANA_TX_RESERVED_MASK, 0x1));
+}
+
+static int rk_hdptx_phy_set_voltages(struct rk_hdptx_phy *hdptx,
+ struct phy_configure_opts_dp *dp)
+{
+ u8 lane;
+ u32 status;
+ int ret;
+
+ for (lane = 0; lane < hdptx->lanes; lane++)
+ rk_hdptx_phy_set_voltage(hdptx, dp, lane);
+
+ reset_control_deassert(hdptx->rsts[RST_LANE].rstc);
+
+ ret = regmap_read_poll_timeout(hdptx->grf, GRF_HDPTX_STATUS,
+ status, FIELD_GET(HDPTX_O_PHY_RDY, status),
+ 50, 5000);
+ if (ret) {
+ dev_err(hdptx->dev, "Failed to get phy ready: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int rk_hdptx_phy_set_mode(struct phy *phy, enum phy_mode mode, int submode)
+{
+ struct rk_hdptx_phy *hdptx = phy_get_drvdata(phy);
+
+ if (mode == PHY_MODE_DP)
+ return 0;
+
+ if (mode != PHY_MODE_HDMI) {
+ dev_err(&phy->dev, "invalid PHY mode: %d\n", mode);
+ return -EINVAL;
+ }
+
+ switch (submode) {
+ case PHY_HDMI_MODE_TMDS:
+ case PHY_HDMI_MODE_FRL:
+ hdptx->hdmi_cfg.mode = submode;
+ break;
+ default:
+ dev_err(&phy->dev, "invalid HDMI mode: %d\n", submode);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int rk_hdptx_phy_configure(struct phy *phy, union phy_configure_opts *opts)
+{
+ struct rk_hdptx_phy *hdptx = phy_get_drvdata(phy);
+ enum phy_mode mode = phy_get_mode(phy);
+ int ret;
+
+ if (mode != PHY_MODE_DP) {
+ ret = rk_hdptx_phy_verify_hdmi_config(hdptx, &opts->hdmi, &hdptx->hdmi_cfg);
+ if (ret) {
+ dev_err(hdptx->dev, "invalid hdmi params for phy configure\n");
+ } else {
+ hdptx->pll_config_dirty = true;
+
+ dev_dbg(hdptx->dev, "%s %s rate=%llu bpc=%u\n", __func__,
+ hdptx->hdmi_cfg.mode ? "FRL" : "TMDS",
+ hdptx->hdmi_cfg.rate, hdptx->hdmi_cfg.bpc);
+ }
+
+ return ret;
+ }
+
+ ret = rk_hdptx_phy_verify_dp_config(hdptx, &opts->dp);
+ if (ret) {
+ dev_err(hdptx->dev, "invalid dp params for phy configure\n");
+ return ret;
+ }
+
+ if (opts->dp.set_rate) {
+ ret = rk_hdptx_phy_set_rate(hdptx, &opts->dp);
+ if (ret) {
+ dev_err(hdptx->dev, "failed to set rate: %d\n", ret);
+ return ret;
+ }
+ }
+
+ if (opts->dp.set_lanes) {
+ ret = rk_hdptx_phy_set_lanes(hdptx, &opts->dp);
+ if (ret) {
+ dev_err(hdptx->dev, "failed to set lanes: %d\n", ret);
+ return ret;
+ }
+ }
+
+ if (opts->dp.set_voltages) {
+ ret = rk_hdptx_phy_set_voltages(hdptx, &opts->dp);
+ if (ret) {
+ dev_err(hdptx->dev, "failed to set voltages: %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int rk_hdptx_phy_validate(struct phy *phy, enum phy_mode mode,
+ int submode, union phy_configure_opts *opts)
+{
+ struct rk_hdptx_phy *hdptx = phy_get_drvdata(phy);
+
+ if (mode != PHY_MODE_DP)
+ return rk_hdptx_phy_verify_hdmi_config(hdptx, &opts->hdmi, NULL);
+
+ return rk_hdptx_phy_verify_dp_config(hdptx, &opts->dp);
+}
+
+static const struct phy_ops rk_hdptx_phy_ops = {
+ .power_on = rk_hdptx_phy_power_on,
+ .power_off = rk_hdptx_phy_power_off,
+ .set_mode = rk_hdptx_phy_set_mode,
+ .configure = rk_hdptx_phy_configure,
+ .validate = rk_hdptx_phy_validate,
+ .owner = THIS_MODULE,
+};
+
+static struct rk_hdptx_phy *to_rk_hdptx_phy(struct clk_hw *hw)
+{
+ return container_of(hw, struct rk_hdptx_phy, hw);
+}
+
+static int rk_hdptx_phy_clk_prepare(struct clk_hw *hw)
+{
+ struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw);
+
+ return rk_hdptx_phy_consumer_get(hdptx);
+}
+
+static void rk_hdptx_phy_clk_unprepare(struct clk_hw *hw)
+{
+ struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw);
+
+ rk_hdptx_phy_consumer_put(hdptx, true);
+}
+
+#define PLL_REF_CLK 24000000ULL
+
+static u64 rk_hdptx_phy_clk_calc_rate_from_pll_cfg(struct rk_hdptx_phy *hdptx)
+{
+ struct lcpll_config lcpll_hw;
+ struct ropll_config ropll_hw;
+ u64 fout, sdm;
+ u32 mode, bpc, val;
+ int ret, i;
+
+ ret = regmap_read(hdptx->regmap, CMN_REG(0008), &mode);
+ if (ret)
+ return 0;
+
+ if (mode & LCPLL_LCVCO_MODE_EN_MASK) {
+ ret = regmap_read(hdptx->regmap, CMN_REG(0020), &val);
+ if (ret)
+ return 0;
+ lcpll_hw.pms_mdiv = val;
+
+ ret = regmap_read(hdptx->regmap, CMN_REG(0023), &val);
+ if (ret)
+ return 0;
+ lcpll_hw.pms_sdiv = FIELD_GET(LCPLL_PMS_SDIV_HBR_MASK, val);
+
+ ret = regmap_read(hdptx->regmap, CMN_REG(002B), &val);
+ if (ret)
+ return 0;
+ lcpll_hw.sdm_num_sign = val;
+
+ ret = regmap_read(hdptx->regmap, CMN_REG(002C), &val);
+ if (ret)
+ return 0;
+ lcpll_hw.sdm_num = val;
+
+ ret = regmap_read(hdptx->regmap, CMN_REG(002A), &val);
+ if (ret)
+ return 0;
+ lcpll_hw.sdm_deno = val;
+
+ ret = regmap_read(hdptx->regmap, CMN_REG(002D), &val);
+ if (ret)
+ return 0;
+ lcpll_hw.sdc_n = FIELD_GET(LCPLL_SDC_N_MASK, val);
+
+ ret = regmap_read(hdptx->grf, GRF_HDPTX_CON0, &val);
+ if (ret)
+ return 0;
+
+ if (val & LC_REF_CLK_SEL) {
+ if (lcpll_hw.pms_mdiv == 0x6b &&
+ lcpll_hw.sdm_num_sign == 0x01 &&
+ lcpll_hw.sdm_num == 0x02 &&
+ lcpll_hw.sdm_deno == 0x09 &&
+ lcpll_hw.sdc_n == FIELD_GET(LCPLL_SDC_N_MASK, 0x02))
+ return FRL_8G4L_RATE;
+ } else {
+ const struct lcpll_config *cfg;
+
+ for (i = 0; i < ARRAY_SIZE(rk_hdptx_frl_lcpll_cfg); i++) {
+ cfg = &rk_hdptx_frl_lcpll_cfg[i];
+
+ if (cfg->pms_mdiv == lcpll_hw.pms_mdiv &&
+ cfg->pms_sdiv == lcpll_hw.pms_sdiv &&
+ cfg->sdm_num_sign == lcpll_hw.sdm_num_sign &&
+ cfg->sdm_num == lcpll_hw.sdm_num &&
+ cfg->sdm_deno == lcpll_hw.sdm_deno &&
+ cfg->sdc_n == lcpll_hw.sdc_n)
+ return cfg->rate;
+ }
+ }
+
+ dev_dbg(hdptx->dev, "%s no FRL match found\n", __func__);
+ return 0;
+ }
+
+ ret = regmap_read(hdptx->regmap, CMN_REG(0051), &val);
+ if (ret)
+ return 0;
+ ropll_hw.pms_mdiv = val;
+
+ ret = regmap_read(hdptx->regmap, CMN_REG(005E), &val);
+ if (ret)
+ return 0;
+ ropll_hw.sdm_en = FIELD_GET(ROPLL_SDM_EN_MASK, val);
+
+ ret = regmap_read(hdptx->regmap, CMN_REG(0064), &val);
+ if (ret)
+ return 0;
+ ropll_hw.sdm_num_sign = FIELD_GET(ROPLL_SDM_NUM_SIGN_RBR_MASK, val);
+
+ ret = regmap_read(hdptx->regmap, CMN_REG(0065), &val);
+ if (ret)
+ return 0;
+ ropll_hw.sdm_num = val;
+
+ ret = regmap_read(hdptx->regmap, CMN_REG(0060), &val);
+ if (ret)
+ return 0;
+ ropll_hw.sdm_deno = val;
+
+ ret = regmap_read(hdptx->regmap, CMN_REG(0069), &val);
+ if (ret)
+ return 0;
+ ropll_hw.sdc_n = FIELD_GET(ROPLL_SDC_N_RBR_MASK, val) + 3;
+
+ ret = regmap_read(hdptx->regmap, CMN_REG(006c), &val);
+ if (ret)
+ return 0;
+ ropll_hw.sdc_num = val;
+
+ ret = regmap_read(hdptx->regmap, CMN_REG(0070), &val);
+ if (ret)
+ return 0;
+ ropll_hw.sdc_deno = val;
+
+ ret = regmap_read(hdptx->regmap, CMN_REG(0086), &val);
+ if (ret)
+ return 0;
+ ropll_hw.pms_sdiv = FIELD_GET(PLL_PCG_POSTDIV_SEL_MASK, val) + 1;
+ bpc = (FIELD_GET(PLL_PCG_CLK_SEL_MASK, val) << 1) + 8;
+
+ fout = PLL_REF_CLK * ropll_hw.pms_mdiv;
+ if (ropll_hw.sdm_en) {
+ val = 16U * ropll_hw.sdm_deno *
+ (ropll_hw.sdc_deno * ropll_hw.sdc_n - ropll_hw.sdc_num);
+ if (!val) {
+ /*
+ * The PLL config currently stored in hardware can't be
+ * translated into a rate. The next .set_rate() should
+ * program a valid configuration and help with recovery.
+ */
+ dev_dbg(hdptx->dev, "Invalid ROPLL hw state: deno == 0\n");
+ return 0;
+ }
+
+ sdm = div_u64(PLL_REF_CLK * ropll_hw.sdc_deno *
+ ropll_hw.pms_mdiv * ropll_hw.sdm_num, val);
+
+ if (ropll_hw.sdm_num_sign) {
+ if (sdm > fout) {
+ /*
+ * Similarly to the case above, it is expected
+ * the next .set_rate() will help with recovery.
+ */
+ dev_dbg(hdptx->dev, "Invalid ROPLL hw state: sdm > fout\n");
+ return 0;
+ }
+
+ fout = fout - sdm;
+ } else {
+ fout = fout + sdm;
+ }
+ }
+
+ return DIV_ROUND_CLOSEST_ULL(fout * 2 * 8, ropll_hw.pms_sdiv * 10 * bpc);
+}
+
+static unsigned long rk_hdptx_phy_clk_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw);
+ u32 status;
+ int ret;
+
+ ret = regmap_read(hdptx->grf, GRF_HDPTX_CON0, &status);
+ if (ret || !(status & HDPTX_I_PLL_EN))
+ return 0;
+
+ return rk_hdptx_phy_clk_calc_rate_from_pll_cfg(hdptx);
+}
+
+static int rk_hdptx_phy_clk_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw);
+
+ /*
+ * For uncommitted PLL configuration, invalidate the current clock rate
+ * to ensure rk_hdptx_phy_clk_set_rate() will be always invoked.
+ * Otherwise, restrict the rate according to the PHY link setup.
+ */
+ if (hdptx->pll_config_dirty)
+ req->rate = 0;
+ else if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL)
+ req->rate = hdptx->hdmi_cfg.rate;
+ else
+ req->rate = DIV_ROUND_CLOSEST_ULL(hdptx->hdmi_cfg.rate * 8,
+ hdptx->hdmi_cfg.bpc);
+
+ return 0;
+}
+
+static int rk_hdptx_phy_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw);
+
+ /*
+ * The link rate would be normally programmed in HW during
+ * phy_ops.power_on() or clk_ops.prepare() callbacks, but it might
+ * happen that the former gets fired too late, i.e. after this call,
+ * while the latter being executed only once, i.e. when clock remains
+ * in the prepared state during rate changes.
+ */
+ return rk_hdptx_pll_cmn_config(hdptx);
+}
+
+static const struct clk_ops hdptx_phy_clk_ops = {
+ .prepare = rk_hdptx_phy_clk_prepare,
+ .unprepare = rk_hdptx_phy_clk_unprepare,
+ .recalc_rate = rk_hdptx_phy_clk_recalc_rate,
+ .determine_rate = rk_hdptx_phy_clk_determine_rate,
+ .set_rate = rk_hdptx_phy_clk_set_rate,
+};
+
+static int rk_hdptx_phy_clk_register(struct rk_hdptx_phy *hdptx)
+{
+ struct device *dev = hdptx->dev;
+ const char *name, *pname;
+ struct clk *refclk;
+ int ret;
+
+ refclk = devm_clk_get(dev, "ref");
+ if (IS_ERR(refclk))
+ return dev_err_probe(dev, PTR_ERR(refclk),
+ "Failed to get ref clock\n");
+
+ name = hdptx->phy_id > 0 ? "clk_hdmiphy_pixel1" : "clk_hdmiphy_pixel0";
+ pname = __clk_get_name(refclk);
+
+ hdptx->hw.init = CLK_HW_INIT(name, pname, &hdptx_phy_clk_ops,
+ CLK_GET_RATE_NOCACHE);
+
+ ret = devm_clk_hw_register(dev, &hdptx->hw);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to register clock\n");
+
+ ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, &hdptx->hw);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to register clk provider\n");
+ return 0;
+}
+
+static int rk_hdptx_phy_runtime_suspend(struct device *dev)
+{
+ struct rk_hdptx_phy *hdptx = dev_get_drvdata(dev);
+
+ clk_bulk_disable_unprepare(hdptx->nr_clks, hdptx->clks);
+
+ return 0;
+}
+
+static int rk_hdptx_phy_runtime_resume(struct device *dev)
+{
+ struct rk_hdptx_phy *hdptx = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_bulk_prepare_enable(hdptx->nr_clks, hdptx->clks);
+ if (ret)
+ dev_err(hdptx->dev, "Failed to enable clocks: %d\n", ret);
+
+ return ret;
+}
+
+static int rk_hdptx_phy_probe(struct platform_device *pdev)
+{
+ const struct rk_hdptx_phy_cfg *cfgs;
+ struct phy_provider *phy_provider;
+ struct device *dev = &pdev->dev;
+ struct rk_hdptx_phy *hdptx;
+ struct resource *res;
+ void __iomem *regs;
+ int ret, id;
+
+ hdptx = devm_kzalloc(dev, sizeof(*hdptx), GFP_KERNEL);
+ if (!hdptx)
+ return -ENOMEM;
+
+ hdptx->dev = dev;
+ hdptx->hdmi_cfg.bpc = 8;
+
+ regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(regs))
+ return dev_err_probe(dev, PTR_ERR(regs),
+ "Failed to ioremap resource\n");
+
+ cfgs = device_get_match_data(dev);
+ if (!cfgs)
+ return dev_err_probe(dev, -EINVAL, "missing match data\n");
+
+ /* find the phy-id from the io address */
+ hdptx->phy_id = -ENODEV;
+ for (id = 0; id < cfgs->num_phys; id++) {
+ if (res->start == cfgs->phy_ids[id]) {
+ hdptx->phy_id = id;
+ break;
+ }
+ }
+
+ if (hdptx->phy_id < 0)
+ return dev_err_probe(dev, -ENODEV, "no matching device found\n");
+
+ ret = devm_clk_bulk_get_all(dev, &hdptx->clks);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to get clocks\n");
+ if (ret == 0)
+ return dev_err_probe(dev, -EINVAL, "Missing clocks\n");
+
+ hdptx->nr_clks = ret;
+
+ hdptx->regmap = devm_regmap_init_mmio(dev, regs,
+ &rk_hdptx_phy_regmap_config);
+ if (IS_ERR(hdptx->regmap))
+ return dev_err_probe(dev, PTR_ERR(hdptx->regmap),
+ "Failed to init regmap\n");
+
+ hdptx->rsts[RST_APB].id = "apb";
+ hdptx->rsts[RST_INIT].id = "init";
+ hdptx->rsts[RST_CMN].id = "cmn";
+ hdptx->rsts[RST_LANE].id = "lane";
+
+ ret = devm_reset_control_bulk_get_exclusive(dev, RST_MAX, hdptx->rsts);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get resets\n");
+
+ hdptx->grf = syscon_regmap_lookup_by_phandle(dev->of_node,
+ "rockchip,grf");
+ if (IS_ERR(hdptx->grf))
+ return dev_err_probe(dev, PTR_ERR(hdptx->grf),
+ "Could not get GRF syscon\n");
+
+ platform_set_drvdata(pdev, hdptx);
+
+ ret = devm_pm_runtime_enable(dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to enable runtime PM\n");
+
+ hdptx->phy = devm_phy_create(dev, NULL, &rk_hdptx_phy_ops);
+ if (IS_ERR(hdptx->phy))
+ return dev_err_probe(dev, PTR_ERR(hdptx->phy),
+ "Failed to create HDMI PHY\n");
+
+ phy_set_drvdata(hdptx->phy, hdptx);
+ phy_set_bus_width(hdptx->phy, 8);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ if (IS_ERR(phy_provider))
+ return dev_err_probe(dev, PTR_ERR(phy_provider),
+ "Failed to register PHY provider\n");
+
+ reset_control_deassert(hdptx->rsts[RST_APB].rstc);
+ reset_control_deassert(hdptx->rsts[RST_CMN].rstc);
+ reset_control_deassert(hdptx->rsts[RST_INIT].rstc);
+
+ return rk_hdptx_phy_clk_register(hdptx);
+}
+
+static const struct dev_pm_ops rk_hdptx_phy_pm_ops = {
+ RUNTIME_PM_OPS(rk_hdptx_phy_runtime_suspend,
+ rk_hdptx_phy_runtime_resume, NULL)
+};
+
+static const struct rk_hdptx_phy_cfg rk3576_hdptx_phy_cfgs = {
+ .num_phys = 1,
+ .phy_ids = {
+ 0x2b000000,
+ },
+};
+
+static const struct rk_hdptx_phy_cfg rk3588_hdptx_phy_cfgs = {
+ .num_phys = 2,
+ .phy_ids = {
+ 0xfed60000,
+ 0xfed70000,
+ },
+};
+
+static const struct of_device_id rk_hdptx_phy_of_match[] = {
+ {
+ .compatible = "rockchip,rk3576-hdptx-phy",
+ .data = &rk3576_hdptx_phy_cfgs
+ },
+ {
+ .compatible = "rockchip,rk3588-hdptx-phy",
+ .data = &rk3588_hdptx_phy_cfgs
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, rk_hdptx_phy_of_match);
+
+static struct platform_driver rk_hdptx_phy_driver = {
+ .probe = rk_hdptx_phy_probe,
+ .driver = {
+ .name = "rockchip-hdptx-phy",
+ .pm = &rk_hdptx_phy_pm_ops,
+ .of_match_table = rk_hdptx_phy_of_match,
+ },
+};
+module_platform_driver(rk_hdptx_phy_driver);
+
+MODULE_AUTHOR("Algea Cao <algea.cao@rock-chips.com>");
+MODULE_AUTHOR("Cristian Ciocaltea <cristian.ciocaltea@collabora.com>");
+MODULE_AUTHOR("Damon Ding <damon.ding@rock-chips.com>");
+MODULE_DESCRIPTION("Samsung HDMI/eDP Transmitter Combo PHY Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/phy/rockchip/phy-rockchip-snps-pcie3.c b/drivers/phy/rockchip/phy-rockchip-snps-pcie3.c
new file mode 100644
index 000000000..4e8ffd173
--- /dev/null
+++ b/drivers/phy/rockchip/phy-rockchip-snps-pcie3.c
@@ -0,0 +1,355 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Rockchip PCIE3.0 phy driver
+ *
+ * Copyright (C) 2022 Rockchip Electronics Co., Ltd.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/pcie.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+
+/* Register for RK3568 */
+#define GRF_PCIE30PHY_CON1 0x4
+#define GRF_PCIE30PHY_CON6 0x18
+#define GRF_PCIE30PHY_CON9 0x24
+#define GRF_PCIE30PHY_DA_OCM (BIT(15) | BIT(31))
+#define GRF_PCIE30PHY_STATUS0 0x80
+#define GRF_PCIE30PHY_WR_EN (0xf << 16)
+#define SRAM_INIT_DONE(reg) (reg & BIT(14))
+
+#define RK3568_BIFURCATION_LANE_0_1 BIT(0)
+
+/* Register for RK3588 */
+#define PHP_GRF_PCIESEL_CON 0x100
+#define RK3588_PCIE3PHY_GRF_CMN_CON0 0x0
+#define RK3588_PCIE3PHY_GRF_PHY0_STATUS1 0x904
+#define RK3588_PCIE3PHY_GRF_PHY1_STATUS1 0xa04
+#define RK3588_PCIE3PHY_GRF_PHY0_LN0_CON1 0x1004
+#define RK3588_PCIE3PHY_GRF_PHY0_LN1_CON1 0x1104
+#define RK3588_PCIE3PHY_GRF_PHY1_LN0_CON1 0x2004
+#define RK3588_PCIE3PHY_GRF_PHY1_LN1_CON1 0x2104
+#define RK3588_SRAM_INIT_DONE(reg) (reg & BIT(0))
+
+#define RK3588_BIFURCATION_LANE_0_1 BIT(0)
+#define RK3588_BIFURCATION_LANE_2_3 BIT(1)
+#define RK3588_LANE_AGGREGATION BIT(2)
+#define RK3588_RX_CMN_REFCLK_MODE_EN ((BIT(7) << 16) | BIT(7))
+#define RK3588_RX_CMN_REFCLK_MODE_DIS (BIT(7) << 16)
+#define RK3588_PCIE1LN_SEL_EN (GENMASK(1, 0) << 16)
+#define RK3588_PCIE30_PHY_MODE_EN (GENMASK(2, 0) << 16)
+
+struct rockchip_p3phy_ops;
+
+struct rockchip_p3phy_priv {
+ const struct rockchip_p3phy_ops *ops;
+ void __iomem *mmio;
+ /* mode: RC, EP */
+ int mode;
+ /* pcie30_phymode: Aggregation, Bifurcation */
+ int pcie30_phymode;
+ struct regmap *phy_grf;
+ struct regmap *pipe_grf;
+ struct reset_control *p30phy;
+ struct phy *phy;
+ struct clk_bulk_data *clks;
+ int num_clks;
+ int num_lanes;
+ u32 lanes[4];
+ u32 rx_cmn_refclk_mode[4];
+};
+
+struct rockchip_p3phy_ops {
+ int (*phy_init)(struct rockchip_p3phy_priv *priv);
+};
+
+static int rockchip_p3phy_set_mode(struct phy *phy, enum phy_mode mode, int submode)
+{
+ struct rockchip_p3phy_priv *priv = phy_get_drvdata(phy);
+
+ /* Actually We don't care EP/RC mode, but just record it */
+ switch (submode) {
+ case PHY_MODE_PCIE_RC:
+ priv->mode = PHY_MODE_PCIE_RC;
+ break;
+ case PHY_MODE_PCIE_EP:
+ priv->mode = PHY_MODE_PCIE_EP;
+ break;
+ default:
+ dev_err(&phy->dev, "%s, invalid mode\n", __func__);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int rockchip_p3phy_rk3568_init(struct rockchip_p3phy_priv *priv)
+{
+ struct phy *phy = priv->phy;
+ bool bifurcation = false;
+ int ret;
+ u32 reg;
+
+ /* Deassert PCIe PMA output clamp mode */
+ regmap_write(priv->phy_grf, GRF_PCIE30PHY_CON9, GRF_PCIE30PHY_DA_OCM);
+
+ for (int i = 0; i < priv->num_lanes; i++) {
+ dev_info(&phy->dev, "lane number %d, val %d\n", i, priv->lanes[i]);
+ if (priv->lanes[i] > 1)
+ bifurcation = true;
+ }
+
+ /* Set bifurcation if needed, and it doesn't care RC/EP */
+ if (bifurcation) {
+ dev_info(&phy->dev, "bifurcation enabled\n");
+ regmap_write(priv->phy_grf, GRF_PCIE30PHY_CON6,
+ GRF_PCIE30PHY_WR_EN | RK3568_BIFURCATION_LANE_0_1);
+ regmap_write(priv->phy_grf, GRF_PCIE30PHY_CON1,
+ GRF_PCIE30PHY_DA_OCM);
+ } else {
+ dev_dbg(&phy->dev, "bifurcation disabled\n");
+ regmap_write(priv->phy_grf, GRF_PCIE30PHY_CON6,
+ GRF_PCIE30PHY_WR_EN & ~RK3568_BIFURCATION_LANE_0_1);
+ }
+
+ reset_control_deassert(priv->p30phy);
+
+ ret = regmap_read_poll_timeout(priv->phy_grf,
+ GRF_PCIE30PHY_STATUS0,
+ reg, SRAM_INIT_DONE(reg),
+ 0, 500);
+ if (ret)
+ dev_err(&priv->phy->dev, "%s: lock failed 0x%x, check input refclk and power supply\n",
+ __func__, reg);
+ return ret;
+}
+
+static const struct rockchip_p3phy_ops rk3568_ops = {
+ .phy_init = rockchip_p3phy_rk3568_init,
+};
+
+static int rockchip_p3phy_rk3588_init(struct rockchip_p3phy_priv *priv)
+{
+ u32 reg = 0;
+ u8 mode = RK3588_LANE_AGGREGATION; /* default */
+ int ret;
+
+ regmap_write(priv->phy_grf, RK3588_PCIE3PHY_GRF_PHY0_LN0_CON1,
+ priv->rx_cmn_refclk_mode[0] ? RK3588_RX_CMN_REFCLK_MODE_EN :
+ RK3588_RX_CMN_REFCLK_MODE_DIS);
+ regmap_write(priv->phy_grf, RK3588_PCIE3PHY_GRF_PHY0_LN1_CON1,
+ priv->rx_cmn_refclk_mode[1] ? RK3588_RX_CMN_REFCLK_MODE_EN :
+ RK3588_RX_CMN_REFCLK_MODE_DIS);
+ regmap_write(priv->phy_grf, RK3588_PCIE3PHY_GRF_PHY1_LN0_CON1,
+ priv->rx_cmn_refclk_mode[2] ? RK3588_RX_CMN_REFCLK_MODE_EN :
+ RK3588_RX_CMN_REFCLK_MODE_DIS);
+ regmap_write(priv->phy_grf, RK3588_PCIE3PHY_GRF_PHY1_LN1_CON1,
+ priv->rx_cmn_refclk_mode[3] ? RK3588_RX_CMN_REFCLK_MODE_EN :
+ RK3588_RX_CMN_REFCLK_MODE_DIS);
+
+ /* Deassert PCIe PMA output clamp mode */
+ regmap_write(priv->phy_grf, RK3588_PCIE3PHY_GRF_CMN_CON0, BIT(8) | BIT(24));
+
+ /* Set bifurcation if needed */
+ for (int i = 0; i < priv->num_lanes; i++) {
+ if (priv->lanes[i] > 1)
+ mode &= ~RK3588_LANE_AGGREGATION;
+ if (priv->lanes[i] == 3)
+ mode |= RK3588_BIFURCATION_LANE_0_1;
+ if (priv->lanes[i] == 4)
+ mode |= RK3588_BIFURCATION_LANE_2_3;
+ }
+
+ reg = mode;
+ regmap_write(priv->phy_grf, RK3588_PCIE3PHY_GRF_CMN_CON0,
+ RK3588_PCIE30_PHY_MODE_EN | reg);
+
+ /* Set pcie1ln_sel in PHP_GRF_PCIESEL_CON */
+ if (!IS_ERR(priv->pipe_grf)) {
+ reg = mode & (RK3588_BIFURCATION_LANE_0_1 | RK3588_BIFURCATION_LANE_2_3);
+ if (reg)
+ regmap_write(priv->pipe_grf, PHP_GRF_PCIESEL_CON,
+ RK3588_PCIE1LN_SEL_EN | reg);
+ }
+
+ reset_control_deassert(priv->p30phy);
+
+ ret = regmap_read_poll_timeout(priv->phy_grf,
+ RK3588_PCIE3PHY_GRF_PHY0_STATUS1,
+ reg, RK3588_SRAM_INIT_DONE(reg),
+ 0, 500);
+ ret |= regmap_read_poll_timeout(priv->phy_grf,
+ RK3588_PCIE3PHY_GRF_PHY1_STATUS1,
+ reg, RK3588_SRAM_INIT_DONE(reg),
+ 0, 500);
+ if (ret)
+ dev_err(&priv->phy->dev, "lock failed 0x%x, check input refclk and power supply\n",
+ reg);
+ return ret;
+}
+
+static const struct rockchip_p3phy_ops rk3588_ops = {
+ .phy_init = rockchip_p3phy_rk3588_init,
+};
+
+static int rockchip_p3phy_init(struct phy *phy)
+{
+ struct rockchip_p3phy_priv *priv = phy_get_drvdata(phy);
+ int ret;
+
+ ret = clk_bulk_prepare_enable(priv->num_clks, priv->clks);
+ if (ret) {
+ dev_err(&priv->phy->dev, "failed to enable PCIe bulk clks %d\n", ret);
+ return ret;
+ }
+
+ reset_control_assert(priv->p30phy);
+ udelay(1);
+
+ if (priv->ops->phy_init) {
+ ret = priv->ops->phy_init(priv);
+ if (ret)
+ clk_bulk_disable_unprepare(priv->num_clks, priv->clks);
+ }
+
+ return ret;
+}
+
+static int rockchip_p3phy_exit(struct phy *phy)
+{
+ struct rockchip_p3phy_priv *priv = phy_get_drvdata(phy);
+
+ clk_bulk_disable_unprepare(priv->num_clks, priv->clks);
+ reset_control_assert(priv->p30phy);
+ return 0;
+}
+
+static const struct phy_ops rockchip_p3phy_ops = {
+ .init = rockchip_p3phy_init,
+ .exit = rockchip_p3phy_exit,
+ .set_mode = rockchip_p3phy_set_mode,
+ .owner = THIS_MODULE,
+};
+
+static int rockchip_p3phy_probe(struct platform_device *pdev)
+{
+ struct phy_provider *phy_provider;
+ struct device *dev = &pdev->dev;
+ struct rockchip_p3phy_priv *priv;
+ struct device_node *np = dev->of_node;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->mmio = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
+ if (IS_ERR(priv->mmio)) {
+ ret = PTR_ERR(priv->mmio);
+ return ret;
+ }
+
+ priv->ops = of_device_get_match_data(&pdev->dev);
+ if (!priv->ops) {
+ dev_err(dev, "no of match data provided\n");
+ return -EINVAL;
+ }
+
+ priv->phy_grf = syscon_regmap_lookup_by_phandle(np, "rockchip,phy-grf");
+ if (IS_ERR(priv->phy_grf)) {
+ dev_err(dev, "failed to find rockchip,phy_grf regmap\n");
+ return PTR_ERR(priv->phy_grf);
+ }
+
+ if (of_device_is_compatible(np, "rockchip,rk3588-pcie3-phy")) {
+ priv->pipe_grf =
+ syscon_regmap_lookup_by_phandle(dev->of_node,
+ "rockchip,pipe-grf");
+ if (IS_ERR(priv->pipe_grf))
+ dev_info(dev, "failed to find rockchip,pipe_grf regmap\n");
+ } else {
+ priv->pipe_grf = NULL;
+ }
+
+ priv->num_lanes = of_property_read_variable_u32_array(dev->of_node, "data-lanes",
+ priv->lanes, 2,
+ ARRAY_SIZE(priv->lanes));
+
+ /* if no data-lanes assume aggregation */
+ if (priv->num_lanes == -EINVAL) {
+ dev_dbg(dev, "no data-lanes property found\n");
+ priv->num_lanes = 1;
+ priv->lanes[0] = 1;
+ } else if (priv->num_lanes < 0) {
+ dev_err(dev, "failed to read data-lanes property %d\n", priv->num_lanes);
+ return priv->num_lanes;
+ }
+
+ ret = of_property_read_variable_u32_array(dev->of_node,
+ "rockchip,rx-common-refclk-mode",
+ priv->rx_cmn_refclk_mode, 1,
+ ARRAY_SIZE(priv->rx_cmn_refclk_mode));
+ /*
+ * if no rockchip,rx-common-refclk-mode, assume enabled for all lanes in
+ * order to be DT backwards compatible. (Since HW reset val is enabled.)
+ */
+ if (ret == -EINVAL) {
+ for (int i = 0; i < ARRAY_SIZE(priv->rx_cmn_refclk_mode); i++)
+ priv->rx_cmn_refclk_mode[i] = 1;
+ } else if (ret < 0) {
+ dev_err(dev, "failed to read rockchip,rx-common-refclk-mode property %d\n",
+ ret);
+ return ret;
+ }
+
+ priv->phy = devm_phy_create(dev, NULL, &rockchip_p3phy_ops);
+ if (IS_ERR(priv->phy)) {
+ dev_err(dev, "failed to create combphy\n");
+ return PTR_ERR(priv->phy);
+ }
+
+ priv->p30phy = devm_reset_control_get_optional_exclusive(dev, "phy");
+ if (IS_ERR(priv->p30phy)) {
+ return dev_err_probe(dev, PTR_ERR(priv->p30phy),
+ "failed to get phy reset control\n");
+ }
+ if (!priv->p30phy)
+ dev_info(dev, "no phy reset control specified\n");
+
+ priv->num_clks = devm_clk_bulk_get_all(dev, &priv->clks);
+ if (priv->num_clks < 1)
+ return -ENODEV;
+
+ dev_set_drvdata(dev, priv);
+ phy_set_drvdata(priv->phy, priv);
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id rockchip_p3phy_of_match[] = {
+ { .compatible = "rockchip,rk3568-pcie3-phy", .data = &rk3568_ops },
+ { .compatible = "rockchip,rk3588-pcie3-phy", .data = &rk3588_ops },
+ { },
+};
+MODULE_DEVICE_TABLE(of, rockchip_p3phy_of_match);
+
+static struct platform_driver rockchip_p3phy_driver = {
+ .probe = rockchip_p3phy_probe,
+ .driver = {
+ .name = "rockchip-snps-pcie3-phy",
+ .of_match_table = rockchip_p3phy_of_match,
+ },
+};
+module_platform_driver(rockchip_p3phy_driver);
+MODULE_DESCRIPTION("Rockchip Synopsys PCIe 3.0 PHY driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/phy/rockchip/phy-rockchip-typec.c b/drivers/phy/rockchip/phy-rockchip-typec.c
new file mode 100644
index 000000000..d9701b610
--- /dev/null
+++ b/drivers/phy/rockchip/phy-rockchip-typec.c
@@ -0,0 +1,1225 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) Rockchip Electronics Co., Ltd.
+ * Author: Chris Zhong <zyw@rock-chips.com>
+ * Kever Yang <kever.yang@rock-chips.com>
+ *
+ * The ROCKCHIP Type-C PHY has two PLL clocks. The first PLL clock
+ * is used for USB3, the second PLL clock is used for DP. This Type-C PHY has
+ * 3 working modes: USB3 only mode, DP only mode, and USB3+DP mode.
+ * At USB3 only mode, both PLL clocks need to be initialized, this allows the
+ * PHY to switch mode between USB3 and USB3+DP, without disconnecting the USB
+ * device.
+ * In The DP only mode, only the DP PLL needs to be powered on, and the 4 lanes
+ * are all used for DP.
+ *
+ * This driver gets extcon cable state and property, then decides which mode to
+ * select:
+ *
+ * 1. USB3 only mode:
+ * EXTCON_USB or EXTCON_USB_HOST state is true, and
+ * EXTCON_PROP_USB_SS property is true.
+ * EXTCON_DISP_DP state is false.
+ *
+ * 2. DP only mode:
+ * EXTCON_DISP_DP state is true, and
+ * EXTCON_PROP_USB_SS property is false.
+ * If EXTCON_USB_HOST state is true, it is DP + USB2 mode, since the USB2 phy
+ * is a separate phy, so this case is still DP only mode.
+ *
+ * 3. USB3+DP mode:
+ * EXTCON_USB_HOST and EXTCON_DISP_DP are both true, and
+ * EXTCON_PROP_USB_SS property is true.
+ *
+ * This Type-C PHY driver supports normal and flip orientation. The orientation
+ * is reported by the EXTCON_PROP_USB_TYPEC_POLARITY property: true is flip
+ * orientation, false is normal orientation.
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/extcon.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+
+#include <linux/mfd/syscon.h>
+#include <linux/phy/phy.h>
+
+#define CMN_SSM_BANDGAP (0x21 << 2)
+#define CMN_SSM_BIAS (0x22 << 2)
+#define CMN_PLLSM0_PLLEN (0x29 << 2)
+#define CMN_PLLSM0_PLLPRE (0x2a << 2)
+#define CMN_PLLSM0_PLLVREF (0x2b << 2)
+#define CMN_PLLSM0_PLLLOCK (0x2c << 2)
+#define CMN_PLLSM1_PLLEN (0x31 << 2)
+#define CMN_PLLSM1_PLLPRE (0x32 << 2)
+#define CMN_PLLSM1_PLLVREF (0x33 << 2)
+#define CMN_PLLSM1_PLLLOCK (0x34 << 2)
+#define CMN_PLLSM1_USER_DEF_CTRL (0x37 << 2)
+#define CMN_ICAL_OVRD (0xc1 << 2)
+#define CMN_PLL0_VCOCAL_OVRD (0x83 << 2)
+#define CMN_PLL0_VCOCAL_INIT (0x84 << 2)
+#define CMN_PLL0_VCOCAL_ITER (0x85 << 2)
+#define CMN_PLL0_LOCK_REFCNT_START (0x90 << 2)
+#define CMN_PLL0_LOCK_PLLCNT_START (0x92 << 2)
+#define CMN_PLL0_LOCK_PLLCNT_THR (0x93 << 2)
+#define CMN_PLL0_INTDIV (0x94 << 2)
+#define CMN_PLL0_FRACDIV (0x95 << 2)
+#define CMN_PLL0_HIGH_THR (0x96 << 2)
+#define CMN_PLL0_DSM_DIAG (0x97 << 2)
+#define CMN_PLL0_SS_CTRL1 (0x98 << 2)
+#define CMN_PLL0_SS_CTRL2 (0x99 << 2)
+#define CMN_PLL1_VCOCAL_START (0xa1 << 2)
+#define CMN_PLL1_VCOCAL_OVRD (0xa3 << 2)
+#define CMN_PLL1_VCOCAL_INIT (0xa4 << 2)
+#define CMN_PLL1_VCOCAL_ITER (0xa5 << 2)
+#define CMN_PLL1_LOCK_REFCNT_START (0xb0 << 2)
+#define CMN_PLL1_LOCK_PLLCNT_START (0xb2 << 2)
+#define CMN_PLL1_LOCK_PLLCNT_THR (0xb3 << 2)
+#define CMN_PLL1_INTDIV (0xb4 << 2)
+#define CMN_PLL1_FRACDIV (0xb5 << 2)
+#define CMN_PLL1_HIGH_THR (0xb6 << 2)
+#define CMN_PLL1_DSM_DIAG (0xb7 << 2)
+#define CMN_PLL1_SS_CTRL1 (0xb8 << 2)
+#define CMN_PLL1_SS_CTRL2 (0xb9 << 2)
+#define CMN_RXCAL_OVRD (0xd1 << 2)
+
+#define CMN_TXPUCAL_CTRL (0xe0 << 2)
+#define CMN_TXPUCAL_OVRD (0xe1 << 2)
+#define CMN_TXPDCAL_CTRL (0xf0 << 2)
+#define CMN_TXPDCAL_OVRD (0xf1 << 2)
+
+/* For CMN_TXPUCAL_CTRL, CMN_TXPDCAL_CTRL */
+#define CMN_TXPXCAL_START BIT(15)
+#define CMN_TXPXCAL_DONE BIT(14)
+#define CMN_TXPXCAL_NO_RESPONSE BIT(13)
+#define CMN_TXPXCAL_CURRENT_RESPONSE BIT(12)
+
+#define CMN_TXPU_ADJ_CTRL (0x108 << 2)
+#define CMN_TXPD_ADJ_CTRL (0x10c << 2)
+
+/*
+ * For CMN_TXPUCAL_CTRL, CMN_TXPDCAL_CTRL,
+ * CMN_TXPU_ADJ_CTRL, CMN_TXPDCAL_CTRL
+ *
+ * NOTE: some of these registers are documented to be 2's complement
+ * signed numbers, but then documented to be always positive. Weird.
+ * In such a case, using CMN_CALIB_CODE_POS() avoids the unnecessary
+ * sign extension.
+ */
+#define CMN_CALIB_CODE_WIDTH 7
+#define CMN_CALIB_CODE_OFFSET 0
+#define CMN_CALIB_CODE_MASK GENMASK(CMN_CALIB_CODE_WIDTH, 0)
+#define CMN_CALIB_CODE(x) \
+ sign_extend32((x) >> CMN_CALIB_CODE_OFFSET, CMN_CALIB_CODE_WIDTH)
+
+#define CMN_CALIB_CODE_POS_MASK GENMASK(CMN_CALIB_CODE_WIDTH - 1, 0)
+#define CMN_CALIB_CODE_POS(x) \
+ (((x) >> CMN_CALIB_CODE_OFFSET) & CMN_CALIB_CODE_POS_MASK)
+
+#define CMN_DIAG_PLL0_FBH_OVRD (0x1c0 << 2)
+#define CMN_DIAG_PLL0_FBL_OVRD (0x1c1 << 2)
+#define CMN_DIAG_PLL0_OVRD (0x1c2 << 2)
+#define CMN_DIAG_PLL0_V2I_TUNE (0x1c5 << 2)
+#define CMN_DIAG_PLL0_CP_TUNE (0x1c6 << 2)
+#define CMN_DIAG_PLL0_LF_PROG (0x1c7 << 2)
+#define CMN_DIAG_PLL1_FBH_OVRD (0x1d0 << 2)
+#define CMN_DIAG_PLL1_FBL_OVRD (0x1d1 << 2)
+#define CMN_DIAG_PLL1_OVRD (0x1d2 << 2)
+#define CMN_DIAG_PLL1_V2I_TUNE (0x1d5 << 2)
+#define CMN_DIAG_PLL1_CP_TUNE (0x1d6 << 2)
+#define CMN_DIAG_PLL1_LF_PROG (0x1d7 << 2)
+#define CMN_DIAG_PLL1_PTATIS_TUNE1 (0x1d8 << 2)
+#define CMN_DIAG_PLL1_PTATIS_TUNE2 (0x1d9 << 2)
+#define CMN_DIAG_PLL1_INCLK_CTRL (0x1da << 2)
+#define CMN_DIAG_HSCLK_SEL (0x1e0 << 2)
+
+#define XCVR_PSM_RCTRL(n) ((0x4001 | ((n) << 9)) << 2)
+#define XCVR_PSM_CAL_TMR(n) ((0x4002 | ((n) << 9)) << 2)
+#define XCVR_PSM_A0IN_TMR(n) ((0x4003 | ((n) << 9)) << 2)
+#define TX_TXCC_CAL_SCLR_MULT(n) ((0x4047 | ((n) << 9)) << 2)
+#define TX_TXCC_CPOST_MULT_00(n) ((0x404c | ((n) << 9)) << 2)
+#define TX_TXCC_CPOST_MULT_01(n) ((0x404d | ((n) << 9)) << 2)
+#define TX_TXCC_CPOST_MULT_10(n) ((0x404e | ((n) << 9)) << 2)
+#define TX_TXCC_CPOST_MULT_11(n) ((0x404f | ((n) << 9)) << 2)
+#define TX_TXCC_MGNFS_MULT_000(n) ((0x4050 | ((n) << 9)) << 2)
+#define TX_TXCC_MGNFS_MULT_001(n) ((0x4051 | ((n) << 9)) << 2)
+#define TX_TXCC_MGNFS_MULT_010(n) ((0x4052 | ((n) << 9)) << 2)
+#define TX_TXCC_MGNFS_MULT_011(n) ((0x4053 | ((n) << 9)) << 2)
+#define TX_TXCC_MGNFS_MULT_100(n) ((0x4054 | ((n) << 9)) << 2)
+#define TX_TXCC_MGNFS_MULT_101(n) ((0x4055 | ((n) << 9)) << 2)
+#define TX_TXCC_MGNFS_MULT_110(n) ((0x4056 | ((n) << 9)) << 2)
+#define TX_TXCC_MGNFS_MULT_111(n) ((0x4057 | ((n) << 9)) << 2)
+#define TX_TXCC_MGNLS_MULT_000(n) ((0x4058 | ((n) << 9)) << 2)
+#define TX_TXCC_MGNLS_MULT_001(n) ((0x4059 | ((n) << 9)) << 2)
+#define TX_TXCC_MGNLS_MULT_010(n) ((0x405a | ((n) << 9)) << 2)
+#define TX_TXCC_MGNLS_MULT_011(n) ((0x405b | ((n) << 9)) << 2)
+#define TX_TXCC_MGNLS_MULT_100(n) ((0x405c | ((n) << 9)) << 2)
+#define TX_TXCC_MGNLS_MULT_101(n) ((0x405d | ((n) << 9)) << 2)
+#define TX_TXCC_MGNLS_MULT_110(n) ((0x405e | ((n) << 9)) << 2)
+#define TX_TXCC_MGNLS_MULT_111(n) ((0x405f | ((n) << 9)) << 2)
+
+#define XCVR_DIAG_PLLDRC_CTRL(n) ((0x40e0 | ((n) << 9)) << 2)
+#define XCVR_DIAG_BIDI_CTRL(n) ((0x40e8 | ((n) << 9)) << 2)
+#define XCVR_DIAG_LANE_FCM_EN_MGN(n) ((0x40f2 | ((n) << 9)) << 2)
+#define TX_PSC_A0(n) ((0x4100 | ((n) << 9)) << 2)
+#define TX_PSC_A1(n) ((0x4101 | ((n) << 9)) << 2)
+#define TX_PSC_A2(n) ((0x4102 | ((n) << 9)) << 2)
+#define TX_PSC_A3(n) ((0x4103 | ((n) << 9)) << 2)
+#define TX_RCVDET_CTRL(n) ((0x4120 | ((n) << 9)) << 2)
+#define TX_RCVDET_EN_TMR(n) ((0x4122 | ((n) << 9)) << 2)
+#define TX_RCVDET_ST_TMR(n) ((0x4123 | ((n) << 9)) << 2)
+#define TX_DIAG_TX_DRV(n) ((0x41e1 | ((n) << 9)) << 2)
+#define TX_DIAG_BGREF_PREDRV_DELAY (0x41e7 << 2)
+
+/* Use this for "n" in macros like "_MULT_XXX" to target the aux channel */
+#define AUX_CH_LANE 8
+
+#define TX_ANA_CTRL_REG_1 (0x5020 << 2)
+
+#define TXDA_DP_AUX_EN BIT(15)
+#define AUXDA_SE_EN BIT(14)
+#define TXDA_CAL_LATCH_EN BIT(13)
+#define AUXDA_POLARITY BIT(12)
+#define TXDA_DRV_POWER_ISOLATION_EN BIT(11)
+#define TXDA_DRV_POWER_EN_PH_2_N BIT(10)
+#define TXDA_DRV_POWER_EN_PH_1_N BIT(9)
+#define TXDA_BGREF_EN BIT(8)
+#define TXDA_DRV_LDO_EN BIT(7)
+#define TXDA_DECAP_EN_DEL BIT(6)
+#define TXDA_DECAP_EN BIT(5)
+#define TXDA_UPHY_SUPPLY_EN_DEL BIT(4)
+#define TXDA_UPHY_SUPPLY_EN BIT(3)
+#define TXDA_LOW_LEAKAGE_EN BIT(2)
+#define TXDA_DRV_IDLE_LOWI_EN BIT(1)
+#define TXDA_DRV_CMN_MODE_EN BIT(0)
+
+#define TX_ANA_CTRL_REG_2 (0x5021 << 2)
+
+#define AUXDA_DEBOUNCING_CLK BIT(15)
+#define TXDA_LPBK_RECOVERED_CLK_EN BIT(14)
+#define TXDA_LPBK_ISI_GEN_EN BIT(13)
+#define TXDA_LPBK_SERIAL_EN BIT(12)
+#define TXDA_LPBK_LINE_EN BIT(11)
+#define TXDA_DRV_LDO_REDC_SINKIQ BIT(10)
+#define XCVR_DECAP_EN_DEL BIT(9)
+#define XCVR_DECAP_EN BIT(8)
+#define TXDA_MPHY_ENABLE_HS_NT BIT(7)
+#define TXDA_MPHY_SA_MODE BIT(6)
+#define TXDA_DRV_LDO_RBYR_FB_EN BIT(5)
+#define TXDA_DRV_RST_PULL_DOWN BIT(4)
+#define TXDA_DRV_LDO_BG_FB_EN BIT(3)
+#define TXDA_DRV_LDO_BG_REF_EN BIT(2)
+#define TXDA_DRV_PREDRV_EN_DEL BIT(1)
+#define TXDA_DRV_PREDRV_EN BIT(0)
+
+#define TXDA_COEFF_CALC_CTRL (0x5022 << 2)
+
+#define TX_HIGH_Z BIT(6)
+#define TX_VMARGIN_OFFSET 3
+#define TX_VMARGIN_MASK 0x7
+#define LOW_POWER_SWING_EN BIT(2)
+#define TX_FCM_DRV_MAIN_EN BIT(1)
+#define TX_FCM_FULL_MARGIN BIT(0)
+
+#define TX_DIG_CTRL_REG_2 (0x5024 << 2)
+
+#define TX_HIGH_Z_TM_EN BIT(15)
+#define TX_RESCAL_CODE_OFFSET 0
+#define TX_RESCAL_CODE_MASK 0x3f
+
+#define TXDA_CYA_AUXDA_CYA (0x5025 << 2)
+#define TX_ANA_CTRL_REG_3 (0x5026 << 2)
+#define TX_ANA_CTRL_REG_4 (0x5027 << 2)
+#define TX_ANA_CTRL_REG_5 (0x5029 << 2)
+
+#define RX_PSC_A0(n) ((0x8000 | ((n) << 9)) << 2)
+#define RX_PSC_A1(n) ((0x8001 | ((n) << 9)) << 2)
+#define RX_PSC_A2(n) ((0x8002 | ((n) << 9)) << 2)
+#define RX_PSC_A3(n) ((0x8003 | ((n) << 9)) << 2)
+#define RX_PSC_CAL(n) ((0x8006 | ((n) << 9)) << 2)
+#define RX_PSC_RDY(n) ((0x8007 | ((n) << 9)) << 2)
+#define RX_IQPI_ILL_CAL_OVRD (0x8023 << 2)
+#define RX_EPI_ILL_CAL_OVRD (0x8033 << 2)
+#define RX_SDCAL0_OVRD (0x8041 << 2)
+#define RX_SDCAL1_OVRD (0x8049 << 2)
+#define RX_SLC_INIT (0x806d << 2)
+#define RX_SLC_RUN (0x806e << 2)
+#define RX_CDRLF_CNFG2 (0x8081 << 2)
+#define RX_SIGDET_HL_FILT_TMR(n) ((0x8090 | ((n) << 9)) << 2)
+#define RX_SLC_IOP0_OVRD (0x8101 << 2)
+#define RX_SLC_IOP1_OVRD (0x8105 << 2)
+#define RX_SLC_QOP0_OVRD (0x8109 << 2)
+#define RX_SLC_QOP1_OVRD (0x810d << 2)
+#define RX_SLC_EOP0_OVRD (0x8111 << 2)
+#define RX_SLC_EOP1_OVRD (0x8115 << 2)
+#define RX_SLC_ION0_OVRD (0x8119 << 2)
+#define RX_SLC_ION1_OVRD (0x811d << 2)
+#define RX_SLC_QON0_OVRD (0x8121 << 2)
+#define RX_SLC_QON1_OVRD (0x8125 << 2)
+#define RX_SLC_EON0_OVRD (0x8129 << 2)
+#define RX_SLC_EON1_OVRD (0x812d << 2)
+#define RX_SLC_IEP0_OVRD (0x8131 << 2)
+#define RX_SLC_IEP1_OVRD (0x8135 << 2)
+#define RX_SLC_QEP0_OVRD (0x8139 << 2)
+#define RX_SLC_QEP1_OVRD (0x813d << 2)
+#define RX_SLC_EEP0_OVRD (0x8141 << 2)
+#define RX_SLC_EEP1_OVRD (0x8145 << 2)
+#define RX_SLC_IEN0_OVRD (0x8149 << 2)
+#define RX_SLC_IEN1_OVRD (0x814d << 2)
+#define RX_SLC_QEN0_OVRD (0x8151 << 2)
+#define RX_SLC_QEN1_OVRD (0x8155 << 2)
+#define RX_SLC_EEN0_OVRD (0x8159 << 2)
+#define RX_SLC_EEN1_OVRD (0x815d << 2)
+#define RX_REE_CTRL_DATA_MASK(n) ((0x81bb | ((n) << 9)) << 2)
+#define RX_DIAG_SIGDET_TUNE(n) ((0x81dc | ((n) << 9)) << 2)
+#define RX_DIAG_SC2C_DELAY (0x81e1 << 2)
+
+#define PMA_LANE_CFG (0xc000 << 2)
+#define PIPE_CMN_CTRL1 (0xc001 << 2)
+#define PIPE_CMN_CTRL2 (0xc002 << 2)
+#define PIPE_COM_LOCK_CFG1 (0xc003 << 2)
+#define PIPE_COM_LOCK_CFG2 (0xc004 << 2)
+#define PIPE_RCV_DET_INH (0xc005 << 2)
+#define DP_MODE_CTL (0xc008 << 2)
+#define DP_CLK_CTL (0xc009 << 2)
+#define STS (0xc00F << 2)
+#define PHY_ISO_CMN_CTRL (0xc010 << 2)
+#define PHY_DP_TX_CTL (0xc408 << 2)
+#define PMA_CMN_CTRL1 (0xc800 << 2)
+#define PHY_PMA_ISO_CMN_CTRL (0xc810 << 2)
+#define PHY_ISOLATION_CTRL (0xc81f << 2)
+#define PHY_PMA_ISO_XCVR_CTRL(n) ((0xcc11 | ((n) << 6)) << 2)
+#define PHY_PMA_ISO_LINK_MODE(n) ((0xcc12 | ((n) << 6)) << 2)
+#define PHY_PMA_ISO_PWRST_CTRL(n) ((0xcc13 | ((n) << 6)) << 2)
+#define PHY_PMA_ISO_TX_DATA_LO(n) ((0xcc14 | ((n) << 6)) << 2)
+#define PHY_PMA_ISO_TX_DATA_HI(n) ((0xcc15 | ((n) << 6)) << 2)
+#define PHY_PMA_ISO_RX_DATA_LO(n) ((0xcc16 | ((n) << 6)) << 2)
+#define PHY_PMA_ISO_RX_DATA_HI(n) ((0xcc17 | ((n) << 6)) << 2)
+#define TX_BIST_CTRL(n) ((0x4140 | ((n) << 9)) << 2)
+#define TX_BIST_UDDWR(n) ((0x4141 | ((n) << 9)) << 2)
+
+/*
+ * Selects which PLL clock will be driven on the analog high speed
+ * clock 0: PLL 0 div 1
+ * clock 1: PLL 1 div 2
+ */
+#define CLK_PLL_CONFIG 0X30
+#define CLK_PLL_MASK 0x33
+
+#define CMN_READY BIT(0)
+
+#define DP_PLL_CLOCK_ENABLE BIT(2)
+#define DP_PLL_ENABLE BIT(0)
+#define DP_PLL_DATA_RATE_RBR ((2 << 12) | (4 << 8))
+#define DP_PLL_DATA_RATE_HBR ((2 << 12) | (4 << 8))
+#define DP_PLL_DATA_RATE_HBR2 ((1 << 12) | (2 << 8))
+
+#define DP_MODE_A0 BIT(4)
+#define DP_MODE_A2 BIT(6)
+#define DP_MODE_ENTER_A0 0xc101
+#define DP_MODE_ENTER_A2 0xc104
+
+#define PHY_MODE_SET_TIMEOUT 100000
+
+#define PIN_ASSIGN_C_E 0x51d9
+#define PIN_ASSIGN_D_F 0x5100
+
+#define MODE_DISCONNECT 0
+#define MODE_UFP_USB BIT(0)
+#define MODE_DFP_USB BIT(1)
+#define MODE_DFP_DP BIT(2)
+
+struct usb3phy_reg {
+ u32 offset;
+ u32 enable_bit;
+ u32 write_enable;
+};
+
+/**
+ * struct rockchip_usb3phy_port_cfg - usb3-phy port configuration.
+ * @reg: the base address for usb3-phy config.
+ * @typec_conn_dir: the register of type-c connector direction.
+ * @usb3tousb2_en: the register of type-c force usb2 to usb2 enable.
+ * @external_psm: the register of type-c phy external psm clock.
+ * @pipe_status: the register of type-c phy pipe status.
+ * @usb3_host_disable: the register of type-c usb3 host disable.
+ * @usb3_host_port: the register of type-c usb3 host port.
+ * @uphy_dp_sel: the register of type-c phy DP select control.
+ */
+struct rockchip_usb3phy_port_cfg {
+ unsigned int reg;
+ struct usb3phy_reg typec_conn_dir;
+ struct usb3phy_reg usb3tousb2_en;
+ struct usb3phy_reg external_psm;
+ struct usb3phy_reg pipe_status;
+ struct usb3phy_reg usb3_host_disable;
+ struct usb3phy_reg usb3_host_port;
+ struct usb3phy_reg uphy_dp_sel;
+};
+
+struct rockchip_typec_phy {
+ struct device *dev;
+ void __iomem *base;
+ struct extcon_dev *extcon;
+ struct regmap *grf_regs;
+ struct clk *clk_core;
+ struct clk *clk_ref;
+ struct reset_control *uphy_rst;
+ struct reset_control *pipe_rst;
+ struct reset_control *tcphy_rst;
+ const struct rockchip_usb3phy_port_cfg *port_cfgs;
+ /* mutex to protect access to individual PHYs */
+ struct mutex lock;
+
+ bool flip;
+ u8 mode;
+};
+
+struct phy_reg {
+ u16 value;
+ u32 addr;
+};
+
+static struct phy_reg usb3_pll_cfg[] = {
+ { 0xf0, CMN_PLL0_VCOCAL_INIT },
+ { 0x18, CMN_PLL0_VCOCAL_ITER },
+ { 0xd0, CMN_PLL0_INTDIV },
+ { 0x4a4a, CMN_PLL0_FRACDIV },
+ { 0x34, CMN_PLL0_HIGH_THR },
+ { 0x1ee, CMN_PLL0_SS_CTRL1 },
+ { 0x7f03, CMN_PLL0_SS_CTRL2 },
+ { 0x20, CMN_PLL0_DSM_DIAG },
+ { 0, CMN_DIAG_PLL0_OVRD },
+ { 0, CMN_DIAG_PLL0_FBH_OVRD },
+ { 0, CMN_DIAG_PLL0_FBL_OVRD },
+ { 0x7, CMN_DIAG_PLL0_V2I_TUNE },
+ { 0x45, CMN_DIAG_PLL0_CP_TUNE },
+ { 0x8, CMN_DIAG_PLL0_LF_PROG },
+};
+
+static struct phy_reg dp_pll_cfg[] = {
+ { 0xf0, CMN_PLL1_VCOCAL_INIT },
+ { 0x18, CMN_PLL1_VCOCAL_ITER },
+ { 0x30b9, CMN_PLL1_VCOCAL_START },
+ { 0x21c, CMN_PLL1_INTDIV },
+ { 0, CMN_PLL1_FRACDIV },
+ { 0x5, CMN_PLL1_HIGH_THR },
+ { 0x35, CMN_PLL1_SS_CTRL1 },
+ { 0x7f1e, CMN_PLL1_SS_CTRL2 },
+ { 0x20, CMN_PLL1_DSM_DIAG },
+ { 0, CMN_PLLSM1_USER_DEF_CTRL },
+ { 0, CMN_DIAG_PLL1_OVRD },
+ { 0, CMN_DIAG_PLL1_FBH_OVRD },
+ { 0, CMN_DIAG_PLL1_FBL_OVRD },
+ { 0x6, CMN_DIAG_PLL1_V2I_TUNE },
+ { 0x45, CMN_DIAG_PLL1_CP_TUNE },
+ { 0x8, CMN_DIAG_PLL1_LF_PROG },
+ { 0x100, CMN_DIAG_PLL1_PTATIS_TUNE1 },
+ { 0x7, CMN_DIAG_PLL1_PTATIS_TUNE2 },
+ { 0x4, CMN_DIAG_PLL1_INCLK_CTRL },
+};
+
+static const struct rockchip_usb3phy_port_cfg rk3399_usb3phy_port_cfgs[] = {
+ {
+ .reg = 0xff7c0000,
+ .typec_conn_dir = { 0xe580, 0, 16 },
+ .usb3tousb2_en = { 0xe580, 3, 19 },
+ .external_psm = { 0xe588, 14, 30 },
+ .pipe_status = { 0xe5c0, 0, 0 },
+ .usb3_host_disable = { 0x2434, 0, 16 },
+ .usb3_host_port = { 0x2434, 12, 28 },
+ .uphy_dp_sel = { 0x6268, 19, 19 },
+ },
+ {
+ .reg = 0xff800000,
+ .typec_conn_dir = { 0xe58c, 0, 16 },
+ .usb3tousb2_en = { 0xe58c, 3, 19 },
+ .external_psm = { 0xe594, 14, 30 },
+ .pipe_status = { 0xe5c0, 16, 16 },
+ .usb3_host_disable = { 0x2444, 0, 16 },
+ .usb3_host_port = { 0x2444, 12, 28 },
+ .uphy_dp_sel = { 0x6268, 3, 19 },
+ },
+ { /* sentinel */ }
+};
+
+static void tcphy_cfg_24m(struct rockchip_typec_phy *tcphy)
+{
+ u32 i, rdata;
+
+ /*
+ * cmn_ref_clk_sel = 3, select the 24Mhz for clk parent
+ * cmn_psm_clk_dig_div = 2, set the clk division to 2
+ */
+ writel(0x830, tcphy->base + PMA_CMN_CTRL1);
+ for (i = 0; i < 4; i++) {
+ /*
+ * The following PHY configuration assumes a 24 MHz reference
+ * clock.
+ */
+ writel(0x90, tcphy->base + XCVR_DIAG_LANE_FCM_EN_MGN(i));
+ writel(0x960, tcphy->base + TX_RCVDET_EN_TMR(i));
+ writel(0x30, tcphy->base + TX_RCVDET_ST_TMR(i));
+ }
+
+ rdata = readl(tcphy->base + CMN_DIAG_HSCLK_SEL);
+ rdata &= ~CLK_PLL_MASK;
+ rdata |= CLK_PLL_CONFIG;
+ writel(rdata, tcphy->base + CMN_DIAG_HSCLK_SEL);
+}
+
+static void tcphy_cfg_usb3_pll(struct rockchip_typec_phy *tcphy)
+{
+ u32 i;
+
+ /* load the configuration of PLL0 */
+ for (i = 0; i < ARRAY_SIZE(usb3_pll_cfg); i++)
+ writel(usb3_pll_cfg[i].value,
+ tcphy->base + usb3_pll_cfg[i].addr);
+}
+
+static void tcphy_cfg_dp_pll(struct rockchip_typec_phy *tcphy)
+{
+ u32 i;
+
+ /* set the default mode to RBR */
+ writel(DP_PLL_CLOCK_ENABLE | DP_PLL_ENABLE | DP_PLL_DATA_RATE_RBR,
+ tcphy->base + DP_CLK_CTL);
+
+ /* load the configuration of PLL1 */
+ for (i = 0; i < ARRAY_SIZE(dp_pll_cfg); i++)
+ writel(dp_pll_cfg[i].value, tcphy->base + dp_pll_cfg[i].addr);
+}
+
+static void tcphy_tx_usb3_cfg_lane(struct rockchip_typec_phy *tcphy, u32 lane)
+{
+ writel(0x7799, tcphy->base + TX_PSC_A0(lane));
+ writel(0x7798, tcphy->base + TX_PSC_A1(lane));
+ writel(0x5098, tcphy->base + TX_PSC_A2(lane));
+ writel(0x5098, tcphy->base + TX_PSC_A3(lane));
+ writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_000(lane));
+ writel(0xbf, tcphy->base + XCVR_DIAG_BIDI_CTRL(lane));
+}
+
+static void tcphy_rx_usb3_cfg_lane(struct rockchip_typec_phy *tcphy, u32 lane)
+{
+ writel(0xa6fd, tcphy->base + RX_PSC_A0(lane));
+ writel(0xa6fd, tcphy->base + RX_PSC_A1(lane));
+ writel(0xa410, tcphy->base + RX_PSC_A2(lane));
+ writel(0x2410, tcphy->base + RX_PSC_A3(lane));
+ writel(0x23ff, tcphy->base + RX_PSC_CAL(lane));
+ writel(0x13, tcphy->base + RX_SIGDET_HL_FILT_TMR(lane));
+ writel(0x03e7, tcphy->base + RX_REE_CTRL_DATA_MASK(lane));
+ writel(0x1004, tcphy->base + RX_DIAG_SIGDET_TUNE(lane));
+ writel(0x2010, tcphy->base + RX_PSC_RDY(lane));
+ writel(0xfb, tcphy->base + XCVR_DIAG_BIDI_CTRL(lane));
+}
+
+static void tcphy_dp_cfg_lane(struct rockchip_typec_phy *tcphy, u32 lane)
+{
+ u16 rdata;
+
+ writel(0xbefc, tcphy->base + XCVR_PSM_RCTRL(lane));
+ writel(0x6799, tcphy->base + TX_PSC_A0(lane));
+ writel(0x6798, tcphy->base + TX_PSC_A1(lane));
+ writel(0x98, tcphy->base + TX_PSC_A2(lane));
+ writel(0x98, tcphy->base + TX_PSC_A3(lane));
+
+ writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_000(lane));
+ writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_001(lane));
+ writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_010(lane));
+ writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_011(lane));
+ writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_100(lane));
+ writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_101(lane));
+ writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_110(lane));
+ writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_111(lane));
+ writel(0, tcphy->base + TX_TXCC_CPOST_MULT_10(lane));
+ writel(0, tcphy->base + TX_TXCC_CPOST_MULT_01(lane));
+ writel(0, tcphy->base + TX_TXCC_CPOST_MULT_00(lane));
+ writel(0, tcphy->base + TX_TXCC_CPOST_MULT_11(lane));
+
+ writel(0x128, tcphy->base + TX_TXCC_CAL_SCLR_MULT(lane));
+ writel(0x400, tcphy->base + TX_DIAG_TX_DRV(lane));
+
+ rdata = readl(tcphy->base + XCVR_DIAG_PLLDRC_CTRL(lane));
+ rdata = (rdata & 0x8fff) | 0x6000;
+ writel(rdata, tcphy->base + XCVR_DIAG_PLLDRC_CTRL(lane));
+}
+
+static inline int property_enable(struct rockchip_typec_phy *tcphy,
+ const struct usb3phy_reg *reg, bool en)
+{
+ u32 mask = 1 << reg->write_enable;
+ u32 val = en << reg->enable_bit;
+
+ return regmap_write(tcphy->grf_regs, reg->offset, val | mask);
+}
+
+static void tcphy_dp_aux_set_flip(struct rockchip_typec_phy *tcphy)
+{
+ u16 tx_ana_ctrl_reg_1;
+
+ /*
+ * Select the polarity of the xcvr:
+ * 1, Reverses the polarity (If TYPEC, Pulls ups aux_p and pull
+ * down aux_m)
+ * 0, Normal polarity (if TYPEC, pulls up aux_m and pulls down
+ * aux_p)
+ */
+ tx_ana_ctrl_reg_1 = readl(tcphy->base + TX_ANA_CTRL_REG_1);
+ if (!tcphy->flip)
+ tx_ana_ctrl_reg_1 |= AUXDA_POLARITY;
+ else
+ tx_ana_ctrl_reg_1 &= ~AUXDA_POLARITY;
+ writel(tx_ana_ctrl_reg_1, tcphy->base + TX_ANA_CTRL_REG_1);
+}
+
+static void tcphy_dp_aux_calibration(struct rockchip_typec_phy *tcphy)
+{
+ u16 val;
+ u16 tx_ana_ctrl_reg_1;
+ u16 tx_ana_ctrl_reg_2;
+ s32 pu_calib_code, pd_calib_code;
+ s32 pu_adj, pd_adj;
+ u16 calib;
+
+ /*
+ * Calculate calibration code as per docs: use an average of the
+ * pull down and pull up. Then add in adjustments.
+ */
+ val = readl(tcphy->base + CMN_TXPUCAL_CTRL);
+ pu_calib_code = CMN_CALIB_CODE_POS(val);
+ val = readl(tcphy->base + CMN_TXPDCAL_CTRL);
+ pd_calib_code = CMN_CALIB_CODE_POS(val);
+ val = readl(tcphy->base + CMN_TXPU_ADJ_CTRL);
+ pu_adj = CMN_CALIB_CODE(val);
+ val = readl(tcphy->base + CMN_TXPD_ADJ_CTRL);
+ pd_adj = CMN_CALIB_CODE(val);
+ calib = (pu_calib_code + pd_calib_code) / 2 + pu_adj + pd_adj;
+
+ /* disable txda_cal_latch_en for rewrite the calibration values */
+ tx_ana_ctrl_reg_1 = readl(tcphy->base + TX_ANA_CTRL_REG_1);
+ tx_ana_ctrl_reg_1 &= ~TXDA_CAL_LATCH_EN;
+ writel(tx_ana_ctrl_reg_1, tcphy->base + TX_ANA_CTRL_REG_1);
+
+ /* write the calibration, then delay 10 ms as sample in docs */
+ val = readl(tcphy->base + TX_DIG_CTRL_REG_2);
+ val &= ~(TX_RESCAL_CODE_MASK << TX_RESCAL_CODE_OFFSET);
+ val |= calib << TX_RESCAL_CODE_OFFSET;
+ writel(val, tcphy->base + TX_DIG_CTRL_REG_2);
+ usleep_range(10000, 10050);
+
+ /*
+ * Enable signal for latch that sample and holds calibration values.
+ * Activate this signal for 1 clock cycle to sample new calibration
+ * values.
+ */
+ tx_ana_ctrl_reg_1 |= TXDA_CAL_LATCH_EN;
+ writel(tx_ana_ctrl_reg_1, tcphy->base + TX_ANA_CTRL_REG_1);
+ usleep_range(150, 200);
+
+ /* set TX Voltage Level and TX Deemphasis to 0 */
+ writel(0, tcphy->base + PHY_DP_TX_CTL);
+
+ /* re-enable decap */
+ tx_ana_ctrl_reg_2 = XCVR_DECAP_EN;
+ writel(tx_ana_ctrl_reg_2, tcphy->base + TX_ANA_CTRL_REG_2);
+ udelay(1);
+ tx_ana_ctrl_reg_2 |= XCVR_DECAP_EN_DEL;
+ writel(tx_ana_ctrl_reg_2, tcphy->base + TX_ANA_CTRL_REG_2);
+
+ writel(0, tcphy->base + TX_ANA_CTRL_REG_3);
+
+ tx_ana_ctrl_reg_1 |= TXDA_UPHY_SUPPLY_EN;
+ writel(tx_ana_ctrl_reg_1, tcphy->base + TX_ANA_CTRL_REG_1);
+ udelay(1);
+ tx_ana_ctrl_reg_1 |= TXDA_UPHY_SUPPLY_EN_DEL;
+ writel(tx_ana_ctrl_reg_1, tcphy->base + TX_ANA_CTRL_REG_1);
+
+ writel(0, tcphy->base + TX_ANA_CTRL_REG_5);
+
+ /*
+ * Programs txda_drv_ldo_prog[15:0], Sets driver LDO
+ * voltage 16'h1001 for DP-AUX-TX and RX
+ */
+ writel(0x1001, tcphy->base + TX_ANA_CTRL_REG_4);
+
+ /* re-enables Bandgap reference for LDO */
+ tx_ana_ctrl_reg_1 |= TXDA_DRV_LDO_EN;
+ writel(tx_ana_ctrl_reg_1, tcphy->base + TX_ANA_CTRL_REG_1);
+ udelay(5);
+ tx_ana_ctrl_reg_1 |= TXDA_BGREF_EN;
+ writel(tx_ana_ctrl_reg_1, tcphy->base + TX_ANA_CTRL_REG_1);
+
+ /*
+ * re-enables the transmitter pre-driver, driver data selection MUX,
+ * and receiver detect circuits.
+ */
+ tx_ana_ctrl_reg_2 |= TXDA_DRV_PREDRV_EN;
+ writel(tx_ana_ctrl_reg_2, tcphy->base + TX_ANA_CTRL_REG_2);
+ udelay(1);
+ tx_ana_ctrl_reg_2 |= TXDA_DRV_PREDRV_EN_DEL;
+ writel(tx_ana_ctrl_reg_2, tcphy->base + TX_ANA_CTRL_REG_2);
+
+ /*
+ * Do all the undocumented magic:
+ * - Turn on TXDA_DP_AUX_EN, whatever that is, even though sample
+ * never shows this going on.
+ * - Turn on TXDA_DECAP_EN (and TXDA_DECAP_EN_DEL) even though
+ * docs say for aux it's always 0.
+ * - Turn off the LDO and BGREF, which we just spent time turning
+ * on above (???).
+ *
+ * Without this magic, things seem worse.
+ */
+ tx_ana_ctrl_reg_1 |= TXDA_DP_AUX_EN;
+ tx_ana_ctrl_reg_1 |= TXDA_DECAP_EN;
+ tx_ana_ctrl_reg_1 &= ~TXDA_DRV_LDO_EN;
+ tx_ana_ctrl_reg_1 &= ~TXDA_BGREF_EN;
+ writel(tx_ana_ctrl_reg_1, tcphy->base + TX_ANA_CTRL_REG_1);
+ udelay(1);
+ tx_ana_ctrl_reg_1 |= TXDA_DECAP_EN_DEL;
+ writel(tx_ana_ctrl_reg_1, tcphy->base + TX_ANA_CTRL_REG_1);
+
+ /*
+ * Undo the work we did to set the LDO voltage.
+ * This doesn't seem to help nor hurt, but it kinda goes with the
+ * undocumented magic above.
+ */
+ writel(0, tcphy->base + TX_ANA_CTRL_REG_4);
+
+ /* Don't set voltage swing to 400 mV peak to peak (differential) */
+ writel(0, tcphy->base + TXDA_COEFF_CALC_CTRL);
+
+ /* Init TXDA_CYA_AUXDA_CYA for unknown magic reasons */
+ writel(0, tcphy->base + TXDA_CYA_AUXDA_CYA);
+
+ /*
+ * More undocumented magic, presumably the goal of which is to
+ * make the "auxda_source_aux_oen" be ignored and instead to decide
+ * about "high impedance state" based on what software puts in the
+ * register TXDA_COEFF_CALC_CTRL (see TX_HIGH_Z). Since we only
+ * program that register once and we don't set the bit TX_HIGH_Z,
+ * presumably the goal here is that we should never put the analog
+ * driver in high impedance state.
+ */
+ val = readl(tcphy->base + TX_DIG_CTRL_REG_2);
+ val |= TX_HIGH_Z_TM_EN;
+ writel(val, tcphy->base + TX_DIG_CTRL_REG_2);
+}
+
+static int tcphy_phy_init(struct rockchip_typec_phy *tcphy, u8 mode)
+{
+ const struct rockchip_usb3phy_port_cfg *cfg = tcphy->port_cfgs;
+ int ret, i;
+ u32 val;
+
+ ret = clk_prepare_enable(tcphy->clk_core);
+ if (ret) {
+ dev_err(tcphy->dev, "Failed to prepare_enable core clock\n");
+ return ret;
+ }
+
+ ret = clk_prepare_enable(tcphy->clk_ref);
+ if (ret) {
+ dev_err(tcphy->dev, "Failed to prepare_enable ref clock\n");
+ goto err_clk_core;
+ }
+
+ reset_control_deassert(tcphy->tcphy_rst);
+
+ property_enable(tcphy, &cfg->typec_conn_dir, tcphy->flip);
+ tcphy_dp_aux_set_flip(tcphy);
+
+ tcphy_cfg_24m(tcphy);
+
+ if (mode == MODE_DFP_DP) {
+ tcphy_cfg_dp_pll(tcphy);
+ for (i = 0; i < 4; i++)
+ tcphy_dp_cfg_lane(tcphy, i);
+
+ writel(PIN_ASSIGN_C_E, tcphy->base + PMA_LANE_CFG);
+ } else {
+ tcphy_cfg_usb3_pll(tcphy);
+ tcphy_cfg_dp_pll(tcphy);
+ if (tcphy->flip) {
+ tcphy_tx_usb3_cfg_lane(tcphy, 3);
+ tcphy_rx_usb3_cfg_lane(tcphy, 2);
+ tcphy_dp_cfg_lane(tcphy, 0);
+ tcphy_dp_cfg_lane(tcphy, 1);
+ } else {
+ tcphy_tx_usb3_cfg_lane(tcphy, 0);
+ tcphy_rx_usb3_cfg_lane(tcphy, 1);
+ tcphy_dp_cfg_lane(tcphy, 2);
+ tcphy_dp_cfg_lane(tcphy, 3);
+ }
+
+ writel(PIN_ASSIGN_D_F, tcphy->base + PMA_LANE_CFG);
+ }
+
+ writel(DP_MODE_ENTER_A2, tcphy->base + DP_MODE_CTL);
+
+ reset_control_deassert(tcphy->uphy_rst);
+
+ ret = readx_poll_timeout(readl, tcphy->base + PMA_CMN_CTRL1,
+ val, val & CMN_READY, 10,
+ PHY_MODE_SET_TIMEOUT);
+ if (ret < 0) {
+ dev_err(tcphy->dev, "wait pma ready timeout\n");
+ ret = -ETIMEDOUT;
+ goto err_wait_pma;
+ }
+
+ reset_control_deassert(tcphy->pipe_rst);
+
+ return 0;
+
+err_wait_pma:
+ reset_control_assert(tcphy->uphy_rst);
+ reset_control_assert(tcphy->tcphy_rst);
+ clk_disable_unprepare(tcphy->clk_ref);
+err_clk_core:
+ clk_disable_unprepare(tcphy->clk_core);
+ return ret;
+}
+
+static void tcphy_phy_deinit(struct rockchip_typec_phy *tcphy)
+{
+ reset_control_assert(tcphy->tcphy_rst);
+ reset_control_assert(tcphy->uphy_rst);
+ reset_control_assert(tcphy->pipe_rst);
+ clk_disable_unprepare(tcphy->clk_core);
+ clk_disable_unprepare(tcphy->clk_ref);
+}
+
+static int tcphy_get_mode(struct rockchip_typec_phy *tcphy)
+{
+ struct extcon_dev *edev = tcphy->extcon;
+ union extcon_property_value property;
+ unsigned int id;
+ u8 mode;
+ int ret, ufp, dp;
+
+ if (!edev)
+ return MODE_DFP_USB;
+
+ ufp = extcon_get_state(edev, EXTCON_USB);
+ dp = extcon_get_state(edev, EXTCON_DISP_DP);
+
+ mode = MODE_DFP_USB;
+ id = EXTCON_USB_HOST;
+
+ if (ufp > 0) {
+ mode = MODE_UFP_USB;
+ id = EXTCON_USB;
+ } else if (dp > 0) {
+ mode = MODE_DFP_DP;
+ id = EXTCON_DISP_DP;
+
+ ret = extcon_get_property(edev, id, EXTCON_PROP_USB_SS,
+ &property);
+ if (ret) {
+ dev_err(tcphy->dev, "get superspeed property failed\n");
+ return ret;
+ }
+
+ if (property.intval)
+ mode |= MODE_DFP_USB;
+ }
+
+ ret = extcon_get_property(edev, id, EXTCON_PROP_USB_TYPEC_POLARITY,
+ &property);
+ if (ret) {
+ dev_err(tcphy->dev, "get polarity property failed\n");
+ return ret;
+ }
+
+ tcphy->flip = property.intval ? 1 : 0;
+
+ return mode;
+}
+
+static int tcphy_cfg_usb3_to_usb2_only(struct rockchip_typec_phy *tcphy,
+ bool value)
+{
+ const struct rockchip_usb3phy_port_cfg *cfg = tcphy->port_cfgs;
+
+ property_enable(tcphy, &cfg->usb3tousb2_en, value);
+ property_enable(tcphy, &cfg->usb3_host_disable, value);
+ property_enable(tcphy, &cfg->usb3_host_port, !value);
+
+ return 0;
+}
+
+static int rockchip_usb3_phy_power_on(struct phy *phy)
+{
+ struct rockchip_typec_phy *tcphy = phy_get_drvdata(phy);
+ const struct rockchip_usb3phy_port_cfg *cfg = tcphy->port_cfgs;
+ const struct usb3phy_reg *reg = &cfg->pipe_status;
+ int timeout, new_mode, ret = 0;
+ u32 val;
+
+ mutex_lock(&tcphy->lock);
+
+ new_mode = tcphy_get_mode(tcphy);
+ if (new_mode < 0) {
+ ret = new_mode;
+ goto unlock_ret;
+ }
+
+ /* DP-only mode; fall back to USB2 */
+ if (!(new_mode & (MODE_DFP_USB | MODE_UFP_USB))) {
+ tcphy_cfg_usb3_to_usb2_only(tcphy, true);
+ goto unlock_ret;
+ }
+
+ if (tcphy->mode == new_mode)
+ goto unlock_ret;
+
+ if (tcphy->mode == MODE_DISCONNECT) {
+ ret = tcphy_phy_init(tcphy, new_mode);
+ if (ret)
+ goto unlock_ret;
+ }
+
+ /* wait TCPHY for pipe ready */
+ for (timeout = 0; timeout < 100; timeout++) {
+ regmap_read(tcphy->grf_regs, reg->offset, &val);
+ if (!(val & BIT(reg->enable_bit))) {
+ tcphy->mode |= new_mode & (MODE_DFP_USB | MODE_UFP_USB);
+
+ /* enable usb3 host */
+ tcphy_cfg_usb3_to_usb2_only(tcphy, false);
+ goto unlock_ret;
+ }
+ usleep_range(10, 20);
+ }
+
+ if (tcphy->mode == MODE_DISCONNECT)
+ tcphy_phy_deinit(tcphy);
+
+ ret = -ETIMEDOUT;
+
+unlock_ret:
+ mutex_unlock(&tcphy->lock);
+ return ret;
+}
+
+static int rockchip_usb3_phy_power_off(struct phy *phy)
+{
+ struct rockchip_typec_phy *tcphy = phy_get_drvdata(phy);
+
+ mutex_lock(&tcphy->lock);
+ tcphy_cfg_usb3_to_usb2_only(tcphy, false);
+
+ if (tcphy->mode == MODE_DISCONNECT)
+ goto unlock;
+
+ tcphy->mode &= ~(MODE_UFP_USB | MODE_DFP_USB);
+ if (tcphy->mode == MODE_DISCONNECT)
+ tcphy_phy_deinit(tcphy);
+
+unlock:
+ mutex_unlock(&tcphy->lock);
+ return 0;
+}
+
+static const struct phy_ops rockchip_usb3_phy_ops = {
+ .power_on = rockchip_usb3_phy_power_on,
+ .power_off = rockchip_usb3_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static int rockchip_dp_phy_power_on(struct phy *phy)
+{
+ struct rockchip_typec_phy *tcphy = phy_get_drvdata(phy);
+ const struct rockchip_usb3phy_port_cfg *cfg = tcphy->port_cfgs;
+ int new_mode, ret = 0;
+ u32 val;
+
+ mutex_lock(&tcphy->lock);
+
+ new_mode = tcphy_get_mode(tcphy);
+ if (new_mode < 0) {
+ ret = new_mode;
+ goto unlock_ret;
+ }
+
+ if (!(new_mode & MODE_DFP_DP)) {
+ ret = -ENODEV;
+ goto unlock_ret;
+ }
+
+ if (tcphy->mode == new_mode)
+ goto unlock_ret;
+
+ /*
+ * If the PHY has been power on, but the mode is not DP only mode,
+ * re-init the PHY for setting all of 4 lanes to DP.
+ */
+ if (new_mode == MODE_DFP_DP && tcphy->mode != MODE_DISCONNECT) {
+ tcphy_phy_deinit(tcphy);
+ ret = tcphy_phy_init(tcphy, new_mode);
+ } else if (tcphy->mode == MODE_DISCONNECT) {
+ ret = tcphy_phy_init(tcphy, new_mode);
+ }
+ if (ret)
+ goto unlock_ret;
+
+ property_enable(tcphy, &cfg->uphy_dp_sel, 1);
+
+ ret = readx_poll_timeout(readl, tcphy->base + DP_MODE_CTL,
+ val, val & DP_MODE_A2, 1000,
+ PHY_MODE_SET_TIMEOUT);
+ if (ret < 0) {
+ dev_err(tcphy->dev, "failed to wait TCPHY enter A2\n");
+ goto power_on_finish;
+ }
+
+ tcphy_dp_aux_calibration(tcphy);
+
+ writel(DP_MODE_ENTER_A0, tcphy->base + DP_MODE_CTL);
+
+ ret = readx_poll_timeout(readl, tcphy->base + DP_MODE_CTL,
+ val, val & DP_MODE_A0, 1000,
+ PHY_MODE_SET_TIMEOUT);
+ if (ret < 0) {
+ writel(DP_MODE_ENTER_A2, tcphy->base + DP_MODE_CTL);
+ dev_err(tcphy->dev, "failed to wait TCPHY enter A0\n");
+ goto power_on_finish;
+ }
+
+ tcphy->mode |= MODE_DFP_DP;
+
+power_on_finish:
+ if (tcphy->mode == MODE_DISCONNECT)
+ tcphy_phy_deinit(tcphy);
+unlock_ret:
+ mutex_unlock(&tcphy->lock);
+ return ret;
+}
+
+static int rockchip_dp_phy_power_off(struct phy *phy)
+{
+ struct rockchip_typec_phy *tcphy = phy_get_drvdata(phy);
+
+ mutex_lock(&tcphy->lock);
+
+ if (tcphy->mode == MODE_DISCONNECT)
+ goto unlock;
+
+ tcphy->mode &= ~MODE_DFP_DP;
+
+ writel(DP_MODE_ENTER_A2, tcphy->base + DP_MODE_CTL);
+
+ if (tcphy->mode == MODE_DISCONNECT)
+ tcphy_phy_deinit(tcphy);
+
+unlock:
+ mutex_unlock(&tcphy->lock);
+ return 0;
+}
+
+static const struct phy_ops rockchip_dp_phy_ops = {
+ .power_on = rockchip_dp_phy_power_on,
+ .power_off = rockchip_dp_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static int tcphy_parse_dt(struct rockchip_typec_phy *tcphy,
+ struct device *dev)
+{
+ tcphy->grf_regs = syscon_regmap_lookup_by_phandle(dev->of_node,
+ "rockchip,grf");
+ if (IS_ERR(tcphy->grf_regs)) {
+ dev_err(dev, "could not find grf dt node\n");
+ return PTR_ERR(tcphy->grf_regs);
+ }
+
+ tcphy->clk_core = devm_clk_get(dev, "tcpdcore");
+ if (IS_ERR(tcphy->clk_core)) {
+ dev_err(dev, "could not get uphy core clock\n");
+ return PTR_ERR(tcphy->clk_core);
+ }
+
+ tcphy->clk_ref = devm_clk_get(dev, "tcpdphy-ref");
+ if (IS_ERR(tcphy->clk_ref)) {
+ dev_err(dev, "could not get uphy ref clock\n");
+ return PTR_ERR(tcphy->clk_ref);
+ }
+
+ tcphy->uphy_rst = devm_reset_control_get(dev, "uphy");
+ if (IS_ERR(tcphy->uphy_rst)) {
+ dev_err(dev, "no uphy_rst reset control found\n");
+ return PTR_ERR(tcphy->uphy_rst);
+ }
+
+ tcphy->pipe_rst = devm_reset_control_get(dev, "uphy-pipe");
+ if (IS_ERR(tcphy->pipe_rst)) {
+ dev_err(dev, "no pipe_rst reset control found\n");
+ return PTR_ERR(tcphy->pipe_rst);
+ }
+
+ tcphy->tcphy_rst = devm_reset_control_get(dev, "uphy-tcphy");
+ if (IS_ERR(tcphy->tcphy_rst)) {
+ dev_err(dev, "no tcphy_rst reset control found\n");
+ return PTR_ERR(tcphy->tcphy_rst);
+ }
+
+ return 0;
+}
+
+static void typec_phy_pre_init(struct rockchip_typec_phy *tcphy)
+{
+ const struct rockchip_usb3phy_port_cfg *cfg = tcphy->port_cfgs;
+
+ reset_control_assert(tcphy->tcphy_rst);
+ reset_control_assert(tcphy->uphy_rst);
+ reset_control_assert(tcphy->pipe_rst);
+
+ /* select external psm clock */
+ property_enable(tcphy, &cfg->external_psm, 1);
+ property_enable(tcphy, &cfg->usb3tousb2_en, 0);
+
+ tcphy->mode = MODE_DISCONNECT;
+}
+
+static int rockchip_typec_phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct device_node *child_np;
+ struct rockchip_typec_phy *tcphy;
+ struct phy_provider *phy_provider;
+ struct resource *res;
+ const struct rockchip_usb3phy_port_cfg *phy_cfgs;
+ int index, ret;
+
+ tcphy = devm_kzalloc(dev, sizeof(*tcphy), GFP_KERNEL);
+ if (!tcphy)
+ return -ENOMEM;
+
+ phy_cfgs = of_device_get_match_data(dev);
+ if (!phy_cfgs) {
+ dev_err(dev, "phy configs are not assigned!\n");
+ return -EINVAL;
+ }
+
+ tcphy->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(tcphy->base))
+ return PTR_ERR(tcphy->base);
+
+ /* find out a proper config which can be matched with dt. */
+ index = 0;
+ while (phy_cfgs[index].reg) {
+ if (phy_cfgs[index].reg == res->start) {
+ tcphy->port_cfgs = &phy_cfgs[index];
+ break;
+ }
+
+ ++index;
+ }
+
+ if (!tcphy->port_cfgs) {
+ dev_err(dev, "no phy-config can be matched with %pOFn node\n",
+ np);
+ return -EINVAL;
+ }
+
+ ret = tcphy_parse_dt(tcphy, dev);
+ if (ret)
+ return ret;
+
+ tcphy->dev = dev;
+ platform_set_drvdata(pdev, tcphy);
+ mutex_init(&tcphy->lock);
+
+ typec_phy_pre_init(tcphy);
+
+ tcphy->extcon = extcon_get_edev_by_phandle(dev, 0);
+ if (IS_ERR(tcphy->extcon)) {
+ if (PTR_ERR(tcphy->extcon) == -ENODEV) {
+ tcphy->extcon = NULL;
+ } else {
+ if (PTR_ERR(tcphy->extcon) != -EPROBE_DEFER)
+ dev_err(dev, "Invalid or missing extcon\n");
+ return PTR_ERR(tcphy->extcon);
+ }
+ }
+
+ pm_runtime_enable(dev);
+
+ for_each_available_child_of_node(np, child_np) {
+ struct phy *phy;
+
+ if (of_node_name_eq(child_np, "dp-port"))
+ phy = devm_phy_create(dev, child_np,
+ &rockchip_dp_phy_ops);
+ else if (of_node_name_eq(child_np, "usb3-port"))
+ phy = devm_phy_create(dev, child_np,
+ &rockchip_usb3_phy_ops);
+ else
+ continue;
+
+ if (IS_ERR(phy)) {
+ dev_err(dev, "failed to create phy: %pOFn\n",
+ child_np);
+ pm_runtime_disable(dev);
+ of_node_put(child_np);
+ return PTR_ERR(phy);
+ }
+
+ phy_set_drvdata(phy, tcphy);
+ }
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ if (IS_ERR(phy_provider)) {
+ dev_err(dev, "Failed to register phy provider\n");
+ pm_runtime_disable(dev);
+ return PTR_ERR(phy_provider);
+ }
+
+ return 0;
+}
+
+static void rockchip_typec_phy_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+}
+
+static const struct of_device_id rockchip_typec_phy_dt_ids[] = {
+ {
+ .compatible = "rockchip,rk3399-typec-phy",
+ .data = &rk3399_usb3phy_port_cfgs
+ },
+ { /* sentinel */ }
+};
+
+MODULE_DEVICE_TABLE(of, rockchip_typec_phy_dt_ids);
+
+static struct platform_driver rockchip_typec_phy_driver = {
+ .probe = rockchip_typec_phy_probe,
+ .remove = rockchip_typec_phy_remove,
+ .driver = {
+ .name = "rockchip-typec-phy",
+ .of_match_table = rockchip_typec_phy_dt_ids,
+ },
+};
+
+module_platform_driver(rockchip_typec_phy_driver);
+
+MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>");
+MODULE_AUTHOR("Kever Yang <kever.yang@rock-chips.com>");
+MODULE_DESCRIPTION("Rockchip USB TYPE-C PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/rockchip/phy-rockchip-usb.c b/drivers/phy/rockchip/phy-rockchip-usb.c
new file mode 100644
index 000000000..cef96739c
--- /dev/null
+++ b/drivers/phy/rockchip/phy-rockchip-usb.c
@@ -0,0 +1,563 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Rockchip usb PHY driver
+ *
+ * Copyright (C) 2014 Yunzhi Li <lyz@rock-chips.com>
+ * Copyright (C) 2014 ROCKCHIP, Inc.
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/hw_bitfield.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+#include <linux/delay.h>
+
+static int enable_usb_uart;
+
+#define UOC_CON0 0x00
+#define UOC_CON0_SIDDQ BIT(13)
+#define UOC_CON0_DISABLE BIT(4)
+#define UOC_CON0_COMMON_ON_N BIT(0)
+
+#define UOC_CON2 0x08
+#define UOC_CON2_SOFT_CON_SEL BIT(2)
+
+#define UOC_CON3 0x0c
+/* bits present on rk3188 and rk3288 phys */
+#define UOC_CON3_UTMI_TERMSEL_FULLSPEED BIT(5)
+#define UOC_CON3_UTMI_XCVRSEELCT_FSTRANSC 1UL
+#define UOC_CON3_UTMI_XCVRSEELCT_MASK GENMASK(4, 3)
+#define UOC_CON3_UTMI_OPMODE_NODRIVING 1UL
+#define UOC_CON3_UTMI_OPMODE_MASK GENMASK(2, 1)
+#define UOC_CON3_UTMI_SUSPENDN BIT(0)
+
+struct rockchip_usb_phys {
+ int reg;
+ const char *pll_name;
+};
+
+struct rockchip_usb_phy_base;
+struct rockchip_usb_phy_pdata {
+ struct rockchip_usb_phys *phys;
+ int (*init_usb_uart)(struct regmap *grf,
+ const struct rockchip_usb_phy_pdata *pdata);
+ int usb_uart_phy;
+};
+
+struct rockchip_usb_phy_base {
+ struct device *dev;
+ struct regmap *reg_base;
+ const struct rockchip_usb_phy_pdata *pdata;
+};
+
+struct rockchip_usb_phy {
+ struct rockchip_usb_phy_base *base;
+ struct device_node *np;
+ unsigned int reg_offset;
+ struct clk *clk;
+ struct clk *clk480m;
+ struct clk_hw clk480m_hw;
+ struct phy *phy;
+ bool uart_enabled;
+ struct reset_control *reset;
+ struct regulator *vbus;
+};
+
+static int rockchip_usb_phy_power(struct rockchip_usb_phy *phy,
+ bool siddq)
+{
+ u32 val = FIELD_PREP_WM16(UOC_CON0_SIDDQ, siddq);
+
+ return regmap_write(phy->base->reg_base, phy->reg_offset, val);
+}
+
+static unsigned long rockchip_usb_phy480m_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ return 480000000;
+}
+
+static void rockchip_usb_phy480m_disable(struct clk_hw *hw)
+{
+ struct rockchip_usb_phy *phy = container_of(hw,
+ struct rockchip_usb_phy,
+ clk480m_hw);
+
+ if (phy->vbus)
+ regulator_disable(phy->vbus);
+
+ /* Power down usb phy analog blocks by set siddq 1 */
+ rockchip_usb_phy_power(phy, 1);
+}
+
+static int rockchip_usb_phy480m_enable(struct clk_hw *hw)
+{
+ struct rockchip_usb_phy *phy = container_of(hw,
+ struct rockchip_usb_phy,
+ clk480m_hw);
+
+ /* Power up usb phy analog blocks by set siddq 0 */
+ return rockchip_usb_phy_power(phy, 0);
+}
+
+static int rockchip_usb_phy480m_is_enabled(struct clk_hw *hw)
+{
+ struct rockchip_usb_phy *phy = container_of(hw,
+ struct rockchip_usb_phy,
+ clk480m_hw);
+ int ret;
+ u32 val;
+
+ ret = regmap_read(phy->base->reg_base, phy->reg_offset, &val);
+ if (ret < 0)
+ return ret;
+
+ return (val & UOC_CON0_SIDDQ) ? 0 : 1;
+}
+
+static const struct clk_ops rockchip_usb_phy480m_ops = {
+ .enable = rockchip_usb_phy480m_enable,
+ .disable = rockchip_usb_phy480m_disable,
+ .is_enabled = rockchip_usb_phy480m_is_enabled,
+ .recalc_rate = rockchip_usb_phy480m_recalc_rate,
+};
+
+static int rockchip_usb_phy_power_off(struct phy *_phy)
+{
+ struct rockchip_usb_phy *phy = phy_get_drvdata(_phy);
+
+ if (phy->uart_enabled)
+ return -EBUSY;
+
+ clk_disable_unprepare(phy->clk480m);
+
+ return 0;
+}
+
+static int rockchip_usb_phy_power_on(struct phy *_phy)
+{
+ struct rockchip_usb_phy *phy = phy_get_drvdata(_phy);
+
+ if (phy->uart_enabled)
+ return -EBUSY;
+
+ if (phy->vbus) {
+ int ret;
+
+ ret = regulator_enable(phy->vbus);
+ if (ret)
+ return ret;
+ }
+
+ return clk_prepare_enable(phy->clk480m);
+}
+
+static int rockchip_usb_phy_reset(struct phy *_phy)
+{
+ struct rockchip_usb_phy *phy = phy_get_drvdata(_phy);
+
+ if (phy->reset) {
+ reset_control_assert(phy->reset);
+ udelay(10);
+ reset_control_deassert(phy->reset);
+ }
+
+ return 0;
+}
+
+static const struct phy_ops ops = {
+ .power_on = rockchip_usb_phy_power_on,
+ .power_off = rockchip_usb_phy_power_off,
+ .reset = rockchip_usb_phy_reset,
+ .owner = THIS_MODULE,
+};
+
+static void rockchip_usb_phy_action(void *data)
+{
+ struct rockchip_usb_phy *rk_phy = data;
+
+ if (!rk_phy->uart_enabled) {
+ of_clk_del_provider(rk_phy->np);
+ clk_unregister(rk_phy->clk480m);
+ }
+
+ if (rk_phy->clk)
+ clk_put(rk_phy->clk);
+}
+
+static int rockchip_usb_phy_init(struct rockchip_usb_phy_base *base,
+ struct device_node *child)
+{
+ struct rockchip_usb_phy *rk_phy;
+ unsigned int reg_offset;
+ const char *clk_name;
+ struct clk_init_data init;
+ int err, i;
+
+ rk_phy = devm_kzalloc(base->dev, sizeof(*rk_phy), GFP_KERNEL);
+ if (!rk_phy)
+ return -ENOMEM;
+
+ rk_phy->base = base;
+ rk_phy->np = child;
+
+ if (of_property_read_u32(child, "reg", &reg_offset)) {
+ dev_err(base->dev, "missing reg property in node %pOFn\n",
+ child);
+ return -EINVAL;
+ }
+
+ rk_phy->reset = of_reset_control_get(child, "phy-reset");
+ if (IS_ERR(rk_phy->reset))
+ rk_phy->reset = NULL;
+
+ rk_phy->reg_offset = reg_offset;
+
+ rk_phy->clk = of_clk_get_by_name(child, "phyclk");
+ if (IS_ERR(rk_phy->clk))
+ rk_phy->clk = NULL;
+
+ i = 0;
+ init.name = NULL;
+ while (base->pdata->phys[i].reg) {
+ if (base->pdata->phys[i].reg == reg_offset) {
+ init.name = base->pdata->phys[i].pll_name;
+ break;
+ }
+ i++;
+ }
+
+ if (!init.name) {
+ dev_err(base->dev, "phy data not found\n");
+ return -EINVAL;
+ }
+
+ if (enable_usb_uart && base->pdata->usb_uart_phy == i) {
+ dev_dbg(base->dev, "phy%d used as uart output\n", i);
+ rk_phy->uart_enabled = true;
+ } else {
+ if (rk_phy->clk) {
+ clk_name = __clk_get_name(rk_phy->clk);
+ init.flags = 0;
+ init.parent_names = &clk_name;
+ init.num_parents = 1;
+ } else {
+ init.flags = 0;
+ init.parent_names = NULL;
+ init.num_parents = 0;
+ }
+
+ init.ops = &rockchip_usb_phy480m_ops;
+ rk_phy->clk480m_hw.init = &init;
+
+ rk_phy->clk480m = clk_register(base->dev, &rk_phy->clk480m_hw);
+ if (IS_ERR(rk_phy->clk480m)) {
+ err = PTR_ERR(rk_phy->clk480m);
+ goto err_clk;
+ }
+
+ err = of_clk_add_provider(child, of_clk_src_simple_get,
+ rk_phy->clk480m);
+ if (err < 0)
+ goto err_clk_prov;
+ }
+
+ err = devm_add_action_or_reset(base->dev, rockchip_usb_phy_action,
+ rk_phy);
+ if (err)
+ return err;
+
+ rk_phy->phy = devm_phy_create(base->dev, child, &ops);
+ if (IS_ERR(rk_phy->phy)) {
+ dev_err(base->dev, "failed to create PHY\n");
+ return PTR_ERR(rk_phy->phy);
+ }
+ phy_set_drvdata(rk_phy->phy, rk_phy);
+
+ rk_phy->vbus = devm_regulator_get_optional(&rk_phy->phy->dev, "vbus");
+ if (IS_ERR(rk_phy->vbus)) {
+ if (PTR_ERR(rk_phy->vbus) == -EPROBE_DEFER)
+ return PTR_ERR(rk_phy->vbus);
+ rk_phy->vbus = NULL;
+ }
+
+ /*
+ * When acting as uart-pipe, just keep clock on otherwise
+ * only power up usb phy when it use, so disable it when init
+ */
+ if (rk_phy->uart_enabled)
+ return clk_prepare_enable(rk_phy->clk);
+ else
+ return rockchip_usb_phy_power(rk_phy, 1);
+
+err_clk_prov:
+ if (!rk_phy->uart_enabled)
+ clk_unregister(rk_phy->clk480m);
+err_clk:
+ if (rk_phy->clk)
+ clk_put(rk_phy->clk);
+ return err;
+}
+
+static const struct rockchip_usb_phy_pdata rk3066a_pdata = {
+ .phys = (struct rockchip_usb_phys[]){
+ { .reg = 0x17c, .pll_name = "sclk_otgphy0_480m" },
+ { .reg = 0x188, .pll_name = "sclk_otgphy1_480m" },
+ { /* sentinel */ }
+ },
+};
+
+static int __init rockchip_init_usb_uart_common(struct regmap *grf,
+ const struct rockchip_usb_phy_pdata *pdata)
+{
+ int regoffs = pdata->phys[pdata->usb_uart_phy].reg;
+ int ret;
+ u32 val;
+
+ /*
+ * COMMON_ON and DISABLE settings are described in the TRM,
+ * but were not present in the original code.
+ * Also disable the analog phy components to save power.
+ */
+ val = FIELD_PREP_WM16(UOC_CON0_COMMON_ON_N, 1) |
+ FIELD_PREP_WM16(UOC_CON0_DISABLE, 1) |
+ FIELD_PREP_WM16(UOC_CON0_SIDDQ, 1);
+ ret = regmap_write(grf, regoffs + UOC_CON0, val);
+ if (ret)
+ return ret;
+
+ val = FIELD_PREP_WM16(UOC_CON2_SOFT_CON_SEL, 1);
+ ret = regmap_write(grf, regoffs + UOC_CON2, val);
+ if (ret)
+ return ret;
+
+ val = FIELD_PREP_WM16(UOC_CON3_UTMI_SUSPENDN, 0) |
+ FIELD_PREP_WM16(UOC_CON3_UTMI_OPMODE_MASK,
+ UOC_CON3_UTMI_OPMODE_NODRIVING) |
+ FIELD_PREP_WM16(UOC_CON3_UTMI_XCVRSEELCT_MASK,
+ UOC_CON3_UTMI_XCVRSEELCT_FSTRANSC) |
+ FIELD_PREP_WM16(UOC_CON3_UTMI_TERMSEL_FULLSPEED, 1);
+ ret = regmap_write(grf, UOC_CON3, val);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+#define RK3188_UOC0_CON0 0x10c
+#define RK3188_UOC0_CON0_BYPASSSEL BIT(9)
+#define RK3188_UOC0_CON0_BYPASSDMEN BIT(8)
+
+/*
+ * Enable the bypass of uart2 data through the otg usb phy.
+ * See description of rk3288-variant for details.
+ */
+static int __init rk3188_init_usb_uart(struct regmap *grf,
+ const struct rockchip_usb_phy_pdata *pdata)
+{
+ u32 val;
+ int ret;
+
+ ret = rockchip_init_usb_uart_common(grf, pdata);
+ if (ret)
+ return ret;
+
+ val = FIELD_PREP_WM16(RK3188_UOC0_CON0_BYPASSSEL, 1) |
+ FIELD_PREP_WM16(RK3188_UOC0_CON0_BYPASSDMEN, 1);
+ ret = regmap_write(grf, RK3188_UOC0_CON0, val);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static const struct rockchip_usb_phy_pdata rk3188_pdata = {
+ .phys = (struct rockchip_usb_phys[]){
+ { .reg = 0x10c, .pll_name = "sclk_otgphy0_480m" },
+ { .reg = 0x11c, .pll_name = "sclk_otgphy1_480m" },
+ { /* sentinel */ }
+ },
+ .init_usb_uart = rk3188_init_usb_uart,
+ .usb_uart_phy = 0,
+};
+
+#define RK3288_UOC0_CON3 0x32c
+#define RK3288_UOC0_CON3_BYPASSDMEN BIT(6)
+#define RK3288_UOC0_CON3_BYPASSSEL BIT(7)
+
+/*
+ * Enable the bypass of uart2 data through the otg usb phy.
+ * Original description in the TRM.
+ * 1. Disable the OTG block by setting OTGDISABLE0 to 1’b1.
+ * 2. Disable the pull-up resistance on the D+ line by setting
+ * OPMODE0[1:0] to 2’b01.
+ * 3. To ensure that the XO, Bias, and PLL blocks are powered down in Suspend
+ * mode, set COMMONONN to 1’b1.
+ * 4. Place the USB PHY in Suspend mode by setting SUSPENDM0 to 1’b0.
+ * 5. Set BYPASSSEL0 to 1’b1.
+ * 6. To transmit data, controls BYPASSDMEN0, and BYPASSDMDATA0.
+ * To receive data, monitor FSVPLUS0.
+ *
+ * The actual code in the vendor kernel does some things differently.
+ */
+static int __init rk3288_init_usb_uart(struct regmap *grf,
+ const struct rockchip_usb_phy_pdata *pdata)
+{
+ u32 val;
+ int ret;
+
+ ret = rockchip_init_usb_uart_common(grf, pdata);
+ if (ret)
+ return ret;
+
+ val = FIELD_PREP_WM16(RK3288_UOC0_CON3_BYPASSSEL, 1) |
+ FIELD_PREP_WM16(RK3288_UOC0_CON3_BYPASSDMEN, 1);
+ ret = regmap_write(grf, RK3288_UOC0_CON3, val);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static const struct rockchip_usb_phy_pdata rk3288_pdata = {
+ .phys = (struct rockchip_usb_phys[]){
+ { .reg = 0x320, .pll_name = "sclk_otgphy0_480m" },
+ { .reg = 0x334, .pll_name = "sclk_otgphy1_480m" },
+ { .reg = 0x348, .pll_name = "sclk_otgphy2_480m" },
+ { /* sentinel */ }
+ },
+ .init_usb_uart = rk3288_init_usb_uart,
+ .usb_uart_phy = 0,
+};
+
+static int rockchip_usb_phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rockchip_usb_phy_base *phy_base;
+ struct phy_provider *phy_provider;
+ int err;
+
+ phy_base = devm_kzalloc(dev, sizeof(*phy_base), GFP_KERNEL);
+ if (!phy_base)
+ return -ENOMEM;
+
+ phy_base->pdata = device_get_match_data(dev);
+ if (!phy_base->pdata) {
+ dev_err(dev, "missing phy data\n");
+ return -EINVAL;
+ }
+
+ phy_base->dev = dev;
+ phy_base->reg_base = ERR_PTR(-ENODEV);
+ if (dev->parent && dev->parent->of_node)
+ phy_base->reg_base = syscon_node_to_regmap(
+ dev->parent->of_node);
+ if (IS_ERR(phy_base->reg_base))
+ phy_base->reg_base = syscon_regmap_lookup_by_phandle(
+ dev->of_node, "rockchip,grf");
+ if (IS_ERR(phy_base->reg_base)) {
+ dev_err(&pdev->dev, "Missing rockchip,grf property\n");
+ return PTR_ERR(phy_base->reg_base);
+ }
+
+ for_each_available_child_of_node_scoped(dev->of_node, child) {
+ err = rockchip_usb_phy_init(phy_base, child);
+ if (err)
+ return err;
+ }
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id rockchip_usb_phy_dt_ids[] = {
+ { .compatible = "rockchip,rk3066a-usb-phy", .data = &rk3066a_pdata },
+ { .compatible = "rockchip,rk3188-usb-phy", .data = &rk3188_pdata },
+ { .compatible = "rockchip,rk3288-usb-phy", .data = &rk3288_pdata },
+ {}
+};
+
+MODULE_DEVICE_TABLE(of, rockchip_usb_phy_dt_ids);
+
+static struct platform_driver rockchip_usb_driver = {
+ .probe = rockchip_usb_phy_probe,
+ .driver = {
+ .name = "rockchip-usb-phy",
+ .of_match_table = rockchip_usb_phy_dt_ids,
+ },
+};
+
+module_platform_driver(rockchip_usb_driver);
+
+#ifndef MODULE
+static int __init rockchip_init_usb_uart(void)
+{
+ const struct of_device_id *match;
+ const struct rockchip_usb_phy_pdata *data;
+ struct device_node *np;
+ struct regmap *grf;
+ int ret;
+
+ if (!enable_usb_uart)
+ return 0;
+
+ np = of_find_matching_node_and_match(NULL, rockchip_usb_phy_dt_ids,
+ &match);
+ if (!np) {
+ pr_err("%s: failed to find usbphy node\n", __func__);
+ return -ENOTSUPP;
+ }
+
+ pr_debug("%s: using settings for %s\n", __func__, match->compatible);
+ data = match->data;
+
+ if (!data->init_usb_uart) {
+ pr_err("%s: usb-uart not available on %s\n",
+ __func__, match->compatible);
+ return -ENOTSUPP;
+ }
+
+ grf = ERR_PTR(-ENODEV);
+ if (np->parent)
+ grf = syscon_node_to_regmap(np->parent);
+ if (IS_ERR(grf))
+ grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
+ if (IS_ERR(grf)) {
+ pr_err("%s: Missing rockchip,grf property, %lu\n",
+ __func__, PTR_ERR(grf));
+ return PTR_ERR(grf);
+ }
+
+ ret = data->init_usb_uart(grf, data);
+ if (ret) {
+ pr_err("%s: could not init usb_uart, %d\n", __func__, ret);
+ enable_usb_uart = 0;
+ return ret;
+ }
+
+ return 0;
+}
+early_initcall(rockchip_init_usb_uart);
+
+static int __init rockchip_usb_uart(char *buf)
+{
+ enable_usb_uart = true;
+ return 0;
+}
+early_param("rockchip.usb_uart", rockchip_usb_uart);
+#endif
+
+MODULE_AUTHOR("Yunzhi Li <lyz@rock-chips.com>");
+MODULE_DESCRIPTION("Rockchip USB 2.0 PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c
new file mode 100644
index 000000000..07a839e41
--- /dev/null
+++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c
@@ -0,0 +1,1665 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Rockchip USBDP Combo PHY with Samsung IP block driver
+ *
+ * Copyright (C) 2021-2024 Rockchip Electronics Co., Ltd
+ * Copyright (C) 2024 Collabora Ltd
+ */
+
+#include <dt-bindings/phy/phy.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/typec_dp.h>
+#include <linux/usb/typec_mux.h>
+
+/* USBDP PHY Register Definitions */
+#define UDPHY_PCS 0x4000
+#define UDPHY_PMA 0x8000
+
+/* VO0 GRF Registers */
+#define DP_SINK_HPD_CFG BIT(11)
+#define DP_SINK_HPD_SEL BIT(10)
+#define DP_AUX_DIN_SEL BIT(9)
+#define DP_AUX_DOUT_SEL BIT(8)
+#define DP_LANE_SEL_N(n) GENMASK(2 * (n) + 1, 2 * (n))
+#define DP_LANE_SEL_ALL GENMASK(7, 0)
+
+/* PMA CMN Registers */
+#define CMN_LANE_MUX_AND_EN_OFFSET 0x0288 /* cmn_reg00A2 */
+#define CMN_DP_LANE_MUX_N(n) BIT((n) + 4)
+#define CMN_DP_LANE_EN_N(n) BIT(n)
+#define CMN_DP_LANE_MUX_ALL GENMASK(7, 4)
+#define CMN_DP_LANE_EN_ALL GENMASK(3, 0)
+
+#define CMN_DP_LINK_OFFSET 0x28c /* cmn_reg00A3 */
+#define CMN_DP_TX_LINK_BW GENMASK(6, 5)
+#define CMN_DP_TX_LANE_SWAP_EN BIT(2)
+
+#define CMN_SSC_EN_OFFSET 0x2d0 /* cmn_reg00B4 */
+#define CMN_ROPLL_SSC_EN BIT(1)
+#define CMN_LCPLL_SSC_EN BIT(0)
+
+#define CMN_ANA_LCPLL_DONE_OFFSET 0x0350 /* cmn_reg00D4 */
+#define CMN_ANA_LCPLL_LOCK_DONE BIT(7)
+#define CMN_ANA_LCPLL_AFC_DONE BIT(6)
+
+#define CMN_ANA_ROPLL_DONE_OFFSET 0x0354 /* cmn_reg00D5 */
+#define CMN_ANA_ROPLL_LOCK_DONE BIT(1)
+#define CMN_ANA_ROPLL_AFC_DONE BIT(0)
+
+#define CMN_DP_RSTN_OFFSET 0x038c /* cmn_reg00E3 */
+#define CMN_DP_INIT_RSTN BIT(3)
+#define CMN_DP_CMN_RSTN BIT(2)
+#define CMN_CDR_WTCHDG_EN BIT(1)
+#define CMN_CDR_WTCHDG_MSK_CDR_EN BIT(0)
+
+#define TRSV_ANA_TX_CLK_OFFSET_N(n) (0x854 + (n) * 0x800) /* trsv_reg0215 */
+#define LN_ANA_TX_SER_TXCLK_INV BIT(1)
+
+#define TRSV_LN0_MON_RX_CDR_DONE_OFFSET 0x0b84 /* trsv_reg02E1 */
+#define TRSV_LN0_MON_RX_CDR_LOCK_DONE BIT(0)
+
+#define TRSV_LN2_MON_RX_CDR_DONE_OFFSET 0x1b84 /* trsv_reg06E1 */
+#define TRSV_LN2_MON_RX_CDR_LOCK_DONE BIT(0)
+
+#define BIT_WRITEABLE_SHIFT 16
+#define PHY_AUX_DP_DATA_POL_NORMAL 0
+#define PHY_AUX_DP_DATA_POL_INVERT 1
+#define PHY_LANE_MUX_USB 0
+#define PHY_LANE_MUX_DP 1
+
+enum {
+ DP_BW_RBR,
+ DP_BW_HBR,
+ DP_BW_HBR2,
+ DP_BW_HBR3,
+};
+
+enum {
+ UDPHY_MODE_NONE = 0,
+ UDPHY_MODE_USB = BIT(0),
+ UDPHY_MODE_DP = BIT(1),
+ UDPHY_MODE_DP_USB = BIT(1) | BIT(0),
+};
+
+struct rk_udphy_grf_reg {
+ unsigned int offset;
+ unsigned int disable;
+ unsigned int enable;
+};
+
+#define _RK_UDPHY_GEN_GRF_REG(offset, mask, disable, enable) \
+{\
+ offset, \
+ FIELD_PREP_CONST(mask, disable) | (mask << BIT_WRITEABLE_SHIFT), \
+ FIELD_PREP_CONST(mask, enable) | (mask << BIT_WRITEABLE_SHIFT), \
+}
+
+#define RK_UDPHY_GEN_GRF_REG(offset, bitend, bitstart, disable, enable) \
+ _RK_UDPHY_GEN_GRF_REG(offset, GENMASK(bitend, bitstart), disable, enable)
+
+struct rk_udphy_grf_cfg {
+ /* u2phy-grf */
+ struct rk_udphy_grf_reg bvalid_phy_con;
+ struct rk_udphy_grf_reg bvalid_grf_con;
+
+ /* usb-grf */
+ struct rk_udphy_grf_reg usb3otg0_cfg;
+ struct rk_udphy_grf_reg usb3otg1_cfg;
+
+ /* usbdpphy-grf */
+ struct rk_udphy_grf_reg low_pwrn;
+ struct rk_udphy_grf_reg rx_lfps;
+};
+
+struct rk_udphy_vogrf_cfg {
+ /* vo-grf */
+ struct rk_udphy_grf_reg hpd_trigger;
+ u32 dp_lane_reg;
+};
+
+struct rk_udphy_dp_tx_drv_ctrl {
+ u32 trsv_reg0204;
+ u32 trsv_reg0205;
+ u32 trsv_reg0206;
+ u32 trsv_reg0207;
+};
+
+struct rk_udphy_cfg {
+ unsigned int num_phys;
+ unsigned int phy_ids[2];
+ /* resets to be requested */
+ const char * const *rst_list;
+ int num_rsts;
+
+ struct rk_udphy_grf_cfg grfcfg;
+ struct rk_udphy_vogrf_cfg vogrfcfg[2];
+ const struct rk_udphy_dp_tx_drv_ctrl (*dp_tx_ctrl_cfg[4])[4];
+ const struct rk_udphy_dp_tx_drv_ctrl (*dp_tx_ctrl_cfg_typec[4])[4];
+};
+
+struct rk_udphy {
+ struct device *dev;
+ struct regmap *pma_regmap;
+ struct regmap *u2phygrf;
+ struct regmap *udphygrf;
+ struct regmap *usbgrf;
+ struct regmap *vogrf;
+ struct typec_switch_dev *sw;
+ struct typec_mux_dev *mux;
+ struct mutex mutex; /* mutex to protect access to individual PHYs */
+
+ /* clocks and rests */
+ int num_clks;
+ struct clk_bulk_data *clks;
+ struct clk *refclk;
+ int num_rsts;
+ struct reset_control_bulk_data *rsts;
+
+ /* PHY status management */
+ bool flip;
+ bool mode_change;
+ u8 mode;
+ u8 status;
+
+ /* utilized for USB */
+ bool hs; /* flag for high-speed */
+
+ /* utilized for DP */
+ struct gpio_desc *sbu1_dc_gpio;
+ struct gpio_desc *sbu2_dc_gpio;
+ u32 lane_mux_sel[4];
+ u32 dp_lane_sel[4];
+ u32 dp_aux_dout_sel;
+ u32 dp_aux_din_sel;
+ bool dp_sink_hpd_sel;
+ bool dp_sink_hpd_cfg;
+ unsigned int link_rate;
+ unsigned int lanes;
+ u8 bw;
+ int id;
+
+ bool dp_in_use;
+
+ /* PHY const config */
+ const struct rk_udphy_cfg *cfgs;
+
+ /* PHY devices */
+ struct phy *phy_dp;
+ struct phy *phy_u3;
+};
+
+static const struct rk_udphy_dp_tx_drv_ctrl rk3588_dp_tx_drv_ctrl_rbr_hbr[4][4] = {
+ /* voltage swing 0, pre-emphasis 0->3 */
+ {
+ { 0x20, 0x10, 0x42, 0xe5 },
+ { 0x26, 0x14, 0x42, 0xe5 },
+ { 0x29, 0x18, 0x42, 0xe5 },
+ { 0x2b, 0x1c, 0x43, 0xe7 },
+ },
+
+ /* voltage swing 1, pre-emphasis 0->2 */
+ {
+ { 0x23, 0x10, 0x42, 0xe7 },
+ { 0x2a, 0x17, 0x43, 0xe7 },
+ { 0x2b, 0x1a, 0x43, 0xe7 },
+ },
+
+ /* voltage swing 2, pre-emphasis 0->1 */
+ {
+ { 0x27, 0x10, 0x42, 0xe7 },
+ { 0x2b, 0x17, 0x43, 0xe7 },
+ },
+
+ /* voltage swing 3, pre-emphasis 0 */
+ {
+ { 0x29, 0x10, 0x43, 0xe7 },
+ },
+};
+
+static const struct rk_udphy_dp_tx_drv_ctrl rk3588_dp_tx_drv_ctrl_rbr_hbr_typec[4][4] = {
+ /* voltage swing 0, pre-emphasis 0->3 */
+ {
+ { 0x20, 0x10, 0x42, 0xe5 },
+ { 0x26, 0x14, 0x42, 0xe5 },
+ { 0x29, 0x18, 0x42, 0xe5 },
+ { 0x2b, 0x1c, 0x43, 0xe7 },
+ },
+
+ /* voltage swing 1, pre-emphasis 0->2 */
+ {
+ { 0x23, 0x10, 0x42, 0xe7 },
+ { 0x2a, 0x17, 0x43, 0xe7 },
+ { 0x2b, 0x1a, 0x43, 0xe7 },
+ },
+
+ /* voltage swing 2, pre-emphasis 0->1 */
+ {
+ { 0x27, 0x10, 0x43, 0x67 },
+ { 0x2b, 0x17, 0x43, 0xe7 },
+ },
+
+ /* voltage swing 3, pre-emphasis 0 */
+ {
+ { 0x29, 0x10, 0x43, 0xe7 },
+ },
+};
+
+static const struct rk_udphy_dp_tx_drv_ctrl rk3588_dp_tx_drv_ctrl_hbr2[4][4] = {
+ /* voltage swing 0, pre-emphasis 0->3 */
+ {
+ { 0x21, 0x10, 0x42, 0xe5 },
+ { 0x26, 0x14, 0x42, 0xe5 },
+ { 0x26, 0x16, 0x43, 0xe5 },
+ { 0x2a, 0x19, 0x43, 0xe7 },
+ },
+
+ /* voltage swing 1, pre-emphasis 0->2 */
+ {
+ { 0x24, 0x10, 0x42, 0xe7 },
+ { 0x2a, 0x17, 0x43, 0xe7 },
+ { 0x2b, 0x1a, 0x43, 0xe7 },
+ },
+
+ /* voltage swing 2, pre-emphasis 0->1 */
+ {
+ { 0x28, 0x10, 0x42, 0xe7 },
+ { 0x2b, 0x17, 0x43, 0xe7 },
+ },
+
+ /* voltage swing 3, pre-emphasis 0 */
+ {
+ { 0x28, 0x10, 0x43, 0xe7 },
+ },
+};
+
+static const struct rk_udphy_dp_tx_drv_ctrl rk3588_dp_tx_drv_ctrl_hbr3[4][4] = {
+ /* voltage swing 0, pre-emphasis 0->3 */
+ {
+ { 0x21, 0x10, 0x42, 0xe5 },
+ { 0x26, 0x14, 0x42, 0xe5 },
+ { 0x26, 0x16, 0x43, 0xe5 },
+ { 0x29, 0x18, 0x43, 0xe7 },
+ },
+
+ /* voltage swing 1, pre-emphasis 0->2 */
+ {
+ { 0x24, 0x10, 0x42, 0xe7 },
+ { 0x2a, 0x18, 0x43, 0xe7 },
+ { 0x2b, 0x1b, 0x43, 0xe7 }
+ },
+
+ /* voltage swing 2, pre-emphasis 0->1 */
+ {
+ { 0x27, 0x10, 0x42, 0xe7 },
+ { 0x2b, 0x18, 0x43, 0xe7 }
+ },
+
+ /* voltage swing 3, pre-emphasis 0 */
+ {
+ { 0x28, 0x10, 0x43, 0xe7 },
+ },
+};
+
+static const struct reg_sequence rk_udphy_24m_refclk_cfg[] = {
+ {0x0090, 0x68}, {0x0094, 0x68},
+ {0x0128, 0x24}, {0x012c, 0x44},
+ {0x0130, 0x3f}, {0x0134, 0x44},
+ {0x015c, 0xa9}, {0x0160, 0x71},
+ {0x0164, 0x71}, {0x0168, 0xa9},
+ {0x0174, 0xa9}, {0x0178, 0x71},
+ {0x017c, 0x71}, {0x0180, 0xa9},
+ {0x018c, 0x41}, {0x0190, 0x00},
+ {0x0194, 0x05}, {0x01ac, 0x2a},
+ {0x01b0, 0x17}, {0x01b4, 0x17},
+ {0x01b8, 0x2a}, {0x01c8, 0x04},
+ {0x01cc, 0x08}, {0x01d0, 0x08},
+ {0x01d4, 0x04}, {0x01d8, 0x20},
+ {0x01dc, 0x01}, {0x01e0, 0x09},
+ {0x01e4, 0x03}, {0x01f0, 0x29},
+ {0x01f4, 0x02}, {0x01f8, 0x02},
+ {0x01fc, 0x29}, {0x0208, 0x2a},
+ {0x020c, 0x17}, {0x0210, 0x17},
+ {0x0214, 0x2a}, {0x0224, 0x20},
+ {0x03f0, 0x0a}, {0x03f4, 0x07},
+ {0x03f8, 0x07}, {0x03fc, 0x0c},
+ {0x0404, 0x12}, {0x0408, 0x1a},
+ {0x040c, 0x1a}, {0x0410, 0x3f},
+ {0x0ce0, 0x68}, {0x0ce8, 0xd0},
+ {0x0cf0, 0x87}, {0x0cf8, 0x70},
+ {0x0d00, 0x70}, {0x0d08, 0xa9},
+ {0x1ce0, 0x68}, {0x1ce8, 0xd0},
+ {0x1cf0, 0x87}, {0x1cf8, 0x70},
+ {0x1d00, 0x70}, {0x1d08, 0xa9},
+ {0x0a3c, 0xd0}, {0x0a44, 0xd0},
+ {0x0a48, 0x01}, {0x0a4c, 0x0d},
+ {0x0a54, 0xe0}, {0x0a5c, 0xe0},
+ {0x0a64, 0xa8}, {0x1a3c, 0xd0},
+ {0x1a44, 0xd0}, {0x1a48, 0x01},
+ {0x1a4c, 0x0d}, {0x1a54, 0xe0},
+ {0x1a5c, 0xe0}, {0x1a64, 0xa8}
+};
+
+static const struct reg_sequence rk_udphy_26m_refclk_cfg[] = {
+ {0x0830, 0x07}, {0x085c, 0x80},
+ {0x1030, 0x07}, {0x105c, 0x80},
+ {0x1830, 0x07}, {0x185c, 0x80},
+ {0x2030, 0x07}, {0x205c, 0x80},
+ {0x0228, 0x38}, {0x0104, 0x44},
+ {0x0248, 0x44}, {0x038c, 0x02},
+ {0x0878, 0x04}, {0x1878, 0x04},
+ {0x0898, 0x77}, {0x1898, 0x77},
+ {0x0054, 0x01}, {0x00e0, 0x38},
+ {0x0060, 0x24}, {0x0064, 0x77},
+ {0x0070, 0x76}, {0x0234, 0xe8},
+ {0x0af4, 0x15}, {0x1af4, 0x15},
+ {0x081c, 0xe5}, {0x181c, 0xe5},
+ {0x099c, 0x48}, {0x199c, 0x48},
+ {0x09a4, 0x07}, {0x09a8, 0x22},
+ {0x19a4, 0x07}, {0x19a8, 0x22},
+ {0x09b8, 0x3e}, {0x19b8, 0x3e},
+ {0x09e4, 0x02}, {0x19e4, 0x02},
+ {0x0a34, 0x1e}, {0x1a34, 0x1e},
+ {0x0a98, 0x2f}, {0x1a98, 0x2f},
+ {0x0c30, 0x0e}, {0x0c48, 0x06},
+ {0x1c30, 0x0e}, {0x1c48, 0x06},
+ {0x028c, 0x18}, {0x0af0, 0x00},
+ {0x1af0, 0x00}
+};
+
+static const struct reg_sequence rk_udphy_init_sequence[] = {
+ {0x0104, 0x44}, {0x0234, 0xe8},
+ {0x0248, 0x44}, {0x028c, 0x18},
+ {0x081c, 0xe5}, {0x0878, 0x00},
+ {0x0994, 0x1c}, {0x0af0, 0x00},
+ {0x181c, 0xe5}, {0x1878, 0x00},
+ {0x1994, 0x1c}, {0x1af0, 0x00},
+ {0x0428, 0x60}, {0x0d58, 0x33},
+ {0x1d58, 0x33}, {0x0990, 0x74},
+ {0x0d64, 0x17}, {0x08c8, 0x13},
+ {0x1990, 0x74}, {0x1d64, 0x17},
+ {0x18c8, 0x13}, {0x0d90, 0x40},
+ {0x0da8, 0x40}, {0x0dc0, 0x40},
+ {0x0dd8, 0x40}, {0x1d90, 0x40},
+ {0x1da8, 0x40}, {0x1dc0, 0x40},
+ {0x1dd8, 0x40}, {0x03c0, 0x30},
+ {0x03c4, 0x06}, {0x0e10, 0x00},
+ {0x1e10, 0x00}, {0x043c, 0x0f},
+ {0x0d2c, 0xff}, {0x1d2c, 0xff},
+ {0x0d34, 0x0f}, {0x1d34, 0x0f},
+ {0x08fc, 0x2a}, {0x0914, 0x28},
+ {0x0a30, 0x03}, {0x0e38, 0x03},
+ {0x0ecc, 0x27}, {0x0ed0, 0x22},
+ {0x0ed4, 0x26}, {0x18fc, 0x2a},
+ {0x1914, 0x28}, {0x1a30, 0x03},
+ {0x1e38, 0x03}, {0x1ecc, 0x27},
+ {0x1ed0, 0x22}, {0x1ed4, 0x26},
+ {0x0048, 0x0f}, {0x0060, 0x3c},
+ {0x0064, 0xf7}, {0x006c, 0x20},
+ {0x0070, 0x7d}, {0x0074, 0x68},
+ {0x0af4, 0x1a}, {0x1af4, 0x1a},
+ {0x0440, 0x3f}, {0x10d4, 0x08},
+ {0x20d4, 0x08}, {0x00d4, 0x30},
+ {0x0024, 0x6e},
+};
+
+static inline int rk_udphy_grfreg_write(struct regmap *base,
+ const struct rk_udphy_grf_reg *reg, bool en)
+{
+ return regmap_write(base, reg->offset, en ? reg->enable : reg->disable);
+}
+
+static int rk_udphy_clk_init(struct rk_udphy *udphy, struct device *dev)
+{
+ int i;
+
+ udphy->num_clks = devm_clk_bulk_get_all(dev, &udphy->clks);
+ if (udphy->num_clks < 1)
+ return -ENODEV;
+
+ /* used for configure phy reference clock frequency */
+ for (i = 0; i < udphy->num_clks; i++) {
+ if (!strncmp(udphy->clks[i].id, "refclk", 6)) {
+ udphy->refclk = udphy->clks[i].clk;
+ break;
+ }
+ }
+
+ if (!udphy->refclk)
+ return dev_err_probe(udphy->dev, -EINVAL, "no refclk found\n");
+
+ return 0;
+}
+
+static int rk_udphy_reset_assert_all(struct rk_udphy *udphy)
+{
+ return reset_control_bulk_assert(udphy->num_rsts, udphy->rsts);
+}
+
+static int rk_udphy_reset_deassert_all(struct rk_udphy *udphy)
+{
+ return reset_control_bulk_deassert(udphy->num_rsts, udphy->rsts);
+}
+
+static int rk_udphy_reset_deassert(struct rk_udphy *udphy, char *name)
+{
+ struct reset_control_bulk_data *list = udphy->rsts;
+ int idx;
+
+ for (idx = 0; idx < udphy->num_rsts; idx++) {
+ if (!strcmp(list[idx].id, name))
+ return reset_control_deassert(list[idx].rstc);
+ }
+
+ return -EINVAL;
+}
+
+static int rk_udphy_reset_init(struct rk_udphy *udphy, struct device *dev)
+{
+ const struct rk_udphy_cfg *cfg = udphy->cfgs;
+ int idx;
+
+ udphy->num_rsts = cfg->num_rsts;
+ udphy->rsts = devm_kcalloc(dev, udphy->num_rsts,
+ sizeof(*udphy->rsts), GFP_KERNEL);
+ if (!udphy->rsts)
+ return -ENOMEM;
+
+ for (idx = 0; idx < cfg->num_rsts; idx++)
+ udphy->rsts[idx].id = cfg->rst_list[idx];
+
+ return devm_reset_control_bulk_get_exclusive(dev, cfg->num_rsts,
+ udphy->rsts);
+}
+
+static void rk_udphy_u3_port_disable(struct rk_udphy *udphy, u8 disable)
+{
+ const struct rk_udphy_cfg *cfg = udphy->cfgs;
+ const struct rk_udphy_grf_reg *preg;
+
+ preg = udphy->id ? &cfg->grfcfg.usb3otg1_cfg : &cfg->grfcfg.usb3otg0_cfg;
+ rk_udphy_grfreg_write(udphy->usbgrf, preg, disable);
+}
+
+static void rk_udphy_usb_bvalid_enable(struct rk_udphy *udphy, u8 enable)
+{
+ const struct rk_udphy_cfg *cfg = udphy->cfgs;
+
+ rk_udphy_grfreg_write(udphy->u2phygrf, &cfg->grfcfg.bvalid_phy_con, enable);
+ rk_udphy_grfreg_write(udphy->u2phygrf, &cfg->grfcfg.bvalid_grf_con, enable);
+}
+
+/*
+ * In usb/dp combo phy driver, here are 2 ways to mapping lanes.
+ *
+ * 1 Type-C Mapping table (DP_Alt_Mode V1.0b remove ABF pin mapping)
+ * ---------------------------------------------------------------------------
+ * Type-C Pin B11-B10 A2-A3 A11-A10 B2-B3
+ * PHY Pad ln0(tx/rx) ln1(tx) ln2(tx/rx) ln3(tx)
+ * C/E(Normal) dpln3 dpln2 dpln0 dpln1
+ * C/E(Flip ) dpln0 dpln1 dpln3 dpln2
+ * D/F(Normal) usbrx usbtx dpln0 dpln1
+ * D/F(Flip ) dpln0 dpln1 usbrx usbtx
+ * A(Normal ) dpln3 dpln1 dpln2 dpln0
+ * A(Flip ) dpln2 dpln0 dpln3 dpln1
+ * B(Normal ) usbrx usbtx dpln1 dpln0
+ * B(Flip ) dpln1 dpln0 usbrx usbtx
+ * ---------------------------------------------------------------------------
+ *
+ * 2 Mapping the lanes in dtsi
+ * if all 4 lane assignment for dp function, define rockchip,dp-lane-mux = <x x x x>;
+ * sample as follow:
+ * ---------------------------------------------------------------------------
+ * B11-B10 A2-A3 A11-A10 B2-B3
+ * rockchip,dp-lane-mux ln0(tx/rx) ln1(tx) ln2(tx/rx) ln3(tx)
+ * <0 1 2 3> dpln0 dpln1 dpln2 dpln3
+ * <2 3 0 1> dpln2 dpln3 dpln0 dpln1
+ * ---------------------------------------------------------------------------
+ * if 2 lane for dp function, 2 lane for usb function, define rockchip,dp-lane-mux = <x x>;
+ * sample as follow:
+ * ---------------------------------------------------------------------------
+ * B11-B10 A2-A3 A11-A10 B2-B3
+ * rockchip,dp-lane-mux ln0(tx/rx) ln1(tx) ln2(tx/rx) ln3(tx)
+ * <0 1> dpln0 dpln1 usbrx usbtx
+ * <2 3> usbrx usbtx dpln0 dpln1
+ * ---------------------------------------------------------------------------
+ */
+
+static void rk_udphy_dplane_select(struct rk_udphy *udphy)
+{
+ const struct rk_udphy_cfg *cfg = udphy->cfgs;
+ u32 value = 0;
+
+ switch (udphy->mode) {
+ case UDPHY_MODE_DP:
+ value |= 2 << udphy->dp_lane_sel[2] * 2;
+ value |= 3 << udphy->dp_lane_sel[3] * 2;
+ fallthrough;
+
+ case UDPHY_MODE_DP_USB:
+ value |= 0 << udphy->dp_lane_sel[0] * 2;
+ value |= 1 << udphy->dp_lane_sel[1] * 2;
+ break;
+
+ case UDPHY_MODE_USB:
+ break;
+
+ default:
+ break;
+ }
+
+ regmap_write(udphy->vogrf, cfg->vogrfcfg[udphy->id].dp_lane_reg,
+ ((DP_AUX_DIN_SEL | DP_AUX_DOUT_SEL | DP_LANE_SEL_ALL) << 16) |
+ FIELD_PREP(DP_AUX_DIN_SEL, udphy->dp_aux_din_sel) |
+ FIELD_PREP(DP_AUX_DOUT_SEL, udphy->dp_aux_dout_sel) | value);
+}
+
+static int rk_udphy_dplane_get(struct rk_udphy *udphy)
+{
+ int dp_lanes;
+
+ switch (udphy->mode) {
+ case UDPHY_MODE_DP:
+ dp_lanes = 4;
+ break;
+
+ case UDPHY_MODE_DP_USB:
+ dp_lanes = 2;
+ break;
+
+ case UDPHY_MODE_USB:
+ default:
+ dp_lanes = 0;
+ break;
+ }
+
+ return dp_lanes;
+}
+
+static void rk_udphy_dplane_enable(struct rk_udphy *udphy, int dp_lanes)
+{
+ u32 val = 0;
+ int i;
+
+ for (i = 0; i < dp_lanes; i++)
+ val |= BIT(udphy->dp_lane_sel[i]);
+
+ regmap_update_bits(udphy->pma_regmap, CMN_LANE_MUX_AND_EN_OFFSET, CMN_DP_LANE_EN_ALL,
+ FIELD_PREP(CMN_DP_LANE_EN_ALL, val));
+
+ if (!dp_lanes)
+ regmap_update_bits(udphy->pma_regmap, CMN_DP_RSTN_OFFSET,
+ CMN_DP_CMN_RSTN, FIELD_PREP(CMN_DP_CMN_RSTN, 0x0));
+}
+
+static void rk_udphy_dp_hpd_event_trigger(struct rk_udphy *udphy, bool hpd)
+{
+ const struct rk_udphy_cfg *cfg = udphy->cfgs;
+
+ udphy->dp_sink_hpd_sel = true;
+ udphy->dp_sink_hpd_cfg = hpd;
+
+ if (!udphy->dp_in_use)
+ return;
+
+ rk_udphy_grfreg_write(udphy->vogrf, &cfg->vogrfcfg[udphy->id].hpd_trigger, hpd);
+}
+
+static void rk_udphy_set_typec_default_mapping(struct rk_udphy *udphy)
+{
+ if (udphy->flip) {
+ udphy->dp_lane_sel[0] = 0;
+ udphy->dp_lane_sel[1] = 1;
+ udphy->dp_lane_sel[2] = 3;
+ udphy->dp_lane_sel[3] = 2;
+ udphy->lane_mux_sel[0] = PHY_LANE_MUX_DP;
+ udphy->lane_mux_sel[1] = PHY_LANE_MUX_DP;
+ udphy->lane_mux_sel[2] = PHY_LANE_MUX_USB;
+ udphy->lane_mux_sel[3] = PHY_LANE_MUX_USB;
+ udphy->dp_aux_dout_sel = PHY_AUX_DP_DATA_POL_INVERT;
+ udphy->dp_aux_din_sel = PHY_AUX_DP_DATA_POL_INVERT;
+ gpiod_set_value_cansleep(udphy->sbu1_dc_gpio, 1);
+ gpiod_set_value_cansleep(udphy->sbu2_dc_gpio, 0);
+ } else {
+ udphy->dp_lane_sel[0] = 2;
+ udphy->dp_lane_sel[1] = 3;
+ udphy->dp_lane_sel[2] = 1;
+ udphy->dp_lane_sel[3] = 0;
+ udphy->lane_mux_sel[0] = PHY_LANE_MUX_USB;
+ udphy->lane_mux_sel[1] = PHY_LANE_MUX_USB;
+ udphy->lane_mux_sel[2] = PHY_LANE_MUX_DP;
+ udphy->lane_mux_sel[3] = PHY_LANE_MUX_DP;
+ udphy->dp_aux_dout_sel = PHY_AUX_DP_DATA_POL_NORMAL;
+ udphy->dp_aux_din_sel = PHY_AUX_DP_DATA_POL_NORMAL;
+ gpiod_set_value_cansleep(udphy->sbu1_dc_gpio, 0);
+ gpiod_set_value_cansleep(udphy->sbu2_dc_gpio, 1);
+ }
+
+ udphy->mode = UDPHY_MODE_DP_USB;
+}
+
+static int rk_udphy_orien_sw_set(struct typec_switch_dev *sw,
+ enum typec_orientation orien)
+{
+ struct rk_udphy *udphy = typec_switch_get_drvdata(sw);
+
+ mutex_lock(&udphy->mutex);
+
+ if (orien == TYPEC_ORIENTATION_NONE) {
+ gpiod_set_value_cansleep(udphy->sbu1_dc_gpio, 0);
+ gpiod_set_value_cansleep(udphy->sbu2_dc_gpio, 0);
+ /* unattached */
+ rk_udphy_usb_bvalid_enable(udphy, false);
+ goto unlock_ret;
+ }
+
+ udphy->flip = orien == TYPEC_ORIENTATION_REVERSE;
+ rk_udphy_set_typec_default_mapping(udphy);
+ rk_udphy_usb_bvalid_enable(udphy, true);
+
+unlock_ret:
+ mutex_unlock(&udphy->mutex);
+ return 0;
+}
+
+static void rk_udphy_orien_switch_unregister(void *data)
+{
+ struct rk_udphy *udphy = data;
+
+ typec_switch_unregister(udphy->sw);
+}
+
+static int rk_udphy_setup_orien_switch(struct rk_udphy *udphy)
+{
+ struct typec_switch_desc sw_desc = { };
+
+ sw_desc.drvdata = udphy;
+ sw_desc.fwnode = dev_fwnode(udphy->dev);
+ sw_desc.set = rk_udphy_orien_sw_set;
+
+ udphy->sw = typec_switch_register(udphy->dev, &sw_desc);
+ if (IS_ERR(udphy->sw)) {
+ dev_err(udphy->dev, "Error register typec orientation switch: %ld\n",
+ PTR_ERR(udphy->sw));
+ return PTR_ERR(udphy->sw);
+ }
+
+ return devm_add_action_or_reset(udphy->dev,
+ rk_udphy_orien_switch_unregister, udphy);
+}
+
+static int rk_udphy_refclk_set(struct rk_udphy *udphy)
+{
+ unsigned long rate;
+ int ret;
+
+ /* configure phy reference clock */
+ rate = clk_get_rate(udphy->refclk);
+ dev_dbg(udphy->dev, "refclk freq %ld\n", rate);
+
+ switch (rate) {
+ case 24000000:
+ ret = regmap_multi_reg_write(udphy->pma_regmap, rk_udphy_24m_refclk_cfg,
+ ARRAY_SIZE(rk_udphy_24m_refclk_cfg));
+ if (ret)
+ return ret;
+ break;
+
+ case 26000000:
+ /* register default is 26MHz */
+ ret = regmap_multi_reg_write(udphy->pma_regmap, rk_udphy_26m_refclk_cfg,
+ ARRAY_SIZE(rk_udphy_26m_refclk_cfg));
+ if (ret)
+ return ret;
+ break;
+
+ default:
+ dev_err(udphy->dev, "unsupported refclk freq %ld\n", rate);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int rk_udphy_status_check(struct rk_udphy *udphy)
+{
+ unsigned int val;
+ int ret;
+
+ /* LCPLL check */
+ if (udphy->mode & UDPHY_MODE_USB) {
+ ret = regmap_read_poll_timeout(udphy->pma_regmap, CMN_ANA_LCPLL_DONE_OFFSET,
+ val, (val & CMN_ANA_LCPLL_AFC_DONE) &&
+ (val & CMN_ANA_LCPLL_LOCK_DONE), 200, 100000);
+ if (ret) {
+ dev_err(udphy->dev, "cmn ana lcpll lock timeout\n");
+ /*
+ * If earlier software (U-Boot) enabled USB once already
+ * the PLL may have problems locking on the first try.
+ * It will be successful on the second try, so for the
+ * time being a -EPROBE_DEFER will solve the issue.
+ *
+ * This requires further investigation to understand the
+ * root cause, especially considering that the driver is
+ * asserting all reset lines at probe time.
+ */
+ return -EPROBE_DEFER;
+ }
+
+ if (!udphy->flip) {
+ ret = regmap_read_poll_timeout(udphy->pma_regmap,
+ TRSV_LN0_MON_RX_CDR_DONE_OFFSET, val,
+ val & TRSV_LN0_MON_RX_CDR_LOCK_DONE,
+ 200, 100000);
+ if (ret)
+ dev_err(udphy->dev, "trsv ln0 mon rx cdr lock timeout\n");
+ } else {
+ ret = regmap_read_poll_timeout(udphy->pma_regmap,
+ TRSV_LN2_MON_RX_CDR_DONE_OFFSET, val,
+ val & TRSV_LN2_MON_RX_CDR_LOCK_DONE,
+ 200, 100000);
+ if (ret)
+ dev_err(udphy->dev, "trsv ln2 mon rx cdr lock timeout\n");
+ }
+ }
+
+ return 0;
+}
+
+static int rk_udphy_init(struct rk_udphy *udphy)
+{
+ const struct rk_udphy_cfg *cfg = udphy->cfgs;
+ int ret;
+
+ rk_udphy_reset_assert_all(udphy);
+ usleep_range(10000, 11000);
+
+ /* enable rx lfps for usb */
+ if (udphy->mode & UDPHY_MODE_USB)
+ rk_udphy_grfreg_write(udphy->udphygrf, &cfg->grfcfg.rx_lfps, true);
+
+ /* Step 1: power on pma and deassert apb rstn */
+ rk_udphy_grfreg_write(udphy->udphygrf, &cfg->grfcfg.low_pwrn, true);
+
+ rk_udphy_reset_deassert(udphy, "pma_apb");
+ rk_udphy_reset_deassert(udphy, "pcs_apb");
+
+ /* Step 2: set init sequence and phy refclk */
+ ret = regmap_multi_reg_write(udphy->pma_regmap, rk_udphy_init_sequence,
+ ARRAY_SIZE(rk_udphy_init_sequence));
+ if (ret) {
+ dev_err(udphy->dev, "init sequence set error %d\n", ret);
+ goto assert_resets;
+ }
+
+ ret = rk_udphy_refclk_set(udphy);
+ if (ret) {
+ dev_err(udphy->dev, "refclk set error %d\n", ret);
+ goto assert_resets;
+ }
+
+ /* Step 3: configure lane mux */
+ regmap_update_bits(udphy->pma_regmap, CMN_LANE_MUX_AND_EN_OFFSET,
+ CMN_DP_LANE_MUX_ALL | CMN_DP_LANE_EN_ALL,
+ FIELD_PREP(CMN_DP_LANE_MUX_N(3), udphy->lane_mux_sel[3]) |
+ FIELD_PREP(CMN_DP_LANE_MUX_N(2), udphy->lane_mux_sel[2]) |
+ FIELD_PREP(CMN_DP_LANE_MUX_N(1), udphy->lane_mux_sel[1]) |
+ FIELD_PREP(CMN_DP_LANE_MUX_N(0), udphy->lane_mux_sel[0]) |
+ FIELD_PREP(CMN_DP_LANE_EN_ALL, 0));
+
+ /* Step 4: deassert init rstn and wait for 200ns from datasheet */
+ if (udphy->mode & UDPHY_MODE_USB)
+ rk_udphy_reset_deassert(udphy, "init");
+
+ if (udphy->mode & UDPHY_MODE_DP) {
+ regmap_update_bits(udphy->pma_regmap, CMN_DP_RSTN_OFFSET,
+ CMN_DP_INIT_RSTN,
+ FIELD_PREP(CMN_DP_INIT_RSTN, 0x1));
+ }
+
+ udelay(1);
+
+ /* Step 5: deassert cmn/lane rstn */
+ if (udphy->mode & UDPHY_MODE_USB) {
+ rk_udphy_reset_deassert(udphy, "cmn");
+ rk_udphy_reset_deassert(udphy, "lane");
+ }
+
+ /* Step 6: wait for lock done of pll */
+ ret = rk_udphy_status_check(udphy);
+ if (ret)
+ goto assert_resets;
+
+ return 0;
+
+assert_resets:
+ rk_udphy_reset_assert_all(udphy);
+ return ret;
+}
+
+static int rk_udphy_setup(struct rk_udphy *udphy)
+{
+ int ret;
+
+ ret = clk_bulk_prepare_enable(udphy->num_clks, udphy->clks);
+ if (ret) {
+ dev_err(udphy->dev, "failed to enable clk\n");
+ return ret;
+ }
+
+ ret = rk_udphy_init(udphy);
+ if (ret) {
+ dev_err(udphy->dev, "failed to init combophy\n");
+ clk_bulk_disable_unprepare(udphy->num_clks, udphy->clks);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void rk_udphy_disable(struct rk_udphy *udphy)
+{
+ clk_bulk_disable_unprepare(udphy->num_clks, udphy->clks);
+ rk_udphy_reset_assert_all(udphy);
+}
+
+static int rk_udphy_parse_lane_mux_data(struct rk_udphy *udphy)
+{
+ int ret, i, num_lanes;
+
+ num_lanes = device_property_count_u32(udphy->dev, "rockchip,dp-lane-mux");
+ if (num_lanes < 0) {
+ dev_dbg(udphy->dev, "no dp-lane-mux, following dp alt mode\n");
+ udphy->mode = UDPHY_MODE_USB;
+ return 0;
+ }
+
+ if (num_lanes != 2 && num_lanes != 4)
+ return dev_err_probe(udphy->dev, -EINVAL,
+ "invalid number of lane mux\n");
+
+ ret = device_property_read_u32_array(udphy->dev, "rockchip,dp-lane-mux",
+ udphy->dp_lane_sel, num_lanes);
+ if (ret)
+ return dev_err_probe(udphy->dev, ret, "get dp lane mux failed\n");
+
+ for (i = 0; i < num_lanes; i++) {
+ int j;
+
+ if (udphy->dp_lane_sel[i] > 3)
+ return dev_err_probe(udphy->dev, -EINVAL,
+ "lane mux between 0 and 3, exceeding the range\n");
+
+ udphy->lane_mux_sel[udphy->dp_lane_sel[i]] = PHY_LANE_MUX_DP;
+
+ for (j = i + 1; j < num_lanes; j++) {
+ if (udphy->dp_lane_sel[i] == udphy->dp_lane_sel[j])
+ return dev_err_probe(udphy->dev, -EINVAL,
+ "set repeat lane mux value\n");
+ }
+ }
+
+ udphy->mode = UDPHY_MODE_DP;
+ if (num_lanes == 2) {
+ udphy->mode |= UDPHY_MODE_USB;
+ udphy->flip = (udphy->lane_mux_sel[0] == PHY_LANE_MUX_DP);
+ }
+
+ return 0;
+}
+
+static int rk_udphy_get_initial_status(struct rk_udphy *udphy)
+{
+ int ret;
+ u32 value;
+
+ ret = clk_bulk_prepare_enable(udphy->num_clks, udphy->clks);
+ if (ret) {
+ dev_err(udphy->dev, "failed to enable clk\n");
+ return ret;
+ }
+
+ rk_udphy_reset_deassert_all(udphy);
+
+ regmap_read(udphy->pma_regmap, CMN_LANE_MUX_AND_EN_OFFSET, &value);
+ if (FIELD_GET(CMN_DP_LANE_MUX_ALL, value) && FIELD_GET(CMN_DP_LANE_EN_ALL, value))
+ udphy->status = UDPHY_MODE_DP;
+ else
+ rk_udphy_disable(udphy);
+
+ return 0;
+}
+
+static int rk_udphy_parse_dt(struct rk_udphy *udphy)
+{
+ struct device *dev = udphy->dev;
+ struct device_node *np = dev_of_node(dev);
+ enum usb_device_speed maximum_speed;
+ int ret;
+
+ udphy->u2phygrf = syscon_regmap_lookup_by_phandle(np, "rockchip,u2phy-grf");
+ if (IS_ERR(udphy->u2phygrf))
+ return dev_err_probe(dev, PTR_ERR(udphy->u2phygrf), "failed to get u2phy-grf\n");
+
+ udphy->udphygrf = syscon_regmap_lookup_by_phandle(np, "rockchip,usbdpphy-grf");
+ if (IS_ERR(udphy->udphygrf))
+ return dev_err_probe(dev, PTR_ERR(udphy->udphygrf), "failed to get usbdpphy-grf\n");
+
+ udphy->usbgrf = syscon_regmap_lookup_by_phandle(np, "rockchip,usb-grf");
+ if (IS_ERR(udphy->usbgrf))
+ return dev_err_probe(dev, PTR_ERR(udphy->usbgrf), "failed to get usb-grf\n");
+
+ udphy->vogrf = syscon_regmap_lookup_by_phandle(np, "rockchip,vo-grf");
+ if (IS_ERR(udphy->vogrf))
+ return dev_err_probe(dev, PTR_ERR(udphy->vogrf), "failed to get vo-grf\n");
+
+ ret = rk_udphy_parse_lane_mux_data(udphy);
+ if (ret)
+ return ret;
+
+ udphy->sbu1_dc_gpio = devm_gpiod_get_optional(dev, "sbu1-dc", GPIOD_OUT_LOW);
+ if (IS_ERR(udphy->sbu1_dc_gpio))
+ return PTR_ERR(udphy->sbu1_dc_gpio);
+
+ udphy->sbu2_dc_gpio = devm_gpiod_get_optional(dev, "sbu2-dc", GPIOD_OUT_LOW);
+ if (IS_ERR(udphy->sbu2_dc_gpio))
+ return PTR_ERR(udphy->sbu2_dc_gpio);
+
+ if (device_property_present(dev, "maximum-speed")) {
+ maximum_speed = usb_get_maximum_speed(dev);
+ udphy->hs = maximum_speed <= USB_SPEED_HIGH;
+ }
+
+ ret = rk_udphy_clk_init(udphy, dev);
+ if (ret)
+ return ret;
+
+ return rk_udphy_reset_init(udphy, dev);
+}
+
+static int rk_udphy_power_on(struct rk_udphy *udphy, u8 mode)
+{
+ int ret;
+
+ if (!(udphy->mode & mode)) {
+ dev_info(udphy->dev, "mode 0x%02x is not support\n", mode);
+ return 0;
+ }
+
+ if (udphy->status == UDPHY_MODE_NONE) {
+ udphy->mode_change = false;
+ ret = rk_udphy_setup(udphy);
+ if (ret)
+ return ret;
+
+ if (udphy->mode & UDPHY_MODE_USB)
+ rk_udphy_u3_port_disable(udphy, false);
+ } else if (udphy->mode_change) {
+ udphy->mode_change = false;
+ udphy->status = UDPHY_MODE_NONE;
+ if (udphy->mode == UDPHY_MODE_DP)
+ rk_udphy_u3_port_disable(udphy, true);
+
+ rk_udphy_disable(udphy);
+ ret = rk_udphy_setup(udphy);
+ if (ret)
+ return ret;
+ }
+
+ udphy->status |= mode;
+
+ return 0;
+}
+
+static void rk_udphy_power_off(struct rk_udphy *udphy, u8 mode)
+{
+ if (!(udphy->mode & mode)) {
+ dev_info(udphy->dev, "mode 0x%02x is not support\n", mode);
+ return;
+ }
+
+ if (!udphy->status)
+ return;
+
+ udphy->status &= ~mode;
+
+ if (udphy->status == UDPHY_MODE_NONE)
+ rk_udphy_disable(udphy);
+}
+
+static int rk_udphy_dp_phy_init(struct phy *phy)
+{
+ struct rk_udphy *udphy = phy_get_drvdata(phy);
+
+ mutex_lock(&udphy->mutex);
+
+ udphy->dp_in_use = true;
+
+ mutex_unlock(&udphy->mutex);
+
+ return 0;
+}
+
+static int rk_udphy_dp_phy_exit(struct phy *phy)
+{
+ struct rk_udphy *udphy = phy_get_drvdata(phy);
+
+ mutex_lock(&udphy->mutex);
+ udphy->dp_in_use = false;
+ mutex_unlock(&udphy->mutex);
+ return 0;
+}
+
+static int rk_udphy_dp_phy_power_on(struct phy *phy)
+{
+ struct rk_udphy *udphy = phy_get_drvdata(phy);
+ int ret, dp_lanes;
+
+ mutex_lock(&udphy->mutex);
+
+ dp_lanes = rk_udphy_dplane_get(udphy);
+ phy_set_bus_width(phy, dp_lanes);
+
+ ret = rk_udphy_power_on(udphy, UDPHY_MODE_DP);
+ if (ret)
+ goto unlock;
+
+ rk_udphy_dplane_enable(udphy, dp_lanes);
+
+ rk_udphy_dplane_select(udphy);
+
+unlock:
+ mutex_unlock(&udphy->mutex);
+ /*
+ * If data send by aux channel too fast after phy power on,
+ * the aux may be not ready which will cause aux error. Adding
+ * delay to avoid this issue.
+ */
+ usleep_range(10000, 11000);
+ return ret;
+}
+
+static int rk_udphy_dp_phy_power_off(struct phy *phy)
+{
+ struct rk_udphy *udphy = phy_get_drvdata(phy);
+
+ mutex_lock(&udphy->mutex);
+ rk_udphy_dplane_enable(udphy, 0);
+ rk_udphy_power_off(udphy, UDPHY_MODE_DP);
+ mutex_unlock(&udphy->mutex);
+
+ return 0;
+}
+
+/*
+ * Verify link rate
+ */
+static int rk_udphy_dp_phy_verify_link_rate(struct rk_udphy *udphy,
+ struct phy_configure_opts_dp *dp)
+{
+ switch (dp->link_rate) {
+ case 1620:
+ case 2700:
+ case 5400:
+ case 8100:
+ udphy->link_rate = dp->link_rate;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int rk_udphy_dp_phy_verify_lanes(struct rk_udphy *udphy,
+ struct phy_configure_opts_dp *dp)
+{
+ switch (dp->lanes) {
+ case 1:
+ case 2:
+ case 4:
+ /* valid lane count. */
+ udphy->lanes = dp->lanes;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * If changing voltages is required, check swing and pre-emphasis
+ * levels, per-lane.
+ */
+static int rk_udphy_dp_phy_verify_voltages(struct rk_udphy *udphy,
+ struct phy_configure_opts_dp *dp)
+{
+ int i;
+
+ /* Lane count verified previously. */
+ for (i = 0; i < udphy->lanes; i++) {
+ if (dp->voltage[i] > 3 || dp->pre[i] > 3)
+ return -EINVAL;
+
+ /*
+ * Sum of voltage swing and pre-emphasis levels cannot
+ * exceed 3.
+ */
+ if (dp->voltage[i] + dp->pre[i] > 3)
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void rk_udphy_dp_set_voltage(struct rk_udphy *udphy, u8 bw,
+ u32 voltage, u32 pre, u32 lane)
+{
+ const struct rk_udphy_cfg *cfg = udphy->cfgs;
+ const struct rk_udphy_dp_tx_drv_ctrl (*dp_ctrl)[4];
+ u32 offset = 0x800 * lane;
+ u32 val;
+
+ if (udphy->mux)
+ dp_ctrl = cfg->dp_tx_ctrl_cfg_typec[bw];
+ else
+ dp_ctrl = cfg->dp_tx_ctrl_cfg[bw];
+
+ val = dp_ctrl[voltage][pre].trsv_reg0204;
+ regmap_write(udphy->pma_regmap, 0x0810 + offset, val);
+
+ val = dp_ctrl[voltage][pre].trsv_reg0205;
+ regmap_write(udphy->pma_regmap, 0x0814 + offset, val);
+
+ val = dp_ctrl[voltage][pre].trsv_reg0206;
+ regmap_write(udphy->pma_regmap, 0x0818 + offset, val);
+
+ val = dp_ctrl[voltage][pre].trsv_reg0207;
+ regmap_write(udphy->pma_regmap, 0x081c + offset, val);
+}
+
+static int rk_udphy_dp_phy_configure(struct phy *phy,
+ union phy_configure_opts *opts)
+{
+ struct rk_udphy *udphy = phy_get_drvdata(phy);
+ struct phy_configure_opts_dp *dp = &opts->dp;
+ u32 i, val, lane;
+ int ret;
+
+ if (dp->set_rate) {
+ ret = rk_udphy_dp_phy_verify_link_rate(udphy, dp);
+ if (ret)
+ return ret;
+ }
+
+ if (dp->set_lanes) {
+ ret = rk_udphy_dp_phy_verify_lanes(udphy, dp);
+ if (ret)
+ return ret;
+ }
+
+ if (dp->set_voltages) {
+ ret = rk_udphy_dp_phy_verify_voltages(udphy, dp);
+ if (ret)
+ return ret;
+ }
+
+ if (dp->set_rate) {
+ regmap_update_bits(udphy->pma_regmap, CMN_DP_RSTN_OFFSET,
+ CMN_DP_CMN_RSTN, FIELD_PREP(CMN_DP_CMN_RSTN, 0x0));
+
+ switch (dp->link_rate) {
+ case 1620:
+ udphy->bw = DP_BW_RBR;
+ break;
+
+ case 2700:
+ udphy->bw = DP_BW_HBR;
+ break;
+
+ case 5400:
+ udphy->bw = DP_BW_HBR2;
+ break;
+
+ case 8100:
+ udphy->bw = DP_BW_HBR3;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ regmap_update_bits(udphy->pma_regmap, CMN_DP_LINK_OFFSET, CMN_DP_TX_LINK_BW,
+ FIELD_PREP(CMN_DP_TX_LINK_BW, udphy->bw));
+ regmap_update_bits(udphy->pma_regmap, CMN_SSC_EN_OFFSET, CMN_ROPLL_SSC_EN,
+ FIELD_PREP(CMN_ROPLL_SSC_EN, dp->ssc));
+ regmap_update_bits(udphy->pma_regmap, CMN_DP_RSTN_OFFSET, CMN_DP_CMN_RSTN,
+ FIELD_PREP(CMN_DP_CMN_RSTN, 0x1));
+
+ ret = regmap_read_poll_timeout(udphy->pma_regmap, CMN_ANA_ROPLL_DONE_OFFSET, val,
+ FIELD_GET(CMN_ANA_ROPLL_LOCK_DONE, val) &&
+ FIELD_GET(CMN_ANA_ROPLL_AFC_DONE, val),
+ 0, 1000);
+ if (ret) {
+ dev_err(udphy->dev, "ROPLL is not lock, set_rate failed\n");
+ return ret;
+ }
+ }
+
+ if (dp->set_voltages) {
+ for (i = 0; i < udphy->lanes; i++) {
+ lane = udphy->dp_lane_sel[i];
+ switch (udphy->link_rate) {
+ case 1620:
+ case 2700:
+ regmap_update_bits(udphy->pma_regmap,
+ TRSV_ANA_TX_CLK_OFFSET_N(lane),
+ LN_ANA_TX_SER_TXCLK_INV,
+ FIELD_PREP(LN_ANA_TX_SER_TXCLK_INV,
+ udphy->lane_mux_sel[lane]));
+ break;
+
+ case 5400:
+ case 8100:
+ regmap_update_bits(udphy->pma_regmap,
+ TRSV_ANA_TX_CLK_OFFSET_N(lane),
+ LN_ANA_TX_SER_TXCLK_INV,
+ FIELD_PREP(LN_ANA_TX_SER_TXCLK_INV, 0x0));
+ break;
+ }
+
+ rk_udphy_dp_set_voltage(udphy, udphy->bw, dp->voltage[i],
+ dp->pre[i], lane);
+ }
+ }
+
+ return 0;
+}
+
+static const struct phy_ops rk_udphy_dp_phy_ops = {
+ .init = rk_udphy_dp_phy_init,
+ .exit = rk_udphy_dp_phy_exit,
+ .power_on = rk_udphy_dp_phy_power_on,
+ .power_off = rk_udphy_dp_phy_power_off,
+ .configure = rk_udphy_dp_phy_configure,
+ .owner = THIS_MODULE,
+};
+
+static int rk_udphy_usb3_phy_init(struct phy *phy)
+{
+ struct rk_udphy *udphy = phy_get_drvdata(phy);
+ int ret = 0;
+
+ mutex_lock(&udphy->mutex);
+ /* DP only or high-speed, disable U3 port */
+ if (!(udphy->mode & UDPHY_MODE_USB) || udphy->hs) {
+ rk_udphy_u3_port_disable(udphy, true);
+ goto unlock;
+ }
+
+ ret = rk_udphy_power_on(udphy, UDPHY_MODE_USB);
+
+unlock:
+ mutex_unlock(&udphy->mutex);
+ return ret;
+}
+
+static int rk_udphy_usb3_phy_exit(struct phy *phy)
+{
+ struct rk_udphy *udphy = phy_get_drvdata(phy);
+
+ mutex_lock(&udphy->mutex);
+ /* DP only or high-speed */
+ if (!(udphy->mode & UDPHY_MODE_USB) || udphy->hs)
+ goto unlock;
+
+ rk_udphy_power_off(udphy, UDPHY_MODE_USB);
+
+unlock:
+ mutex_unlock(&udphy->mutex);
+ return 0;
+}
+
+static const struct phy_ops rk_udphy_usb3_phy_ops = {
+ .init = rk_udphy_usb3_phy_init,
+ .exit = rk_udphy_usb3_phy_exit,
+ .owner = THIS_MODULE,
+};
+
+static int rk_udphy_typec_mux_set(struct typec_mux_dev *mux,
+ struct typec_mux_state *state)
+{
+ struct rk_udphy *udphy = typec_mux_get_drvdata(mux);
+ u8 mode;
+
+ mutex_lock(&udphy->mutex);
+
+ switch (state->mode) {
+ case TYPEC_DP_STATE_C:
+ case TYPEC_DP_STATE_E:
+ udphy->lane_mux_sel[0] = PHY_LANE_MUX_DP;
+ udphy->lane_mux_sel[1] = PHY_LANE_MUX_DP;
+ udphy->lane_mux_sel[2] = PHY_LANE_MUX_DP;
+ udphy->lane_mux_sel[3] = PHY_LANE_MUX_DP;
+ mode = UDPHY_MODE_DP;
+ break;
+
+ case TYPEC_DP_STATE_D:
+ default:
+ if (udphy->flip) {
+ udphy->lane_mux_sel[0] = PHY_LANE_MUX_DP;
+ udphy->lane_mux_sel[1] = PHY_LANE_MUX_DP;
+ udphy->lane_mux_sel[2] = PHY_LANE_MUX_USB;
+ udphy->lane_mux_sel[3] = PHY_LANE_MUX_USB;
+ } else {
+ udphy->lane_mux_sel[0] = PHY_LANE_MUX_USB;
+ udphy->lane_mux_sel[1] = PHY_LANE_MUX_USB;
+ udphy->lane_mux_sel[2] = PHY_LANE_MUX_DP;
+ udphy->lane_mux_sel[3] = PHY_LANE_MUX_DP;
+ }
+ mode = UDPHY_MODE_DP_USB;
+ break;
+ }
+
+ if (state->alt && state->alt->svid == USB_TYPEC_DP_SID) {
+ struct typec_displayport_data *data = state->data;
+
+ if (!data) {
+ rk_udphy_dp_hpd_event_trigger(udphy, false);
+ } else if (data->status & DP_STATUS_IRQ_HPD) {
+ rk_udphy_dp_hpd_event_trigger(udphy, false);
+ usleep_range(750, 800);
+ rk_udphy_dp_hpd_event_trigger(udphy, true);
+ } else if (data->status & DP_STATUS_HPD_STATE) {
+ if (udphy->mode != mode) {
+ udphy->mode = mode;
+ udphy->mode_change = true;
+ }
+ rk_udphy_dp_hpd_event_trigger(udphy, true);
+ } else {
+ rk_udphy_dp_hpd_event_trigger(udphy, false);
+ }
+ }
+
+ mutex_unlock(&udphy->mutex);
+ return 0;
+}
+
+static void rk_udphy_typec_mux_unregister(void *data)
+{
+ struct rk_udphy *udphy = data;
+
+ typec_mux_unregister(udphy->mux);
+}
+
+static int rk_udphy_setup_typec_mux(struct rk_udphy *udphy)
+{
+ struct typec_mux_desc mux_desc = {};
+
+ mux_desc.drvdata = udphy;
+ mux_desc.fwnode = dev_fwnode(udphy->dev);
+ mux_desc.set = rk_udphy_typec_mux_set;
+
+ udphy->mux = typec_mux_register(udphy->dev, &mux_desc);
+ if (IS_ERR(udphy->mux)) {
+ dev_err(udphy->dev, "Error register typec mux: %ld\n",
+ PTR_ERR(udphy->mux));
+ return PTR_ERR(udphy->mux);
+ }
+
+ return devm_add_action_or_reset(udphy->dev, rk_udphy_typec_mux_unregister,
+ udphy);
+}
+
+static const struct regmap_config rk_udphy_pma_regmap_cfg = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0x20dc,
+};
+
+static struct phy *rk_udphy_phy_xlate(struct device *dev, const struct of_phandle_args *args)
+{
+ struct rk_udphy *udphy = dev_get_drvdata(dev);
+
+ if (args->args_count == 0)
+ return ERR_PTR(-EINVAL);
+
+ switch (args->args[0]) {
+ case PHY_TYPE_USB3:
+ return udphy->phy_u3;
+ case PHY_TYPE_DP:
+ return udphy->phy_dp;
+ }
+
+ return ERR_PTR(-EINVAL);
+}
+
+static int rk_udphy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct phy_provider *phy_provider;
+ struct resource *res;
+ struct rk_udphy *udphy;
+ void __iomem *base;
+ int id, ret;
+
+ udphy = devm_kzalloc(dev, sizeof(*udphy), GFP_KERNEL);
+ if (!udphy)
+ return -ENOMEM;
+
+ udphy->cfgs = device_get_match_data(dev);
+ if (!udphy->cfgs)
+ return dev_err_probe(dev, -EINVAL, "missing match data\n");
+
+ base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ /* find the phy-id from the io address */
+ udphy->id = -ENODEV;
+ for (id = 0; id < udphy->cfgs->num_phys; id++) {
+ if (res->start == udphy->cfgs->phy_ids[id]) {
+ udphy->id = id;
+ break;
+ }
+ }
+
+ if (udphy->id < 0)
+ return dev_err_probe(dev, -ENODEV, "no matching device found\n");
+
+ udphy->pma_regmap = devm_regmap_init_mmio(dev, base + UDPHY_PMA,
+ &rk_udphy_pma_regmap_cfg);
+ if (IS_ERR(udphy->pma_regmap))
+ return PTR_ERR(udphy->pma_regmap);
+
+ udphy->dev = dev;
+ ret = rk_udphy_parse_dt(udphy);
+ if (ret)
+ return ret;
+
+ ret = rk_udphy_get_initial_status(udphy);
+ if (ret)
+ return ret;
+
+ mutex_init(&udphy->mutex);
+ platform_set_drvdata(pdev, udphy);
+
+ if (device_property_present(dev, "orientation-switch")) {
+ ret = rk_udphy_setup_orien_switch(udphy);
+ if (ret)
+ return ret;
+ }
+
+ if (device_property_present(dev, "mode-switch")) {
+ ret = rk_udphy_setup_typec_mux(udphy);
+ if (ret)
+ return ret;
+ }
+
+ udphy->phy_u3 = devm_phy_create(dev, dev->of_node, &rk_udphy_usb3_phy_ops);
+ if (IS_ERR(udphy->phy_u3)) {
+ ret = PTR_ERR(udphy->phy_u3);
+ return dev_err_probe(dev, ret, "failed to create USB3 phy\n");
+ }
+ phy_set_drvdata(udphy->phy_u3, udphy);
+
+ udphy->phy_dp = devm_phy_create(dev, dev->of_node, &rk_udphy_dp_phy_ops);
+ if (IS_ERR(udphy->phy_dp)) {
+ ret = PTR_ERR(udphy->phy_dp);
+ return dev_err_probe(dev, ret, "failed to create DP phy\n");
+ }
+ phy_set_bus_width(udphy->phy_dp, rk_udphy_dplane_get(udphy));
+ udphy->phy_dp->attrs.max_link_rate = 8100;
+ phy_set_drvdata(udphy->phy_dp, udphy);
+
+ phy_provider = devm_of_phy_provider_register(dev, rk_udphy_phy_xlate);
+ if (IS_ERR(phy_provider)) {
+ ret = PTR_ERR(phy_provider);
+ return dev_err_probe(dev, ret, "failed to register phy provider\n");
+ }
+
+ return 0;
+}
+
+static int __maybe_unused rk_udphy_resume(struct device *dev)
+{
+ struct rk_udphy *udphy = dev_get_drvdata(dev);
+
+ if (udphy->dp_sink_hpd_sel)
+ rk_udphy_dp_hpd_event_trigger(udphy, udphy->dp_sink_hpd_cfg);
+
+ return 0;
+}
+
+static const struct dev_pm_ops rk_udphy_pm_ops = {
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(NULL, rk_udphy_resume)
+};
+
+static const char * const rk_udphy_rst_list[] = {
+ "init", "cmn", "lane", "pcs_apb", "pma_apb"
+};
+
+static const struct rk_udphy_cfg rk3576_udphy_cfgs = {
+ .num_phys = 1,
+ .phy_ids = { 0x2b010000 },
+ .num_rsts = ARRAY_SIZE(rk_udphy_rst_list),
+ .rst_list = rk_udphy_rst_list,
+ .grfcfg = {
+ /* u2phy-grf */
+ .bvalid_phy_con = RK_UDPHY_GEN_GRF_REG(0x0010, 1, 0, 0x2, 0x3),
+ .bvalid_grf_con = RK_UDPHY_GEN_GRF_REG(0x0000, 15, 14, 0x1, 0x3),
+
+ /* usb-grf */
+ .usb3otg0_cfg = RK_UDPHY_GEN_GRF_REG(0x0030, 15, 0, 0x1100, 0x0188),
+
+ /* usbdpphy-grf */
+ .low_pwrn = RK_UDPHY_GEN_GRF_REG(0x0004, 13, 13, 0, 1),
+ .rx_lfps = RK_UDPHY_GEN_GRF_REG(0x0004, 14, 14, 0, 1),
+ },
+ .vogrfcfg = {
+ {
+ .hpd_trigger = RK_UDPHY_GEN_GRF_REG(0x0000, 11, 10, 1, 3),
+ .dp_lane_reg = 0x0000,
+ },
+ },
+ .dp_tx_ctrl_cfg = {
+ rk3588_dp_tx_drv_ctrl_rbr_hbr_typec,
+ rk3588_dp_tx_drv_ctrl_rbr_hbr_typec,
+ rk3588_dp_tx_drv_ctrl_hbr2,
+ rk3588_dp_tx_drv_ctrl_hbr3,
+ },
+ .dp_tx_ctrl_cfg_typec = {
+ rk3588_dp_tx_drv_ctrl_rbr_hbr_typec,
+ rk3588_dp_tx_drv_ctrl_rbr_hbr_typec,
+ rk3588_dp_tx_drv_ctrl_hbr2,
+ rk3588_dp_tx_drv_ctrl_hbr3,
+ },
+};
+
+static const struct rk_udphy_cfg rk3588_udphy_cfgs = {
+ .num_phys = 2,
+ .phy_ids = {
+ 0xfed80000,
+ 0xfed90000,
+ },
+ .num_rsts = ARRAY_SIZE(rk_udphy_rst_list),
+ .rst_list = rk_udphy_rst_list,
+ .grfcfg = {
+ /* u2phy-grf */
+ .bvalid_phy_con = RK_UDPHY_GEN_GRF_REG(0x0008, 1, 0, 0x2, 0x3),
+ .bvalid_grf_con = RK_UDPHY_GEN_GRF_REG(0x0010, 3, 2, 0x2, 0x3),
+
+ /* usb-grf */
+ .usb3otg0_cfg = RK_UDPHY_GEN_GRF_REG(0x001c, 15, 0, 0x1100, 0x0188),
+ .usb3otg1_cfg = RK_UDPHY_GEN_GRF_REG(0x0034, 15, 0, 0x1100, 0x0188),
+
+ /* usbdpphy-grf */
+ .low_pwrn = RK_UDPHY_GEN_GRF_REG(0x0004, 13, 13, 0, 1),
+ .rx_lfps = RK_UDPHY_GEN_GRF_REG(0x0004, 14, 14, 0, 1),
+ },
+ .vogrfcfg = {
+ {
+ .hpd_trigger = RK_UDPHY_GEN_GRF_REG(0x0000, 11, 10, 1, 3),
+ .dp_lane_reg = 0x0000,
+ },
+ {
+ .hpd_trigger = RK_UDPHY_GEN_GRF_REG(0x0008, 11, 10, 1, 3),
+ .dp_lane_reg = 0x0008,
+ },
+ },
+ .dp_tx_ctrl_cfg = {
+ rk3588_dp_tx_drv_ctrl_rbr_hbr,
+ rk3588_dp_tx_drv_ctrl_rbr_hbr,
+ rk3588_dp_tx_drv_ctrl_hbr2,
+ rk3588_dp_tx_drv_ctrl_hbr3,
+ },
+ .dp_tx_ctrl_cfg_typec = {
+ rk3588_dp_tx_drv_ctrl_rbr_hbr_typec,
+ rk3588_dp_tx_drv_ctrl_rbr_hbr_typec,
+ rk3588_dp_tx_drv_ctrl_hbr2,
+ rk3588_dp_tx_drv_ctrl_hbr3,
+ },
+};
+
+static const struct of_device_id rk_udphy_dt_match[] = {
+ {
+ .compatible = "rockchip,rk3576-usbdp-phy",
+ .data = &rk3576_udphy_cfgs
+ },
+ {
+ .compatible = "rockchip,rk3588-usbdp-phy",
+ .data = &rk3588_udphy_cfgs
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rk_udphy_dt_match);
+
+static struct platform_driver rk_udphy_driver = {
+ .probe = rk_udphy_probe,
+ .driver = {
+ .name = "rockchip-usbdp-phy",
+ .of_match_table = rk_udphy_dt_match,
+ .pm = &rk_udphy_pm_ops,
+ },
+};
+module_platform_driver(rk_udphy_driver);
+
+MODULE_AUTHOR("Frank Wang <frank.wang@rock-chips.com>");
+MODULE_AUTHOR("Zhang Yubing <yubing.zhang@rock-chips.com>");
+MODULE_DESCRIPTION("Rockchip USBDP Combo PHY driver");
+MODULE_LICENSE("GPL");