Commit | Line | Data |
---|---|---|
9ba96ae5 AK |
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 | ||
9ba96ae5 AK |
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 | ||
9ba96ae5 AK |
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>"); |