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