]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
phy: exynos5-usbdrd: support Exynos USBDRD 3.2 4nm controller
authorIvaylo Ivanov <ivo.ivanov.ivanov1@gmail.com>
Sun, 4 May 2025 14:45:27 +0000 (17:45 +0300)
committerVinod Koul <vkoul@kernel.org>
Wed, 14 May 2025 10:43:38 +0000 (11:43 +0100)
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 <ivo.ivanov.ivanov1@gmail.com>
Link: https://lore.kernel.org/r/20250504144527.1723980-11-ivo.ivanov.ivanov1@gmail.com
Signed-off-by: Vinod Koul <vkoul@kernel.org>
drivers/phy/samsung/phy-exynos5-usbdrd.c
include/linux/soc/samsung/exynos-regs-pmu.h

index 6cbe563a7bd0449248d6b41c4f6785c91ccc47ba..917a76d584f0856f1e445630e2cf97b3c3e46b13 100644 (file)
 #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;
index cde299a85384a70d04dae49ee9a4e2daa88fbbf6..e921e7fe9be7f5f6d35ad78a40bea34752127446 100644 (file)
 /* 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