Commit eb70e5ab8f95a81283623c03d2c99dfc59fcb319
Committed by
Greg Kroah-Hartman
1 parent
758fc9860c
Exists in
smarc-l5.0.0_1.0.0-ga
and in
5 other branches
usb: chipidea: add host role
This adds EHCI host support to the chipidea driver. We want it to be part of the hdrc driver and not a standalone (sub-)driver module, as the structure of ehci-hcd.c suggests, so for chipidea controller we hack it to not provide platform-related code, but only the ehci hcd. The ehci-platform driver won't work for us here too, because the controller uses the same registers for both device and host mode and also otg-related bits, so it's not really possible to put ehci registers into a separate resource. This is not a pretty solution, but the alternative is exporting symbols from the chipidea driver to a ehci-chipidea driver and doing all the module refcounting. Signed-off-by: Alexander Shishkin <alexander.shishkin@linux.intel.com> Cc: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Showing 9 changed files with 212 additions and 9 deletions Side-by-side Diff
drivers/usb/chipidea/Kconfig
... | ... | @@ -18,6 +18,12 @@ |
18 | 18 | Say Y here to enable device controller functionality of the |
19 | 19 | ChipIdea driver. |
20 | 20 | |
21 | +config USB_CHIPIDEA_HOST | |
22 | + bool "ChipIdea host controller" | |
23 | + help | |
24 | + Say Y here to enable host controller functionality of the | |
25 | + ChipIdea driver. | |
26 | + | |
21 | 27 | config USB_CHIPIDEA_DEBUG |
22 | 28 | bool "ChipIdea driver debug" |
23 | 29 | help |
drivers/usb/chipidea/Makefile
drivers/usb/chipidea/bits.h
drivers/usb/chipidea/ci.h
... | ... | @@ -15,6 +15,7 @@ |
15 | 15 | |
16 | 16 | #include <linux/list.h> |
17 | 17 | #include <linux/irqreturn.h> |
18 | +#include <linux/usb.h> | |
18 | 19 | #include <linux/usb/gadget.h> |
19 | 20 | |
20 | 21 | /****************************************************************************** |
... | ... | @@ -84,6 +85,7 @@ |
84 | 85 | /** |
85 | 86 | * struct hw_bank - hardware register mapping representation |
86 | 87 | * @lpm: set if the device is LPM capable |
88 | + * @phys: physical address of the controller's registers | |
87 | 89 | * @abs: absolute address of the beginning of register window |
88 | 90 | * @cap: capability registers |
89 | 91 | * @op: operational registers |
... | ... | @@ -92,6 +94,7 @@ |
92 | 94 | */ |
93 | 95 | struct hw_bank { |
94 | 96 | unsigned lpm; |
97 | + resource_size_t phys; | |
95 | 98 | void __iomem *abs; |
96 | 99 | void __iomem *cap; |
97 | 100 | void __iomem *op; |
... | ... | @@ -128,6 +131,7 @@ |
128 | 131 | * @udc_driver: platform specific information supplied by parent device |
129 | 132 | * @vbus_active: is VBUS active |
130 | 133 | * @transceiver: pointer to USB PHY, if any |
134 | + * @hcd: pointer to usb_hcd for ehci host driver | |
131 | 135 | */ |
132 | 136 | struct ci13xxx { |
133 | 137 | struct device *dev; |
... | ... | @@ -160,6 +164,7 @@ |
160 | 164 | struct ci13xxx_udc_driver *udc_driver; |
161 | 165 | int vbus_active; |
162 | 166 | struct usb_phy *transceiver; |
167 | + struct usb_hcd *hcd; | |
163 | 168 | }; |
164 | 169 | |
165 | 170 | static inline struct ci_role_driver *ci_role(struct ci13xxx *ci) |
... | ... | @@ -302,7 +307,7 @@ |
302 | 307 | return (val & mask) >> ffs_nr(mask); |
303 | 308 | } |
304 | 309 | |
305 | -int hw_device_reset(struct ci13xxx *ci); | |
310 | +int hw_device_reset(struct ci13xxx *ci, u32 mode); | |
306 | 311 | |
307 | 312 | int hw_port_test_set(struct ci13xxx *ci, u8 mode); |
308 | 313 |
drivers/usb/chipidea/core.c
... | ... | @@ -70,6 +70,7 @@ |
70 | 70 | #include "ci.h" |
71 | 71 | #include "udc.h" |
72 | 72 | #include "bits.h" |
73 | +#include "host.h" | |
73 | 74 | #include "debug.h" |
74 | 75 | |
75 | 76 | /* Controller register map */ |
... | ... | @@ -215,7 +216,7 @@ |
215 | 216 | * |
216 | 217 | * This function returns an error code |
217 | 218 | */ |
218 | -int hw_device_reset(struct ci13xxx *ci) | |
219 | +int hw_device_reset(struct ci13xxx *ci, u32 mode) | |
219 | 220 | { |
220 | 221 | /* should flush & stop before reset */ |
221 | 222 | hw_write(ci, OP_ENDPTFLUSH, ~0, ~0); |
222 | 223 | |
... | ... | @@ -235,12 +236,12 @@ |
235 | 236 | |
236 | 237 | /* USBMODE should be configured step by step */ |
237 | 238 | hw_write(ci, OP_USBMODE, USBMODE_CM, USBMODE_CM_IDLE); |
238 | - hw_write(ci, OP_USBMODE, USBMODE_CM, USBMODE_CM_DC); | |
239 | + hw_write(ci, OP_USBMODE, USBMODE_CM, mode); | |
239 | 240 | /* HW >= 2.3 */ |
240 | 241 | hw_write(ci, OP_USBMODE, USBMODE_SLOM, USBMODE_SLOM); |
241 | 242 | |
242 | - if (hw_read(ci, OP_USBMODE, USBMODE_CM) != USBMODE_CM_DC) { | |
243 | - pr_err("cannot enter in device mode"); | |
243 | + if (hw_read(ci, OP_USBMODE, USBMODE_CM) != mode) { | |
244 | + pr_err("cannot enter in %s mode", ci_role(ci)->name); | |
244 | 245 | pr_err("lpm = %i", ci->hw_bank.lpm); |
245 | 246 | return -ENODEV; |
246 | 247 | } |
... | ... | @@ -371,6 +372,8 @@ |
371 | 372 | return -ENODEV; |
372 | 373 | } |
373 | 374 | |
375 | + ci->hw_bank.phys = res->start; | |
376 | + | |
374 | 377 | ci->irq = platform_get_irq(pdev, 0); |
375 | 378 | if (ci->irq < 0) { |
376 | 379 | dev_err(dev, "missing IRQ\n"); |
... | ... | @@ -385,6 +388,10 @@ |
385 | 388 | } |
386 | 389 | |
387 | 390 | /* initialize role(s) before the interrupt is requested */ |
391 | + ret = ci_hdrc_host_init(ci); | |
392 | + if (ret) | |
393 | + dev_info(dev, "doesn't support host\n"); | |
394 | + | |
388 | 395 | ret = ci_hdrc_gadget_init(ci); |
389 | 396 | if (ret) |
390 | 397 | dev_info(dev, "doesn't support gadget\n"); |
drivers/usb/chipidea/host.c
1 | +/* | |
2 | + * host.c - ChipIdea USB host controller driver | |
3 | + * | |
4 | + * Copyright (c) 2012 Intel Corporation | |
5 | + * | |
6 | + * Author: Alexander Shishkin | |
7 | + * | |
8 | + * This program is free software; you can redistribute it and/or modify | |
9 | + * it under the terms of the GNU General Public License version 2 as | |
10 | + * published by the Free Software Foundation. | |
11 | + * | |
12 | + * This program is distributed in the hope that it will be useful, | |
13 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | + * GNU General Public License for more details. | |
16 | + * | |
17 | + * You should have received a copy of the GNU General Public License | |
18 | + * along with this program; if not, write to the Free Software | |
19 | + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
20 | + */ | |
21 | + | |
22 | +#include <linux/kernel.h> | |
23 | +#include <linux/usb.h> | |
24 | +#include <linux/usb/hcd.h> | |
25 | +#include <linux/usb/chipidea.h> | |
26 | + | |
27 | +#define CHIPIDEA_EHCI | |
28 | +#include "../host/ehci-hcd.c" | |
29 | + | |
30 | +#include "ci.h" | |
31 | +#include "bits.h" | |
32 | +#include "host.h" | |
33 | + | |
34 | +static int ci_ehci_setup(struct usb_hcd *hcd) | |
35 | +{ | |
36 | + struct ehci_hcd *ehci = hcd_to_ehci(hcd); | |
37 | + int ret; | |
38 | + | |
39 | + hcd->has_tt = 1; | |
40 | + | |
41 | + ret = ehci_setup(hcd); | |
42 | + if (ret) | |
43 | + return ret; | |
44 | + | |
45 | + ehci_port_power(ehci, 0); | |
46 | + | |
47 | + return ret; | |
48 | +} | |
49 | + | |
50 | +static const struct hc_driver ci_ehci_hc_driver = { | |
51 | + .description = "ehci_hcd", | |
52 | + .product_desc = "ChipIdea HDRC EHCI", | |
53 | + .hcd_priv_size = sizeof(struct ehci_hcd), | |
54 | + | |
55 | + /* | |
56 | + * generic hardware linkage | |
57 | + */ | |
58 | + .irq = ehci_irq, | |
59 | + .flags = HCD_MEMORY | HCD_USB2, | |
60 | + | |
61 | + /* | |
62 | + * basic lifecycle operations | |
63 | + */ | |
64 | + .reset = ci_ehci_setup, | |
65 | + .start = ehci_run, | |
66 | + .stop = ehci_stop, | |
67 | + .shutdown = ehci_shutdown, | |
68 | + | |
69 | + /* | |
70 | + * managing i/o requests and associated device resources | |
71 | + */ | |
72 | + .urb_enqueue = ehci_urb_enqueue, | |
73 | + .urb_dequeue = ehci_urb_dequeue, | |
74 | + .endpoint_disable = ehci_endpoint_disable, | |
75 | + .endpoint_reset = ehci_endpoint_reset, | |
76 | + | |
77 | + /* | |
78 | + * scheduling support | |
79 | + */ | |
80 | + .get_frame_number = ehci_get_frame, | |
81 | + | |
82 | + /* | |
83 | + * root hub support | |
84 | + */ | |
85 | + .hub_status_data = ehci_hub_status_data, | |
86 | + .hub_control = ehci_hub_control, | |
87 | + .bus_suspend = ehci_bus_suspend, | |
88 | + .bus_resume = ehci_bus_resume, | |
89 | + .relinquish_port = ehci_relinquish_port, | |
90 | + .port_handed_over = ehci_port_handed_over, | |
91 | + | |
92 | + .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, | |
93 | +}; | |
94 | + | |
95 | +static irqreturn_t host_irq(struct ci13xxx *ci) | |
96 | +{ | |
97 | + return usb_hcd_irq(ci->irq, ci->hcd); | |
98 | +} | |
99 | + | |
100 | +static int host_start(struct ci13xxx *ci) | |
101 | +{ | |
102 | + struct usb_hcd *hcd; | |
103 | + struct ehci_hcd *ehci; | |
104 | + int ret; | |
105 | + | |
106 | + if (usb_disabled()) | |
107 | + return -ENODEV; | |
108 | + | |
109 | + hcd = usb_create_hcd(&ci_ehci_hc_driver, ci->dev, dev_name(ci->dev)); | |
110 | + if (!hcd) | |
111 | + return -ENOMEM; | |
112 | + | |
113 | + dev_set_drvdata(ci->dev, ci); | |
114 | + hcd->rsrc_start = ci->hw_bank.phys; | |
115 | + hcd->rsrc_len = ci->hw_bank.size; | |
116 | + hcd->regs = ci->hw_bank.abs; | |
117 | + hcd->has_tt = 1; | |
118 | + | |
119 | + ehci = hcd_to_ehci(hcd); | |
120 | + ehci->caps = ci->hw_bank.cap; | |
121 | + ehci->has_hostpc = ci->hw_bank.lpm; | |
122 | + | |
123 | + ret = usb_add_hcd(hcd, 0, 0); | |
124 | + if (ret) | |
125 | + usb_remove_hcd(hcd); | |
126 | + else | |
127 | + ci->hcd = hcd; | |
128 | + | |
129 | + return ret; | |
130 | +} | |
131 | + | |
132 | +static void host_stop(struct ci13xxx *ci) | |
133 | +{ | |
134 | + struct usb_hcd *hcd = ci->hcd; | |
135 | + | |
136 | + usb_remove_hcd(hcd); | |
137 | + usb_put_hcd(hcd); | |
138 | +} | |
139 | + | |
140 | +int ci_hdrc_host_init(struct ci13xxx *ci) | |
141 | +{ | |
142 | + struct ci_role_driver *rdrv; | |
143 | + | |
144 | + if (!hw_read(ci, CAP_DCCPARAMS, DCCPARAMS_HC)) | |
145 | + return -ENXIO; | |
146 | + | |
147 | + rdrv = devm_kzalloc(ci->dev, sizeof(struct ci_role_driver), GFP_KERNEL); | |
148 | + if (!rdrv) | |
149 | + return -ENOMEM; | |
150 | + | |
151 | + rdrv->start = host_start; | |
152 | + rdrv->stop = host_stop; | |
153 | + rdrv->irq = host_irq; | |
154 | + rdrv->name = "host"; | |
155 | + ci->roles[CI_ROLE_HOST] = rdrv; | |
156 | + | |
157 | + return 0; | |
158 | +} |
drivers/usb/chipidea/host.h
1 | +#ifndef __DRIVERS_USB_CHIPIDEA_HOST_H | |
2 | +#define __DRIVERS_USB_CHIPIDEA_HOST_H | |
3 | + | |
4 | +#ifdef CONFIG_USB_CHIPIDEA_HOST | |
5 | + | |
6 | +int ci_hdrc_host_init(struct ci13xxx *ci); | |
7 | + | |
8 | +#else | |
9 | + | |
10 | +static inline int ci_hdrc_host_init(struct ci13xxx *ci) | |
11 | +{ | |
12 | + return -ENXIO; | |
13 | +} | |
14 | + | |
15 | +#endif | |
16 | + | |
17 | +#endif /* __DRIVERS_USB_CHIPIDEA_HOST_H */ |
drivers/usb/chipidea/udc.c
1 | 1 | /* |
2 | - * udc.h - ChipIdea UDC driver | |
2 | + * udc.c - ChipIdea UDC driver | |
3 | 3 | * |
4 | 4 | * Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved. |
5 | 5 | * |
... | ... | @@ -1396,7 +1396,7 @@ |
1396 | 1396 | if (gadget_ready) { |
1397 | 1397 | if (is_active) { |
1398 | 1398 | pm_runtime_get_sync(&_gadget->dev); |
1399 | - hw_device_reset(udc); | |
1399 | + hw_device_reset(udc, USBMODE_CM_DC); | |
1400 | 1400 | hw_device_state(udc, udc->ep0out->qh.dma); |
1401 | 1401 | } else { |
1402 | 1402 | hw_device_state(udc, 0); |
... | ... | @@ -1540,7 +1540,7 @@ |
1540 | 1540 | if (udc->udc_driver->flags & CI13XXX_PULLUP_ON_VBUS) { |
1541 | 1541 | if (udc->vbus_active) { |
1542 | 1542 | if (udc->udc_driver->flags & CI13XXX_REGS_SHARED) |
1543 | - hw_device_reset(udc); | |
1543 | + hw_device_reset(udc, USBMODE_CM_DC); | |
1544 | 1544 | } else { |
1545 | 1545 | pm_runtime_put_sync(&udc->gadget.dev); |
1546 | 1546 | goto done; |
... | ... | @@ -1720,7 +1720,7 @@ |
1720 | 1720 | } |
1721 | 1721 | |
1722 | 1722 | if (!(udc->udc_driver->flags & CI13XXX_REGS_SHARED)) { |
1723 | - retval = hw_device_reset(udc); | |
1723 | + retval = hw_device_reset(udc, USBMODE_CM_DC); | |
1724 | 1724 | if (retval) |
1725 | 1725 | goto put_transceiver; |
1726 | 1726 | } |
drivers/usb/host/ehci-hcd.c
... | ... | @@ -1246,6 +1246,13 @@ |
1246 | 1246 | } |
1247 | 1247 | |
1248 | 1248 | /*-------------------------------------------------------------------------*/ |
1249 | +/* | |
1250 | + * The EHCI in ChipIdea HDRC cannot be a separate module or device, | |
1251 | + * because its registers (and irq) are shared between host/gadget/otg | |
1252 | + * functions and in order to facilitate role switching we cannot | |
1253 | + * give the ehci driver exclusive access to those. | |
1254 | + */ | |
1255 | +#ifndef CHIPIDEA_EHCI | |
1249 | 1256 | |
1250 | 1257 | MODULE_DESCRIPTION(DRIVER_DESC); |
1251 | 1258 | MODULE_AUTHOR (DRIVER_AUTHOR); |
... | ... | @@ -1503,4 +1510,6 @@ |
1503 | 1510 | clear_bit(USB_EHCI_LOADED, &usb_hcds_loaded); |
1504 | 1511 | } |
1505 | 1512 | module_exit(ehci_hcd_cleanup); |
1513 | + | |
1514 | +#endif /* CHIPIDEA_EHCI */ |