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 = 0; 86 87 switch (typeReq) { 88 case SetPortFeature: 89 if (wValue == USB_PORT_FEAT_POWER) 90 ret = ohci_jz4740_set_vbus_power(jz4740_ohci, true); 91 break; 92 case ClearPortFeature: 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 irq = platform_get_irq(pdev, 0); 157 if (irq < 0) { 158 dev_err(&pdev->dev, "Failed to get platform irq\n"); 159 return irq; 160 } 161 162 hcd = usb_create_hcd(&ohci_jz4740_hc_driver, &pdev->dev, "jz4740"); 163 if (!hcd) { 164 dev_err(&pdev->dev, "Failed to create hcd.\n"); 165 return -ENOMEM; 166 } 167 168 jz4740_ohci = hcd_to_jz4740_hcd(hcd); 169 170 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 171 hcd->regs = devm_ioremap_resource(&pdev->dev, res); 172 if (IS_ERR(hcd->regs)) { 173 ret = PTR_ERR(hcd->regs); 174 goto err_free; 175 } 176 hcd->rsrc_start = res->start; 177 hcd->rsrc_len = resource_size(res); 178 179 jz4740_ohci->clk = devm_clk_get(&pdev->dev, "uhc"); 180 if (IS_ERR(jz4740_ohci->clk)) { 181 ret = PTR_ERR(jz4740_ohci->clk); 182 dev_err(&pdev->dev, "Failed to get clock: %d\n", ret); 183 goto err_free; 184 } 185 186 jz4740_ohci->vbus = devm_regulator_get(&pdev->dev, "vbus"); 187 if (IS_ERR(jz4740_ohci->vbus)) 188 jz4740_ohci->vbus = NULL; 189 190 191 clk_set_rate(jz4740_ohci->clk, 48000000); 192 clk_enable(jz4740_ohci->clk); 193 if (jz4740_ohci->vbus) 194 ohci_jz4740_set_vbus_power(jz4740_ohci, true); 195 196 platform_set_drvdata(pdev, hcd); 197 198 ohci_hcd_init(hcd_to_ohci(hcd)); 199 200 ret = usb_add_hcd(hcd, irq, 0); 201 if (ret) { 202 dev_err(&pdev->dev, "Failed to add hcd: %d\n", ret); 203 goto err_disable; 204 } 205 device_wakeup_enable(hcd->self.controller); 206 207 return 0; 208 209err_disable: 210 if (jz4740_ohci->vbus) 211 regulator_disable(jz4740_ohci->vbus); 212 clk_disable(jz4740_ohci->clk); 213 214err_free: 215 usb_put_hcd(hcd); 216 217 return ret; 218} 219 220static int jz4740_ohci_remove(struct platform_device *pdev) 221{ 222 struct usb_hcd *hcd = platform_get_drvdata(pdev); 223 struct jz4740_ohci_hcd *jz4740_ohci = hcd_to_jz4740_hcd(hcd); 224 225 usb_remove_hcd(hcd); 226 227 if (jz4740_ohci->vbus) 228 regulator_disable(jz4740_ohci->vbus); 229 230 clk_disable(jz4740_ohci->clk); 231 232 usb_put_hcd(hcd); 233 234 return 0; 235} 236 237static struct platform_driver ohci_hcd_jz4740_driver = { 238 .probe = jz4740_ohci_probe, 239 .remove = jz4740_ohci_remove, 240 .driver = { 241 .name = "jz4740-ohci", 242 }, 243}; 244 245MODULE_ALIAS("platform:jz4740-ohci"); 246