Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v3.17-rc5 350 lines 9.0 kB view raw
1/* linux/drivers/usb/phy/phy-samsung-usb3.c 2 * 3 * Copyright (c) 2013 Samsung Electronics Co., Ltd. 4 * http://www.samsung.com 5 * 6 * Author: Vivek Gautam <gautam.vivek@samsung.com> 7 * 8 * Samsung USB 3.0 PHY transceiver; talks to DWC3 controller. 9 * 10 * This program is free software; you can redistribute it and/or modify 11 * it under the terms of the GNU General Public License version 2 as 12 * published by the Free Software Foundation. 13 * 14 * This program is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 * GNU General Public License for more details. 18 */ 19 20#include <linux/module.h> 21#include <linux/platform_device.h> 22#include <linux/clk.h> 23#include <linux/delay.h> 24#include <linux/err.h> 25#include <linux/io.h> 26#include <linux/of.h> 27#include <linux/usb/samsung_usb_phy.h> 28#include <linux/platform_data/samsung-usbphy.h> 29 30#include "phy-samsung-usb.h" 31 32/* 33 * Sets the phy clk as EXTREFCLK (XXTI) which is internal clock from clock core. 34 */ 35static u32 samsung_usb3phy_set_refclk(struct samsung_usbphy *sphy) 36{ 37 u32 reg; 38 u32 refclk; 39 40 refclk = sphy->ref_clk_freq; 41 42 reg = PHYCLKRST_REFCLKSEL_EXT_REFCLK | 43 PHYCLKRST_FSEL(refclk); 44 45 switch (refclk) { 46 case FSEL_CLKSEL_50M: 47 reg |= (PHYCLKRST_MPLL_MULTIPLIER_50M_REF | 48 PHYCLKRST_SSC_REFCLKSEL(0x00)); 49 break; 50 case FSEL_CLKSEL_20M: 51 reg |= (PHYCLKRST_MPLL_MULTIPLIER_20MHZ_REF | 52 PHYCLKRST_SSC_REFCLKSEL(0x00)); 53 break; 54 case FSEL_CLKSEL_19200K: 55 reg |= (PHYCLKRST_MPLL_MULTIPLIER_19200KHZ_REF | 56 PHYCLKRST_SSC_REFCLKSEL(0x88)); 57 break; 58 case FSEL_CLKSEL_24M: 59 default: 60 reg |= (PHYCLKRST_MPLL_MULTIPLIER_24MHZ_REF | 61 PHYCLKRST_SSC_REFCLKSEL(0x88)); 62 break; 63 } 64 65 return reg; 66} 67 68static void samsung_exynos5_usb3phy_enable(struct samsung_usbphy *sphy) 69{ 70 void __iomem *regs = sphy->regs; 71 u32 phyparam0; 72 u32 phyparam1; 73 u32 linksystem; 74 u32 phybatchg; 75 u32 phytest; 76 u32 phyclkrst; 77 78 /* Reset USB 3.0 PHY */ 79 writel(0x0, regs + EXYNOS5_DRD_PHYREG0); 80 81 phyparam0 = readl(regs + EXYNOS5_DRD_PHYPARAM0); 82 /* Select PHY CLK source */ 83 phyparam0 &= ~PHYPARAM0_REF_USE_PAD; 84 /* Set Loss-of-Signal Detector sensitivity */ 85 phyparam0 &= ~PHYPARAM0_REF_LOSLEVEL_MASK; 86 phyparam0 |= PHYPARAM0_REF_LOSLEVEL; 87 writel(phyparam0, regs + EXYNOS5_DRD_PHYPARAM0); 88 89 writel(0x0, regs + EXYNOS5_DRD_PHYRESUME); 90 91 /* 92 * Setting the Frame length Adj value[6:1] to default 0x20 93 * See xHCI 1.0 spec, 5.2.4 94 */ 95 linksystem = LINKSYSTEM_XHCI_VERSION_CONTROL | 96 LINKSYSTEM_FLADJ(0x20); 97 writel(linksystem, regs + EXYNOS5_DRD_LINKSYSTEM); 98 99 phyparam1 = readl(regs + EXYNOS5_DRD_PHYPARAM1); 100 /* Set Tx De-Emphasis level */ 101 phyparam1 &= ~PHYPARAM1_PCS_TXDEEMPH_MASK; 102 phyparam1 |= PHYPARAM1_PCS_TXDEEMPH; 103 writel(phyparam1, regs + EXYNOS5_DRD_PHYPARAM1); 104 105 phybatchg = readl(regs + EXYNOS5_DRD_PHYBATCHG); 106 phybatchg |= PHYBATCHG_UTMI_CLKSEL; 107 writel(phybatchg, regs + EXYNOS5_DRD_PHYBATCHG); 108 109 /* PHYTEST POWERDOWN Control */ 110 phytest = readl(regs + EXYNOS5_DRD_PHYTEST); 111 phytest &= ~(PHYTEST_POWERDOWN_SSP | 112 PHYTEST_POWERDOWN_HSP); 113 writel(phytest, regs + EXYNOS5_DRD_PHYTEST); 114 115 /* UTMI Power Control */ 116 writel(PHYUTMI_OTGDISABLE, regs + EXYNOS5_DRD_PHYUTMI); 117 118 phyclkrst = samsung_usb3phy_set_refclk(sphy); 119 120 phyclkrst |= PHYCLKRST_PORTRESET | 121 /* Digital power supply in normal operating mode */ 122 PHYCLKRST_RETENABLEN | 123 /* Enable ref clock for SS function */ 124 PHYCLKRST_REF_SSP_EN | 125 /* Enable spread spectrum */ 126 PHYCLKRST_SSC_EN | 127 /* Power down HS Bias and PLL blocks in suspend mode */ 128 PHYCLKRST_COMMONONN; 129 130 writel(phyclkrst, regs + EXYNOS5_DRD_PHYCLKRST); 131 132 udelay(10); 133 134 phyclkrst &= ~(PHYCLKRST_PORTRESET); 135 writel(phyclkrst, regs + EXYNOS5_DRD_PHYCLKRST); 136} 137 138static void samsung_exynos5_usb3phy_disable(struct samsung_usbphy *sphy) 139{ 140 u32 phyutmi; 141 u32 phyclkrst; 142 u32 phytest; 143 void __iomem *regs = sphy->regs; 144 145 phyutmi = PHYUTMI_OTGDISABLE | 146 PHYUTMI_FORCESUSPEND | 147 PHYUTMI_FORCESLEEP; 148 writel(phyutmi, regs + EXYNOS5_DRD_PHYUTMI); 149 150 /* Resetting the PHYCLKRST enable bits to reduce leakage current */ 151 phyclkrst = readl(regs + EXYNOS5_DRD_PHYCLKRST); 152 phyclkrst &= ~(PHYCLKRST_REF_SSP_EN | 153 PHYCLKRST_SSC_EN | 154 PHYCLKRST_COMMONONN); 155 writel(phyclkrst, regs + EXYNOS5_DRD_PHYCLKRST); 156 157 /* Control PHYTEST to remove leakage current */ 158 phytest = readl(regs + EXYNOS5_DRD_PHYTEST); 159 phytest |= (PHYTEST_POWERDOWN_SSP | 160 PHYTEST_POWERDOWN_HSP); 161 writel(phytest, regs + EXYNOS5_DRD_PHYTEST); 162} 163 164static int samsung_usb3phy_init(struct usb_phy *phy) 165{ 166 struct samsung_usbphy *sphy; 167 unsigned long flags; 168 int ret = 0; 169 170 sphy = phy_to_sphy(phy); 171 172 /* Enable the phy clock */ 173 ret = clk_prepare_enable(sphy->clk); 174 if (ret) { 175 dev_err(sphy->dev, "%s: clk_prepare_enable failed\n", __func__); 176 return ret; 177 } 178 179 spin_lock_irqsave(&sphy->lock, flags); 180 181 /* setting default phy-type for USB 3.0 */ 182 samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_DEVICE); 183 184 /* Disable phy isolation */ 185 if (sphy->drv_data->set_isolation) 186 sphy->drv_data->set_isolation(sphy, false); 187 188 /* Initialize usb phy registers */ 189 sphy->drv_data->phy_enable(sphy); 190 191 spin_unlock_irqrestore(&sphy->lock, flags); 192 193 /* Disable the phy clock */ 194 clk_disable_unprepare(sphy->clk); 195 196 return ret; 197} 198 199/* 200 * The function passed to the usb driver for phy shutdown 201 */ 202static void samsung_usb3phy_shutdown(struct usb_phy *phy) 203{ 204 struct samsung_usbphy *sphy; 205 unsigned long flags; 206 207 sphy = phy_to_sphy(phy); 208 209 if (clk_prepare_enable(sphy->clk)) { 210 dev_err(sphy->dev, "%s: clk_prepare_enable failed\n", __func__); 211 return; 212 } 213 214 spin_lock_irqsave(&sphy->lock, flags); 215 216 /* setting default phy-type for USB 3.0 */ 217 samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_DEVICE); 218 219 /* De-initialize usb phy registers */ 220 sphy->drv_data->phy_disable(sphy); 221 222 /* Enable phy isolation */ 223 if (sphy->drv_data->set_isolation) 224 sphy->drv_data->set_isolation(sphy, true); 225 226 spin_unlock_irqrestore(&sphy->lock, flags); 227 228 clk_disable_unprepare(sphy->clk); 229} 230 231static int samsung_usb3phy_probe(struct platform_device *pdev) 232{ 233 struct samsung_usbphy *sphy; 234 struct samsung_usbphy_data *pdata = dev_get_platdata(&pdev->dev); 235 struct device *dev = &pdev->dev; 236 struct resource *phy_mem; 237 void __iomem *phy_base; 238 struct clk *clk; 239 int ret; 240 241 phy_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); 242 phy_base = devm_ioremap_resource(dev, phy_mem); 243 if (IS_ERR(phy_base)) 244 return PTR_ERR(phy_base); 245 246 sphy = devm_kzalloc(dev, sizeof(*sphy), GFP_KERNEL); 247 if (!sphy) 248 return -ENOMEM; 249 250 clk = devm_clk_get(dev, "usbdrd30"); 251 if (IS_ERR(clk)) { 252 dev_err(dev, "Failed to get device clock\n"); 253 return PTR_ERR(clk); 254 } 255 256 sphy->dev = dev; 257 258 if (dev->of_node) { 259 ret = samsung_usbphy_parse_dt(sphy); 260 if (ret < 0) 261 return ret; 262 } else { 263 if (!pdata) { 264 dev_err(dev, "no platform data specified\n"); 265 return -EINVAL; 266 } 267 } 268 269 sphy->plat = pdata; 270 sphy->regs = phy_base; 271 sphy->clk = clk; 272 sphy->phy.dev = sphy->dev; 273 sphy->phy.label = "samsung-usb3phy"; 274 sphy->phy.type = USB_PHY_TYPE_USB3; 275 sphy->phy.init = samsung_usb3phy_init; 276 sphy->phy.shutdown = samsung_usb3phy_shutdown; 277 sphy->drv_data = samsung_usbphy_get_driver_data(pdev); 278 279 sphy->ref_clk_freq = samsung_usbphy_get_refclk_freq(sphy); 280 if (sphy->ref_clk_freq < 0) 281 return -EINVAL; 282 283 spin_lock_init(&sphy->lock); 284 285 platform_set_drvdata(pdev, sphy); 286 287 return usb_add_phy_dev(&sphy->phy); 288} 289 290static int samsung_usb3phy_remove(struct platform_device *pdev) 291{ 292 struct samsung_usbphy *sphy = platform_get_drvdata(pdev); 293 294 usb_remove_phy(&sphy->phy); 295 296 if (sphy->pmuregs) 297 iounmap(sphy->pmuregs); 298 if (sphy->sysreg) 299 iounmap(sphy->sysreg); 300 301 return 0; 302} 303 304static struct samsung_usbphy_drvdata usb3phy_exynos5 = { 305 .cpu_type = TYPE_EXYNOS5250, 306 .devphy_en_mask = EXYNOS_USBPHY_ENABLE, 307 .rate_to_clksel = samsung_usbphy_rate_to_clksel_4x12, 308 .set_isolation = samsung_usbphy_set_isolation_4210, 309 .phy_enable = samsung_exynos5_usb3phy_enable, 310 .phy_disable = samsung_exynos5_usb3phy_disable, 311}; 312 313#ifdef CONFIG_OF 314static const struct of_device_id samsung_usbphy_dt_match[] = { 315 { 316 .compatible = "samsung,exynos5250-usb3phy", 317 .data = &usb3phy_exynos5 318 }, 319 {}, 320}; 321MODULE_DEVICE_TABLE(of, samsung_usbphy_dt_match); 322#endif 323 324static struct platform_device_id samsung_usbphy_driver_ids[] = { 325 { 326 .name = "exynos5250-usb3phy", 327 .driver_data = (unsigned long)&usb3phy_exynos5, 328 }, 329 {}, 330}; 331 332MODULE_DEVICE_TABLE(platform, samsung_usbphy_driver_ids); 333 334static struct platform_driver samsung_usb3phy_driver = { 335 .probe = samsung_usb3phy_probe, 336 .remove = samsung_usb3phy_remove, 337 .id_table = samsung_usbphy_driver_ids, 338 .driver = { 339 .name = "samsung-usb3phy", 340 .owner = THIS_MODULE, 341 .of_match_table = of_match_ptr(samsung_usbphy_dt_match), 342 }, 343}; 344 345module_platform_driver(samsung_usb3phy_driver); 346 347MODULE_DESCRIPTION("Samsung USB 3.0 phy controller"); 348MODULE_AUTHOR("Vivek Gautam <gautam.vivek@samsung.com>"); 349MODULE_LICENSE("GPL"); 350MODULE_ALIAS("platform:samsung-usb3phy");