From 3983b4e9746da1b6091f1d9083f44ed3f12717cb Mon Sep 17 00:00:00 2001 From: Ivaylo Ivanov Date: Sun, 4 May 2025 17:45:22 +0300 Subject: [PATCH 01/16] phy: phy-snps-eusb2: split phy init code The current phy init consists of hardware power-up, as well as QCOM-specific eUSB2 init code. Split it into two parts, to make room for such non-QCOM init code. Signed-off-by: Ivaylo Ivanov Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20250504144527.1723980-6-ivo.ivanov.ivanov1@gmail.com Signed-off-by: Vinod Koul --- drivers/phy/phy-snps-eusb2.c | 94 +++++++++++++++++++++++------------- 1 file changed, 61 insertions(+), 33 deletions(-) diff --git a/drivers/phy/phy-snps-eusb2.c b/drivers/phy/phy-snps-eusb2.c index e1b175f481b4..1933e8440945 100644 --- a/drivers/phy/phy-snps-eusb2.c +++ b/drivers/phy/phy-snps-eusb2.c @@ -121,6 +121,10 @@ static const char * const eusb2_hsphy_vreg_names[] = { #define EUSB2_NUM_VREGS ARRAY_SIZE(eusb2_hsphy_vreg_names) +struct snps_eusb2_phy_drvdata { + int (*phy_init)(struct phy *p); +}; + struct snps_eusb2_hsphy { struct phy *phy; void __iomem *base; @@ -133,6 +137,8 @@ struct snps_eusb2_hsphy { enum phy_mode mode; struct phy *repeater; + + const struct snps_eusb2_phy_drvdata *data; }; static int snps_eusb2_hsphy_set_mode(struct phy *p, enum phy_mode mode, int submode) @@ -230,41 +236,11 @@ static int qcom_eusb2_ref_clk_init(struct snps_eusb2_hsphy *phy) return 0; } -static int snps_eusb2_hsphy_init(struct phy *p) +static int qcom_snps_eusb2_hsphy_init(struct phy *p) { struct snps_eusb2_hsphy *phy = phy_get_drvdata(p); int ret; - ret = regulator_bulk_enable(ARRAY_SIZE(phy->vregs), phy->vregs); - if (ret) - return ret; - - ret = phy_init(phy->repeater); - if (ret) { - dev_err(&p->dev, "repeater init failed. %d\n", ret); - goto disable_vreg; - } - - ret = clk_prepare_enable(phy->ref_clk); - if (ret) { - dev_err(&p->dev, "failed to enable ref clock, %d\n", ret); - goto disable_vreg; - } - - ret = reset_control_assert(phy->phy_reset); - if (ret) { - dev_err(&p->dev, "failed to assert phy_reset, %d\n", ret); - goto disable_ref_clk; - } - - usleep_range(100, 150); - - ret = reset_control_deassert(phy->phy_reset); - if (ret) { - dev_err(&p->dev, "failed to de-assert phy_reset, %d\n", ret); - goto disable_ref_clk; - } - snps_eusb2_hsphy_write_mask(phy->base, QCOM_USB_PHY_CFG0, CMN_CTRL_OVERRIDE_EN, CMN_CTRL_OVERRIDE_EN); @@ -334,6 +310,52 @@ static int snps_eusb2_hsphy_init(struct phy *p) USB2_SUSPEND_N_SEL, 0); return 0; +} + +static const struct snps_eusb2_phy_drvdata sm8550_snps_eusb2_phy = { + .phy_init = qcom_snps_eusb2_hsphy_init, +}; + +static int snps_eusb2_hsphy_init(struct phy *p) +{ + struct snps_eusb2_hsphy *phy = phy_get_drvdata(p); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(phy->vregs), phy->vregs); + if (ret) + return ret; + + ret = phy_init(phy->repeater); + if (ret) { + dev_err(&p->dev, "repeater init failed. %d\n", ret); + goto disable_vreg; + } + + ret = clk_prepare_enable(phy->ref_clk); + if (ret) { + dev_err(&p->dev, "failed to enable ref clock, %d\n", ret); + goto disable_vreg; + } + + ret = reset_control_assert(phy->phy_reset); + if (ret) { + dev_err(&p->dev, "failed to assert phy_reset, %d\n", ret); + goto disable_ref_clk; + } + + usleep_range(100, 150); + + ret = reset_control_deassert(phy->phy_reset); + if (ret) { + dev_err(&p->dev, "failed to de-assert phy_reset, %d\n", ret); + goto disable_ref_clk; + } + + ret = phy->data->phy_init(p); + if (ret) + goto disable_ref_clk; + + return 0; disable_ref_clk: clk_disable_unprepare(phy->ref_clk); @@ -378,6 +400,10 @@ static int snps_eusb2_hsphy_probe(struct platform_device *pdev) if (!phy) return -ENOMEM; + phy->data = device_get_match_data(dev); + if (!phy->data) + return -EINVAL; + phy->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(phy->base)) return PTR_ERR(phy->base); @@ -424,8 +450,10 @@ static int snps_eusb2_hsphy_probe(struct platform_device *pdev) } static const struct of_device_id snps_eusb2_hsphy_of_match_table[] = { - { .compatible = "qcom,sm8550-snps-eusb2-phy", }, - { }, + { + .compatible = "qcom,sm8550-snps-eusb2-phy", + .data = &sm8550_snps_eusb2_phy, + }, { }, }; MODULE_DEVICE_TABLE(of, snps_eusb2_hsphy_of_match_table); -- 2.51.0 From d460be705ae599c0cbfc1ee4ba6a41b225525609 Mon Sep 17 00:00:00 2001 From: Ivaylo Ivanov Date: Sun, 4 May 2025 17:45:23 +0300 Subject: [PATCH 02/16] phy: phy-snps-eusb2: make repeater optional As described in the device tree bindings, it's not necessary for the SNPS eUSB2 phy to be connected to a repeater. In configurations where there are such instances, the driver probing fails and the usb controller does not work. Make the repeater optional to avoid that, which also lets us use the eUSB2 phy when it's connected to a repeater that is not configurable by the kernel (for example it's missing a driver), as long as it has been configured beforehand (usually by the bootloader). Signed-off-by: Ivaylo Ivanov Acked-by: Neil Armstrong Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20250504144527.1723980-7-ivo.ivanov.ivanov1@gmail.com Signed-off-by: Vinod Koul --- drivers/phy/phy-snps-eusb2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/phy/phy-snps-eusb2.c b/drivers/phy/phy-snps-eusb2.c index 1933e8440945..4094786d2737 100644 --- a/drivers/phy/phy-snps-eusb2.c +++ b/drivers/phy/phy-snps-eusb2.c @@ -426,7 +426,7 @@ static int snps_eusb2_hsphy_probe(struct platform_device *pdev) return dev_err_probe(dev, ret, "failed to get regulator supplies\n"); - phy->repeater = devm_of_phy_get_by_index(dev, np, 0); + phy->repeater = devm_of_phy_optional_get(dev, np, 0); if (IS_ERR(phy->repeater)) return dev_err_probe(dev, PTR_ERR(phy->repeater), "failed to get repeater\n"); -- 2.51.0 From aba7a966b50d11deeb3e2f1a66182d150eeb7843 Mon Sep 17 00:00:00 2001 From: Ivaylo Ivanov Date: Sun, 4 May 2025 17:45:24 +0300 Subject: [PATCH 03/16] phy: phy-snps-eusb2: make reset control optional Not all SoCs expose the reset line controls to the kernel, so make them optional. Signed-off-by: Ivaylo Ivanov Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20250504144527.1723980-8-ivo.ivanov.ivanov1@gmail.com Signed-off-by: Vinod Koul --- drivers/phy/phy-snps-eusb2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/phy/phy-snps-eusb2.c b/drivers/phy/phy-snps-eusb2.c index 4094786d2737..f05333901cd4 100644 --- a/drivers/phy/phy-snps-eusb2.c +++ b/drivers/phy/phy-snps-eusb2.c @@ -408,7 +408,7 @@ static int snps_eusb2_hsphy_probe(struct platform_device *pdev) if (IS_ERR(phy->base)) return PTR_ERR(phy->base); - phy->phy_reset = devm_reset_control_get_exclusive(dev, NULL); + phy->phy_reset = devm_reset_control_get_optional_exclusive(dev, NULL); if (IS_ERR(phy->phy_reset)) return PTR_ERR(phy->phy_reset); -- 2.51.0 From e36a5d1ecc5f2bda92e4f954f41d65d1ddd5728e Mon Sep 17 00:00:00 2001 From: Ivaylo Ivanov Date: Sun, 4 May 2025 17:45:25 +0300 Subject: [PATCH 04/16] phy: phy-snps-eusb2: refactor reference clock init Instead of matching frequencies with a switch and case, introduce a table-based lookup. This improves readability, reduces redundancy, and makes it easier to extend support for additional frequencies in the future. Signed-off-by: Ivaylo Ivanov Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20250504144527.1723980-9-ivo.ivanov.ivanov1@gmail.com Signed-off-by: Vinod Koul --- drivers/phy/phy-snps-eusb2.c | 61 +++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/drivers/phy/phy-snps-eusb2.c b/drivers/phy/phy-snps-eusb2.c index f05333901cd4..8caa62c0b48c 100644 --- a/drivers/phy/phy-snps-eusb2.c +++ b/drivers/phy/phy-snps-eusb2.c @@ -192,44 +192,47 @@ static void qcom_eusb2_default_parameters(struct snps_eusb2_hsphy *phy) FIELD_PREP(PHY_CFG_TX_HS_XV_TUNE_MASK, 0x0)); } +struct snps_eusb2_ref_clk { + unsigned long freq; + u32 fsel_val; + u32 div_7_0_val; + u32 div_11_8_val; +}; + +static const struct snps_eusb2_ref_clk qcom_eusb2_ref_clk[] = { + { 19200000, FSEL_19_2_MHZ_VAL, DIV_7_0_19_2_MHZ_VAL, DIV_11_8_19_2_MHZ_VAL }, + { 38400000, FSEL_38_4_MHZ_VAL, DIV_7_0_38_4_MHZ_VAL, DIV_11_8_38_4_MHZ_VAL }, +}; + static int qcom_eusb2_ref_clk_init(struct snps_eusb2_hsphy *phy) { + const struct snps_eusb2_ref_clk *config = NULL; unsigned long ref_clk_freq = clk_get_rate(phy->ref_clk); - switch (ref_clk_freq) { - case 19200000: - snps_eusb2_hsphy_write_mask(phy->base, QCOM_USB_PHY_HS_PHY_CTRL_COMMON0, - FSEL_MASK, - FIELD_PREP(FSEL_MASK, FSEL_19_2_MHZ_VAL)); - - snps_eusb2_hsphy_write_mask(phy->base, QCOM_USB_PHY_CFG_CTRL_2, - PHY_CFG_PLL_FB_DIV_7_0_MASK, - DIV_7_0_19_2_MHZ_VAL); - - snps_eusb2_hsphy_write_mask(phy->base, QCOM_USB_PHY_CFG_CTRL_3, - PHY_CFG_PLL_FB_DIV_11_8_MASK, - DIV_11_8_19_2_MHZ_VAL); - break; - - case 38400000: - snps_eusb2_hsphy_write_mask(phy->base, QCOM_USB_PHY_HS_PHY_CTRL_COMMON0, - FSEL_MASK, - FIELD_PREP(FSEL_MASK, FSEL_38_4_MHZ_VAL)); - - snps_eusb2_hsphy_write_mask(phy->base, QCOM_USB_PHY_CFG_CTRL_2, - PHY_CFG_PLL_FB_DIV_7_0_MASK, - DIV_7_0_38_4_MHZ_VAL); - - snps_eusb2_hsphy_write_mask(phy->base, QCOM_USB_PHY_CFG_CTRL_3, - PHY_CFG_PLL_FB_DIV_11_8_MASK, - DIV_11_8_38_4_MHZ_VAL); - break; + for (int i = 0; i < ARRAY_SIZE(qcom_eusb2_ref_clk); i++) { + if (qcom_eusb2_ref_clk[i].freq == ref_clk_freq) { + config = &qcom_eusb2_ref_clk[i]; + break; + } + } - default: + if (!config) { dev_err(&phy->phy->dev, "unsupported ref_clk_freq:%lu\n", ref_clk_freq); return -EINVAL; } + snps_eusb2_hsphy_write_mask(phy->base, QCOM_USB_PHY_HS_PHY_CTRL_COMMON0, + FSEL_MASK, + FIELD_PREP(FSEL_MASK, config->fsel_val)); + + snps_eusb2_hsphy_write_mask(phy->base, QCOM_USB_PHY_CFG_CTRL_2, + PHY_CFG_PLL_FB_DIV_7_0_MASK, + config->div_7_0_val); + + snps_eusb2_hsphy_write_mask(phy->base, QCOM_USB_PHY_CFG_CTRL_3, + PHY_CFG_PLL_FB_DIV_11_8_MASK, + config->div_11_8_val); + snps_eusb2_hsphy_write_mask(phy->base, QCOM_USB_PHY_CFG_CTRL_3, PHY_CFG_PLL_REF_DIV, PLL_REF_DIV_VAL); -- 2.51.0 From c4098f3e6134e79e070ec44c58976e1f00d9bfad Mon Sep 17 00:00:00 2001 From: Ivaylo Ivanov Date: Sun, 4 May 2025 17:45:26 +0300 Subject: [PATCH 05/16] phy: phy-snps-eusb2: add support for exynos2200 The Exynos2200 SoC reuses the Synopsis eUSB2 PHY IP, alongside an external repeater, for USB 2.0. Add support for it to the existing driver, while keeping in mind that it requires enabled more than the reference clock. Signed-off-by: Ivaylo Ivanov Acked-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20250504144527.1723980-10-ivo.ivanov.ivanov1@gmail.com Signed-off-by: Vinod Koul --- drivers/phy/Kconfig | 2 +- drivers/phy/phy-snps-eusb2.c | 162 ++++++++++++++++++++++++++++++++++- 2 files changed, 159 insertions(+), 5 deletions(-) diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 11c166204b12..58c911e1b2d2 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -45,7 +45,7 @@ config PHY_PISTACHIO_USB config PHY_SNPS_EUSB2 tristate "SNPS eUSB2 PHY Driver" - depends on OF && (ARCH_QCOM || COMPILE_TEST) + depends on OF && (ARCH_EXYNOS || ARCH_QCOM || COMPILE_TEST) select GENERIC_PHY help Enable support for the USB high-speed SNPS eUSB2 phy on select diff --git a/drivers/phy/phy-snps-eusb2.c b/drivers/phy/phy-snps-eusb2.c index 8caa62c0b48c..b73a1d7e57b3 100644 --- a/drivers/phy/phy-snps-eusb2.c +++ b/drivers/phy/phy-snps-eusb2.c @@ -13,6 +13,39 @@ #include #include +#define EXYNOS_USB_PHY_HS_PHY_CTRL_RST (0x0) +#define USB_PHY_RST_MASK GENMASK(1, 0) +#define UTMI_PORT_RST_MASK GENMASK(5, 4) + +#define EXYNOS_USB_PHY_HS_PHY_CTRL_COMMON (0x4) +#define RPTR_MODE BIT(10) +#define FSEL_20_MHZ_VAL (0x1) +#define FSEL_24_MHZ_VAL (0x2) +#define FSEL_26_MHZ_VAL (0x3) +#define FSEL_48_MHZ_VAL (0x2) + +#define EXYNOS_USB_PHY_CFG_PLLCFG0 (0x8) +#define PHY_CFG_PLL_FB_DIV_19_8_MASK GENMASK(19, 8) +#define DIV_19_8_19_2_MHZ_VAL (0x170) +#define DIV_19_8_20_MHZ_VAL (0x160) +#define DIV_19_8_24_MHZ_VAL (0x120) +#define DIV_19_8_26_MHZ_VAL (0x107) +#define DIV_19_8_48_MHZ_VAL (0x120) + +#define EXYNOS_USB_PHY_CFG_PLLCFG1 (0xc) +#define EXYNOS_PHY_CFG_PLL_FB_DIV_11_8_MASK GENMASK(11, 8) +#define EXYNOS_DIV_11_8_19_2_MHZ_VAL (0x0) +#define EXYNOS_DIV_11_8_20_MHZ_VAL (0x0) +#define EXYNOS_DIV_11_8_24_MHZ_VAL (0x0) +#define EXYNOS_DIV_11_8_26_MHZ_VAL (0x0) +#define EXYNOS_DIV_11_8_48_MHZ_VAL (0x1) + +#define EXYNOS_PHY_CFG_TX (0x14) +#define EXYNOS_PHY_CFG_TX_FSLS_VREF_TUNE_MASK GENMASK(2, 1) + +#define EXYNOS_USB_PHY_UTMI_TESTSE (0x20) +#define TEST_IDDQ BIT(6) + #define QCOM_USB_PHY_UTMI_CTRL0 (0x3c) #define SLEEPM BIT(0) #define OPMODE_MASK GENMASK(4, 3) @@ -123,6 +156,8 @@ static const char * const eusb2_hsphy_vreg_names[] = { struct snps_eusb2_phy_drvdata { int (*phy_init)(struct phy *p); + const char * const *clk_names; + int num_clks; }; struct snps_eusb2_hsphy { @@ -130,6 +165,7 @@ struct snps_eusb2_hsphy { void __iomem *base; struct clk *ref_clk; + struct clk_bulk_data *clks; struct reset_control *phy_reset; struct regulator_bulk_data vregs[EUSB2_NUM_VREGS]; @@ -199,6 +235,46 @@ struct snps_eusb2_ref_clk { u32 div_11_8_val; }; +static const struct snps_eusb2_ref_clk exynos_eusb2_ref_clk[] = { + { 19200000, FSEL_19_2_MHZ_VAL, DIV_19_8_19_2_MHZ_VAL, EXYNOS_DIV_11_8_19_2_MHZ_VAL }, + { 20000000, FSEL_20_MHZ_VAL, DIV_19_8_20_MHZ_VAL, EXYNOS_DIV_11_8_20_MHZ_VAL }, + { 24000000, FSEL_24_MHZ_VAL, DIV_19_8_24_MHZ_VAL, EXYNOS_DIV_11_8_24_MHZ_VAL }, + { 26000000, FSEL_26_MHZ_VAL, DIV_19_8_26_MHZ_VAL, EXYNOS_DIV_11_8_26_MHZ_VAL }, + { 48000000, FSEL_48_MHZ_VAL, DIV_19_8_48_MHZ_VAL, EXYNOS_DIV_11_8_48_MHZ_VAL }, +}; + +static int exynos_eusb2_ref_clk_init(struct snps_eusb2_hsphy *phy) +{ + const struct snps_eusb2_ref_clk *config = NULL; + unsigned long ref_clk_freq = clk_get_rate(phy->ref_clk); + + for (int i = 0; i < ARRAY_SIZE(exynos_eusb2_ref_clk); i++) { + if (exynos_eusb2_ref_clk[i].freq == ref_clk_freq) { + config = &exynos_eusb2_ref_clk[i]; + break; + } + } + + if (!config) { + dev_err(&phy->phy->dev, "unsupported ref_clk_freq:%lu\n", ref_clk_freq); + return -EINVAL; + } + + snps_eusb2_hsphy_write_mask(phy->base, EXYNOS_USB_PHY_HS_PHY_CTRL_COMMON, + FSEL_MASK, + FIELD_PREP(FSEL_MASK, config->fsel_val)); + + snps_eusb2_hsphy_write_mask(phy->base, EXYNOS_USB_PHY_CFG_PLLCFG0, + PHY_CFG_PLL_FB_DIV_19_8_MASK, + FIELD_PREP(PHY_CFG_PLL_FB_DIV_19_8_MASK, + config->div_7_0_val)); + + snps_eusb2_hsphy_write_mask(phy->base, EXYNOS_USB_PHY_CFG_PLLCFG1, + EXYNOS_PHY_CFG_PLL_FB_DIV_11_8_MASK, + config->div_11_8_val); + return 0; +} + static const struct snps_eusb2_ref_clk qcom_eusb2_ref_clk[] = { { 19200000, FSEL_19_2_MHZ_VAL, DIV_7_0_19_2_MHZ_VAL, DIV_11_8_19_2_MHZ_VAL }, { 38400000, FSEL_38_4_MHZ_VAL, DIV_7_0_38_4_MHZ_VAL, DIV_11_8_38_4_MHZ_VAL }, @@ -239,6 +315,55 @@ static int qcom_eusb2_ref_clk_init(struct snps_eusb2_hsphy *phy) return 0; } +static int exynos_snps_eusb2_hsphy_init(struct phy *p) +{ + struct snps_eusb2_hsphy *phy = phy_get_drvdata(p); + int ret; + + snps_eusb2_hsphy_write_mask(phy->base, EXYNOS_USB_PHY_HS_PHY_CTRL_RST, + USB_PHY_RST_MASK | UTMI_PORT_RST_MASK, + USB_PHY_RST_MASK | UTMI_PORT_RST_MASK); + fsleep(50); /* required after holding phy in reset */ + + snps_eusb2_hsphy_write_mask(phy->base, EXYNOS_USB_PHY_HS_PHY_CTRL_COMMON, + RPTR_MODE, RPTR_MODE); + + /* update ref_clk related registers */ + ret = exynos_eusb2_ref_clk_init(phy); + if (ret) + return ret; + + /* default parameter: tx fsls-vref */ + snps_eusb2_hsphy_write_mask(phy->base, EXYNOS_PHY_CFG_TX, + EXYNOS_PHY_CFG_TX_FSLS_VREF_TUNE_MASK, + FIELD_PREP(EXYNOS_PHY_CFG_TX_FSLS_VREF_TUNE_MASK, 0x0)); + + snps_eusb2_hsphy_write_mask(phy->base, EXYNOS_USB_PHY_UTMI_TESTSE, + TEST_IDDQ, 0); + fsleep(10); /* required after releasing test_iddq */ + + snps_eusb2_hsphy_write_mask(phy->base, EXYNOS_USB_PHY_HS_PHY_CTRL_RST, + USB_PHY_RST_MASK, 0); + + snps_eusb2_hsphy_write_mask(phy->base, EXYNOS_USB_PHY_HS_PHY_CTRL_COMMON, + PHY_ENABLE, PHY_ENABLE); + + snps_eusb2_hsphy_write_mask(phy->base, EXYNOS_USB_PHY_HS_PHY_CTRL_RST, + UTMI_PORT_RST_MASK, 0); + + return 0; +} + +static const char * const exynos_eusb2_hsphy_clock_names[] = { + "ref", "bus", "ctrl", +}; + +static const struct snps_eusb2_phy_drvdata exynos2200_snps_eusb2_phy = { + .phy_init = exynos_snps_eusb2_hsphy_init, + .clk_names = exynos_eusb2_hsphy_clock_names, + .num_clks = ARRAY_SIZE(exynos_eusb2_hsphy_clock_names), +}; + static int qcom_snps_eusb2_hsphy_init(struct phy *p) { struct snps_eusb2_hsphy *phy = phy_get_drvdata(p); @@ -315,8 +440,14 @@ static int qcom_snps_eusb2_hsphy_init(struct phy *p) return 0; } +static const char * const qcom_eusb2_hsphy_clock_names[] = { + "ref", +}; + static const struct snps_eusb2_phy_drvdata sm8550_snps_eusb2_phy = { .phy_init = qcom_snps_eusb2_hsphy_init, + .clk_names = qcom_eusb2_hsphy_clock_names, + .num_clks = ARRAY_SIZE(qcom_eusb2_hsphy_clock_names), }; static int snps_eusb2_hsphy_init(struct phy *p) @@ -334,7 +465,7 @@ static int snps_eusb2_hsphy_init(struct phy *p) goto disable_vreg; } - ret = clk_prepare_enable(phy->ref_clk); + ret = clk_bulk_prepare_enable(phy->data->num_clks, phy->clks); if (ret) { dev_err(&p->dev, "failed to enable ref clock, %d\n", ret); goto disable_vreg; @@ -361,7 +492,7 @@ static int snps_eusb2_hsphy_init(struct phy *p) return 0; disable_ref_clk: - clk_disable_unprepare(phy->ref_clk); + clk_bulk_disable_unprepare(phy->data->num_clks, phy->clks); disable_vreg: regulator_bulk_disable(ARRAY_SIZE(phy->vregs), phy->vregs); @@ -415,8 +546,28 @@ static int snps_eusb2_hsphy_probe(struct platform_device *pdev) if (IS_ERR(phy->phy_reset)) return PTR_ERR(phy->phy_reset); - phy->ref_clk = devm_clk_get(dev, "ref"); - if (IS_ERR(phy->ref_clk)) + phy->clks = devm_kcalloc(dev, phy->data->num_clks, sizeof(*phy->clks), + GFP_KERNEL); + if (!phy->clks) + return -ENOMEM; + + for (int i = 0; i < phy->data->num_clks; ++i) + phy->clks[i].id = phy->data->clk_names[i]; + + ret = devm_clk_bulk_get(dev, phy->data->num_clks, phy->clks); + if (ret) + return dev_err_probe(dev, ret, + "failed to get phy clock(s)\n"); + + phy->ref_clk = NULL; + for (int i = 0; i < phy->data->num_clks; ++i) { + if (!strcmp(phy->clks[i].id, "ref")) { + phy->ref_clk = phy->clks[i].clk; + break; + } + } + + if (IS_ERR_OR_NULL(phy->ref_clk)) return dev_err_probe(dev, PTR_ERR(phy->ref_clk), "failed to get ref clk\n"); @@ -456,6 +607,9 @@ static const struct of_device_id snps_eusb2_hsphy_of_match_table[] = { { .compatible = "qcom,sm8550-snps-eusb2-phy", .data = &sm8550_snps_eusb2_phy, + }, { + .compatible = "samsung,exynos2200-eusb2-phy", + .data = &exynos2200_snps_eusb2_phy, }, { }, }; MODULE_DEVICE_TABLE(of, snps_eusb2_hsphy_of_match_table); -- 2.51.0 From cc52a697f87e8b2d88298827aca3f81398385572 Mon Sep 17 00:00:00 2001 From: Ivaylo Ivanov Date: Sun, 4 May 2025 17:45:27 +0300 Subject: [PATCH 06/16] phy: exynos5-usbdrd: support Exynos USBDRD 3.2 4nm controller Add support for the Exynos USB 3.2 DRD 4nm controller. It's used in recent 4nm SoCs like Exynos2200 and Exynos2400. This device consists of 3 underlying and independent phys: SEC link control phy, Synopsys eUSB 2.0 and Synopsys USBDP/SS combophy. Unlike older device designs, where the internal phy blocks were all IP of Samsung, Synopsys phys are present. This means that the link controller is now mapped differently to account for missing bits and registers. The Synopsys phys also have separate register bases. As there are non-SEC PHYs present now, it doesn't make much sense to implement them in this driver. They are expected to be configured by external drivers, so pass phandles to them. USBDRD3.2 link controller set up is still required beforehand. This commit adds the necessary changes for USB HS to work. USB SS and DisplayPort are out of scope in this commit and will be introduced in the future. Signed-off-by: Ivaylo Ivanov Link: https://lore.kernel.org/r/20250504144527.1723980-11-ivo.ivanov.ivanov1@gmail.com Signed-off-by: Vinod Koul --- drivers/phy/samsung/phy-exynos5-usbdrd.c | 227 ++++++++++++++++++-- include/linux/soc/samsung/exynos-regs-pmu.h | 3 + 2 files changed, 215 insertions(+), 15 deletions(-) diff --git a/drivers/phy/samsung/phy-exynos5-usbdrd.c b/drivers/phy/samsung/phy-exynos5-usbdrd.c index 6cbe563a7bd0..917a76d584f0 100644 --- a/drivers/phy/samsung/phy-exynos5-usbdrd.c +++ b/drivers/phy/samsung/phy-exynos5-usbdrd.c @@ -36,6 +36,21 @@ #define EXYNOS5_FSEL_26MHZ 0x6 #define EXYNOS5_FSEL_50MHZ 0x7 +/* USB 3.2 DRD 4nm PHY link controller registers */ +#define EXYNOS2200_DRD_CLKRST 0x0c +#define EXYNOS2200_CLKRST_LINK_PCLK_SEL BIT(1) + +#define EXYNOS2200_DRD_UTMI 0x10 +#define EXYNOS2200_UTMI_FORCE_VBUSVALID BIT(1) +#define EXYNOS2200_UTMI_FORCE_BVALID BIT(0) + +#define EXYNOS2200_DRD_HSP_MISC 0x114 +#define HSP_MISC_SET_REQ_IN2 BIT(4) +#define HSP_MISC_RES_TUNE GENMASK(1, 0) +#define RES_TUNE_PHY1_PHY2 0x1 +#define RES_TUNE_PHY1 0x2 +#define RES_TUNE_PHY2 0x3 + /* Exynos5: USB 3.0 DRD PHY registers */ #define EXYNOS5_DRD_LINKSYSTEM 0x04 #define LINKSYSTEM_XHCI_VERSION_CONTROL BIT(27) @@ -431,6 +446,7 @@ struct exynos5_usbdrd_phy_drvdata { * @clks: clocks for register access * @core_clks: core clocks for phy (ref, pipe3, utmi+, ITP, etc. as required) * @drv_data: pointer to SoC level driver data structure + * @hs_phy: pointer to non-Samsung IP high-speed phy controller * @phy_mutex: mutex protecting phy_init/exit & TCPC callbacks * @phys: array for 'EXYNOS5_DRDPHYS_NUM' number of PHY * instances each with its 'phy' and 'phy_cfg'. @@ -448,6 +464,7 @@ struct exynos5_usbdrd_phy { struct clk_bulk_data *clks; struct clk_bulk_data *core_clks; const struct exynos5_usbdrd_phy_drvdata *drv_data; + struct phy *hs_phy; struct mutex phy_mutex; struct phy_usb_instance { struct phy *phy; @@ -1285,6 +1302,149 @@ static const struct phy_ops exynos7870_usbdrd_phy_ops = { .owner = THIS_MODULE, }; +static void exynos2200_usbdrd_utmi_init(struct exynos5_usbdrd_phy *phy_drd) +{ + /* Configure non-Samsung IP PHY, responsible for UTMI */ + phy_init(phy_drd->hs_phy); +} + +static void exynos2200_usbdrd_link_init(struct exynos5_usbdrd_phy *phy_drd) +{ + void __iomem *regs_base = phy_drd->reg_phy; + u32 reg; + + /* + * Disable HWACG (hardware auto clock gating control). This will force + * QACTIVE signal in Q-Channel interface to HIGH level, to make sure + * the PHY clock is not gated by the hardware. + */ + reg = readl(regs_base + EXYNOS850_DRD_LINKCTRL); + reg |= LINKCTRL_FORCE_QACT; + writel(reg, regs_base + EXYNOS850_DRD_LINKCTRL); + + /* De-assert link reset */ + reg = readl(regs_base + EXYNOS2200_DRD_CLKRST); + reg &= ~CLKRST_LINK_SW_RST; + writel(reg, regs_base + EXYNOS2200_DRD_CLKRST); + + /* Set link VBUS Valid */ + reg = readl(regs_base + EXYNOS2200_DRD_UTMI); + reg |= EXYNOS2200_UTMI_FORCE_BVALID | EXYNOS2200_UTMI_FORCE_VBUSVALID; + writel(reg, regs_base + EXYNOS2200_DRD_UTMI); +} + +static void +exynos2200_usbdrd_link_attach_detach_pipe3_phy(struct phy_usb_instance *inst) +{ + struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst); + void __iomem *regs_base = phy_drd->reg_phy; + u32 reg; + + reg = readl(regs_base + EXYNOS850_DRD_LINKCTRL); + if (inst->phy_cfg->id == EXYNOS5_DRDPHY_UTMI) { + /* force pipe3 signal for link */ + reg &= ~LINKCTRL_FORCE_PHYSTATUS; + reg |= LINKCTRL_FORCE_PIPE_EN | LINKCTRL_FORCE_RXELECIDLE; + } else { + /* disable forcing pipe interface */ + reg &= ~LINKCTRL_FORCE_PIPE_EN; + } + writel(reg, regs_base + EXYNOS850_DRD_LINKCTRL); + + reg = readl(regs_base + EXYNOS2200_DRD_HSP_MISC); + if (inst->phy_cfg->id == EXYNOS5_DRDPHY_UTMI) { + /* calibrate only eUSB phy */ + reg |= FIELD_PREP(HSP_MISC_RES_TUNE, RES_TUNE_PHY1); + reg |= HSP_MISC_SET_REQ_IN2; + } else { + /* calibrate for dual phy */ + reg |= FIELD_PREP(HSP_MISC_RES_TUNE, RES_TUNE_PHY1_PHY2); + reg &= ~HSP_MISC_SET_REQ_IN2; + } + writel(reg, regs_base + EXYNOS2200_DRD_HSP_MISC); + + reg = readl(regs_base + EXYNOS2200_DRD_CLKRST); + if (inst->phy_cfg->id == EXYNOS5_DRDPHY_UTMI) + reg &= ~EXYNOS2200_CLKRST_LINK_PCLK_SEL; + else + reg |= EXYNOS2200_CLKRST_LINK_PCLK_SEL; + + writel(reg, regs_base + EXYNOS2200_DRD_CLKRST); +} + +static int exynos2200_usbdrd_phy_init(struct phy *phy) +{ + struct phy_usb_instance *inst = phy_get_drvdata(phy); + struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst); + int ret; + + if (inst->phy_cfg->id == EXYNOS5_DRDPHY_UTMI) { + /* Power-on PHY ... */ + ret = regulator_bulk_enable(phy_drd->drv_data->n_regulators, + phy_drd->regulators); + if (ret) { + dev_err(phy_drd->dev, + "Failed to enable PHY regulator(s)\n"); + return ret; + } + } + /* + * ... and ungate power via PMU. Without this here, we get an SError + * trying to access PMA registers + */ + exynos5_usbdrd_phy_isol(inst, false); + + ret = clk_bulk_prepare_enable(phy_drd->drv_data->n_clks, phy_drd->clks); + if (ret) + return ret; + + /* Set up the link controller */ + exynos2200_usbdrd_link_init(phy_drd); + + /* UTMI or PIPE3 link preparation */ + exynos2200_usbdrd_link_attach_detach_pipe3_phy(inst); + + /* UTMI or PIPE3 specific init */ + inst->phy_cfg->phy_init(phy_drd); + + clk_bulk_disable_unprepare(phy_drd->drv_data->n_clks, phy_drd->clks); + + return 0; +} + +static int exynos2200_usbdrd_phy_exit(struct phy *phy) +{ + struct phy_usb_instance *inst = phy_get_drvdata(phy); + struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst); + void __iomem *regs_base = phy_drd->reg_phy; + u32 reg; + int ret; + + ret = clk_bulk_prepare_enable(phy_drd->drv_data->n_clks, phy_drd->clks); + if (ret) + return ret; + + reg = readl(regs_base + EXYNOS2200_DRD_UTMI); + reg &= ~(EXYNOS2200_UTMI_FORCE_BVALID | EXYNOS2200_UTMI_FORCE_VBUSVALID); + writel(reg, regs_base + EXYNOS2200_DRD_UTMI); + + reg = readl(regs_base + EXYNOS2200_DRD_CLKRST); + reg |= CLKRST_LINK_SW_RST; + writel(reg, regs_base + EXYNOS2200_DRD_CLKRST); + + clk_bulk_disable_unprepare(phy_drd->drv_data->n_clks, phy_drd->clks); + + exynos5_usbdrd_phy_isol(inst, true); + return regulator_bulk_disable(phy_drd->drv_data->n_regulators, + phy_drd->regulators); +} + +static const struct phy_ops exynos2200_usbdrd_phy_ops = { + .init = exynos2200_usbdrd_phy_init, + .exit = exynos2200_usbdrd_phy_exit, + .owner = THIS_MODULE, +}; + static void exynos5_usbdrd_usb_v3p1_pipe_override(struct exynos5_usbdrd_phy *phy_drd) { @@ -1594,27 +1754,37 @@ static int exynos5_usbdrd_phy_clk_handle(struct exynos5_usbdrd_phy *phy_drd) return dev_err_probe(phy_drd->dev, ret, "failed to get phy core clock(s)\n"); - ref_clk = NULL; - for (int i = 0; i < phy_drd->drv_data->n_core_clks; ++i) { - if (!strcmp(phy_drd->core_clks[i].id, "ref")) { - ref_clk = phy_drd->core_clks[i].clk; - break; + if (phy_drd->drv_data->n_core_clks) { + ref_clk = NULL; + for (int i = 0; i < phy_drd->drv_data->n_core_clks; ++i) { + if (!strcmp(phy_drd->core_clks[i].id, "ref")) { + ref_clk = phy_drd->core_clks[i].clk; + break; + } } - } - if (!ref_clk) - return dev_err_probe(phy_drd->dev, -ENODEV, - "failed to find phy reference clock\n"); + if (!ref_clk) + return dev_err_probe(phy_drd->dev, -ENODEV, + "failed to find phy reference clock\n"); - ref_rate = clk_get_rate(ref_clk); - ret = exynos5_rate_to_clk(ref_rate, &phy_drd->extrefclk); - if (ret) - return dev_err_probe(phy_drd->dev, ret, - "clock rate (%ld) not supported\n", - ref_rate); + ref_rate = clk_get_rate(ref_clk); + ret = exynos5_rate_to_clk(ref_rate, &phy_drd->extrefclk); + if (ret) + return dev_err_probe(phy_drd->dev, ret, + "clock rate (%ld) not supported\n", + ref_rate); + } return 0; } +static const struct exynos5_usbdrd_phy_config phy_cfg_exynos2200[] = { + { + .id = EXYNOS5_DRDPHY_UTMI, + .phy_isol = exynos5_usbdrd_phy_isol, + .phy_init = exynos2200_usbdrd_utmi_init, + }, +}; + static int exynos5_usbdrd_orien_sw_set(struct typec_switch_dev *sw, enum typec_orientation orientation) { @@ -1767,6 +1937,19 @@ static const char * const exynos5_regulator_names[] = { "vbus", "vbus-boost", }; +static const struct exynos5_usbdrd_phy_drvdata exynos2200_usb32drd_phy = { + .phy_cfg = phy_cfg_exynos2200, + .phy_ops = &exynos2200_usbdrd_phy_ops, + .pmu_offset_usbdrd0_phy = EXYNOS2200_PHY_CTRL_USB20, + .clk_names = exynos5_clk_names, + .n_clks = ARRAY_SIZE(exynos5_clk_names), + /* clocks and regulators are specific to the underlying PHY blocks */ + .core_clk_names = NULL, + .n_core_clks = 0, + .regulator_names = NULL, + .n_regulators = 0, +}; + static const struct exynos5_usbdrd_phy_drvdata exynos5420_usbdrd_phy = { .phy_cfg = phy_cfg_exynos5, .phy_ops = &exynos5_usbdrd_phy_ops, @@ -2024,6 +2207,9 @@ static const struct of_device_id exynos5_usbdrd_phy_of_match[] = { { .compatible = "google,gs101-usb31drd-phy", .data = &gs101_usbd31rd_phy + }, { + .compatible = "samsung,exynos2200-usb32drd-phy", + .data = &exynos2200_usb32drd_phy, }, { .compatible = "samsung,exynos5250-usbdrd-phy", .data = &exynos5250_usbdrd_phy @@ -2099,6 +2285,17 @@ static int exynos5_usbdrd_phy_probe(struct platform_device *pdev) return PTR_ERR(phy_drd->reg_phy); } + /* + * USB32DRD 4nm controller implements Synopsys eUSB2.0 PHY + * and Synopsys SS/USBDP COMBOPHY, managed by external code. + */ + if (of_property_present(dev->of_node, "phy-names")) { + phy_drd->hs_phy = devm_of_phy_get(dev, dev->of_node, "hs"); + if (IS_ERR(phy_drd->hs_phy)) + return dev_err_probe(dev, PTR_ERR(phy_drd->hs_phy), + "failed to get hs_phy\n"); + } + ret = exynos5_usbdrd_phy_clk_handle(phy_drd); if (ret) return ret; diff --git a/include/linux/soc/samsung/exynos-regs-pmu.h b/include/linux/soc/samsung/exynos-regs-pmu.h index cde299a85384..e921e7fe9be7 100644 --- a/include/linux/soc/samsung/exynos-regs-pmu.h +++ b/include/linux/soc/samsung/exynos-regs-pmu.h @@ -187,6 +187,9 @@ /* Only for S5Pv210 */ #define S5PV210_EINT_WAKEUP_MASK 0xC004 +/* Only for Exynos2200 */ +#define EXYNOS2200_PHY_CTRL_USB20 0x72C + /* Only for Exynos4210 */ #define S5P_CMU_CLKSTOP_LCD1_LOWPWR 0x1154 #define S5P_CMU_RESET_LCD1_LOWPWR 0x1174 -- 2.51.0 From 31eebeef8cdd4c9bddc9d34053cab6553616d0b7 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Mon, 14 Apr 2025 15:57:26 +0100 Subject: [PATCH 07/16] dt-bindings: phy: renesas,usb2-phy: Add clock constraint for RZ/G2L family The RZ/G2L family requires two clocks for USB2 PHY, which are already defined in the DTSI files. Add a constraint in the DT binding document to ensure validation with `dtbs_check`. Signed-off-by: Lad Prabhakar Acked-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20250414145729.343133-2-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Vinod Koul --- Documentation/devicetree/bindings/phy/renesas,usb2-phy.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/phy/renesas,usb2-phy.yaml b/Documentation/devicetree/bindings/phy/renesas,usb2-phy.yaml index af275cea3456..f8d15f239b18 100644 --- a/Documentation/devicetree/bindings/phy/renesas,usb2-phy.yaml +++ b/Documentation/devicetree/bindings/phy/renesas,usb2-phy.yaml @@ -107,6 +107,9 @@ allOf: contains: const: renesas,rzg2l-usb2-phy then: + properties: + clocks: + minItems: 2 required: - resets -- 2.51.0 From 9c4fbefc962dd13694b4a5051f432ed435c92220 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Mon, 14 Apr 2025 15:57:27 +0100 Subject: [PATCH 08/16] dt-bindings: phy: renesas,usb2-phy: Document RZ/V2H(P) SoC Document USB2.0 phy bindings for RZ/V2H(P) ("R9A09gG57") SoC. RZ/V2H(P) USB2.0 phy is similar to one found on the RZ/G2L SoC, but it needs additional configuration to be done as compared RZ/G2L USB2.0 phy. To handle this difference a SoC specific compat string is added for RZ/V2H(P) SoC. Like the RZ/G2L SoC, the RZ/V2H(P) USB2.0 PHY requires the `resets` property and has two clocks. Signed-off-by: Lad Prabhakar Acked-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20250414145729.343133-3-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Vinod Koul --- Documentation/devicetree/bindings/phy/renesas,usb2-phy.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/phy/renesas,usb2-phy.yaml b/Documentation/devicetree/bindings/phy/renesas,usb2-phy.yaml index f8d15f239b18..2822dce8d9f4 100644 --- a/Documentation/devicetree/bindings/phy/renesas,usb2-phy.yaml +++ b/Documentation/devicetree/bindings/phy/renesas,usb2-phy.yaml @@ -16,6 +16,7 @@ properties: - enum: - renesas,usb2-phy-r8a77470 # RZ/G1C - renesas,usb2-phy-r9a08g045 # RZ/G3S + - renesas,usb2-phy-r9a09g057 # RZ/V2H(P) - items: - enum: @@ -105,7 +106,9 @@ allOf: properties: compatible: contains: - const: renesas,rzg2l-usb2-phy + enum: + - renesas,usb2-phy-r9a09g057 + - renesas,rzg2l-usb2-phy then: properties: clocks: -- 2.51.0 From 9414ceb38a74470249dee69784d6079daa452c3a Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Mon, 14 Apr 2025 15:57:28 +0100 Subject: [PATCH 09/16] phy: renesas: phy-rcar-gen3-usb2: Sort compatible entries by SoC part number Reorder the compatible entries in `rcar_gen3_phy_usb2_match_table` to maintain sorting based on SoC part numbers. Keep the entries ordered numerically while ensuring the existing generic compatible strings remain at the bottom. Signed-off-by: Lad Prabhakar Link: https://lore.kernel.org/r/20250414145729.343133-4-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Vinod Koul --- drivers/phy/renesas/phy-rcar-gen3-usb2.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/phy/renesas/phy-rcar-gen3-usb2.c b/drivers/phy/renesas/phy-rcar-gen3-usb2.c index 775f4f973a6c..10d1a89558be 100644 --- a/drivers/phy/renesas/phy-rcar-gen3-usb2.c +++ b/drivers/phy/renesas/phy-rcar-gen3-usb2.c @@ -606,14 +606,14 @@ static const struct of_device_id rcar_gen3_phy_usb2_match_table[] = { .compatible = "renesas,usb2-phy-r8a77965", .data = &rcar_gen3_phy_usb2_data, }, - { - .compatible = "renesas,rzg2l-usb2-phy", - .data = &rz_g2l_phy_usb2_data, - }, { .compatible = "renesas,usb2-phy-r9a08g045", .data = &rz_g3s_phy_usb2_data, }, + { + .compatible = "renesas,rzg2l-usb2-phy", + .data = &rz_g2l_phy_usb2_data, + }, { .compatible = "renesas,rcar-gen3-usb2-phy", .data = &rcar_gen3_phy_usb2_data, -- 2.51.0 From 3767474d7497a4d03f58118d02b742b903626d03 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Mon, 14 Apr 2025 15:57:29 +0100 Subject: [PATCH 10/16] phy: renesas: phy-rcar-gen3-usb2: Add USB2.0 PHY support for RZ/V2H(P) Add USB2.0 PHY support for RZ/V2H(P) SoC. On the RZ/V2H(P) SoC we need to configure the UTMI to a specific value as compared to other SoCs (which doesn't need configuring it). Signed-off-by: Lad Prabhakar Link: https://lore.kernel.org/r/20250414145729.343133-5-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Vinod Koul --- drivers/phy/renesas/phy-rcar-gen3-usb2.c | 30 ++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/drivers/phy/renesas/phy-rcar-gen3-usb2.c b/drivers/phy/renesas/phy-rcar-gen3-usb2.c index 10d1a89558be..867f43132b7a 100644 --- a/drivers/phy/renesas/phy-rcar-gen3-usb2.c +++ b/drivers/phy/renesas/phy-rcar-gen3-usb2.c @@ -28,8 +28,10 @@ #define USB2_INT_ENABLE 0x000 #define USB2_AHB_BUS_CTR 0x008 #define USB2_USBCTR 0x00c +#define USB2_REGEN_CG_CTRL 0x104 /* RZ/V2H(P) only */ #define USB2_SPD_RSM_TIMSET 0x10c #define USB2_OC_TIMSET 0x110 +#define USB2_UTMI_CTRL 0x118 /* RZ/V2H(P) only */ #define USB2_COMMCTRL 0x600 #define USB2_OBINTSTA 0x604 #define USB2_OBINTEN 0x608 @@ -50,12 +52,18 @@ #define USB2_USBCTR_DIRPD BIT(2) #define USB2_USBCTR_PLL_RST BIT(1) +/* REGEN_CG_CTRL*/ +#define USB2_REGEN_CG_CTRL_UPHY_WEN BIT(0) + /* SPD_RSM_TIMSET */ #define USB2_SPD_RSM_TIMSET_INIT 0x014e029b /* OC_TIMSET */ #define USB2_OC_TIMSET_INIT 0x000209ab +/* UTMI_CTRL */ +#define USB2_UTMI_CTRL_INIT 0x8000018f + /* COMMCTRL */ #define USB2_COMMCTRL_OTG_PERI BIT(31) /* 1 = Peripheral mode */ @@ -127,12 +135,14 @@ struct rcar_gen3_chan { bool is_otg_channel; bool uses_otg_pins; bool soc_no_adp_ctrl; + bool utmi_ctrl; }; struct rcar_gen3_phy_drv_data { const struct phy_ops *phy_usb2_ops; bool no_adp_ctrl; bool init_bus; + bool utmi_ctrl; }; /* @@ -472,6 +482,14 @@ static int rcar_gen3_phy_usb2_init(struct phy *p) rphy->otg_initialized = true; } + if (channel->utmi_ctrl) { + val = readl(usb2_base + USB2_REGEN_CG_CTRL) | USB2_REGEN_CG_CTRL_UPHY_WEN; + writel(val, usb2_base + USB2_REGEN_CG_CTRL); + + writel(USB2_UTMI_CTRL_INIT, usb2_base + USB2_UTMI_CTRL); + writel(val & ~USB2_REGEN_CG_CTRL_UPHY_WEN, usb2_base + USB2_REGEN_CG_CTRL); + } + rphy->initialized = true; return 0; @@ -589,6 +607,12 @@ static const struct rcar_gen3_phy_drv_data rz_g3s_phy_usb2_data = { .init_bus = true, }; +static const struct rcar_gen3_phy_drv_data rz_v2h_phy_usb2_data = { + .phy_usb2_ops = &rcar_gen3_phy_usb2_ops, + .no_adp_ctrl = true, + .utmi_ctrl = true, +}; + static const struct of_device_id rcar_gen3_phy_usb2_match_table[] = { { .compatible = "renesas,usb2-phy-r8a77470", @@ -610,6 +634,10 @@ static const struct of_device_id rcar_gen3_phy_usb2_match_table[] = { .compatible = "renesas,usb2-phy-r9a08g045", .data = &rz_g3s_phy_usb2_data, }, + { + .compatible = "renesas,usb2-phy-r9a09g057", + .data = &rz_v2h_phy_usb2_data, + }, { .compatible = "renesas,rzg2l-usb2-phy", .data = &rz_g2l_phy_usb2_data, @@ -763,6 +791,8 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) if (phy_data->no_adp_ctrl) channel->obint_enable_bits = USB2_OBINT_IDCHG_EN; + channel->utmi_ctrl = phy_data->utmi_ctrl; + mutex_init(&channel->lock); for (i = 0; i < NUM_OF_PHYS; i++) { channel->rphys[i].phy = devm_phy_create(dev, NULL, -- 2.51.0 From be79213b4f9ab6e5abf870b97f8a1cab5bf049b3 Mon Sep 17 00:00:00 2001 From: Adam Ford Date: Sun, 4 May 2025 15:40:40 -0500 Subject: [PATCH 11/16] phy: freescale: fsl-samsung-hdmi: Rename phy_clk_round_rate MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit phy_clk_round_rate sounds like a generic helper function. In reality, it is unique to the phy-fsl-samsung-hdmi. Rename phy_clk_round_rate to fsl_samsung_hdmi_phy_clk_round_rate. No functional change intended. Suggested-by: Uwe Kleine-König Signed-off-by: Adam Ford Link: https://lore.kernel.org/r/20250504204043.418924-1-aford173@gmail.com Signed-off-by: Vinod Koul --- drivers/phy/freescale/phy-fsl-samsung-hdmi.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/phy/freescale/phy-fsl-samsung-hdmi.c b/drivers/phy/freescale/phy-fsl-samsung-hdmi.c index 10fbe8dee116..40f33e5ac6f5 100644 --- a/drivers/phy/freescale/phy-fsl-samsung-hdmi.c +++ b/drivers/phy/freescale/phy-fsl-samsung-hdmi.c @@ -531,8 +531,8 @@ static u32 fsl_samsung_hdmi_phy_get_closest_rate(unsigned long rate, return frac_div_clk; } -static long phy_clk_round_rate(struct clk_hw *hw, - unsigned long rate, unsigned long *parent_rate) +static long fsl_samsung_hdmi_phy_clk_round_rate(struct clk_hw *hw, + unsigned long rate, unsigned long *parent_rate) { const struct phy_config *fract_div_phy; u32 int_div_clk; @@ -616,7 +616,7 @@ static int phy_clk_set_rate(struct clk_hw *hw, static const struct clk_ops phy_clk_ops = { .recalc_rate = phy_clk_recalc_rate, - .round_rate = phy_clk_round_rate, + .round_rate = fsl_samsung_hdmi_phy_clk_round_rate, .set_rate = phy_clk_set_rate, }; -- 2.51.0 From 41db4623346777be6ce694338b5adc570c4b671d Mon Sep 17 00:00:00 2001 From: Adam Ford Date: Sun, 4 May 2025 15:40:41 -0500 Subject: [PATCH 12/16] phy: freescale: fsl-samsung-hdmi: Refactor finding PHY settings MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit There are two functions, round_rate and set_rate that duplicate a lot of the same work, so simplify the code by creating a helper function that will identify the phy settings for a desired clock rate and return the structure with the corresponding settings. >From this structure, the round_rate and set_rate can both get what they need to achieve the clock setting closest to the desired rate as possible while minimizing the duplicated code. Also rename phy_clk_set_rate to fsl_samsung_hdmi_phy_clk_set_rate. Suggested-by: Uwe Kleine-König Signed-off-by: Adam Ford Link: https://lore.kernel.org/r/20250504204043.418924-2-aford173@gmail.com Signed-off-by: Vinod Koul --- drivers/phy/freescale/phy-fsl-samsung-hdmi.c | 106 +++++++------------ 1 file changed, 41 insertions(+), 65 deletions(-) diff --git a/drivers/phy/freescale/phy-fsl-samsung-hdmi.c b/drivers/phy/freescale/phy-fsl-samsung-hdmi.c index 40f33e5ac6f5..a081f07681db 100644 --- a/drivers/phy/freescale/phy-fsl-samsung-hdmi.c +++ b/drivers/phy/freescale/phy-fsl-samsung-hdmi.c @@ -456,6 +456,8 @@ static int fsl_samsung_hdmi_phy_configure(struct fsl_samsung_hdmi_phy *phy, int i, ret; u8 val; + phy->cur_cfg = cfg; + /* HDMI PHY init */ writeb(REG33_FIX_DA, phy->regs + PHY_REG(33)); @@ -521,18 +523,9 @@ static void fsl_samsung_hdmi_calculate_phy(struct phy_config *cal_phy, unsigned /* pll_div_regs 3-6 are fixed and pre-defined already */ } -static u32 fsl_samsung_hdmi_phy_get_closest_rate(unsigned long rate, - u32 int_div_clk, u32 frac_div_clk) -{ - /* Calculate the absolute value of the differences and return whichever is closest */ - if (abs((long)rate - (long)int_div_clk) < abs((long)(rate - (long)frac_div_clk))) - return int_div_clk; - - return frac_div_clk; -} - -static long fsl_samsung_hdmi_phy_clk_round_rate(struct clk_hw *hw, - unsigned long rate, unsigned long *parent_rate) +static +const struct phy_config *fsl_samsung_hdmi_phy_find_settings(struct fsl_samsung_hdmi_phy *phy, + unsigned long rate) { const struct phy_config *fract_div_phy; u32 int_div_clk; @@ -541,83 +534,66 @@ static long fsl_samsung_hdmi_phy_clk_round_rate(struct clk_hw *hw, /* If the clock is out of range return error instead of searching */ if (rate > 297000000 || rate < 22250000) - return -EINVAL; + return NULL; /* Search the fractional divider lookup table */ fract_div_phy = fsl_samsung_hdmi_phy_lookup_rate(rate); + if (fract_div_phy->pixclk == rate) { + dev_dbg(phy->dev, "fractional divider match = %u\n", fract_div_phy->pixclk); + return fract_div_phy; + } - /* If the rate is an exact match, return that value */ - if (rate == fract_div_phy->pixclk) - return fract_div_phy->pixclk; - - /* If the exact match isn't found, calculate the integer divider */ + /* Calculate the integer divider */ int_div_clk = fsl_samsung_hdmi_phy_find_pms(rate, &p, &m, &s); + fsl_samsung_hdmi_calculate_phy(&calculated_phy_pll_cfg, int_div_clk, p, m, s); + if (int_div_clk == rate) { + dev_dbg(phy->dev, "integer divider match = %u\n", calculated_phy_pll_cfg.pixclk); + return &calculated_phy_pll_cfg; + } - /* If the int_div_clk rate is an exact match, return that value */ - if (int_div_clk == rate) - return int_div_clk; + /* Calculate the absolute value of the differences and return whichever is closest */ + if (abs((long)rate - (long)int_div_clk) < + abs((long)rate - (long)fract_div_phy->pixclk)) { + dev_dbg(phy->dev, "integer divider = %u\n", calculated_phy_pll_cfg.pixclk); + return &calculated_phy_pll_cfg; + } - /* If neither rate is an exact match, use the value from the LUT */ - return fract_div_phy->pixclk; -} + dev_dbg(phy->dev, "fractional divider = %u\n", phy->cur_cfg->pixclk); -static int phy_use_fract_div(struct fsl_samsung_hdmi_phy *phy, const struct phy_config *fract_div_phy) -{ - phy->cur_cfg = fract_div_phy; - dev_dbg(phy->dev, "fsl_samsung_hdmi_phy: using fractional divider rate = %u\n", - phy->cur_cfg->pixclk); - return fsl_samsung_hdmi_phy_configure(phy, phy->cur_cfg); + return fract_div_phy; } -static int phy_use_integer_div(struct fsl_samsung_hdmi_phy *phy, - const struct phy_config *int_div_clk) +static long fsl_samsung_hdmi_phy_clk_round_rate(struct clk_hw *hw, + unsigned long rate, unsigned long *parent_rate) { - phy->cur_cfg = &calculated_phy_pll_cfg; - dev_dbg(phy->dev, "fsl_samsung_hdmi_phy: integer divider rate = %u\n", - phy->cur_cfg->pixclk); - return fsl_samsung_hdmi_phy_configure(phy, phy->cur_cfg); + struct fsl_samsung_hdmi_phy *phy = to_fsl_samsung_hdmi_phy(hw); + const struct phy_config *target_settings = fsl_samsung_hdmi_phy_find_settings(phy, rate); + + if (target_settings == NULL) + return -EINVAL; + + dev_dbg(phy->dev, "round_rate, closest rate = %u\n", target_settings->pixclk); + return target_settings->pixclk; } -static int phy_clk_set_rate(struct clk_hw *hw, +static int fsl_samsung_hdmi_phy_clk_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { struct fsl_samsung_hdmi_phy *phy = to_fsl_samsung_hdmi_phy(hw); - const struct phy_config *fract_div_phy; - u32 int_div_clk; - u16 m; - u8 p, s; - - /* Search the fractional divider lookup table */ - fract_div_phy = fsl_samsung_hdmi_phy_lookup_rate(rate); + const struct phy_config *target_settings = fsl_samsung_hdmi_phy_find_settings(phy, rate); - /* If the rate is an exact match, use that value */ - if (fract_div_phy->pixclk == rate) - return phy_use_fract_div(phy, fract_div_phy); + if (target_settings == NULL) + return -EINVAL; - /* - * If the rate from the fractional divider is not exact, check the integer divider, - * and use it if that value is an exact match. - */ - int_div_clk = fsl_samsung_hdmi_phy_find_pms(rate, &p, &m, &s); - fsl_samsung_hdmi_calculate_phy(&calculated_phy_pll_cfg, int_div_clk, p, m, s); - if (int_div_clk == rate) - return phy_use_integer_div(phy, &calculated_phy_pll_cfg); + dev_dbg(phy->dev, "set_rate, closest rate = %u\n", target_settings->pixclk); - /* - * Compare the difference between the integer clock and the fractional clock against - * the desired clock and which whichever is closest. - */ - if (fsl_samsung_hdmi_phy_get_closest_rate(rate, int_div_clk, - fract_div_phy->pixclk) == fract_div_phy->pixclk) - return phy_use_fract_div(phy, fract_div_phy); - else - return phy_use_integer_div(phy, &calculated_phy_pll_cfg); + return fsl_samsung_hdmi_phy_configure(phy, target_settings); } static const struct clk_ops phy_clk_ops = { .recalc_rate = phy_clk_recalc_rate, .round_rate = fsl_samsung_hdmi_phy_clk_round_rate, - .set_rate = phy_clk_set_rate, + .set_rate = fsl_samsung_hdmi_phy_clk_set_rate, }; static int phy_clk_register(struct fsl_samsung_hdmi_phy *phy) -- 2.51.0 From 46a87260fc4f719f58e07a53cc1b70a38d98da37 Mon Sep 17 00:00:00 2001 From: Adam Ford Date: Sun, 4 May 2025 15:40:42 -0500 Subject: [PATCH 13/16] phy: freescale: fsl-samsung-hdmi: Improve LUT search for best clock Searching the look-up-table runs so long as the frequency in the table is at or below the desired rate. This works well in most cases, but the next entry in the LUT might be closer to the nominal value than the lower one. Add some logic to check the higer value is any closer to the nominal value and use it. Signed-off-by: Adam Ford Link: https://lore.kernel.org/r/20250504204043.418924-3-aford173@gmail.com Signed-off-by: Vinod Koul --- drivers/phy/freescale/phy-fsl-samsung-hdmi.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/phy/freescale/phy-fsl-samsung-hdmi.c b/drivers/phy/freescale/phy-fsl-samsung-hdmi.c index a081f07681db..191c282246d9 100644 --- a/drivers/phy/freescale/phy-fsl-samsung-hdmi.c +++ b/drivers/phy/freescale/phy-fsl-samsung-hdmi.c @@ -510,7 +510,14 @@ static const struct phy_config *fsl_samsung_hdmi_phy_lookup_rate(unsigned long r if (phy_pll_cfg[i].pixclk <= rate) break; - return &phy_pll_cfg[i]; + /* If there is an exact match, or the array has been searched, return the value*/ + if (phy_pll_cfg[i].pixclk == rate || i + 1 > ARRAY_SIZE(phy_pll_cfg) - 1) + return &phy_pll_cfg[i]; + + /* See if the next entry is closer to nominal than this one */ + return (abs((long) rate - (long) phy_pll_cfg[i].pixclk) < + abs((long) rate - (long) phy_pll_cfg[i+1].pixclk) ? + &phy_pll_cfg[i] : &phy_pll_cfg[i+1]); } static void fsl_samsung_hdmi_calculate_phy(struct phy_config *cal_phy, unsigned long rate, -- 2.51.0 From d78b565371314e48242cb9383d0f9d331119ab2e Mon Sep 17 00:00:00 2001 From: Heiko Stuebner Date: Sat, 3 May 2025 22:15:10 +0200 Subject: [PATCH 14/16] dt-bindings: phy: rockchip,inno-usb2phy: add rk3036 compatible Add compatible for the USB2 phy in the Rockchip RK3036 SoC. Apart from some bits that got swapped around in the phy registers, the block is nearly the same as the one on the rk3128. Signed-off-by: Heiko Stuebner Acked-by: Conor Dooley Link: https://lore.kernel.org/r/20250503201512.991277-2-heiko@sntech.de Signed-off-by: Vinod Koul --- .../devicetree/bindings/phy/rockchip,inno-usb2phy.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/phy/rockchip,inno-usb2phy.yaml b/Documentation/devicetree/bindings/phy/rockchip,inno-usb2phy.yaml index 6a7ef556414c..7bcefe8c22d1 100644 --- a/Documentation/devicetree/bindings/phy/rockchip,inno-usb2phy.yaml +++ b/Documentation/devicetree/bindings/phy/rockchip,inno-usb2phy.yaml @@ -13,6 +13,7 @@ properties: compatible: enum: - rockchip,px30-usb2phy + - rockchip,rk3036-usb2phy - rockchip,rk3128-usb2phy - rockchip,rk3228-usb2phy - rockchip,rk3308-usb2phy @@ -184,6 +185,7 @@ allOf: contains: enum: - rockchip,px30-usb2phy + - rockchip,rk3036-usb2phy - rockchip,rk3128-usb2phy - rockchip,rk3228-usb2phy - rockchip,rk3308-usb2phy -- 2.51.0 From 3ca48b955bb34a3b07dd915318d73a23740de282 Mon Sep 17 00:00:00 2001 From: Heiko Stuebner Date: Sat, 3 May 2025 22:15:11 +0200 Subject: [PATCH 15/16] phy: rockchip: inno-usb2: add phy definition for rk3036 The usb2phy on rk3036 is very similar to rk3128 but not 100% identical. While most registers and bits are the same, a small fraction did get moved around. So we can re-use the phy-tuning function, but need a new set a bits. The biggest change might be that the phy on rk3036 does not support the charger detection, that rk3128 (and newer phys) have. Signed-off-by: Heiko Stuebner Link: https://lore.kernel.org/r/20250503201512.991277-3-heiko@sntech.de Signed-off-by: Vinod Koul --- drivers/phy/rockchip/phy-rockchip-inno-usb2.c | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c index b5e6a864deeb..32fdd64d7c2a 100644 --- a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c +++ b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c @@ -1583,6 +1583,37 @@ static int rk3588_usb2phy_tuning(struct rockchip_usb2phy *rphy) 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, @@ -2204,6 +2235,7 @@ static const struct rockchip_usb2phy_cfg rv1108_phy_cfgs[] = { 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 }, -- 2.51.0 From abf55cdf9c5e58bac1feaff2e21bec43b898746c Mon Sep 17 00:00:00 2001 From: Kever Yang Date: Tue, 15 Apr 2025 13:00:04 +0800 Subject: [PATCH 16/16] dt-bindings: phy: rockchip,inno-usb2phy: add rk3562 Add compatible for the USB2 phy in the Rockchip RK3562 SoC. Signed-off-by: Kever Yang Reviewed-by: Heiko Stuebner Acked-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20250415050005.52773-1-kever.yang@rock-chips.com Signed-off-by: Vinod Koul --- .../devicetree/bindings/phy/rockchip,inno-usb2phy.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/phy/rockchip,inno-usb2phy.yaml b/Documentation/devicetree/bindings/phy/rockchip,inno-usb2phy.yaml index 7bcefe8c22d1..58e735b5dd05 100644 --- a/Documentation/devicetree/bindings/phy/rockchip,inno-usb2phy.yaml +++ b/Documentation/devicetree/bindings/phy/rockchip,inno-usb2phy.yaml @@ -20,6 +20,7 @@ properties: - rockchip,rk3328-usb2phy - rockchip,rk3366-usb2phy - rockchip,rk3399-usb2phy + - rockchip,rk3562-usb2phy - rockchip,rk3568-usb2phy - rockchip,rk3576-usb2phy - rockchip,rk3588-usb2phy @@ -192,6 +193,7 @@ allOf: - rockchip,rk3328-usb2phy - rockchip,rk3366-usb2phy - rockchip,rk3399-usb2phy + - rockchip,rk3562-usb2phy - rockchip,rk3568-usb2phy - rockchip,rk3588-usb2phy - rockchip,rv1108-usb2phy -- 2.51.0