1efe7daf2SSergei Shtylyov /* 2efe7daf2SSergei Shtylyov * OHCI HCD (Host Controller Driver) for USB. 3efe7daf2SSergei Shtylyov * 4efe7daf2SSergei Shtylyov * TI DA8xx (OMAP-L1x) Bus Glue 5efe7daf2SSergei Shtylyov * 6efe7daf2SSergei Shtylyov * Derived from: ohci-omap.c and ohci-s3c2410.c 7efe7daf2SSergei Shtylyov * Copyright (C) 2008-2009 MontaVista Software, Inc. <source@mvista.com> 8efe7daf2SSergei Shtylyov * 9efe7daf2SSergei Shtylyov * This file is licensed under the terms of the GNU General Public License 10efe7daf2SSergei Shtylyov * version 2. This program is licensed "as is" without any warranty of any 11efe7daf2SSergei Shtylyov * kind, whether express or implied. 12efe7daf2SSergei Shtylyov */ 13efe7daf2SSergei Shtylyov 14efe7daf2SSergei Shtylyov #include <linux/interrupt.h> 15efe7daf2SSergei Shtylyov #include <linux/jiffies.h> 16efe7daf2SSergei Shtylyov #include <linux/platform_device.h> 17efe7daf2SSergei Shtylyov #include <linux/clk.h> 18efe7daf2SSergei Shtylyov 19efe7daf2SSergei Shtylyov #include <mach/da8xx.h> 20ec2a0833SArnd Bergmann #include <linux/platform_data/usb-davinci.h> 21efe7daf2SSergei Shtylyov 22efe7daf2SSergei Shtylyov #ifndef CONFIG_ARCH_DAVINCI_DA8XX 23efe7daf2SSergei Shtylyov #error "This file is DA8xx bus glue. Define CONFIG_ARCH_DAVINCI_DA8XX." 24efe7daf2SSergei Shtylyov #endif 25efe7daf2SSergei Shtylyov 269600cbb2SSergei Shtylyov #define CFGCHIP2 DA8XX_SYSCFG0_VIRT(DA8XX_CFGCHIP2_REG) 27efe7daf2SSergei Shtylyov 28efe7daf2SSergei Shtylyov static struct clk *usb11_clk; 29efe7daf2SSergei Shtylyov static struct clk *usb20_clk; 30efe7daf2SSergei Shtylyov 31efe7daf2SSergei Shtylyov /* Over-current indicator change bitmask */ 32efe7daf2SSergei Shtylyov static volatile u16 ocic_mask; 33efe7daf2SSergei Shtylyov 34efe7daf2SSergei Shtylyov static void ohci_da8xx_clock(int on) 35efe7daf2SSergei Shtylyov { 36efe7daf2SSergei Shtylyov u32 cfgchip2; 37efe7daf2SSergei Shtylyov 38efe7daf2SSergei Shtylyov cfgchip2 = __raw_readl(CFGCHIP2); 39efe7daf2SSergei Shtylyov if (on) { 40efe7daf2SSergei Shtylyov clk_enable(usb11_clk); 41efe7daf2SSergei Shtylyov 42efe7daf2SSergei Shtylyov /* 43efe7daf2SSergei Shtylyov * If USB 1.1 reference clock is sourced from USB 2.0 PHY, we 44efe7daf2SSergei Shtylyov * need to enable the USB 2.0 module clocking, start its PHY, 45efe7daf2SSergei Shtylyov * and not allow it to stop the clock during USB 2.0 suspend. 46efe7daf2SSergei Shtylyov */ 47efe7daf2SSergei Shtylyov if (!(cfgchip2 & CFGCHIP2_USB1PHYCLKMUX)) { 48efe7daf2SSergei Shtylyov clk_enable(usb20_clk); 49efe7daf2SSergei Shtylyov 50efe7daf2SSergei Shtylyov cfgchip2 &= ~(CFGCHIP2_RESET | CFGCHIP2_PHYPWRDN); 51efe7daf2SSergei Shtylyov cfgchip2 |= CFGCHIP2_PHY_PLLON; 52efe7daf2SSergei Shtylyov __raw_writel(cfgchip2, CFGCHIP2); 53efe7daf2SSergei Shtylyov 54efe7daf2SSergei Shtylyov pr_info("Waiting for USB PHY clock good...\n"); 55efe7daf2SSergei Shtylyov while (!(__raw_readl(CFGCHIP2) & CFGCHIP2_PHYCLKGD)) 56efe7daf2SSergei Shtylyov cpu_relax(); 57efe7daf2SSergei Shtylyov } 58efe7daf2SSergei Shtylyov 59efe7daf2SSergei Shtylyov /* Enable USB 1.1 PHY */ 60efe7daf2SSergei Shtylyov cfgchip2 |= CFGCHIP2_USB1SUSPENDM; 61efe7daf2SSergei Shtylyov } else { 62efe7daf2SSergei Shtylyov clk_disable(usb11_clk); 63efe7daf2SSergei Shtylyov if (!(cfgchip2 & CFGCHIP2_USB1PHYCLKMUX)) 64efe7daf2SSergei Shtylyov clk_disable(usb20_clk); 65efe7daf2SSergei Shtylyov 66efe7daf2SSergei Shtylyov /* Disable USB 1.1 PHY */ 67efe7daf2SSergei Shtylyov cfgchip2 &= ~CFGCHIP2_USB1SUSPENDM; 68efe7daf2SSergei Shtylyov } 69efe7daf2SSergei Shtylyov __raw_writel(cfgchip2, CFGCHIP2); 70efe7daf2SSergei Shtylyov } 71efe7daf2SSergei Shtylyov 72efe7daf2SSergei Shtylyov /* 73efe7daf2SSergei Shtylyov * Handle the port over-current indicator change. 74efe7daf2SSergei Shtylyov */ 75efe7daf2SSergei Shtylyov static void ohci_da8xx_ocic_handler(struct da8xx_ohci_root_hub *hub, 76efe7daf2SSergei Shtylyov unsigned port) 77efe7daf2SSergei Shtylyov { 78efe7daf2SSergei Shtylyov ocic_mask |= 1 << port; 79efe7daf2SSergei Shtylyov 80efe7daf2SSergei Shtylyov /* Once over-current is detected, the port needs to be powered down */ 81efe7daf2SSergei Shtylyov if (hub->get_oci(port) > 0) 82efe7daf2SSergei Shtylyov hub->set_power(port, 0); 83efe7daf2SSergei Shtylyov } 84efe7daf2SSergei Shtylyov 85efe7daf2SSergei Shtylyov static int ohci_da8xx_init(struct usb_hcd *hcd) 86efe7daf2SSergei Shtylyov { 87efe7daf2SSergei Shtylyov struct device *dev = hcd->self.controller; 88d4f09e28SJingoo Han struct da8xx_ohci_root_hub *hub = dev_get_platdata(dev); 89efe7daf2SSergei Shtylyov struct ohci_hcd *ohci = hcd_to_ohci(hcd); 90efe7daf2SSergei Shtylyov int result; 91efe7daf2SSergei Shtylyov u32 rh_a; 92efe7daf2SSergei Shtylyov 93efe7daf2SSergei Shtylyov dev_dbg(dev, "starting USB controller\n"); 94efe7daf2SSergei Shtylyov 95efe7daf2SSergei Shtylyov ohci_da8xx_clock(1); 96efe7daf2SSergei Shtylyov 97efe7daf2SSergei Shtylyov /* 98efe7daf2SSergei Shtylyov * DA8xx only have 1 port connected to the pins but the HC root hub 99efe7daf2SSergei Shtylyov * register A reports 2 ports, thus we'll have to override it... 100efe7daf2SSergei Shtylyov */ 101efe7daf2SSergei Shtylyov ohci->num_ports = 1; 102efe7daf2SSergei Shtylyov 103efe7daf2SSergei Shtylyov result = ohci_init(ohci); 104efe7daf2SSergei Shtylyov if (result < 0) 105efe7daf2SSergei Shtylyov return result; 106efe7daf2SSergei Shtylyov 107efe7daf2SSergei Shtylyov /* 108efe7daf2SSergei Shtylyov * Since we're providing a board-specific root hub port power control 109efe7daf2SSergei Shtylyov * and over-current reporting, we have to override the HC root hub A 110efe7daf2SSergei Shtylyov * register's default value, so that ohci_hub_control() could return 111efe7daf2SSergei Shtylyov * the correct hub descriptor... 112efe7daf2SSergei Shtylyov */ 113efe7daf2SSergei Shtylyov rh_a = ohci_readl(ohci, &ohci->regs->roothub.a); 114efe7daf2SSergei Shtylyov if (hub->set_power) { 115efe7daf2SSergei Shtylyov rh_a &= ~RH_A_NPS; 116efe7daf2SSergei Shtylyov rh_a |= RH_A_PSM; 117efe7daf2SSergei Shtylyov } 118efe7daf2SSergei Shtylyov if (hub->get_oci) { 119efe7daf2SSergei Shtylyov rh_a &= ~RH_A_NOCP; 120efe7daf2SSergei Shtylyov rh_a |= RH_A_OCPM; 121efe7daf2SSergei Shtylyov } 122efe7daf2SSergei Shtylyov rh_a &= ~RH_A_POTPGT; 123efe7daf2SSergei Shtylyov rh_a |= hub->potpgt << 24; 124efe7daf2SSergei Shtylyov ohci_writel(ohci, rh_a, &ohci->regs->roothub.a); 125efe7daf2SSergei Shtylyov 126efe7daf2SSergei Shtylyov return result; 127efe7daf2SSergei Shtylyov } 128efe7daf2SSergei Shtylyov 129efe7daf2SSergei Shtylyov static void ohci_da8xx_stop(struct usb_hcd *hcd) 130efe7daf2SSergei Shtylyov { 131efe7daf2SSergei Shtylyov ohci_stop(hcd); 132efe7daf2SSergei Shtylyov ohci_da8xx_clock(0); 133efe7daf2SSergei Shtylyov } 134efe7daf2SSergei Shtylyov 135efe7daf2SSergei Shtylyov static int ohci_da8xx_start(struct usb_hcd *hcd) 136efe7daf2SSergei Shtylyov { 137efe7daf2SSergei Shtylyov struct ohci_hcd *ohci = hcd_to_ohci(hcd); 138efe7daf2SSergei Shtylyov int result; 139efe7daf2SSergei Shtylyov 140efe7daf2SSergei Shtylyov result = ohci_run(ohci); 141efe7daf2SSergei Shtylyov if (result < 0) 142efe7daf2SSergei Shtylyov ohci_da8xx_stop(hcd); 143efe7daf2SSergei Shtylyov 144efe7daf2SSergei Shtylyov return result; 145efe7daf2SSergei Shtylyov } 146efe7daf2SSergei Shtylyov 147efe7daf2SSergei Shtylyov /* 148efe7daf2SSergei Shtylyov * Update the status data from the hub with the over-current indicator change. 149efe7daf2SSergei Shtylyov */ 150efe7daf2SSergei Shtylyov static int ohci_da8xx_hub_status_data(struct usb_hcd *hcd, char *buf) 151efe7daf2SSergei Shtylyov { 152efe7daf2SSergei Shtylyov int length = ohci_hub_status_data(hcd, buf); 153efe7daf2SSergei Shtylyov 154efe7daf2SSergei Shtylyov /* See if we have OCIC bit set on port 1 */ 155efe7daf2SSergei Shtylyov if (ocic_mask & (1 << 1)) { 156efe7daf2SSergei Shtylyov dev_dbg(hcd->self.controller, "over-current indicator change " 157efe7daf2SSergei Shtylyov "on port 1\n"); 158efe7daf2SSergei Shtylyov 159efe7daf2SSergei Shtylyov if (!length) 160efe7daf2SSergei Shtylyov length = 1; 161efe7daf2SSergei Shtylyov 162efe7daf2SSergei Shtylyov buf[0] |= 1 << 1; 163efe7daf2SSergei Shtylyov } 164efe7daf2SSergei Shtylyov return length; 165efe7daf2SSergei Shtylyov } 166efe7daf2SSergei Shtylyov 167efe7daf2SSergei Shtylyov /* 168efe7daf2SSergei Shtylyov * Look at the control requests to the root hub and see if we need to override. 169efe7daf2SSergei Shtylyov */ 170efe7daf2SSergei Shtylyov static int ohci_da8xx_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, 171efe7daf2SSergei Shtylyov u16 wIndex, char *buf, u16 wLength) 172efe7daf2SSergei Shtylyov { 173efe7daf2SSergei Shtylyov struct device *dev = hcd->self.controller; 174d4f09e28SJingoo Han struct da8xx_ohci_root_hub *hub = dev_get_platdata(dev); 175efe7daf2SSergei Shtylyov int temp; 176efe7daf2SSergei Shtylyov 177efe7daf2SSergei Shtylyov switch (typeReq) { 178efe7daf2SSergei Shtylyov case GetPortStatus: 179efe7daf2SSergei Shtylyov /* Check the port number */ 180efe7daf2SSergei Shtylyov if (wIndex != 1) 181efe7daf2SSergei Shtylyov break; 182efe7daf2SSergei Shtylyov 183efe7daf2SSergei Shtylyov dev_dbg(dev, "GetPortStatus(%u)\n", wIndex); 184efe7daf2SSergei Shtylyov 185efe7daf2SSergei Shtylyov temp = roothub_portstatus(hcd_to_ohci(hcd), wIndex - 1); 186efe7daf2SSergei Shtylyov 187efe7daf2SSergei Shtylyov /* The port power status (PPS) bit defaults to 1 */ 188efe7daf2SSergei Shtylyov if (hub->get_power && hub->get_power(wIndex) == 0) 189efe7daf2SSergei Shtylyov temp &= ~RH_PS_PPS; 190efe7daf2SSergei Shtylyov 191efe7daf2SSergei Shtylyov /* The port over-current indicator (POCI) bit is always 0 */ 192efe7daf2SSergei Shtylyov if (hub->get_oci && hub->get_oci(wIndex) > 0) 193efe7daf2SSergei Shtylyov temp |= RH_PS_POCI; 194efe7daf2SSergei Shtylyov 195efe7daf2SSergei Shtylyov /* The over-current indicator change (OCIC) bit is 0 too */ 196efe7daf2SSergei Shtylyov if (ocic_mask & (1 << wIndex)) 197efe7daf2SSergei Shtylyov temp |= RH_PS_OCIC; 198efe7daf2SSergei Shtylyov 199efe7daf2SSergei Shtylyov put_unaligned(cpu_to_le32(temp), (__le32 *)buf); 200efe7daf2SSergei Shtylyov return 0; 201efe7daf2SSergei Shtylyov case SetPortFeature: 202efe7daf2SSergei Shtylyov temp = 1; 203efe7daf2SSergei Shtylyov goto check_port; 204efe7daf2SSergei Shtylyov case ClearPortFeature: 205efe7daf2SSergei Shtylyov temp = 0; 206efe7daf2SSergei Shtylyov 207efe7daf2SSergei Shtylyov check_port: 208efe7daf2SSergei Shtylyov /* Check the port number */ 209efe7daf2SSergei Shtylyov if (wIndex != 1) 210efe7daf2SSergei Shtylyov break; 211efe7daf2SSergei Shtylyov 212efe7daf2SSergei Shtylyov switch (wValue) { 213efe7daf2SSergei Shtylyov case USB_PORT_FEAT_POWER: 214efe7daf2SSergei Shtylyov dev_dbg(dev, "%sPortFeature(%u): %s\n", 215efe7daf2SSergei Shtylyov temp ? "Set" : "Clear", wIndex, "POWER"); 216efe7daf2SSergei Shtylyov 217efe7daf2SSergei Shtylyov if (!hub->set_power) 218efe7daf2SSergei Shtylyov return -EPIPE; 219efe7daf2SSergei Shtylyov 220efe7daf2SSergei Shtylyov return hub->set_power(wIndex, temp) ? -EPIPE : 0; 221efe7daf2SSergei Shtylyov case USB_PORT_FEAT_C_OVER_CURRENT: 222efe7daf2SSergei Shtylyov dev_dbg(dev, "%sPortFeature(%u): %s\n", 223efe7daf2SSergei Shtylyov temp ? "Set" : "Clear", wIndex, 224efe7daf2SSergei Shtylyov "C_OVER_CURRENT"); 225efe7daf2SSergei Shtylyov 226efe7daf2SSergei Shtylyov if (temp) 227efe7daf2SSergei Shtylyov ocic_mask |= 1 << wIndex; 228efe7daf2SSergei Shtylyov else 229efe7daf2SSergei Shtylyov ocic_mask &= ~(1 << wIndex); 230efe7daf2SSergei Shtylyov return 0; 231efe7daf2SSergei Shtylyov } 232efe7daf2SSergei Shtylyov } 233efe7daf2SSergei Shtylyov 234efe7daf2SSergei Shtylyov return ohci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength); 235efe7daf2SSergei Shtylyov } 236efe7daf2SSergei Shtylyov 237efe7daf2SSergei Shtylyov static const struct hc_driver ohci_da8xx_hc_driver = { 238efe7daf2SSergei Shtylyov .description = hcd_name, 239efe7daf2SSergei Shtylyov .product_desc = "DA8xx OHCI", 240efe7daf2SSergei Shtylyov .hcd_priv_size = sizeof(struct ohci_hcd), 241efe7daf2SSergei Shtylyov 242efe7daf2SSergei Shtylyov /* 243efe7daf2SSergei Shtylyov * generic hardware linkage 244efe7daf2SSergei Shtylyov */ 245efe7daf2SSergei Shtylyov .irq = ohci_irq, 246efe7daf2SSergei Shtylyov .flags = HCD_USB11 | HCD_MEMORY, 247efe7daf2SSergei Shtylyov 248efe7daf2SSergei Shtylyov /* 249efe7daf2SSergei Shtylyov * basic lifecycle operations 250efe7daf2SSergei Shtylyov */ 251efe7daf2SSergei Shtylyov .reset = ohci_da8xx_init, 252efe7daf2SSergei Shtylyov .start = ohci_da8xx_start, 253efe7daf2SSergei Shtylyov .stop = ohci_da8xx_stop, 254efe7daf2SSergei Shtylyov .shutdown = ohci_shutdown, 255efe7daf2SSergei Shtylyov 256efe7daf2SSergei Shtylyov /* 257efe7daf2SSergei Shtylyov * managing i/o requests and associated device resources 258efe7daf2SSergei Shtylyov */ 259efe7daf2SSergei Shtylyov .urb_enqueue = ohci_urb_enqueue, 260efe7daf2SSergei Shtylyov .urb_dequeue = ohci_urb_dequeue, 261efe7daf2SSergei Shtylyov .endpoint_disable = ohci_endpoint_disable, 262efe7daf2SSergei Shtylyov 263efe7daf2SSergei Shtylyov /* 264efe7daf2SSergei Shtylyov * scheduling support 265efe7daf2SSergei Shtylyov */ 266efe7daf2SSergei Shtylyov .get_frame_number = ohci_get_frame, 267efe7daf2SSergei Shtylyov 268efe7daf2SSergei Shtylyov /* 269efe7daf2SSergei Shtylyov * root hub support 270efe7daf2SSergei Shtylyov */ 271efe7daf2SSergei Shtylyov .hub_status_data = ohci_da8xx_hub_status_data, 272efe7daf2SSergei Shtylyov .hub_control = ohci_da8xx_hub_control, 273efe7daf2SSergei Shtylyov 274efe7daf2SSergei Shtylyov #ifdef CONFIG_PM 275efe7daf2SSergei Shtylyov .bus_suspend = ohci_bus_suspend, 276efe7daf2SSergei Shtylyov .bus_resume = ohci_bus_resume, 277efe7daf2SSergei Shtylyov #endif 278efe7daf2SSergei Shtylyov .start_port_reset = ohci_start_port_reset, 279efe7daf2SSergei Shtylyov }; 280efe7daf2SSergei Shtylyov 281efe7daf2SSergei Shtylyov /*-------------------------------------------------------------------------*/ 282efe7daf2SSergei Shtylyov 283efe7daf2SSergei Shtylyov 284efe7daf2SSergei Shtylyov /** 285efe7daf2SSergei Shtylyov * usb_hcd_da8xx_probe - initialize DA8xx-based HCDs 286efe7daf2SSergei Shtylyov * Context: !in_interrupt() 287efe7daf2SSergei Shtylyov * 288efe7daf2SSergei Shtylyov * Allocates basic resources for this USB host controller, and 289efe7daf2SSergei Shtylyov * then invokes the start() method for the HCD associated with it 290efe7daf2SSergei Shtylyov * through the hotplug entry's driver_data. 291efe7daf2SSergei Shtylyov */ 292efe7daf2SSergei Shtylyov static int usb_hcd_da8xx_probe(const struct hc_driver *driver, 293efe7daf2SSergei Shtylyov struct platform_device *pdev) 294efe7daf2SSergei Shtylyov { 295d4f09e28SJingoo Han struct da8xx_ohci_root_hub *hub = dev_get_platdata(&pdev->dev); 296efe7daf2SSergei Shtylyov struct usb_hcd *hcd; 297efe7daf2SSergei Shtylyov struct resource *mem; 298efe7daf2SSergei Shtylyov int error, irq; 299efe7daf2SSergei Shtylyov 300efe7daf2SSergei Shtylyov if (hub == NULL) 301efe7daf2SSergei Shtylyov return -ENODEV; 302efe7daf2SSergei Shtylyov 303644db166SJingoo Han usb11_clk = devm_clk_get(&pdev->dev, "usb11"); 304efe7daf2SSergei Shtylyov if (IS_ERR(usb11_clk)) 305efe7daf2SSergei Shtylyov return PTR_ERR(usb11_clk); 306efe7daf2SSergei Shtylyov 307644db166SJingoo Han usb20_clk = devm_clk_get(&pdev->dev, "usb20"); 308644db166SJingoo Han if (IS_ERR(usb20_clk)) 309644db166SJingoo Han return PTR_ERR(usb20_clk); 310efe7daf2SSergei Shtylyov 311efe7daf2SSergei Shtylyov hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev)); 312644db166SJingoo Han if (!hcd) 313644db166SJingoo Han return -ENOMEM; 314efe7daf2SSergei Shtylyov 315efe7daf2SSergei Shtylyov mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); 316644db166SJingoo Han hcd->regs = devm_ioremap_resource(&pdev->dev, mem); 317644db166SJingoo Han if (IS_ERR(hcd->regs)) { 318644db166SJingoo Han error = PTR_ERR(hcd->regs); 319644db166SJingoo Han goto err; 320efe7daf2SSergei Shtylyov } 321*54891d74SVarka Bhadram hcd->rsrc_start = mem->start; 322*54891d74SVarka Bhadram hcd->rsrc_len = resource_size(mem); 323efe7daf2SSergei Shtylyov 324efe7daf2SSergei Shtylyov ohci_hcd_init(hcd_to_ohci(hcd)); 325efe7daf2SSergei Shtylyov 326efe7daf2SSergei Shtylyov irq = platform_get_irq(pdev, 0); 327efe7daf2SSergei Shtylyov if (irq < 0) { 328efe7daf2SSergei Shtylyov error = -ENODEV; 329644db166SJingoo Han goto err; 330efe7daf2SSergei Shtylyov } 331b5dd18d8SYong Zhang error = usb_add_hcd(hcd, irq, 0); 332efe7daf2SSergei Shtylyov if (error) 333644db166SJingoo Han goto err; 334efe7daf2SSergei Shtylyov 3353c9740a1SPeter Chen device_wakeup_enable(hcd->self.controller); 3363c9740a1SPeter Chen 337efe7daf2SSergei Shtylyov if (hub->ocic_notify) { 338efe7daf2SSergei Shtylyov error = hub->ocic_notify(ohci_da8xx_ocic_handler); 339efe7daf2SSergei Shtylyov if (!error) 340efe7daf2SSergei Shtylyov return 0; 341efe7daf2SSergei Shtylyov } 342efe7daf2SSergei Shtylyov 343efe7daf2SSergei Shtylyov usb_remove_hcd(hcd); 344644db166SJingoo Han err: 345efe7daf2SSergei Shtylyov usb_put_hcd(hcd); 346efe7daf2SSergei Shtylyov return error; 347efe7daf2SSergei Shtylyov } 348efe7daf2SSergei Shtylyov 349efe7daf2SSergei Shtylyov /** 350efe7daf2SSergei Shtylyov * usb_hcd_da8xx_remove - shutdown processing for DA8xx-based HCDs 351efe7daf2SSergei Shtylyov * @dev: USB Host Controller being removed 352efe7daf2SSergei Shtylyov * Context: !in_interrupt() 353efe7daf2SSergei Shtylyov * 354efe7daf2SSergei Shtylyov * Reverses the effect of usb_hcd_da8xx_probe(), first invoking 355efe7daf2SSergei Shtylyov * the HCD's stop() method. It is always called from a thread 356efe7daf2SSergei Shtylyov * context, normally "rmmod", "apmd", or something similar. 357efe7daf2SSergei Shtylyov */ 358efe7daf2SSergei Shtylyov static inline void 359efe7daf2SSergei Shtylyov usb_hcd_da8xx_remove(struct usb_hcd *hcd, struct platform_device *pdev) 360efe7daf2SSergei Shtylyov { 361d4f09e28SJingoo Han struct da8xx_ohci_root_hub *hub = dev_get_platdata(&pdev->dev); 362efe7daf2SSergei Shtylyov 363efe7daf2SSergei Shtylyov hub->ocic_notify(NULL); 364efe7daf2SSergei Shtylyov usb_remove_hcd(hcd); 365efe7daf2SSergei Shtylyov usb_put_hcd(hcd); 366efe7daf2SSergei Shtylyov } 367efe7daf2SSergei Shtylyov 368efe7daf2SSergei Shtylyov static int ohci_hcd_da8xx_drv_probe(struct platform_device *dev) 369efe7daf2SSergei Shtylyov { 370efe7daf2SSergei Shtylyov return usb_hcd_da8xx_probe(&ohci_da8xx_hc_driver, dev); 371efe7daf2SSergei Shtylyov } 372efe7daf2SSergei Shtylyov 373efe7daf2SSergei Shtylyov static int ohci_hcd_da8xx_drv_remove(struct platform_device *dev) 374efe7daf2SSergei Shtylyov { 375efe7daf2SSergei Shtylyov struct usb_hcd *hcd = platform_get_drvdata(dev); 376efe7daf2SSergei Shtylyov 377efe7daf2SSergei Shtylyov usb_hcd_da8xx_remove(hcd, dev); 378efe7daf2SSergei Shtylyov 379efe7daf2SSergei Shtylyov return 0; 380efe7daf2SSergei Shtylyov } 381efe7daf2SSergei Shtylyov 382efe7daf2SSergei Shtylyov #ifdef CONFIG_PM 383933bb1f0SMajunath Goudar static int ohci_da8xx_suspend(struct platform_device *pdev, 384933bb1f0SMajunath Goudar pm_message_t message) 385efe7daf2SSergei Shtylyov { 386933bb1f0SMajunath Goudar struct usb_hcd *hcd = platform_get_drvdata(pdev); 387efe7daf2SSergei Shtylyov struct ohci_hcd *ohci = hcd_to_ohci(hcd); 388933bb1f0SMajunath Goudar bool do_wakeup = device_may_wakeup(&pdev->dev); 389933bb1f0SMajunath Goudar int ret; 390933bb1f0SMajunath Goudar 391efe7daf2SSergei Shtylyov 392efe7daf2SSergei Shtylyov if (time_before(jiffies, ohci->next_statechange)) 393efe7daf2SSergei Shtylyov msleep(5); 394efe7daf2SSergei Shtylyov ohci->next_statechange = jiffies; 395efe7daf2SSergei Shtylyov 396933bb1f0SMajunath Goudar ret = ohci_suspend(hcd, do_wakeup); 397933bb1f0SMajunath Goudar if (ret) 398933bb1f0SMajunath Goudar return ret; 399933bb1f0SMajunath Goudar 400efe7daf2SSergei Shtylyov ohci_da8xx_clock(0); 401efe7daf2SSergei Shtylyov hcd->state = HC_STATE_SUSPENDED; 402933bb1f0SMajunath Goudar 403933bb1f0SMajunath Goudar return ret; 404efe7daf2SSergei Shtylyov } 405efe7daf2SSergei Shtylyov 406efe7daf2SSergei Shtylyov static int ohci_da8xx_resume(struct platform_device *dev) 407efe7daf2SSergei Shtylyov { 408efe7daf2SSergei Shtylyov struct usb_hcd *hcd = platform_get_drvdata(dev); 409efe7daf2SSergei Shtylyov struct ohci_hcd *ohci = hcd_to_ohci(hcd); 410efe7daf2SSergei Shtylyov 411efe7daf2SSergei Shtylyov if (time_before(jiffies, ohci->next_statechange)) 412efe7daf2SSergei Shtylyov msleep(5); 413efe7daf2SSergei Shtylyov ohci->next_statechange = jiffies; 414efe7daf2SSergei Shtylyov 415efe7daf2SSergei Shtylyov ohci_da8xx_clock(1); 416efe7daf2SSergei Shtylyov dev->dev.power.power_state = PMSG_ON; 417efe7daf2SSergei Shtylyov usb_hcd_resume_root_hub(hcd); 418efe7daf2SSergei Shtylyov return 0; 419efe7daf2SSergei Shtylyov } 420efe7daf2SSergei Shtylyov #endif 421efe7daf2SSergei Shtylyov 422efe7daf2SSergei Shtylyov /* 423efe7daf2SSergei Shtylyov * Driver definition to register with platform structure. 424efe7daf2SSergei Shtylyov */ 425efe7daf2SSergei Shtylyov static struct platform_driver ohci_hcd_da8xx_driver = { 426efe7daf2SSergei Shtylyov .probe = ohci_hcd_da8xx_drv_probe, 427efe7daf2SSergei Shtylyov .remove = ohci_hcd_da8xx_drv_remove, 428efe7daf2SSergei Shtylyov .shutdown = usb_hcd_platform_shutdown, 429efe7daf2SSergei Shtylyov #ifdef CONFIG_PM 430efe7daf2SSergei Shtylyov .suspend = ohci_da8xx_suspend, 431efe7daf2SSergei Shtylyov .resume = ohci_da8xx_resume, 432efe7daf2SSergei Shtylyov #endif 433efe7daf2SSergei Shtylyov .driver = { 434efe7daf2SSergei Shtylyov .owner = THIS_MODULE, 435efe7daf2SSergei Shtylyov .name = "ohci", 436efe7daf2SSergei Shtylyov }, 437efe7daf2SSergei Shtylyov }; 438ab59ac01SJan Luebbe 439ab59ac01SJan Luebbe MODULE_ALIAS("platform:ohci"); 440