1 /* 2 * OHCI HCD (Host Controller Driver) for USB. 3 * 4 * Copyright (C) 2010 ST Microelectronics. 5 * Deepak Sikri<deepak.sikri@st.com> 6 * 7 * Based on various ohci-*.c drivers 8 * 9 * This file is licensed under the terms of the GNU General Public 10 * License version 2. This program is licensed "as is" without any 11 * warranty of any kind, whether express or implied. 12 */ 13 14 #include <linux/signal.h> 15 #include <linux/platform_device.h> 16 #include <linux/clk.h> 17 #include <linux/of.h> 18 19 struct spear_ohci { 20 struct ohci_hcd ohci; 21 struct clk *clk; 22 }; 23 24 #define to_spear_ohci(hcd) (struct spear_ohci *)hcd_to_ohci(hcd) 25 26 static void spear_start_ohci(struct spear_ohci *ohci) 27 { 28 clk_prepare_enable(ohci->clk); 29 } 30 31 static void spear_stop_ohci(struct spear_ohci *ohci) 32 { 33 clk_disable_unprepare(ohci->clk); 34 } 35 36 static int ohci_spear_start(struct usb_hcd *hcd) 37 { 38 struct ohci_hcd *ohci = hcd_to_ohci(hcd); 39 int ret; 40 41 ret = ohci_init(ohci); 42 if (ret < 0) 43 return ret; 44 ohci->regs = hcd->regs; 45 46 ret = ohci_run(ohci); 47 if (ret < 0) { 48 dev_err(hcd->self.controller, "can't start\n"); 49 ohci_stop(hcd); 50 return ret; 51 } 52 53 create_debug_files(ohci); 54 55 #ifdef DEBUG 56 ohci_dump(ohci, 1); 57 #endif 58 return 0; 59 } 60 61 static const struct hc_driver ohci_spear_hc_driver = { 62 .description = hcd_name, 63 .product_desc = "SPEAr OHCI", 64 .hcd_priv_size = sizeof(struct spear_ohci), 65 66 /* generic hardware linkage */ 67 .irq = ohci_irq, 68 .flags = HCD_USB11 | HCD_MEMORY, 69 70 /* basic lifecycle operations */ 71 .start = ohci_spear_start, 72 .stop = ohci_stop, 73 .shutdown = ohci_shutdown, 74 #ifdef CONFIG_PM 75 .bus_suspend = ohci_bus_suspend, 76 .bus_resume = ohci_bus_resume, 77 #endif 78 79 /* managing i/o requests and associated device resources */ 80 .urb_enqueue = ohci_urb_enqueue, 81 .urb_dequeue = ohci_urb_dequeue, 82 .endpoint_disable = ohci_endpoint_disable, 83 84 /* scheduling support */ 85 .get_frame_number = ohci_get_frame, 86 87 /* root hub support */ 88 .hub_status_data = ohci_hub_status_data, 89 .hub_control = ohci_hub_control, 90 91 .start_port_reset = ohci_start_port_reset, 92 }; 93 94 static int spear_ohci_hcd_drv_probe(struct platform_device *pdev) 95 { 96 const struct hc_driver *driver = &ohci_spear_hc_driver; 97 struct usb_hcd *hcd = NULL; 98 struct clk *usbh_clk; 99 struct spear_ohci *ohci_p; 100 struct resource *res; 101 int retval, irq; 102 103 irq = platform_get_irq(pdev, 0); 104 if (irq < 0) { 105 retval = irq; 106 goto fail; 107 } 108 109 /* 110 * Right now device-tree probed devices don't get dma_mask set. 111 * Since shared usb code relies on it, set it here for now. 112 * Once we have dma capability bindings this can go away. 113 */ 114 if (!pdev->dev.dma_mask) 115 pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask; 116 if (!pdev->dev.coherent_dma_mask) 117 pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); 118 119 usbh_clk = devm_clk_get(&pdev->dev, NULL); 120 if (IS_ERR(usbh_clk)) { 121 dev_err(&pdev->dev, "Error getting interface clock\n"); 122 retval = PTR_ERR(usbh_clk); 123 goto fail; 124 } 125 126 hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev)); 127 if (!hcd) { 128 retval = -ENOMEM; 129 goto fail; 130 } 131 132 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 133 if (!res) { 134 retval = -ENODEV; 135 goto err_put_hcd; 136 } 137 138 hcd->rsrc_start = pdev->resource[0].start; 139 hcd->rsrc_len = resource_size(res); 140 if (!devm_request_mem_region(&pdev->dev, hcd->rsrc_start, hcd->rsrc_len, 141 hcd_name)) { 142 dev_dbg(&pdev->dev, "request_mem_region failed\n"); 143 retval = -EBUSY; 144 goto err_put_hcd; 145 } 146 147 hcd->regs = devm_ioremap(&pdev->dev, hcd->rsrc_start, hcd->rsrc_len); 148 if (!hcd->regs) { 149 dev_dbg(&pdev->dev, "ioremap failed\n"); 150 retval = -ENOMEM; 151 goto err_put_hcd; 152 } 153 154 ohci_p = (struct spear_ohci *)hcd_to_ohci(hcd); 155 ohci_p->clk = usbh_clk; 156 spear_start_ohci(ohci_p); 157 ohci_hcd_init(hcd_to_ohci(hcd)); 158 159 retval = usb_add_hcd(hcd, platform_get_irq(pdev, 0), 0); 160 if (retval == 0) 161 return retval; 162 163 spear_stop_ohci(ohci_p); 164 err_put_hcd: 165 usb_put_hcd(hcd); 166 fail: 167 dev_err(&pdev->dev, "init fail, %d\n", retval); 168 169 return retval; 170 } 171 172 static int spear_ohci_hcd_drv_remove(struct platform_device *pdev) 173 { 174 struct usb_hcd *hcd = platform_get_drvdata(pdev); 175 struct spear_ohci *ohci_p = to_spear_ohci(hcd); 176 177 usb_remove_hcd(hcd); 178 if (ohci_p->clk) 179 spear_stop_ohci(ohci_p); 180 181 usb_put_hcd(hcd); 182 183 platform_set_drvdata(pdev, NULL); 184 return 0; 185 } 186 187 #if defined(CONFIG_PM) 188 static int spear_ohci_hcd_drv_suspend(struct platform_device *dev, 189 pm_message_t message) 190 { 191 struct usb_hcd *hcd = platform_get_drvdata(dev); 192 struct ohci_hcd *ohci = hcd_to_ohci(hcd); 193 struct spear_ohci *ohci_p = to_spear_ohci(hcd); 194 195 if (time_before(jiffies, ohci->next_statechange)) 196 msleep(5); 197 ohci->next_statechange = jiffies; 198 199 spear_stop_ohci(ohci_p); 200 return 0; 201 } 202 203 static int spear_ohci_hcd_drv_resume(struct platform_device *dev) 204 { 205 struct usb_hcd *hcd = platform_get_drvdata(dev); 206 struct ohci_hcd *ohci = hcd_to_ohci(hcd); 207 struct spear_ohci *ohci_p = to_spear_ohci(hcd); 208 209 if (time_before(jiffies, ohci->next_statechange)) 210 msleep(5); 211 ohci->next_statechange = jiffies; 212 213 spear_start_ohci(ohci_p); 214 ohci_resume(hcd, false); 215 return 0; 216 } 217 #endif 218 219 static struct of_device_id spear_ohci_id_table[] = { 220 { .compatible = "st,spear600-ohci", }, 221 { }, 222 }; 223 224 /* Driver definition to register with the platform bus */ 225 static struct platform_driver spear_ohci_hcd_driver = { 226 .probe = spear_ohci_hcd_drv_probe, 227 .remove = spear_ohci_hcd_drv_remove, 228 #ifdef CONFIG_PM 229 .suspend = spear_ohci_hcd_drv_suspend, 230 .resume = spear_ohci_hcd_drv_resume, 231 #endif 232 .driver = { 233 .owner = THIS_MODULE, 234 .name = "spear-ohci", 235 .of_match_table = of_match_ptr(spear_ohci_id_table), 236 }, 237 }; 238 239 MODULE_ALIAS("platform:spear-ohci"); 240