@@ -1857,6 +1857,9 @@ static void musb_free(struct musb *musb)
}
#ifdef CONFIG_USB_MUSB_HDRC_HCD
+ if (musb->gb_queue)
+ destroy_workqueue(musb->gb_queue);
+ free_queue(musb);
usb_put_hcd(musb_to_hcd(musb));
#else
kfree(musb);
@@ -1915,6 +1918,9 @@ bad_config:
return -ENOMEM;
spin_lock_init(&musb->lock);
+#ifdef CONFIG_USB_MUSB_HDRC_HCD
+ spin_lock_init(&musb->qlock);
+#endif
musb->board_mode = plat->mode;
musb->board_set_power = plat->set_power;
musb->set_clock = plat->set_clock;
@@ -2078,8 +2084,24 @@ bad_config:
? "DMA" : "PIO",
musb->nIrq);
+#ifdef CONFIG_USB_MUSB_HDRC_HCD
+ musb->gb_queue = create_singlethread_workqueue(dev_name(dev));
+ if (musb->gb_queue == NULL)
+ goto fail2;
+ /* Init giveback workqueue */
+ INIT_WORK(&musb->gb_work, musb_gb_work);
+
+ /* init queue */
+ init_queue(musb);
+ if (!musb->qhead)
+ goto fail3;
+#endif
return 0;
+#ifdef CONFIG_USB_MUSB_HDRC_HCD
+fail3:
+ destroy_workqueue(musb->gb_queue);
+#endif
fail2:
musb_platform_exit(musb);
fail:
@@ -322,6 +322,16 @@ static inline struct usb_request *next_out_request(struct musb_hw_ep *hw_ep)
#endif
}
+#ifdef CONFIG_USB_MUSB_HDRC_HCD
+/*
+ * struct queue - Queue data structure
+ */
+struct queue {
+ struct urb *urb;
+ struct queue *next;
+};
+#endif
+
/*
* struct musb - Driver instance data.
*/
@@ -355,6 +365,11 @@ struct musb {
struct list_head in_bulk; /* of musb_qh */
struct list_head out_bulk; /* of musb_qh */
+ struct workqueue_struct *gb_queue;
+ struct work_struct gb_work;
+ spinlock_t qlock;
+ struct queue *qhead;
+
struct timer_list otg_timer;
#endif
@@ -610,5 +625,8 @@ extern int musb_platform_get_vbus_status(struct musb *musb);
extern int __init musb_platform_init(struct musb *musb);
extern int musb_platform_exit(struct musb *musb);
+#ifdef CONFIG_USB_MUSB_HDRC_HCD
+extern void musb_gb_work(struct work_struct *data);
+#endif
#endif /* __MUSB_CORE_H__ */
@@ -99,7 +99,56 @@
static void musb_ep_program(struct musb *musb, u8 epnum,
struct urb *urb, int is_out,
u8 *buf, u32 offset, u32 len);
+struct queue *create(void)
+{
+ struct queue *new;
+ new = kmalloc(sizeof(struct queue), GFP_ATOMIC);
+ if (!new)
+ return NULL;
+ new->next = NULL;
+ return new;
+}
+void push_queue(struct musb *musb, struct urb *urb)
+{
+ struct queue *new, *temp;
+
+ new = create();
+ new->urb = urb;
+
+ temp = musb->qhead;
+
+ spin_lock(&musb->qlock);
+ while (temp->next != NULL)
+ temp = temp->next;
+ temp->next = new;
+ spin_unlock(&musb->qlock);
+}
+
+struct urb *pop_queue(struct musb *musb)
+{
+ struct urb *urb;
+ struct queue *head = musb->qhead;
+ struct queue *temp;
+ unsigned long flags;
+
+ spin_lock_irqsave(&musb->qlock, flags);
+ temp = head->next;
+ if (!temp) {
+ spin_unlock_irqrestore(&musb->qlock, flags);
+ return NULL;
+ }
+ head->next = head->next->next;
+ spin_unlock_irqrestore(&musb->qlock, flags);
+
+ urb = temp->urb;
+ kfree(temp);
+ return urb;
+}
+void init_queue(struct musb *musb)
+{
+ musb->qhead = create();
+}
/*
* Clear TX fifo. Needed to avoid BABBLE errors.
*/
@@ -297,8 +346,6 @@ start:
/* Context: caller owns controller lock, IRQs are blocked */
static void musb_giveback(struct musb *musb, struct urb *urb, int status)
-__releases(musb->lock)
-__acquires(musb->lock)
{
DBG(({ int level; switch (status) {
case 0:
@@ -323,10 +370,20 @@ __acquires(musb->lock)
urb->actual_length, urb->transfer_buffer_length
);
- usb_hcd_unlink_urb_from_ep(musb_to_hcd(musb), urb);
- spin_unlock(&musb->lock);
usb_hcd_giveback_urb(musb_to_hcd(musb), urb, status);
- spin_lock(&musb->lock);
+}
+
+void free_queue(struct musb *musb)
+{
+ struct urb *urb;
+
+ /* giveback any urb still in queue */
+ while ((urb = pop_queue(musb)) != 0)
+ musb_giveback(musb, urb, 0);
+
+ /* free up the queue head memory */
+ kfree(musb->qhead);
+ musb->qhead = NULL;
}
/* For bulk/interrupt endpoints only */
@@ -348,6 +405,15 @@ static inline void musb_save_toggle(struct musb_qh *qh, int is_in,
usb_settoggle(urb->dev, qh->epnum, !is_in, csr ? 1 : 0);
}
+/* Used to complete urb giveback */
+void musb_gb_work(struct work_struct *data)
+{
+ struct musb *musb = container_of(data, struct musb, gb_work);
+ struct urb *urb;
+
+ while ((urb = pop_queue(musb)) != 0)
+ musb_giveback(musb, urb, 0);
+}
/*
* Advance this hardware endpoint's queue, completing the specified URB and
@@ -378,10 +444,16 @@ static void musb_advance_schedule(struct musb *musb, struct urb *urb,
break;
}
- qh->is_ready = 0;
- musb_giveback(musb, urb, status);
- qh->is_ready = ready;
+ usb_hcd_unlink_urb_from_ep(musb_to_hcd(musb), urb);
+ /* If URB completed with error then giveback first */
+ if (status != 0) {
+ qh->is_ready = 0;
+ spin_unlock(&musb->lock);
+ musb_giveback(musb, urb, status);
+ spin_lock(&musb->lock);
+ qh->is_ready = ready;
+ }
/* reclaim resources (and bandwidth) ASAP; deschedule it, and
* invalidate qh as soon as list_empty(&hep->urb_list)
*/
@@ -429,6 +501,12 @@ static void musb_advance_schedule(struct musb *musb, struct urb *urb,
hw_ep->epnum, is_in ? 'R' : 'T', next_urb(qh));
musb_start_urb(musb, is_in, qh);
}
+
+ /* if URB is successfully completed then giveback in workqueue */
+ if (status == 0) {
+ push_queue(musb, urb);
+ queue_work(musb->gb_queue, &musb->gb_work);
+ }
}
static u16 musb_h_flush_rxfifo(struct musb_hw_ep *hw_ep, u16 csr)
@@ -2167,8 +2245,12 @@ static int musb_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
|| musb_ep_get_qh(qh->hw_ep, is_in) != qh) {
int ready = qh->is_ready;
+ usb_hcd_unlink_urb_from_ep(musb_to_hcd(musb), urb);
+
qh->is_ready = 0;
+ spin_unlock(&musb->lock);
musb_giveback(musb, urb, 0);
+ spin_lock(&musb->lock);
qh->is_ready = ready;
/* If nothing else (usually musb_giveback) is using it
@@ -2229,8 +2311,13 @@ musb_h_disable(struct usb_hcd *hcd, struct usb_host_endpoint *hep)
* other transfers, and since !qh->is_ready nothing
* will activate any of these as it advances.
*/
- while (!list_empty(&hep->urb_list))
- musb_giveback(musb, next_urb(qh), -ESHUTDOWN);
+ while (!list_empty(&hep->urb_list)) {
+ urb = next_urb(qh);
+ usb_hcd_unlink_urb_from_ep(musb_to_hcd(musb), urb);
+ spin_unlock(&musb->lock);
+ musb_giveback(musb, urb, -ESHUTDOWN);
+ spin_lock(&musb->lock);
+ }
hep->hcpriv = NULL;
list_del(&qh->ring);
@@ -83,6 +83,8 @@ static inline struct musb_qh *first_qh(struct list_head *q)
extern void musb_root_disconnect(struct musb *musb);
+extern void init_queue(struct musb *musb);
+extern void free_queue(struct musb *musb);
struct usb_hcd;