aboutsummaryrefslogtreecommitdiff
path: root/drivers/usb
diff options
context:
space:
mode:
authorrajaram <rajaram.ragupathy@stericsson.com>2010-12-24 15:38:03 +0530
committerSrinidhi KASAGAR <srinidhi.kasagar@stericsson.com>2010-12-24 11:17:44 +0100
commitb06ba357a297f9d37a0a829e383bbe6fb8a15107 (patch)
treec9081d32c129b3bf0854ac0e9cad48cde4bf99e7 /drivers/usb
parentd2bfdb5266b387b83250a6c4e54a0d53c872cb37 (diff)
Generic: musb : Host DMA support
This is host side DMA support for u8500 musb which is mostly reuse of open source code and made it available for U8500. This enables host functionality with DMA. ST-Ericsson ID - 265668 Change-Id: I895081368ef459dad2344304599fed314f2fb69a Signed-off-by: rajaram <rajaram.ragupathy@stericsson.com> Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/11052 Tested-by: Ragupathy RAJARAM <ragupathy.rajaram@stericsson.com> Reviewed-by: Praveena NADAHALLY <praveen.nadahally@stericsson.com> Reviewed-by: Srinidhi KASAGAR <srinidhi.kasagar@stericsson.com>
Diffstat (limited to 'drivers/usb')
-rw-r--r--drivers/usb/musb/musb_host.c270
-rw-r--r--drivers/usb/musb/stm_musb_dma.c10
2 files changed, 273 insertions, 7 deletions
diff --git a/drivers/usb/musb/musb_host.c b/drivers/usb/musb/musb_host.c
index 5490c2bc46a..d8432432f71 100644
--- a/drivers/usb/musb/musb_host.c
+++ b/drivers/usb/musb/musb_host.c
@@ -45,6 +45,9 @@
#include "musb_core.h"
#include "musb_host.h"
+#ifdef CONFIG_ARCH_U8500
+#include "ste_config.h"
+#endif
/* MUSB HOST status 22-mar-2006
*
@@ -192,9 +195,16 @@ static inline void musb_h_tx_dma_start(struct musb_hw_ep *ep)
/* NOTE: no locks here; caller should lock and select EP */
txcsr = musb_readw(ep->regs, MUSB_TXCSR);
+
+#ifdef CONFIG_USB_U8500_DMA
+ txcsr |= MUSB_TXCSR_TXPKTRDY | MUSB_TXCSR_DMAENAB |
+ MUSB_TXCSR_H_WZC_BITS;
+ txcsr |= MUSB_TXCSR_DMAMODE;
+#else
txcsr |= MUSB_TXCSR_DMAENAB | MUSB_TXCSR_H_WZC_BITS;
if (is_cppi_enabled())
txcsr |= MUSB_TXCSR_DMAMODE;
+#endif
musb_writew(ep->regs, MUSB_TXCSR, txcsr);
}
@@ -307,8 +317,13 @@ start:
if (!hw_ep->tx_channel)
musb_h_tx_start(hw_ep);
+#ifdef CONFIG_USB_U8500_DMA
+ else
+ musb_h_tx_dma_start(hw_ep);
+#else
else if (is_cppi_enabled() || tusb_dma_omap())
musb_h_tx_dma_start(hw_ep);
+#endif
}
}
@@ -340,8 +355,15 @@ __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);
+#ifdef CONFIG_USB_U8500_DMA
+ /* Make it safe to call this routine more than once */
+ urb->transfer_flags &= ~(URB_SETUP_MAP_SINGLE | URB_SETUP_MAP_LOCAL |
+ URB_DMA_MAP_SG | URB_DMA_MAP_PAGE |
+ URB_DMA_MAP_SINGLE | URB_MAP_LOCAL);
+#endif
usb_hcd_giveback_urb(musb_to_hcd(musb), urb, status);
spin_lock(&musb->lock);
}
@@ -644,7 +666,34 @@ static bool musb_tx_dma_program(struct dma_controller *dma,
u16 csr;
u8 mode;
-#ifdef CONFIG_USB_INVENTRA_DMA
+#ifdef CONFIG_USB_U8500_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_DMAMODE | MUSB_TXCSR_DMAENAB;
+ /* autoset shouldn't be set in high bandwidth */
+ if (qh->hb_mult == 1)
+ csr |= MUSB_TXCSR_AUTOSET;
+ } 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);
+
+ 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;
+
+#else if CONFIG_USB_INVENTRA_DMA
if (length > channel->max_len)
length = channel->max_len;
@@ -859,7 +908,37 @@ static void musb_ep_program(struct musb *musb, u8 epnum,
}
/* kick things off */
+#ifdef CONFIG_USB_U8500_DMA
+
+ if ((len >= packet_sz) && dma_channel) {
+ if (dma_channel) {
+ dma_channel->actual_len = 0L;
+ qh->segsize = len;
+ /* AUTOREQ is in a DMA register */
+ musb_writew(hw_ep->regs, MUSB_RXCSR, csr);
+ csr = musb_readw(hw_ep->regs,
+ MUSB_RXCSR);
+
+ /* unless caller treats short rx transfers as
+ * errors, we dare not queue multiple transfers.
+ */
+ dma_ok = dma_controller->channel_program(
+ dma_channel, packet_sz,
+ !(urb->transfer_flags
+ & URB_SHORT_NOT_OK),
+ urb->transfer_dma + offset,
+ packet_sz);
+ if (!dma_ok) {
+ dma_controller->channel_release(
+ dma_channel);
+ hw_ep->rx_channel = NULL;
+ dma_channel = NULL;
+ } else
+ csr |= MUSB_RXCSR_DMAENAB;
+ }
+ }
+#else
if ((is_cppi_enabled() || tusb_dma_omap()) && dma_channel) {
/* candidate for DMA */
if (dma_channel) {
@@ -889,7 +968,7 @@ static void musb_ep_program(struct musb *musb, u8 epnum,
csr |= MUSB_RXCSR_DMAENAB;
}
}
-
+#endif
csr |= MUSB_RXCSR_H_REQPKT;
DBG(7, "RXCSR%d := %04x\n", epnum, csr);
musb_writew(hw_ep->regs, MUSB_RXCSR, csr);
@@ -1546,6 +1625,10 @@ void musb_host_rx(struct musb *musb, u8 epnum)
done = true;
goto finish;
}
+#ifdef CONFIG_USB_U8500_DMA
+ if (urb->transfer_buffer_length < hw_ep->max_packet_sz_rx)
+ goto u8500_no_dma;
+#endif
if (unlikely(dma_channel_status(dma) == MUSB_DMA_STATUS_BUSY)) {
/* SHOULD NEVER HAPPEN ... but at least DaVinci has done it */
@@ -1583,6 +1666,185 @@ void musb_host_rx(struct musb *musb, u8 epnum)
MUSB_RXCSR_H_WZC_BITS | rx_csr);
}
#endif
+
+#ifdef CONFIG_USB_U8500_DMA
+
+ if (dma && (rx_csr & MUSB_RXCSR_DMAENAB)) {
+ xfer_len = dma->actual_len;
+
+ val &= ~(MUSB_RXCSR_DMAENAB
+ | MUSB_RXCSR_H_AUTOREQ
+ | MUSB_RXCSR_AUTOCLEAR
+ | MUSB_RXCSR_RXPKTRDY);
+ musb_writew(hw_ep->regs, MUSB_RXCSR, val);
+
+ if (usb_pipeisoc(pipe)) {
+ struct usb_iso_packet_descriptor *d;
+
+ d = urb->iso_frame_desc + qh->iso_idx;
+ d->actual_length = xfer_len;
+
+ /* even if there was an error, we did the dma
+ * for iso_frame_desc->length
+ */
+ if (d->status != EILSEQ && d->status != -EOVERFLOW)
+ d->status = 0;
+
+ if (++qh->iso_idx >= urb->number_of_packets)
+ done = true;
+ else
+ done = false;
+
+ } else {
+ /* done if urb buffer is full or short packet is recd */
+ done = (urb->actual_length + xfer_len >=
+ urb->transfer_buffer_length
+ || dma->actual_len < qh->maxpacket);
+ }
+
+ /* send IN token for next packet, without AUTOREQ */
+ if (!done) {
+ val |= MUSB_RXCSR_H_REQPKT;
+ musb_writew(epio, MUSB_RXCSR,
+ MUSB_RXCSR_H_WZC_BITS | val);
+ }
+
+ DBG(4, "ep %d dma %s, rxcsr %04x, rxcount %d\n", epnum,
+ done ? "off" : "reset",
+ musb_readw(epio, MUSB_RXCSR),
+ musb_readw(epio, MUSB_RXCOUNT));
+ } else if (urb->status == -EINPROGRESS) {
+ /* if no errors, be sure a packet is ready for unloading */
+ if (unlikely(!(rx_csr & MUSB_RXCSR_RXPKTRDY))) {
+ status = -EPROTO;
+ ERR("Rx interrupt with no errors or packet!\n");
+
+ /* FIXME this is another "SHOULD NEVER HAPPEN" */
+
+/* SCRUB (RX) */
+ /* do the proper sequence to abort the transfer */
+ musb_ep_select(mbase, epnum);
+ val &= ~MUSB_RXCSR_H_REQPKT;
+ musb_writew(epio, MUSB_RXCSR, val);
+ goto finish;
+ }
+
+ /* we are expecting IN packets */
+ if (dma) {
+ struct dma_controller *c;
+ u16 rx_count;
+ int ret, length;
+ dma_addr_t buf;
+
+ rx_count = musb_readw(epio, MUSB_RXCOUNT);
+
+ DBG(2, "RX%d count %d, buffer 0x%x len %d/%d\n",
+ epnum, rx_count,
+ urb->transfer_dma
+ + urb->actual_length,
+ qh->offset,
+ urb->transfer_buffer_length);
+
+ c = musb->dma_controller;
+
+ if (usb_pipeisoc(pipe)) {
+ int d_status = 0;
+ struct usb_iso_packet_descriptor *d;
+
+ d = urb->iso_frame_desc + qh->iso_idx;
+
+ if (iso_err) {
+ d_status = -EILSEQ;
+ urb->error_count++;
+ }
+ if (rx_count > d->length) {
+ if (d_status == 0) {
+ d_status = -EOVERFLOW;
+ urb->error_count++;
+ }
+ DBG(2, "** OVERFLOW %d into %d\n",\
+ rx_count, d->length);
+
+ length = d->length;
+ } else
+ length = rx_count;
+ d->status = d_status;
+ buf = urb->transfer_dma + d->offset;
+ } else {
+ length = rx_count;
+ buf = urb->transfer_dma +
+ urb->actual_length;
+ }
+
+ dma->desired_mode = 0;
+#ifdef USE_MODE1
+ /* because of the issue below, mode 1 will
+ * only rarely behave with correct semantics.
+ */
+ if ((urb->transfer_flags &
+ URB_SHORT_NOT_OK)
+ && (urb->transfer_buffer_length -
+ urb->actual_length)
+ > qh->maxpacket)
+ dma->desired_mode = 1;
+ if (rx_count < hw_ep->max_packet_sz_rx) {
+ length = rx_count;
+ dma->desired_mode = 0;
+ } else {
+ length = urb->transfer_buffer_length;
+ }
+#endif
+
+/* Disadvantage of using mode 1:
+ * It's basically usable only for mass storage class; essentially all
+ * other protocols also terminate transfers on short packets.
+ *
+ * Details:
+ * An extra IN token is sent at the end of the transfer (due to AUTOREQ)
+ * If you try to use mode 1 for (transfer_buffer_length - 512), and try
+ * to use the extra IN token to grab the last packet using mode 0, then
+ * the problem is that you cannot be sure when the device will send the
+ * last packet and RxPktRdy set. Sometimes the packet is recd too soon
+ * such that it gets lost when RxCSR is re-set at the end of the mode 1
+ * transfer, while sometimes it is recd just a little late so that if you
+ * try to configure for mode 0 soon after the mode 1 transfer is
+ * completed, you will find rxcount 0. Okay, so you might think why not
+ * wait for an interrupt when the pkt is recd. Well, you won't get any!
+ */
+
+ val = musb_readw(epio, MUSB_RXCSR);
+ val &= ~MUSB_RXCSR_H_REQPKT;
+
+ if (dma->desired_mode == 0)
+ val &= ~MUSB_RXCSR_H_AUTOREQ;
+ else
+ val |= MUSB_RXCSR_H_AUTOREQ;
+ val |= MUSB_RXCSR_DMAENAB;
+
+ /* autoclear shouldn't be set in high bandwidth */
+ if (qh->hb_mult == 1)
+ val |= MUSB_RXCSR_AUTOCLEAR;
+
+ musb_writew(epio, MUSB_RXCSR,
+ MUSB_RXCSR_H_WZC_BITS | val);
+
+ /* REVISIT if when actual_length != 0,
+ * transfer_buffer_length needs to be
+ * adjusted first...
+ */
+ ret = c->channel_program(
+ dma, qh->maxpacket,
+ dma->desired_mode, buf, length);
+
+ if (!ret) {
+ c->channel_release(dma);
+ hw_ep->rx_channel = NULL;
+ dma = NULL;
+ /* REVISIT reset CSR */
+ }
+ }
+
+#else
if (dma && (rx_csr & MUSB_RXCSR_DMAENAB)) {
xfer_len = dma->actual_len;
@@ -1763,8 +2025,12 @@ void musb_host_rx(struct musb *musb, u8 epnum)
}
}
#endif /* Mentor DMA */
+#endif
if (!dma) {
+#ifdef CONFIG_USB_U8500_DMA
+u8500_no_dma:
+#endif
done = musb_host_packet_rx(musb, urb,
epnum, iso_err);
DBG(6, "read %spacket\n", done ? "last " : "");
diff --git a/drivers/usb/musb/stm_musb_dma.c b/drivers/usb/musb/stm_musb_dma.c
index 8fa0a492bf9..766f2328a0f 100644
--- a/drivers/usb/musb/stm_musb_dma.c
+++ b/drivers/usb/musb/stm_musb_dma.c
@@ -56,8 +56,10 @@ static int dma_controller_start(struct dma_controller *c)
musb_channel = &(controller->channel[bit]);
info = kzalloc(sizeof(struct stedma40_chan_cfg), GFP_KERNEL);
- if (!info)
+ if (!info) {
ERR("could not allocate dma info structure\n");
+ return -1;
+ }
musb_channel->info = info;
musb_channel->controller = controller;
#ifdef CONFIG_USB_U8500_DMA
@@ -70,7 +72,7 @@ static int dma_controller_start(struct dma_controller *c)
#ifndef CONFIG_USB_U8500_DMA
if (bit) {
#else
- if ((bit >= TX_CHANNEL_1) && (bit <= TX_CHANNEL_7)) {
+ if ((bit <= TX_CHANNEL_7)) {
#endif
int dst_dev_type;
@@ -147,8 +149,6 @@ static int dma_controller_start(struct dma_controller *c)
src_dev_type =
DB8500_DMA_DEV16_USB_OTG_IEP_7_15;
break;
- default:
- break;
}
info->src_dev_type = src_dev_type;
@@ -178,7 +178,7 @@ static int dma_controller_start(struct dma_controller *c)
/* Tx => mode 1; Rx => mode 0 */
if (bit) {
#else
- if ((bit >= TX_CHANNEL_1) && (bit <= TX_CHANNEL_7)) {
+ if ((bit <= TX_CHANNEL_7)) {
#endif
INIT_WORK(&musb_channel->channel_data_tx,
musb_channel_work_tx);