diff options
Diffstat (limited to 'drivers/phy/samsung/phy-exynos5-usbdrd.c')
-rw-r--r-- | drivers/phy/samsung/phy-exynos5-usbdrd.c | 184 |
1 files changed, 180 insertions, 4 deletions
diff --git a/drivers/phy/samsung/phy-exynos5-usbdrd.c b/drivers/phy/samsung/phy-exynos5-usbdrd.c index ee0848fe8432..3f310b28bfff 100644 --- a/drivers/phy/samsung/phy-exynos5-usbdrd.c +++ b/drivers/phy/samsung/phy-exynos5-usbdrd.c @@ -14,8 +14,6 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/of.h> -#include <linux/of_address.h> -#include <linux/of_device.h> #include <linux/iopoll.h> #include <linux/phy/phy.h> #include <linux/platform_device.h> @@ -32,6 +30,7 @@ #define EXYNOS5_FSEL_19MHZ2 0x3 #define EXYNOS5_FSEL_20MHZ 0x4 #define EXYNOS5_FSEL_24MHZ 0x5 +#define EXYNOS5_FSEL_26MHZ 0x82 #define EXYNOS5_FSEL_50MHZ 0x7 /* Exynos5: USB 3.0 DRD PHY registers */ @@ -146,6 +145,34 @@ #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) @@ -167,6 +194,7 @@ struct exynos5_usbdrd_phy_config { struct exynos5_usbdrd_phy_drvdata { const struct exynos5_usbdrd_phy_config *phy_cfg; + const struct phy_ops *phy_ops; u32 pmu_offset_usbdrd0_phy; u32 pmu_offset_usbdrd1_phy; bool has_common_clk_gate; @@ -245,6 +273,9 @@ static unsigned int exynos5_rate_to_clk(unsigned long rate, u32 *reg) case 24 * MHZ: *reg = EXYNOS5_FSEL_24MHZ; break; + case 26 * MHZ: + *reg = EXYNOS5_FSEL_26MHZ; + break; case 50 * MHZ: *reg = EXYNOS5_FSEL_50MHZ; break; @@ -713,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; @@ -779,8 +933,17 @@ 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, .pmu_offset_usbdrd0_phy = EXYNOS5_USBDRD_PHY_CONTROL, .pmu_offset_usbdrd1_phy = EXYNOS5420_USBDRD1_PHY_CONTROL, .has_common_clk_gate = true, @@ -788,12 +951,14 @@ static const struct exynos5_usbdrd_phy_drvdata exynos5420_usbdrd_phy = { static const struct exynos5_usbdrd_phy_drvdata exynos5250_usbdrd_phy = { .phy_cfg = phy_cfg_exynos5, + .phy_ops = &exynos5_usbdrd_phy_ops, .pmu_offset_usbdrd0_phy = EXYNOS5_USBDRD_PHY_CONTROL, .has_common_clk_gate = true, }; static const struct exynos5_usbdrd_phy_drvdata exynos5433_usbdrd_phy = { .phy_cfg = phy_cfg_exynos5, + .phy_ops = &exynos5_usbdrd_phy_ops, .pmu_offset_usbdrd0_phy = EXYNOS5_USBDRD_PHY_CONTROL, .pmu_offset_usbdrd1_phy = EXYNOS5433_USBHOST30_PHY_CONTROL, .has_common_clk_gate = false, @@ -801,10 +966,18 @@ static const struct exynos5_usbdrd_phy_drvdata exynos5433_usbdrd_phy = { static const struct exynos5_usbdrd_phy_drvdata exynos7_usbdrd_phy = { .phy_cfg = phy_cfg_exynos5, + .phy_ops = &exynos5_usbdrd_phy_ops, .pmu_offset_usbdrd0_phy = EXYNOS5_USBDRD_PHY_CONTROL, .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", @@ -818,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 }, { }, }; @@ -908,8 +1084,8 @@ static int exynos5_usbdrd_phy_probe(struct platform_device *pdev) dev_vdbg(dev, "Creating usbdrd_phy phy\n"); for (i = 0; i < EXYNOS5_DRDPHYS_NUM; i++) { - struct phy *phy = devm_phy_create(dev, NULL, - &exynos5_usbdrd_phy_ops); + struct phy *phy = devm_phy_create(dev, NULL, drv_data->phy_ops); + if (IS_ERR(phy)) { dev_err(dev, "Failed to create usbdrd_phy phy\n"); return PTR_ERR(phy); |