diff options
Diffstat (limited to 'drivers/usb/phy/phy-tahvo.c')
-rw-r--r-- | drivers/usb/phy/phy-tahvo.c | 457 |
1 files changed, 457 insertions, 0 deletions
diff --git a/drivers/usb/phy/phy-tahvo.c b/drivers/usb/phy/phy-tahvo.c new file mode 100644 index 000000000000..cc61ee44b911 --- /dev/null +++ b/drivers/usb/phy/phy-tahvo.c | |||
@@ -0,0 +1,457 @@ | |||
1 | /* | ||
2 | * Tahvo USB transceiver driver | ||
3 | * | ||
4 | * Copyright (C) 2005-2006 Nokia Corporation | ||
5 | * | ||
6 | * Parts copied from isp1301_omap.c. | ||
7 | * Copyright (C) 2004 Texas Instruments | ||
8 | * Copyright (C) 2004 David Brownell | ||
9 | * | ||
10 | * Original driver written by Juha Yrjölä, Tony Lindgren and Timo Teräs. | ||
11 | * Modified for Retu/Tahvo MFD by Aaro Koskinen. | ||
12 | * | ||
13 | * This file is subject to the terms and conditions of the GNU General | ||
14 | * Public License. See the file "COPYING" in the main directory of this | ||
15 | * archive for more details. | ||
16 | * | ||
17 | * This program is distributed in the hope that it will be useful, | ||
18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
20 | * GNU General Public License for more details. | ||
21 | */ | ||
22 | |||
23 | #include <linux/io.h> | ||
24 | #include <linux/clk.h> | ||
25 | #include <linux/usb.h> | ||
26 | #include <linux/extcon.h> | ||
27 | #include <linux/kernel.h> | ||
28 | #include <linux/module.h> | ||
29 | #include <linux/usb/otg.h> | ||
30 | #include <linux/mfd/retu.h> | ||
31 | #include <linux/usb/gadget.h> | ||
32 | #include <linux/platform_device.h> | ||
33 | |||
34 | #define DRIVER_NAME "tahvo-usb" | ||
35 | |||
36 | #define TAHVO_REG_IDSR 0x02 | ||
37 | #define TAHVO_REG_USBR 0x06 | ||
38 | |||
39 | #define USBR_SLAVE_CONTROL (1 << 8) | ||
40 | #define USBR_VPPVIO_SW (1 << 7) | ||
41 | #define USBR_SPEED (1 << 6) | ||
42 | #define USBR_REGOUT (1 << 5) | ||
43 | #define USBR_MASTER_SW2 (1 << 4) | ||
44 | #define USBR_MASTER_SW1 (1 << 3) | ||
45 | #define USBR_SLAVE_SW (1 << 2) | ||
46 | #define USBR_NSUSPEND (1 << 1) | ||
47 | #define USBR_SEMODE (1 << 0) | ||
48 | |||
49 | #define TAHVO_MODE_HOST 0 | ||
50 | #define TAHVO_MODE_PERIPHERAL 1 | ||
51 | |||
52 | struct tahvo_usb { | ||
53 | struct platform_device *pt_dev; | ||
54 | struct usb_phy phy; | ||
55 | int vbus_state; | ||
56 | struct mutex serialize; | ||
57 | struct clk *ick; | ||
58 | int irq; | ||
59 | int tahvo_mode; | ||
60 | struct extcon_dev extcon; | ||
61 | }; | ||
62 | |||
63 | static const char *tahvo_cable[] = { | ||
64 | "USB-HOST", | ||
65 | "USB", | ||
66 | NULL, | ||
67 | }; | ||
68 | |||
69 | static ssize_t vbus_state_show(struct device *device, | ||
70 | struct device_attribute *attr, char *buf) | ||
71 | { | ||
72 | struct tahvo_usb *tu = dev_get_drvdata(device); | ||
73 | return sprintf(buf, "%s\n", tu->vbus_state ? "on" : "off"); | ||
74 | } | ||
75 | static DEVICE_ATTR(vbus, 0444, vbus_state_show, NULL); | ||
76 | |||
77 | static void check_vbus_state(struct tahvo_usb *tu) | ||
78 | { | ||
79 | struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent); | ||
80 | int reg, prev_state; | ||
81 | |||
82 | reg = retu_read(rdev, TAHVO_REG_IDSR); | ||
83 | if (reg & TAHVO_STAT_VBUS) { | ||
84 | switch (tu->phy.state) { | ||
85 | case OTG_STATE_B_IDLE: | ||
86 | /* Enable the gadget driver */ | ||
87 | if (tu->phy.otg->gadget) | ||
88 | usb_gadget_vbus_connect(tu->phy.otg->gadget); | ||
89 | tu->phy.state = OTG_STATE_B_PERIPHERAL; | ||
90 | break; | ||
91 | case OTG_STATE_A_IDLE: | ||
92 | /* | ||
93 | * Session is now valid assuming the USB hub is driving | ||
94 | * Vbus. | ||
95 | */ | ||
96 | tu->phy.state = OTG_STATE_A_HOST; | ||
97 | break; | ||
98 | default: | ||
99 | break; | ||
100 | } | ||
101 | dev_info(&tu->pt_dev->dev, "USB cable connected\n"); | ||
102 | } else { | ||
103 | switch (tu->phy.state) { | ||
104 | case OTG_STATE_B_PERIPHERAL: | ||
105 | if (tu->phy.otg->gadget) | ||
106 | usb_gadget_vbus_disconnect(tu->phy.otg->gadget); | ||
107 | tu->phy.state = OTG_STATE_B_IDLE; | ||
108 | break; | ||
109 | case OTG_STATE_A_HOST: | ||
110 | tu->phy.state = OTG_STATE_A_IDLE; | ||
111 | break; | ||
112 | default: | ||
113 | break; | ||
114 | } | ||
115 | dev_info(&tu->pt_dev->dev, "USB cable disconnected\n"); | ||
116 | } | ||
117 | |||
118 | prev_state = tu->vbus_state; | ||
119 | tu->vbus_state = reg & TAHVO_STAT_VBUS; | ||
120 | if (prev_state != tu->vbus_state) { | ||
121 | extcon_set_cable_state(&tu->extcon, "USB", tu->vbus_state); | ||
122 | sysfs_notify(&tu->pt_dev->dev.kobj, NULL, "vbus_state"); | ||
123 | } | ||
124 | } | ||
125 | |||
126 | static void tahvo_usb_become_host(struct tahvo_usb *tu) | ||
127 | { | ||
128 | struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent); | ||
129 | |||
130 | extcon_set_cable_state(&tu->extcon, "USB-HOST", true); | ||
131 | |||
132 | /* Power up the transceiver in USB host mode */ | ||
133 | retu_write(rdev, TAHVO_REG_USBR, USBR_REGOUT | USBR_NSUSPEND | | ||
134 | USBR_MASTER_SW2 | USBR_MASTER_SW1); | ||
135 | tu->phy.state = OTG_STATE_A_IDLE; | ||
136 | |||
137 | check_vbus_state(tu); | ||
138 | } | ||
139 | |||
140 | static void tahvo_usb_stop_host(struct tahvo_usb *tu) | ||
141 | { | ||
142 | tu->phy.state = OTG_STATE_A_IDLE; | ||
143 | } | ||
144 | |||
145 | static void tahvo_usb_become_peripheral(struct tahvo_usb *tu) | ||
146 | { | ||
147 | struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent); | ||
148 | |||
149 | extcon_set_cable_state(&tu->extcon, "USB-HOST", false); | ||
150 | |||
151 | /* Power up transceiver and set it in USB peripheral mode */ | ||
152 | retu_write(rdev, TAHVO_REG_USBR, USBR_SLAVE_CONTROL | USBR_REGOUT | | ||
153 | USBR_NSUSPEND | USBR_SLAVE_SW); | ||
154 | tu->phy.state = OTG_STATE_B_IDLE; | ||
155 | |||
156 | check_vbus_state(tu); | ||
157 | } | ||
158 | |||
159 | static void tahvo_usb_stop_peripheral(struct tahvo_usb *tu) | ||
160 | { | ||
161 | if (tu->phy.otg->gadget) | ||
162 | usb_gadget_vbus_disconnect(tu->phy.otg->gadget); | ||
163 | tu->phy.state = OTG_STATE_B_IDLE; | ||
164 | } | ||
165 | |||
166 | static void tahvo_usb_power_off(struct tahvo_usb *tu) | ||
167 | { | ||
168 | struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent); | ||
169 | |||
170 | /* Disable gadget controller if any */ | ||
171 | if (tu->phy.otg->gadget) | ||
172 | usb_gadget_vbus_disconnect(tu->phy.otg->gadget); | ||
173 | |||
174 | /* Power off transceiver */ | ||
175 | retu_write(rdev, TAHVO_REG_USBR, 0); | ||
176 | tu->phy.state = OTG_STATE_UNDEFINED; | ||
177 | } | ||
178 | |||
179 | static int tahvo_usb_set_suspend(struct usb_phy *dev, int suspend) | ||
180 | { | ||
181 | struct tahvo_usb *tu = container_of(dev, struct tahvo_usb, phy); | ||
182 | struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent); | ||
183 | u16 w; | ||
184 | |||
185 | dev_dbg(&tu->pt_dev->dev, "%s\n", __func__); | ||
186 | |||
187 | w = retu_read(rdev, TAHVO_REG_USBR); | ||
188 | if (suspend) | ||
189 | w &= ~USBR_NSUSPEND; | ||
190 | else | ||
191 | w |= USBR_NSUSPEND; | ||
192 | retu_write(rdev, TAHVO_REG_USBR, w); | ||
193 | |||
194 | return 0; | ||
195 | } | ||
196 | |||
197 | static int tahvo_usb_set_host(struct usb_otg *otg, struct usb_bus *host) | ||
198 | { | ||
199 | struct tahvo_usb *tu = container_of(otg->phy, struct tahvo_usb, phy); | ||
200 | |||
201 | dev_dbg(&tu->pt_dev->dev, "%s %p\n", __func__, host); | ||
202 | |||
203 | mutex_lock(&tu->serialize); | ||
204 | |||
205 | if (host == NULL) { | ||
206 | if (tu->tahvo_mode == TAHVO_MODE_HOST) | ||
207 | tahvo_usb_power_off(tu); | ||
208 | otg->host = NULL; | ||
209 | mutex_unlock(&tu->serialize); | ||
210 | return 0; | ||
211 | } | ||
212 | |||
213 | if (tu->tahvo_mode == TAHVO_MODE_HOST) { | ||
214 | otg->host = NULL; | ||
215 | tahvo_usb_become_host(tu); | ||
216 | } | ||
217 | |||
218 | otg->host = host; | ||
219 | |||
220 | mutex_unlock(&tu->serialize); | ||
221 | |||
222 | return 0; | ||
223 | } | ||
224 | |||
225 | static int tahvo_usb_set_peripheral(struct usb_otg *otg, | ||
226 | struct usb_gadget *gadget) | ||
227 | { | ||
228 | struct tahvo_usb *tu = container_of(otg->phy, struct tahvo_usb, phy); | ||
229 | |||
230 | dev_dbg(&tu->pt_dev->dev, "%s %p\n", __func__, gadget); | ||
231 | |||
232 | mutex_lock(&tu->serialize); | ||
233 | |||
234 | if (!gadget) { | ||
235 | if (tu->tahvo_mode == TAHVO_MODE_PERIPHERAL) | ||
236 | tahvo_usb_power_off(tu); | ||
237 | tu->phy.otg->gadget = NULL; | ||
238 | mutex_unlock(&tu->serialize); | ||
239 | return 0; | ||
240 | } | ||
241 | |||
242 | tu->phy.otg->gadget = gadget; | ||
243 | if (tu->tahvo_mode == TAHVO_MODE_PERIPHERAL) | ||
244 | tahvo_usb_become_peripheral(tu); | ||
245 | |||
246 | mutex_unlock(&tu->serialize); | ||
247 | |||
248 | return 0; | ||
249 | } | ||
250 | |||
251 | static irqreturn_t tahvo_usb_vbus_interrupt(int irq, void *_tu) | ||
252 | { | ||
253 | struct tahvo_usb *tu = _tu; | ||
254 | |||
255 | mutex_lock(&tu->serialize); | ||
256 | check_vbus_state(tu); | ||
257 | mutex_unlock(&tu->serialize); | ||
258 | |||
259 | return IRQ_HANDLED; | ||
260 | } | ||
261 | |||
262 | static ssize_t otg_mode_show(struct device *device, | ||
263 | struct device_attribute *attr, char *buf) | ||
264 | { | ||
265 | struct tahvo_usb *tu = dev_get_drvdata(device); | ||
266 | |||
267 | switch (tu->tahvo_mode) { | ||
268 | case TAHVO_MODE_HOST: | ||
269 | return sprintf(buf, "host\n"); | ||
270 | case TAHVO_MODE_PERIPHERAL: | ||
271 | return sprintf(buf, "peripheral\n"); | ||
272 | } | ||
273 | |||
274 | return -EINVAL; | ||
275 | } | ||
276 | |||
277 | static ssize_t otg_mode_store(struct device *device, | ||
278 | struct device_attribute *attr, | ||
279 | const char *buf, size_t count) | ||
280 | { | ||
281 | struct tahvo_usb *tu = dev_get_drvdata(device); | ||
282 | int r; | ||
283 | |||
284 | mutex_lock(&tu->serialize); | ||
285 | if (count >= 4 && strncmp(buf, "host", 4) == 0) { | ||
286 | if (tu->tahvo_mode == TAHVO_MODE_PERIPHERAL) | ||
287 | tahvo_usb_stop_peripheral(tu); | ||
288 | tu->tahvo_mode = TAHVO_MODE_HOST; | ||
289 | if (tu->phy.otg->host) { | ||
290 | dev_info(device, "HOST mode: host controller present\n"); | ||
291 | tahvo_usb_become_host(tu); | ||
292 | } else { | ||
293 | dev_info(device, "HOST mode: no host controller, powering off\n"); | ||
294 | tahvo_usb_power_off(tu); | ||
295 | } | ||
296 | r = strlen(buf); | ||
297 | } else if (count >= 10 && strncmp(buf, "peripheral", 10) == 0) { | ||
298 | if (tu->tahvo_mode == TAHVO_MODE_HOST) | ||
299 | tahvo_usb_stop_host(tu); | ||
300 | tu->tahvo_mode = TAHVO_MODE_PERIPHERAL; | ||
301 | if (tu->phy.otg->gadget) { | ||
302 | dev_info(device, "PERIPHERAL mode: gadget driver present\n"); | ||
303 | tahvo_usb_become_peripheral(tu); | ||
304 | } else { | ||
305 | dev_info(device, "PERIPHERAL mode: no gadget driver, powering off\n"); | ||
306 | tahvo_usb_power_off(tu); | ||
307 | } | ||
308 | r = strlen(buf); | ||
309 | } else { | ||
310 | r = -EINVAL; | ||
311 | } | ||
312 | mutex_unlock(&tu->serialize); | ||
313 | |||
314 | return r; | ||
315 | } | ||
316 | static DEVICE_ATTR(otg_mode, 0644, otg_mode_show, otg_mode_store); | ||
317 | |||
318 | static struct attribute *tahvo_attributes[] = { | ||
319 | &dev_attr_vbus.attr, | ||
320 | &dev_attr_otg_mode.attr, | ||
321 | NULL | ||
322 | }; | ||
323 | |||
324 | static struct attribute_group tahvo_attr_group = { | ||
325 | .attrs = tahvo_attributes, | ||
326 | }; | ||
327 | |||
328 | static int tahvo_usb_probe(struct platform_device *pdev) | ||
329 | { | ||
330 | struct retu_dev *rdev = dev_get_drvdata(pdev->dev.parent); | ||
331 | struct tahvo_usb *tu; | ||
332 | int ret; | ||
333 | |||
334 | tu = devm_kzalloc(&pdev->dev, sizeof(*tu), GFP_KERNEL); | ||
335 | if (!tu) | ||
336 | return -ENOMEM; | ||
337 | |||
338 | tu->phy.otg = devm_kzalloc(&pdev->dev, sizeof(*tu->phy.otg), | ||
339 | GFP_KERNEL); | ||
340 | if (!tu->phy.otg) | ||
341 | return -ENOMEM; | ||
342 | |||
343 | tu->pt_dev = pdev; | ||
344 | |||
345 | /* Default mode */ | ||
346 | #ifdef CONFIG_TAHVO_USB_HOST_BY_DEFAULT | ||
347 | tu->tahvo_mode = TAHVO_MODE_HOST; | ||
348 | #else | ||
349 | tu->tahvo_mode = TAHVO_MODE_PERIPHERAL; | ||
350 | #endif | ||
351 | |||
352 | mutex_init(&tu->serialize); | ||
353 | |||
354 | tu->ick = devm_clk_get(&pdev->dev, "usb_l4_ick"); | ||
355 | if (!IS_ERR(tu->ick)) | ||
356 | clk_enable(tu->ick); | ||
357 | |||
358 | /* | ||
359 | * Set initial state, so that we generate kevents only on state changes. | ||
360 | */ | ||
361 | tu->vbus_state = retu_read(rdev, TAHVO_REG_IDSR) & TAHVO_STAT_VBUS; | ||
362 | |||
363 | tu->extcon.name = DRIVER_NAME; | ||
364 | tu->extcon.supported_cable = tahvo_cable; | ||
365 | tu->extcon.dev.parent = &pdev->dev; | ||
366 | |||
367 | ret = extcon_dev_register(&tu->extcon); | ||
368 | if (ret) { | ||
369 | dev_err(&pdev->dev, "could not register extcon device: %d\n", | ||
370 | ret); | ||
371 | goto err_disable_clk; | ||
372 | } | ||
373 | |||
374 | /* Set the initial cable state. */ | ||
375 | extcon_set_cable_state(&tu->extcon, "USB-HOST", | ||
376 | tu->tahvo_mode == TAHVO_MODE_HOST); | ||
377 | extcon_set_cable_state(&tu->extcon, "USB", tu->vbus_state); | ||
378 | |||
379 | /* Create OTG interface */ | ||
380 | tahvo_usb_power_off(tu); | ||
381 | tu->phy.dev = &pdev->dev; | ||
382 | tu->phy.state = OTG_STATE_UNDEFINED; | ||
383 | tu->phy.label = DRIVER_NAME; | ||
384 | tu->phy.set_suspend = tahvo_usb_set_suspend; | ||
385 | |||
386 | tu->phy.otg->phy = &tu->phy; | ||
387 | tu->phy.otg->set_host = tahvo_usb_set_host; | ||
388 | tu->phy.otg->set_peripheral = tahvo_usb_set_peripheral; | ||
389 | |||
390 | ret = usb_add_phy(&tu->phy, USB_PHY_TYPE_USB2); | ||
391 | if (ret < 0) { | ||
392 | dev_err(&pdev->dev, "cannot register USB transceiver: %d\n", | ||
393 | ret); | ||
394 | goto err_extcon_unreg; | ||
395 | } | ||
396 | |||
397 | dev_set_drvdata(&pdev->dev, tu); | ||
398 | |||
399 | tu->irq = platform_get_irq(pdev, 0); | ||
400 | ret = request_threaded_irq(tu->irq, NULL, tahvo_usb_vbus_interrupt, 0, | ||
401 | "tahvo-vbus", tu); | ||
402 | if (ret) { | ||
403 | dev_err(&pdev->dev, "could not register tahvo-vbus irq: %d\n", | ||
404 | ret); | ||
405 | goto err_remove_phy; | ||
406 | } | ||
407 | |||
408 | /* Attributes */ | ||
409 | ret = sysfs_create_group(&pdev->dev.kobj, &tahvo_attr_group); | ||
410 | if (ret) { | ||
411 | dev_err(&pdev->dev, "cannot create sysfs group: %d\n", ret); | ||
412 | goto err_free_irq; | ||
413 | } | ||
414 | |||
415 | return 0; | ||
416 | |||
417 | err_free_irq: | ||
418 | free_irq(tu->irq, tu); | ||
419 | err_remove_phy: | ||
420 | usb_remove_phy(&tu->phy); | ||
421 | err_extcon_unreg: | ||
422 | extcon_dev_unregister(&tu->extcon); | ||
423 | err_disable_clk: | ||
424 | if (!IS_ERR(tu->ick)) | ||
425 | clk_disable(tu->ick); | ||
426 | |||
427 | return ret; | ||
428 | } | ||
429 | |||
430 | static int tahvo_usb_remove(struct platform_device *pdev) | ||
431 | { | ||
432 | struct tahvo_usb *tu = platform_get_drvdata(pdev); | ||
433 | |||
434 | sysfs_remove_group(&pdev->dev.kobj, &tahvo_attr_group); | ||
435 | free_irq(tu->irq, tu); | ||
436 | usb_remove_phy(&tu->phy); | ||
437 | extcon_dev_unregister(&tu->extcon); | ||
438 | if (!IS_ERR(tu->ick)) | ||
439 | clk_disable(tu->ick); | ||
440 | |||
441 | return 0; | ||
442 | } | ||
443 | |||
444 | static struct platform_driver tahvo_usb_driver = { | ||
445 | .probe = tahvo_usb_probe, | ||
446 | .remove = tahvo_usb_remove, | ||
447 | .driver = { | ||
448 | .name = "tahvo-usb", | ||
449 | .owner = THIS_MODULE, | ||
450 | }, | ||
451 | }; | ||
452 | module_platform_driver(tahvo_usb_driver); | ||
453 | |||
454 | MODULE_DESCRIPTION("Tahvo USB transceiver driver"); | ||
455 | MODULE_LICENSE("GPL"); | ||
456 | MODULE_AUTHOR("Juha Yrjölä, Tony Lindgren, and Timo Teräs"); | ||
457 | MODULE_AUTHOR("Aaro Koskinen <aaro.koskinen@iki.fi>"); | ||