diff options
author | Paul Zimmerman <Paul.Zimmerman@synopsys.com> | 2013-03-11 17:48:00 -0700 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2013-03-11 18:16:37 -0700 |
commit | dc4c76e7b22cdcc1ba71ff87edee55f464e01658 (patch) | |
tree | bde9eaecd4e1dac304194bb6b9f9ceabba777bb2 /drivers/staging/dwc2 | |
parent | 7359d482eb4d3967cc8be354405ae6be6eaf732c (diff) |
staging: HCD descriptor DMA support for the DWC2 driver
This file contains code to support the HCD descriptor DMA mode of
the controller
Signed-off-by: Paul Zimmerman <paulz@synopsys.com>
Reviewed-by: Felipe Balbi <balbi@ti.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/staging/dwc2')
-rw-r--r-- | drivers/staging/dwc2/hcd_ddma.c | 1196 |
1 files changed, 1196 insertions, 0 deletions
diff --git a/drivers/staging/dwc2/hcd_ddma.c b/drivers/staging/dwc2/hcd_ddma.c new file mode 100644 index 000000000000..ab88f5069183 --- /dev/null +++ b/drivers/staging/dwc2/hcd_ddma.c @@ -0,0 +1,1196 @@ +/* + * hcd_ddma.c - DesignWare HS OTG Controller descriptor DMA routines + * + * Copyright (C) 2004-2013 Synopsys, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any + * later version. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This file contains the Descriptor DMA implementation for Host mode + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/dma-mapping.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/usb.h> + +#include <linux/usb/hcd.h> +#include <linux/usb/ch11.h> + +#include "core.h" +#include "hcd.h" + +static u16 dwc2_frame_list_idx(u16 frame) +{ + return frame & (FRLISTEN_64_SIZE - 1); +} + +static u16 dwc2_desclist_idx_inc(u16 idx, u16 inc, u8 speed) +{ + return (idx + inc) & + ((speed == USB_SPEED_HIGH ? MAX_DMA_DESC_NUM_HS_ISOC : + MAX_DMA_DESC_NUM_GENERIC) - 1); +} + +static u16 dwc2_desclist_idx_dec(u16 idx, u16 inc, u8 speed) +{ + return (idx - inc) & + ((speed == USB_SPEED_HIGH ? MAX_DMA_DESC_NUM_HS_ISOC : + MAX_DMA_DESC_NUM_GENERIC) - 1); +} + +static u16 dwc2_max_desc_num(struct dwc2_qh *qh) +{ + return (qh->ep_type == USB_ENDPOINT_XFER_ISOC && + qh->dev_speed == USB_SPEED_HIGH) ? + MAX_DMA_DESC_NUM_HS_ISOC : MAX_DMA_DESC_NUM_GENERIC; +} + +static u16 dwc2_frame_incr_val(struct dwc2_qh *qh) +{ + return qh->dev_speed == USB_SPEED_HIGH ? + (qh->interval + 8 - 1) / 8 : qh->interval; +} + +static int dwc2_desc_list_alloc(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, + gfp_t flags) +{ + qh->desc_list = dma_alloc_coherent(hsotg->dev, + sizeof(struct dwc2_hcd_dma_desc) * + dwc2_max_desc_num(qh), &qh->desc_list_dma, + flags); + + if (!qh->desc_list) + return -ENOMEM; + + memset(qh->desc_list, 0, + sizeof(struct dwc2_hcd_dma_desc) * dwc2_max_desc_num(qh)); + + qh->n_bytes = kzalloc(sizeof(u32) * dwc2_max_desc_num(qh), flags); + if (!qh->n_bytes) { + dma_free_coherent(hsotg->dev, sizeof(struct dwc2_hcd_dma_desc) + * dwc2_max_desc_num(qh), qh->desc_list, + qh->desc_list_dma); + qh->desc_list = NULL; + return -ENOMEM; + } + + return 0; +} + +static void dwc2_desc_list_free(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ + if (qh->desc_list) { + dma_free_coherent(hsotg->dev, sizeof(struct dwc2_hcd_dma_desc) + * dwc2_max_desc_num(qh), qh->desc_list, + qh->desc_list_dma); + qh->desc_list = NULL; + } + + kfree(qh->n_bytes); + qh->n_bytes = NULL; +} + +static int dwc2_frame_list_alloc(struct dwc2_hsotg *hsotg, gfp_t mem_flags) +{ + if (hsotg->frame_list) + return 0; + + hsotg->frame_list = dma_alloc_coherent(hsotg->dev, + 4 * FRLISTEN_64_SIZE, + &hsotg->frame_list_dma, + mem_flags); + if (!hsotg->frame_list) + return -ENOMEM; + + memset(hsotg->frame_list, 0, 4 * FRLISTEN_64_SIZE); + return 0; +} + +static void dwc2_frame_list_free(struct dwc2_hsotg *hsotg) +{ + u32 *frame_list; + dma_addr_t frame_list_dma; + unsigned long flags; + + spin_lock_irqsave(&hsotg->lock, flags); + + if (!hsotg->frame_list) { + spin_unlock_irqrestore(&hsotg->lock, flags); + return; + } + + frame_list = hsotg->frame_list; + frame_list_dma = hsotg->frame_list_dma; + hsotg->frame_list = NULL; + + spin_unlock_irqrestore(&hsotg->lock, flags); + + dma_free_coherent(hsotg->dev, 4 * FRLISTEN_64_SIZE, frame_list, + frame_list_dma); +} + +static void dwc2_per_sched_enable(struct dwc2_hsotg *hsotg, u32 fr_list_en) +{ + u32 hcfg; + unsigned long flags; + + spin_lock_irqsave(&hsotg->lock, flags); + + hcfg = readl(hsotg->regs + HCFG); + if (hcfg & HCFG_PERSCHEDENA) { + /* already enabled */ + spin_unlock_irqrestore(&hsotg->lock, flags); + return; + } + + writel(hsotg->frame_list_dma, hsotg->regs + HFLBADDR); + + hcfg &= ~HCFG_FRLISTEN_MASK; + hcfg |= fr_list_en | HCFG_PERSCHEDENA; + dev_vdbg(hsotg->dev, "Enabling Periodic schedule\n"); + writel(hcfg, hsotg->regs + HCFG); + + spin_unlock_irqrestore(&hsotg->lock, flags); +} + +static void dwc2_per_sched_disable(struct dwc2_hsotg *hsotg) +{ + u32 hcfg; + unsigned long flags; + + spin_lock_irqsave(&hsotg->lock, flags); + + hcfg = readl(hsotg->regs + HCFG); + if (!(hcfg & HCFG_PERSCHEDENA)) { + /* already disabled */ + spin_unlock_irqrestore(&hsotg->lock, flags); + return; + } + + hcfg &= ~HCFG_PERSCHEDENA; + dev_vdbg(hsotg->dev, "Disabling Periodic schedule\n"); + writel(hcfg, hsotg->regs + HCFG); + + spin_unlock_irqrestore(&hsotg->lock, flags); +} + +/* + * Activates/Deactivates FrameList entries for the channel based on endpoint + * servicing period + */ +static void dwc2_update_frame_list(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, + int enable) +{ + struct dwc2_host_chan *chan; + u16 i, j, inc; + + if (!qh->channel) { + dev_err(hsotg->dev, "qh->channel = %p", qh->channel); + return; + } + + if (!hsotg) { + dev_err(hsotg->dev, "------hsotg = %p", hsotg); + return; + } + + if (!hsotg->frame_list) { + dev_err(hsotg->dev, "-------hsotg->frame_list = %p", + hsotg->frame_list); + return; + } + + chan = qh->channel; + inc = dwc2_frame_incr_val(qh); + if (qh->ep_type == USB_ENDPOINT_XFER_ISOC) + i = dwc2_frame_list_idx(qh->sched_frame); + else + i = 0; + + j = i; + do { + if (enable) + hsotg->frame_list[j] |= 1 << chan->hc_num; + else + hsotg->frame_list[j] &= ~(1 << chan->hc_num); + j = (j + inc) & (FRLISTEN_64_SIZE - 1); + } while (j != i); + + if (!enable) + return; + + chan->schinfo = 0; + if (chan->speed == USB_SPEED_HIGH && qh->interval) { + j = 1; + /* TODO - check this */ + inc = (8 + qh->interval - 1) / qh->interval; + for (i = 0; i < inc; i++) { + chan->schinfo |= j; + j = j << qh->interval; + } + } else { + chan->schinfo = 0xff; + } +} + +static void dwc2_release_channel_ddma(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh) +{ + struct dwc2_host_chan *chan = qh->channel; + + if (dwc2_qh_is_non_per(qh)) + hsotg->non_periodic_channels--; + else + dwc2_update_frame_list(hsotg, qh, 0); + + /* + * The condition is added to prevent double cleanup try in case of + * device disconnect. See channel cleanup in dwc2_hcd_disconnect(). + */ + if (chan->qh) { + if (!list_empty(&chan->hc_list_entry)) + list_del(&chan->hc_list_entry); + dwc2_hc_cleanup(hsotg, chan); + list_add_tail(&chan->hc_list_entry, &hsotg->free_hc_list); + chan->qh = NULL; + } + + qh->channel = NULL; + qh->ntd = 0; + + if (qh->desc_list) + memset(qh->desc_list, 0, sizeof(struct dwc2_hcd_dma_desc) * + dwc2_max_desc_num(qh)); +} + +/** + * dwc2_hcd_qh_init_ddma() - Initializes a QH structure's Descriptor DMA + * related members + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @qh: The QH to init + * + * Return: 0 if successful, negative error code otherwise + * + * Allocates memory for the descriptor list. For the first periodic QH, + * allocates memory for the FrameList and enables periodic scheduling. + */ +int dwc2_hcd_qh_init_ddma(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, + gfp_t mem_flags) +{ + int retval; + + if (qh->do_split) { + dev_err(hsotg->dev, + "SPLIT Transfers are not supported in Descriptor DMA mode.\n"); + retval = -EINVAL; + goto err0; + } + + retval = dwc2_desc_list_alloc(hsotg, qh, mem_flags); + if (retval) + goto err0; + + if (qh->ep_type == USB_ENDPOINT_XFER_ISOC || + qh->ep_type == USB_ENDPOINT_XFER_INT) { + if (!hsotg->frame_list) { + retval = dwc2_frame_list_alloc(hsotg, mem_flags); + if (retval) + goto err1; + /* Enable periodic schedule on first periodic QH */ + dwc2_per_sched_enable(hsotg, HCFG_FRLISTEN_64); + } + } + + qh->ntd = 0; + return 0; + +err1: + dwc2_desc_list_free(hsotg, qh); +err0: + return retval; +} + +/** + * dwc2_hcd_qh_free_ddma() - Frees a QH structure's Descriptor DMA related + * members + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @qh: The QH to free + * + * Frees descriptor list memory associated with the QH. If QH is periodic and + * the last, frees FrameList memory and disables periodic scheduling. + */ +void dwc2_hcd_qh_free_ddma(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ + dwc2_desc_list_free(hsotg, qh); + + /* + * Channel still assigned due to some reasons. + * Seen on Isoc URB dequeue. Channel halted but no subsequent + * ChHalted interrupt to release the channel. Afterwards + * when it comes here from endpoint disable routine + * channel remains assigned. + */ + if (qh->channel) + dwc2_release_channel_ddma(hsotg, qh); + + if ((qh->ep_type == USB_ENDPOINT_XFER_ISOC || + qh->ep_type == USB_ENDPOINT_XFER_INT) && + !hsotg->periodic_channels && hsotg->frame_list) { + dwc2_per_sched_disable(hsotg); + dwc2_frame_list_free(hsotg); + } +} + +static u8 dwc2_frame_to_desc_idx(struct dwc2_qh *qh, u16 frame_idx) +{ + if (qh->dev_speed == USB_SPEED_HIGH) + /* Descriptor set (8 descriptors) index which is 8-aligned */ + return (frame_idx & ((MAX_DMA_DESC_NUM_HS_ISOC / 8) - 1)) * 8; + else + return frame_idx & (MAX_DMA_DESC_NUM_GENERIC - 1); +} + +/* + * Determine starting frame for Isochronous transfer. + * Few frames skipped to prevent race condition with HC. + */ +static u16 dwc2_calc_starting_frame(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh, u16 *skip_frames) +{ + u16 frame; + + hsotg->frame_number = dwc2_hcd_get_frame_number(hsotg); + + /* sched_frame is always frame number (not uFrame) both in FS and HS! */ + + /* + * skip_frames is used to limit activated descriptors number + * to avoid the situation when HC services the last activated + * descriptor firstly. + * Example for FS: + * Current frame is 1, scheduled frame is 3. Since HC always fetches + * the descriptor corresponding to curr_frame+1, the descriptor + * corresponding to frame 2 will be fetched. If the number of + * descriptors is max=64 (or greather) the list will be fully programmed + * with Active descriptors and it is possible case (rare) that the + * latest descriptor(considering rollback) corresponding to frame 2 will + * be serviced first. HS case is more probable because, in fact, up to + * 11 uframes (16 in the code) may be skipped. + */ + if (qh->dev_speed == USB_SPEED_HIGH) { + /* + * Consider uframe counter also, to start xfer asap. If half of + * the frame elapsed skip 2 frames otherwise just 1 frame. + * Starting descriptor index must be 8-aligned, so if the + * current frame is near to complete the next one is skipped as + * well. + */ + if (dwc2_micro_frame_num(hsotg->frame_number) >= 5) { + *skip_frames = 2 * 8; + frame = dwc2_frame_num_inc(hsotg->frame_number, + *skip_frames); + } else { + *skip_frames = 1 * 8; + frame = dwc2_frame_num_inc(hsotg->frame_number, + *skip_frames); + } + + frame = dwc2_full_frame_num(frame); + } else { + /* + * Two frames are skipped for FS - the current and the next. + * But for descriptor programming, 1 frame (descriptor) is + * enough, see example above. + */ + *skip_frames = 1; + frame = dwc2_frame_num_inc(hsotg->frame_number, 2); + } + + return frame; +} + +/* + * Calculate initial descriptor index for isochronous transfer based on + * scheduled frame + */ +static u16 dwc2_recalc_initial_desc_idx(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh) +{ + u16 frame, fr_idx, fr_idx_tmp, skip_frames; + + /* + * With current ISOC processing algorithm the channel is being released + * when no more QTDs in the list (qh->ntd == 0). Thus this function is + * called only when qh->ntd == 0 and qh->channel == 0. + * + * So qh->channel != NULL branch is not used and just not removed from + * the source file. It is required for another possible approach which + * is, do not disable and release the channel when ISOC session + * completed, just move QH to inactive schedule until new QTD arrives. + * On new QTD, the QH moved back to 'ready' schedule, starting frame and + * therefore starting desc_index are recalculated. In this case channel + * is released only on ep_disable. + */ + + /* + * Calculate starting descriptor index. For INTERRUPT endpoint it is + * always 0. + */ + if (qh->channel) { + frame = dwc2_calc_starting_frame(hsotg, qh, &skip_frames); + /* + * Calculate initial descriptor index based on FrameList current + * bitmap and servicing period + */ + fr_idx_tmp = dwc2_frame_list_idx(frame); + fr_idx = (FRLISTEN_64_SIZE + + dwc2_frame_list_idx(qh->sched_frame) - fr_idx_tmp) + % dwc2_frame_incr_val(qh); + fr_idx = (fr_idx + fr_idx_tmp) % FRLISTEN_64_SIZE; + } else { + qh->sched_frame = dwc2_calc_starting_frame(hsotg, qh, + &skip_frames); + fr_idx = dwc2_frame_list_idx(qh->sched_frame); + } + + qh->td_first = qh->td_last = dwc2_frame_to_desc_idx(qh, fr_idx); + + return skip_frames; +} + +#define ISOC_URB_GIVEBACK_ASAP + +#define MAX_ISOC_XFER_SIZE_FS 1023 +#define MAX_ISOC_XFER_SIZE_HS 3072 +#define DESCNUM_THRESHOLD 4 + +static void dwc2_fill_host_isoc_dma_desc(struct dwc2_hsotg *hsotg, + struct dwc2_qtd *qtd, + struct dwc2_qh *qh, u32 max_xfer_size, + u16 idx) +{ + struct dwc2_hcd_dma_desc *dma_desc = &qh->desc_list[idx]; + struct dwc2_hcd_iso_packet_desc *frame_desc; + + memset(dma_desc, 0, sizeof(*dma_desc)); + frame_desc = &qtd->urb->iso_descs[qtd->isoc_frame_index_last]; + + if (frame_desc->length > max_xfer_size) + qh->n_bytes[idx] = max_xfer_size; + else + qh->n_bytes[idx] = frame_desc->length; + + dma_desc->buf = (u32)(qtd->urb->dma + frame_desc->offset); + dma_desc->status = qh->n_bytes[idx] << HOST_DMA_ISOC_NBYTES_SHIFT & + HOST_DMA_ISOC_NBYTES_MASK; + +#ifdef ISOC_URB_GIVEBACK_ASAP + /* Set IOC for each descriptor corresponding to last frame of URB */ + if (qtd->isoc_frame_index_last == qtd->urb->packet_count) + dma_desc->status |= HOST_DMA_IOC; +#endif + + qh->ntd++; + qtd->isoc_frame_index_last++; +} + +static void dwc2_init_isoc_dma_desc(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh, u16 skip_frames) +{ + struct dwc2_qtd *qtd; + u32 max_xfer_size; + u16 idx, inc, n_desc, ntd_max = 0; + + idx = qh->td_last; + inc = qh->interval; + n_desc = 0; + + if (qh->interval) { + ntd_max = (dwc2_max_desc_num(qh) + qh->interval - 1) / + qh->interval; + if (skip_frames && !qh->channel) + ntd_max -= skip_frames / qh->interval; + } + + max_xfer_size = qh->dev_speed == USB_SPEED_HIGH ? + MAX_ISOC_XFER_SIZE_HS : MAX_ISOC_XFER_SIZE_FS; + + list_for_each_entry(qtd, &qh->qtd_list, qtd_list_entry) { + while (qh->ntd < ntd_max && qtd->isoc_frame_index_last < + qtd->urb->packet_count) { + if (n_desc > 1) + qh->desc_list[n_desc - 1].status |= HOST_DMA_A; + dwc2_fill_host_isoc_dma_desc(hsotg, qtd, qh, + max_xfer_size, idx); + idx = dwc2_desclist_idx_inc(idx, inc, qh->dev_speed); + n_desc++; + } + qtd->in_process = 1; + } + + qh->td_last = idx; + +#ifdef ISOC_URB_GIVEBACK_ASAP + /* Set IOC for last descriptor if descriptor list is full */ + if (qh->ntd == ntd_max) { + idx = dwc2_desclist_idx_dec(qh->td_last, inc, qh->dev_speed); + qh->desc_list[idx].status |= HOST_DMA_IOC; + } +#else + /* + * Set IOC bit only for one descriptor. Always try to be ahead of HW + * processing, i.e. on IOC generation driver activates next descriptor + * but core continues to process descriptors following the one with IOC + * set. + */ + + if (n_desc > DESCNUM_THRESHOLD) + /* + * Move IOC "up". Required even if there is only one QTD + * in the list, because QTDs might continue to be queued, + * but during the activation it was only one queued. + * Actually more than one QTD might be in the list if this + * function called from XferCompletion - QTDs was queued during + * HW processing of the previous descriptor chunk. + */ + idx = dwc2_desclist_idx_dec(idx, inc * ((qh->ntd + 1) / 2), + qh->dev_speed); + else + /* + * Set the IOC for the latest descriptor if either number of + * descriptors is not greater than threshold or no more new + * descriptors activated + */ + idx = dwc2_desclist_idx_dec(qh->td_last, inc, qh->dev_speed); + + qh->desc_list[idx].status |= HOST_DMA_IOC; +#endif + + if (n_desc) { + qh->desc_list[n_desc - 1].status |= HOST_DMA_A; + if (n_desc > 1) + qh->desc_list[0].status |= HOST_DMA_A; + } +} + +static void dwc2_fill_host_dma_desc(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, + struct dwc2_qtd *qtd, struct dwc2_qh *qh, + int n_desc) +{ + struct dwc2_hcd_dma_desc *dma_desc = &qh->desc_list[n_desc]; + int len = chan->xfer_len; + + if (len > MAX_DMA_DESC_SIZE) + len = MAX_DMA_DESC_SIZE - chan->max_packet + 1; + + if (chan->ep_is_in) { + int num_packets; + + if (len > 0 && chan->max_packet) + num_packets = (len + chan->max_packet - 1) + / chan->max_packet; + else + /* Need 1 packet for transfer length of 0 */ + num_packets = 1; + + /* Always program an integral # of packets for IN transfers */ + len = num_packets * chan->max_packet; + } + + dma_desc->status = len << HOST_DMA_NBYTES_SHIFT & HOST_DMA_NBYTES_MASK; + qh->n_bytes[n_desc] = len; + + if (qh->ep_type == USB_ENDPOINT_XFER_CONTROL && + qtd->control_phase == DWC2_CONTROL_SETUP) + dma_desc->status |= HOST_DMA_SUP; + + dma_desc->buf = (u32)chan->xfer_dma; + + /* + * Last (or only) descriptor of IN transfer with actual size less + * than MaxPacket + */ + if (len > chan->xfer_len) { + chan->xfer_len = 0; + } else { + chan->xfer_dma += len; + chan->xfer_len -= len; + } +} + +static void dwc2_init_non_isoc_dma_desc(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh) +{ + struct dwc2_qtd *qtd; + struct dwc2_host_chan *chan = qh->channel; + int n_desc = 0; + + dev_vdbg(hsotg->dev, "%s(): qh=%p dma=%08lx len=%d\n", __func__, qh, + (unsigned long)chan->xfer_dma, chan->xfer_len); + + /* + * Start with chan->xfer_dma initialized in assign_and_init_hc(), then + * if SG transfer consists of multiple URBs, this pointer is re-assigned + * to the buffer of the currently processed QTD. For non-SG request + * there is always one QTD active. + */ + + list_for_each_entry(qtd, &qh->qtd_list, qtd_list_entry) { + dev_vdbg(hsotg->dev, "qtd=%p\n", qtd); + + if (n_desc) { + /* SG request - more than 1 QTD */ + chan->xfer_dma = qtd->urb->dma + + qtd->urb->actual_length; + chan->xfer_len = qtd->urb->length - + qtd->urb->actual_length; + dev_vdbg(hsotg->dev, "buf=%08lx len=%d\n", + (unsigned long)chan->xfer_dma, chan->xfer_len); + } + + qtd->n_desc = 0; + do { + if (n_desc > 1) { + qh->desc_list[n_desc - 1].status |= HOST_DMA_A; + dev_vdbg(hsotg->dev, + "set A bit in desc %d (%p)\n", + n_desc - 1, + &qh->desc_list[n_desc - 1]); + } + dwc2_fill_host_dma_desc(hsotg, chan, qtd, qh, n_desc); + dev_vdbg(hsotg->dev, + "desc %d (%p) buf=%08x status=%08x\n", + n_desc, &qh->desc_list[n_desc], + qh->desc_list[n_desc].buf, + qh->desc_list[n_desc].status); + qtd->n_desc++; + n_desc++; + } while (chan->xfer_len > 0 && + n_desc != MAX_DMA_DESC_NUM_GENERIC); + + dev_vdbg(hsotg->dev, "n_desc=%d\n", n_desc); + qtd->in_process = 1; + if (qh->ep_type == USB_ENDPOINT_XFER_CONTROL) + break; + if (n_desc == MAX_DMA_DESC_NUM_GENERIC) + break; + } + + if (n_desc) { + qh->desc_list[n_desc - 1].status |= + HOST_DMA_IOC | HOST_DMA_EOL | HOST_DMA_A; + dev_vdbg(hsotg->dev, "set IOC/EOL/A bits in desc %d (%p)\n", + n_desc - 1, &qh->desc_list[n_desc - 1]); + if (n_desc > 1) { + qh->desc_list[0].status |= HOST_DMA_A; + dev_vdbg(hsotg->dev, "set A bit in desc 0 (%p)\n", + &qh->desc_list[0]); + } + chan->ntd = n_desc; + } +} + +/** + * dwc2_hcd_start_xfer_ddma() - Starts a transfer in Descriptor DMA mode + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @qh: The QH to init + * + * Return: 0 if successful, negative error code otherwise + * + * For Control and Bulk endpoints, initializes descriptor list and starts the + * transfer. For Interrupt and Isochronous endpoints, initializes descriptor + * list then updates FrameList, marking appropriate entries as active. + * + * For Isochronous endpoints the starting descriptor index is calculated based + * on the scheduled frame, but only on the first transfer descriptor within a + * session. Then the transfer is started via enabling the channel. + * + * For Isochronous endpoints the channel is not halted on XferComplete + * interrupt so remains assigned to the endpoint(QH) until session is done. + */ +void dwc2_hcd_start_xfer_ddma(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ + /* Channel is already assigned */ + struct dwc2_host_chan *chan = qh->channel; + u16 skip_frames = 0; + + switch (chan->ep_type) { + case USB_ENDPOINT_XFER_CONTROL: + case USB_ENDPOINT_XFER_BULK: + dwc2_init_non_isoc_dma_desc(hsotg, qh); + dwc2_hc_start_transfer_ddma(hsotg, chan); + break; + case USB_ENDPOINT_XFER_INT: + dwc2_init_non_isoc_dma_desc(hsotg, qh); + dwc2_update_frame_list(hsotg, qh, 1); + dwc2_hc_start_transfer_ddma(hsotg, chan); + break; + case USB_ENDPOINT_XFER_ISOC: + if (!qh->ntd) + skip_frames = dwc2_recalc_initial_desc_idx(hsotg, qh); + dwc2_init_isoc_dma_desc(hsotg, qh, skip_frames); + + if (!chan->xfer_started) { + dwc2_update_frame_list(hsotg, qh, 1); + + /* + * Always set to max, instead of actual size. Otherwise + * ntd will be changed with channel being enabled. Not + * recommended. + */ + chan->ntd = dwc2_max_desc_num(qh); + + /* Enable channel only once for ISOC */ + dwc2_hc_start_transfer_ddma(hsotg, chan); + } + + break; + default: + break; + } +} + +#define DWC2_CMPL_DONE 1 +#define DWC2_CMPL_STOP 2 + +static int dwc2_cmpl_host_isoc_dma_desc(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, + struct dwc2_qtd *qtd, + struct dwc2_qh *qh, u16 idx) +{ + struct dwc2_hcd_dma_desc *dma_desc = &qh->desc_list[idx]; + struct dwc2_hcd_iso_packet_desc *frame_desc; + u16 remain = 0; + int rc = 0; + + frame_desc = &qtd->urb->iso_descs[qtd->isoc_frame_index_last]; + dma_desc->buf = (u32)(qtd->urb->dma + frame_desc->offset); + if (chan->ep_is_in) + remain = dma_desc->status >> HOST_DMA_ISOC_NBYTES_SHIFT & + HOST_DMA_ISOC_NBYTES_MASK >> HOST_DMA_ISOC_NBYTES_SHIFT; + + if ((dma_desc->status & HOST_DMA_STS_MASK) == HOST_DMA_STS_PKTERR) { + /* + * XactError, or unable to complete all the transactions + * in the scheduled micro-frame/frame, both indicated by + * HOST_DMA_STS_PKTERR + */ + qtd->urb->error_count++; + frame_desc->actual_length = qh->n_bytes[idx] - remain; + frame_desc->status = -EPROTO; + } else { + /* Success */ + frame_desc->actual_length = qh->n_bytes[idx] - remain; + frame_desc->status = 0; + } + + if (++qtd->isoc_frame_index == qtd->urb->packet_count) { + /* + * urb->status is not used for isoc transfers here. The + * individual frame_desc status are used instead. + */ + dwc2_host_complete(hsotg, qtd->urb->priv, qtd->urb, 0); + dwc2_hcd_qtd_unlink_and_free(hsotg, qtd, qh); + + /* + * This check is necessary because urb_dequeue can be called + * from urb complete callback (sound driver for example). All + * pending URBs are dequeued there, so no need for further + * processing. + */ + if (chan->halt_status == DWC2_HC_XFER_URB_DEQUEUE) + return -1; + rc = DWC2_CMPL_DONE; + } + + qh->ntd--; + + /* Stop if IOC requested descriptor reached */ + if (dma_desc->status & HOST_DMA_IOC) + rc = DWC2_CMPL_STOP; + + return rc; +} + +static void dwc2_complete_isoc_xfer_ddma(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, + enum dwc2_halt_status halt_status) +{ + struct dwc2_hcd_iso_packet_desc *frame_desc; + struct dwc2_qtd *qtd, *qtd_tmp; + struct dwc2_qh *qh; + u16 idx; + int rc; + + qh = chan->qh; + idx = qh->td_first; + + if (chan->halt_status == DWC2_HC_XFER_URB_DEQUEUE) { + list_for_each_entry(qtd, &qh->qtd_list, qtd_list_entry) + qtd->in_process = 0; + return; + } + + if (halt_status == DWC2_HC_XFER_AHB_ERR || + halt_status == DWC2_HC_XFER_BABBLE_ERR) { + /* + * Channel is halted in these error cases, considered as serious + * issues. + * Complete all URBs marking all frames as failed, irrespective + * whether some of the descriptors (frames) succeeded or not. + * Pass error code to completion routine as well, to update + * urb->status, some of class drivers might use it to stop + * queing transfer requests. + */ + int err = halt_status == DWC2_HC_XFER_AHB_ERR ? + -EIO : -EOVERFLOW; + + list_for_each_entry_safe(qtd, qtd_tmp, &qh->qtd_list, + qtd_list_entry) { + for (idx = 0; idx < qtd->urb->packet_count; idx++) { + frame_desc = &qtd->urb->iso_descs[idx]; + frame_desc->status = err; + } + + dwc2_host_complete(hsotg, qtd->urb->priv, qtd->urb, + err); + dwc2_hcd_qtd_unlink_and_free(hsotg, qtd, qh); + } + + return; + } + + list_for_each_entry_safe(qtd, qtd_tmp, &qh->qtd_list, qtd_list_entry) { + if (!qtd->in_process) + break; + do { + rc = dwc2_cmpl_host_isoc_dma_desc(hsotg, chan, qtd, qh, + idx); + if (rc < 0) + return; + idx = dwc2_desclist_idx_inc(idx, qh->interval, + chan->speed); + if (rc == DWC2_CMPL_STOP) + goto stop_scan; + if (rc == DWC2_CMPL_DONE) + break; + } while (idx != qh->td_first); + } + +stop_scan: + qh->td_first = idx; +} + +static int dwc2_update_non_isoc_urb_state_ddma(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, + struct dwc2_qtd *qtd, + struct dwc2_hcd_dma_desc *dma_desc, + enum dwc2_halt_status halt_status, + u32 n_bytes, int *xfer_done) +{ + struct dwc2_hcd_urb *urb = qtd->urb; + u16 remain = 0; + + if (chan->ep_is_in) + remain = dma_desc->status >> HOST_DMA_NBYTES_SHIFT & + HOST_DMA_NBYTES_MASK >> HOST_DMA_NBYTES_SHIFT; + + dev_vdbg(hsotg->dev, "remain=%d dwc2_urb=%p\n", remain, urb); + + if (halt_status == DWC2_HC_XFER_AHB_ERR) { + dev_err(hsotg->dev, "EIO\n"); + urb->status = -EIO; + return 1; + } + + if ((dma_desc->status & HOST_DMA_STS_MASK) == HOST_DMA_STS_PKTERR) { + switch (halt_status) { + case DWC2_HC_XFER_STALL: + dev_vdbg(hsotg->dev, "Stall\n"); + urb->status = -EPIPE; + break; + case DWC2_HC_XFER_BABBLE_ERR: + dev_err(hsotg->dev, "Babble\n"); + urb->status = -EOVERFLOW; + break; + case DWC2_HC_XFER_XACT_ERR: + dev_err(hsotg->dev, "XactErr\n"); + urb->status = -EPROTO; + break; + default: + dev_err(hsotg->dev, + "%s: Unhandled descriptor error status (%d)\n", + __func__, halt_status); + break; + } + return 1; + } + + if (dma_desc->status & HOST_DMA_A) { + dev_vdbg(hsotg->dev, + "Active descriptor encountered on channel %d\n", + chan->hc_num); + return 0; + } + + if (chan->ep_type == USB_ENDPOINT_XFER_CONTROL) { + if (qtd->control_phase == DWC2_CONTROL_DATA) { + urb->actual_length += n_bytes - remain; + if (remain || urb->actual_length >= urb->length) { + /* + * For Control Data stage do not set urb->status + * to 0, to prevent URB callback. Set it when + * Status phase is done. See below. + */ + *xfer_done = 1; + } + } else if (qtd->control_phase == DWC2_CONTROL_STATUS) { + urb->status = 0; + *xfer_done = 1; + } + /* No handling for SETUP stage */ + } else { + /* BULK and INTR */ + urb->actual_length += n_bytes - remain; + dev_vdbg(hsotg->dev, "length=%d actual=%d\n", urb->length, + urb->actual_length); + if (remain || urb->actual_length >= urb->length) { + urb->status = 0; + *xfer_done = 1; + } + } + + return 0; +} + +static int dwc2_process_non_isoc_desc(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, + int chnum, struct dwc2_qtd *qtd, + int desc_num, + enum dwc2_halt_status halt_status, + int *xfer_done) +{ + struct dwc2_qh *qh = chan->qh; + struct dwc2_hcd_urb *urb = qtd->urb; + struct dwc2_hcd_dma_desc *dma_desc; + u32 n_bytes; + int failed; + + dev_vdbg(hsotg->dev, "%s()\n", __func__); + + dma_desc = &qh->desc_list[desc_num]; + n_bytes = qh->n_bytes[desc_num]; + dev_vdbg(hsotg->dev, + "qtd=%p dwc2_urb=%p desc_num=%d desc=%p n_bytes=%d\n", + qtd, urb, desc_num, dma_desc, n_bytes); + failed = dwc2_update_non_isoc_urb_state_ddma(hsotg, chan, qtd, dma_desc, + halt_status, n_bytes, + xfer_done); + if (failed || (*xfer_done && urb->status != -EINPROGRESS)) { + dwc2_host_complete(hsotg, urb->priv, urb, urb->status); + dwc2_hcd_qtd_unlink_and_free(hsotg, qtd, qh); + dev_vdbg(hsotg->dev, "failed=%1x xfer_done=%1x status=%08x\n", + failed, *xfer_done, urb->status); + return failed; + } + + if (qh->ep_type == USB_ENDPOINT_XFER_CONTROL) { + switch (qtd->control_phase) { + case DWC2_CONTROL_SETUP: + if (urb->length > 0) + qtd->control_phase = DWC2_CONTROL_DATA; + else + qtd->control_phase = DWC2_CONTROL_STATUS; + dev_vdbg(hsotg->dev, + " Control setup transaction done\n"); + break; + case DWC2_CONTROL_DATA: + if (*xfer_done) { + qtd->control_phase = DWC2_CONTROL_STATUS; + dev_vdbg(hsotg->dev, + " Control data transfer done\n"); + } else if (desc_num + 1 == qtd->n_desc) { + /* + * Last descriptor for Control data stage which + * is not completed yet + */ + dwc2_hcd_save_data_toggle(hsotg, chan, chnum, + qtd); + } + break; + default: + break; + } + } + + return 0; +} + +static void dwc2_complete_non_isoc_xfer_ddma(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, + int chnum, + enum dwc2_halt_status halt_status) +{ + struct list_head *qtd_item, *qtd_tmp; + struct dwc2_qh *qh = chan->qh; + struct dwc2_qtd *qtd = NULL; + int xfer_done; + int desc_num = 0; + + if (chan->halt_status == DWC2_HC_XFER_URB_DEQUEUE) { + list_for_each_entry(qtd, &qh->qtd_list, qtd_list_entry) + qtd->in_process = 0; + return; + } + + list_for_each_safe(qtd_item, qtd_tmp, &qh->qtd_list) { + int i; + + qtd = list_entry(qtd_item, struct dwc2_qtd, qtd_list_entry); + xfer_done = 0; + + for (i = 0; i < qtd->n_desc; i++) { + if (dwc2_process_non_isoc_desc(hsotg, chan, chnum, qtd, + desc_num, halt_status, + &xfer_done)) + break; + desc_num++; + } + } + + if (qh->ep_type != USB_ENDPOINT_XFER_CONTROL) { + /* + * Resetting the data toggle for bulk and interrupt endpoints + * in case of stall. See handle_hc_stall_intr(). + */ + if (halt_status == DWC2_HC_XFER_STALL) + qh->data_toggle = DWC2_HC_PID_DATA0; + else if (qtd) + dwc2_hcd_save_data_toggle(hsotg, chan, chnum, qtd); + } + + if (halt_status == DWC2_HC_XFER_COMPLETE) { + if (chan->hcint & HCINTMSK_NYET) { + /* + * Got a NYET on the last transaction of the transfer. + * It means that the endpoint should be in the PING + * state at the beginning of the next transfer. + */ + qh->ping_state = 1; + } + } +} + +/** + * dwc2_hcd_complete_xfer_ddma() - Scans the descriptor list, updates URB's + * status and calls completion routine for the URB if it's done. Called from + * interrupt handlers. + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @chan: Host channel the transfer is completed on + * @chnum: Index of Host channel registers + * @halt_status: Reason the channel is being halted or just XferComplete + * for isochronous transfers + * + * Releases the channel to be used by other transfers. + * In case of Isochronous endpoint the channel is not halted until the end of + * the session, i.e. QTD list is empty. + * If periodic channel released the FrameList is updated accordingly. + * Calls transaction selection routines to activate pending transfers. + */ +void dwc2_hcd_complete_xfer_ddma(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, int chnum, + enum dwc2_halt_status halt_status) +{ + struct dwc2_qh *qh = chan->qh; + int continue_isoc_xfer = 0; + enum dwc2_transaction_type tr_type; + + if (chan->ep_type == USB_ENDPOINT_XFER_ISOC) { + dwc2_complete_isoc_xfer_ddma(hsotg, chan, halt_status); + + /* Release the channel if halted or session completed */ + if (halt_status != DWC2_HC_XFER_COMPLETE || + list_empty(&qh->qtd_list)) { + /* Halt the channel if session completed */ + if (halt_status == DWC2_HC_XFER_COMPLETE) + dwc2_hc_halt(hsotg, chan, halt_status); + dwc2_release_channel_ddma(hsotg, qh); + dwc2_hcd_qh_unlink(hsotg, qh); + } else { + /* Keep in assigned schedule to continue transfer */ + list_move(&qh->qh_list_entry, + &hsotg->periodic_sched_assigned); + continue_isoc_xfer = 1; + } + /* + * Todo: Consider the case when period exceeds FrameList size. + * Frame Rollover interrupt should be used. + */ + } else { + /* + * Scan descriptor list to complete the URB(s), then release + * the channel + */ + dwc2_complete_non_isoc_xfer_ddma(hsotg, chan, chnum, + halt_status); + dwc2_release_channel_ddma(hsotg, qh); + dwc2_hcd_qh_unlink(hsotg, qh); + + if (!list_empty(&qh->qtd_list)) { + /* + * Add back to inactive non-periodic schedule on normal + * completion + */ + dwc2_hcd_qh_add(hsotg, qh); + } + } + + tr_type = dwc2_hcd_select_transactions(hsotg); + if (tr_type != DWC2_TRANSACTION_NONE || continue_isoc_xfer) { + if (continue_isoc_xfer) { + if (tr_type == DWC2_TRANSACTION_NONE) + tr_type = DWC2_TRANSACTION_PERIODIC; + else if (tr_type == DWC2_TRANSACTION_NON_PERIODIC) + tr_type = DWC2_TRANSACTION_ALL; + } + dwc2_hcd_queue_transactions(hsotg, tr_type); + } +} |