phy: exynos5-usbdrd: Add Exynos850 support
authorSam Protsenko <semen.protsenko@linaro.org>
Sat, 19 Aug 2023 03:17:29 +0000 (22:17 -0500)
committerVinod Koul <vkoul@kernel.org>
Tue, 22 Aug 2023 14:11:15 +0000 (19:41 +0530)
Implement Exynos850 USB 2.0 DRD PHY controller support. Exynos850 has
quite a different PHY controller than Exynos5 compatible controllers,
but it's still possible to implement it on top of existing
exynos5-usbdrd driver infrastructure.

Only UTMI+ (USB 2.0) PHY interface is implemented, as Exynos850 doesn't
support USB 3.0.

Only two clocks are used for this controller:
  - phy: bus clock, used for PHY registers access
  - ref: PHY reference clock (OSCCLK)

Signed-off-by: Sam Protsenko <semen.protsenko@linaro.org>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Link: https://lore.kernel.org/r/20230819031731.22618-7-semen.protsenko@linaro.org
Signed-off-by: Vinod Koul <vkoul@kernel.org>
drivers/phy/samsung/phy-exynos5-usbdrd.c

index 41508db..3f310b2 100644 (file)
 #define LANE0_TX_DEBUG_RXDET_MEAS_TIME_62M5            (0x20 << 4)
 #define LANE0_TX_DEBUG_RXDET_MEAS_TIME_96M_100M                (0x40 << 4)
 
+/* Exynos850: USB DRD PHY registers */
+#define EXYNOS850_DRD_LINKCTRL                 0x04
+#define LINKCTRL_BUS_FILTER_BYPASS(_x)         ((_x) << 4)
+#define LINKCTRL_FORCE_QACT                    BIT(8)
+
+#define EXYNOS850_DRD_CLKRST                   0x20
+#define CLKRST_LINK_SW_RST                     BIT(0)
+#define CLKRST_PORT_RST                                BIT(1)
+#define CLKRST_PHY_SW_RST                      BIT(3)
+
+#define EXYNOS850_DRD_UTMI                     0x50
+#define UTMI_FORCE_SLEEP                       BIT(0)
+#define UTMI_FORCE_SUSPEND                     BIT(1)
+#define UTMI_DM_PULLDOWN                       BIT(2)
+#define UTMI_DP_PULLDOWN                       BIT(3)
+#define UTMI_FORCE_BVALID                      BIT(4)
+#define UTMI_FORCE_VBUSVALID                   BIT(5)
+
+#define EXYNOS850_DRD_HSP                      0x54
+#define HSP_COMMONONN                          BIT(8)
+#define HSP_EN_UTMISUSPEND                     BIT(9)
+#define HSP_VBUSVLDEXT                         BIT(12)
+#define HSP_VBUSVLDEXTSEL                      BIT(13)
+#define HSP_FSV_OUT_EN                         BIT(24)
+
+#define EXYNOS850_DRD_HSP_TEST                 0x5c
+#define HSP_TEST_SIDDQ                         BIT(24)
+
 #define KHZ    1000
 #define MHZ    (KHZ * KHZ)
 
@@ -716,6 +744,129 @@ static const struct phy_ops exynos5_usbdrd_phy_ops = {
        .owner          = THIS_MODULE,
 };
 
+static void exynos850_usbdrd_utmi_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);
+
+       /* Start PHY Reset (POR=high) */
+       reg = readl(regs_base + EXYNOS850_DRD_CLKRST);
+       reg |= CLKRST_PHY_SW_RST;
+       writel(reg, regs_base + EXYNOS850_DRD_CLKRST);
+
+       /* Enable UTMI+ */
+       reg = readl(regs_base + EXYNOS850_DRD_UTMI);
+       reg &= ~(UTMI_FORCE_SUSPEND | UTMI_FORCE_SLEEP | UTMI_DP_PULLDOWN |
+                UTMI_DM_PULLDOWN);
+       writel(reg, regs_base + EXYNOS850_DRD_UTMI);
+
+       /* Set PHY clock and control HS PHY */
+       reg = readl(regs_base + EXYNOS850_DRD_HSP);
+       reg |= HSP_EN_UTMISUSPEND | HSP_COMMONONN;
+       writel(reg, regs_base + EXYNOS850_DRD_HSP);
+
+       /* Set VBUS Valid and D+ pull-up control by VBUS pad usage */
+       reg = readl(regs_base + EXYNOS850_DRD_LINKCTRL);
+       reg |= LINKCTRL_BUS_FILTER_BYPASS(0xf);
+       writel(reg, regs_base + EXYNOS850_DRD_LINKCTRL);
+
+       reg = readl(regs_base + EXYNOS850_DRD_UTMI);
+       reg |= UTMI_FORCE_BVALID | UTMI_FORCE_VBUSVALID;
+       writel(reg, regs_base + EXYNOS850_DRD_UTMI);
+
+       reg = readl(regs_base + EXYNOS850_DRD_HSP);
+       reg |= HSP_VBUSVLDEXT | HSP_VBUSVLDEXTSEL;
+       writel(reg, regs_base + EXYNOS850_DRD_HSP);
+
+       /* Power up PHY analog blocks */
+       reg = readl(regs_base + EXYNOS850_DRD_HSP_TEST);
+       reg &= ~HSP_TEST_SIDDQ;
+       writel(reg, regs_base + EXYNOS850_DRD_HSP_TEST);
+
+       /* Finish PHY reset (POR=low) */
+       udelay(10); /* required before doing POR=low */
+       reg = readl(regs_base + EXYNOS850_DRD_CLKRST);
+       reg &= ~(CLKRST_PHY_SW_RST | CLKRST_PORT_RST);
+       writel(reg, regs_base + EXYNOS850_DRD_CLKRST);
+       udelay(75); /* required after POR=low for guaranteed PHY clock */
+
+       /* Disable single ended signal out */
+       reg = readl(regs_base + EXYNOS850_DRD_HSP);
+       reg &= ~HSP_FSV_OUT_EN;
+       writel(reg, regs_base + EXYNOS850_DRD_HSP);
+}
+
+static int exynos850_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;
+
+       ret = clk_prepare_enable(phy_drd->clk);
+       if (ret)
+               return ret;
+
+       /* UTMI or PIPE3 specific init */
+       inst->phy_cfg->phy_init(phy_drd);
+
+       clk_disable_unprepare(phy_drd->clk);
+
+       return 0;
+}
+
+static int exynos850_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_prepare_enable(phy_drd->clk);
+       if (ret)
+               return ret;
+
+       /* Set PHY clock and control HS PHY */
+       reg = readl(regs_base + EXYNOS850_DRD_UTMI);
+       reg &= ~(UTMI_DP_PULLDOWN | UTMI_DM_PULLDOWN);
+       reg |= UTMI_FORCE_SUSPEND | UTMI_FORCE_SLEEP;
+       writel(reg, regs_base + EXYNOS850_DRD_UTMI);
+
+       /* Power down PHY analog blocks */
+       reg = readl(regs_base + EXYNOS850_DRD_HSP_TEST);
+       reg |= HSP_TEST_SIDDQ;
+       writel(reg, regs_base + EXYNOS850_DRD_HSP_TEST);
+
+       /* Link reset */
+       reg = readl(regs_base + EXYNOS850_DRD_CLKRST);
+       reg |= CLKRST_LINK_SW_RST;
+       writel(reg, regs_base + EXYNOS850_DRD_CLKRST);
+       udelay(10); /* required before doing POR=low */
+       reg &= ~CLKRST_LINK_SW_RST;
+       writel(reg, regs_base + EXYNOS850_DRD_CLKRST);
+
+       clk_disable_unprepare(phy_drd->clk);
+
+       return 0;
+}
+
+static const struct phy_ops exynos850_usbdrd_phy_ops = {
+       .init           = exynos850_usbdrd_phy_init,
+       .exit           = exynos850_usbdrd_phy_exit,
+       .power_on       = exynos5_usbdrd_phy_power_on,
+       .power_off      = exynos5_usbdrd_phy_power_off,
+       .owner          = THIS_MODULE,
+};
+
 static int exynos5_usbdrd_phy_clk_handle(struct exynos5_usbdrd_phy *phy_drd)
 {
        unsigned long ref_rate;
@@ -782,6 +933,14 @@ static const struct exynos5_usbdrd_phy_config phy_cfg_exynos5[] = {
        },
 };
 
+static const struct exynos5_usbdrd_phy_config phy_cfg_exynos850[] = {
+       {
+               .id             = EXYNOS5_DRDPHY_UTMI,
+               .phy_isol       = exynos5_usbdrd_phy_isol,
+               .phy_init       = exynos850_usbdrd_utmi_init,
+       },
+};
+
 static const struct exynos5_usbdrd_phy_drvdata exynos5420_usbdrd_phy = {
        .phy_cfg                = phy_cfg_exynos5,
        .phy_ops                = &exynos5_usbdrd_phy_ops,
@@ -812,6 +971,13 @@ static const struct exynos5_usbdrd_phy_drvdata exynos7_usbdrd_phy = {
        .has_common_clk_gate    = false,
 };
 
+static const struct exynos5_usbdrd_phy_drvdata exynos850_usbdrd_phy = {
+       .phy_cfg                = phy_cfg_exynos850,
+       .phy_ops                = &exynos850_usbdrd_phy_ops,
+       .pmu_offset_usbdrd0_phy = EXYNOS5_USBDRD_PHY_CONTROL,
+       .has_common_clk_gate    = true,
+};
+
 static const struct of_device_id exynos5_usbdrd_phy_of_match[] = {
        {
                .compatible = "samsung,exynos5250-usbdrd-phy",
@@ -825,6 +991,9 @@ static const struct of_device_id exynos5_usbdrd_phy_of_match[] = {
        }, {
                .compatible = "samsung,exynos7-usbdrd-phy",
                .data = &exynos7_usbdrd_phy
+       }, {
+               .compatible = "samsung,exynos850-usbdrd-phy",
+               .data = &exynos850_usbdrd_phy
        },
        { },
 };