[RFC] usb: musb: dsps, OTG detection
Markus Pargmann
mpa at pengutronix.de
Mon Oct 14 11:22:26 EDT 2013
Hi,
On Mon, Oct 14, 2013 at 08:54:09AM -0500, Bin Liu wrote:
> On Mon, Oct 14, 2013 at 8:35 AM, Markus Pargmann <mpa at pengutronix.de> wrote:
> > The USB Controller does not support ID pin change interrupts. So we have
> > to use a polling function to detect changes of A/B device state
> > (otg_timer). This poll function has to check in several states if a
> > other device type might be connected to the USB port. This check is
> > triggered by manually starting/stopping a USB Session.
>
> I think this is an arguable approach. Toggling the SESSION in
> otg_timer() causes voltage pulses on VBUS, which will not pass the USB
> certification.
This is only done when no device is connected, so I am not sure if it is
important. Unfortunately we do not see the A/B state changes until
toggling the SESSION. Is there another way to check this?
>
> I have not seen any products required the dynamic dual role switching
> yet. It always fixed in either device mode or host mode.
OTG is explicitly listed in the devicetree bindings documentation, so
I think the driver should be able to detect different roles.
Regards,
Markus Pargmann
>
> Regards,
> -Bin.
>
> >
> > So in A mode, we cancel the currently running session which also
> > disables the possibility to detect new devices via interrupt. In B mode,
> > we start a session to check for ID-Pin and possibly connected devices.
> >
> > Whenever a real USB session ends, we have to trigger the otg_timer poll
> > function again.
> >
> > Signed-off-by: Markus Pargmann <mpa at pengutronix.de>
> > ---
> > drivers/usb/musb/musb_dsps.c | 84 ++++++++++++++++++++++++++++++++++++++++----
> > 1 file changed, 78 insertions(+), 6 deletions(-)
> >
> > diff --git a/drivers/usb/musb/musb_dsps.c b/drivers/usb/musb/musb_dsps.c
> > index b24b697..0245e8d 100644
> > --- a/drivers/usb/musb/musb_dsps.c
> > +++ b/drivers/usb/musb/musb_dsps.c
> > @@ -178,6 +178,43 @@ static const struct file_operations musb_regdump_fops = {
> >
> > #endif /* IS_ENABLED(CONFIG_DEBUG_FS) */
> >
> > +/*
> > + * Compare driver and hardware mode and update driver state if necessary.
> > + * Not all hardware changes actually reach the driver through interrupts.
> > + */
> > +static void dsps_update_mode(struct musb *musb)
> > +{
> > + u8 devctl;
> > +
> > + devctl = dsps_readb(musb->mregs, MUSB_DEVCTL);
> > +
> > + switch (musb->xceiv->state) {
> > + case OTG_STATE_A_IDLE:
> > + if (devctl & MUSB_DEVCTL_BDEVICE) {
> > + dev_dbg(musb->controller, "detected controller state B, software state A\n");
> > + musb->xceiv->state = OTG_STATE_B_IDLE;
> > + }
> > + break;
> > + case OTG_STATE_B_IDLE:
> > + if (!(devctl & MUSB_DEVCTL_BDEVICE)) {
> > + dev_dbg(musb->controller, "detected controller state A, software state B\n");
> > + musb->xceiv->state = OTG_STATE_A_IDLE;
> > + }
> > + break;
> > + default:
> > + if (!(devctl & MUSB_DEVCTL_SESSION)) {
> > + dev_dbg(musb->controller, "detected controller out of session (%x), software state %s\n",
> > + devctl,
> > + usb_otg_state_string(musb->xceiv->state));
> > + if (devctl & MUSB_DEVCTL_BDEVICE)
> > + musb->xceiv->state = OTG_STATE_B_IDLE;
> > + else
> > + musb->xceiv->state = OTG_STATE_A_IDLE;
> > + }
> > + break;
> > + }
> > +}
> > +
> > /**
> > * dsps_musb_enable - enable interrupts
> > */
> > @@ -229,6 +266,8 @@ static void otg_timer(unsigned long _musb)
> > u8 devctl;
> > unsigned long flags;
> >
> > + dsps_update_mode(musb);
> > +
> > /*
> > * We poll because DSPS IP's won't expose several OTG-critical
> > * status change events (from the transceiver) otherwise.
> > @@ -239,6 +278,16 @@ static void otg_timer(unsigned long _musb)
> >
> > spin_lock_irqsave(&musb->lock, flags);
> > switch (musb->xceiv->state) {
> > + case OTG_STATE_A_IDLE:
> > + case OTG_STATE_A_WAIT_VRISE:
> > + /*
> > + * Poll the devctl register to know when the controller switches
> > + * back to B state.
> > + */
> > + musb_writeb(mregs, MUSB_DEVCTL,
> > + devctl & (~MUSB_DEVCTL_SESSION));
> > + mod_timer(&glue->timer, jiffies + wrp->poll_seconds * HZ);
> > + break;
> > case OTG_STATE_A_WAIT_BCON:
> > devctl &= ~MUSB_DEVCTL_SESSION;
> > dsps_writeb(musb->mregs, MUSB_DEVCTL, devctl);
> > @@ -251,6 +300,8 @@ static void otg_timer(unsigned long _musb)
> > musb->xceiv->state = OTG_STATE_A_IDLE;
> > MUSB_HST_MODE(musb);
> > }
> > + mod_timer(&glue->timer,
> > + jiffies + wrp->poll_seconds * HZ);
> > break;
> > case OTG_STATE_A_WAIT_VFALL:
> > musb->xceiv->state = OTG_STATE_A_WAIT_VRISE;
> > @@ -258,12 +309,24 @@ static void otg_timer(unsigned long _musb)
> > MUSB_INTR_VBUSERROR << wrp->usb_shift);
> > break;
> > case OTG_STATE_B_IDLE:
> > + /*
> > + * There's no ID-changed IRQ, so we have no good way to tell
> > + * when to switch to the A-Default state machine (by setting
> > + * the DEVCTL.Session bit).
> > + *
> > + * Workaround: whenever we're in B_IDLE, try setting the
> > + * session flag every few seconds. If it works, ID was
> > + * grounded and we're now in the A-Default state machine.
> > + *
> > + * NOTE: setting the session flag is _supposed_ to trigger
> > + * SRP but clearly it doesn't.
> > + */
> > + musb_writeb(mregs, MUSB_DEVCTL, devctl | MUSB_DEVCTL_SESSION);
> > devctl = dsps_readb(mregs, MUSB_DEVCTL);
> > - if (devctl & MUSB_DEVCTL_BDEVICE)
> > - mod_timer(&glue->timer,
> > - jiffies + wrp->poll_seconds * HZ);
> > - else
> > + if (!(devctl & MUSB_DEVCTL_BDEVICE))
> > musb->xceiv->state = OTG_STATE_A_IDLE;
> > + mod_timer(&glue->timer,
> > + jiffies + wrp->poll_seconds * HZ);
> > break;
> > default:
> > break;
> > @@ -376,7 +439,6 @@ static irqreturn_t dsps_interrupt(int irq, void *hci)
> > MUSB_HST_MODE(musb);
> > musb->xceiv->otg->default_a = 1;
> > musb->xceiv->state = OTG_STATE_A_WAIT_VRISE;
> > - del_timer(&glue->timer);
> > } else {
> > musb->is_active = 0;
> > MUSB_DEV_MODE(musb);
> > @@ -397,8 +459,16 @@ static irqreturn_t dsps_interrupt(int irq, void *hci)
> > ret |= musb_interrupt(musb);
> >
> > /* Poll for ID change */
> > - if (musb->xceiv->state == OTG_STATE_B_IDLE)
> > + switch (musb->xceiv->state) {
> > + case OTG_STATE_A_IDLE:
> > + case OTG_STATE_A_WAIT_BCON:
> > + case OTG_STATE_A_WAIT_VRISE:
> > + case OTG_STATE_B_IDLE:
> > mod_timer(&glue->timer, jiffies + wrp->poll_seconds * HZ);
> > + break;
> > + default:
> > + break;
> > + }
> > out:
> > spin_unlock_irqrestore(&musb->lock, flags);
> >
> > @@ -479,6 +549,8 @@ static int dsps_musb_init(struct musb *musb)
> >
> > dev_info(dev, "%s:%d %s: OK\n", __FILE__, __LINE__, __func__);
> >
> > + musb->xceiv->otg->default_a = 0;
> > +
> > return 0;
> > }
> >
> > --
> > 1.8.4.rc3
> >
> > --
> > To unsubscribe from this list: send the line "unsubscribe linux-usb" in
> > the body of a message to majordomo at vger.kernel.org
> > More majordomo info at http://vger.kernel.org/majordomo-info.html
>
--
Pengutronix e.K. | |
Industrial Linux Solutions | http://www.pengutronix.de/ |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
More information about the linux-arm-kernel
mailing list