diff options
Diffstat (limited to 'drivers/usb/musb/musb_cppi41.c')
-rw-r--r-- | drivers/usb/musb/musb_cppi41.c | 69 |
1 files changed, 68 insertions, 1 deletions
diff --git a/drivers/usb/musb/musb_cppi41.c b/drivers/usb/musb/musb_cppi41.c index f88929609bac..7b8bbf53127e 100644 --- a/drivers/usb/musb/musb_cppi41.c +++ b/drivers/usb/musb/musb_cppi41.c @@ -39,6 +39,7 @@ struct cppi41_dma_channel { u32 transferred; u32 packet_sz; struct list_head tx_check; + struct work_struct dma_completion; }; #define MUSB_DMA_NUM_CHANNELS 15 @@ -112,6 +113,18 @@ static bool musb_is_tx_fifo_empty(struct musb_hw_ep *hw_ep) return true; } +static bool is_isoc(struct musb_hw_ep *hw_ep, bool in) +{ + if (in && hw_ep->in_qh) { + if (hw_ep->in_qh->type == USB_ENDPOINT_XFER_ISOC) + return true; + } else if (hw_ep->out_qh) { + if (hw_ep->out_qh->type == USB_ENDPOINT_XFER_ISOC) + return true; + } + return false; +} + static void cppi41_dma_callback(void *private_data); static void cppi41_trans_done(struct cppi41_dma_channel *cppi41_channel) @@ -119,7 +132,8 @@ static void cppi41_trans_done(struct cppi41_dma_channel *cppi41_channel) struct musb_hw_ep *hw_ep = cppi41_channel->hw_ep; struct musb *musb = hw_ep->musb; - if (!cppi41_channel->prog_len) { + if (!cppi41_channel->prog_len || + (cppi41_channel->channel.status == MUSB_DMA_STATUS_FREE)) { /* done, complete */ cppi41_channel->channel.actual_len = @@ -165,6 +179,32 @@ static void cppi41_trans_done(struct cppi41_dma_channel *cppi41_channel) } } +static void cppi_trans_done_work(struct work_struct *work) +{ + unsigned long flags; + struct cppi41_dma_channel *cppi41_channel = + container_of(work, struct cppi41_dma_channel, dma_completion); + struct cppi41_dma_controller *controller = cppi41_channel->controller; + struct musb *musb = controller->musb; + struct musb_hw_ep *hw_ep = cppi41_channel->hw_ep; + bool empty; + + if (!cppi41_channel->is_tx && is_isoc(hw_ep, 1)) { + spin_lock_irqsave(&musb->lock, flags); + cppi41_trans_done(cppi41_channel); + spin_unlock_irqrestore(&musb->lock, flags); + } else { + empty = musb_is_tx_fifo_empty(hw_ep); + if (empty) { + spin_lock_irqsave(&musb->lock, flags); + cppi41_trans_done(cppi41_channel); + spin_unlock_irqrestore(&musb->lock, flags); + } else { + schedule_work(&cppi41_channel->dma_completion); + } + } +} + static enum hrtimer_restart cppi41_recheck_tx_req(struct hrtimer *timer) { struct cppi41_dma_controller *controller; @@ -228,6 +268,14 @@ static void cppi41_dma_callback(void *private_data) transferred < cppi41_channel->packet_sz) cppi41_channel->prog_len = 0; + if (!cppi41_channel->is_tx) { + if (is_isoc(hw_ep, 1)) + schedule_work(&cppi41_channel->dma_completion); + else + cppi41_trans_done(cppi41_channel); + goto out; + } + empty = musb_is_tx_fifo_empty(hw_ep); if (empty) { cppi41_trans_done(cppi41_channel); @@ -264,6 +312,10 @@ static void cppi41_dma_callback(void *private_data) goto out; } } + if (is_isoc(hw_ep, 0)) { + schedule_work(&cppi41_channel->dma_completion); + goto out; + } list_add_tail(&cppi41_channel->tx_check, &controller->early_tx_list); if (!hrtimer_active(&controller->early_tx)) { @@ -448,12 +500,25 @@ static int cppi41_dma_channel_program(struct dma_channel *channel, dma_addr_t dma_addr, u32 len) { int ret; + struct cppi41_dma_channel *cppi41_channel = channel->private_data; + int hb_mult = 0; BUG_ON(channel->status == MUSB_DMA_STATUS_UNKNOWN || channel->status == MUSB_DMA_STATUS_BUSY); + if (is_host_active(cppi41_channel->controller->musb)) { + if (cppi41_channel->is_tx) + hb_mult = cppi41_channel->hw_ep->out_qh->hb_mult; + else + hb_mult = cppi41_channel->hw_ep->in_qh->hb_mult; + } + channel->status = MUSB_DMA_STATUS_BUSY; channel->actual_len = 0; + + if (hb_mult) + packet_sz = hb_mult * (packet_sz & 0x7FF); + ret = cppi41_configure_channel(channel, packet_sz, mode, dma_addr, len); if (!ret) channel->status = MUSB_DMA_STATUS_FREE; @@ -607,6 +672,8 @@ static int cppi41_dma_controller_start(struct cppi41_dma_controller *controller) cppi41_channel->port_num = port; cppi41_channel->is_tx = is_tx; INIT_LIST_HEAD(&cppi41_channel->tx_check); + INIT_WORK(&cppi41_channel->dma_completion, + cppi_trans_done_work); musb_dma = &cppi41_channel->channel; musb_dma->private_data = cppi41_channel; |