1
0
Fork 0

usb: musb: NAK timeout scheme on bulk TX endpoint

Fixes endpoint starvation issue when more than one bulk QH is
multiplexed on the reserved bulk TX endpoint.

This patch sets the NAK timeout interval for such QHs, and when
a timeout triggers the next QH will be scheduled.

This scheme doesn't work for devices which are connected to a
high to full speed tree (transaction translator) as there is
no NAK timeout interrupt from the musb controller from such
devices.

Signed-off-by: Ajay Kumar Gupta <ajay.gupta@ti.com>
Signed-off-by: Felipe Balbi <balbi@ti.com>
hifive-unleashed-5.1
Ajay Kumar Gupta 2012-07-19 13:41:59 +05:30 committed by Felipe Balbi
parent 603fe2b21d
commit f283862f3b
1 changed files with 92 additions and 62 deletions

View File

@ -892,6 +892,73 @@ static void musb_ep_program(struct musb *musb, u8 epnum,
}
}
/* Schedule next QH from musb->in_bulk/out_bulk and move the current qh to
* the end; avoids starvation for other endpoints.
*/
static void musb_bulk_nak_timeout(struct musb *musb, struct musb_hw_ep *ep,
int is_in)
{
struct dma_channel *dma;
struct urb *urb;
void __iomem *mbase = musb->mregs;
void __iomem *epio = ep->regs;
struct musb_qh *cur_qh, *next_qh;
u16 rx_csr, tx_csr;
musb_ep_select(mbase, ep->epnum);
if (is_in) {
dma = is_dma_capable() ? ep->rx_channel : NULL;
/* clear nak timeout bit */
rx_csr = musb_readw(epio, MUSB_RXCSR);
rx_csr |= MUSB_RXCSR_H_WZC_BITS;
rx_csr &= ~MUSB_RXCSR_DATAERROR;
musb_writew(epio, MUSB_RXCSR, rx_csr);
cur_qh = first_qh(&musb->in_bulk);
} else {
dma = is_dma_capable() ? ep->tx_channel : NULL;
/* clear nak timeout bit */
tx_csr = musb_readw(epio, MUSB_TXCSR);
tx_csr |= MUSB_TXCSR_H_WZC_BITS;
tx_csr &= ~MUSB_TXCSR_H_NAKTIMEOUT;
musb_writew(epio, MUSB_TXCSR, tx_csr);
cur_qh = first_qh(&musb->out_bulk);
}
if (cur_qh) {
urb = next_urb(cur_qh);
if (dma_channel_status(dma) == MUSB_DMA_STATUS_BUSY) {
dma->status = MUSB_DMA_STATUS_CORE_ABORT;
musb->dma_controller->channel_abort(dma);
urb->actual_length += dma->actual_len;
dma->actual_len = 0L;
}
musb_save_toggle(cur_qh, is_in, urb);
if (is_in) {
/* move cur_qh to end of queue */
list_move_tail(&cur_qh->ring, &musb->in_bulk);
/* get the next qh from musb->in_bulk */
next_qh = first_qh(&musb->in_bulk);
/* set rx_reinit and schedule the next qh */
ep->rx_reinit = 1;
} else {
/* move cur_qh to end of queue */
list_move_tail(&cur_qh->ring, &musb->out_bulk);
/* get the next qh from musb->out_bulk */
next_qh = first_qh(&musb->out_bulk);
/* set tx_reinit and schedule the next qh */
ep->tx_reinit = 1;
}
musb_start_urb(musb, is_in, next_qh);
}
}
/*
* Service the default endpoint (ep0) as host.
@ -1156,21 +1223,28 @@ void musb_host_tx(struct musb *musb, u8 epnum)
status = -ETIMEDOUT;
} else if (tx_csr & MUSB_TXCSR_H_NAKTIMEOUT) {
dev_dbg(musb->controller, "TX end=%d device not responding\n", epnum);
/* NOTE: this code path would be a good place to PAUSE a
* transfer, if there's some other (nonperiodic) tx urb
* that could use this fifo. (dma complicates it...)
* That's already done for bulk RX transfers.
*
* if (bulk && qh->ring.next != &musb->out_bulk), then
* we have a candidate... NAKing is *NOT* an error
*/
musb_ep_select(mbase, epnum);
musb_writew(epio, MUSB_TXCSR,
MUSB_TXCSR_H_WZC_BITS
| MUSB_TXCSR_TXPKTRDY);
return;
if (USB_ENDPOINT_XFER_BULK == qh->type && qh->mux == 1
&& !list_is_singular(&musb->out_bulk)) {
dev_dbg(musb->controller,
"NAK timeout on TX%d ep\n", epnum);
musb_bulk_nak_timeout(musb, hw_ep, 0);
} else {
dev_dbg(musb->controller,
"TX end=%d device not responding\n", epnum);
/* NOTE: this code path would be a good place to PAUSE a
* transfer, if there's some other (nonperiodic) tx urb
* that could use this fifo. (dma complicates it...)
* That's already done for bulk RX transfers.
*
* if (bulk && qh->ring.next != &musb->out_bulk), then
* we have a candidate... NAKing is *NOT* an error
*/
musb_ep_select(mbase, epnum);
musb_writew(epio, MUSB_TXCSR,
MUSB_TXCSR_H_WZC_BITS
| MUSB_TXCSR_TXPKTRDY);
}
return;
}
if (status) {
@ -1390,50 +1464,6 @@ void musb_host_tx(struct musb *musb, u8 epnum)
#endif
/* Schedule next QH from musb->in_bulk and move the current qh to
* the end; avoids starvation for other endpoints.
*/
static void musb_bulk_rx_nak_timeout(struct musb *musb, struct musb_hw_ep *ep)
{
struct dma_channel *dma;
struct urb *urb;
void __iomem *mbase = musb->mregs;
void __iomem *epio = ep->regs;
struct musb_qh *cur_qh, *next_qh;
u16 rx_csr;
musb_ep_select(mbase, ep->epnum);
dma = is_dma_capable() ? ep->rx_channel : NULL;
/* clear nak timeout bit */
rx_csr = musb_readw(epio, MUSB_RXCSR);
rx_csr |= MUSB_RXCSR_H_WZC_BITS;
rx_csr &= ~MUSB_RXCSR_DATAERROR;
musb_writew(epio, MUSB_RXCSR, rx_csr);
cur_qh = first_qh(&musb->in_bulk);
if (cur_qh) {
urb = next_urb(cur_qh);
if (dma_channel_status(dma) == MUSB_DMA_STATUS_BUSY) {
dma->status = MUSB_DMA_STATUS_CORE_ABORT;
musb->dma_controller->channel_abort(dma);
urb->actual_length += dma->actual_len;
dma->actual_len = 0L;
}
musb_save_toggle(cur_qh, 1, urb);
/* move cur_qh to end of queue */
list_move_tail(&cur_qh->ring, &musb->in_bulk);
/* get the next qh from musb->in_bulk */
next_qh = first_qh(&musb->in_bulk);
/* set rx_reinit and schedule the next qh */
ep->rx_reinit = 1;
musb_start_urb(musb, 1, next_qh);
}
}
/*
* Service an RX interrupt for the given IN endpoint; docs cover bulk, iso,
* and high-bandwidth IN transfer cases.
@ -1510,7 +1540,7 @@ void musb_host_rx(struct musb *musb, u8 epnum)
if (usb_pipebulk(urb->pipe)
&& qh->mux == 1
&& !list_is_singular(&musb->in_bulk)) {
musb_bulk_rx_nak_timeout(musb, hw_ep);
musb_bulk_nak_timeout(musb, hw_ep, 1);
return;
}
musb_ep_select(mbase, epnum);
@ -1873,14 +1903,14 @@ static int musb_schedule(
else
head = &musb->out_bulk;
/* Enable bulk RX NAK timeout scheme when bulk requests are
/* Enable bulk RX/TX NAK timeout scheme when bulk requests are
* multiplexed. This scheme doen't work in high speed to full
* speed scenario as NAK interrupts are not coming from a
* full speed device connected to a high speed device.
* NAK timeout interval is 8 (128 uframe or 16ms) for HS and
* 4 (8 frame or 8ms) for FS device.
*/
if (is_in && qh->dev)
if (qh->dev)
qh->intv_reg =
(USB_SPEED_HIGH == qh->dev->speed) ? 8 : 4;
goto success;