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. Signed-off-by: Kaustabh Chakraborty <[email protected]> --- drivers/phy/phy-exynos-usbdrd.c | 246 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 238 insertions(+), 8 deletions(-) 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, -- 2.52.0

