MUSB: Do not enable TX and RX DMA at the same time This is a workaround for OMAP3 errata 1.130. This bug causes an occassional DMA controller hang when two DMA channels are simultaneously enabled in RX and TX directions. With this workaround, in the best case, we effectively use DMA in both directions, and in the worst case, we use PIO mode in one direction. The average throughput is better than using PIO mode in one direction all the time. Signed-off-by: Anand Gadiyar <gadiyar@xxxxxx> Signed-off-by: Nilkesh Patra <Nilkesh.patra@xxxxxx> Signed-off-by: Ajay Kumar Gupta <ajay.gupta@xxxxxx> --- - To reproduce the hang condition, use g_ether and start pings in both directions - higher ping sizes reproduce the failure faster. - Needs another patch [1] from me to work reliably. [1] <http://marc.info/?l=linux-usb&m=125897074717960&w=2> - An alternative workaround for this bug involves using the SystemDMA to do a non-synchronized DMA transfer to unload the RX FIFO, and use the MUSB DMA for all TX channels. If someone wants to use that patch, I can post it as well. drivers/usb/musb/musbhsdma.c | 41 +++++++++++++++++++++++++++++++++++++++++ drivers/usb/musb/musbhsdma.h | 2 ++ 2 files changed, 43 insertions(+) Index: linux-omap-2.6/drivers/usb/musb/musbhsdma.c =================================================================== --- linux-omap-2.6.orig/drivers/usb/musb/musbhsdma.c +++ linux-omap-2.6/drivers/usb/musb/musbhsdma.c @@ -151,6 +151,11 @@ static void configure_channel(struct dma ? (1 << MUSB_HSDMA_TRANSMIT_SHIFT) : 0); + if (musb_channel->transmit) + controller->tx_active |= (1 << bchannel); + else + controller->rx_active |= (1 << bchannel); + /* address/count */ musb_write_hsdma_addr(mbase, bchannel, dma_addr); musb_write_hsdma_count(mbase, bchannel, len); @@ -161,11 +166,32 @@ static void configure_channel(struct dma csr); } +/* In version 1.4, if two DMA channels are simultaneously + * enabled in opposite directions, there is a chance that + * the DMA controller will hang. However, it is safe to + * use channels in the same direction. + * FIXME: provide a better description here + */ +static int musb_okay_to_use_dma(struct musb_dma_controller *controller, + int is_tx) +{ + struct musb *musb = controller->private_data; + + if (musb->hwvers == MUSB_HWVERS_1400) { + if (is_tx && controller->rx_active) + return 0; + else if (!is_tx && controller->tx_active) + return 0; + } + return 1; +} + static int dma_channel_program(struct dma_channel *channel, u16 packet_sz, u8 mode, dma_addr_t dma_addr, u32 len) { struct musb_dma_channel *musb_channel = channel->private_data; + struct musb_dma_controller *controller = musb_channel->controller; DBG(2, "ep%d-%s pkt_sz %d, dma_addr 0x%x length %d, mode %d\n", musb_channel->epnum, @@ -175,6 +201,9 @@ static int dma_channel_program(struct dm BUG_ON(channel->status == MUSB_DMA_STATUS_UNKNOWN || channel->status == MUSB_DMA_STATUS_BUSY); + if (!musb_okay_to_use_dma(controller, musb_channel->transmit)) + return false; + channel->actual_len = 0; musb_channel->start_addr = dma_addr; musb_channel->len = len; @@ -229,6 +258,11 @@ static int dma_channel_abort(struct dma_ musb_write_hsdma_addr(mbase, bchannel, 0); musb_write_hsdma_count(mbase, bchannel, 0); channel->status = MUSB_DMA_STATUS_FREE; + + if (musb_channel->transmit) + musb_channel->controller->tx_active &= ~(1 << bchannel); + else + musb_channel->controller->rx_active &= ~(1 << bchannel); } return 0; @@ -292,6 +326,13 @@ static irqreturn_t dma_controller_irq(in channel->status = MUSB_DMA_STATUS_FREE; + if (musb_channel->transmit) + controller->tx_active &= + ~(1 << bchannel); + else + controller->rx_active &= + ~(1 << bchannel); + /* completed */ if ((devctl & MUSB_DEVCTL_HM) && (musb_channel->transmit) Index: linux-omap-2.6/drivers/usb/musb/musbhsdma.h =================================================================== --- linux-omap-2.6.orig/drivers/usb/musb/musbhsdma.h +++ linux-omap-2.6/drivers/usb/musb/musbhsdma.h @@ -146,4 +146,6 @@ struct musb_dma_controller { u8 channel_count; u8 used_channels; u8 irq; + u8 tx_active; + u8 rx_active; }; -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html