summaryrefslogtreecommitdiff
path: root/target/linux/cns3xxx/files/drivers/usb/dwc/otg_hcd.c
diff options
context:
space:
mode:
Diffstat (limited to 'target/linux/cns3xxx/files/drivers/usb/dwc/otg_hcd.c')
-rw-r--r--target/linux/cns3xxx/files/drivers/usb/dwc/otg_hcd.c250
1 files changed, 199 insertions, 51 deletions
diff --git a/target/linux/cns3xxx/files/drivers/usb/dwc/otg_hcd.c b/target/linux/cns3xxx/files/drivers/usb/dwc/otg_hcd.c
index e51b8c8..5f33fa5 100644
--- a/target/linux/cns3xxx/files/drivers/usb/dwc/otg_hcd.c
+++ b/target/linux/cns3xxx/files/drivers/usb/dwc/otg_hcd.c
@@ -176,8 +176,10 @@ static void kill_urbs_in_qh_list(dwc_otg_hcd_t *hcd, struct list_head *qh_list)
qtd_item = qh->qtd_list.next) {
qtd = list_entry(qtd_item, dwc_otg_qtd_t, qtd_list_entry);
if (qtd->urb != NULL) {
+ SPIN_UNLOCK_IRQRESTORE(&hcd->lock, flags);
dwc_otg_hcd_complete_urb(hcd, qtd->urb,
-ETIMEDOUT);
+ SPIN_LOCK_IRQSAVE(&hcd->lock, flags);
}
dwc_otg_hcd_qtd_remove_and_free(hcd, qtd);
}
@@ -589,6 +591,7 @@ static void hcd_reinit(dwc_otg_hcd_t *hcd)
hcd->non_periodic_qh_ptr = &hcd->non_periodic_sched_active;
hcd->non_periodic_channels = 0;
hcd->periodic_channels = 0;
+ hcd->nakking_channels = 0;
/*
* Put all channels in the free channel list and clean up channel
@@ -853,10 +856,10 @@ static void dump_channel_info(dwc_otg_hcd_t *hcd,
//OTG host require the DMA addr is DWORD-aligned,
//patch it if the buffer is not DWORD-aligned
inline
-void hcd_check_and_patch_dma_addr(struct urb *urb){
+int hcd_check_and_patch_dma_addr(struct urb *urb){
if((!urb->transfer_buffer)||!urb->transfer_dma||urb->transfer_dma==0xffffffff)
- return;
+ return 0;
if(((u32)urb->transfer_buffer)& 0x3){
/*
@@ -881,11 +884,12 @@ void hcd_check_and_patch_dma_addr(struct urb *urb){
kfree(urb->aligned_transfer_buffer);
}
urb->aligned_transfer_buffer=kmalloc(urb->aligned_transfer_buffer_length,GFP_KERNEL|GFP_DMA|GFP_ATOMIC);
- urb->aligned_transfer_dma=dma_map_single(NULL,(void *)(urb->aligned_transfer_buffer),(urb->aligned_transfer_buffer_length),DMA_FROM_DEVICE);
if(!urb->aligned_transfer_buffer){
DWC_ERROR("Cannot alloc required buffer!!\n");
- BUG();
+ //BUG();
+ return -1;
}
+ urb->aligned_transfer_dma=dma_map_single(NULL,(void *)(urb->aligned_transfer_buffer),(urb->aligned_transfer_buffer_length),DMA_FROM_DEVICE);
//printk(" new allocated aligned_buf=%.8x aligned_buf_len=%d\n", (u32)urb->aligned_transfer_buffer, urb->aligned_transfer_buffer_length);
}
urb->transfer_dma=urb->aligned_transfer_dma;
@@ -894,6 +898,7 @@ void hcd_check_and_patch_dma_addr(struct urb *urb){
dma_sync_single_for_device(NULL,urb->transfer_dma,urb->transfer_buffer_length,DMA_TO_DEVICE);
}
}
+ return 0;
}
@@ -910,7 +915,15 @@ int dwc_otg_hcd_urb_enqueue(struct usb_hcd *hcd,
int retval = 0;
dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd);
dwc_otg_qtd_t *qtd;
+ unsigned long flags;
+
+ SPIN_LOCK_IRQSAVE(&dwc_otg_hcd->lock, flags);
+
+ if (urb->hcpriv != NULL) {
+ SPIN_UNLOCK_IRQRESTORE(&dwc_otg_hcd->lock, flags);
+ return -ENOMEM;
+ }
#ifdef DEBUG
if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) {
dump_urb_info(urb, "dwc_otg_hcd_urb_enqueue");
@@ -918,13 +931,19 @@ int dwc_otg_hcd_urb_enqueue(struct usb_hcd *hcd,
#endif
if (!dwc_otg_hcd->flags.b.port_connect_status) {
/* No longer connected. */
+ SPIN_UNLOCK_IRQRESTORE(&dwc_otg_hcd->lock, flags);
return -ENODEV;
}
- hcd_check_and_patch_dma_addr(urb);
+ if (hcd_check_and_patch_dma_addr(urb)) {
+ DWC_ERROR("Unable to check and patch dma addr\n");
+ SPIN_UNLOCK_IRQRESTORE(&dwc_otg_hcd->lock, flags);
+ return -ENOMEM;
+ }
qtd = dwc_otg_hcd_qtd_create(urb);
if (qtd == NULL) {
DWC_ERROR("DWC OTG HCD URB Enqueue failed creating QTD\n");
+ SPIN_UNLOCK_IRQRESTORE(&dwc_otg_hcd->lock, flags);
return -ENOMEM;
}
@@ -934,7 +953,7 @@ int dwc_otg_hcd_urb_enqueue(struct usb_hcd *hcd,
"Error status %d\n", retval);
dwc_otg_hcd_qtd_free(qtd);
}
-
+ SPIN_UNLOCK_IRQRESTORE(&dwc_otg_hcd->lock, flags);
return retval;
}
@@ -948,6 +967,7 @@ int dwc_otg_hcd_urb_dequeue(struct usb_hcd *hcd,
dwc_otg_qtd_t *urb_qtd;
dwc_otg_qh_t *qh;
struct usb_host_endpoint *ep = dwc_urb_to_endpoint(urb);
+ int rc;
DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD URB Dequeue\n");
@@ -958,10 +978,6 @@ int dwc_otg_hcd_urb_dequeue(struct usb_hcd *hcd,
urb_qtd = (dwc_otg_qtd_t *)urb->hcpriv;
qh = (dwc_otg_qh_t *)ep->hcpriv;
- if (urb_qtd == NULL) {
- SPIN_UNLOCK_IRQRESTORE(&dwc_otg_hcd->lock, flags);
- return 0;
- }
#ifdef DEBUG
if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) {
dump_urb_info(urb, "dwc_otg_hcd_urb_dequeue");
@@ -971,7 +987,7 @@ int dwc_otg_hcd_urb_dequeue(struct usb_hcd *hcd,
}
#endif
- if (urb_qtd == qh->qtd_in_process) {
+ if (qh && urb_qtd == qh->qtd_in_process) {
/* The QTD is in process (it has been assigned to a channel). */
if (dwc_otg_hcd->flags.b.port_connect_status) {
@@ -982,7 +998,7 @@ int dwc_otg_hcd_urb_dequeue(struct usb_hcd *hcd,
* written to halt the channel since the core is in
* device mode.
*/
- dwc_otg_hc_halt(dwc_otg_hcd->core_if, qh->channel,
+ dwc_otg_hc_halt(dwc_otg_hcd, qh->channel,
DWC_OTG_HC_XFER_URB_DEQUEUE);
}
}
@@ -992,22 +1008,28 @@ int dwc_otg_hcd_urb_dequeue(struct usb_hcd *hcd,
* schedule if it has any remaining QTDs.
*/
dwc_otg_hcd_qtd_remove_and_free(dwc_otg_hcd, urb_qtd);
- if (urb_qtd == qh->qtd_in_process) {
- /* Note that dwc_otg_hcd_qh_deactivate() locks the spin_lock again */
- SPIN_UNLOCK_IRQRESTORE(&dwc_otg_hcd->lock, flags);
+ if (qh && urb_qtd == qh->qtd_in_process) {
dwc_otg_hcd_qh_deactivate(dwc_otg_hcd, qh, 0);
qh->channel = NULL;
qh->qtd_in_process = NULL;
} else {
- if (list_empty(&qh->qtd_list))
+ if (qh && list_empty(&qh->qtd_list)) {
dwc_otg_hcd_qh_remove(dwc_otg_hcd, qh);
- SPIN_UNLOCK_IRQRESTORE(&dwc_otg_hcd->lock, flags);
+ }
}
+
+ rc = usb_hcd_check_unlink_urb(hcd, urb, status);
+
+ if (!rc) {
+ usb_hcd_unlink_urb_from_ep(hcd, urb);
+ }
urb->hcpriv = NULL;
+ SPIN_UNLOCK_IRQRESTORE(&dwc_otg_hcd->lock, flags);
- /* Higher layer software sets URB status. */
- usb_hcd_giveback_urb(hcd, urb, status);
+ if (!rc) {
+ usb_hcd_giveback_urb(hcd, urb, status);
+ }
if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) {
DWC_PRINT("Called usb_hcd_giveback_urb()\n");
DWC_PRINT(" urb->status = %d\n", urb->status);
@@ -2035,15 +2057,19 @@ static void assign_and_init_hc(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh)
dwc_otg_qtd_t *qtd;
struct urb *urb;
- DWC_DEBUGPL(DBG_HCDV, "%s(%p,%p)\n", __func__, hcd, qh);
-
+ DWC_DEBUGPL(DBG_HCD_FLOOD, "%s(%p,%p)\n", __func__, hcd, qh);
hc = list_entry(hcd->free_hc_list.next, dwc_hc_t, hc_list_entry);
+ qtd = list_entry(qh->qtd_list.next, dwc_otg_qtd_t, qtd_list_entry);
+ urb = qtd->urb;
+
+ if (!urb){
+ return;
+ }
+
/* Remove the host channel from the free list. */
list_del_init(&hc->hc_list_entry);
- qtd = list_entry(qh->qtd_list.next, dwc_otg_qtd_t, qtd_list_entry);
- urb = qtd->urb;
qh->channel = hc;
qh->qtd_in_process = qtd;
@@ -2202,16 +2228,24 @@ static void assign_and_init_hc(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh)
dwc_otg_transaction_type_e dwc_otg_hcd_select_transactions(dwc_otg_hcd_t *hcd)
{
struct list_head *qh_ptr;
- dwc_otg_qh_t *qh;
+ dwc_otg_qh_t *qh = NULL;
int num_channels;
dwc_otg_transaction_type_e ret_val = DWC_OTG_TRANSACTION_NONE;
+ uint16_t cur_frame = dwc_otg_hcd_get_frame_number(dwc_otg_hcd_to_hcd(hcd));
+ unsigned long flags;
+ int include_nakd, channels_full;
+ /* This condition has once been observed, but the cause was
+ * never determined. Check for it here, to collect debug data if
+ * it occurs again. */
+ WARN_ON_ONCE(hcd->non_periodic_channels < 0);
+ check_nakking(hcd, __FUNCTION__, "start");
#ifdef DEBUG_SOF
DWC_DEBUGPL(DBG_HCD, " Select Transactions\n");
#endif
- spin_lock(&hcd->lock);
- /* Process entries in the periodic ready list. */
+ SPIN_LOCK_IRQSAVE(&hcd->lock, flags);
+ /* Process entries in the periodic ready list. */
qh_ptr = hcd->periodic_sched_ready.next;
while (qh_ptr != &hcd->periodic_sched_ready &&
!list_empty(&hcd->free_hc_list)) {
@@ -2234,37 +2268,140 @@ dwc_otg_transaction_type_e dwc_otg_hcd_select_transactions(dwc_otg_hcd_t *hcd)
* schedule. Some free host channels may not be used if they are
* reserved for periodic transfers.
*/
- qh_ptr = hcd->non_periodic_sched_inactive.next;
num_channels = hcd->core_if->core_params->host_channels;
- while (qh_ptr != &hcd->non_periodic_sched_inactive &&
- (hcd->non_periodic_channels <
- num_channels - hcd->periodic_channels) &&
- !list_empty(&hcd->free_hc_list)) {
- qh = list_entry(qh_ptr, dwc_otg_qh_t, qh_list_entry);
- assign_and_init_hc(hcd, qh);
+ /* Go over the queue twice: Once while not including nak'd
+ * entries, one while including them. This is so a retransmit of
+ * an entry that has received a nak is scheduled only after all
+ * new entries.
+ */
+ channels_full = 0;
+ for (include_nakd = 0; include_nakd < 2 && !channels_full; ++include_nakd) {
+ qh_ptr = hcd->non_periodic_sched_inactive.next;
+ while (qh_ptr != &hcd->non_periodic_sched_inactive) {
+ qh = list_entry(qh_ptr, dwc_otg_qh_t, qh_list_entry);
+ qh_ptr = qh_ptr->next;
- /*
- * Move the QH from the non-periodic inactive schedule to the
- * non-periodic active schedule.
- */
- qh_ptr = qh_ptr->next;
- list_move(&qh->qh_list_entry, &hcd->non_periodic_sched_active);
+ /* If a nak'd frame is in the queue for 100ms, forget
+ * about its nak status, to prevent the situation where
+ * a nak'd frame never gets resubmitted because there
+ * are continously non-nakking tranfsfers available.
+ */
+ if (qh->nak_frame != 0xffff &&
+ dwc_frame_num_gt(cur_frame, qh->nak_frame + 800))
+ qh->nak_frame = 0xffff;
- if (ret_val == DWC_OTG_TRANSACTION_NONE) {
- ret_val = DWC_OTG_TRANSACTION_NON_PERIODIC;
- } else {
- ret_val = DWC_OTG_TRANSACTION_ALL;
+ /* In the first pass, ignore NAK'd retransmit
+ * alltogether, to give them lower priority. */
+ if (!include_nakd && qh->nak_frame != 0xffff)
+ continue;
+
+ /*
+ * Check to see if this is a NAK'd retransmit, in which case ignore for retransmission
+ * we hold off on bulk retransmissions to reduce NAK interrupt overhead for
+ * cheeky devices that just hold off using NAKs
+ */
+ if (dwc_full_frame_num(qh->nak_frame) == dwc_full_frame_num(dwc_otg_hcd_get_frame_number(dwc_otg_hcd_to_hcd(hcd))))
+ continue;
+
+ /* Ok, we found a candidate for scheduling. Is there a
+ * free channel? */
+ if (hcd->non_periodic_channels >=
+ num_channels - hcd->periodic_channels ||
+ list_empty(&hcd->free_hc_list)) {
+ channels_full = 1;
+ break;
+ }
+
+ /* When retrying a NAK'd transfer, we give it a fair
+ * chance of completing again. */
+ qh->nak_frame = 0xffff;
+ assign_and_init_hc(hcd, qh);
+
+ /*
+ * Move the QH from the non-periodic inactive schedule to the
+ * non-periodic active schedule.
+ */
+ list_move(&qh->qh_list_entry, &hcd->non_periodic_sched_active);
+
+ if (ret_val == DWC_OTG_TRANSACTION_NONE) {
+ ret_val = DWC_OTG_TRANSACTION_NON_PERIODIC;
+ } else {
+ ret_val = DWC_OTG_TRANSACTION_ALL;
+ }
+
+ hcd->non_periodic_channels++;
}
+ if (hcd->core_if->dma_enable && channels_full &&
+ hcd->periodic_channels + hcd->nakking_channels >= num_channels) {
+ /* There are items queued, but all channels are either
+ * reserved for periodic or have received NAKs. This
+ * means that it could take an indefinite amount of time
+ * before a channel is actually freed (since in DMA
+ * mode, the hardware takes care of retries), so we take
+ * action here by forcing a nakking channel to halt to
+ * give other transfers a chance to run. */
+ dwc_otg_qtd_t *qtd = list_entry(qh->qtd_list.next, dwc_otg_qtd_t, qtd_list_entry);
+ struct urb *urb = qtd->urb;
+ dwc_hc_t *hc = dwc_otg_halt_nakking_channel(hcd);
+
+ if (hc)
+ DWC_DEBUGPL(DBG_HCD "Out of Host Channels for non-periodic transfer - Halting channel %d (dev %d ep%d%s) to service qh %p (dev %d ep%d%s)\n", hc->hc_num, hc->dev_addr, hc->ep_num, (hc->ep_is_in ? "in" : "out"), qh, usb_pipedevice(urb->pipe), usb_pipeendpoint(urb->pipe), (usb_pipein(urb->pipe) != 0) ? "in" : "out");
- hcd->non_periodic_channels++;
+ }
}
- spin_unlock(&hcd->lock);
+
+ SPIN_UNLOCK_IRQRESTORE(&hcd->lock, flags);
return ret_val;
}
/**
+ * Halt a bulk channel that is blocking on NAKs to free up space.
+ *
+ * This will decrement hcd->nakking_channels immediately, but
+ * hcd->non_periodic_channels is not decremented until the channel is
+ * actually halted.
+ *
+ * Returns the halted channel.
+ */
+dwc_hc_t *dwc_otg_halt_nakking_channel(dwc_otg_hcd_t *hcd) {
+ int num_channels, i;
+ uint16_t cur_frame;
+
+ cur_frame = dwc_otg_hcd_get_frame_number(dwc_otg_hcd_to_hcd(hcd));
+ num_channels = hcd->core_if->core_params->host_channels;
+
+ for (i = 0; i < num_channels; i++) {
+ int channel = (hcd->last_channel_halted + 1 + i) % num_channels;
+ dwc_hc_t *hc = hcd->hc_ptr_array[channel];
+ if (hc->xfer_started
+ && !hc->halt_on_queue
+ && !hc->halt_pending
+ && hc->qh->nak_frame != 0xffff) {
+ dwc_otg_hc_halt(hcd, hc, DWC_OTG_HC_XFER_NAK);
+ /* Store the last channel halted to
+ * fairly rotate the channel to halt.
+ * This prevent the scenario where there
+ * are three blocking endpoints and only
+ * two free host channels, where the
+ * blocking endpoint that gets hc 3 will
+ * never be halted, while the other two
+ * endpoints will be fighting over the
+ * other host channel. */
+ hcd->last_channel_halted = channel;
+ /* Update nak_frame, so this frame is
+ * kept at low priority for a period of
+ * time starting now. */
+ hc->qh->nak_frame = cur_frame;
+ return hc;
+ }
+ }
+ dwc_otg_hcd_dump_state(hcd);
+ return NULL;
+}
+
+/**
* Attempts to queue a single transaction request for a host channel
* associated with either a periodic or non-periodic transfer. This function
* assumes that there is space available in the appropriate request queue. For
@@ -2298,7 +2435,7 @@ static int queue_transaction(dwc_otg_hcd_t *hcd,
/* Don't queue a request if the channel has been halted. */
retval = 0;
} else if (hc->halt_on_queue) {
- dwc_otg_hc_halt(hcd->core_if, hc, hc->halt_status);
+ dwc_otg_hc_halt(hcd, hc, hc->halt_status);
retval = 0;
} else if (hc->do_ping) {
if (!hc->xfer_started) {
@@ -2446,12 +2583,12 @@ static void process_periodic_channels(dwc_otg_hcd_t *hcd)
dwc_otg_host_global_regs_t *host_regs;
host_regs = hcd->core_if->host_if->host_global_regs;
- DWC_DEBUGPL(DBG_HCDV, "Queue periodic transactions\n");
+ DWC_DEBUGPL(DBG_HCD_FLOOD, "Queue periodic transactions\n");
#ifdef DEBUG
tx_status.d32 = dwc_read_reg32(&host_regs->hptxsts);
- DWC_DEBUGPL(DBG_HCDV, " P Tx Req Queue Space Avail (before queue): %d\n",
+ DWC_DEBUGPL(DBG_HCD_FLOOD, " P Tx Req Queue Space Avail (before queue): %d\n",
tx_status.b.ptxqspcavail);
- DWC_DEBUGPL(DBG_HCDV, " P Tx FIFO Space Avail (before queue): %d\n",
+ DWC_DEBUGPL(DBG_HCD_FLOOD, " P Tx FIFO Space Avail (before queue): %d\n",
tx_status.b.ptxfspcavail);
#endif
@@ -2586,7 +2723,12 @@ void dwc_otg_hcd_queue_transactions(dwc_otg_hcd_t *hcd,
*/
void dwc_otg_hcd_complete_urb(dwc_otg_hcd_t *hcd, struct urb *urb, int status)
{
+ unsigned long flags;
+
+ SPIN_LOCK_IRQSAVE(&hcd->lock, flags);
+
#ifdef DEBUG
+
if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) {
DWC_PRINT("%s: urb %p, device %d, ep %d %s, status=%d\n",
__func__, urb, usb_pipedevice(urb->pipe),
@@ -2609,10 +2751,12 @@ void dwc_otg_hcd_complete_urb(dwc_otg_hcd_t *hcd, struct urb *urb, int status)
memcpy(urb->transfer_buffer,urb->aligned_transfer_buffer,urb->actual_length);
}
-
+ usb_hcd_unlink_urb_from_ep(dwc_otg_hcd_to_hcd(hcd), urb);
urb->status = status;
urb->hcpriv = NULL;
+ SPIN_UNLOCK_IRQRESTORE(&hcd->lock, flags);
usb_hcd_giveback_urb(dwc_otg_hcd_to_hcd(hcd), urb, status);
+
}
/*
@@ -2674,7 +2818,7 @@ void dwc_otg_hcd_dump_state(dwc_otg_hcd_t *hcd)
DWC_PRINT(" Num channels: %d\n", num_channels);
for (i = 0; i < num_channels; i++) {
dwc_hc_t *hc = hcd->hc_ptr_array[i];
- DWC_PRINT(" Channel %d:\n", i);
+ DWC_PRINT(" Channel %d: %p\n", i, hc);
DWC_PRINT(" dev_addr: %d, ep_num: %d, ep_is_in: %d\n",
hc->dev_addr, hc->ep_num, hc->ep_is_in);
DWC_PRINT(" speed: %d\n", hc->speed);
@@ -2696,6 +2840,8 @@ void dwc_otg_hcd_dump_state(dwc_otg_hcd_t *hcd)
DWC_PRINT(" xact_pos: %d\n", hc->xact_pos);
DWC_PRINT(" requests: %d\n", hc->requests);
DWC_PRINT(" qh: %p\n", hc->qh);
+ if (hc->qh)
+ DWC_PRINT(" nak_frame: %x\n", hc->qh->nak_frame);
if (hc->xfer_started) {
hfnum_data_t hfnum;
hcchar_data_t hcchar;
@@ -2735,6 +2881,8 @@ void dwc_otg_hcd_dump_state(dwc_otg_hcd_t *hcd)
}
DWC_PRINT(" non_periodic_channels: %d\n", hcd->non_periodic_channels);
DWC_PRINT(" periodic_channels: %d\n", hcd->periodic_channels);
+ DWC_PRINT(" nakking_channels: %d\n", hcd->nakking_channels);
+ DWC_PRINT(" last_channel_halted: %d\n", hcd->last_channel_halted);
DWC_PRINT(" periodic_usecs: %d\n", hcd->periodic_usecs);
np_tx_status.d32 = dwc_read_reg32(&hcd->core_if->core_global_regs->gnptxsts);
DWC_PRINT(" NP Tx Req Queue Space Avail: %d\n", np_tx_status.b.nptxqspcavail);