Yu Xu | a67e76a | 2012-08-09 22:29:31 +0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2011 Marvell International Ltd. All rights reserved. |
| 3 | * |
| 4 | * This program is free software; you can redistribute it and/or modify it |
| 5 | * under the terms and conditions of the GNU General Public License, |
| 6 | * version 2, as published by the Free Software Foundation. |
| 7 | */ |
| 8 | |
| 9 | #include <linux/module.h> |
| 10 | #include <linux/platform_device.h> |
| 11 | #include <linux/clk.h> |
| 12 | #include <linux/delay.h> |
| 13 | #include <linux/err.h> |
| 14 | #include <linux/io.h> |
| 15 | #include <linux/usb/otg.h> |
| 16 | #include <linux/platform_data/mv_usb.h> |
| 17 | |
Felipe Balbi | 94ae984 | 2013-03-07 17:37:59 +0200 | [diff] [blame] | 18 | #include "phy-mv-u3d-usb.h" |
Yu Xu | a67e76a | 2012-08-09 22:29:31 +0800 | [diff] [blame] | 19 | |
| 20 | /* |
| 21 | * struct mv_u3d_phy - transceiver driver state |
| 22 | * @phy: transceiver structure |
| 23 | * @dev: The parent device supplied to the probe function |
| 24 | * @clk: usb phy clock |
| 25 | * @base: usb phy register memory base |
| 26 | */ |
| 27 | struct mv_u3d_phy { |
| 28 | struct usb_phy phy; |
| 29 | struct mv_usb_platform_data *plat; |
| 30 | struct device *dev; |
| 31 | struct clk *clk; |
| 32 | void __iomem *base; |
| 33 | }; |
| 34 | |
| 35 | static u32 mv_u3d_phy_read(void __iomem *base, u32 reg) |
| 36 | { |
| 37 | void __iomem *addr, *data; |
| 38 | |
| 39 | addr = base; |
| 40 | data = base + 0x4; |
| 41 | |
| 42 | writel_relaxed(reg, addr); |
| 43 | return readl_relaxed(data); |
| 44 | } |
| 45 | |
| 46 | static void mv_u3d_phy_set(void __iomem *base, u32 reg, u32 value) |
| 47 | { |
| 48 | void __iomem *addr, *data; |
| 49 | u32 tmp; |
| 50 | |
| 51 | addr = base; |
| 52 | data = base + 0x4; |
| 53 | |
| 54 | writel_relaxed(reg, addr); |
| 55 | tmp = readl_relaxed(data); |
| 56 | tmp |= value; |
| 57 | writel_relaxed(tmp, data); |
| 58 | } |
| 59 | |
| 60 | static void mv_u3d_phy_clear(void __iomem *base, u32 reg, u32 value) |
| 61 | { |
| 62 | void __iomem *addr, *data; |
| 63 | u32 tmp; |
| 64 | |
| 65 | addr = base; |
| 66 | data = base + 0x4; |
| 67 | |
| 68 | writel_relaxed(reg, addr); |
| 69 | tmp = readl_relaxed(data); |
| 70 | tmp &= ~value; |
| 71 | writel_relaxed(tmp, data); |
| 72 | } |
| 73 | |
| 74 | static void mv_u3d_phy_write(void __iomem *base, u32 reg, u32 value) |
| 75 | { |
| 76 | void __iomem *addr, *data; |
| 77 | |
| 78 | addr = base; |
| 79 | data = base + 0x4; |
| 80 | |
| 81 | writel_relaxed(reg, addr); |
| 82 | writel_relaxed(value, data); |
| 83 | } |
| 84 | |
Jingoo Han | 1e0f20b | 2013-08-05 14:17:11 +0900 | [diff] [blame] | 85 | static void mv_u3d_phy_shutdown(struct usb_phy *phy) |
Yu Xu | a67e76a | 2012-08-09 22:29:31 +0800 | [diff] [blame] | 86 | { |
| 87 | struct mv_u3d_phy *mv_u3d_phy; |
| 88 | void __iomem *base; |
| 89 | u32 val; |
| 90 | |
| 91 | mv_u3d_phy = container_of(phy, struct mv_u3d_phy, phy); |
| 92 | base = mv_u3d_phy->base; |
| 93 | |
| 94 | /* Power down Reference Analog current, bit 15 |
| 95 | * Power down PLL, bit 14 |
| 96 | * Power down Receiver, bit 13 |
| 97 | * Power down Transmitter, bit 12 |
| 98 | * of USB3_POWER_PLL_CONTROL register |
| 99 | */ |
| 100 | val = mv_u3d_phy_read(base, USB3_POWER_PLL_CONTROL); |
| 101 | val &= ~(USB3_POWER_PLL_CONTROL_PU); |
| 102 | mv_u3d_phy_write(base, USB3_POWER_PLL_CONTROL, val); |
| 103 | |
| 104 | if (mv_u3d_phy->clk) |
| 105 | clk_disable(mv_u3d_phy->clk); |
| 106 | } |
| 107 | |
| 108 | static int mv_u3d_phy_init(struct usb_phy *phy) |
| 109 | { |
| 110 | struct mv_u3d_phy *mv_u3d_phy; |
| 111 | void __iomem *base; |
| 112 | u32 val, count; |
| 113 | |
| 114 | /* enable usb3 phy */ |
| 115 | mv_u3d_phy = container_of(phy, struct mv_u3d_phy, phy); |
| 116 | |
| 117 | if (mv_u3d_phy->clk) |
| 118 | clk_enable(mv_u3d_phy->clk); |
| 119 | |
| 120 | base = mv_u3d_phy->base; |
| 121 | |
| 122 | val = mv_u3d_phy_read(base, USB3_POWER_PLL_CONTROL); |
| 123 | val &= ~(USB3_POWER_PLL_CONTROL_PU_MASK); |
| 124 | val |= 0xF << USB3_POWER_PLL_CONTROL_PU_SHIFT; |
| 125 | mv_u3d_phy_write(base, USB3_POWER_PLL_CONTROL, val); |
| 126 | udelay(100); |
| 127 | |
| 128 | mv_u3d_phy_write(base, USB3_RESET_CONTROL, |
| 129 | USB3_RESET_CONTROL_RESET_PIPE); |
| 130 | udelay(100); |
| 131 | |
| 132 | mv_u3d_phy_write(base, USB3_RESET_CONTROL, |
| 133 | USB3_RESET_CONTROL_RESET_PIPE |
| 134 | | USB3_RESET_CONTROL_RESET_PHY); |
| 135 | udelay(100); |
| 136 | |
| 137 | val = mv_u3d_phy_read(base, USB3_POWER_PLL_CONTROL); |
| 138 | val &= ~(USB3_POWER_PLL_CONTROL_REF_FREF_SEL_MASK |
| 139 | | USB3_POWER_PLL_CONTROL_PHY_MODE_MASK); |
| 140 | val |= (USB3_PLL_25MHZ << USB3_POWER_PLL_CONTROL_REF_FREF_SEL_SHIFT) |
| 141 | | (0x5 << USB3_POWER_PLL_CONTROL_PHY_MODE_SHIFT); |
| 142 | mv_u3d_phy_write(base, USB3_POWER_PLL_CONTROL, val); |
| 143 | udelay(100); |
| 144 | |
| 145 | mv_u3d_phy_clear(base, USB3_KVCO_CALI_CONTROL, |
| 146 | USB3_KVCO_CALI_CONTROL_USE_MAX_PLL_RATE_MASK); |
| 147 | udelay(100); |
| 148 | |
| 149 | val = mv_u3d_phy_read(base, USB3_SQUELCH_FFE); |
| 150 | val &= ~(USB3_SQUELCH_FFE_FFE_CAP_SEL_MASK |
| 151 | | USB3_SQUELCH_FFE_FFE_RES_SEL_MASK |
| 152 | | USB3_SQUELCH_FFE_SQ_THRESH_IN_MASK); |
| 153 | val |= ((0xD << USB3_SQUELCH_FFE_FFE_CAP_SEL_SHIFT) |
| 154 | | (0x7 << USB3_SQUELCH_FFE_FFE_RES_SEL_SHIFT) |
| 155 | | (0x8 << USB3_SQUELCH_FFE_SQ_THRESH_IN_SHIFT)); |
| 156 | mv_u3d_phy_write(base, USB3_SQUELCH_FFE, val); |
| 157 | udelay(100); |
| 158 | |
| 159 | val = mv_u3d_phy_read(base, USB3_GEN1_SET0); |
| 160 | val &= ~USB3_GEN1_SET0_G1_TX_SLEW_CTRL_EN_MASK; |
| 161 | val |= 1 << USB3_GEN1_SET0_G1_TX_EMPH_EN_SHIFT; |
| 162 | mv_u3d_phy_write(base, USB3_GEN1_SET0, val); |
| 163 | udelay(100); |
| 164 | |
| 165 | val = mv_u3d_phy_read(base, USB3_GEN2_SET0); |
| 166 | val &= ~(USB3_GEN2_SET0_G2_TX_AMP_MASK |
| 167 | | USB3_GEN2_SET0_G2_TX_EMPH_AMP_MASK |
| 168 | | USB3_GEN2_SET0_G2_TX_SLEW_CTRL_EN_MASK); |
| 169 | val |= ((0x14 << USB3_GEN2_SET0_G2_TX_AMP_SHIFT) |
| 170 | | (1 << USB3_GEN2_SET0_G2_TX_AMP_ADJ_SHIFT) |
| 171 | | (0xA << USB3_GEN2_SET0_G2_TX_EMPH_AMP_SHIFT) |
| 172 | | (1 << USB3_GEN2_SET0_G2_TX_EMPH_EN_SHIFT)); |
| 173 | mv_u3d_phy_write(base, USB3_GEN2_SET0, val); |
| 174 | udelay(100); |
| 175 | |
| 176 | mv_u3d_phy_read(base, USB3_TX_EMPPH); |
| 177 | val &= ~(USB3_TX_EMPPH_AMP_MASK |
| 178 | | USB3_TX_EMPPH_EN_MASK |
| 179 | | USB3_TX_EMPPH_AMP_FORCE_MASK |
| 180 | | USB3_TX_EMPPH_PAR1_MASK |
| 181 | | USB3_TX_EMPPH_PAR2_MASK); |
| 182 | val |= ((0xB << USB3_TX_EMPPH_AMP_SHIFT) |
| 183 | | (1 << USB3_TX_EMPPH_EN_SHIFT) |
| 184 | | (1 << USB3_TX_EMPPH_AMP_FORCE_SHIFT) |
| 185 | | (0x1C << USB3_TX_EMPPH_PAR1_SHIFT) |
| 186 | | (1 << USB3_TX_EMPPH_PAR2_SHIFT)); |
| 187 | |
| 188 | mv_u3d_phy_write(base, USB3_TX_EMPPH, val); |
| 189 | udelay(100); |
| 190 | |
| 191 | val = mv_u3d_phy_read(base, USB3_GEN2_SET1); |
| 192 | val &= ~(USB3_GEN2_SET1_G2_RX_SELMUPI_MASK |
| 193 | | USB3_GEN2_SET1_G2_RX_SELMUPF_MASK |
| 194 | | USB3_GEN2_SET1_G2_RX_SELMUFI_MASK |
| 195 | | USB3_GEN2_SET1_G2_RX_SELMUFF_MASK); |
| 196 | val |= ((1 << USB3_GEN2_SET1_G2_RX_SELMUPI_SHIFT) |
| 197 | | (1 << USB3_GEN2_SET1_G2_RX_SELMUPF_SHIFT) |
| 198 | | (1 << USB3_GEN2_SET1_G2_RX_SELMUFI_SHIFT) |
| 199 | | (1 << USB3_GEN2_SET1_G2_RX_SELMUFF_SHIFT)); |
| 200 | mv_u3d_phy_write(base, USB3_GEN2_SET1, val); |
| 201 | udelay(100); |
| 202 | |
| 203 | val = mv_u3d_phy_read(base, USB3_DIGITAL_LOOPBACK_EN); |
| 204 | val &= ~USB3_DIGITAL_LOOPBACK_EN_SEL_BITS_MASK; |
| 205 | val |= 1 << USB3_DIGITAL_LOOPBACK_EN_SEL_BITS_SHIFT; |
| 206 | mv_u3d_phy_write(base, USB3_DIGITAL_LOOPBACK_EN, val); |
| 207 | udelay(100); |
| 208 | |
| 209 | val = mv_u3d_phy_read(base, USB3_IMPEDANCE_TX_SSC); |
| 210 | val &= ~USB3_IMPEDANCE_TX_SSC_SSC_AMP_MASK; |
| 211 | val |= 0xC << USB3_IMPEDANCE_TX_SSC_SSC_AMP_SHIFT; |
| 212 | mv_u3d_phy_write(base, USB3_IMPEDANCE_TX_SSC, val); |
| 213 | udelay(100); |
| 214 | |
| 215 | val = mv_u3d_phy_read(base, USB3_IMPEDANCE_CALI_CTRL); |
| 216 | val &= ~USB3_IMPEDANCE_CALI_CTRL_IMP_CAL_THR_MASK; |
| 217 | val |= 0x4 << USB3_IMPEDANCE_CALI_CTRL_IMP_CAL_THR_SHIFT; |
| 218 | mv_u3d_phy_write(base, USB3_IMPEDANCE_CALI_CTRL, val); |
| 219 | udelay(100); |
| 220 | |
| 221 | val = mv_u3d_phy_read(base, USB3_PHY_ISOLATION_MODE); |
| 222 | val &= ~(USB3_PHY_ISOLATION_MODE_PHY_GEN_RX_MASK |
| 223 | | USB3_PHY_ISOLATION_MODE_PHY_GEN_TX_MASK |
| 224 | | USB3_PHY_ISOLATION_MODE_TX_DRV_IDLE_MASK); |
| 225 | val |= ((1 << USB3_PHY_ISOLATION_MODE_PHY_GEN_RX_SHIFT) |
| 226 | | (1 << USB3_PHY_ISOLATION_MODE_PHY_GEN_TX_SHIFT)); |
| 227 | mv_u3d_phy_write(base, USB3_PHY_ISOLATION_MODE, val); |
| 228 | udelay(100); |
| 229 | |
| 230 | val = mv_u3d_phy_read(base, USB3_TXDETRX); |
| 231 | val &= ~(USB3_TXDETRX_VTHSEL_MASK); |
| 232 | val |= 0x1 << USB3_TXDETRX_VTHSEL_SHIFT; |
| 233 | mv_u3d_phy_write(base, USB3_TXDETRX, val); |
| 234 | udelay(100); |
| 235 | |
| 236 | dev_dbg(mv_u3d_phy->dev, "start calibration\n"); |
| 237 | |
| 238 | calstart: |
| 239 | /* Perform Manual Calibration */ |
| 240 | mv_u3d_phy_set(base, USB3_KVCO_CALI_CONTROL, |
| 241 | 1 << USB3_KVCO_CALI_CONTROL_CAL_START_SHIFT); |
| 242 | |
| 243 | mdelay(1); |
| 244 | |
| 245 | count = 0; |
| 246 | while (1) { |
| 247 | val = mv_u3d_phy_read(base, USB3_KVCO_CALI_CONTROL); |
| 248 | if (val & (1 << USB3_KVCO_CALI_CONTROL_CAL_DONE_SHIFT)) |
| 249 | break; |
| 250 | else if (count > 50) { |
| 251 | dev_dbg(mv_u3d_phy->dev, "calibration failure, retry...\n"); |
| 252 | goto calstart; |
| 253 | } |
| 254 | count++; |
| 255 | mdelay(1); |
| 256 | } |
| 257 | |
| 258 | /* active PIPE interface */ |
| 259 | mv_u3d_phy_write(base, USB3_PIPE_SM_CTRL, |
| 260 | 1 << USB3_PIPE_SM_CTRL_PHY_INIT_DONE); |
| 261 | |
| 262 | return 0; |
| 263 | } |
| 264 | |
Bill Pemberton | 41ac7b3 | 2012-11-19 13:21:48 -0500 | [diff] [blame] | 265 | static int mv_u3d_phy_probe(struct platform_device *pdev) |
Yu Xu | a67e76a | 2012-08-09 22:29:31 +0800 | [diff] [blame] | 266 | { |
| 267 | struct mv_u3d_phy *mv_u3d_phy; |
| 268 | struct mv_usb_platform_data *pdata; |
| 269 | struct device *dev = &pdev->dev; |
| 270 | struct resource *res; |
| 271 | void __iomem *phy_base; |
| 272 | int ret; |
| 273 | |
Jingoo Han | 19f9e18 | 2013-07-30 17:02:13 +0900 | [diff] [blame] | 274 | pdata = dev_get_platdata(&pdev->dev); |
Yu Xu | a67e76a | 2012-08-09 22:29:31 +0800 | [diff] [blame] | 275 | if (!pdata) { |
| 276 | dev_err(&pdev->dev, "%s: no platform data defined\n", __func__); |
| 277 | return -EINVAL; |
| 278 | } |
| 279 | |
| 280 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
Thierry Reding | 148e113 | 2013-01-21 11:09:22 +0100 | [diff] [blame] | 281 | phy_base = devm_ioremap_resource(dev, res); |
| 282 | if (IS_ERR(phy_base)) |
| 283 | return PTR_ERR(phy_base); |
Yu Xu | a67e76a | 2012-08-09 22:29:31 +0800 | [diff] [blame] | 284 | |
| 285 | mv_u3d_phy = devm_kzalloc(dev, sizeof(*mv_u3d_phy), GFP_KERNEL); |
| 286 | if (!mv_u3d_phy) |
| 287 | return -ENOMEM; |
| 288 | |
| 289 | mv_u3d_phy->dev = &pdev->dev; |
| 290 | mv_u3d_phy->plat = pdata; |
| 291 | mv_u3d_phy->base = phy_base; |
| 292 | mv_u3d_phy->phy.dev = mv_u3d_phy->dev; |
| 293 | mv_u3d_phy->phy.label = "mv-u3d-phy"; |
| 294 | mv_u3d_phy->phy.init = mv_u3d_phy_init; |
| 295 | mv_u3d_phy->phy.shutdown = mv_u3d_phy_shutdown; |
| 296 | |
| 297 | ret = usb_add_phy(&mv_u3d_phy->phy, USB_PHY_TYPE_USB3); |
| 298 | if (ret) |
| 299 | goto err; |
| 300 | |
| 301 | if (!mv_u3d_phy->clk) |
| 302 | mv_u3d_phy->clk = clk_get(mv_u3d_phy->dev, "u3dphy"); |
| 303 | |
| 304 | platform_set_drvdata(pdev, mv_u3d_phy); |
| 305 | |
| 306 | dev_info(&pdev->dev, "Initialized Marvell USB 3.0 PHY\n"); |
| 307 | err: |
| 308 | return ret; |
| 309 | } |
| 310 | |
Dmitry Torokhov | 39d3568 | 2013-02-24 00:55:07 -0800 | [diff] [blame] | 311 | static int mv_u3d_phy_remove(struct platform_device *pdev) |
Yu Xu | a67e76a | 2012-08-09 22:29:31 +0800 | [diff] [blame] | 312 | { |
| 313 | struct mv_u3d_phy *mv_u3d_phy = platform_get_drvdata(pdev); |
| 314 | |
| 315 | usb_remove_phy(&mv_u3d_phy->phy); |
| 316 | |
| 317 | if (mv_u3d_phy->clk) { |
| 318 | clk_put(mv_u3d_phy->clk); |
| 319 | mv_u3d_phy->clk = NULL; |
| 320 | } |
| 321 | |
| 322 | return 0; |
| 323 | } |
| 324 | |
| 325 | static struct platform_driver mv_u3d_phy_driver = { |
| 326 | .probe = mv_u3d_phy_probe, |
Bill Pemberton | 7690417 | 2012-11-19 13:21:08 -0500 | [diff] [blame] | 327 | .remove = mv_u3d_phy_remove, |
Yu Xu | a67e76a | 2012-08-09 22:29:31 +0800 | [diff] [blame] | 328 | .driver = { |
| 329 | .name = "mv-u3d-phy", |
| 330 | .owner = THIS_MODULE, |
| 331 | }, |
| 332 | }; |
| 333 | |
| 334 | module_platform_driver(mv_u3d_phy_driver); |
| 335 | MODULE_DESCRIPTION("Marvell USB 3.0 PHY controller"); |
| 336 | MODULE_AUTHOR("Yu Xu <yuxu@marvell.com>"); |
| 337 | MODULE_LICENSE("GPL"); |
| 338 | MODULE_ALIAS("platform:mv-u3d-phy"); |