summaryrefslogtreecommitdiff
path: root/drivers/phy
diff options
context:
space:
mode:
authorTom Rini <[email protected]>2026-03-09 15:26:34 -0600
committerTom Rini <[email protected]>2026-03-09 15:26:34 -0600
commit1e240f7206fccde4ec73ea432ab8360d321c5fe5 (patch)
tree931d5985e8a237b5604999922f5662fc464a8817 /drivers/phy
parent36add050eea439f0b2a15e4ea0d3a8e21216f159 (diff)
parentba7bf918dafcd093ad733b07ba490baeb20cf5da (diff)
Merge tag 'v2026.04-rc4' into next
Prepare v2026.04-rc4
Diffstat (limited to 'drivers/phy')
-rw-r--r--drivers/phy/Kconfig10
-rw-r--r--drivers/phy/Makefile1
-rw-r--r--drivers/phy/phy-exynos-usbdrd.c271
-rw-r--r--drivers/phy/phy-mtk-ufs.c190
4 files changed, 462 insertions, 10 deletions
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-exynos-usbdrd.c b/drivers/phy/phy-exynos-usbdrd.c
index db5815ed184..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)
@@ -66,6 +149,11 @@
#define KHZ 1000
#define MHZ (KHZ * KHZ)
+enum exynos_usbdrd_phy_variant {
+ EXYNOS7870_USBDRD_PHY,
+ EXYNOS850_USBDRD_PHY,
+};
+
/**
* struct exynos_usbdrd_phy - driver data for Exynos USB PHY
* @reg_phy: USB PHY controller register memory base
@@ -73,6 +161,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,18 +169,23 @@ 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)
+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);
}
/*
@@ -132,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);
@@ -219,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);
@@ -254,7 +480,16 @@ static int exynos_usbdrd_phy_init(struct phy *phy)
if (ret)
return ret;
- exynos850_usbdrd_utmi_init(phy);
+ switch (phy_drd->variant) {
+ case EXYNOS7870_USBDRD_PHY:
+ exynos7870_usbdrd_utmi_init(phy);
+ break;
+ 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 +505,16 @@ static int exynos_usbdrd_phy_exit(struct phy *phy)
if (ret)
return ret;
- exynos850_usbdrd_utmi_exit(phy);
+ switch (phy_drd->variant) {
+ case EXYNOS7870_USBDRD_PHY:
+ exynos7870_usbdrd_utmi_exit(phy);
+ break;
+ 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);
@@ -289,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;
}
@@ -301,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);
@@ -359,6 +603,8 @@ static int exynos_usbdrd_phy_probe(struct udevice *dev)
return err;
}
+ phy_drd->variant = dev_get_driver_data(dev);
+
return 0;
}
@@ -371,7 +617,12 @@ 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,
},
{ }
};
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 <[email protected]>
+ *
+ * Copyright (c) 2025, Igor Belwon <[email protected]>
+ */
+
+#include "dm/ofnode.h"
+#include "dm/read.h"
+#include <clk.h>
+#include <dm.h>
+#include <generic-phy.h>
+#include <malloc.h>
+#include <mapmem.h>
+#include <regmap.h>
+#include <syscon.h>
+#include <asm/io.h>
+#include <dm/device_compat.h>
+#include <dm/devres.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+
+#include <dt-bindings/phy/phy.h>
+
+/* 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),
+};