1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * MUSB OTG driver virtual root hub support |
4 | * |
5 | * Copyright 2005 Mentor Graphics Corporation |
6 | * Copyright (C) 2005-2006 by Texas Instruments |
7 | * Copyright (C) 2006-2007 Nokia Corporation |
8 | */ |
9 | |
10 | #include <linux/module.h> |
11 | #include <linux/kernel.h> |
12 | #include <linux/sched.h> |
13 | #include <linux/errno.h> |
14 | #include <linux/time.h> |
15 | #include <linux/timer.h> |
16 | |
17 | #include <asm/unaligned.h> |
18 | |
19 | #include "musb_core.h" |
20 | |
21 | void musb_host_finish_resume(struct work_struct *work) |
22 | { |
23 | struct musb *musb; |
24 | unsigned long flags; |
25 | u8 power; |
26 | |
27 | musb = container_of(work, struct musb, finish_resume_work.work); |
28 | |
29 | spin_lock_irqsave(&musb->lock, flags); |
30 | |
31 | power = musb_readb(musb->mregs, MUSB_POWER); |
32 | power &= ~MUSB_POWER_RESUME; |
33 | musb_dbg(musb, fmt: "root port resume stopped, power %02x" , power); |
34 | musb_writeb(musb->mregs, MUSB_POWER, power); |
35 | |
36 | /* |
37 | * ISSUE: DaVinci (RTL 1.300) disconnects after |
38 | * resume of high speed peripherals (but not full |
39 | * speed ones). |
40 | */ |
41 | musb->is_active = 1; |
42 | musb->port1_status &= ~(USB_PORT_STAT_SUSPEND | MUSB_PORT_STAT_RESUME); |
43 | musb->port1_status |= USB_PORT_STAT_C_SUSPEND << 16; |
44 | usb_hcd_poll_rh_status(hcd: musb->hcd); |
45 | /* NOTE: it might really be A_WAIT_BCON ... */ |
46 | musb_set_state(musb, otg_state: OTG_STATE_A_HOST); |
47 | |
48 | spin_unlock_irqrestore(lock: &musb->lock, flags); |
49 | } |
50 | |
51 | int musb_port_suspend(struct musb *musb, bool do_suspend) |
52 | { |
53 | u8 power; |
54 | void __iomem *mbase = musb->mregs; |
55 | |
56 | if (!is_host_active(musb)) |
57 | return 0; |
58 | |
59 | /* NOTE: this doesn't necessarily put PHY into low power mode, |
60 | * turning off its clock; that's a function of PHY integration and |
61 | * MUSB_POWER_ENSUSPEND. PHY may need a clock (sigh) to detect |
62 | * SE0 changing to connect (J) or wakeup (K) states. |
63 | */ |
64 | power = musb_readb(mbase, MUSB_POWER); |
65 | if (do_suspend) { |
66 | int retries = 10000; |
67 | |
68 | if (power & MUSB_POWER_RESUME) |
69 | return -EBUSY; |
70 | |
71 | if (!(power & MUSB_POWER_SUSPENDM)) { |
72 | power |= MUSB_POWER_SUSPENDM; |
73 | musb_writeb(mbase, MUSB_POWER, power); |
74 | |
75 | /* Needed for OPT A tests */ |
76 | power = musb_readb(mbase, MUSB_POWER); |
77 | while (power & MUSB_POWER_SUSPENDM) { |
78 | power = musb_readb(mbase, MUSB_POWER); |
79 | if (retries-- < 1) |
80 | break; |
81 | } |
82 | } |
83 | |
84 | musb_dbg(musb, fmt: "Root port suspended, power %02x" , power); |
85 | |
86 | musb->port1_status |= USB_PORT_STAT_SUSPEND; |
87 | switch (musb_get_state(musb)) { |
88 | case OTG_STATE_A_HOST: |
89 | musb_set_state(musb, otg_state: OTG_STATE_A_SUSPEND); |
90 | musb->is_active = musb->xceiv && |
91 | musb->xceiv->otg->host->b_hnp_enable; |
92 | if (musb->is_active) |
93 | mod_timer(timer: &musb->otg_timer, expires: jiffies |
94 | + msecs_to_jiffies( |
95 | OTG_TIME_A_AIDL_BDIS)); |
96 | musb_platform_try_idle(musb, timeout: 0); |
97 | break; |
98 | case OTG_STATE_B_HOST: |
99 | musb_set_state(musb, otg_state: OTG_STATE_B_WAIT_ACON); |
100 | musb->is_active = musb->xceiv && |
101 | musb->xceiv->otg->host->b_hnp_enable; |
102 | musb_platform_try_idle(musb, timeout: 0); |
103 | break; |
104 | default: |
105 | musb_dbg(musb, fmt: "bogus rh suspend? %s" , |
106 | musb_otg_state_string(musb)); |
107 | } |
108 | } else if (power & MUSB_POWER_SUSPENDM) { |
109 | power &= ~MUSB_POWER_SUSPENDM; |
110 | power |= MUSB_POWER_RESUME; |
111 | musb_writeb(mbase, MUSB_POWER, power); |
112 | |
113 | musb_dbg(musb, fmt: "Root port resuming, power %02x" , power); |
114 | |
115 | musb->port1_status |= MUSB_PORT_STAT_RESUME; |
116 | schedule_delayed_work(dwork: &musb->finish_resume_work, |
117 | delay: msecs_to_jiffies(USB_RESUME_TIMEOUT)); |
118 | } |
119 | return 0; |
120 | } |
121 | |
122 | void musb_port_reset(struct musb *musb, bool do_reset) |
123 | { |
124 | u8 power; |
125 | void __iomem *mbase = musb->mregs; |
126 | |
127 | if (musb_get_state(musb) == OTG_STATE_B_IDLE) { |
128 | musb_dbg(musb, fmt: "HNP: Returning from HNP; no hub reset from b_idle" ); |
129 | musb->port1_status &= ~USB_PORT_STAT_RESET; |
130 | return; |
131 | } |
132 | |
133 | if (!is_host_active(musb)) |
134 | return; |
135 | |
136 | /* NOTE: caller guarantees it will turn off the reset when |
137 | * the appropriate amount of time has passed |
138 | */ |
139 | power = musb_readb(mbase, MUSB_POWER); |
140 | if (do_reset) { |
141 | /* |
142 | * If RESUME is set, we must make sure it stays minimum 20 ms. |
143 | * Then we must clear RESUME and wait a bit to let musb start |
144 | * generating SOFs. If we don't do this, OPT HS A 6.8 tests |
145 | * fail with "Error! Did not receive an SOF before suspend |
146 | * detected". |
147 | */ |
148 | if (power & MUSB_POWER_RESUME) { |
149 | long remain = (unsigned long) musb->rh_timer - jiffies; |
150 | |
151 | if (musb->rh_timer > 0 && remain > 0) { |
152 | /* take into account the minimum delay after resume */ |
153 | schedule_delayed_work( |
154 | dwork: &musb->deassert_reset_work, delay: remain); |
155 | return; |
156 | } |
157 | |
158 | musb_writeb(mbase, MUSB_POWER, |
159 | power & ~MUSB_POWER_RESUME); |
160 | |
161 | /* Give the core 1 ms to clear MUSB_POWER_RESUME */ |
162 | schedule_delayed_work(dwork: &musb->deassert_reset_work, |
163 | delay: msecs_to_jiffies(m: 1)); |
164 | return; |
165 | } |
166 | |
167 | power &= 0xf0; |
168 | musb_writeb(mbase, MUSB_POWER, |
169 | power | MUSB_POWER_RESET); |
170 | |
171 | musb->port1_status |= USB_PORT_STAT_RESET; |
172 | musb->port1_status &= ~USB_PORT_STAT_ENABLE; |
173 | schedule_delayed_work(dwork: &musb->deassert_reset_work, |
174 | delay: msecs_to_jiffies(m: 50)); |
175 | } else { |
176 | musb_dbg(musb, fmt: "root port reset stopped" ); |
177 | musb_platform_pre_root_reset_end(musb); |
178 | musb_writeb(mbase, MUSB_POWER, |
179 | power & ~MUSB_POWER_RESET); |
180 | musb_platform_post_root_reset_end(musb); |
181 | |
182 | power = musb_readb(mbase, MUSB_POWER); |
183 | if (power & MUSB_POWER_HSMODE) { |
184 | musb_dbg(musb, fmt: "high-speed device connected" ); |
185 | musb->port1_status |= USB_PORT_STAT_HIGH_SPEED; |
186 | } |
187 | |
188 | musb->port1_status &= ~USB_PORT_STAT_RESET; |
189 | musb->port1_status |= USB_PORT_STAT_ENABLE |
190 | | (USB_PORT_STAT_C_RESET << 16) |
191 | | (USB_PORT_STAT_C_ENABLE << 16); |
192 | usb_hcd_poll_rh_status(hcd: musb->hcd); |
193 | |
194 | musb->vbuserr_retry = VBUSERR_RETRY_COUNT; |
195 | } |
196 | } |
197 | |
198 | void musb_root_disconnect(struct musb *musb) |
199 | { |
200 | musb->port1_status = USB_PORT_STAT_POWER |
201 | | (USB_PORT_STAT_C_CONNECTION << 16); |
202 | |
203 | usb_hcd_poll_rh_status(hcd: musb->hcd); |
204 | musb->is_active = 0; |
205 | |
206 | switch (musb_get_state(musb)) { |
207 | case OTG_STATE_A_SUSPEND: |
208 | if (musb->xceiv && musb->xceiv->otg->host->b_hnp_enable) { |
209 | musb_set_state(musb, otg_state: OTG_STATE_A_PERIPHERAL); |
210 | musb->g.is_a_peripheral = 1; |
211 | break; |
212 | } |
213 | fallthrough; |
214 | case OTG_STATE_A_HOST: |
215 | musb_set_state(musb, otg_state: OTG_STATE_A_WAIT_BCON); |
216 | musb->is_active = 0; |
217 | break; |
218 | case OTG_STATE_A_WAIT_VFALL: |
219 | musb_set_state(musb, otg_state: OTG_STATE_B_IDLE); |
220 | break; |
221 | default: |
222 | musb_dbg(musb, fmt: "host disconnect (%s)" , |
223 | musb_otg_state_string(musb)); |
224 | } |
225 | } |
226 | EXPORT_SYMBOL_GPL(musb_root_disconnect); |
227 | |
228 | |
229 | /*---------------------------------------------------------------------*/ |
230 | |
231 | /* Caller may or may not hold musb->lock */ |
232 | int musb_hub_status_data(struct usb_hcd *hcd, char *buf) |
233 | { |
234 | struct musb *musb = hcd_to_musb(hcd); |
235 | int retval = 0; |
236 | |
237 | /* called in_irq() via usb_hcd_poll_rh_status() */ |
238 | if (musb->port1_status & 0xffff0000) { |
239 | *buf = 0x02; |
240 | retval = 1; |
241 | } |
242 | return retval; |
243 | } |
244 | |
245 | static int musb_has_gadget(struct musb *musb) |
246 | { |
247 | /* |
248 | * In host-only mode we start a connection right away. In OTG mode |
249 | * we have to wait until we loaded a gadget. We don't really need a |
250 | * gadget if we operate as a host but we should not start a session |
251 | * as a device without a gadget or else we explode. |
252 | */ |
253 | #ifdef CONFIG_USB_MUSB_HOST |
254 | return 1; |
255 | #else |
256 | return musb->port_mode == MUSB_HOST; |
257 | #endif |
258 | } |
259 | |
260 | int musb_hub_control( |
261 | struct usb_hcd *hcd, |
262 | u16 typeReq, |
263 | u16 wValue, |
264 | u16 wIndex, |
265 | char *buf, |
266 | u16 wLength) |
267 | { |
268 | struct musb *musb = hcd_to_musb(hcd); |
269 | u32 temp; |
270 | int retval = 0; |
271 | unsigned long flags; |
272 | bool start_musb = false; |
273 | |
274 | spin_lock_irqsave(&musb->lock, flags); |
275 | |
276 | if (unlikely(!HCD_HW_ACCESSIBLE(hcd))) { |
277 | spin_unlock_irqrestore(lock: &musb->lock, flags); |
278 | return -ESHUTDOWN; |
279 | } |
280 | |
281 | /* hub features: always zero, setting is a NOP |
282 | * port features: reported, sometimes updated when host is active |
283 | * no indicators |
284 | */ |
285 | switch (typeReq) { |
286 | case ClearHubFeature: |
287 | case SetHubFeature: |
288 | switch (wValue) { |
289 | case C_HUB_OVER_CURRENT: |
290 | case C_HUB_LOCAL_POWER: |
291 | break; |
292 | default: |
293 | goto error; |
294 | } |
295 | break; |
296 | case ClearPortFeature: |
297 | if ((wIndex & 0xff) != 1) |
298 | goto error; |
299 | |
300 | switch (wValue) { |
301 | case USB_PORT_FEAT_ENABLE: |
302 | break; |
303 | case USB_PORT_FEAT_SUSPEND: |
304 | musb_port_suspend(musb, do_suspend: false); |
305 | break; |
306 | case USB_PORT_FEAT_POWER: |
307 | if (!hcd->self.is_b_host) |
308 | musb_platform_set_vbus(musb, is_on: 0); |
309 | break; |
310 | case USB_PORT_FEAT_C_CONNECTION: |
311 | case USB_PORT_FEAT_C_ENABLE: |
312 | case USB_PORT_FEAT_C_OVER_CURRENT: |
313 | case USB_PORT_FEAT_C_RESET: |
314 | case USB_PORT_FEAT_C_SUSPEND: |
315 | break; |
316 | default: |
317 | goto error; |
318 | } |
319 | musb_dbg(musb, fmt: "clear feature %d" , wValue); |
320 | musb->port1_status &= ~(1 << wValue); |
321 | break; |
322 | case GetHubDescriptor: |
323 | { |
324 | struct usb_hub_descriptor *desc = (void *)buf; |
325 | |
326 | desc->bDescLength = 9; |
327 | desc->bDescriptorType = USB_DT_HUB; |
328 | desc->bNbrPorts = 1; |
329 | desc->wHubCharacteristics = cpu_to_le16( |
330 | HUB_CHAR_INDV_PORT_LPSM /* per-port power switching */ |
331 | | HUB_CHAR_NO_OCPM /* no overcurrent reporting */ |
332 | ); |
333 | desc->bPwrOn2PwrGood = 5; /* msec/2 */ |
334 | desc->bHubContrCurrent = 0; |
335 | |
336 | /* workaround bogus struct definition */ |
337 | desc->u.hs.DeviceRemovable[0] = 0x02; /* port 1 */ |
338 | desc->u.hs.DeviceRemovable[1] = 0xff; |
339 | } |
340 | break; |
341 | case GetHubStatus: |
342 | temp = 0; |
343 | *(__le32 *) buf = cpu_to_le32(temp); |
344 | break; |
345 | case GetPortStatus: |
346 | if (wIndex != 1) |
347 | goto error; |
348 | |
349 | put_unaligned(cpu_to_le32(musb->port1_status |
350 | & ~MUSB_PORT_STAT_RESUME), |
351 | (__le32 *) buf); |
352 | |
353 | /* port change status is more interesting */ |
354 | musb_dbg(musb, fmt: "port status %08x" , musb->port1_status); |
355 | break; |
356 | case SetPortFeature: |
357 | if ((wIndex & 0xff) != 1) |
358 | goto error; |
359 | |
360 | switch (wValue) { |
361 | case USB_PORT_FEAT_POWER: |
362 | /* NOTE: this controller has a strange state machine |
363 | * that involves "requesting sessions" according to |
364 | * magic side effects from incompletely-described |
365 | * rules about startup... |
366 | * |
367 | * This call is what really starts the host mode; be |
368 | * very careful about side effects if you reorder any |
369 | * initialization logic, e.g. for OTG, or change any |
370 | * logic relating to VBUS power-up. |
371 | */ |
372 | if (!hcd->self.is_b_host && musb_has_gadget(musb)) |
373 | start_musb = true; |
374 | break; |
375 | case USB_PORT_FEAT_RESET: |
376 | musb_port_reset(musb, do_reset: true); |
377 | break; |
378 | case USB_PORT_FEAT_SUSPEND: |
379 | musb_port_suspend(musb, do_suspend: true); |
380 | break; |
381 | case USB_PORT_FEAT_TEST: |
382 | if (unlikely(is_host_active(musb))) |
383 | goto error; |
384 | |
385 | wIndex >>= 8; |
386 | switch (wIndex) { |
387 | case USB_TEST_J: |
388 | pr_debug("USB_TEST_J\n" ); |
389 | temp = MUSB_TEST_J; |
390 | break; |
391 | case USB_TEST_K: |
392 | pr_debug("USB_TEST_K\n" ); |
393 | temp = MUSB_TEST_K; |
394 | break; |
395 | case USB_TEST_SE0_NAK: |
396 | pr_debug("USB_TEST_SE0_NAK\n" ); |
397 | temp = MUSB_TEST_SE0_NAK; |
398 | break; |
399 | case USB_TEST_PACKET: |
400 | pr_debug("USB_TEST_PACKET\n" ); |
401 | temp = MUSB_TEST_PACKET; |
402 | musb_load_testpacket(musb); |
403 | break; |
404 | case USB_TEST_FORCE_ENABLE: |
405 | pr_debug("USB_TEST_FORCE_ENABLE\n" ); |
406 | temp = MUSB_TEST_FORCE_HOST |
407 | | MUSB_TEST_FORCE_HS; |
408 | |
409 | musb_writeb(musb->mregs, MUSB_DEVCTL, |
410 | MUSB_DEVCTL_SESSION); |
411 | break; |
412 | case 6: |
413 | pr_debug("TEST_FIFO_ACCESS\n" ); |
414 | temp = MUSB_TEST_FIFO_ACCESS; |
415 | break; |
416 | default: |
417 | goto error; |
418 | } |
419 | musb_writeb(musb->mregs, MUSB_TESTMODE, temp); |
420 | break; |
421 | default: |
422 | goto error; |
423 | } |
424 | musb_dbg(musb, fmt: "set feature %d" , wValue); |
425 | musb->port1_status |= 1 << wValue; |
426 | break; |
427 | |
428 | default: |
429 | error: |
430 | /* "protocol stall" on error */ |
431 | retval = -EPIPE; |
432 | } |
433 | spin_unlock_irqrestore(lock: &musb->lock, flags); |
434 | |
435 | if (start_musb) |
436 | musb_start(musb); |
437 | |
438 | return retval; |
439 | } |
440 | |