ohci-jz4740.c revision 41ac7b3ab7fe1d6175839947a877fdf95cbd2211
1/* 2 * Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de> 3 * 4 * This program is free software; you can redistribute it and/or modify it 5 * under the terms of the GNU General Public License as published by the 6 * Free Software Foundation; either version 2 of the License, or (at your 7 * option) any later version. 8 * 9 * You should have received a copy of the GNU General Public License along 10 * with this program; if not, write to the Free Software Foundation, Inc., 11 * 675 Mass Ave, Cambridge, MA 02139, USA. 12 * 13 */ 14 15#include <linux/platform_device.h> 16#include <linux/clk.h> 17#include <linux/regulator/consumer.h> 18 19struct jz4740_ohci_hcd { 20 struct ohci_hcd ohci_hcd; 21 22 struct regulator *vbus; 23 bool vbus_enabled; 24 struct clk *clk; 25}; 26 27static inline struct jz4740_ohci_hcd *hcd_to_jz4740_hcd(struct usb_hcd *hcd) 28{ 29 return (struct jz4740_ohci_hcd *)(hcd->hcd_priv); 30} 31 32static inline struct usb_hcd *jz4740_hcd_to_hcd(struct jz4740_ohci_hcd *jz4740_ohci) 33{ 34 return container_of((void *)jz4740_ohci, struct usb_hcd, hcd_priv); 35} 36 37static int ohci_jz4740_start(struct usb_hcd *hcd) 38{ 39 struct ohci_hcd *ohci = hcd_to_ohci(hcd); 40 int ret; 41 42 ret = ohci_init(ohci); 43 if (ret < 0) 44 return ret; 45 46 ohci->num_ports = 1; 47 48 ret = ohci_run(ohci); 49 if (ret < 0) { 50 dev_err(hcd->self.controller, "Can not start %s", 51 hcd->self.bus_name); 52 ohci_stop(hcd); 53 return ret; 54 } 55 return 0; 56} 57 58static int ohci_jz4740_set_vbus_power(struct jz4740_ohci_hcd *jz4740_ohci, 59 bool enabled) 60{ 61 int ret = 0; 62 63 if (!jz4740_ohci->vbus) 64 return 0; 65 66 if (enabled && !jz4740_ohci->vbus_enabled) { 67 ret = regulator_enable(jz4740_ohci->vbus); 68 if (ret) 69 dev_err(jz4740_hcd_to_hcd(jz4740_ohci)->self.controller, 70 "Could not power vbus\n"); 71 } else if (!enabled && jz4740_ohci->vbus_enabled) { 72 ret = regulator_disable(jz4740_ohci->vbus); 73 } 74 75 if (ret == 0) 76 jz4740_ohci->vbus_enabled = enabled; 77 78 return ret; 79} 80 81static int ohci_jz4740_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, 82 u16 wIndex, char *buf, u16 wLength) 83{ 84 struct jz4740_ohci_hcd *jz4740_ohci = hcd_to_jz4740_hcd(hcd); 85 int ret; 86 87 switch (typeReq) { 88 case SetHubFeature: 89 if (wValue == USB_PORT_FEAT_POWER) 90 ret = ohci_jz4740_set_vbus_power(jz4740_ohci, true); 91 break; 92 case ClearHubFeature: 93 if (wValue == USB_PORT_FEAT_POWER) 94 ret = ohci_jz4740_set_vbus_power(jz4740_ohci, false); 95 break; 96 } 97 98 if (ret) 99 return ret; 100 101 return ohci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength); 102} 103 104 105static const struct hc_driver ohci_jz4740_hc_driver = { 106 .description = hcd_name, 107 .product_desc = "JZ4740 OHCI", 108 .hcd_priv_size = sizeof(struct jz4740_ohci_hcd), 109 110 /* 111 * generic hardware linkage 112 */ 113 .irq = ohci_irq, 114 .flags = HCD_USB11 | HCD_MEMORY, 115 116 /* 117 * basic lifecycle operations 118 */ 119 .start = ohci_jz4740_start, 120 .stop = ohci_stop, 121 .shutdown = ohci_shutdown, 122 123 /* 124 * managing i/o requests and associated device resources 125 */ 126 .urb_enqueue = ohci_urb_enqueue, 127 .urb_dequeue = ohci_urb_dequeue, 128 .endpoint_disable = ohci_endpoint_disable, 129 130 /* 131 * scheduling support 132 */ 133 .get_frame_number = ohci_get_frame, 134 135 /* 136 * root hub support 137 */ 138 .hub_status_data = ohci_hub_status_data, 139 .hub_control = ohci_jz4740_hub_control, 140#ifdef CONFIG_PM 141 .bus_suspend = ohci_bus_suspend, 142 .bus_resume = ohci_bus_resume, 143#endif 144 .start_port_reset = ohci_start_port_reset, 145}; 146 147 148static int jz4740_ohci_probe(struct platform_device *pdev) 149{ 150 int ret; 151 struct usb_hcd *hcd; 152 struct jz4740_ohci_hcd *jz4740_ohci; 153 struct resource *res; 154 int irq; 155 156 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 157 158 if (!res) { 159 dev_err(&pdev->dev, "Failed to get platform resource\n"); 160 return -ENOENT; 161 } 162 163 irq = platform_get_irq(pdev, 0); 164 if (irq < 0) { 165 dev_err(&pdev->dev, "Failed to get platform irq\n"); 166 return irq; 167 } 168 169 hcd = usb_create_hcd(&ohci_jz4740_hc_driver, &pdev->dev, "jz4740"); 170 if (!hcd) { 171 dev_err(&pdev->dev, "Failed to create hcd.\n"); 172 return -ENOMEM; 173 } 174 175 jz4740_ohci = hcd_to_jz4740_hcd(hcd); 176 177 res = request_mem_region(res->start, resource_size(res), hcd_name); 178 if (!res) { 179 dev_err(&pdev->dev, "Failed to request mem region.\n"); 180 ret = -EBUSY; 181 goto err_free; 182 } 183 184 hcd->rsrc_start = res->start; 185 hcd->rsrc_len = resource_size(res); 186 hcd->regs = ioremap(res->start, resource_size(res)); 187 188 if (!hcd->regs) { 189 dev_err(&pdev->dev, "Failed to ioremap registers.\n"); 190 ret = -EBUSY; 191 goto err_release_mem; 192 } 193 194 jz4740_ohci->clk = clk_get(&pdev->dev, "uhc"); 195 if (IS_ERR(jz4740_ohci->clk)) { 196 ret = PTR_ERR(jz4740_ohci->clk); 197 dev_err(&pdev->dev, "Failed to get clock: %d\n", ret); 198 goto err_iounmap; 199 } 200 201 jz4740_ohci->vbus = regulator_get(&pdev->dev, "vbus"); 202 if (IS_ERR(jz4740_ohci->vbus)) 203 jz4740_ohci->vbus = NULL; 204 205 206 clk_set_rate(jz4740_ohci->clk, 48000000); 207 clk_enable(jz4740_ohci->clk); 208 if (jz4740_ohci->vbus) 209 ohci_jz4740_set_vbus_power(jz4740_ohci, true); 210 211 platform_set_drvdata(pdev, hcd); 212 213 ohci_hcd_init(hcd_to_ohci(hcd)); 214 215 ret = usb_add_hcd(hcd, irq, 0); 216 if (ret) { 217 dev_err(&pdev->dev, "Failed to add hcd: %d\n", ret); 218 goto err_disable; 219 } 220 221 return 0; 222 223err_disable: 224 platform_set_drvdata(pdev, NULL); 225 if (jz4740_ohci->vbus) { 226 regulator_disable(jz4740_ohci->vbus); 227 regulator_put(jz4740_ohci->vbus); 228 } 229 clk_disable(jz4740_ohci->clk); 230 231 clk_put(jz4740_ohci->clk); 232err_iounmap: 233 iounmap(hcd->regs); 234err_release_mem: 235 release_mem_region(res->start, resource_size(res)); 236err_free: 237 usb_put_hcd(hcd); 238 239 return ret; 240} 241 242static __devexit int jz4740_ohci_remove(struct platform_device *pdev) 243{ 244 struct usb_hcd *hcd = platform_get_drvdata(pdev); 245 struct jz4740_ohci_hcd *jz4740_ohci = hcd_to_jz4740_hcd(hcd); 246 247 usb_remove_hcd(hcd); 248 249 platform_set_drvdata(pdev, NULL); 250 251 if (jz4740_ohci->vbus) { 252 regulator_disable(jz4740_ohci->vbus); 253 regulator_put(jz4740_ohci->vbus); 254 } 255 256 clk_disable(jz4740_ohci->clk); 257 clk_put(jz4740_ohci->clk); 258 259 iounmap(hcd->regs); 260 release_mem_region(hcd->rsrc_start, hcd->rsrc_len); 261 262 usb_put_hcd(hcd); 263 264 return 0; 265} 266 267static struct platform_driver ohci_hcd_jz4740_driver = { 268 .probe = jz4740_ohci_probe, 269 .remove = jz4740_ohci_remove, 270 .driver = { 271 .name = "jz4740-ohci", 272 .owner = THIS_MODULE, 273 }, 274}; 275 276MODULE_ALIAS("platform:jz4740-ohci"); 277