Hello all,
What's SRP or Session Request Protocol? What is this for? How does this happen? How to implement it? How can I see it working?
These are the questions we're trying to answer in this post. Our first piece of code will come in this post. Like I said before, all codes are based on Linux OMAP git tree and MUSB driver.
Session Request Protocol
OTG devices are mostly battery powered and as a limited resource we should save as much as we can. Keeping alive a USB connection can be really power dissipative as we must source 5V vbus whenever a device is attached to USB port. That's why OTG specs allows us to shutdown Vbus if the device is not using it. Of course this will kill USB connection (in this case known as OTG Session), so how to get a connection again without needing to do unplug-replug cycle with USB cable to generate a Connect Interrupt?
That's why SRP was created. It provides means for the device to ask the Host to initiate a session. And this is not OTG specific; any USB Host Controller should answer to SRP correctly, but most common USB Host Controllers don't ever shutdown Vbus. Such controllers will only ack the SRP and do nothing.
How does SRP really happen?
There are two ways of doing SRP: Data Line Pulsing and Vbus Pulsing. What's the difference? Most of the readers already know USB has 4 pins (5 in mini/micro connectors, we'll see why later) - Vbus, Data+, Data-, GND. Data line Pulsing will send a pulse in Data+ and Data- lines and Vbus pulsing, guess what, will send a pulse in Vbus.
There are timings to respect/follow. Below there's image for better understanding (b-device SRP).
According to OTG Specs, we should first issue Data Line Pulsing and after that Vbus Pulsing.
How to Implement it?
Most of OTG Controllers will provide you a correct bit in some register for handling Session Request Protocol, so it's not really complicated. Let's see how we did it in MUSB driver:
static int musb_gadget_wakeup(struct usb_gadget *gadget) { struct musb *musb = gadget_to_musb(gadget); void __iomem *mregs = musb->mregs; unsigned long flags; int status = -EINVAL; u8 power, devctl; int retries; spin_lock_irqsave(&musb->lock, flags); switch (musb->xceiv.state) { case OTG_STATE_B_PERIPHERAL: /* NOTE: OTG state machine doesn't include B_SUSPENDED; * that's part of the standard usb 1.1 state machine, and * doesn't affect OTG transitions. */ if (musb->may_wakeup && musb->is_suspended) break; goto done; case OTG_STATE_B_IDLE: /* Start SRP ... OTG not required. */ devctl = musb_readb(mregs, MUSB_DEVCTL); DBG(2, "Sending SRP: devctl: %02x\n", devctl); devctl |= MUSB_DEVCTL_SESSION; musb_writeb(mregs, MUSB_DEVCTL, devctl); devctl = musb_readb(mregs, MUSB_DEVCTL); retries = 100; while (!(devctl & MUSB_DEVCTL_SESSION)) { devctl = musb_readb(mregs, MUSB_DEVCTL); if (retries-- < 1) break; } retries = 10000; while (devctl & MUSB_DEVCTL_SESSION) { devctl = musb_readb(mregs, MUSB_DEVCTL); if (retries-- < 1) break; } /* Block idling for at least 1s */ musb_platform_try_idle(musb, jiffies + msecs_to_jiffies(1 * HZ)); status = 0; goto done; default: DBG(2, "Unhandled wake: %s\n", otg_state_string(musb)); goto done; } status = 0; power = musb_readb(mregs, MUSB_POWER); power |= MUSB_POWER_RESUME; musb_writeb(mregs, MUSB_POWER, power); DBG(2, "issue wakeup\n"); /* FIXME do this next chunk in a timer callback, no udelay */ mdelay(2); power = musb_readb(mregs, MUSB_POWER); power &= ~MUSB_POWER_RESUME; musb_writeb(mregs, MUSB_POWER, power); done: spin_unlock_irqrestore(&musb->lock, flags); return status; }
This was took from drivers/usb/musb/musb_gadget.c. What we should really care about is that:
case OTG_STATE_B_IDLE: /* Start SRP ... OTG not required. */ devctl = musb_readb(mregs, MUSB_DEVCTL); DBG(2, "Sending SRP: devctl: %02x\n", devctl); devctl |= MUSB_DEVCTL_SESSION; musb_writeb(mregs, MUSB_DEVCTL, devctl); devctl = musb_readb(mregs, MUSB_DEVCTL); retries = 100; while (!(devctl & MUSB_DEVCTL_SESSION)) { devctl = musb_readb(mregs, MUSB_DEVCTL); if (retries-- < 1) break; } retries = 10000; while (devctl & MUSB_DEVCTL_SESSION) { devctl = musb_readb(mregs, MUSB_DEVCTL); if (retries-- < 1) break; }
The most important part is just two lines of code:
devctl |= MUSB_DEVCTL_SESSION; musb_writeb(mregs, MUSB_DEVCTL, devctl);
The first line enables the session bit in DevCtl register and the second one writes the new configuration to the register itself. After B-device writes the session bit in the devctl register, MUSB will send Data Line Pulsing itself. The rest of the code is just to guarantee SRP will happen correctly.
SRP is not that difficult to handle cuz if Vbus is already on it won't do nothing nor break anything.
That's all for now. See y'all
