418 lines
12 KiB
Diff
418 lines
12 KiB
Diff
From 035cd4a26e9b1638b4b0419b98409026176563ca Mon Sep 17 00:00:00 2001
|
|
From: Sergei Shtylyov <sshtylyov-hkdhdckH98+B+jHODAdFcQ@public.gmane.org>
|
|
Date: Thu, 26 Mar 2009 18:29:19 -0700
|
|
Subject: [PATCH] musb: fix isochronous TXDMA (take 2)
|
|
|
|
Multi-frame isochronous TX URBs transfers in DMA mode never
|
|
complete with CPPI DMA because musb_host_tx() doesn't restart
|
|
DMA on the second frame, only emitting a debug message.
|
|
With Inventra DMA they complete, but in PIO mode. To fix:
|
|
|
|
- Factor out programming of the DMA transfer from
|
|
musb_ep_program() into musb_tx_dma_program();
|
|
|
|
- Reorder the code at the end of musb_host_tx() to
|
|
facilitate the fallback to PIO iff DMA fails;
|
|
|
|
- Handle the buffer offset consistently for both
|
|
PIO and DMA modes;
|
|
|
|
- Add an argument to musb_ep_program() for the same
|
|
reason (it only worked correctly with non-zero
|
|
offset of the first frame in PIO mode);
|
|
|
|
- Set the completed isochronous frame descriptor's
|
|
'actual_length' and 'status' fields correctly in
|
|
DMA mode.
|
|
|
|
Also, since CPPI reportedly doesn't like sending isochronous
|
|
packets in the RNDIS mode, change the criterion for this
|
|
mode to be used only for multi-packet transfers. (There's
|
|
no need for that mode in the single-packet case anyway.)
|
|
|
|
[ dbrownell-Rn4VEauK+AKRv+LV9MX5uipxlwaOVQ5f@public.gmane.org: split comment paragraph
|
|
into bullet list, shrink patch delta, style tweaks ]
|
|
|
|
Signed-off-by: Pavel Kiryukhin <pkiryukhin-hkdhdckH98+B+jHODAdFcQ@public.gmane.org>
|
|
Signed-off-by: Sergei Shtylyov <sshtylyov-hkdhdckH98+B+jHODAdFcQ@public.gmane.org>
|
|
Signed-off-by: David Brownell <dbrownell-Rn4VEauK+AKRv+LV9MX5uipxlwaOVQ5f@public.gmane.org>
|
|
---
|
|
drivers/usb/musb/cppi_dma.c | 1 +
|
|
drivers/usb/musb/musb_host.c | 227 +++++++++++++++++++-----------------------
|
|
2 files changed, 105 insertions(+), 123 deletions(-)
|
|
|
|
diff --git a/drivers/usb/musb/cppi_dma.c b/drivers/usb/musb/cppi_dma.c
|
|
index 569ef0f..ac7227c 100644
|
|
--- a/drivers/usb/musb/cppi_dma.c
|
|
+++ b/drivers/usb/musb/cppi_dma.c
|
|
@@ -579,6 +579,7 @@ cppi_next_tx_segment(struct musb *musb, struct cppi_channel *tx)
|
|
* trigger the "send a ZLP?" confusion.
|
|
*/
|
|
rndis = (maxpacket & 0x3f) == 0
|
|
+ && length > maxpacket
|
|
&& length < 0xffff
|
|
&& (length % maxpacket) != 0;
|
|
|
|
diff --git a/drivers/usb/musb/musb_host.c b/drivers/usb/musb/musb_host.c
|
|
index 6591282..f6e84a0 100644
|
|
--- a/drivers/usb/musb/musb_host.c
|
|
+++ b/drivers/usb/musb/musb_host.c
|
|
@@ -96,8 +96,8 @@
|
|
|
|
|
|
static void musb_ep_program(struct musb *musb, u8 epnum,
|
|
- struct urb *urb, unsigned int nOut,
|
|
- u8 *buf, u32 len);
|
|
+ struct urb *urb, int is_out,
|
|
+ u8 *buf, u32 offset, u32 len);
|
|
|
|
/*
|
|
* Clear TX fifo. Needed to avoid BABBLE errors.
|
|
@@ -189,9 +189,10 @@ musb_start_urb(struct musb *musb, int is_in, struct musb_qh *qh)
|
|
{
|
|
u16 frame;
|
|
u32 len;
|
|
- void *buf;
|
|
void __iomem *mbase = musb->mregs;
|
|
struct urb *urb = next_urb(qh);
|
|
+ void *buf = urb->transfer_buffer;
|
|
+ u32 offset = 0;
|
|
struct musb_hw_ep *hw_ep = qh->hw_ep;
|
|
unsigned pipe = urb->pipe;
|
|
u8 address = usb_pipedevice(pipe);
|
|
@@ -214,7 +215,7 @@ musb_start_urb(struct musb *musb, int is_in, struct musb_qh *qh)
|
|
case USB_ENDPOINT_XFER_ISOC:
|
|
qh->iso_idx = 0;
|
|
qh->frame = 0;
|
|
- buf = urb->transfer_buffer + urb->iso_frame_desc[0].offset;
|
|
+ offset = urb->iso_frame_desc[0].offset;
|
|
len = urb->iso_frame_desc[0].length;
|
|
break;
|
|
default: /* bulk, interrupt */
|
|
@@ -232,14 +233,14 @@ musb_start_urb(struct musb *musb, int is_in, struct musb_qh *qh)
|
|
case USB_ENDPOINT_XFER_ISOC: s = "-iso"; break;
|
|
default: s = "-intr"; break;
|
|
}; s; }),
|
|
- epnum, buf, len);
|
|
+ epnum, buf + offset, len);
|
|
|
|
/* Configure endpoint */
|
|
if (is_in || hw_ep->is_shared_fifo)
|
|
hw_ep->in_qh = qh;
|
|
else
|
|
hw_ep->out_qh = qh;
|
|
- musb_ep_program(musb, epnum, urb, !is_in, buf, len);
|
|
+ musb_ep_program(musb, epnum, urb, !is_in, buf, offset, len);
|
|
|
|
/* transmit may have more work: start it when it is time */
|
|
if (is_in)
|
|
@@ -250,7 +251,6 @@ musb_start_urb(struct musb *musb, int is_in, struct musb_qh *qh)
|
|
case USB_ENDPOINT_XFER_ISOC:
|
|
case USB_ENDPOINT_XFER_INT:
|
|
DBG(3, "check whether there's still time for periodic Tx\n");
|
|
- qh->iso_idx = 0;
|
|
frame = musb_readw(mbase, MUSB_FRAME);
|
|
/* FIXME this doesn't implement that scheduling policy ...
|
|
* or handle framecounter wrapping
|
|
@@ -631,14 +631,68 @@ musb_rx_reinit(struct musb *musb, struct musb_qh *qh, struct musb_hw_ep *ep)
|
|
ep->rx_reinit = 0;
|
|
}
|
|
|
|
+static bool musb_tx_dma_program(struct dma_controller *dma,
|
|
+ struct musb_hw_ep *hw_ep, struct musb_qh *qh,
|
|
+ struct urb *urb, u32 offset, u32 length)
|
|
+{
|
|
+ struct dma_channel *channel = hw_ep->tx_channel;
|
|
+ void __iomem *epio = hw_ep->regs;
|
|
+ u16 pkt_size = qh->maxpacket;
|
|
+ u16 csr;
|
|
+ u8 mode;
|
|
+
|
|
+#ifdef CONFIG_USB_INVENTRA_DMA
|
|
+ if (length > channel->max_len)
|
|
+ length = channel->max_len;
|
|
+
|
|
+ csr = musb_readw(epio, MUSB_TXCSR);
|
|
+ if (length > pkt_size) {
|
|
+ mode = 1;
|
|
+ csr |= MUSB_TXCSR_AUTOSET
|
|
+ | MUSB_TXCSR_DMAMODE
|
|
+ | MUSB_TXCSR_DMAENAB;
|
|
+ } else {
|
|
+ mode = 0;
|
|
+ csr &= ~(MUSB_TXCSR_AUTOSET | MUSB_TXCSR_DMAMODE);
|
|
+ csr |= MUSB_TXCSR_DMAENAB; /* against programmer's guide */
|
|
+ }
|
|
+ channel->desired_mode = mode;
|
|
+ musb_writew(epio, MUSB_TXCSR, csr);
|
|
+#else
|
|
+ if (!is_cppi_enabled() && !tusb_dma_omap())
|
|
+ return false;
|
|
+
|
|
+ channel->actual_len = 0;
|
|
+
|
|
+ /*
|
|
+ * TX uses "RNDIS" mode automatically but needs help
|
|
+ * to identify the zero-length-final-packet case.
|
|
+ */
|
|
+ mode = (urb->transfer_flags & URB_ZERO_PACKET) ? 1 : 0;
|
|
+#endif
|
|
+
|
|
+ qh->segsize = length;
|
|
+
|
|
+ if (!dma->channel_program(channel, pkt_size, mode,
|
|
+ urb->transfer_dma + offset, length)) {
|
|
+ dma->channel_release(channel);
|
|
+ hw_ep->tx_channel = NULL;
|
|
+
|
|
+ csr = musb_readw(epio, MUSB_TXCSR);
|
|
+ csr &= ~(MUSB_TXCSR_AUTOSET | MUSB_TXCSR_DMAENAB);
|
|
+ musb_writew(epio, MUSB_TXCSR, csr | MUSB_TXCSR_H_WZC_BITS);
|
|
+ return false;
|
|
+ }
|
|
+ return true;
|
|
+}
|
|
|
|
/*
|
|
* Program an HDRC endpoint as per the given URB
|
|
* Context: irqs blocked, controller lock held
|
|
*/
|
|
static void musb_ep_program(struct musb *musb, u8 epnum,
|
|
- struct urb *urb, unsigned int is_out,
|
|
- u8 *buf, u32 len)
|
|
+ struct urb *urb, int is_out,
|
|
+ u8 *buf, u32 offset, u32 len)
|
|
{
|
|
struct dma_controller *dma_controller;
|
|
struct dma_channel *dma_channel;
|
|
@@ -764,82 +818,9 @@ static void musb_ep_program(struct musb *musb, u8 epnum,
|
|
else
|
|
load_count = min((u32) packet_sz, len);
|
|
|
|
-#ifdef CONFIG_USB_INVENTRA_DMA
|
|
- if (dma_channel) {
|
|
- qh->segsize = min(len, dma_channel->max_len);
|
|
- if (qh->segsize <= packet_sz)
|
|
- dma_channel->desired_mode = 0;
|
|
- else
|
|
- dma_channel->desired_mode = 1;
|
|
-
|
|
- if (dma_channel->desired_mode == 0) {
|
|
- /* Against the programming guide */
|
|
- csr |= (MUSB_TXCSR_DMAENAB);
|
|
- } else
|
|
- csr |= (MUSB_TXCSR_AUTOSET
|
|
- | MUSB_TXCSR_DMAENAB
|
|
- | MUSB_TXCSR_DMAMODE);
|
|
- musb_writew(epio, MUSB_TXCSR, csr);
|
|
-
|
|
- dma_ok = dma_controller->channel_program(
|
|
- dma_channel, packet_sz,
|
|
- dma_channel->desired_mode,
|
|
- urb->transfer_dma,
|
|
- qh->segsize);
|
|
- if (dma_ok) {
|
|
- load_count = 0;
|
|
- } else {
|
|
- dma_controller->channel_release(dma_channel);
|
|
- if (is_out)
|
|
- hw_ep->tx_channel = NULL;
|
|
- else
|
|
- hw_ep->rx_channel = NULL;
|
|
- dma_channel = NULL;
|
|
-
|
|
- /*
|
|
- * The programming guide says that we must
|
|
- * clear the DMAENAB bit before DMAMODE...
|
|
- */
|
|
- csr = musb_readw(epio, MUSB_TXCSR);
|
|
- csr &= ~(MUSB_TXCSR_DMAENAB
|
|
- | MUSB_TXCSR_AUTOSET);
|
|
- musb_writew(epio, MUSB_TXCSR, csr);
|
|
- csr &= ~MUSB_TXCSR_DMAMODE;
|
|
- musb_writew(epio, MUSB_TXCSR, csr);
|
|
- }
|
|
- }
|
|
-#endif
|
|
-
|
|
- /* candidate for DMA */
|
|
- if ((is_cppi_enabled() || tusb_dma_omap()) && dma_channel) {
|
|
-
|
|
- /* Defer enabling DMA */
|
|
- dma_channel->actual_len = 0L;
|
|
- qh->segsize = len;
|
|
-
|
|
- /* TX uses "rndis" mode automatically, but needs help
|
|
- * to identify the zero-length-final-packet case.
|
|
- */
|
|
- dma_ok = dma_controller->channel_program(
|
|
- dma_channel, packet_sz,
|
|
- (urb->transfer_flags
|
|
- & URB_ZERO_PACKET)
|
|
- == URB_ZERO_PACKET,
|
|
- urb->transfer_dma,
|
|
- qh->segsize);
|
|
- if (dma_ok) {
|
|
- load_count = 0;
|
|
- } else {
|
|
- dma_controller->channel_release(dma_channel);
|
|
- hw_ep->tx_channel = NULL;
|
|
- dma_channel = NULL;
|
|
-
|
|
- /* REVISIT there's an error path here that
|
|
- * needs handling: can't do dma, but
|
|
- * there's no pio buffer address...
|
|
- */
|
|
- }
|
|
- }
|
|
+ if (dma_channel && musb_tx_dma_program(dma_controller,
|
|
+ hw_ep, qh, urb, offset, len))
|
|
+ load_count = 0;
|
|
|
|
if (load_count) {
|
|
/* PIO to load FIFO */
|
|
@@ -899,7 +880,7 @@ static void musb_ep_program(struct musb *musb, u8 epnum,
|
|
dma_channel, packet_sz,
|
|
!(urb->transfer_flags
|
|
& URB_SHORT_NOT_OK),
|
|
- urb->transfer_dma,
|
|
+ urb->transfer_dma + offset,
|
|
qh->segsize);
|
|
if (!dma_ok) {
|
|
dma_controller->channel_release(
|
|
@@ -1142,8 +1123,8 @@ void musb_host_tx(struct musb *musb, u8 epnum)
|
|
int pipe;
|
|
bool done = false;
|
|
u16 tx_csr;
|
|
- size_t wLength = 0;
|
|
- u8 *buf = NULL;
|
|
+ size_t length = 0;
|
|
+ size_t offset = 0;
|
|
struct urb *urb;
|
|
struct musb_hw_ep *hw_ep = musb->endpoints + epnum;
|
|
void __iomem *epio = hw_ep->regs;
|
|
@@ -1161,7 +1142,7 @@ void musb_host_tx(struct musb *musb, u8 epnum)
|
|
/* with CPPI, DMA sometimes triggers "extra" irqs */
|
|
if (!urb) {
|
|
DBG(4, "extra TX%d ready, csr %04x\n", epnum, tx_csr);
|
|
- goto finish;
|
|
+ return;
|
|
}
|
|
|
|
pipe = urb->pipe;
|
|
@@ -1198,7 +1179,7 @@ void musb_host_tx(struct musb *musb, u8 epnum)
|
|
musb_writew(epio, MUSB_TXCSR,
|
|
MUSB_TXCSR_H_WZC_BITS
|
|
| MUSB_TXCSR_TXPKTRDY);
|
|
- goto finish;
|
|
+ return;
|
|
}
|
|
|
|
if (status) {
|
|
@@ -1230,29 +1211,28 @@ void musb_host_tx(struct musb *musb, u8 epnum)
|
|
/* second cppi case */
|
|
if (dma_channel_status(dma) == MUSB_DMA_STATUS_BUSY) {
|
|
DBG(4, "extra TX%d ready, csr %04x\n", epnum, tx_csr);
|
|
- goto finish;
|
|
-
|
|
+ return;
|
|
}
|
|
|
|
- /* REVISIT this looks wrong... */
|
|
if (!status || dma || usb_pipeisoc(pipe)) {
|
|
if (dma)
|
|
- wLength = dma->actual_len;
|
|
+ length = dma->actual_len;
|
|
else
|
|
- wLength = qh->segsize;
|
|
- qh->offset += wLength;
|
|
+ length = qh->segsize;
|
|
+ qh->offset += length;
|
|
|
|
if (usb_pipeisoc(pipe)) {
|
|
struct usb_iso_packet_descriptor *d;
|
|
|
|
d = urb->iso_frame_desc + qh->iso_idx;
|
|
- d->actual_length = qh->segsize;
|
|
+ d->actual_length = length;
|
|
+ d->status = status;
|
|
if (++qh->iso_idx >= urb->number_of_packets) {
|
|
done = true;
|
|
} else {
|
|
d++;
|
|
- buf = urb->transfer_buffer + d->offset;
|
|
- wLength = d->length;
|
|
+ offset = d->offset;
|
|
+ length = d->length;
|
|
}
|
|
} else if (dma) {
|
|
done = true;
|
|
@@ -1265,10 +1245,8 @@ void musb_host_tx(struct musb *musb, u8 epnum)
|
|
& URB_ZERO_PACKET))
|
|
done = true;
|
|
if (!done) {
|
|
- buf = urb->transfer_buffer
|
|
- + qh->offset;
|
|
- wLength = urb->transfer_buffer_length
|
|
- - qh->offset;
|
|
+ offset = qh->offset;
|
|
+ length = urb->transfer_buffer_length - offset;
|
|
}
|
|
}
|
|
}
|
|
@@ -1287,28 +1265,31 @@ void musb_host_tx(struct musb *musb, u8 epnum)
|
|
urb->status = status;
|
|
urb->actual_length = qh->offset;
|
|
musb_advance_schedule(musb, urb, hw_ep, USB_DIR_OUT);
|
|
+ return;
|
|
+ } else if (usb_pipeisoc(pipe) && dma) {
|
|
+ if (musb_tx_dma_program(musb->dma_controller, hw_ep, qh, urb,
|
|
+ offset, length))
|
|
+ return;
|
|
+ } else if (tx_csr & MUSB_TXCSR_DMAENAB) {
|
|
+ DBG(1, "not complete, but DMA enabled?\n");
|
|
+ return;
|
|
+ }
|
|
|
|
- } else if (!(tx_csr & MUSB_TXCSR_DMAENAB)) {
|
|
- /* WARN_ON(!buf); */
|
|
-
|
|
- /* REVISIT: some docs say that when hw_ep->tx_double_buffered,
|
|
- * (and presumably, fifo is not half-full) we should write TWO
|
|
- * packets before updating TXCSR ... other docs disagree ...
|
|
- */
|
|
- /* PIO: start next packet in this URB */
|
|
- if (wLength > qh->maxpacket)
|
|
- wLength = qh->maxpacket;
|
|
- musb_write_fifo(hw_ep, wLength, buf);
|
|
- qh->segsize = wLength;
|
|
-
|
|
- musb_ep_select(mbase, epnum);
|
|
- musb_writew(epio, MUSB_TXCSR,
|
|
- MUSB_TXCSR_H_WZC_BITS | MUSB_TXCSR_TXPKTRDY);
|
|
- } else
|
|
- DBG(1, "not complete, but dma enabled?\n");
|
|
+ /*
|
|
+ * PIO: start next packet in this URB.
|
|
+ *
|
|
+ * REVISIT: some docs say that when hw_ep->tx_double_buffered,
|
|
+ * (and presumably, FIFO is not half-full) we should write *two*
|
|
+ * packets before updating TXCSR; other docs disagree...
|
|
+ */
|
|
+ if (length > qh->maxpacket)
|
|
+ length = qh->maxpacket;
|
|
+ musb_write_fifo(hw_ep, length, urb->transfer_buffer + offset);
|
|
+ qh->segsize = length;
|
|
|
|
-finish:
|
|
- return;
|
|
+ musb_ep_select(mbase, epnum);
|
|
+ musb_writew(epio, MUSB_TXCSR,
|
|
+ MUSB_TXCSR_H_WZC_BITS | MUSB_TXCSR_TXPKTRDY);
|
|
}
|
|
|
|
|
|
--
|
|
1.6.0.4
|
|
|