|
@@ -523,6 +523,383 @@ void musb_hnp_stop(struct musb *musb)
|
|
|
|
|
|
static void musb_recover_from_babble(struct musb *musb);
|
|
|
|
|
|
+static void musb_handle_intr_resume(struct musb *musb, u8 devctl)
|
|
|
+{
|
|
|
+ musb_dbg(musb, "RESUME (%s)",
|
|
|
+ usb_otg_state_string(musb->xceiv->otg->state));
|
|
|
+
|
|
|
+ if (devctl & MUSB_DEVCTL_HM) {
|
|
|
+ switch (musb->xceiv->otg->state) {
|
|
|
+ case OTG_STATE_A_SUSPEND:
|
|
|
+ /* remote wakeup? */
|
|
|
+ musb->port1_status |=
|
|
|
+ (USB_PORT_STAT_C_SUSPEND << 16)
|
|
|
+ | MUSB_PORT_STAT_RESUME;
|
|
|
+ musb->rh_timer = jiffies
|
|
|
+ + msecs_to_jiffies(USB_RESUME_TIMEOUT);
|
|
|
+ musb->xceiv->otg->state = OTG_STATE_A_HOST;
|
|
|
+ musb->is_active = 1;
|
|
|
+ musb_host_resume_root_hub(musb);
|
|
|
+ schedule_delayed_work(&musb->finish_resume_work,
|
|
|
+ msecs_to_jiffies(USB_RESUME_TIMEOUT));
|
|
|
+ break;
|
|
|
+ case OTG_STATE_B_WAIT_ACON:
|
|
|
+ musb->xceiv->otg->state = OTG_STATE_B_PERIPHERAL;
|
|
|
+ musb->is_active = 1;
|
|
|
+ MUSB_DEV_MODE(musb);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ WARNING("bogus %s RESUME (%s)\n",
|
|
|
+ "host",
|
|
|
+ usb_otg_state_string(musb->xceiv->otg->state));
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ switch (musb->xceiv->otg->state) {
|
|
|
+ case OTG_STATE_A_SUSPEND:
|
|
|
+ /* possibly DISCONNECT is upcoming */
|
|
|
+ musb->xceiv->otg->state = OTG_STATE_A_HOST;
|
|
|
+ musb_host_resume_root_hub(musb);
|
|
|
+ break;
|
|
|
+ case OTG_STATE_B_WAIT_ACON:
|
|
|
+ case OTG_STATE_B_PERIPHERAL:
|
|
|
+ /* disconnect while suspended? we may
|
|
|
+ * not get a disconnect irq...
|
|
|
+ */
|
|
|
+ if ((devctl & MUSB_DEVCTL_VBUS)
|
|
|
+ != (3 << MUSB_DEVCTL_VBUS_SHIFT)
|
|
|
+ ) {
|
|
|
+ musb->int_usb |= MUSB_INTR_DISCONNECT;
|
|
|
+ musb->int_usb &= ~MUSB_INTR_SUSPEND;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ musb_g_resume(musb);
|
|
|
+ break;
|
|
|
+ case OTG_STATE_B_IDLE:
|
|
|
+ musb->int_usb &= ~MUSB_INTR_SUSPEND;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ WARNING("bogus %s RESUME (%s)\n",
|
|
|
+ "peripheral",
|
|
|
+ usb_otg_state_string(musb->xceiv->otg->state));
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* return IRQ_HANDLED to tell the caller to return immediately */
|
|
|
+static irqreturn_t musb_handle_intr_sessreq(struct musb *musb, u8 devctl)
|
|
|
+{
|
|
|
+ void __iomem *mbase = musb->mregs;
|
|
|
+
|
|
|
+ if ((devctl & MUSB_DEVCTL_VBUS) == MUSB_DEVCTL_VBUS
|
|
|
+ && (devctl & MUSB_DEVCTL_BDEVICE)) {
|
|
|
+ musb_dbg(musb, "SessReq while on B state");
|
|
|
+ return IRQ_HANDLED;
|
|
|
+ }
|
|
|
+
|
|
|
+ musb_dbg(musb, "SESSION_REQUEST (%s)",
|
|
|
+ usb_otg_state_string(musb->xceiv->otg->state));
|
|
|
+
|
|
|
+ /* IRQ arrives from ID pin sense or (later, if VBUS power
|
|
|
+ * is removed) SRP. responses are time critical:
|
|
|
+ * - turn on VBUS (with silicon-specific mechanism)
|
|
|
+ * - go through A_WAIT_VRISE
|
|
|
+ * - ... to A_WAIT_BCON.
|
|
|
+ * a_wait_vrise_tmout triggers VBUS_ERROR transitions
|
|
|
+ */
|
|
|
+ musb_writeb(mbase, MUSB_DEVCTL, MUSB_DEVCTL_SESSION);
|
|
|
+ musb->ep0_stage = MUSB_EP0_START;
|
|
|
+ musb->xceiv->otg->state = OTG_STATE_A_IDLE;
|
|
|
+ MUSB_HST_MODE(musb);
|
|
|
+ musb_platform_set_vbus(musb, 1);
|
|
|
+
|
|
|
+ return IRQ_NONE;
|
|
|
+}
|
|
|
+
|
|
|
+static void musb_handle_intr_vbuserr(struct musb *musb, u8 devctl)
|
|
|
+{
|
|
|
+ int ignore = 0;
|
|
|
+
|
|
|
+ /* During connection as an A-Device, we may see a short
|
|
|
+ * current spikes causing voltage drop, because of cable
|
|
|
+ * and peripheral capacitance combined with vbus draw.
|
|
|
+ * (So: less common with truly self-powered devices, where
|
|
|
+ * vbus doesn't act like a power supply.)
|
|
|
+ *
|
|
|
+ * Such spikes are short; usually less than ~500 usec, max
|
|
|
+ * of ~2 msec. That is, they're not sustained overcurrent
|
|
|
+ * errors, though they're reported using VBUSERROR irqs.
|
|
|
+ *
|
|
|
+ * Workarounds: (a) hardware: use self powered devices.
|
|
|
+ * (b) software: ignore non-repeated VBUS errors.
|
|
|
+ *
|
|
|
+ * REVISIT: do delays from lots of DEBUG_KERNEL checks
|
|
|
+ * make trouble here, keeping VBUS < 4.4V ?
|
|
|
+ */
|
|
|
+ switch (musb->xceiv->otg->state) {
|
|
|
+ case OTG_STATE_A_HOST:
|
|
|
+ /* recovery is dicey once we've gotten past the
|
|
|
+ * initial stages of enumeration, but if VBUS
|
|
|
+ * stayed ok at the other end of the link, and
|
|
|
+ * another reset is due (at least for high speed,
|
|
|
+ * to redo the chirp etc), it might work OK...
|
|
|
+ */
|
|
|
+ case OTG_STATE_A_WAIT_BCON:
|
|
|
+ case OTG_STATE_A_WAIT_VRISE:
|
|
|
+ if (musb->vbuserr_retry) {
|
|
|
+ void __iomem *mbase = musb->mregs;
|
|
|
+
|
|
|
+ musb->vbuserr_retry--;
|
|
|
+ ignore = 1;
|
|
|
+ devctl |= MUSB_DEVCTL_SESSION;
|
|
|
+ musb_writeb(mbase, MUSB_DEVCTL, devctl);
|
|
|
+ } else {
|
|
|
+ musb->port1_status |=
|
|
|
+ USB_PORT_STAT_OVERCURRENT
|
|
|
+ | (USB_PORT_STAT_C_OVERCURRENT << 16);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ dev_printk(ignore ? KERN_DEBUG : KERN_ERR, musb->controller,
|
|
|
+ "VBUS_ERROR in %s (%02x, %s), retry #%d, port1 %08x\n",
|
|
|
+ usb_otg_state_string(musb->xceiv->otg->state),
|
|
|
+ devctl,
|
|
|
+ ({ char *s;
|
|
|
+ switch (devctl & MUSB_DEVCTL_VBUS) {
|
|
|
+ case 0 << MUSB_DEVCTL_VBUS_SHIFT:
|
|
|
+ s = "<SessEnd"; break;
|
|
|
+ case 1 << MUSB_DEVCTL_VBUS_SHIFT:
|
|
|
+ s = "<AValid"; break;
|
|
|
+ case 2 << MUSB_DEVCTL_VBUS_SHIFT:
|
|
|
+ s = "<VBusValid"; break;
|
|
|
+ /* case 3 << MUSB_DEVCTL_VBUS_SHIFT: */
|
|
|
+ default:
|
|
|
+ s = "VALID"; break;
|
|
|
+ } s; }),
|
|
|
+ VBUSERR_RETRY_COUNT - musb->vbuserr_retry,
|
|
|
+ musb->port1_status);
|
|
|
+
|
|
|
+ /* go through A_WAIT_VFALL then start a new session */
|
|
|
+ if (!ignore)
|
|
|
+ musb_platform_set_vbus(musb, 0);
|
|
|
+}
|
|
|
+
|
|
|
+static void musb_handle_intr_suspend(struct musb *musb, u8 devctl)
|
|
|
+{
|
|
|
+ musb_dbg(musb, "SUSPEND (%s) devctl %02x",
|
|
|
+ usb_otg_state_string(musb->xceiv->otg->state), devctl);
|
|
|
+
|
|
|
+ switch (musb->xceiv->otg->state) {
|
|
|
+ case OTG_STATE_A_PERIPHERAL:
|
|
|
+ /* We also come here if the cable is removed, since
|
|
|
+ * this silicon doesn't report ID-no-longer-grounded.
|
|
|
+ *
|
|
|
+ * We depend on T(a_wait_bcon) to shut us down, and
|
|
|
+ * hope users don't do anything dicey during this
|
|
|
+ * undesired detour through A_WAIT_BCON.
|
|
|
+ */
|
|
|
+ musb_hnp_stop(musb);
|
|
|
+ musb_host_resume_root_hub(musb);
|
|
|
+ musb_root_disconnect(musb);
|
|
|
+ musb_platform_try_idle(musb, jiffies
|
|
|
+ + msecs_to_jiffies(musb->a_wait_bcon
|
|
|
+ ? : OTG_TIME_A_WAIT_BCON));
|
|
|
+
|
|
|
+ break;
|
|
|
+ case OTG_STATE_B_IDLE:
|
|
|
+ if (!musb->is_active)
|
|
|
+ break;
|
|
|
+ /* fall through */
|
|
|
+ case OTG_STATE_B_PERIPHERAL:
|
|
|
+ musb_g_suspend(musb);
|
|
|
+ musb->is_active = musb->g.b_hnp_enable;
|
|
|
+ if (musb->is_active) {
|
|
|
+ musb->xceiv->otg->state = OTG_STATE_B_WAIT_ACON;
|
|
|
+ musb_dbg(musb, "HNP: Setting timer for b_ase0_brst");
|
|
|
+ mod_timer(&musb->otg_timer, jiffies
|
|
|
+ + msecs_to_jiffies(
|
|
|
+ OTG_TIME_B_ASE0_BRST));
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case OTG_STATE_A_WAIT_BCON:
|
|
|
+ if (musb->a_wait_bcon != 0)
|
|
|
+ musb_platform_try_idle(musb, jiffies
|
|
|
+ + msecs_to_jiffies(musb->a_wait_bcon));
|
|
|
+ break;
|
|
|
+ case OTG_STATE_A_HOST:
|
|
|
+ musb->xceiv->otg->state = OTG_STATE_A_SUSPEND;
|
|
|
+ musb->is_active = musb->hcd->self.b_hnp_enable;
|
|
|
+ break;
|
|
|
+ case OTG_STATE_B_HOST:
|
|
|
+ /* Transition to B_PERIPHERAL, see 6.8.2.6 p 44 */
|
|
|
+ musb_dbg(musb, "REVISIT: SUSPEND as B_HOST");
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ /* "should not happen" */
|
|
|
+ musb->is_active = 0;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void musb_handle_intr_connect(struct musb *musb, u8 devctl, u8 int_usb)
|
|
|
+{
|
|
|
+ struct usb_hcd *hcd = musb->hcd;
|
|
|
+
|
|
|
+ musb->is_active = 1;
|
|
|
+ musb->ep0_stage = MUSB_EP0_START;
|
|
|
+
|
|
|
+ musb->intrtxe = musb->epmask;
|
|
|
+ musb_writew(musb->mregs, MUSB_INTRTXE, musb->intrtxe);
|
|
|
+ musb->intrrxe = musb->epmask & 0xfffe;
|
|
|
+ musb_writew(musb->mregs, MUSB_INTRRXE, musb->intrrxe);
|
|
|
+ musb_writeb(musb->mregs, MUSB_INTRUSBE, 0xf7);
|
|
|
+ musb->port1_status &= ~(USB_PORT_STAT_LOW_SPEED
|
|
|
+ |USB_PORT_STAT_HIGH_SPEED
|
|
|
+ |USB_PORT_STAT_ENABLE
|
|
|
+ );
|
|
|
+ musb->port1_status |= USB_PORT_STAT_CONNECTION
|
|
|
+ |(USB_PORT_STAT_C_CONNECTION << 16);
|
|
|
+
|
|
|
+ /* high vs full speed is just a guess until after reset */
|
|
|
+ if (devctl & MUSB_DEVCTL_LSDEV)
|
|
|
+ musb->port1_status |= USB_PORT_STAT_LOW_SPEED;
|
|
|
+
|
|
|
+ /* indicate new connection to OTG machine */
|
|
|
+ switch (musb->xceiv->otg->state) {
|
|
|
+ case OTG_STATE_B_PERIPHERAL:
|
|
|
+ if (int_usb & MUSB_INTR_SUSPEND) {
|
|
|
+ musb_dbg(musb, "HNP: SUSPEND+CONNECT, now b_host");
|
|
|
+ int_usb &= ~MUSB_INTR_SUSPEND;
|
|
|
+ goto b_host;
|
|
|
+ } else
|
|
|
+ musb_dbg(musb, "CONNECT as b_peripheral???");
|
|
|
+ break;
|
|
|
+ case OTG_STATE_B_WAIT_ACON:
|
|
|
+ musb_dbg(musb, "HNP: CONNECT, now b_host");
|
|
|
+b_host:
|
|
|
+ musb->xceiv->otg->state = OTG_STATE_B_HOST;
|
|
|
+ if (musb->hcd)
|
|
|
+ musb->hcd->self.is_b_host = 1;
|
|
|
+ del_timer(&musb->otg_timer);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ if ((devctl & MUSB_DEVCTL_VBUS)
|
|
|
+ == (3 << MUSB_DEVCTL_VBUS_SHIFT)) {
|
|
|
+ musb->xceiv->otg->state = OTG_STATE_A_HOST;
|
|
|
+ if (hcd)
|
|
|
+ hcd->self.is_b_host = 0;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ musb_host_poke_root_hub(musb);
|
|
|
+
|
|
|
+ musb_dbg(musb, "CONNECT (%s) devctl %02x",
|
|
|
+ usb_otg_state_string(musb->xceiv->otg->state), devctl);
|
|
|
+}
|
|
|
+
|
|
|
+static void musb_handle_intr_disconnect(struct musb *musb, u8 devctl)
|
|
|
+{
|
|
|
+ musb_dbg(musb, "DISCONNECT (%s) as %s, devctl %02x",
|
|
|
+ usb_otg_state_string(musb->xceiv->otg->state),
|
|
|
+ MUSB_MODE(musb), devctl);
|
|
|
+
|
|
|
+ switch (musb->xceiv->otg->state) {
|
|
|
+ case OTG_STATE_A_HOST:
|
|
|
+ case OTG_STATE_A_SUSPEND:
|
|
|
+ musb_host_resume_root_hub(musb);
|
|
|
+ musb_root_disconnect(musb);
|
|
|
+ if (musb->a_wait_bcon != 0)
|
|
|
+ musb_platform_try_idle(musb, jiffies
|
|
|
+ + msecs_to_jiffies(musb->a_wait_bcon));
|
|
|
+ break;
|
|
|
+ case OTG_STATE_B_HOST:
|
|
|
+ /* REVISIT this behaves for "real disconnect"
|
|
|
+ * cases; make sure the other transitions from
|
|
|
+ * from B_HOST act right too. The B_HOST code
|
|
|
+ * in hnp_stop() is currently not used...
|
|
|
+ */
|
|
|
+ musb_root_disconnect(musb);
|
|
|
+ if (musb->hcd)
|
|
|
+ musb->hcd->self.is_b_host = 0;
|
|
|
+ musb->xceiv->otg->state = OTG_STATE_B_PERIPHERAL;
|
|
|
+ MUSB_DEV_MODE(musb);
|
|
|
+ musb_g_disconnect(musb);
|
|
|
+ break;
|
|
|
+ case OTG_STATE_A_PERIPHERAL:
|
|
|
+ musb_hnp_stop(musb);
|
|
|
+ musb_root_disconnect(musb);
|
|
|
+ /* FALLTHROUGH */
|
|
|
+ case OTG_STATE_B_WAIT_ACON:
|
|
|
+ /* FALLTHROUGH */
|
|
|
+ case OTG_STATE_B_PERIPHERAL:
|
|
|
+ case OTG_STATE_B_IDLE:
|
|
|
+ musb_g_disconnect(musb);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ WARNING("unhandled DISCONNECT transition (%s)\n",
|
|
|
+ usb_otg_state_string(musb->xceiv->otg->state));
|
|
|
+ break;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * mentor saves a bit: bus reset and babble share the same irq.
|
|
|
+ * only host sees babble; only peripheral sees bus reset.
|
|
|
+ */
|
|
|
+static void musb_handle_intr_reset(struct musb *musb)
|
|
|
+{
|
|
|
+ if (is_host_active(musb)) {
|
|
|
+ /*
|
|
|
+ * When BABBLE happens what we can depends on which
|
|
|
+ * platform MUSB is running, because some platforms
|
|
|
+ * implemented proprietary means for 'recovering' from
|
|
|
+ * Babble conditions. One such platform is AM335x. In
|
|
|
+ * most cases, however, the only thing we can do is
|
|
|
+ * drop the session.
|
|
|
+ */
|
|
|
+ dev_err(musb->controller, "Babble\n");
|
|
|
+ musb_recover_from_babble(musb);
|
|
|
+ } else {
|
|
|
+ musb_dbg(musb, "BUS RESET as %s",
|
|
|
+ usb_otg_state_string(musb->xceiv->otg->state));
|
|
|
+ switch (musb->xceiv->otg->state) {
|
|
|
+ case OTG_STATE_A_SUSPEND:
|
|
|
+ musb_g_reset(musb);
|
|
|
+ /* FALLTHROUGH */
|
|
|
+ case OTG_STATE_A_WAIT_BCON: /* OPT TD.4.7-900ms */
|
|
|
+ /* never use invalid T(a_wait_bcon) */
|
|
|
+ musb_dbg(musb, "HNP: in %s, %d msec timeout",
|
|
|
+ usb_otg_state_string(musb->xceiv->otg->state),
|
|
|
+ TA_WAIT_BCON(musb));
|
|
|
+ mod_timer(&musb->otg_timer, jiffies
|
|
|
+ + msecs_to_jiffies(TA_WAIT_BCON(musb)));
|
|
|
+ break;
|
|
|
+ case OTG_STATE_A_PERIPHERAL:
|
|
|
+ del_timer(&musb->otg_timer);
|
|
|
+ musb_g_reset(musb);
|
|
|
+ break;
|
|
|
+ case OTG_STATE_B_WAIT_ACON:
|
|
|
+ musb_dbg(musb, "HNP: RESET (%s), to b_peripheral",
|
|
|
+ usb_otg_state_string(musb->xceiv->otg->state));
|
|
|
+ musb->xceiv->otg->state = OTG_STATE_B_PERIPHERAL;
|
|
|
+ musb_g_reset(musb);
|
|
|
+ break;
|
|
|
+ case OTG_STATE_B_IDLE:
|
|
|
+ musb->xceiv->otg->state = OTG_STATE_B_PERIPHERAL;
|
|
|
+ /* FALLTHROUGH */
|
|
|
+ case OTG_STATE_B_PERIPHERAL:
|
|
|
+ musb_g_reset(musb);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ musb_dbg(musb, "Unhandled BUS RESET as %s",
|
|
|
+ usb_otg_state_string(musb->xceiv->otg->state));
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
/*
|
|
|
* Interrupt Service Routine to record USB "global" interrupts.
|
|
|
* Since these do not happen often and signify things of
|
|
@@ -547,379 +924,40 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb,
|
|
|
* spurious RESUME irqs happen too, paired with SUSPEND.
|
|
|
*/
|
|
|
if (int_usb & MUSB_INTR_RESUME) {
|
|
|
+ musb_handle_intr_resume(musb, devctl);
|
|
|
handled = IRQ_HANDLED;
|
|
|
- musb_dbg(musb, "RESUME (%s)",
|
|
|
- usb_otg_state_string(musb->xceiv->otg->state));
|
|
|
-
|
|
|
- if (devctl & MUSB_DEVCTL_HM) {
|
|
|
- switch (musb->xceiv->otg->state) {
|
|
|
- case OTG_STATE_A_SUSPEND:
|
|
|
- /* remote wakeup? */
|
|
|
- musb->port1_status |=
|
|
|
- (USB_PORT_STAT_C_SUSPEND << 16)
|
|
|
- | MUSB_PORT_STAT_RESUME;
|
|
|
- musb->rh_timer = jiffies
|
|
|
- + msecs_to_jiffies(USB_RESUME_TIMEOUT);
|
|
|
- musb->xceiv->otg->state = OTG_STATE_A_HOST;
|
|
|
- musb->is_active = 1;
|
|
|
- musb_host_resume_root_hub(musb);
|
|
|
- schedule_delayed_work(&musb->finish_resume_work,
|
|
|
- msecs_to_jiffies(USB_RESUME_TIMEOUT));
|
|
|
- break;
|
|
|
- case OTG_STATE_B_WAIT_ACON:
|
|
|
- musb->xceiv->otg->state = OTG_STATE_B_PERIPHERAL;
|
|
|
- musb->is_active = 1;
|
|
|
- MUSB_DEV_MODE(musb);
|
|
|
- break;
|
|
|
- default:
|
|
|
- WARNING("bogus %s RESUME (%s)\n",
|
|
|
- "host",
|
|
|
- usb_otg_state_string(musb->xceiv->otg->state));
|
|
|
- }
|
|
|
- } else {
|
|
|
- switch (musb->xceiv->otg->state) {
|
|
|
- case OTG_STATE_A_SUSPEND:
|
|
|
- /* possibly DISCONNECT is upcoming */
|
|
|
- musb->xceiv->otg->state = OTG_STATE_A_HOST;
|
|
|
- musb_host_resume_root_hub(musb);
|
|
|
- break;
|
|
|
- case OTG_STATE_B_WAIT_ACON:
|
|
|
- case OTG_STATE_B_PERIPHERAL:
|
|
|
- /* disconnect while suspended? we may
|
|
|
- * not get a disconnect irq...
|
|
|
- */
|
|
|
- if ((devctl & MUSB_DEVCTL_VBUS)
|
|
|
- != (3 << MUSB_DEVCTL_VBUS_SHIFT)
|
|
|
- ) {
|
|
|
- musb->int_usb |= MUSB_INTR_DISCONNECT;
|
|
|
- musb->int_usb &= ~MUSB_INTR_SUSPEND;
|
|
|
- break;
|
|
|
- }
|
|
|
- musb_g_resume(musb);
|
|
|
- break;
|
|
|
- case OTG_STATE_B_IDLE:
|
|
|
- musb->int_usb &= ~MUSB_INTR_SUSPEND;
|
|
|
- break;
|
|
|
- default:
|
|
|
- WARNING("bogus %s RESUME (%s)\n",
|
|
|
- "peripheral",
|
|
|
- usb_otg_state_string(musb->xceiv->otg->state));
|
|
|
- }
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
/* see manual for the order of the tests */
|
|
|
if (int_usb & MUSB_INTR_SESSREQ) {
|
|
|
- void __iomem *mbase = musb->mregs;
|
|
|
-
|
|
|
- if ((devctl & MUSB_DEVCTL_VBUS) == MUSB_DEVCTL_VBUS
|
|
|
- && (devctl & MUSB_DEVCTL_BDEVICE)) {
|
|
|
- musb_dbg(musb, "SessReq while on B state");
|
|
|
+ if (musb_handle_intr_sessreq(musb, devctl))
|
|
|
return IRQ_HANDLED;
|
|
|
- }
|
|
|
-
|
|
|
- musb_dbg(musb, "SESSION_REQUEST (%s)",
|
|
|
- usb_otg_state_string(musb->xceiv->otg->state));
|
|
|
-
|
|
|
- /* IRQ arrives from ID pin sense or (later, if VBUS power
|
|
|
- * is removed) SRP. responses are time critical:
|
|
|
- * - turn on VBUS (with silicon-specific mechanism)
|
|
|
- * - go through A_WAIT_VRISE
|
|
|
- * - ... to A_WAIT_BCON.
|
|
|
- * a_wait_vrise_tmout triggers VBUS_ERROR transitions
|
|
|
- */
|
|
|
- musb_writeb(mbase, MUSB_DEVCTL, MUSB_DEVCTL_SESSION);
|
|
|
- musb->ep0_stage = MUSB_EP0_START;
|
|
|
- musb->xceiv->otg->state = OTG_STATE_A_IDLE;
|
|
|
- MUSB_HST_MODE(musb);
|
|
|
- musb_platform_set_vbus(musb, 1);
|
|
|
-
|
|
|
handled = IRQ_HANDLED;
|
|
|
}
|
|
|
|
|
|
if (int_usb & MUSB_INTR_VBUSERROR) {
|
|
|
- int ignore = 0;
|
|
|
-
|
|
|
- /* During connection as an A-Device, we may see a short
|
|
|
- * current spikes causing voltage drop, because of cable
|
|
|
- * and peripheral capacitance combined with vbus draw.
|
|
|
- * (So: less common with truly self-powered devices, where
|
|
|
- * vbus doesn't act like a power supply.)
|
|
|
- *
|
|
|
- * Such spikes are short; usually less than ~500 usec, max
|
|
|
- * of ~2 msec. That is, they're not sustained overcurrent
|
|
|
- * errors, though they're reported using VBUSERROR irqs.
|
|
|
- *
|
|
|
- * Workarounds: (a) hardware: use self powered devices.
|
|
|
- * (b) software: ignore non-repeated VBUS errors.
|
|
|
- *
|
|
|
- * REVISIT: do delays from lots of DEBUG_KERNEL checks
|
|
|
- * make trouble here, keeping VBUS < 4.4V ?
|
|
|
- */
|
|
|
- switch (musb->xceiv->otg->state) {
|
|
|
- case OTG_STATE_A_HOST:
|
|
|
- /* recovery is dicey once we've gotten past the
|
|
|
- * initial stages of enumeration, but if VBUS
|
|
|
- * stayed ok at the other end of the link, and
|
|
|
- * another reset is due (at least for high speed,
|
|
|
- * to redo the chirp etc), it might work OK...
|
|
|
- */
|
|
|
- case OTG_STATE_A_WAIT_BCON:
|
|
|
- case OTG_STATE_A_WAIT_VRISE:
|
|
|
- if (musb->vbuserr_retry) {
|
|
|
- void __iomem *mbase = musb->mregs;
|
|
|
-
|
|
|
- musb->vbuserr_retry--;
|
|
|
- ignore = 1;
|
|
|
- devctl |= MUSB_DEVCTL_SESSION;
|
|
|
- musb_writeb(mbase, MUSB_DEVCTL, devctl);
|
|
|
- } else {
|
|
|
- musb->port1_status |=
|
|
|
- USB_PORT_STAT_OVERCURRENT
|
|
|
- | (USB_PORT_STAT_C_OVERCURRENT << 16);
|
|
|
- }
|
|
|
- break;
|
|
|
- default:
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- dev_printk(ignore ? KERN_DEBUG : KERN_ERR, musb->controller,
|
|
|
- "VBUS_ERROR in %s (%02x, %s), retry #%d, port1 %08x\n",
|
|
|
- usb_otg_state_string(musb->xceiv->otg->state),
|
|
|
- devctl,
|
|
|
- ({ char *s;
|
|
|
- switch (devctl & MUSB_DEVCTL_VBUS) {
|
|
|
- case 0 << MUSB_DEVCTL_VBUS_SHIFT:
|
|
|
- s = "<SessEnd"; break;
|
|
|
- case 1 << MUSB_DEVCTL_VBUS_SHIFT:
|
|
|
- s = "<AValid"; break;
|
|
|
- case 2 << MUSB_DEVCTL_VBUS_SHIFT:
|
|
|
- s = "<VBusValid"; break;
|
|
|
- /* case 3 << MUSB_DEVCTL_VBUS_SHIFT: */
|
|
|
- default:
|
|
|
- s = "VALID"; break;
|
|
|
- } s; }),
|
|
|
- VBUSERR_RETRY_COUNT - musb->vbuserr_retry,
|
|
|
- musb->port1_status);
|
|
|
-
|
|
|
- /* go through A_WAIT_VFALL then start a new session */
|
|
|
- if (!ignore)
|
|
|
- musb_platform_set_vbus(musb, 0);
|
|
|
+ musb_handle_intr_vbuserr(musb, devctl);
|
|
|
handled = IRQ_HANDLED;
|
|
|
}
|
|
|
|
|
|
if (int_usb & MUSB_INTR_SUSPEND) {
|
|
|
- musb_dbg(musb, "SUSPEND (%s) devctl %02x",
|
|
|
- usb_otg_state_string(musb->xceiv->otg->state), devctl);
|
|
|
+ musb_handle_intr_suspend(musb, devctl);
|
|
|
handled = IRQ_HANDLED;
|
|
|
-
|
|
|
- switch (musb->xceiv->otg->state) {
|
|
|
- case OTG_STATE_A_PERIPHERAL:
|
|
|
- /* We also come here if the cable is removed, since
|
|
|
- * this silicon doesn't report ID-no-longer-grounded.
|
|
|
- *
|
|
|
- * We depend on T(a_wait_bcon) to shut us down, and
|
|
|
- * hope users don't do anything dicey during this
|
|
|
- * undesired detour through A_WAIT_BCON.
|
|
|
- */
|
|
|
- musb_hnp_stop(musb);
|
|
|
- musb_host_resume_root_hub(musb);
|
|
|
- musb_root_disconnect(musb);
|
|
|
- musb_platform_try_idle(musb, jiffies
|
|
|
- + msecs_to_jiffies(musb->a_wait_bcon
|
|
|
- ? : OTG_TIME_A_WAIT_BCON));
|
|
|
-
|
|
|
- break;
|
|
|
- case OTG_STATE_B_IDLE:
|
|
|
- if (!musb->is_active)
|
|
|
- break;
|
|
|
- /* fall through */
|
|
|
- case OTG_STATE_B_PERIPHERAL:
|
|
|
- musb_g_suspend(musb);
|
|
|
- musb->is_active = musb->g.b_hnp_enable;
|
|
|
- if (musb->is_active) {
|
|
|
- musb->xceiv->otg->state = OTG_STATE_B_WAIT_ACON;
|
|
|
- musb_dbg(musb, "HNP: Setting timer for b_ase0_brst");
|
|
|
- mod_timer(&musb->otg_timer, jiffies
|
|
|
- + msecs_to_jiffies(
|
|
|
- OTG_TIME_B_ASE0_BRST));
|
|
|
- }
|
|
|
- break;
|
|
|
- case OTG_STATE_A_WAIT_BCON:
|
|
|
- if (musb->a_wait_bcon != 0)
|
|
|
- musb_platform_try_idle(musb, jiffies
|
|
|
- + msecs_to_jiffies(musb->a_wait_bcon));
|
|
|
- break;
|
|
|
- case OTG_STATE_A_HOST:
|
|
|
- musb->xceiv->otg->state = OTG_STATE_A_SUSPEND;
|
|
|
- musb->is_active = musb->hcd->self.b_hnp_enable;
|
|
|
- break;
|
|
|
- case OTG_STATE_B_HOST:
|
|
|
- /* Transition to B_PERIPHERAL, see 6.8.2.6 p 44 */
|
|
|
- musb_dbg(musb, "REVISIT: SUSPEND as B_HOST");
|
|
|
- break;
|
|
|
- default:
|
|
|
- /* "should not happen" */
|
|
|
- musb->is_active = 0;
|
|
|
- break;
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
if (int_usb & MUSB_INTR_CONNECT) {
|
|
|
- struct usb_hcd *hcd = musb->hcd;
|
|
|
-
|
|
|
+ musb_handle_intr_connect(musb, devctl, int_usb);
|
|
|
handled = IRQ_HANDLED;
|
|
|
- musb->is_active = 1;
|
|
|
-
|
|
|
- musb->ep0_stage = MUSB_EP0_START;
|
|
|
-
|
|
|
- musb->intrtxe = musb->epmask;
|
|
|
- musb_writew(musb->mregs, MUSB_INTRTXE, musb->intrtxe);
|
|
|
- musb->intrrxe = musb->epmask & 0xfffe;
|
|
|
- musb_writew(musb->mregs, MUSB_INTRRXE, musb->intrrxe);
|
|
|
- musb_writeb(musb->mregs, MUSB_INTRUSBE, 0xf7);
|
|
|
- musb->port1_status &= ~(USB_PORT_STAT_LOW_SPEED
|
|
|
- |USB_PORT_STAT_HIGH_SPEED
|
|
|
- |USB_PORT_STAT_ENABLE
|
|
|
- );
|
|
|
- musb->port1_status |= USB_PORT_STAT_CONNECTION
|
|
|
- |(USB_PORT_STAT_C_CONNECTION << 16);
|
|
|
-
|
|
|
- /* high vs full speed is just a guess until after reset */
|
|
|
- if (devctl & MUSB_DEVCTL_LSDEV)
|
|
|
- musb->port1_status |= USB_PORT_STAT_LOW_SPEED;
|
|
|
-
|
|
|
- /* indicate new connection to OTG machine */
|
|
|
- switch (musb->xceiv->otg->state) {
|
|
|
- case OTG_STATE_B_PERIPHERAL:
|
|
|
- if (int_usb & MUSB_INTR_SUSPEND) {
|
|
|
- musb_dbg(musb, "HNP: SUSPEND+CONNECT, now b_host");
|
|
|
- int_usb &= ~MUSB_INTR_SUSPEND;
|
|
|
- goto b_host;
|
|
|
- } else
|
|
|
- musb_dbg(musb, "CONNECT as b_peripheral???");
|
|
|
- break;
|
|
|
- case OTG_STATE_B_WAIT_ACON:
|
|
|
- musb_dbg(musb, "HNP: CONNECT, now b_host");
|
|
|
-b_host:
|
|
|
- musb->xceiv->otg->state = OTG_STATE_B_HOST;
|
|
|
- if (musb->hcd)
|
|
|
- musb->hcd->self.is_b_host = 1;
|
|
|
- del_timer(&musb->otg_timer);
|
|
|
- break;
|
|
|
- default:
|
|
|
- if ((devctl & MUSB_DEVCTL_VBUS)
|
|
|
- == (3 << MUSB_DEVCTL_VBUS_SHIFT)) {
|
|
|
- musb->xceiv->otg->state = OTG_STATE_A_HOST;
|
|
|
- if (hcd)
|
|
|
- hcd->self.is_b_host = 0;
|
|
|
- }
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- musb_host_poke_root_hub(musb);
|
|
|
-
|
|
|
- musb_dbg(musb, "CONNECT (%s) devctl %02x",
|
|
|
- usb_otg_state_string(musb->xceiv->otg->state), devctl);
|
|
|
}
|
|
|
|
|
|
if (int_usb & MUSB_INTR_DISCONNECT) {
|
|
|
- musb_dbg(musb, "DISCONNECT (%s) as %s, devctl %02x",
|
|
|
- usb_otg_state_string(musb->xceiv->otg->state),
|
|
|
- MUSB_MODE(musb), devctl);
|
|
|
+ musb_handle_intr_disconnect(musb, devctl);
|
|
|
handled = IRQ_HANDLED;
|
|
|
-
|
|
|
- switch (musb->xceiv->otg->state) {
|
|
|
- case OTG_STATE_A_HOST:
|
|
|
- case OTG_STATE_A_SUSPEND:
|
|
|
- musb_host_resume_root_hub(musb);
|
|
|
- musb_root_disconnect(musb);
|
|
|
- if (musb->a_wait_bcon != 0)
|
|
|
- musb_platform_try_idle(musb, jiffies
|
|
|
- + msecs_to_jiffies(musb->a_wait_bcon));
|
|
|
- break;
|
|
|
- case OTG_STATE_B_HOST:
|
|
|
- /* REVISIT this behaves for "real disconnect"
|
|
|
- * cases; make sure the other transitions from
|
|
|
- * from B_HOST act right too. The B_HOST code
|
|
|
- * in hnp_stop() is currently not used...
|
|
|
- */
|
|
|
- musb_root_disconnect(musb);
|
|
|
- if (musb->hcd)
|
|
|
- musb->hcd->self.is_b_host = 0;
|
|
|
- musb->xceiv->otg->state = OTG_STATE_B_PERIPHERAL;
|
|
|
- MUSB_DEV_MODE(musb);
|
|
|
- musb_g_disconnect(musb);
|
|
|
- break;
|
|
|
- case OTG_STATE_A_PERIPHERAL:
|
|
|
- musb_hnp_stop(musb);
|
|
|
- musb_root_disconnect(musb);
|
|
|
- /* FALLTHROUGH */
|
|
|
- case OTG_STATE_B_WAIT_ACON:
|
|
|
- /* FALLTHROUGH */
|
|
|
- case OTG_STATE_B_PERIPHERAL:
|
|
|
- case OTG_STATE_B_IDLE:
|
|
|
- musb_g_disconnect(musb);
|
|
|
- break;
|
|
|
- default:
|
|
|
- WARNING("unhandled DISCONNECT transition (%s)\n",
|
|
|
- usb_otg_state_string(musb->xceiv->otg->state));
|
|
|
- break;
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
- /* mentor saves a bit: bus reset and babble share the same irq.
|
|
|
- * only host sees babble; only peripheral sees bus reset.
|
|
|
- */
|
|
|
if (int_usb & MUSB_INTR_RESET) {
|
|
|
+ musb_handle_intr_reset(musb);
|
|
|
handled = IRQ_HANDLED;
|
|
|
- if (is_host_active(musb)) {
|
|
|
- /*
|
|
|
- * When BABBLE happens what we can depends on which
|
|
|
- * platform MUSB is running, because some platforms
|
|
|
- * implemented proprietary means for 'recovering' from
|
|
|
- * Babble conditions. One such platform is AM335x. In
|
|
|
- * most cases, however, the only thing we can do is
|
|
|
- * drop the session.
|
|
|
- */
|
|
|
- dev_err(musb->controller, "Babble\n");
|
|
|
- musb_recover_from_babble(musb);
|
|
|
- } else {
|
|
|
- musb_dbg(musb, "BUS RESET as %s",
|
|
|
- usb_otg_state_string(musb->xceiv->otg->state));
|
|
|
- switch (musb->xceiv->otg->state) {
|
|
|
- case OTG_STATE_A_SUSPEND:
|
|
|
- musb_g_reset(musb);
|
|
|
- /* FALLTHROUGH */
|
|
|
- case OTG_STATE_A_WAIT_BCON: /* OPT TD.4.7-900ms */
|
|
|
- /* never use invalid T(a_wait_bcon) */
|
|
|
- musb_dbg(musb, "HNP: in %s, %d msec timeout",
|
|
|
- usb_otg_state_string(musb->xceiv->otg->state),
|
|
|
- TA_WAIT_BCON(musb));
|
|
|
- mod_timer(&musb->otg_timer, jiffies
|
|
|
- + msecs_to_jiffies(TA_WAIT_BCON(musb)));
|
|
|
- break;
|
|
|
- case OTG_STATE_A_PERIPHERAL:
|
|
|
- del_timer(&musb->otg_timer);
|
|
|
- musb_g_reset(musb);
|
|
|
- break;
|
|
|
- case OTG_STATE_B_WAIT_ACON:
|
|
|
- musb_dbg(musb, "HNP: RESET (%s), to b_peripheral",
|
|
|
- usb_otg_state_string(musb->xceiv->otg->state));
|
|
|
- musb->xceiv->otg->state = OTG_STATE_B_PERIPHERAL;
|
|
|
- musb_g_reset(musb);
|
|
|
- break;
|
|
|
- case OTG_STATE_B_IDLE:
|
|
|
- musb->xceiv->otg->state = OTG_STATE_B_PERIPHERAL;
|
|
|
- /* FALLTHROUGH */
|
|
|
- case OTG_STATE_B_PERIPHERAL:
|
|
|
- musb_g_reset(musb);
|
|
|
- break;
|
|
|
- default:
|
|
|
- musb_dbg(musb, "Unhandled BUS RESET as %s",
|
|
|
- usb_otg_state_string(musb->xceiv->otg->state));
|
|
|
- }
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
#if 0
|