1 /* 2 * ehci-omap.c - driver for USBHOST on OMAP3/4 processors 3 * 4 * Bus Glue for the EHCI controllers in OMAP3/4 5 * Tested on several OMAP3 boards, and OMAP4 Pandaboard 6 * 7 * Copyright (C) 2007-2011 Texas Instruments, Inc. 8 * Author: Vikram Pandita <vikram.pandita@ti.com> 9 * Author: Anand Gadiyar <gadiyar@ti.com> 10 * Author: Keshava Munegowda <keshava_mgowda@ti.com> 11 * 12 * Copyright (C) 2009 Nokia Corporation 13 * Contact: Felipe Balbi <felipe.balbi@nokia.com> 14 * 15 * Based on "ehci-fsl.c" and "ehci-au1xxx.c" ehci glue layers 16 * 17 * This program is free software; you can redistribute it and/or modify 18 * it under the terms of the GNU General Public License as published by 19 * the Free Software Foundation; either version 2 of the License, or 20 * (at your option) any later version. 21 * 22 * This program is distributed in the hope that it will be useful, 23 * but WITHOUT ANY WARRANTY; without even the implied warranty of 24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 * GNU General Public License for more details. 26 * 27 * You should have received a copy of the GNU General Public License 28 * along with this program; if not, write to the Free Software 29 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 30 * 31 * TODO (last updated Feb 27, 2010): 32 * - add kernel-doc 33 * - enable AUTOIDLE 34 * - add suspend/resume 35 * - add HSIC and TLL support 36 * - convert to use hwmod and runtime PM 37 */ 38 39 #include <linux/platform_device.h> 40 #include <linux/slab.h> 41 #include <linux/usb/ulpi.h> 42 #include <plat/usb.h> 43 #include <linux/regulator/consumer.h> 44 45 /* EHCI Register Set */ 46 #define EHCI_INSNREG04 (0xA0) 47 #define EHCI_INSNREG04_DISABLE_UNSUSPEND (1 << 5) 48 #define EHCI_INSNREG05_ULPI (0xA4) 49 #define EHCI_INSNREG05_ULPI_CONTROL_SHIFT 31 50 #define EHCI_INSNREG05_ULPI_PORTSEL_SHIFT 24 51 #define EHCI_INSNREG05_ULPI_OPSEL_SHIFT 22 52 #define EHCI_INSNREG05_ULPI_REGADD_SHIFT 16 53 #define EHCI_INSNREG05_ULPI_EXTREGADD_SHIFT 8 54 #define EHCI_INSNREG05_ULPI_WRDATA_SHIFT 0 55 56 /*-------------------------------------------------------------------------*/ 57 58 static const struct hc_driver ehci_omap_hc_driver; 59 60 61 static inline void ehci_write(void __iomem *base, u32 reg, u32 val) 62 { 63 __raw_writel(val, base + reg); 64 } 65 66 static inline u32 ehci_read(void __iomem *base, u32 reg) 67 { 68 return __raw_readl(base + reg); 69 } 70 71 static void omap_ehci_soft_phy_reset(struct platform_device *pdev, u8 port) 72 { 73 struct usb_hcd *hcd = dev_get_drvdata(&pdev->dev); 74 unsigned long timeout = jiffies + msecs_to_jiffies(1000); 75 unsigned reg = 0; 76 77 reg = ULPI_FUNC_CTRL_RESET 78 /* FUNCTION_CTRL_SET register */ 79 | (ULPI_SET(ULPI_FUNC_CTRL) << EHCI_INSNREG05_ULPI_REGADD_SHIFT) 80 /* Write */ 81 | (2 << EHCI_INSNREG05_ULPI_OPSEL_SHIFT) 82 /* PORTn */ 83 | ((port + 1) << EHCI_INSNREG05_ULPI_PORTSEL_SHIFT) 84 /* start ULPI access*/ 85 | (1 << EHCI_INSNREG05_ULPI_CONTROL_SHIFT); 86 87 ehci_write(hcd->regs, EHCI_INSNREG05_ULPI, reg); 88 89 /* Wait for ULPI access completion */ 90 while ((ehci_read(hcd->regs, EHCI_INSNREG05_ULPI) 91 & (1 << EHCI_INSNREG05_ULPI_CONTROL_SHIFT))) { 92 cpu_relax(); 93 94 if (time_after(jiffies, timeout)) { 95 dev_dbg(&pdev->dev, "phy reset operation timed out\n"); 96 break; 97 } 98 } 99 } 100 101 102 /* configure so an HC device and id are always provided */ 103 /* always called with process context; sleeping is OK */ 104 105 /** 106 * ehci_hcd_omap_probe - initialize TI-based HCDs 107 * 108 * Allocates basic resources for this USB host controller, and 109 * then invokes the start() method for the HCD associated with it 110 * through the hotplug entry's driver_data. 111 */ 112 static int ehci_hcd_omap_probe(struct platform_device *pdev) 113 { 114 struct device *dev = &pdev->dev; 115 struct ehci_hcd_omap_platform_data *pdata = dev->platform_data; 116 struct resource *res; 117 struct usb_hcd *hcd; 118 void __iomem *regs; 119 struct ehci_hcd *omap_ehci; 120 int ret = -ENODEV; 121 int irq; 122 int i; 123 char supply[7]; 124 125 if (usb_disabled()) 126 return -ENODEV; 127 128 if (!dev->parent) { 129 dev_err(dev, "Missing parent device\n"); 130 return -ENODEV; 131 } 132 133 irq = platform_get_irq_byname(pdev, "ehci-irq"); 134 if (irq < 0) { 135 dev_err(dev, "EHCI irq failed\n"); 136 return -ENODEV; 137 } 138 139 res = platform_get_resource_byname(pdev, 140 IORESOURCE_MEM, "ehci"); 141 if (!res) { 142 dev_err(dev, "UHH EHCI get resource failed\n"); 143 return -ENODEV; 144 } 145 146 regs = ioremap(res->start, resource_size(res)); 147 if (!regs) { 148 dev_err(dev, "UHH EHCI ioremap failed\n"); 149 return -ENOMEM; 150 } 151 152 hcd = usb_create_hcd(&ehci_omap_hc_driver, dev, 153 dev_name(dev)); 154 if (!hcd) { 155 dev_err(dev, "failed to create hcd with err %d\n", ret); 156 ret = -ENOMEM; 157 goto err_io; 158 } 159 160 hcd->rsrc_start = res->start; 161 hcd->rsrc_len = resource_size(res); 162 hcd->regs = regs; 163 164 /* get ehci regulator and enable */ 165 for (i = 0 ; i < OMAP3_HS_USB_PORTS ; i++) { 166 if (pdata->port_mode[i] != OMAP_EHCI_PORT_MODE_PHY) { 167 pdata->regulator[i] = NULL; 168 continue; 169 } 170 snprintf(supply, sizeof(supply), "hsusb%d", i); 171 pdata->regulator[i] = regulator_get(dev, supply); 172 if (IS_ERR(pdata->regulator[i])) { 173 pdata->regulator[i] = NULL; 174 dev_dbg(dev, 175 "failed to get ehci port%d regulator\n", i); 176 } else { 177 regulator_enable(pdata->regulator[i]); 178 } 179 } 180 181 ret = omap_usbhs_enable(dev); 182 if (ret) { 183 dev_err(dev, "failed to start usbhs with err %d\n", ret); 184 goto err_enable; 185 } 186 187 /* 188 * An undocumented "feature" in the OMAP3 EHCI controller, 189 * causes suspended ports to be taken out of suspend when 190 * the USBCMD.Run/Stop bit is cleared (for example when 191 * we do ehci_bus_suspend). 192 * This breaks suspend-resume if the root-hub is allowed 193 * to suspend. Writing 1 to this undocumented register bit 194 * disables this feature and restores normal behavior. 195 */ 196 ehci_write(regs, EHCI_INSNREG04, 197 EHCI_INSNREG04_DISABLE_UNSUSPEND); 198 199 /* Soft reset the PHY using PHY reset command over ULPI */ 200 if (pdata->port_mode[0] == OMAP_EHCI_PORT_MODE_PHY) 201 omap_ehci_soft_phy_reset(pdev, 0); 202 if (pdata->port_mode[1] == OMAP_EHCI_PORT_MODE_PHY) 203 omap_ehci_soft_phy_reset(pdev, 1); 204 205 omap_ehci = hcd_to_ehci(hcd); 206 omap_ehci->sbrn = 0x20; 207 208 /* we know this is the memory we want, no need to ioremap again */ 209 omap_ehci->caps = hcd->regs; 210 omap_ehci->regs = hcd->regs 211 + HC_LENGTH(ehci, readl(&omap_ehci->caps->hc_capbase)); 212 213 dbg_hcs_params(omap_ehci, "reset"); 214 dbg_hcc_params(omap_ehci, "reset"); 215 216 /* cache this readonly data; minimize chip reads */ 217 omap_ehci->hcs_params = readl(&omap_ehci->caps->hcs_params); 218 219 ret = usb_add_hcd(hcd, irq, IRQF_DISABLED | IRQF_SHARED); 220 if (ret) { 221 dev_err(dev, "failed to add hcd with err %d\n", ret); 222 goto err_add_hcd; 223 } 224 225 /* root ports should always stay powered */ 226 ehci_port_power(omap_ehci, 1); 227 228 return 0; 229 230 err_add_hcd: 231 omap_usbhs_disable(dev); 232 233 err_enable: 234 usb_put_hcd(hcd); 235 236 err_io: 237 return ret; 238 } 239 240 241 /** 242 * ehci_hcd_omap_remove - shutdown processing for EHCI HCDs 243 * @pdev: USB Host Controller being removed 244 * 245 * Reverses the effect of usb_ehci_hcd_omap_probe(), first invoking 246 * the HCD's stop() method. It is always called from a thread 247 * context, normally "rmmod", "apmd", or something similar. 248 */ 249 static int ehci_hcd_omap_remove(struct platform_device *pdev) 250 { 251 struct device *dev = &pdev->dev; 252 struct usb_hcd *hcd = dev_get_drvdata(dev); 253 254 usb_remove_hcd(hcd); 255 omap_usbhs_disable(dev); 256 usb_put_hcd(hcd); 257 return 0; 258 } 259 260 static void ehci_hcd_omap_shutdown(struct platform_device *pdev) 261 { 262 struct usb_hcd *hcd = dev_get_drvdata(&pdev->dev); 263 264 if (hcd->driver->shutdown) 265 hcd->driver->shutdown(hcd); 266 } 267 268 static struct platform_driver ehci_hcd_omap_driver = { 269 .probe = ehci_hcd_omap_probe, 270 .remove = ehci_hcd_omap_remove, 271 .shutdown = ehci_hcd_omap_shutdown, 272 /*.suspend = ehci_hcd_omap_suspend, */ 273 /*.resume = ehci_hcd_omap_resume, */ 274 .driver = { 275 .name = "ehci-omap", 276 } 277 }; 278 279 /*-------------------------------------------------------------------------*/ 280 281 static const struct hc_driver ehci_omap_hc_driver = { 282 .description = hcd_name, 283 .product_desc = "OMAP-EHCI Host Controller", 284 .hcd_priv_size = sizeof(struct ehci_hcd), 285 286 /* 287 * generic hardware linkage 288 */ 289 .irq = ehci_irq, 290 .flags = HCD_MEMORY | HCD_USB2, 291 292 /* 293 * basic lifecycle operations 294 */ 295 .reset = ehci_init, 296 .start = ehci_run, 297 .stop = ehci_stop, 298 .shutdown = ehci_shutdown, 299 300 /* 301 * managing i/o requests and associated device resources 302 */ 303 .urb_enqueue = ehci_urb_enqueue, 304 .urb_dequeue = ehci_urb_dequeue, 305 .endpoint_disable = ehci_endpoint_disable, 306 .endpoint_reset = ehci_endpoint_reset, 307 308 /* 309 * scheduling support 310 */ 311 .get_frame_number = ehci_get_frame, 312 313 /* 314 * root hub support 315 */ 316 .hub_status_data = ehci_hub_status_data, 317 .hub_control = ehci_hub_control, 318 .bus_suspend = ehci_bus_suspend, 319 .bus_resume = ehci_bus_resume, 320 321 .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, 322 }; 323 324 MODULE_ALIAS("platform:omap-ehci"); 325 MODULE_AUTHOR("Texas Instruments, Inc."); 326 MODULE_AUTHOR("Felipe Balbi <felipe.balbi@nokia.com>"); 327 328