]> git.ipfire.org Git - thirdparty/u-boot.git/commitdiff
phy: samsung: Add Exynos USB DRD PHY driver
authorSam Protsenko <semen.protsenko@linaro.org>
Wed, 9 Jul 2025 22:29:18 +0000 (17:29 -0500)
committerMinkyu Kang <mk7.kang@samsung.com>
Fri, 25 Jul 2025 01:17:21 +0000 (10:17 +0900)
Add DM driver for Exynos USB PHY controllers. For now it only supports
Exynos850 SoC. 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)

Ported from Linux kernel: drivers/phy/samsung/phy-exynos5-usbdrd.c

Signed-off-by: Sam Protsenko <semen.protsenko@linaro.org>
Reviewed-by: Mattijs Korpershoek <mkorpershoek@kernel.org>
Reviewed-by: Minkyu Kang <mk7.kang@samsung.com>
Signed-off-by: Minkyu Kang <mk7.kang@samsung.com>
MAINTAINERS
drivers/phy/Kconfig
drivers/phy/Makefile
drivers/phy/phy-exynos-usbdrd.c [new file with mode: 0644]

index f914fc54f548bc0c1687787896776c49f5d9925c..d490b43c57f72f928e326cb1d7981aa4358a3c1c 100644 (file)
@@ -602,6 +602,7 @@ ARM SAMSUNG EXYNOS850 SOC
 M:     Sam Protsenko <semen.protsenko@linaro.org>
 S:     Maintained
 F:     drivers/clk/exynos/clk-exynos850.c
+F:     drivers/phy/phy-exynos-usbdrd.c
 F:     drivers/pinctrl/exynos/pinctrl-exynos850.c
 
 ARM SAMSUNG SOC DRIVERS
index d3fe90d939e8a1ba4cc09d7e65919e6c76e124f9..c297fa03ea7fa3f8bec7239102bfb9846d5c0a92 100644 (file)
@@ -259,6 +259,15 @@ config MT76X8_USB_PHY
 
          This PHY is found on MT76x8 devices supporting USB.
 
+config PHY_EXYNOS_USBDRD
+       bool "Exynos SoC series USB DRD PHY driver"
+       depends on PHY && CLK
+       depends on ARCH_EXYNOS
+       select REGMAP
+       select SYSCON
+       help
+         Enable USB DRD PHY support for Exynos SoC series.
+
 config PHY_MTK_TPHY
        bool "MediaTek T-PHY Driver"
        depends on PHY
index b4d01fc700dac1418e96893350ffaee1f8a3af9a..98c1ef8683b7354ccb43426f32ba4dd2f2fcf6c6 100644 (file)
@@ -35,6 +35,7 @@ obj-$(CONFIG_KEYSTONE_USB_PHY) += keystone-usb-phy.o
 obj-$(CONFIG_MT7620_USB_PHY) += mt7620-usb-phy.o
 obj-$(CONFIG_MT76X8_USB_PHY) += mt76x8-usb-phy.o
 obj-$(CONFIG_PHY_DA8XX_USB) += phy-da8xx-usb.o
+obj-$(CONFIG_PHY_EXYNOS_USBDRD) += phy-exynos-usbdrd.o
 obj-$(CONFIG_PHY_MTK_TPHY) += phy-mtk-tphy.o
 obj-$(CONFIG_PHY_NPCM_USB) += phy-npcm-usb.o
 obj-$(CONFIG_PHY_IMX8MQ_USB) += phy-imx8mq-usb.o
diff --git a/drivers/phy/phy-exynos-usbdrd.c b/drivers/phy/phy-exynos-usbdrd.c
new file mode 100644 (file)
index 0000000..db5815e
--- /dev/null
@@ -0,0 +1,386 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 Linaro Ltd.
+ * Sam Protsenko <semen.protsenko@linaro.org>
+ *
+ * Samsung Exynos SoC series USB DRD PHY driver.
+ * Based on Linux kernel PHY driver: drivers/phy/samsung/phy-exynos5-usbdrd.c
+ */
+
+#include <clk.h>
+#include <dm.h>
+#include <generic-phy.h>
+#include <regmap.h>
+#include <syscon.h>
+#include <asm/io.h>
+#include <dm/device_compat.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+
+/* Offset of PMU register controlling USB PHY output isolation */
+#define EXYNOS_USBDRD_PHY_CONTROL              0x0704
+#define EXYNOS_PHY_ENABLE                      BIT(0)
+
+/* Exynos USB PHY registers */
+#define EXYNOS5_FSEL_9MHZ6                     0x0
+#define EXYNOS5_FSEL_10MHZ                     0x1
+#define EXYNOS5_FSEL_12MHZ                     0x2
+#define EXYNOS5_FSEL_19MHZ2                    0x3
+#define EXYNOS5_FSEL_20MHZ                     0x4
+#define EXYNOS5_FSEL_24MHZ                     0x5
+#define EXYNOS5_FSEL_26MHZ                     0x6
+#define EXYNOS5_FSEL_50MHZ                     0x7
+
+/* Exynos850: USB DRD PHY registers */
+#define EXYNOS850_DRD_LINKCTRL                 0x04
+#define LINKCTRL_FORCE_QACT                    BIT(8)
+#define LINKCTRL_BUS_FILTER_BYPASS             GENMASK(7, 4)
+
+#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_SSPPLLCTL                        0x30
+#define SSPPLLCTL_FSEL                         GENMASK(2, 0)
+
+#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)
+
+/**
+ * struct exynos_usbdrd_phy - driver data for Exynos USB PHY
+ * @reg_phy: USB PHY controller register memory base
+ * @clk: clock for register access
+ * @core_clk: core clock for phy (ref clock)
+ * @reg_pmu: regmap for PMU block
+ * @extrefclk: frequency select settings when using 'separate reference clocks'
+ */
+struct exynos_usbdrd_phy {
+       void __iomem *reg_phy;
+       struct clk *clk;
+       struct clk *core_clk;
+       struct regmap *reg_pmu;
+       u32 extrefclk;
+};
+
+static void exynos_usbdrd_phy_isol(struct regmap *reg_pmu, bool isolate)
+{
+       unsigned int val;
+
+       if (!reg_pmu)
+               return;
+
+       val = isolate ? 0 : EXYNOS_PHY_ENABLE;
+       regmap_update_bits(reg_pmu, EXYNOS_USBDRD_PHY_CONTROL,
+                          EXYNOS_PHY_ENABLE, val);
+}
+
+/*
+ * Convert the supplied clock rate to the value that can be written to the PHY
+ * register.
+ */
+static unsigned int exynos_rate_to_clk(unsigned long rate, u32 *reg)
+{
+       switch (rate) {
+       case 9600 * KHZ:
+               *reg = EXYNOS5_FSEL_9MHZ6;
+               break;
+       case 10 * MHZ:
+               *reg = EXYNOS5_FSEL_10MHZ;
+               break;
+       case 12 * MHZ:
+               *reg = EXYNOS5_FSEL_12MHZ;
+               break;
+       case 19200 * KHZ:
+               *reg = EXYNOS5_FSEL_19MHZ2;
+               break;
+       case 20 * MHZ:
+               *reg = EXYNOS5_FSEL_20MHZ;
+               break;
+       case 24 * MHZ:
+               *reg = EXYNOS5_FSEL_24MHZ;
+               break;
+       case 26 * MHZ:
+               *reg = EXYNOS5_FSEL_26MHZ;
+               break;
+       case 50 * MHZ:
+               *reg = EXYNOS5_FSEL_50MHZ;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static void exynos850_usbdrd_utmi_init(struct phy *phy)
+{
+       struct exynos_usbdrd_phy *phy_drd = dev_get_priv(phy->dev);
+       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 |= FIELD_PREP(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);
+
+       reg = readl(regs_base + EXYNOS850_DRD_SSPPLLCTL);
+       reg &= ~SSPPLLCTL_FSEL;
+       switch (phy_drd->extrefclk) {
+       case EXYNOS5_FSEL_50MHZ:
+               reg |= FIELD_PREP(SSPPLLCTL_FSEL, 7);
+               break;
+       case EXYNOS5_FSEL_26MHZ:
+               reg |= FIELD_PREP(SSPPLLCTL_FSEL, 6);
+               break;
+       case EXYNOS5_FSEL_24MHZ:
+               reg |= FIELD_PREP(SSPPLLCTL_FSEL, 2);
+               break;
+       case EXYNOS5_FSEL_20MHZ:
+               reg |= FIELD_PREP(SSPPLLCTL_FSEL, 1);
+               break;
+       case EXYNOS5_FSEL_19MHZ2:
+               reg |= FIELD_PREP(SSPPLLCTL_FSEL, 0);
+               break;
+       default:
+               dev_warn(phy->dev, "unsupported ref clk: %#.2x\n",
+                        phy_drd->extrefclk);
+               break;
+       }
+       writel(reg, regs_base + EXYNOS850_DRD_SSPPLLCTL);
+
+       /* 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 void exynos850_usbdrd_utmi_exit(struct phy *phy)
+{
+       struct exynos_usbdrd_phy *phy_drd = dev_get_priv(phy->dev);
+       void __iomem *regs_base = phy_drd->reg_phy;
+       u32 reg;
+
+       /* 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);
+}
+
+static int exynos_usbdrd_phy_init(struct phy *phy)
+{
+       struct exynos_usbdrd_phy *phy_drd = dev_get_priv(phy->dev);
+       int ret;
+
+       ret = clk_prepare_enable(phy_drd->clk);
+       if (ret)
+               return ret;
+
+       exynos850_usbdrd_utmi_init(phy);
+
+       clk_disable_unprepare(phy_drd->clk);
+
+       return 0;
+}
+
+static int exynos_usbdrd_phy_exit(struct phy *phy)
+{
+       struct exynos_usbdrd_phy *phy_drd = dev_get_priv(phy->dev);
+       int ret;
+
+       ret = clk_prepare_enable(phy_drd->clk);
+       if (ret)
+               return ret;
+
+       exynos850_usbdrd_utmi_exit(phy);
+
+       clk_disable_unprepare(phy_drd->clk);
+
+       return 0;
+}
+
+static int exynos_usbdrd_phy_power_on(struct phy *phy)
+{
+       struct exynos_usbdrd_phy *phy_drd = dev_get_priv(phy->dev);
+       int ret;
+
+       dev_dbg(phy->dev, "Request to power_on usbdrd_phy phy\n");
+
+       ret = clk_prepare_enable(phy_drd->core_clk);
+       if (ret)
+               return ret;
+
+       /* Power-on PHY */
+       exynos_usbdrd_phy_isol(phy_drd->reg_pmu, false);
+
+       return 0;
+}
+
+static int exynos_usbdrd_phy_power_off(struct phy *phy)
+{
+       struct exynos_usbdrd_phy *phy_drd = dev_get_priv(phy->dev);
+
+       dev_dbg(phy->dev, "Request to power_off usbdrd_phy phy\n");
+
+       /* Power-off the PHY */
+       exynos_usbdrd_phy_isol(phy_drd->reg_pmu, true);
+
+       clk_disable_unprepare(phy_drd->core_clk);
+
+       return 0;
+}
+
+static int exynos_usbdrd_phy_init_clk(struct udevice *dev)
+{
+       struct exynos_usbdrd_phy *phy_drd = dev_get_priv(dev);
+       unsigned long ref_rate;
+       int err;
+
+       phy_drd->clk = devm_clk_get(dev, "phy");
+       if (IS_ERR(phy_drd->clk)) {
+               err = PTR_ERR(phy_drd->clk);
+               dev_err(dev, "Failed to get phy clock (err=%d)\n", err);
+               return err;
+       }
+
+       phy_drd->core_clk = devm_clk_get(dev, "ref");
+       if (IS_ERR(phy_drd->core_clk)) {
+               err = PTR_ERR(phy_drd->core_clk);
+               dev_err(dev, "Failed to get ref clock (err=%d)\n", err);
+               return err;
+       }
+
+       ref_rate = clk_get_rate(phy_drd->core_clk);
+       err = exynos_rate_to_clk(ref_rate, &phy_drd->extrefclk);
+       if (err) {
+               dev_err(dev, "Clock rate %lu not supported\n", ref_rate);
+               return err;
+       }
+
+       return 0;
+}
+
+static int exynos_usbdrd_phy_probe(struct udevice *dev)
+{
+       struct exynos_usbdrd_phy *phy_drd = dev_get_priv(dev);
+       int err;
+
+       phy_drd->reg_phy = dev_read_addr_ptr(dev);
+       if (!phy_drd->reg_phy)
+               return -EINVAL;
+
+       err = exynos_usbdrd_phy_init_clk(dev);
+       if (err)
+               return err;
+
+       phy_drd->reg_pmu = syscon_regmap_lookup_by_phandle(dev,
+                                                         "samsung,pmu-syscon");
+       if (IS_ERR(phy_drd->reg_pmu)) {
+               err = PTR_ERR(phy_drd->reg_pmu);
+               dev_err(dev, "Failed to lookup PMU regmap\n");
+               return err;
+       }
+
+       return 0;
+}
+
+static const struct phy_ops exynos_usbdrd_phy_ops = {
+       .init           = exynos_usbdrd_phy_init,
+       .exit           = exynos_usbdrd_phy_exit,
+       .power_on       = exynos_usbdrd_phy_power_on,
+       .power_off      = exynos_usbdrd_phy_power_off,
+};
+
+static const struct udevice_id exynos_usbdrd_phy_of_match[] = {
+       {
+               .compatible = "samsung,exynos850-usbdrd-phy",
+       },
+       { }
+};
+
+U_BOOT_DRIVER(exynos_usbdrd_phy) = {
+       .name           = "exynos-usbdrd-phy",
+       .id             = UCLASS_PHY,
+       .of_match       = exynos_usbdrd_phy_of_match,
+       .probe          = exynos_usbdrd_phy_probe,
+       .ops            = &exynos_usbdrd_phy_ops,
+       .priv_auto      = sizeof(struct exynos_usbdrd_phy),
+};