The MUSB code either expects TXCSR.DMAReqEnab bit cleared in several places and then proceeds with clearing the TXCSR.DMAMode bit (but does not bother with actually clearing DMAReqEnab beforehand) or just clears both bits at once -- while is the programming guide explicitly forbids to clear DMAMode before or in the same cycle as DMAReEnab. Fix this and while at it: - in musb_gadget::txstate(), stop clearing the AutoSet and DMAReqMode bits for the CPPI case since they never get set anyway (the former bit is reserved on DaVinci) but do clear the DMAReqEnab bit on the DMA error path; - in musb_host::musb_ep_program(), remove the duplicate DMA controller specific code code clearing the TXCSR previous state, add the code to clear TXCSR DMA bits on the Inventra DMA error path, to replace such code (executed late) on the PIO path; - in musbhsdma::dma_channel_abort()/dma_controller_irq(), add/use the 'offset' variable to avoid MUSB_EP_OFFSET() invocations on every RXCSR/TXCSR access. Signed-off-by: Sergei Shtylyov <sshtylyov@xxxxxxxxxxxxx> --- The patch is against the recent Linus' kernel, atop of the previosly sent patch series... drivers/usb/musb/musb_gadget.c | 35 ++++++++++---- drivers/usb/musb/musb_host.c | 100 ++++++++++++++++------------------------- drivers/usb/musb/musbhsdma.c | 58 ++++++++++++++--------- 3 files changed, 102 insertions(+), 91 deletions(-) Index: linux-2.6/drivers/usb/musb/musb_gadget.c =================================================================== --- linux-2.6.orig/drivers/usb/musb/musb_gadget.c +++ linux-2.6/drivers/usb/musb/musb_gadget.c @@ -165,9 +165,19 @@ static void nuke(struct musb_ep *ep, con if (is_dma_capable() && ep->dma) { struct dma_controller *c = ep->musb->dma_controller; int value; + if (ep->is_in) { + u16 txcsr = musb_readw(epio, MUSB_TXCSR); + + /* + * The programming guide says that we must not clear + * the DMAReqMode bit before DMAReqEnab, so we only + * clear it in the second write... + */ + txcsr &= MUSB_TXCSR_DMAMODE; + musb_writew(epio, MUSB_TXCSR, - 0 | MUSB_TXCSR_FLUSHFIFO); + txcsr | MUSB_TXCSR_FLUSHFIFO); musb_writew(epio, MUSB_TXCSR, 0 | MUSB_TXCSR_FLUSHFIFO); } else { @@ -230,7 +240,7 @@ static inline int max_ep_writesize(struc | IN token(s) are recd from Host. | -> DMA interrupt on completion | calls TxAvail. - | -> stop DMA, ~DmaEenab, + | -> stop DMA, ~DMAEnab, | -> set TxPktRdy for last short pkt or zlp | -> Complete Request | -> Continue next request (call txstate) @@ -315,9 +325,17 @@ static void txstate(struct musb *musb, s request->dma, request_size); if (use_dma) { if (musb_ep->dma->desired_mode == 0) { - /* ASSERT: DMAENAB is clear */ + /* + * We must not clear the DMAReqMode bit + * before the DMAReqEnab bit -- and the + * latter doesn't always get cleared + * before we get here... + */ csr &= ~(MUSB_TXCSR_AUTOSET | - MUSB_TXCSR_DMAMODE); + MUSB_TXCSR_DMAENAB); + musb_writew(epio, MUSB_TXCSR, csr | + MUSB_TXCSR_P_WZC_BITS); + csr &= ~MUSB_TXCSR_DMAMODE; csr |= (MUSB_TXCSR_DMAENAB | MUSB_TXCSR_MODE); /* against programming guide */ @@ -334,10 +352,7 @@ static void txstate(struct musb *musb, s #elif defined(CONFIG_USB_TI_CPPI_DMA) /* program endpoint CSR first, then setup DMA */ - csr &= ~(MUSB_TXCSR_AUTOSET - | MUSB_TXCSR_DMAMODE - | MUSB_TXCSR_P_UNDERRUN - | MUSB_TXCSR_TXPKTRDY); + csr &= ~(MUSB_TXCSR_P_UNDERRUN | MUSB_TXCSR_TXPKTRDY); csr |= MUSB_TXCSR_MODE | MUSB_TXCSR_DMAENAB; musb_writew(epio, MUSB_TXCSR, (MUSB_TXCSR_P_WZC_BITS & ~MUSB_TXCSR_P_UNDERRUN) @@ -364,8 +379,8 @@ static void txstate(struct musb *musb, s if (!use_dma) { c->channel_release(musb_ep->dma); musb_ep->dma = NULL; - /* ASSERT: DMAENAB clear */ - csr &= ~(MUSB_TXCSR_DMAMODE | MUSB_TXCSR_MODE); + csr &= ~MUSB_TXCSR_DMAENAB; + musb_writew(epio, MUSB_TXCSR, csr); /* invariant: prequest->buf is non-null */ } #elif defined(CONFIG_USB_TUSB_OMAP_DMA) Index: linux-2.6/drivers/usb/musb/musb_host.c =================================================================== --- linux-2.6.orig/drivers/usb/musb/musb_host.c +++ linux-2.6/drivers/usb/musb/musb_host.c @@ -575,10 +575,17 @@ musb_rx_reinit(struct musb *musb, struct csr = musb_readw(ep->regs, MUSB_TXCSR); if (csr & MUSB_TXCSR_MODE) { musb_h_tx_flush_fifo(ep); + csr = musb_readw(ep->regs, MUSB_TXCSR); musb_writew(ep->regs, MUSB_TXCSR, - MUSB_TXCSR_FRCDATATOG); + csr | MUSB_TXCSR_FRCDATATOG); } - /* clear mode (and everything else) to enable Rx */ + + /* + * Clear the Mode bit (and everything else) to enable Rx. + * NOTE: we mustn't clear the DMAReqMode bit before DMAReqEnab. + */ + if (csr & MUSB_TXCSR_DMAMODE) + musb_writew(ep->regs, MUSB_TXCSR, MUSB_TXCSR_DMAMODE); musb_writew(ep->regs, MUSB_TXCSR, 0); /* scrub all previous state, clearing toggle */ @@ -675,29 +682,30 @@ static void musb_ep_program(struct musb /* general endpoint setup */ if (epnum) { - /* ASSERT: TXCSR_DMAENAB was already cleared */ - /* flush all old state, set default */ musb_h_tx_flush_fifo(hw_ep); - csr &= ~(MUSB_TXCSR_H_NAKTIMEOUT - | MUSB_TXCSR_DMAMODE - | MUSB_TXCSR_FRCDATATOG - | MUSB_TXCSR_H_RXSTALL - | MUSB_TXCSR_H_ERROR - | MUSB_TXCSR_TXPKTRDY - ); + + /* + * We must not clear the DMAReqMode bit before or in + * the same cycle with the DMAReqEnab bit, so we clear + * the latter first... + */ + csr &= ~(MUSB_TXCSR_AUTOSET | MUSB_TXCSR_DMAENAB | + MUSB_TXCSR_FRCDATATOG | + MUSB_TXCSR_H_NAKTIMEOUT | + MUSB_TXCSR_H_RXSTALL | MUSB_TXCSR_H_ERROR | + MUSB_TXCSR_TXPKTRDY); csr |= MUSB_TXCSR_MODE; - if (usb_gettoggle(urb->dev, - qh->epnum, 1)) + if (usb_gettoggle(urb->dev, qh->epnum, 1)) csr |= MUSB_TXCSR_H_WR_DATATOGGLE | MUSB_TXCSR_H_DATATOGGLE; else csr |= MUSB_TXCSR_CLRDATATOG; - /* twice in case of double packet buffering */ musb_writew(epio, MUSB_TXCSR, csr); /* REVISIT may need to clear FLUSHFIFO ... */ + csr &= ~MUSB_TXCSR_DMAMODE; musb_writew(epio, MUSB_TXCSR, csr); csr = musb_readw(epio, MUSB_TXCSR); } else { @@ -744,34 +752,19 @@ static void musb_ep_program(struct musb #ifdef CONFIG_USB_INVENTRA_DMA if (dma_channel) { - - /* clear previous state */ - csr = musb_readw(epio, MUSB_TXCSR); - csr &= ~(MUSB_TXCSR_AUTOSET - | MUSB_TXCSR_DMAMODE - | MUSB_TXCSR_DMAENAB); - csr |= MUSB_TXCSR_MODE; - musb_writew(epio, MUSB_TXCSR, - csr | MUSB_TXCSR_MODE); - 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) { - csr &= ~(MUSB_TXCSR_AUTOSET - | MUSB_TXCSR_DMAMODE); - csr |= (MUSB_TXCSR_DMAENAB); - /* against programming guide */ - } else - csr |= (MUSB_TXCSR_AUTOSET - | MUSB_TXCSR_DMAENAB - | MUSB_TXCSR_DMAMODE); - + if (dma_channel->desired_mode == 0) + /* Against the programming guide */ + csr |= MUSB_TXCSR_DMAENAB; + else + csr |= MUSB_TXCSR_AUTOSET | + MUSB_TXCSR_DMAMODE | + MUSB_TXCSR_DMAENAB; musb_writew(epio, MUSB_TXCSR, csr); dma_ok = dma_controller->channel_program( @@ -788,6 +781,17 @@ static void musb_ep_program(struct musb else hw_ep->rx_channel = NULL; dma_channel = NULL; + + /* + * The programming guide says that we must only + * clear the DMAReqEnab bit before DMAReqMode... + */ + 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 @@ -795,18 +799,7 @@ static void musb_ep_program(struct musb /* candidate for DMA */ if ((is_cppi_enabled() || tusb_dma_omap()) && dma_channel) { - /* program endpoint CSRs first, then setup DMA. - * assume CPPI setup succeeds. - * defer enabling dma. - */ - csr = musb_readw(epio, MUSB_TXCSR); - csr &= ~(MUSB_TXCSR_AUTOSET - | MUSB_TXCSR_DMAMODE - | MUSB_TXCSR_DMAENAB); - csr |= MUSB_TXCSR_MODE; - musb_writew(epio, MUSB_TXCSR, - csr | MUSB_TXCSR_MODE); - + /* Defer enabling DMA */ dma_channel->actual_len = 0L; qh->segsize = len; @@ -835,20 +828,9 @@ static void musb_ep_program(struct musb } if (load_count) { - /* ASSERT: TXCSR_DMAENAB was already cleared */ - /* PIO to load FIFO */ qh->segsize = load_count; musb_write_fifo(hw_ep, load_count, buf); - csr = musb_readw(epio, MUSB_TXCSR); - csr &= ~(MUSB_TXCSR_DMAENAB - | MUSB_TXCSR_DMAMODE - | MUSB_TXCSR_AUTOSET); - /* write CSR */ - csr |= MUSB_TXCSR_MODE; - - if (epnum) - musb_writew(epio, MUSB_TXCSR, csr); } /* re-enable interrupt */ Index: linux-2.6/drivers/usb/musb/musbhsdma.c =================================================================== --- linux-2.6.orig/drivers/usb/musb/musbhsdma.c +++ linux-2.6/drivers/usb/musb/musbhsdma.c @@ -195,30 +195,32 @@ static int dma_channel_abort(struct dma_ void __iomem *mbase = musb_channel->controller->base; u8 bchannel = musb_channel->idx; + int offset; u16 csr; if (channel->status == MUSB_DMA_STATUS_BUSY) { if (musb_channel->transmit) { + offset = MUSB_EP_OFFSET(musb_channel->epnum, + MUSB_TXCSR); - csr = musb_readw(mbase, - MUSB_EP_OFFSET(musb_channel->epnum, - MUSB_TXCSR)); - csr &= ~(MUSB_TXCSR_AUTOSET | - MUSB_TXCSR_DMAENAB | - MUSB_TXCSR_DMAMODE); - musb_writew(mbase, - MUSB_EP_OFFSET(musb_channel->epnum, MUSB_TXCSR), - csr); + /* + * The programming guide says that we must only clear + * the DMAReqEnab bit before the DMAReqMode bit... + */ + csr = musb_readw(mbase, offset); + csr &= ~(MUSB_TXCSR_AUTOSET | MUSB_TXCSR_DMAENAB); + musb_writew(mbase, offset, csr); + csr &= ~MUSB_TXCSR_DMAMODE; + musb_writew(mbase, offset, csr); } else { - csr = musb_readw(mbase, - MUSB_EP_OFFSET(musb_channel->epnum, - MUSB_RXCSR)); + offset = MUSB_EP_OFFSET(musb_channel->epnum, + MUSB_RXCSR); + + csr = musb_readw(mbase, offset); csr &= ~(MUSB_RXCSR_AUTOCLEAR | MUSB_RXCSR_DMAENAB | MUSB_RXCSR_DMAMODE); - musb_writew(mbase, - MUSB_EP_OFFSET(musb_channel->epnum, MUSB_RXCSR), - csr); + musb_writew(mbase, offset, csr); } musb_writew(mbase, @@ -296,14 +298,26 @@ static irqreturn_t dma_controller_irq(in && ((channel->desired_mode == 0) || (channel->actual_len & (musb_channel->max_packet_sz - 1))) - ) { + ) { + u8 epnum = musb_channel->epnum; + int offset = MUSB_EP_OFFSET(epnum, + MUSB_TXCSR); + u16 txcsr; + + /* + * The programming guide says that we + * must only clear the DMAReqEnab bit + * before the DMAReqMode bit... + */ + musb_ep_select(mbase, epnum); + txcsr = musb_readw(mbase, offset); + txcsr &= ~(MUSB_TXCSR_DMAENAB | + MUSB_TXCSR_AUTOSET); + musb_writew(mbase, offset, txcsr); /* Send out the packet */ - musb_ep_select(mbase, - musb_channel->epnum); - musb_writew(mbase, MUSB_EP_OFFSET( - musb_channel->epnum, - MUSB_TXCSR), - MUSB_TXCSR_TXPKTRDY); + txcsr &= ~MUSB_TXCSR_DMAMODE; + txcsr |= MUSB_TXCSR_TXPKTRDY; + musb_writew(mbase, offset, txcsr); } musb_dma_completion(musb, musb_channel->epnum, musb_channel->transmit); -- 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