aboutsummaryrefslogtreecommitdiff
path: root/drivers/usb/gadget
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/gadget')
-rw-r--r--drivers/usb/gadget/dummy_hcd.c254
1 files changed, 162 insertions, 92 deletions
diff --git a/drivers/usb/gadget/dummy_hcd.c b/drivers/usb/gadget/dummy_hcd.c
index dc0e3233b0e..2d6d2295132 100644
--- a/drivers/usb/gadget/dummy_hcd.c
+++ b/drivers/usb/gadget/dummy_hcd.c
@@ -163,12 +163,16 @@ struct dummy {
struct dummy_request fifo_req;
u8 fifo_buf [FIFO_SIZE];
u16 devstatus;
+ unsigned pullup:1;
+ unsigned active:1;
+ unsigned old_active:1;
/*
* MASTER/HOST side support
*/
struct timer_list timer;
u32 port_status;
+ u32 old_status;
unsigned resuming:1;
unsigned long re_timeout;
@@ -215,6 +219,98 @@ static struct dummy *the_controller;
/*-------------------------------------------------------------------------*/
+/* SLAVE/GADGET SIDE UTILITY ROUTINES */
+
+/* called with spinlock held */
+static void nuke (struct dummy *dum, struct dummy_ep *ep)
+{
+ while (!list_empty (&ep->queue)) {
+ struct dummy_request *req;
+
+ req = list_entry (ep->queue.next, struct dummy_request, queue);
+ list_del_init (&req->queue);
+ req->req.status = -ESHUTDOWN;
+
+ spin_unlock (&dum->lock);
+ req->req.complete (&ep->ep, &req->req);
+ spin_lock (&dum->lock);
+ }
+}
+
+/* caller must hold lock */
+static void
+stop_activity (struct dummy *dum)
+{
+ struct dummy_ep *ep;
+
+ /* prevent any more requests */
+ dum->address = 0;
+
+ /* The timer is left running so that outstanding URBs can fail */
+
+ /* nuke any pending requests first, so driver i/o is quiesced */
+ list_for_each_entry (ep, &dum->gadget.ep_list, ep.ep_list)
+ nuke (dum, ep);
+
+ /* driver now does any non-usb quiescing necessary */
+}
+
+/* caller must hold lock */
+static void
+set_link_state (struct dummy *dum)
+{
+ dum->active = 0;
+ if ((dum->port_status & USB_PORT_STAT_POWER) == 0)
+ dum->port_status = 0;
+ else if (!dum->pullup) {
+ dum->port_status &= ~(USB_PORT_STAT_CONNECTION |
+ USB_PORT_STAT_ENABLE |
+ USB_PORT_STAT_LOW_SPEED |
+ USB_PORT_STAT_HIGH_SPEED |
+ USB_PORT_STAT_SUSPEND);
+ if ((dum->old_status & USB_PORT_STAT_CONNECTION) != 0)
+ dum->port_status |= (USB_PORT_STAT_C_CONNECTION << 16);
+ } else {
+ dum->port_status |= USB_PORT_STAT_CONNECTION;
+ if ((dum->old_status & USB_PORT_STAT_CONNECTION) == 0)
+ dum->port_status |= (USB_PORT_STAT_C_CONNECTION << 16);
+ if ((dum->port_status & USB_PORT_STAT_ENABLE) == 0)
+ dum->port_status &= ~USB_PORT_STAT_SUSPEND;
+ else if ((dum->port_status & USB_PORT_STAT_SUSPEND) == 0)
+ dum->active = 1;
+ }
+
+ if ((dum->port_status & USB_PORT_STAT_ENABLE) == 0 || dum->active)
+ dum->resuming = 0;
+
+ if ((dum->port_status & USB_PORT_STAT_CONNECTION) == 0 ||
+ (dum->port_status & USB_PORT_STAT_RESET) != 0) {
+ if ((dum->old_status & USB_PORT_STAT_CONNECTION) != 0 &&
+ (dum->old_status & USB_PORT_STAT_RESET) == 0 &&
+ dum->driver) {
+ stop_activity (dum);
+ spin_unlock (&dum->lock);
+ dum->driver->disconnect (&dum->gadget);
+ spin_lock (&dum->lock);
+ }
+ } else if (dum->active != dum->old_active) {
+ if (dum->old_active && dum->driver->suspend) {
+ spin_unlock (&dum->lock);
+ dum->driver->suspend (&dum->gadget);
+ spin_lock (&dum->lock);
+ } else if (!dum->old_active && dum->driver->resume) {
+ spin_unlock (&dum->lock);
+ dum->driver->resume (&dum->gadget);
+ spin_lock (&dum->lock);
+ }
+ }
+
+ dum->old_status = dum->port_status;
+ dum->old_active = dum->active;
+}
+
+/*-------------------------------------------------------------------------*/
+
/* SLAVE/GADGET SIDE DRIVER
*
* This only tracks gadget state. All the work is done when the host
@@ -339,22 +435,6 @@ done:
return retval;
}
-/* called with spinlock held */
-static void nuke (struct dummy *dum, struct dummy_ep *ep)
-{
- while (!list_empty (&ep->queue)) {
- struct dummy_request *req;
-
- req = list_entry (ep->queue.next, struct dummy_request, queue);
- list_del_init (&req->queue);
- req->req.status = -ESHUTDOWN;
-
- spin_unlock (&dum->lock);
- req->req.complete (&ep->ep, &req->req);
- spin_lock (&dum->lock);
- }
-}
-
static int dummy_disable (struct usb_ep *_ep)
{
struct dummy_ep *ep;
@@ -603,7 +683,7 @@ static int dummy_wakeup (struct usb_gadget *_gadget)
/* hub notices our request, issues downstream resume, etc */
dum->resuming = 1;
- dum->port_status |= (USB_PORT_STAT_C_SUSPEND << 16);
+ dum->re_timeout = jiffies + msecs_to_jiffies(20);
return 0;
}
@@ -619,10 +699,24 @@ static int dummy_set_selfpowered (struct usb_gadget *_gadget, int value)
return 0;
}
+static int dummy_pullup (struct usb_gadget *_gadget, int value)
+{
+ struct dummy *dum;
+ unsigned long flags;
+
+ dum = gadget_to_dummy (_gadget);
+ spin_lock_irqsave (&dum->lock, flags);
+ dum->pullup = (value != 0);
+ set_link_state (dum);
+ spin_unlock_irqrestore (&dum->lock, flags);
+ return 0;
+}
+
static const struct usb_gadget_ops dummy_ops = {
.get_frame = dummy_g_get_frame,
.wakeup = dummy_wakeup,
.set_selfpowered = dummy_set_selfpowered,
+ .pullup = dummy_pullup,
};
/*-------------------------------------------------------------------------*/
@@ -675,7 +769,6 @@ usb_gadget_register_driver (struct usb_gadget_driver *driver)
*/
dum->devstatus = 0;
- dum->resuming = 0;
INIT_LIST_HEAD (&dum->gadget.ep_list);
for (i = 0; i < DUMMY_ENDPOINTS; i++) {
@@ -714,35 +807,14 @@ usb_gadget_register_driver (struct usb_gadget_driver *driver)
device_bind_driver (&dum->gadget.dev);
/* khubd will enumerate this in a while */
- dum->port_status |= USB_PORT_STAT_CONNECTION
- | (USB_PORT_STAT_C_CONNECTION << 16);
+ spin_lock_irq (&dum->lock);
+ dum->pullup = 1;
+ set_link_state (dum);
+ spin_unlock_irq (&dum->lock);
return 0;
}
EXPORT_SYMBOL (usb_gadget_register_driver);
-/* caller must hold lock */
-static void
-stop_activity (struct dummy *dum, struct usb_gadget_driver *driver)
-{
- struct dummy_ep *ep;
-
- /* prevent any more requests */
- dum->address = 0;
-
- /* The timer is left running so that outstanding URBs can fail */
-
- /* nuke any pending requests first, so driver i/o is quiesced */
- list_for_each_entry (ep, &dum->gadget.ep_list, ep.ep_list)
- nuke (dum, ep);
-
- /* driver now does any non-usb quiescing necessary */
- if (driver) {
- spin_unlock (&dum->lock);
- driver->disconnect (&dum->gadget);
- spin_lock (&dum->lock);
- }
-}
-
int
usb_gadget_unregister_driver (struct usb_gadget_driver *driver)
{
@@ -758,10 +830,8 @@ usb_gadget_unregister_driver (struct usb_gadget_driver *driver)
driver->driver.name);
spin_lock_irqsave (&dum->lock, flags);
- stop_activity (dum, driver);
- dum->port_status &= ~(USB_PORT_STAT_CONNECTION | USB_PORT_STAT_ENABLE |
- USB_PORT_STAT_LOW_SPEED | USB_PORT_STAT_HIGH_SPEED);
- dum->port_status |= (USB_PORT_STAT_C_CONNECTION << 16);
+ dum->pullup = 0;
+ set_link_state (dum);
spin_unlock_irqrestore (&dum->lock, flags);
driver->unbind (&dum->gadget);
@@ -770,6 +840,11 @@ usb_gadget_unregister_driver (struct usb_gadget_driver *driver)
device_release_driver (&dum->gadget.dev);
driver_unregister (&driver->driver);
+ spin_lock_irqsave (&dum->lock, flags);
+ dum->pullup = 0;
+ set_link_state (dum);
+ spin_unlock_irqrestore (&dum->lock, flags);
+
return 0;
}
EXPORT_SYMBOL (usb_gadget_unregister_driver);
@@ -1432,6 +1507,13 @@ static int dummy_hub_status (struct usb_hcd *hcd, char *buf)
dum = hcd_to_dummy (hcd);
spin_lock_irqsave (&dum->lock, flags);
+
+ if (dum->resuming && time_after_eq (jiffies, dum->re_timeout)) {
+ dum->port_status |= (USB_PORT_STAT_C_SUSPEND << 16);
+ dum->port_status &= ~USB_PORT_STAT_SUSPEND;
+ set_link_state (dum);
+ }
+
if (!(dum->port_status & PORT_C_MASK))
retval = 0;
else {
@@ -1480,16 +1562,16 @@ static int dummy_hub_control (
/* 20msec resume signaling */
dum->resuming = 1;
dum->re_timeout = jiffies +
- msecs_to_jiffies(20);
+ msecs_to_jiffies(20);
}
break;
case USB_PORT_FEAT_POWER:
- dum->port_status = 0;
- dum->resuming = 0;
- stop_activity(dum, dum->driver);
- break;
+ if (dum->port_status & USB_PORT_STAT_POWER)
+ dev_dbg (dummy_dev(dum), "power-off\n");
+ /* FALLS THROUGH */
default:
dum->port_status &= ~(1 << wValue);
+ set_link_state (dum);
}
break;
case GetHubDescriptor:
@@ -1505,23 +1587,16 @@ static int dummy_hub_control (
/* whoever resets or resumes must GetPortStatus to
* complete it!!
*/
- if (dum->resuming && time_after (jiffies, dum->re_timeout)) {
+ if (dum->resuming &&
+ time_after_eq (jiffies, dum->re_timeout)) {
dum->port_status |= (USB_PORT_STAT_C_SUSPEND << 16);
dum->port_status &= ~USB_PORT_STAT_SUSPEND;
- dum->resuming = 0;
- dum->re_timeout = 0;
- if (dum->driver && dum->driver->resume) {
- spin_unlock (&dum->lock);
- dum->driver->resume (&dum->gadget);
- spin_lock (&dum->lock);
- }
}
- if ((dum->port_status & USB_PORT_STAT_RESET) != 0
- && time_after (jiffies, dum->re_timeout)) {
+ if ((dum->port_status & USB_PORT_STAT_RESET) != 0 &&
+ time_after_eq (jiffies, dum->re_timeout)) {
dum->port_status |= (USB_PORT_STAT_C_RESET << 16);
dum->port_status &= ~USB_PORT_STAT_RESET;
- dum->re_timeout = 0;
- if (dum->driver) {
+ if (dum->pullup) {
dum->port_status |= USB_PORT_STAT_ENABLE;
/* give it the best speed we agree on */
dum->gadget.speed = dum->driver->speed;
@@ -1542,6 +1617,7 @@ static int dummy_hub_control (
}
}
}
+ set_link_state (dum);
((u16 *) buf)[0] = cpu_to_le16 (dum->port_status);
((u16 *) buf)[1] = cpu_to_le16 (dum->port_status >> 16);
break;
@@ -1551,42 +1627,36 @@ static int dummy_hub_control (
case SetPortFeature:
switch (wValue) {
case USB_PORT_FEAT_SUSPEND:
- if ((dum->port_status & USB_PORT_STAT_SUSPEND)
- == 0) {
+ if (dum->active) {
dum->port_status |= USB_PORT_STAT_SUSPEND;
- if (dum->driver && dum->driver->suspend) {
- spin_unlock (&dum->lock);
- dum->driver->suspend (&dum->gadget);
- spin_lock (&dum->lock);
- /* HNP would happen here; for now we
- * assume b_bus_req is always true.
- */
- if (((1 << USB_DEVICE_B_HNP_ENABLE)
- & dum->devstatus) != 0)
- dev_dbg (dummy_dev(dum),
+
+ /* HNP would happen here; for now we
+ * assume b_bus_req is always true.
+ */
+ set_link_state (dum);
+ if (((1 << USB_DEVICE_B_HNP_ENABLE)
+ & dum->devstatus) != 0)
+ dev_dbg (dummy_dev(dum),
"no HNP yet!\n");
- }
}
break;
+ case USB_PORT_FEAT_POWER:
+ dum->port_status |= USB_PORT_STAT_POWER;
+ set_link_state (dum);
+ break;
case USB_PORT_FEAT_RESET:
- /* if it's already running, disconnect first */
- if (dum->port_status & USB_PORT_STAT_ENABLE) {
- dum->port_status &= ~(USB_PORT_STAT_ENABLE
- | USB_PORT_STAT_LOW_SPEED
- | USB_PORT_STAT_HIGH_SPEED);
- if (dum->driver) {
- dev_dbg (udc_dev(dum),
- "disconnect\n");
- stop_activity (dum, dum->driver);
- }
-
- /* FIXME test that code path! */
- }
+ /* if it's already enabled, disable */
+ dum->port_status &= ~(USB_PORT_STAT_ENABLE
+ | USB_PORT_STAT_LOW_SPEED
+ | USB_PORT_STAT_HIGH_SPEED);
/* 50msec reset signaling */
dum->re_timeout = jiffies + msecs_to_jiffies(50);
- /* FALLTHROUGH */
+ /* FALLS THROUGH */
default:
- dum->port_status |= (1 << wValue);
+ if ((dum->port_status & USB_PORT_STAT_POWER) != 0) {
+ dum->port_status |= (1 << wValue);
+ set_link_state (dum);
+ }
}
break;