1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Freescale QUICC Engine USB Host Controller Driver |
4 | * |
5 | * Copyright (c) Freescale Semicondutor, Inc. 2006. |
6 | * Shlomi Gridish <gridish@freescale.com> |
7 | * Jerry Huang <Chang-Ming.Huang@freescale.com> |
8 | * Copyright (c) Logic Product Development, Inc. 2007 |
9 | * Peter Barada <peterb@logicpd.com> |
10 | * Copyright (c) MontaVista Software, Inc. 2008. |
11 | * Anton Vorontsov <avorontsov@ru.mvista.com> |
12 | */ |
13 | |
14 | #include <linux/kernel.h> |
15 | #include <linux/types.h> |
16 | #include <linux/spinlock.h> |
17 | #include <linux/delay.h> |
18 | #include <linux/errno.h> |
19 | #include <linux/io.h> |
20 | #include <linux/usb.h> |
21 | #include <linux/usb/hcd.h> |
22 | #include <linux/gpio/consumer.h> |
23 | #include <soc/fsl/qe/qe.h> |
24 | #include "fhci.h" |
25 | |
26 | /* virtual root hub specific descriptor */ |
27 | static u8 root_hub_des[] = { |
28 | 0x09, /* blength */ |
29 | USB_DT_HUB, /* bDescriptorType;hub-descriptor */ |
30 | 0x01, /* bNbrPorts */ |
31 | HUB_CHAR_INDV_PORT_LPSM | HUB_CHAR_NO_OCPM, /* wHubCharacteristics */ |
32 | 0x00, /* per-port power, no overcurrent */ |
33 | 0x01, /* bPwrOn2pwrGood;2ms */ |
34 | 0x00, /* bHubContrCurrent;0mA */ |
35 | 0x00, /* DeviceRemoveable */ |
36 | 0xff, /* PortPwrCtrlMask */ |
37 | }; |
38 | |
39 | static void fhci_gpio_set_value(struct fhci_hcd *fhci, int gpio_nr, bool on) |
40 | { |
41 | struct gpio_desc *gpiod = fhci->gpiods[gpio_nr]; |
42 | |
43 | if (!gpiod) |
44 | return; |
45 | |
46 | gpiod_set_value(desc: gpiod, value: on); |
47 | mdelay(5); |
48 | } |
49 | |
50 | void fhci_config_transceiver(struct fhci_hcd *fhci, |
51 | enum fhci_port_status status) |
52 | { |
53 | fhci_dbg(fhci, "-> %s: %d\n" , __func__, status); |
54 | |
55 | switch (status) { |
56 | case FHCI_PORT_POWER_OFF: |
57 | fhci_gpio_set_value(fhci, gpio_nr: GPIO_POWER, on: false); |
58 | break; |
59 | case FHCI_PORT_DISABLED: |
60 | case FHCI_PORT_WAITING: |
61 | fhci_gpio_set_value(fhci, gpio_nr: GPIO_POWER, on: true); |
62 | break; |
63 | case FHCI_PORT_LOW: |
64 | fhci_gpio_set_value(fhci, gpio_nr: GPIO_SPEED, on: false); |
65 | break; |
66 | case FHCI_PORT_FULL: |
67 | fhci_gpio_set_value(fhci, gpio_nr: GPIO_SPEED, on: true); |
68 | break; |
69 | default: |
70 | WARN_ON(1); |
71 | break; |
72 | } |
73 | |
74 | fhci_dbg(fhci, "<- %s: %d\n" , __func__, status); |
75 | } |
76 | |
77 | /* disable the USB port by clearing the EN bit in the USBMOD register */ |
78 | void fhci_port_disable(struct fhci_hcd *fhci) |
79 | { |
80 | struct fhci_usb *usb = (struct fhci_usb *)fhci->usb_lld; |
81 | enum fhci_port_status port_status; |
82 | |
83 | fhci_dbg(fhci, "-> %s\n" , __func__); |
84 | |
85 | fhci_stop_sof_timer(fhci); |
86 | |
87 | fhci_flush_all_transmissions(usb); |
88 | |
89 | fhci_usb_disable_interrupt(usb: (struct fhci_usb *)fhci->usb_lld); |
90 | port_status = usb->port_status; |
91 | usb->port_status = FHCI_PORT_DISABLED; |
92 | |
93 | /* Enable IDLE since we want to know if something comes along */ |
94 | usb->saved_msk |= USB_E_IDLE_MASK; |
95 | out_be16(&usb->fhci->regs->usb_usbmr, usb->saved_msk); |
96 | |
97 | /* check if during the disconnection process attached new device */ |
98 | if (port_status == FHCI_PORT_WAITING) |
99 | fhci_device_connected_interrupt(fhci); |
100 | usb->vroot_hub->port.wPortStatus &= ~USB_PORT_STAT_ENABLE; |
101 | usb->vroot_hub->port.wPortChange |= USB_PORT_STAT_C_ENABLE; |
102 | fhci_usb_enable_interrupt(usb: (struct fhci_usb *)fhci->usb_lld); |
103 | |
104 | fhci_dbg(fhci, "<- %s\n" , __func__); |
105 | } |
106 | |
107 | /* enable the USB port by setting the EN bit in the USBMOD register */ |
108 | void fhci_port_enable(void *lld) |
109 | { |
110 | struct fhci_usb *usb = (struct fhci_usb *)lld; |
111 | struct fhci_hcd *fhci = usb->fhci; |
112 | |
113 | fhci_dbg(fhci, "-> %s\n" , __func__); |
114 | |
115 | fhci_config_transceiver(fhci, status: usb->port_status); |
116 | |
117 | if ((usb->port_status != FHCI_PORT_FULL) && |
118 | (usb->port_status != FHCI_PORT_LOW)) |
119 | fhci_start_sof_timer(fhci); |
120 | |
121 | usb->vroot_hub->port.wPortStatus |= USB_PORT_STAT_ENABLE; |
122 | usb->vroot_hub->port.wPortChange |= USB_PORT_STAT_C_ENABLE; |
123 | |
124 | fhci_dbg(fhci, "<- %s\n" , __func__); |
125 | } |
126 | |
127 | void fhci_io_port_generate_reset(struct fhci_hcd *fhci) |
128 | { |
129 | fhci_dbg(fhci, "-> %s\n" , __func__); |
130 | |
131 | gpiod_direction_output(desc: fhci->gpiods[GPIO_USBOE], value: 0); |
132 | gpiod_direction_output(desc: fhci->gpiods[GPIO_USBTP], value: 0); |
133 | gpiod_direction_output(desc: fhci->gpiods[GPIO_USBTN], value: 0); |
134 | |
135 | mdelay(5); |
136 | |
137 | qe_pin_set_dedicated(pin: fhci->pins[PIN_USBOE]); |
138 | qe_pin_set_dedicated(pin: fhci->pins[PIN_USBTP]); |
139 | qe_pin_set_dedicated(pin: fhci->pins[PIN_USBTN]); |
140 | |
141 | fhci_dbg(fhci, "<- %s\n" , __func__); |
142 | } |
143 | |
144 | /* generate the RESET condition on the bus */ |
145 | void fhci_port_reset(void *lld) |
146 | { |
147 | struct fhci_usb *usb = (struct fhci_usb *)lld; |
148 | struct fhci_hcd *fhci = usb->fhci; |
149 | u8 mode; |
150 | u16 mask; |
151 | |
152 | fhci_dbg(fhci, "-> %s\n" , __func__); |
153 | |
154 | fhci_stop_sof_timer(fhci); |
155 | /* disable the USB controller */ |
156 | mode = in_8(&fhci->regs->usb_usmod); |
157 | out_8(&fhci->regs->usb_usmod, mode & (~USB_MODE_EN)); |
158 | |
159 | /* disable idle interrupts */ |
160 | mask = in_be16(&fhci->regs->usb_usbmr); |
161 | out_be16(&fhci->regs->usb_usbmr, mask & (~USB_E_IDLE_MASK)); |
162 | |
163 | fhci_io_port_generate_reset(fhci); |
164 | |
165 | /* enable interrupt on this endpoint */ |
166 | out_be16(&fhci->regs->usb_usbmr, mask); |
167 | |
168 | /* enable the USB controller */ |
169 | mode = in_8(&fhci->regs->usb_usmod); |
170 | out_8(&fhci->regs->usb_usmod, mode | USB_MODE_EN); |
171 | fhci_start_sof_timer(fhci); |
172 | |
173 | fhci_dbg(fhci, "<- %s\n" , __func__); |
174 | } |
175 | |
176 | int fhci_hub_status_data(struct usb_hcd *hcd, char *buf) |
177 | { |
178 | struct fhci_hcd *fhci = hcd_to_fhci(hcd); |
179 | int ret = 0; |
180 | unsigned long flags; |
181 | |
182 | fhci_dbg(fhci, "-> %s\n" , __func__); |
183 | |
184 | spin_lock_irqsave(&fhci->lock, flags); |
185 | |
186 | if (fhci->vroot_hub->port.wPortChange & (USB_PORT_STAT_C_CONNECTION | |
187 | USB_PORT_STAT_C_ENABLE | USB_PORT_STAT_C_SUSPEND | |
188 | USB_PORT_STAT_C_RESET | USB_PORT_STAT_C_OVERCURRENT)) { |
189 | *buf = 1 << 1; |
190 | ret = 1; |
191 | fhci_dbg(fhci, "-- %s\n" , __func__); |
192 | } |
193 | |
194 | spin_unlock_irqrestore(lock: &fhci->lock, flags); |
195 | |
196 | fhci_dbg(fhci, "<- %s\n" , __func__); |
197 | |
198 | return ret; |
199 | } |
200 | |
201 | int fhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, |
202 | u16 wIndex, char *buf, u16 wLength) |
203 | { |
204 | struct fhci_hcd *fhci = hcd_to_fhci(hcd); |
205 | int retval = 0; |
206 | struct usb_hub_status *hub_status; |
207 | struct usb_port_status *port_status; |
208 | unsigned long flags; |
209 | |
210 | spin_lock_irqsave(&fhci->lock, flags); |
211 | |
212 | fhci_dbg(fhci, "-> %s\n" , __func__); |
213 | |
214 | switch (typeReq) { |
215 | case ClearHubFeature: |
216 | switch (wValue) { |
217 | case C_HUB_LOCAL_POWER: |
218 | case C_HUB_OVER_CURRENT: |
219 | break; |
220 | default: |
221 | goto error; |
222 | } |
223 | break; |
224 | case ClearPortFeature: |
225 | fhci->vroot_hub->feature &= (1 << wValue); |
226 | |
227 | switch (wValue) { |
228 | case USB_PORT_FEAT_ENABLE: |
229 | fhci->vroot_hub->port.wPortStatus &= |
230 | ~USB_PORT_STAT_ENABLE; |
231 | fhci_port_disable(fhci); |
232 | break; |
233 | case USB_PORT_FEAT_C_ENABLE: |
234 | fhci->vroot_hub->port.wPortChange &= |
235 | ~USB_PORT_STAT_C_ENABLE; |
236 | break; |
237 | case USB_PORT_FEAT_SUSPEND: |
238 | fhci->vroot_hub->port.wPortStatus &= |
239 | ~USB_PORT_STAT_SUSPEND; |
240 | fhci_stop_sof_timer(fhci); |
241 | break; |
242 | case USB_PORT_FEAT_C_SUSPEND: |
243 | fhci->vroot_hub->port.wPortChange &= |
244 | ~USB_PORT_STAT_C_SUSPEND; |
245 | break; |
246 | case USB_PORT_FEAT_POWER: |
247 | fhci->vroot_hub->port.wPortStatus &= |
248 | ~USB_PORT_STAT_POWER; |
249 | fhci_config_transceiver(fhci, status: FHCI_PORT_POWER_OFF); |
250 | break; |
251 | case USB_PORT_FEAT_C_CONNECTION: |
252 | fhci->vroot_hub->port.wPortChange &= |
253 | ~USB_PORT_STAT_C_CONNECTION; |
254 | break; |
255 | case USB_PORT_FEAT_C_OVER_CURRENT: |
256 | fhci->vroot_hub->port.wPortChange &= |
257 | ~USB_PORT_STAT_C_OVERCURRENT; |
258 | break; |
259 | case USB_PORT_FEAT_C_RESET: |
260 | fhci->vroot_hub->port.wPortChange &= |
261 | ~USB_PORT_STAT_C_RESET; |
262 | break; |
263 | default: |
264 | goto error; |
265 | } |
266 | break; |
267 | case GetHubDescriptor: |
268 | memcpy(buf, root_hub_des, sizeof(root_hub_des)); |
269 | break; |
270 | case GetHubStatus: |
271 | hub_status = (struct usb_hub_status *)buf; |
272 | hub_status->wHubStatus = |
273 | cpu_to_le16(fhci->vroot_hub->hub.wHubStatus); |
274 | hub_status->wHubChange = |
275 | cpu_to_le16(fhci->vroot_hub->hub.wHubChange); |
276 | break; |
277 | case GetPortStatus: |
278 | port_status = (struct usb_port_status *)buf; |
279 | port_status->wPortStatus = |
280 | cpu_to_le16(fhci->vroot_hub->port.wPortStatus); |
281 | port_status->wPortChange = |
282 | cpu_to_le16(fhci->vroot_hub->port.wPortChange); |
283 | break; |
284 | case SetHubFeature: |
285 | switch (wValue) { |
286 | case C_HUB_OVER_CURRENT: |
287 | case C_HUB_LOCAL_POWER: |
288 | break; |
289 | default: |
290 | goto error; |
291 | } |
292 | break; |
293 | case SetPortFeature: |
294 | fhci->vroot_hub->feature |= (1 << wValue); |
295 | |
296 | switch (wValue) { |
297 | case USB_PORT_FEAT_ENABLE: |
298 | fhci->vroot_hub->port.wPortStatus |= |
299 | USB_PORT_STAT_ENABLE; |
300 | fhci_port_enable(lld: fhci->usb_lld); |
301 | break; |
302 | case USB_PORT_FEAT_SUSPEND: |
303 | fhci->vroot_hub->port.wPortStatus |= |
304 | USB_PORT_STAT_SUSPEND; |
305 | fhci_stop_sof_timer(fhci); |
306 | break; |
307 | case USB_PORT_FEAT_RESET: |
308 | fhci->vroot_hub->port.wPortStatus |= |
309 | USB_PORT_STAT_RESET; |
310 | fhci_port_reset(lld: fhci->usb_lld); |
311 | fhci->vroot_hub->port.wPortStatus |= |
312 | USB_PORT_STAT_ENABLE; |
313 | fhci->vroot_hub->port.wPortStatus &= |
314 | ~USB_PORT_STAT_RESET; |
315 | break; |
316 | case USB_PORT_FEAT_POWER: |
317 | fhci->vroot_hub->port.wPortStatus |= |
318 | USB_PORT_STAT_POWER; |
319 | fhci_config_transceiver(fhci, status: FHCI_PORT_WAITING); |
320 | break; |
321 | default: |
322 | goto error; |
323 | } |
324 | break; |
325 | default: |
326 | error: |
327 | retval = -EPIPE; |
328 | } |
329 | |
330 | fhci_dbg(fhci, "<- %s\n" , __func__); |
331 | |
332 | spin_unlock_irqrestore(lock: &fhci->lock, flags); |
333 | |
334 | return retval; |
335 | } |
336 | |