aboutsummaryrefslogtreecommitdiff
path: root/drivers/usb/musb/musb_gadget_ep0.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/musb/musb_gadget_ep0.c')
-rw-r--r--drivers/usb/musb/musb_gadget_ep0.c232
1 files changed, 166 insertions, 66 deletions
diff --git a/drivers/usb/musb/musb_gadget_ep0.c b/drivers/usb/musb/musb_gadget_ep0.c
index 3f5e30ddfa2..2af45a0c893 100644
--- a/drivers/usb/musb/musb_gadget_ep0.c
+++ b/drivers/usb/musb/musb_gadget_ep0.c
@@ -4,6 +4,7 @@
* Copyright 2005 Mentor Graphics Corporation
* Copyright (C) 2005-2006 by Texas Instruments
* Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2008-2009 MontaVista Software, Inc. <source@mvista.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -36,7 +37,6 @@
#include <linux/list.h>
#include <linux/timer.h>
#include <linux/spinlock.h>
-#include <linux/init.h>
#include <linux/device.h>
#include <linux/interrupt.h>
@@ -58,7 +58,8 @@
static char *decode_ep0stage(u8 stage)
{
switch (stage) {
- case MUSB_EP0_STAGE_SETUP: return "idle";
+ case MUSB_EP0_STAGE_IDLE: return "idle";
+ case MUSB_EP0_STAGE_SETUP: return "setup";
case MUSB_EP0_STAGE_TX: return "in";
case MUSB_EP0_STAGE_RX: return "out";
case MUSB_EP0_STAGE_ACKWAIT: return "wait";
@@ -86,7 +87,6 @@ static int service_tx_status_request(
case USB_RECIP_DEVICE:
result[0] = musb->is_self_powered << USB_DEVICE_SELF_POWERED;
result[0] |= musb->may_wakeup << USB_DEVICE_REMOTE_WAKEUP;
-#ifdef CONFIG_USB_MUSB_OTG
if (musb->g.is_otg) {
result[0] |= musb->g.b_hnp_enable
<< USB_DEVICE_B_HNP_ENABLE;
@@ -95,7 +95,6 @@ static int service_tx_status_request(
result[0] |= musb->g.a_hnp_support
<< USB_DEVICE_A_HNP_SUPPORT;
}
-#endif
break;
case USB_RECIP_INTERFACE:
@@ -197,7 +196,6 @@ service_in_request(struct musb *musb, const struct usb_ctrlrequest *ctrlrequest)
static void musb_g_ep0_giveback(struct musb *musb, struct usb_request *req)
{
musb_g_giveback(&musb->endpoints[0].ep_in, req, 0);
- musb->ep0_state = MUSB_EP0_STAGE_SETUP;
}
/*
@@ -208,7 +206,7 @@ static inline void musb_try_b_hnp_enable(struct musb *musb)
void __iomem *mbase = musb->mregs;
u8 devctl;
- DBG(1, "HNP: Setting HR\n");
+ dev_dbg(musb->controller, "HNP: Setting HR\n");
devctl = musb_readb(mbase, MUSB_DEVCTL);
musb_writeb(mbase, MUSB_DEVCTL, devctl | MUSB_DEVCTL_HR);
}
@@ -256,30 +254,61 @@ __acquires(musb->lock)
case USB_RECIP_INTERFACE:
break;
case USB_RECIP_ENDPOINT:{
- const u8 num = ctrlrequest->wIndex & 0x0f;
- struct musb_ep *musb_ep;
+ const u8 epnum =
+ ctrlrequest->wIndex & 0x0f;
+ struct musb_ep *musb_ep;
+ struct musb_hw_ep *ep;
+ struct musb_request *request;
+ void __iomem *regs;
+ int is_in;
+ u16 csr;
- if (num == 0
- || num >= MUSB_C_NUM_EPS
- || ctrlrequest->wValue
- != USB_ENDPOINT_HALT)
+ if (epnum == 0 || epnum >= MUSB_C_NUM_EPS ||
+ ctrlrequest->wValue != USB_ENDPOINT_HALT)
break;
- if (ctrlrequest->wIndex & USB_DIR_IN)
- musb_ep = &musb->endpoints[num].ep_in;
+ ep = musb->endpoints + epnum;
+ regs = ep->regs;
+ is_in = ctrlrequest->wIndex & USB_DIR_IN;
+ if (is_in)
+ musb_ep = &ep->ep_in;
else
- musb_ep = &musb->endpoints[num].ep_out;
+ musb_ep = &ep->ep_out;
if (!musb_ep->desc)
break;
- /* REVISIT do it directly, no locking games */
- spin_unlock(&musb->lock);
- musb_gadget_set_halt(&musb_ep->end_point, 0);
- spin_lock(&musb->lock);
+ handled = 1;
+ /* Ignore request if endpoint is wedged */
+ if (musb_ep->wedged)
+ break;
+
+ musb_ep_select(mbase, epnum);
+ if (is_in) {
+ csr = musb_readw(regs, MUSB_TXCSR);
+ csr |= MUSB_TXCSR_CLRDATATOG |
+ MUSB_TXCSR_P_WZC_BITS;
+ csr &= ~(MUSB_TXCSR_P_SENDSTALL |
+ MUSB_TXCSR_P_SENTSTALL |
+ MUSB_TXCSR_TXPKTRDY);
+ musb_writew(regs, MUSB_TXCSR, csr);
+ } else {
+ csr = musb_readw(regs, MUSB_RXCSR);
+ csr |= MUSB_RXCSR_CLRDATATOG |
+ MUSB_RXCSR_P_WZC_BITS;
+ csr &= ~(MUSB_RXCSR_P_SENDSTALL |
+ MUSB_RXCSR_P_SENTSTALL);
+ musb_writew(regs, MUSB_RXCSR, csr);
+ }
+
+ /* Maybe start the first request in the queue */
+ request = next_request(musb_ep);
+ if (!musb_ep->busy && request) {
+ dev_dbg(musb->controller, "restarting the request\n");
+ musb_ep_restart(musb, request);
+ }
/* select ep0 again */
musb_ep_select(mbase, 0);
- handled = 1;
} break;
default:
/* class, vendor, etc ... delegate */
@@ -327,6 +356,31 @@ __acquires(musb->lock)
musb->test_mode_nr =
MUSB_TEST_PACKET;
break;
+
+ case 0xc0:
+ /* TEST_FORCE_HS */
+ pr_debug("TEST_FORCE_HS\n");
+ musb->test_mode_nr =
+ MUSB_TEST_FORCE_HS;
+ break;
+ case 0xc1:
+ /* TEST_FORCE_FS */
+ pr_debug("TEST_FORCE_FS\n");
+ musb->test_mode_nr =
+ MUSB_TEST_FORCE_FS;
+ break;
+ case 0xc2:
+ /* TEST_FIFO_ACCESS */
+ pr_debug("TEST_FIFO_ACCESS\n");
+ musb->test_mode_nr =
+ MUSB_TEST_FIFO_ACCESS;
+ break;
+ case 0xc3:
+ /* TEST_FORCE_HOST */
+ pr_debug("TEST_FORCE_HOST\n");
+ musb->test_mode_nr =
+ MUSB_TEST_FORCE_HOST;
+ break;
default:
goto stall;
}
@@ -335,7 +389,6 @@ __acquires(musb->lock)
if (handled > 0)
musb->test_mode = true;
break;
-#ifdef CONFIG_USB_MUSB_OTG
case USB_DEVICE_B_HNP_ENABLE:
if (!musb->g.is_otg)
goto stall;
@@ -352,7 +405,9 @@ __acquires(musb->lock)
goto stall;
musb->g.a_alt_hnp_support = 1;
break;
-#endif
+ case USB_DEVICE_DEBUG_MODE:
+ handled = 0;
+ break;
stall:
default:
handled = -EINVAL;
@@ -372,10 +427,8 @@ stall:
int is_in;
u16 csr;
- if (epnum == 0
- || epnum >= MUSB_C_NUM_EPS
- || ctrlrequest->wValue
- != USB_ENDPOINT_HALT)
+ if (epnum == 0 || epnum >= MUSB_C_NUM_EPS ||
+ ctrlrequest->wValue != USB_ENDPOINT_HALT)
break;
ep = musb->endpoints + epnum;
@@ -390,24 +443,20 @@ stall:
musb_ep_select(mbase, epnum);
if (is_in) {
- csr = musb_readw(regs,
- MUSB_TXCSR);
+ csr = musb_readw(regs, MUSB_TXCSR);
if (csr & MUSB_TXCSR_FIFONOTEMPTY)
csr |= MUSB_TXCSR_FLUSHFIFO;
csr |= MUSB_TXCSR_P_SENDSTALL
| MUSB_TXCSR_CLRDATATOG
| MUSB_TXCSR_P_WZC_BITS;
- musb_writew(regs, MUSB_TXCSR,
- csr);
+ musb_writew(regs, MUSB_TXCSR, csr);
} else {
- csr = musb_readw(regs,
- MUSB_RXCSR);
+ csr = musb_readw(regs, MUSB_RXCSR);
csr |= MUSB_RXCSR_P_SENDSTALL
| MUSB_RXCSR_FLUSHFIFO
| MUSB_RXCSR_CLRDATATOG
- | MUSB_TXCSR_P_WZC_BITS;
- musb_writew(regs, MUSB_RXCSR,
- csr);
+ | MUSB_RXCSR_P_WZC_BITS;
+ musb_writew(regs, MUSB_RXCSR, csr);
}
/* select ep0 again */
@@ -436,10 +485,12 @@ stall:
static void ep0_rxstate(struct musb *musb)
{
void __iomem *regs = musb->control_ep->regs;
+ struct musb_request *request;
struct usb_request *req;
u16 count, csr;
- req = next_ep0_request(musb);
+ request = next_ep0_request(musb);
+ req = &request->request;
/* read packet and ack; or stall because of gadget driver bug:
* should have provided the rx buffer before setup() returned.
@@ -454,8 +505,10 @@ static void ep0_rxstate(struct musb *musb)
req->status = -EOVERFLOW;
count = len;
}
- musb_read_fifo(&musb->endpoints[0], count, buf);
- req->actual += count;
+ if (count > 0) {
+ musb_read_fifo(&musb->endpoints[0], count, buf);
+ req->actual += count;
+ }
csr = MUSB_CSR0_P_SVDRXPKTRDY;
if (count < 64 || req->actual == req->length) {
musb->ep0_state = MUSB_EP0_STAGE_STATUSIN;
@@ -489,17 +542,20 @@ static void ep0_rxstate(struct musb *musb)
static void ep0_txstate(struct musb *musb)
{
void __iomem *regs = musb->control_ep->regs;
- struct usb_request *request = next_ep0_request(musb);
+ struct musb_request *req = next_ep0_request(musb);
+ struct usb_request *request;
u16 csr = MUSB_CSR0_TXPKTRDY;
u8 *fifo_src;
u8 fifo_count;
- if (!request) {
+ if (!req) {
/* WARN_ON(1); */
- DBG(2, "odd; csr0 %04x\n", musb_readw(regs, MUSB_CSR0));
+ dev_dbg(musb->controller, "odd; csr0 %04x\n", musb_readw(regs, MUSB_CSR0));
return;
}
+ request = &req->request;
+
/* load the data */
fifo_src = (u8 *) request->buf + request->actual;
fifo_count = min((unsigned) MUSB_EP0_FIFOSIZE,
@@ -509,7 +565,8 @@ static void ep0_txstate(struct musb *musb)
/* update the flags */
if (fifo_count < MUSB_MAX_END0_PACKET
- || request->actual == request->length) {
+ || (request->actual == request->length
+ && !request->zero)) {
musb->ep0_state = MUSB_EP0_STAGE_STATUSOUT;
csr |= MUSB_CSR0_P_DATAEND;
} else
@@ -542,7 +599,7 @@ static void ep0_txstate(struct musb *musb)
static void
musb_read_setup(struct musb *musb, struct usb_ctrlrequest *req)
{
- struct usb_request *r;
+ struct musb_request *r;
void __iomem *regs = musb->control_ep->regs;
musb_read_fifo(&musb->endpoints[0], sizeof *req, (u8 *)req);
@@ -550,7 +607,7 @@ musb_read_setup(struct musb *musb, struct usb_ctrlrequest *req)
/* NOTE: earlier 2.6 versions changed setup packets to host
* order, but now USB packets always stay in USB byte order.
*/
- DBG(3, "SETUP req%02x.%02x v%04x i%04x l%d\n",
+ dev_dbg(musb->controller, "SETUP req%02x.%02x v%04x i%04x l%d\n",
req->bRequestType,
req->bRequest,
le16_to_cpu(req->wValue),
@@ -560,7 +617,7 @@ musb_read_setup(struct musb *musb, struct usb_ctrlrequest *req)
/* clean up any leftover transfers */
r = next_ep0_request(musb);
if (r)
- musb_g_ep0_giveback(musb, r);
+ musb_g_ep0_giveback(musb, &r->request);
/* For zero-data requests we want to delay the STATUS stage to
* avoid SETUPEND errors. If we read data (OUT), delay accepting
@@ -618,17 +675,23 @@ irqreturn_t musb_g_ep0_irq(struct musb *musb)
csr = musb_readw(regs, MUSB_CSR0);
len = musb_readb(regs, MUSB_COUNT0);
- DBG(4, "csr %04x, count %d, myaddr %d, ep0stage %s\n",
- csr, len,
- musb_readb(mbase, MUSB_FADDR),
- decode_ep0stage(musb->ep0_state));
+ dev_dbg(musb->controller, "csr %04x, count %d, ep0stage %s\n",
+ csr, len, decode_ep0stage(musb->ep0_state));
+
+ if (csr & MUSB_CSR0_P_DATAEND) {
+ /*
+ * If DATAEND is set we should not call the callback,
+ * hence the status stage is not complete.
+ */
+ return IRQ_HANDLED;
+ }
/* I sent a stall.. need to acknowledge it now.. */
if (csr & MUSB_CSR0_P_SENTSTALL) {
musb_writew(regs, MUSB_CSR0,
csr & ~MUSB_CSR0_P_SENTSTALL);
retval = IRQ_HANDLED;
- musb->ep0_state = MUSB_EP0_STAGE_SETUP;
+ musb->ep0_state = MUSB_EP0_STAGE_IDLE;
csr = musb_readw(regs, MUSB_CSR0);
}
@@ -636,7 +699,18 @@ irqreturn_t musb_g_ep0_irq(struct musb *musb)
if (csr & MUSB_CSR0_P_SETUPEND) {
musb_writew(regs, MUSB_CSR0, MUSB_CSR0_P_SVDSETUPEND);
retval = IRQ_HANDLED;
- musb->ep0_state = MUSB_EP0_STAGE_SETUP;
+ /* Transition into the early status phase */
+ switch (musb->ep0_state) {
+ case MUSB_EP0_STAGE_TX:
+ musb->ep0_state = MUSB_EP0_STAGE_STATUSOUT;
+ break;
+ case MUSB_EP0_STAGE_RX:
+ musb->ep0_state = MUSB_EP0_STAGE_STATUSIN;
+ break;
+ default:
+ ERR("SetupEnd came in a wrong ep0stage %s\n",
+ decode_ep0stage(musb->ep0_state));
+ }
csr = musb_readw(regs, MUSB_CSR0);
/* NOTE: request may need completion */
}
@@ -678,7 +752,7 @@ irqreturn_t musb_g_ep0_irq(struct musb *musb)
/* enter test mode if needed (exit by reset) */
else if (musb->test_mode) {
- DBG(1, "entering TESTMODE\n");
+ dev_dbg(musb->controller, "entering TESTMODE\n");
if (MUSB_TEST_PACKET == musb->test_mode_nr)
musb_load_testpacket(musb);
@@ -691,17 +765,37 @@ irqreturn_t musb_g_ep0_irq(struct musb *musb)
case MUSB_EP0_STAGE_STATUSOUT:
/* end of sequence #1: write to host (TX state) */
{
- struct usb_request *req;
+ struct musb_request *req;
req = next_ep0_request(musb);
if (req)
- musb_g_ep0_giveback(musb, req);
+ musb_g_ep0_giveback(musb, &req->request);
}
+
+ /*
+ * In case when several interrupts can get coalesced,
+ * check to see if we've already received a SETUP packet...
+ */
+ if (csr & MUSB_CSR0_RXPKTRDY)
+ goto setup;
+
+ retval = IRQ_HANDLED;
+ musb->ep0_state = MUSB_EP0_STAGE_IDLE;
+ break;
+
+ case MUSB_EP0_STAGE_IDLE:
+ /*
+ * This state is typically (but not always) indiscernible
+ * from the status states since the corresponding interrupts
+ * tend to happen within too little period of time (with only
+ * a zero-length packet in between) and so get coalesced...
+ */
retval = IRQ_HANDLED;
musb->ep0_state = MUSB_EP0_STAGE_SETUP;
/* FALLTHROUGH */
case MUSB_EP0_STAGE_SETUP:
+setup:
if (csr & MUSB_CSR0_RXPKTRDY) {
struct usb_ctrlrequest setup;
int handled = 0;
@@ -737,12 +831,18 @@ irqreturn_t musb_g_ep0_irq(struct musb *musb)
handled = service_zero_data_request(
musb, &setup);
+ /*
+ * We're expecting no data in any case, so
+ * always set the DATAEND bit -- doing this
+ * here helps avoid SetupEnd interrupt coming
+ * in the idle stage when we're stalling...
+ */
+ musb->ackpend |= MUSB_CSR0_P_DATAEND;
+
/* status stage might be immediate */
- if (handled > 0) {
- musb->ackpend |= MUSB_CSR0_P_DATAEND;
+ if (handled > 0)
musb->ep0_state =
MUSB_EP0_STAGE_STATUSIN;
- }
break;
/* sequence #1 (IN to host), includes GET_STATUS
@@ -764,7 +864,7 @@ irqreturn_t musb_g_ep0_irq(struct musb *musb)
break;
}
- DBG(3, "handled %d, csr %04x, ep0stage %s\n",
+ dev_dbg(musb->controller, "handled %d, csr %04x, ep0stage %s\n",
handled, csr,
decode_ep0stage(musb->ep0_state));
@@ -781,9 +881,9 @@ irqreturn_t musb_g_ep0_irq(struct musb *musb)
if (handled < 0) {
musb_ep_select(mbase, 0);
stall:
- DBG(3, "stall (%d)\n", handled);
+ dev_dbg(musb->controller, "stall (%d)\n", handled);
musb->ackpend |= MUSB_CSR0_P_SENDSTALL;
- musb->ep0_state = MUSB_EP0_STAGE_SETUP;
+ musb->ep0_state = MUSB_EP0_STAGE_IDLE;
finish:
musb_writew(regs, MUSB_CSR0,
musb->ackpend);
@@ -803,7 +903,7 @@ finish:
/* "can't happen" */
WARN_ON(1);
musb_writew(regs, MUSB_CSR0, MUSB_CSR0_P_SENDSTALL);
- musb->ep0_state = MUSB_EP0_STAGE_SETUP;
+ musb->ep0_state = MUSB_EP0_STAGE_IDLE;
break;
}
@@ -861,16 +961,16 @@ musb_g_ep0_queue(struct usb_ep *e, struct usb_request *r, gfp_t gfp_flags)
status = 0;
break;
default:
- DBG(1, "ep0 request queued in state %d\n",
+ dev_dbg(musb->controller, "ep0 request queued in state %d\n",
musb->ep0_state);
status = -EINVAL;
goto cleanup;
}
/* add request to the list */
- list_add_tail(&(req->request.list), &(ep->req_list));
+ list_add_tail(&req->list, &ep->req_list);
- DBG(3, "queue to %s (%s), length=%d\n",
+ dev_dbg(musb->controller, "queue to %s (%s), length=%d\n",
ep->name, ep->is_in ? "IN/TX" : "OUT/RX",
req->request.length);
@@ -959,11 +1059,11 @@ static int musb_g_ep0_halt(struct usb_ep *e, int value)
csr |= MUSB_CSR0_P_SENDSTALL;
musb_writew(regs, MUSB_CSR0, csr);
- musb->ep0_state = MUSB_EP0_STAGE_SETUP;
+ musb->ep0_state = MUSB_EP0_STAGE_IDLE;
musb->ackpend = 0;
break;
default:
- DBG(1, "ep0 can't halt in state %d\n", musb->ep0_state);
+ dev_dbg(musb->controller, "ep0 can't halt in state %d\n", musb->ep0_state);
status = -EINVAL;
}