1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * EHCI HCD (Host Controller Driver) for USB. |
4 | * |
5 | * Bus Glue for Xilinx EHCI core on the of_platform bus |
6 | * |
7 | * Copyright (c) 2009 Xilinx, Inc. |
8 | * |
9 | * Based on "ehci-ppc-of.c" by Valentine Barshak <vbarshak@ru.mvista.com> |
10 | * and "ehci-ppc-soc.c" by Stefan Roese <sr@denx.de> |
11 | * and "ohci-ppc-of.c" by Sylvain Munaut <tnt@246tNt.com> |
12 | */ |
13 | |
14 | #include <linux/err.h> |
15 | #include <linux/signal.h> |
16 | |
17 | #include <linux/of.h> |
18 | #include <linux/of_platform.h> |
19 | #include <linux/of_address.h> |
20 | #include <linux/of_irq.h> |
21 | |
22 | /** |
23 | * ehci_xilinx_port_handed_over - hand the port out if failed to enable it |
24 | * @hcd: Pointer to the usb_hcd device to which the host controller bound |
25 | * @portnum:Port number to which the device is attached. |
26 | * |
27 | * This function is used as a place to tell the user that the Xilinx USB host |
28 | * controller does support LS devices. And in an HS only configuration, it |
29 | * does not support FS devices either. It is hoped that this can help a |
30 | * confused user. |
31 | * |
32 | * There are cases when the host controller fails to enable the port due to, |
33 | * for example, insufficient power that can be supplied to the device from |
34 | * the USB bus. In those cases, the messages printed here are not helpful. |
35 | * |
36 | * Return: Always return 0 |
37 | */ |
38 | static int ehci_xilinx_port_handed_over(struct usb_hcd *hcd, int portnum) |
39 | { |
40 | dev_warn(hcd->self.controller, "port %d cannot be enabled\n" , portnum); |
41 | if (hcd->has_tt) { |
42 | dev_warn(hcd->self.controller, |
43 | "Maybe you have connected a low speed device?\n" ); |
44 | |
45 | dev_warn(hcd->self.controller, |
46 | "We do not support low speed devices\n" ); |
47 | } else { |
48 | dev_warn(hcd->self.controller, |
49 | "Maybe your device is not a high speed device?\n" ); |
50 | dev_warn(hcd->self.controller, |
51 | "The USB host controller does not support full speed nor low speed devices\n" ); |
52 | dev_warn(hcd->self.controller, |
53 | "You can reconfigure the host controller to have full speed support\n" ); |
54 | } |
55 | |
56 | return 0; |
57 | } |
58 | |
59 | |
60 | static const struct hc_driver ehci_xilinx_of_hc_driver = { |
61 | .description = hcd_name, |
62 | .product_desc = "OF EHCI" , |
63 | .hcd_priv_size = sizeof(struct ehci_hcd), |
64 | |
65 | /* |
66 | * generic hardware linkage |
67 | */ |
68 | .irq = ehci_irq, |
69 | .flags = HCD_MEMORY | HCD_DMA | HCD_USB2 | HCD_BH, |
70 | |
71 | /* |
72 | * basic lifecycle operations |
73 | */ |
74 | .reset = ehci_setup, |
75 | .start = ehci_run, |
76 | .stop = ehci_stop, |
77 | .shutdown = ehci_shutdown, |
78 | |
79 | /* |
80 | * managing i/o requests and associated device resources |
81 | */ |
82 | .urb_enqueue = ehci_urb_enqueue, |
83 | .urb_dequeue = ehci_urb_dequeue, |
84 | .endpoint_disable = ehci_endpoint_disable, |
85 | .endpoint_reset = ehci_endpoint_reset, |
86 | |
87 | /* |
88 | * scheduling support |
89 | */ |
90 | .get_frame_number = ehci_get_frame, |
91 | |
92 | /* |
93 | * root hub support |
94 | */ |
95 | .hub_status_data = ehci_hub_status_data, |
96 | .hub_control = ehci_hub_control, |
97 | #ifdef CONFIG_PM |
98 | .bus_suspend = ehci_bus_suspend, |
99 | .bus_resume = ehci_bus_resume, |
100 | #endif |
101 | .relinquish_port = NULL, |
102 | .port_handed_over = ehci_xilinx_port_handed_over, |
103 | |
104 | .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, |
105 | }; |
106 | |
107 | /** |
108 | * ehci_hcd_xilinx_of_probe - Probe method for the USB host controller |
109 | * @op: pointer to the platform_device bound to the host controller |
110 | * |
111 | * This function requests resources and sets up appropriate properties for the |
112 | * host controller. Because the Xilinx USB host controller can be configured |
113 | * as HS only or HS/FS only, it checks the configuration in the device tree |
114 | * entry, and sets an appropriate value for hcd->has_tt. |
115 | * |
116 | * Return: zero on success, negative error code otherwise |
117 | */ |
118 | static int ehci_hcd_xilinx_of_probe(struct platform_device *op) |
119 | { |
120 | struct device_node *dn = op->dev.of_node; |
121 | struct usb_hcd *hcd; |
122 | struct ehci_hcd *ehci; |
123 | struct resource res; |
124 | int irq; |
125 | int rv; |
126 | int *value; |
127 | |
128 | if (usb_disabled()) |
129 | return -ENODEV; |
130 | |
131 | dev_dbg(&op->dev, "initializing XILINX-OF USB Controller\n" ); |
132 | |
133 | rv = of_address_to_resource(dev: dn, index: 0, r: &res); |
134 | if (rv) |
135 | return rv; |
136 | |
137 | hcd = usb_create_hcd(&ehci_xilinx_of_hc_driver, &op->dev, |
138 | "XILINX-OF USB" ); |
139 | if (!hcd) |
140 | return -ENOMEM; |
141 | |
142 | hcd->rsrc_start = res.start; |
143 | hcd->rsrc_len = resource_size(res: &res); |
144 | |
145 | irq = irq_of_parse_and_map(node: dn, index: 0); |
146 | if (!irq) { |
147 | dev_err(&op->dev, "%s: irq_of_parse_and_map failed\n" , |
148 | __FILE__); |
149 | rv = -EBUSY; |
150 | goto err_irq; |
151 | } |
152 | |
153 | hcd->regs = devm_ioremap_resource(dev: &op->dev, res: &res); |
154 | if (IS_ERR(ptr: hcd->regs)) { |
155 | rv = PTR_ERR(ptr: hcd->regs); |
156 | goto err_irq; |
157 | } |
158 | |
159 | ehci = hcd_to_ehci(hcd); |
160 | |
161 | /* This core always has big-endian register interface and uses |
162 | * big-endian memory descriptors. |
163 | */ |
164 | ehci->big_endian_mmio = 1; |
165 | ehci->big_endian_desc = 1; |
166 | |
167 | /* Check whether the FS support option is selected in the hardware. |
168 | */ |
169 | value = (int *)of_get_property(node: dn, name: "xlnx,support-usb-fs" , NULL); |
170 | if (value && (*value == 1)) { |
171 | ehci_dbg(ehci, "USB host controller supports FS devices\n" ); |
172 | hcd->has_tt = 1; |
173 | } else { |
174 | ehci_dbg(ehci, |
175 | "USB host controller is HS only\n" ); |
176 | hcd->has_tt = 0; |
177 | } |
178 | |
179 | /* Debug registers are at the first 0x100 region |
180 | */ |
181 | ehci->caps = hcd->regs + 0x100; |
182 | |
183 | rv = usb_add_hcd(hcd, irq, 0); |
184 | if (rv == 0) { |
185 | device_wakeup_enable(dev: hcd->self.controller); |
186 | return 0; |
187 | } |
188 | |
189 | err_irq: |
190 | usb_put_hcd(hcd); |
191 | |
192 | return rv; |
193 | } |
194 | |
195 | /** |
196 | * ehci_hcd_xilinx_of_remove - shutdown hcd and release resources |
197 | * @op: pointer to platform_device structure that is to be removed |
198 | * |
199 | * Remove the hcd structure, and release resources that has been requested |
200 | * during probe. |
201 | * |
202 | * Return: Always return 0 |
203 | */ |
204 | static void ehci_hcd_xilinx_of_remove(struct platform_device *op) |
205 | { |
206 | struct usb_hcd *hcd = platform_get_drvdata(pdev: op); |
207 | |
208 | dev_dbg(&op->dev, "stopping XILINX-OF USB Controller\n" ); |
209 | |
210 | usb_remove_hcd(hcd); |
211 | |
212 | usb_put_hcd(hcd); |
213 | } |
214 | |
215 | static const struct of_device_id ehci_hcd_xilinx_of_match[] = { |
216 | {.compatible = "xlnx,xps-usb-host-1.00.a" ,}, |
217 | {}, |
218 | }; |
219 | MODULE_DEVICE_TABLE(of, ehci_hcd_xilinx_of_match); |
220 | |
221 | static struct platform_driver ehci_hcd_xilinx_of_driver = { |
222 | .probe = ehci_hcd_xilinx_of_probe, |
223 | .remove_new = ehci_hcd_xilinx_of_remove, |
224 | .shutdown = usb_hcd_platform_shutdown, |
225 | .driver = { |
226 | .name = "xilinx-of-ehci" , |
227 | .of_match_table = ehci_hcd_xilinx_of_match, |
228 | }, |
229 | }; |
230 | |