From ae1e081f83fb4b4d79e32f1bdcea3e47519ce05c Mon Sep 17 00:00:00 2001 From: Kaustabh Chakraborty Date: Mon, 23 Feb 2026 19:55:23 +0530 Subject: phy: samsung: add enum for variants based on SoCs The variant enum is used to uniquely identify which SoC the PHY block belongs to. It is initially set in the match table, along with the compatible string, it gets copied to driver data struct during probe. SoC specific functions must only be called if the respective variant enum is set. Add switch-case blocks wherever required. Reviewed-by: Mattijs Korpershoek Signed-off-by: Kaustabh Chakraborty Signed-off-by: Minkyu Kang --- drivers/phy/phy-exynos-usbdrd.c | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) (limited to 'drivers/phy') diff --git a/drivers/phy/phy-exynos-usbdrd.c b/drivers/phy/phy-exynos-usbdrd.c index db5815ed184..4ab8da2a08f 100644 --- a/drivers/phy/phy-exynos-usbdrd.c +++ b/drivers/phy/phy-exynos-usbdrd.c @@ -66,6 +66,10 @@ #define KHZ 1000 #define MHZ (KHZ * KHZ) +enum exynos_usbdrd_phy_variant { + EXYNOS850_USBDRD_PHY, +}; + /** * struct exynos_usbdrd_phy - driver data for Exynos USB PHY * @reg_phy: USB PHY controller register memory base @@ -73,6 +77,7 @@ * @core_clk: core clock for phy (ref clock) * @reg_pmu: regmap for PMU block * @extrefclk: frequency select settings when using 'separate reference clocks' + * @variant: ID to uniquely distinguish USB PHY variant */ struct exynos_usbdrd_phy { void __iomem *reg_phy; @@ -80,6 +85,7 @@ struct exynos_usbdrd_phy { struct clk *core_clk; struct regmap *reg_pmu; u32 extrefclk; + enum exynos_usbdrd_phy_variant variant; }; static void exynos_usbdrd_phy_isol(struct regmap *reg_pmu, bool isolate) @@ -254,7 +260,13 @@ static int exynos_usbdrd_phy_init(struct phy *phy) if (ret) return ret; - exynos850_usbdrd_utmi_init(phy); + switch (phy_drd->variant) { + case EXYNOS850_USBDRD_PHY: + exynos850_usbdrd_utmi_init(phy); + break; + default: + dev_err(phy->dev, "Failed to recognize phy variant\n"); + } clk_disable_unprepare(phy_drd->clk); @@ -270,7 +282,13 @@ static int exynos_usbdrd_phy_exit(struct phy *phy) if (ret) return ret; - exynos850_usbdrd_utmi_exit(phy); + switch (phy_drd->variant) { + case EXYNOS850_USBDRD_PHY: + exynos850_usbdrd_utmi_exit(phy); + break; + default: + dev_err(phy->dev, "Failed to recognize phy variant\n"); + } clk_disable_unprepare(phy_drd->clk); @@ -359,6 +377,8 @@ static int exynos_usbdrd_phy_probe(struct udevice *dev) return err; } + phy_drd->variant = dev_get_driver_data(dev); + return 0; } @@ -372,6 +392,7 @@ static const struct phy_ops exynos_usbdrd_phy_ops = { static const struct udevice_id exynos_usbdrd_phy_of_match[] = { { .compatible = "samsung,exynos850-usbdrd-phy", + .data = EXYNOS850_USBDRD_PHY, }, { } }; -- cgit v1.3.1 From e4001865ff387b928ea3d422d673dcc7389f8665 Mon Sep 17 00:00:00 2001 From: Kaustabh Chakraborty Date: Mon, 23 Feb 2026 19:55:24 +0530 Subject: phy: samsung: add support for exynos7870 USB PHY The USB PHY used by the Exynos7870 SoC has a single USB 2.0 interface. Add its dedicated variant enum, compatible, and init/exit functions. The PHY enable bit of Exynos7870's PHY is different in contrast to that of Exynos850 and most Exynos PHYs. To allow this change, a simple if condition is added in exynos_usbdrd_phy_isol() which changes the bitmask. Since the variant enum is required, the function argument is changed to accept the driver data itself. Reviewed-by: Mattijs Korpershoek Signed-off-by: Kaustabh Chakraborty Signed-off-by: Minkyu Kang --- drivers/phy/phy-exynos-usbdrd.c | 246 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 238 insertions(+), 8 deletions(-) (limited to 'drivers/phy') diff --git a/drivers/phy/phy-exynos-usbdrd.c b/drivers/phy/phy-exynos-usbdrd.c index 4ab8da2a08f..c5eed29a2c8 100644 --- a/drivers/phy/phy-exynos-usbdrd.c +++ b/drivers/phy/phy-exynos-usbdrd.c @@ -21,6 +21,7 @@ /* Offset of PMU register controlling USB PHY output isolation */ #define EXYNOS_USBDRD_PHY_CONTROL 0x0704 #define EXYNOS_PHY_ENABLE BIT(0) +#define EXYNOS7870_PHY_ENABLE BIT(1) /* Exynos USB PHY registers */ #define EXYNOS5_FSEL_9MHZ6 0x0 @@ -32,6 +33,88 @@ #define EXYNOS5_FSEL_26MHZ 0x6 #define EXYNOS5_FSEL_50MHZ 0x7 +/* Exynos5: USB DRD PHY registers */ +#define EXYNOS5_DRD_LINKSYSTEM 0x04 +#define LINKSYSTEM_XHCI_VERSION_CONTROL BIT(27) +#define LINKSYSTEM_FORCE_VBUSVALID BIT(8) +#define LINKSYSTEM_FORCE_BVALID BIT(7) +#define LINKSYSTEM_FLADJ GENMASK(6, 1) + +#define EXYNOS5_DRD_PHYUTMI 0x08 +#define PHYUTMI_UTMI_SUSPEND_COM_N BIT(12) +#define PHYUTMI_UTMI_L1_SUSPEND_COM_N BIT(11) +#define PHYUTMI_VBUSVLDEXTSEL BIT(10) +#define PHYUTMI_VBUSVLDEXT BIT(9) +#define PHYUTMI_TXBITSTUFFENH BIT(8) +#define PHYUTMI_TXBITSTUFFEN BIT(7) +#define PHYUTMI_OTGDISABLE BIT(6) +#define PHYUTMI_IDPULLUP BIT(5) +#define PHYUTMI_DRVVBUS BIT(4) +#define PHYUTMI_DPPULLDOWN BIT(3) +#define PHYUTMI_DMPULLDOWN BIT(2) +#define PHYUTMI_FORCESUSPEND BIT(1) +#define PHYUTMI_FORCESLEEP BIT(0) + +#define EXYNOS5_DRD_PHYCLKRST 0x10 +#define PHYCLKRST_EN_UTMISUSPEND BIT(31) +#define PHYCLKRST_SSC_REFCLKSEL GENMASK(30, 23) +#define PHYCLKRST_SSC_RANGE GENMASK(22, 21) +#define PHYCLKRST_SSC_EN BIT(20) +#define PHYCLKRST_REF_SSP_EN BIT(19) +#define PHYCLKRST_REF_CLKDIV2 BIT(18) +#define PHYCLKRST_MPLL_MULTIPLIER GENMASK(17, 11) +#define PHYCLKRST_MPLL_MULTIPLIER_100MHZ_REF 0x19 +#define PHYCLKRST_MPLL_MULTIPLIER_50M_REF 0x32 +#define PHYCLKRST_MPLL_MULTIPLIER_24MHZ_REF 0x68 +#define PHYCLKRST_MPLL_MULTIPLIER_20MHZ_REF 0x7d +#define PHYCLKRST_MPLL_MULTIPLIER_19200KHZ_REF 0x02 +#define PHYCLKRST_FSEL_PIPE GENMASK(10, 8) +#define PHYCLKRST_FSEL_UTMI GENMASK(7, 5) +#define PHYCLKRST_FSEL_PAD_100MHZ 0x27 +#define PHYCLKRST_FSEL_PAD_24MHZ 0x2a +#define PHYCLKRST_FSEL_PAD_20MHZ 0x31 +#define PHYCLKRST_FSEL_PAD_19_2MHZ 0x38 +#define PHYCLKRST_RETENABLEN BIT(4) +#define PHYCLKRST_REFCLKSEL GENMASK(3, 2) +#define PHYCLKRST_REFCLKSEL_PAD_REFCLK 0x2 +#define PHYCLKRST_REFCLKSEL_EXT_REFCLK 0x3 +#define PHYCLKRST_PORTRESET BIT(1) +#define PHYCLKRST_COMMONONN BIT(0) + +#define EXYNOS5_DRD_PHYPARAM0 0x1c +#define PHYPARAM0_REF_USE_PAD BIT(31) +#define PHYPARAM0_REF_LOSLEVEL GENMASK(30, 26) +#define PHYPARAM0_REF_LOSLEVEL_VAL 0x9 +#define PHYPARAM0_TXVREFTUNE GENMASK(25, 22) +#define PHYPARAM0_TXRISETUNE GENMASK(21, 20) +#define PHYPARAM0_TXRESTUNE GENMASK(19, 18) +#define PHYPARAM0_TXPREEMPPULSETUNE BIT(17) +#define PHYPARAM0_TXPREEMPAMPTUNE GENMASK(16, 15) +#define PHYPARAM0_TXHSXVTUNE GENMASK(14, 13) +#define PHYPARAM0_TXFSLSTUNE GENMASK(12, 9) +#define PHYPARAM0_SQRXTUNE GENMASK(8, 6) +#define PHYPARAM0_OTGTUNE GENMASK(5, 3) +#define PHYPARAM0_COMPDISTUNE GENMASK(2, 0) + +#define EXYNOS5_DRD_LINKPORT 0x44 +#define LINKPORT_HOST_U3_PORT_DISABLE BIT(8) +#define LINKPORT_HOST_U2_PORT_DISABLE BIT(7) +#define LINKPORT_HOST_PORT_OVCR_U3 BIT(5) +#define LINKPORT_HOST_PORT_OVCR_U2 BIT(4) +#define LINKPORT_HOST_PORT_OVCR_U3_SEL BIT(3) +#define LINKPORT_HOST_PORT_OVCR_U2_SEL BIT(2) + +/* Exynos7870: USB DRD PHY registers */ +#define EXYNOS7870_DRD_HSPHYCTRL 0x54 +#define HSPHYCTRL_PHYSWRSTALL BIT(31) +#define HSPHYCTRL_SIDDQ BIT(6) +#define HSPHYCTRL_PHYSWRST BIT(0) + +#define EXYNOS7870_DRD_HSPHYPLLTUNE 0x70 +#define HSPHYPLLTUNE_PLL_B_TUNE BIT(6) +#define HSPHYPLLTUNE_PLL_I_TUNE GENMASK(5, 4) +#define HSPHYPLLTUNE_PLL_P_TUNE GENMASK(3, 0) + /* Exynos850: USB DRD PHY registers */ #define EXYNOS850_DRD_LINKCTRL 0x04 #define LINKCTRL_FORCE_QACT BIT(8) @@ -67,6 +150,7 @@ #define MHZ (KHZ * KHZ) enum exynos_usbdrd_phy_variant { + EXYNOS7870_USBDRD_PHY, EXYNOS850_USBDRD_PHY, }; @@ -88,16 +172,20 @@ struct exynos_usbdrd_phy { enum exynos_usbdrd_phy_variant variant; }; -static void exynos_usbdrd_phy_isol(struct regmap *reg_pmu, bool isolate) +static void exynos_usbdrd_phy_isol(struct exynos_usbdrd_phy *phy_drd, + bool isolate) { - unsigned int val; + unsigned int mask = EXYNOS_PHY_ENABLE, val; - if (!reg_pmu) + if (!phy_drd->reg_pmu) return; - val = isolate ? 0 : EXYNOS_PHY_ENABLE; - regmap_update_bits(reg_pmu, EXYNOS_USBDRD_PHY_CONTROL, - EXYNOS_PHY_ENABLE, val); + if (phy_drd->variant == EXYNOS7870_USBDRD_PHY) + mask = EXYNOS7870_PHY_ENABLE; + + val = isolate ? 0 : mask; + regmap_update_bits(phy_drd->reg_pmu, EXYNOS_USBDRD_PHY_CONTROL, + mask, val); } /* @@ -138,6 +226,111 @@ static unsigned int exynos_rate_to_clk(unsigned long rate, u32 *reg) return 0; } +static void exynos7870_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; + + reg = readl(regs_base + EXYNOS5_DRD_PHYCLKRST); + /* Use PADREFCLK as ref clock */ + reg &= ~PHYCLKRST_REFCLKSEL; + reg |= FIELD_PREP(PHYCLKRST_REFCLKSEL, PHYCLKRST_REFCLKSEL_PAD_REFCLK); + /* Select ref clock rate */ + reg &= ~PHYCLKRST_FSEL_UTMI; + reg &= ~PHYCLKRST_FSEL_PIPE; + reg |= FIELD_PREP(PHYCLKRST_FSEL_UTMI, phy_drd->extrefclk); + /* Enable suspend and reset the port */ + reg |= PHYCLKRST_EN_UTMISUSPEND; + reg |= PHYCLKRST_COMMONONN; + reg |= PHYCLKRST_PORTRESET; + writel(reg, regs_base + EXYNOS5_DRD_PHYCLKRST); + udelay(10); + + /* Clear the port reset bit */ + reg &= ~PHYCLKRST_PORTRESET; + writel(reg, regs_base + EXYNOS5_DRD_PHYCLKRST); + + /* Change PHY PLL tune value */ + reg = readl(regs_base + EXYNOS7870_DRD_HSPHYPLLTUNE); + if (phy_drd->extrefclk == EXYNOS5_FSEL_24MHZ) + reg |= HSPHYPLLTUNE_PLL_B_TUNE; + else + reg &= ~HSPHYPLLTUNE_PLL_B_TUNE; + reg &= ~HSPHYPLLTUNE_PLL_P_TUNE; + reg |= FIELD_PREP(HSPHYPLLTUNE_PLL_P_TUNE, 14); + writel(reg, regs_base + EXYNOS7870_DRD_HSPHYPLLTUNE); + + /* High-Speed PHY control */ + reg = readl(regs_base + EXYNOS7870_DRD_HSPHYCTRL); + reg &= ~HSPHYCTRL_SIDDQ; + reg &= ~HSPHYCTRL_PHYSWRST; + reg &= ~HSPHYCTRL_PHYSWRSTALL; + writel(reg, regs_base + EXYNOS7870_DRD_HSPHYCTRL); + udelay(500); + + reg = readl(regs_base + EXYNOS5_DRD_LINKSYSTEM); + /* + * Setting the Frame length Adj value[6:1] to default 0x20 + * See xHCI 1.0 spec, 5.2.4 + */ + reg |= LINKSYSTEM_XHCI_VERSION_CONTROL; + reg &= ~LINKSYSTEM_FLADJ; + reg |= FIELD_PREP(LINKSYSTEM_FLADJ, 0x20); + /* Set VBUSVALID signal as the VBUS pad is not used */ + reg |= LINKSYSTEM_FORCE_BVALID; + reg |= LINKSYSTEM_FORCE_VBUSVALID; + writel(reg, regs_base + EXYNOS5_DRD_LINKSYSTEM); + + reg = readl(regs_base + EXYNOS5_DRD_PHYUTMI); + /* Release force_sleep & force_suspend */ + reg &= ~PHYUTMI_FORCESLEEP; + reg &= ~PHYUTMI_FORCESUSPEND; + /* DP/DM pull down control */ + reg &= ~PHYUTMI_DMPULLDOWN; + reg &= ~PHYUTMI_DPPULLDOWN; + reg &= ~PHYUTMI_DRVVBUS; + /* Set DP-pull up as the VBUS pad is not used */ + reg |= PHYUTMI_VBUSVLDEXTSEL; + reg |= PHYUTMI_VBUSVLDEXT; + /* Disable OTG block and VBUS valid comparator */ + reg |= PHYUTMI_OTGDISABLE; + writel(reg, regs_base + EXYNOS5_DRD_PHYUTMI); + + /* Configure OVC IO usage */ + reg = readl(regs_base + EXYNOS5_DRD_LINKPORT); + reg |= LINKPORT_HOST_PORT_OVCR_U3_SEL | LINKPORT_HOST_PORT_OVCR_U2_SEL; + writel(reg, regs_base + EXYNOS5_DRD_LINKPORT); + + /* High-Speed PHY swrst */ + reg = readl(regs_base + EXYNOS7870_DRD_HSPHYCTRL); + reg |= HSPHYCTRL_PHYSWRST; + writel(reg, regs_base + EXYNOS7870_DRD_HSPHYCTRL); + udelay(20); + + /* Clear the PHY swrst bit */ + reg = readl(regs_base + EXYNOS7870_DRD_HSPHYCTRL); + reg &= ~HSPHYCTRL_PHYSWRST; + writel(reg, regs_base + EXYNOS7870_DRD_HSPHYCTRL); + + reg = readl(regs_base + EXYNOS5_DRD_PHYPARAM0); + reg &= ~(PHYPARAM0_TXVREFTUNE | PHYPARAM0_TXRISETUNE | + PHYPARAM0_TXRESTUNE | PHYPARAM0_TXPREEMPPULSETUNE | + PHYPARAM0_TXPREEMPAMPTUNE | PHYPARAM0_TXHSXVTUNE | + PHYPARAM0_TXFSLSTUNE | PHYPARAM0_SQRXTUNE | + PHYPARAM0_OTGTUNE | PHYPARAM0_COMPDISTUNE); + reg |= FIELD_PREP_CONST(PHYPARAM0_TXVREFTUNE, 14) | + FIELD_PREP_CONST(PHYPARAM0_TXRISETUNE, 1) | + FIELD_PREP_CONST(PHYPARAM0_TXRESTUNE, 3) | + FIELD_PREP_CONST(PHYPARAM0_TXPREEMPAMPTUNE, 0) | + FIELD_PREP_CONST(PHYPARAM0_TXHSXVTUNE, 0) | + FIELD_PREP_CONST(PHYPARAM0_TXFSLSTUNE, 3) | + FIELD_PREP_CONST(PHYPARAM0_SQRXTUNE, 6) | + FIELD_PREP_CONST(PHYPARAM0_OTGTUNE, 2) | + FIELD_PREP_CONST(PHYPARAM0_COMPDISTUNE, 3); + writel(reg, regs_base + EXYNOS5_DRD_PHYPARAM0); +} + static void exynos850_usbdrd_utmi_init(struct phy *phy) { struct exynos_usbdrd_phy *phy_drd = dev_get_priv(phy->dev); @@ -225,6 +418,33 @@ static void exynos850_usbdrd_utmi_init(struct phy *phy) writel(reg, regs_base + EXYNOS850_DRD_HSP); } +static void exynos7870_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; + + /* + * Disable the VBUS signal and the ID pull-up resistor. + * Enable force-suspend and force-sleep modes. + */ + reg = readl(regs_base + EXYNOS5_DRD_PHYUTMI); + reg &= ~(PHYUTMI_DRVVBUS | PHYUTMI_VBUSVLDEXT | PHYUTMI_VBUSVLDEXTSEL); + reg &= ~PHYUTMI_IDPULLUP; + reg |= PHYUTMI_FORCESUSPEND | PHYUTMI_FORCESLEEP; + writel(reg, regs_base + EXYNOS5_DRD_PHYUTMI); + + /* Power down PHY analog blocks */ + reg = readl(regs_base + EXYNOS7870_DRD_HSPHYCTRL); + reg |= HSPHYCTRL_SIDDQ; + writel(reg, regs_base + EXYNOS7870_DRD_HSPHYCTRL); + + /* Clear VBUSVALID signal as the VBUS pad is not used */ + reg = readl(regs_base + EXYNOS5_DRD_LINKSYSTEM); + reg &= ~(LINKSYSTEM_FORCE_BVALID | LINKSYSTEM_FORCE_VBUSVALID); + writel(reg, regs_base + EXYNOS5_DRD_LINKSYSTEM); +} + static void exynos850_usbdrd_utmi_exit(struct phy *phy) { struct exynos_usbdrd_phy *phy_drd = dev_get_priv(phy->dev); @@ -261,6 +481,9 @@ static int exynos_usbdrd_phy_init(struct phy *phy) return ret; switch (phy_drd->variant) { + case EXYNOS7870_USBDRD_PHY: + exynos7870_usbdrd_utmi_init(phy); + break; case EXYNOS850_USBDRD_PHY: exynos850_usbdrd_utmi_init(phy); break; @@ -283,6 +506,9 @@ static int exynos_usbdrd_phy_exit(struct phy *phy) return ret; switch (phy_drd->variant) { + case EXYNOS7870_USBDRD_PHY: + exynos7870_usbdrd_utmi_exit(phy); + break; case EXYNOS850_USBDRD_PHY: exynos850_usbdrd_utmi_exit(phy); break; @@ -307,7 +533,7 @@ static int exynos_usbdrd_phy_power_on(struct phy *phy) return ret; /* Power-on PHY */ - exynos_usbdrd_phy_isol(phy_drd->reg_pmu, false); + exynos_usbdrd_phy_isol(phy_drd, false); return 0; } @@ -319,7 +545,7 @@ static int exynos_usbdrd_phy_power_off(struct phy *phy) 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); + exynos_usbdrd_phy_isol(phy_drd, true); clk_disable_unprepare(phy_drd->core_clk); @@ -390,6 +616,10 @@ static const struct phy_ops exynos_usbdrd_phy_ops = { }; static const struct udevice_id exynos_usbdrd_phy_of_match[] = { + { + .compatible = "samsung,exynos7870-usbdrd-phy", + .data = EXYNOS7870_USBDRD_PHY, + }, { .compatible = "samsung,exynos850-usbdrd-phy", .data = EXYNOS850_USBDRD_PHY, -- cgit v1.3.1 From 25f142543196e5b5f0f9c916f15671dcd26eb2f7 Mon Sep 17 00:00:00 2001 From: Igor Belwon Date: Sat, 11 Oct 2025 21:10:04 +0200 Subject: phy: Add MediaTek UFS PHY Driver This UFS M-PHY driver can be used on recent MediaTek SoCs as the primary PHY for the UFS controller. Signed-off-by: Igor Belwon Link: https://patch.msgid.link/20251011-mtk-ufs-uboot-v1-1-a05f991ee150@mentallysanemainliners.org Signed-off-by: Neil Armstrong --- drivers/phy/Kconfig | 10 +++ drivers/phy/Makefile | 1 + drivers/phy/phy-mtk-ufs.c | 190 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 201 insertions(+) create mode 100644 drivers/phy/phy-mtk-ufs.c (limited to 'drivers/phy') diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 87729b479bd..09810b62b51 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -282,6 +282,16 @@ config PHY_MTK_TPHY multi-ports is first version, otherwise is second veriosn, so you can easily distinguish them by banks layout. +config PHY_MTK_UFS + tristate "MediaTek UFS M-PHY driver" + depends on ARCH_MEDIATEK + depends on PHY + help + Support for UFS M-PHY on MediaTek chipsets. + Enable this to provide vendor-specific probing, + initialization, power on and power off flow of + specified M-PHYs. + config PHY_NPCM_USB bool "Nuvoton NPCM USB PHY support" depends on PHY diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index 5a6df0ecfeb..83102349669 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -37,6 +37,7 @@ 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_MTK_UFS) += phy-mtk-ufs.o obj-$(CONFIG_PHY_NPCM_USB) += phy-npcm-usb.o obj-$(CONFIG_$(PHASE_)PHY_IMX8MQ_USB) += phy-imx8mq-usb.o obj-$(CONFIG_PHY_IMX8M_PCIE) += phy-imx8m-pcie.o diff --git a/drivers/phy/phy-mtk-ufs.c b/drivers/phy/phy-mtk-ufs.c new file mode 100644 index 00000000000..1eda3df858d --- /dev/null +++ b/drivers/phy/phy-mtk-ufs.c @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 MediaTek Inc. + * Author: Stanley Chu + * + * Copyright (c) 2025, Igor Belwon + */ + +#include "dm/ofnode.h" +#include "dm/read.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* mphy register and offsets */ +#define MP_GLB_DIG_8C 0x008C +#define FRC_PLL_ISO_EN BIT(8) +#define PLL_ISO_EN BIT(9) +#define FRC_FRC_PWR_ON BIT(10) +#define PLL_PWR_ON BIT(11) + +#define MP_LN_DIG_RX_9C 0xA09C +#define FSM_DIFZ_FRC BIT(18) + +#define MP_LN_DIG_RX_AC 0xA0AC +#define FRC_RX_SQ_EN BIT(0) +#define RX_SQ_EN BIT(1) + +#define MP_LN_RX_44 0xB044 +#define FRC_CDR_PWR_ON BIT(17) +#define CDR_PWR_ON BIT(18) +#define FRC_CDR_ISO_EN BIT(19) +#define CDR_ISO_EN BIT(20) + +#define UFSPHY_CLKS_CNT 2 + +struct mtk_ufs_phy { + struct udevice *dev; + void __iomem *mmio; + + struct clk *unipro_clk; + struct clk *mp_clk; +}; + +static void ufs_mtk_phy_set_active(struct mtk_ufs_phy *phy) +{ + /* release DA_MP_PLL_PWR_ON */ + setbits_le32(phy->mmio + MP_GLB_DIG_8C, PLL_PWR_ON); + clrbits_le32(phy->mmio + MP_GLB_DIG_8C, FRC_FRC_PWR_ON); + + /* release DA_MP_PLL_ISO_EN */ + clrbits_le32(phy->mmio + MP_GLB_DIG_8C, PLL_ISO_EN); + clrbits_le32(phy->mmio + MP_GLB_DIG_8C, FRC_PLL_ISO_EN); + + /* release DA_MP_CDR_PWR_ON */ + setbits_le32(phy->mmio + MP_LN_RX_44, CDR_PWR_ON); + clrbits_le32(phy->mmio + MP_LN_RX_44, FRC_CDR_PWR_ON); + + /* release DA_MP_CDR_ISO_EN */ + clrbits_le32(phy->mmio + MP_LN_RX_44, CDR_ISO_EN); + clrbits_le32(phy->mmio + MP_LN_RX_44, FRC_CDR_ISO_EN); + + /* release DA_MP_RX0_SQ_EN */ + setbits_le32(phy->mmio + MP_LN_DIG_RX_AC, RX_SQ_EN); + clrbits_le32(phy->mmio + MP_LN_DIG_RX_AC, FRC_RX_SQ_EN); + + /* delay 1us to wait DIFZ stable */ + udelay(1); + + /* release DIFZ */ + clrbits_le32(phy->mmio + MP_LN_DIG_RX_9C, FSM_DIFZ_FRC); +} + +static int mtk_phy_power_on(struct phy *phy) +{ + struct mtk_ufs_phy *ufs_phy = dev_get_priv(phy->dev); + int ret; + + ret = clk_enable(ufs_phy->mp_clk); + if (ret < 0) { + dev_err(phy->dev, "failed to enable mp_clk\n"); + return ret; + } + + ret = clk_enable(ufs_phy->unipro_clk); + if (ret < 0) { + dev_err(phy->dev, "failed to enable unipro_clk %d\n", ret); + clk_disable(ufs_phy->unipro_clk); + return ret; + } + + ufs_mtk_phy_set_active(ufs_phy); + + return 0; +} + +static int mtk_phy_power_off(struct phy *phy) +{ + struct mtk_ufs_phy *ufs_phy = dev_get_priv(phy->dev); + + /* Set PHY to Deep Hibernate mode */ + setbits_le32(ufs_phy->mmio + MP_LN_DIG_RX_9C, FSM_DIFZ_FRC); + + /* force DA_MP_RX0_SQ_EN */ + setbits_le32(ufs_phy->mmio + MP_LN_DIG_RX_AC, FRC_RX_SQ_EN); + clrbits_le32(ufs_phy->mmio + MP_LN_DIG_RX_AC, RX_SQ_EN); + + /* force DA_MP_CDR_ISO_EN */ + setbits_le32(ufs_phy->mmio + MP_LN_RX_44, FRC_CDR_ISO_EN); + setbits_le32(ufs_phy->mmio + MP_LN_RX_44, CDR_ISO_EN); + + /* force DA_MP_CDR_PWR_ON */ + setbits_le32(ufs_phy->mmio + MP_LN_RX_44, FRC_CDR_PWR_ON); + clrbits_le32(ufs_phy->mmio + MP_LN_RX_44, CDR_PWR_ON); + + /* force DA_MP_PLL_ISO_EN */ + setbits_le32(ufs_phy->mmio + MP_GLB_DIG_8C, FRC_PLL_ISO_EN); + setbits_le32(ufs_phy->mmio + MP_GLB_DIG_8C, PLL_ISO_EN); + + /* force DA_MP_PLL_PWR_ON */ + setbits_le32(ufs_phy->mmio + MP_GLB_DIG_8C, FRC_FRC_PWR_ON); + clrbits_le32(ufs_phy->mmio + MP_GLB_DIG_8C, PLL_PWR_ON); + + return 0; +} + +static const struct phy_ops mtk_ufs_phy_ops = { + .power_on = mtk_phy_power_on, + .power_off = mtk_phy_power_off, +}; + +static int mtk_ufs_phy_probe(struct udevice *dev) +{ + struct mtk_ufs_phy *phy = dev_get_priv(dev); + fdt_addr_t addr; + int ret; + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) + return -ENOMEM; + + addr = dev_read_addr(dev); + if (addr == FDT_ADDR_T_NONE) + return -ENOMEM; + + phy->dev = dev; + phy->mmio = map_sysmem(addr, 0); + + phy->mp_clk = devm_clk_get(dev, "mp"); + if (IS_ERR(phy->mp_clk)) { + ret = PTR_ERR(phy->mp_clk); + dev_err(dev, "Failed to get mp clock (ret=%d)\n", ret); + return ret; + } + + phy->unipro_clk = devm_clk_get(dev, "unipro"); + if (IS_ERR(phy->unipro_clk)) { + ret = PTR_ERR(phy->unipro_clk); + dev_err(dev, "Failed to get unipro clock (ret=%d)\n", ret); + return ret; + } + + return 0; +} + +static const struct udevice_id mtk_ufs_phy_id_table[] = { + {.compatible = "mediatek,mt8183-ufsphy"}, + {}, +}; + +U_BOOT_DRIVER(mtk_ufs_phy) = { + .name = "mtk-ufs_phy", + .id = UCLASS_PHY, + .of_match = mtk_ufs_phy_id_table, + .ops = &mtk_ufs_phy_ops, + .probe = mtk_ufs_phy_probe, + .priv_auto = sizeof(struct mtk_ufs_phy), +}; -- cgit v1.3.1