Miquel Raynal | cc8b7a0 | 2019-01-29 10:36:30 +0100 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | /* |
| 3 | * Copyright (C) 2018 Marvell |
| 4 | * |
| 5 | * Authors: |
| 6 | * Igal Liberman <igall@marvell.com> |
| 7 | * Miquèl Raynal <miquel.raynal@bootlin.com> |
| 8 | * |
| 9 | * Marvell A3700 UTMI PHY driver |
| 10 | */ |
| 11 | |
| 12 | #include <linux/io.h> |
| 13 | #include <linux/iopoll.h> |
| 14 | #include <linux/mfd/syscon.h> |
| 15 | #include <linux/module.h> |
| 16 | #include <linux/of_device.h> |
| 17 | #include <linux/phy/phy.h> |
| 18 | #include <linux/platform_device.h> |
| 19 | #include <linux/regmap.h> |
| 20 | |
| 21 | /* Armada 3700 UTMI PHY registers */ |
| 22 | #define USB2_PHY_PLL_CTRL_REG0 0x0 |
| 23 | #define PLL_REF_DIV_OFF 0 |
| 24 | #define PLL_REF_DIV_MASK GENMASK(6, 0) |
| 25 | #define PLL_REF_DIV_5 5 |
| 26 | #define PLL_FB_DIV_OFF 16 |
| 27 | #define PLL_FB_DIV_MASK GENMASK(24, 16) |
| 28 | #define PLL_FB_DIV_96 96 |
| 29 | #define PLL_SEL_LPFR_OFF 28 |
| 30 | #define PLL_SEL_LPFR_MASK GENMASK(29, 28) |
| 31 | #define PLL_READY BIT(31) |
| 32 | #define USB2_PHY_CAL_CTRL 0x8 |
| 33 | #define PHY_PLLCAL_DONE BIT(31) |
| 34 | #define PHY_IMPCAL_DONE BIT(23) |
| 35 | #define USB2_RX_CHAN_CTRL1 0x18 |
| 36 | #define USB2PHY_SQCAL_DONE BIT(31) |
| 37 | #define USB2_PHY_OTG_CTRL 0x34 |
| 38 | #define PHY_PU_OTG BIT(4) |
| 39 | #define USB2_PHY_CHRGR_DETECT 0x38 |
| 40 | #define PHY_CDP_EN BIT(2) |
| 41 | #define PHY_DCP_EN BIT(3) |
| 42 | #define PHY_PD_EN BIT(4) |
| 43 | #define PHY_PU_CHRG_DTC BIT(5) |
| 44 | #define PHY_CDP_DM_AUTO BIT(7) |
| 45 | #define PHY_ENSWITCH_DP BIT(12) |
| 46 | #define PHY_ENSWITCH_DM BIT(13) |
| 47 | |
| 48 | /* Armada 3700 USB miscellaneous registers */ |
| 49 | #define USB2_PHY_CTRL(usb32) (usb32 ? 0x20 : 0x4) |
| 50 | #define RB_USB2PHY_PU BIT(0) |
| 51 | #define USB2_DP_PULLDN_DEV_MODE BIT(5) |
| 52 | #define USB2_DM_PULLDN_DEV_MODE BIT(6) |
| 53 | #define RB_USB2PHY_SUSPM(usb32) (usb32 ? BIT(14) : BIT(7)) |
| 54 | |
| 55 | #define PLL_LOCK_DELAY_US 10000 |
| 56 | #define PLL_LOCK_TIMEOUT_US 1000000 |
| 57 | |
| 58 | /** |
| 59 | * struct mvebu_a3700_utmi_caps - PHY capabilities |
| 60 | * |
| 61 | * @usb32: Flag indicating which PHY is in use (impacts the register map): |
| 62 | * - The UTMI PHY wired to the USB3/USB2 controller (otg) |
| 63 | * - The UTMI PHY wired to the USB2 controller (host only) |
| 64 | * @ops: PHY operations |
| 65 | */ |
| 66 | struct mvebu_a3700_utmi_caps { |
| 67 | int usb32; |
| 68 | const struct phy_ops *ops; |
| 69 | }; |
| 70 | |
| 71 | /** |
| 72 | * struct mvebu_a3700_utmi - PHY driver data |
| 73 | * |
| 74 | * @regs: PHY registers |
| 75 | * @usb_mis: Regmap with USB miscellaneous registers including PHY ones |
| 76 | * @caps: PHY capabilities |
| 77 | * @phy: PHY handle |
| 78 | */ |
| 79 | struct mvebu_a3700_utmi { |
| 80 | void __iomem *regs; |
| 81 | struct regmap *usb_misc; |
| 82 | const struct mvebu_a3700_utmi_caps *caps; |
| 83 | struct phy *phy; |
| 84 | }; |
| 85 | |
| 86 | static int mvebu_a3700_utmi_phy_power_on(struct phy *phy) |
| 87 | { |
| 88 | struct mvebu_a3700_utmi *utmi = phy_get_drvdata(phy); |
| 89 | struct device *dev = &phy->dev; |
| 90 | int usb32 = utmi->caps->usb32; |
| 91 | int ret = 0; |
| 92 | u32 reg; |
| 93 | |
| 94 | /* |
| 95 | * Setup PLL. 40MHz clock used to be the default, being 25MHz now. |
| 96 | * See "PLL Settings for Typical REFCLK" table. |
| 97 | */ |
| 98 | reg = readl(utmi->regs + USB2_PHY_PLL_CTRL_REG0); |
| 99 | reg &= ~(PLL_REF_DIV_MASK | PLL_FB_DIV_MASK | PLL_SEL_LPFR_MASK); |
| 100 | reg |= (PLL_REF_DIV_5 << PLL_REF_DIV_OFF) | |
| 101 | (PLL_FB_DIV_96 << PLL_FB_DIV_OFF); |
| 102 | writel(reg, utmi->regs + USB2_PHY_PLL_CTRL_REG0); |
| 103 | |
| 104 | /* Enable PHY pull up and disable USB2 suspend */ |
| 105 | regmap_update_bits(utmi->usb_misc, USB2_PHY_CTRL(usb32), |
| 106 | RB_USB2PHY_SUSPM(usb32) | RB_USB2PHY_PU, |
| 107 | RB_USB2PHY_SUSPM(usb32) | RB_USB2PHY_PU); |
| 108 | |
| 109 | if (usb32) { |
| 110 | /* Power up OTG module */ |
| 111 | reg = readl(utmi->regs + USB2_PHY_OTG_CTRL); |
| 112 | reg |= PHY_PU_OTG; |
| 113 | writel(reg, utmi->regs + USB2_PHY_OTG_CTRL); |
| 114 | |
| 115 | /* Disable PHY charger detection */ |
| 116 | reg = readl(utmi->regs + USB2_PHY_CHRGR_DETECT); |
| 117 | reg &= ~(PHY_CDP_EN | PHY_DCP_EN | PHY_PD_EN | PHY_PU_CHRG_DTC | |
| 118 | PHY_CDP_DM_AUTO | PHY_ENSWITCH_DP | PHY_ENSWITCH_DM); |
| 119 | writel(reg, utmi->regs + USB2_PHY_CHRGR_DETECT); |
| 120 | |
| 121 | /* Disable PHY DP/DM pull-down (used for device mode) */ |
| 122 | regmap_update_bits(utmi->usb_misc, USB2_PHY_CTRL(usb32), |
| 123 | USB2_DP_PULLDN_DEV_MODE | |
| 124 | USB2_DM_PULLDN_DEV_MODE, 0); |
| 125 | } |
| 126 | |
| 127 | /* Wait for PLL calibration */ |
| 128 | ret = readl_poll_timeout(utmi->regs + USB2_PHY_CAL_CTRL, reg, |
| 129 | reg & PHY_PLLCAL_DONE, |
| 130 | PLL_LOCK_DELAY_US, PLL_LOCK_TIMEOUT_US); |
| 131 | if (ret) { |
| 132 | dev_err(dev, "Failed to end USB2 PLL calibration\n"); |
| 133 | return ret; |
| 134 | } |
| 135 | |
| 136 | /* Wait for impedance calibration */ |
| 137 | ret = readl_poll_timeout(utmi->regs + USB2_PHY_CAL_CTRL, reg, |
| 138 | reg & PHY_IMPCAL_DONE, |
| 139 | PLL_LOCK_DELAY_US, PLL_LOCK_TIMEOUT_US); |
| 140 | if (ret) { |
| 141 | dev_err(dev, "Failed to end USB2 impedance calibration\n"); |
| 142 | return ret; |
| 143 | } |
| 144 | |
| 145 | /* Wait for squelch calibration */ |
| 146 | ret = readl_poll_timeout(utmi->regs + USB2_RX_CHAN_CTRL1, reg, |
| 147 | reg & USB2PHY_SQCAL_DONE, |
| 148 | PLL_LOCK_DELAY_US, PLL_LOCK_TIMEOUT_US); |
| 149 | if (ret) { |
| 150 | dev_err(dev, "Failed to end USB2 unknown calibration\n"); |
| 151 | return ret; |
| 152 | } |
| 153 | |
| 154 | /* Wait for PLL to be locked */ |
| 155 | ret = readl_poll_timeout(utmi->regs + USB2_PHY_PLL_CTRL_REG0, reg, |
| 156 | reg & PLL_READY, |
| 157 | PLL_LOCK_DELAY_US, PLL_LOCK_TIMEOUT_US); |
| 158 | if (ret) |
| 159 | dev_err(dev, "Failed to lock USB2 PLL\n"); |
| 160 | |
| 161 | return ret; |
| 162 | } |
| 163 | |
| 164 | static int mvebu_a3700_utmi_phy_power_off(struct phy *phy) |
| 165 | { |
| 166 | struct mvebu_a3700_utmi *utmi = phy_get_drvdata(phy); |
| 167 | int usb32 = utmi->caps->usb32; |
| 168 | u32 reg; |
| 169 | |
| 170 | /* Disable PHY pull-up and enable USB2 suspend */ |
| 171 | reg = readl(utmi->regs + USB2_PHY_CTRL(usb32)); |
| 172 | reg &= ~(RB_USB2PHY_PU | RB_USB2PHY_SUSPM(usb32)); |
| 173 | writel(reg, utmi->regs + USB2_PHY_CTRL(usb32)); |
| 174 | |
| 175 | /* Power down OTG module */ |
| 176 | if (usb32) { |
| 177 | reg = readl(utmi->regs + USB2_PHY_OTG_CTRL); |
| 178 | reg &= ~PHY_PU_OTG; |
| 179 | writel(reg, utmi->regs + USB2_PHY_OTG_CTRL); |
| 180 | } |
| 181 | |
| 182 | return 0; |
| 183 | } |
| 184 | |
| 185 | static const struct phy_ops mvebu_a3700_utmi_phy_ops = { |
| 186 | .power_on = mvebu_a3700_utmi_phy_power_on, |
| 187 | .power_off = mvebu_a3700_utmi_phy_power_off, |
| 188 | .owner = THIS_MODULE, |
| 189 | }; |
| 190 | |
| 191 | static const struct mvebu_a3700_utmi_caps mvebu_a3700_utmi_otg_phy_caps = { |
| 192 | .usb32 = true, |
| 193 | .ops = &mvebu_a3700_utmi_phy_ops, |
| 194 | }; |
| 195 | |
| 196 | static const struct mvebu_a3700_utmi_caps mvebu_a3700_utmi_host_phy_caps = { |
| 197 | .usb32 = false, |
| 198 | .ops = &mvebu_a3700_utmi_phy_ops, |
| 199 | }; |
| 200 | |
| 201 | static const struct of_device_id mvebu_a3700_utmi_of_match[] = { |
| 202 | { |
| 203 | .compatible = "marvell,a3700-utmi-otg-phy", |
| 204 | .data = &mvebu_a3700_utmi_otg_phy_caps, |
| 205 | }, |
| 206 | { |
| 207 | .compatible = "marvell,a3700-utmi-host-phy", |
| 208 | .data = &mvebu_a3700_utmi_host_phy_caps, |
| 209 | }, |
| 210 | {}, |
| 211 | }; |
| 212 | MODULE_DEVICE_TABLE(of, mvebu_a3700_utmi_of_match); |
| 213 | |
| 214 | static int mvebu_a3700_utmi_phy_probe(struct platform_device *pdev) |
| 215 | { |
| 216 | struct device *dev = &pdev->dev; |
| 217 | struct mvebu_a3700_utmi *utmi; |
| 218 | struct phy_provider *provider; |
Miquel Raynal | cc8b7a0 | 2019-01-29 10:36:30 +0100 | [diff] [blame] | 219 | |
| 220 | utmi = devm_kzalloc(dev, sizeof(*utmi), GFP_KERNEL); |
| 221 | if (!utmi) |
| 222 | return -ENOMEM; |
| 223 | |
| 224 | /* Get UTMI memory region */ |
Markus Elfring | 265938b | 2019-09-26 18:15:23 +0200 | [diff] [blame^] | 225 | utmi->regs = devm_platform_ioremap_resource(pdev, 0); |
Miquel Raynal | cc8b7a0 | 2019-01-29 10:36:30 +0100 | [diff] [blame] | 226 | if (IS_ERR(utmi->regs)) |
| 227 | return PTR_ERR(utmi->regs); |
| 228 | |
| 229 | /* Get miscellaneous Host/PHY region */ |
| 230 | utmi->usb_misc = syscon_regmap_lookup_by_phandle(dev->of_node, |
| 231 | "marvell,usb-misc-reg"); |
| 232 | if (IS_ERR(utmi->usb_misc)) { |
| 233 | dev_err(dev, |
| 234 | "Missing USB misc purpose system controller\n"); |
| 235 | return PTR_ERR(utmi->usb_misc); |
| 236 | } |
| 237 | |
| 238 | /* Retrieve PHY capabilities */ |
| 239 | utmi->caps = of_device_get_match_data(dev); |
| 240 | |
| 241 | /* Instantiate the PHY */ |
| 242 | utmi->phy = devm_phy_create(dev, NULL, utmi->caps->ops); |
| 243 | if (IS_ERR(utmi->phy)) { |
| 244 | dev_err(dev, "Failed to create the UTMI PHY\n"); |
| 245 | return PTR_ERR(utmi->phy); |
| 246 | } |
| 247 | |
| 248 | phy_set_drvdata(utmi->phy, utmi); |
| 249 | |
| 250 | /* Ensure the PHY is powered off */ |
| 251 | utmi->caps->ops->power_off(utmi->phy); |
| 252 | |
| 253 | provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); |
| 254 | |
| 255 | return PTR_ERR_OR_ZERO(provider); |
| 256 | } |
| 257 | |
| 258 | static struct platform_driver mvebu_a3700_utmi_driver = { |
| 259 | .probe = mvebu_a3700_utmi_phy_probe, |
| 260 | .driver = { |
| 261 | .name = "mvebu-a3700-utmi-phy", |
Miquel Raynal | cc8b7a0 | 2019-01-29 10:36:30 +0100 | [diff] [blame] | 262 | .of_match_table = mvebu_a3700_utmi_of_match, |
| 263 | }, |
| 264 | }; |
| 265 | module_platform_driver(mvebu_a3700_utmi_driver); |
| 266 | |
| 267 | MODULE_AUTHOR("Igal Liberman <igall@marvell.com>"); |
| 268 | MODULE_AUTHOR("Miquel Raynal <miquel.raynal@bootlin.com>"); |
| 269 | MODULE_DESCRIPTION("Marvell EBU A3700 UTMI PHY driver"); |
| 270 | MODULE_LICENSE("GPL v2"); |