diff options
Diffstat (limited to 'target/linux/ramips/files-3.7/drivers/usb/dwc_otg/dwc_otg_hcd_intr.c')
-rw-r--r-- | target/linux/ramips/files-3.7/drivers/usb/dwc_otg/dwc_otg_hcd_intr.c | 1873 |
1 files changed, 0 insertions, 1873 deletions
diff --git a/target/linux/ramips/files-3.7/drivers/usb/dwc_otg/dwc_otg_hcd_intr.c b/target/linux/ramips/files-3.7/drivers/usb/dwc_otg/dwc_otg_hcd_intr.c deleted file mode 100644 index bdf2db99f3..0000000000 --- a/target/linux/ramips/files-3.7/drivers/usb/dwc_otg/dwc_otg_hcd_intr.c +++ /dev/null @@ -1,1873 +0,0 @@ -/* ========================================================================== - * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_hcd_intr.c $ - * $Revision: 1.6.2.1 $ - * $Date: 2009-04-22 03:48:22 $ - * $Change: 1117667 $ - * - * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, - * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless - * otherwise expressly agreed to in writing between Synopsys and you. - * - * The Software IS NOT an item of Licensed Software or Licensed Product under - * any End User Software License Agreement or Agreement for Licensed Product - * with Synopsys or any supplement thereto. You are permitted to use and - * redistribute this Software in source and binary forms, with or without - * modification, provided that redistributions of source code must retain this - * notice. You may not view, use, disclose, copy or distribute this file or - * any information contained herein except pursuant to this license grant from - * Synopsys. If you do not agree with this notice, including the disclaimer - * below, then you are not authorized to use the Software. - * - * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS 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. - * ========================================================================== */ -#ifndef DWC_DEVICE_ONLY - -#include <linux/version.h> - -#include "dwc_otg_driver.h" -#include "dwc_otg_hcd.h" -#include "dwc_otg_regs.h" - -/** @file - * This file contains the implementation of the HCD Interrupt handlers. - */ - -/** This function handles interrupts for the HCD. */ -int32_t dwc_otg_hcd_handle_intr(dwc_otg_hcd_t *dwc_otg_hcd) -{ - int retval = 0; - - dwc_otg_core_if_t *core_if = dwc_otg_hcd->core_if; - gintsts_data_t gintsts; -#ifdef DEBUG - dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; -#endif - - /* Check if HOST Mode */ - if (dwc_otg_is_host_mode(core_if)) { - gintsts.d32 = dwc_otg_read_core_intr(core_if); - if (!gintsts.d32) { - return 0; - } - -#ifdef DEBUG - /* Don't print debug message in the interrupt handler on SOF */ -# ifndef DEBUG_SOF - if (gintsts.d32 != DWC_SOF_INTR_MASK) -# endif - DWC_DEBUGPL(DBG_HCD, "\n"); -#endif - -#ifdef DEBUG -# ifndef DEBUG_SOF - if (gintsts.d32 != DWC_SOF_INTR_MASK) -# endif - DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD Interrupt Detected gintsts&gintmsk=0x%08x\n", gintsts.d32); -#endif - if (gintsts.b.usbreset) { - DWC_PRINT("Usb Reset In Host Mode\n"); - } - - - if (gintsts.b.sofintr) { - retval |= dwc_otg_hcd_handle_sof_intr(dwc_otg_hcd); - } - if (gintsts.b.rxstsqlvl) { - retval |= dwc_otg_hcd_handle_rx_status_q_level_intr(dwc_otg_hcd); - } - if (gintsts.b.nptxfempty) { - retval |= dwc_otg_hcd_handle_np_tx_fifo_empty_intr(dwc_otg_hcd); - } - if (gintsts.b.i2cintr) { - /** @todo Implement i2cintr handler. */ - } - if (gintsts.b.portintr) { - retval |= dwc_otg_hcd_handle_port_intr(dwc_otg_hcd); - } - if (gintsts.b.hcintr) { - retval |= dwc_otg_hcd_handle_hc_intr(dwc_otg_hcd); - } - if (gintsts.b.ptxfempty) { - retval |= dwc_otg_hcd_handle_perio_tx_fifo_empty_intr(dwc_otg_hcd); - } -#ifdef DEBUG -# ifndef DEBUG_SOF - if (gintsts.d32 != DWC_SOF_INTR_MASK) -# endif - { - DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD Finished Servicing Interrupts\n"); - DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD gintsts=0x%08x\n", - dwc_read_reg32(&global_regs->gintsts)); - DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD gintmsk=0x%08x\n", - dwc_read_reg32(&global_regs->gintmsk)); - } -#endif - -#ifdef DEBUG -# ifndef DEBUG_SOF - if (gintsts.d32 != DWC_SOF_INTR_MASK) -# endif - DWC_DEBUGPL(DBG_HCD, "\n"); -#endif - - } - - S3C2410X_CLEAR_EINTPEND(); - - return retval; -} - -#ifdef DWC_TRACK_MISSED_SOFS -#warning Compiling code to track missed SOFs -#define FRAME_NUM_ARRAY_SIZE 1000 -/** - * This function is for debug only. - */ -static inline void track_missed_sofs(uint16_t curr_frame_number) -{ - static uint16_t frame_num_array[FRAME_NUM_ARRAY_SIZE]; - static uint16_t last_frame_num_array[FRAME_NUM_ARRAY_SIZE]; - static int frame_num_idx = 0; - static uint16_t last_frame_num = DWC_HFNUM_MAX_FRNUM; - static int dumped_frame_num_array = 0; - - if (frame_num_idx < FRAME_NUM_ARRAY_SIZE) { - if (((last_frame_num + 1) & DWC_HFNUM_MAX_FRNUM) != curr_frame_number) { - frame_num_array[frame_num_idx] = curr_frame_number; - last_frame_num_array[frame_num_idx++] = last_frame_num; - } - } else if (!dumped_frame_num_array) { - int i; - printk(KERN_EMERG USB_DWC "Frame Last Frame\n"); - printk(KERN_EMERG USB_DWC "----- ----------\n"); - for (i = 0; i < FRAME_NUM_ARRAY_SIZE; i++) { - printk(KERN_EMERG USB_DWC "0x%04x 0x%04x\n", - frame_num_array[i], last_frame_num_array[i]); - } - dumped_frame_num_array = 1; - } - last_frame_num = curr_frame_number; -} -#endif - -/** - * Handles the start-of-frame interrupt in host mode. Non-periodic - * transactions may be queued to the DWC_otg controller for the current - * (micro)frame. Periodic transactions may be queued to the controller for the - * next (micro)frame. - */ -int32_t dwc_otg_hcd_handle_sof_intr(dwc_otg_hcd_t *hcd) -{ - hfnum_data_t hfnum; - struct list_head *qh_entry; - dwc_otg_qh_t *qh; - dwc_otg_transaction_type_e tr_type; - gintsts_data_t gintsts = {.d32 = 0}; - - hfnum.d32 = dwc_read_reg32(&hcd->core_if->host_if->host_global_regs->hfnum); - -#ifdef DEBUG_SOF - DWC_DEBUGPL(DBG_HCD, "--Start of Frame Interrupt--\n"); -#endif - hcd->frame_number = hfnum.b.frnum; - -#ifdef DEBUG - hcd->frrem_accum += hfnum.b.frrem; - hcd->frrem_samples++; -#endif - -#ifdef DWC_TRACK_MISSED_SOFS - track_missed_sofs(hcd->frame_number); -#endif - - /* Determine whether any periodic QHs should be executed. */ - qh_entry = hcd->periodic_sched_inactive.next; - while (qh_entry != &hcd->periodic_sched_inactive) { - qh = list_entry(qh_entry, dwc_otg_qh_t, qh_list_entry); - qh_entry = qh_entry->next; - if (dwc_frame_num_le(qh->sched_frame, hcd->frame_number)) { - /* - * Move QH to the ready list to be executed next - * (micro)frame. - */ - list_move(&qh->qh_list_entry, &hcd->periodic_sched_ready); - } - } - - tr_type = dwc_otg_hcd_select_transactions(hcd); - if (tr_type != DWC_OTG_TRANSACTION_NONE) { - dwc_otg_hcd_queue_transactions(hcd, tr_type); - } - - /* Clear interrupt */ - gintsts.b.sofintr = 1; - dwc_write_reg32(&hcd->core_if->core_global_regs->gintsts, gintsts.d32); - - return 1; -} - -/** Handles the Rx Status Queue Level Interrupt, which indicates that there is at - * least one packet in the Rx FIFO. The packets are moved from the FIFO to - * memory if the DWC_otg controller is operating in Slave mode. */ -int32_t dwc_otg_hcd_handle_rx_status_q_level_intr(dwc_otg_hcd_t *dwc_otg_hcd) -{ - host_grxsts_data_t grxsts; - dwc_hc_t *hc = NULL; - - DWC_DEBUGPL(DBG_HCD, "--RxStsQ Level Interrupt--\n"); - - grxsts.d32 = dwc_read_reg32(&dwc_otg_hcd->core_if->core_global_regs->grxstsp); - - hc = dwc_otg_hcd->hc_ptr_array[grxsts.b.chnum]; - - /* Packet Status */ - DWC_DEBUGPL(DBG_HCDV, " Ch num = %d\n", grxsts.b.chnum); - DWC_DEBUGPL(DBG_HCDV, " Count = %d\n", grxsts.b.bcnt); - DWC_DEBUGPL(DBG_HCDV, " DPID = %d, hc.dpid = %d\n", grxsts.b.dpid, hc->data_pid_start); - DWC_DEBUGPL(DBG_HCDV, " PStatus = %d\n", grxsts.b.pktsts); - - switch (grxsts.b.pktsts) { - case DWC_GRXSTS_PKTSTS_IN: - /* Read the data into the host buffer. */ - if (grxsts.b.bcnt > 0) { - dwc_otg_read_packet(dwc_otg_hcd->core_if, - hc->xfer_buff, - grxsts.b.bcnt); - - /* Update the HC fields for the next packet received. */ - hc->xfer_count += grxsts.b.bcnt; - hc->xfer_buff += grxsts.b.bcnt; - } - - case DWC_GRXSTS_PKTSTS_IN_XFER_COMP: - case DWC_GRXSTS_PKTSTS_DATA_TOGGLE_ERR: - case DWC_GRXSTS_PKTSTS_CH_HALTED: - /* Handled in interrupt, just ignore data */ - break; - default: - DWC_ERROR("RX_STS_Q Interrupt: Unknown status %d\n", grxsts.b.pktsts); - break; - } - - return 1; -} - -/** This interrupt occurs when the non-periodic Tx FIFO is half-empty. More - * data packets may be written to the FIFO for OUT transfers. More requests - * may be written to the non-periodic request queue for IN transfers. This - * interrupt is enabled only in Slave mode. */ -int32_t dwc_otg_hcd_handle_np_tx_fifo_empty_intr(dwc_otg_hcd_t *dwc_otg_hcd) -{ - DWC_DEBUGPL(DBG_HCD, "--Non-Periodic TxFIFO Empty Interrupt--\n"); - dwc_otg_hcd_queue_transactions(dwc_otg_hcd, - DWC_OTG_TRANSACTION_NON_PERIODIC); - return 1; -} - -/** This interrupt occurs when the periodic Tx FIFO is half-empty. More data - * packets may be written to the FIFO for OUT transfers. More requests may be - * written to the periodic request queue for IN transfers. This interrupt is - * enabled only in Slave mode. */ -int32_t dwc_otg_hcd_handle_perio_tx_fifo_empty_intr(dwc_otg_hcd_t *dwc_otg_hcd) -{ - DWC_DEBUGPL(DBG_HCD, "--Periodic TxFIFO Empty Interrupt--\n"); - dwc_otg_hcd_queue_transactions(dwc_otg_hcd, - DWC_OTG_TRANSACTION_PERIODIC); - return 1; -} - -/** There are multiple conditions that can cause a port interrupt. This function - * determines which interrupt conditions have occurred and handles them - * appropriately. */ -int32_t dwc_otg_hcd_handle_port_intr(dwc_otg_hcd_t *dwc_otg_hcd) -{ - int retval = 0; - hprt0_data_t hprt0; - hprt0_data_t hprt0_modify; - - hprt0.d32 = dwc_read_reg32(dwc_otg_hcd->core_if->host_if->hprt0); - hprt0_modify.d32 = dwc_read_reg32(dwc_otg_hcd->core_if->host_if->hprt0); - - /* Clear appropriate bits in HPRT0 to clear the interrupt bit in - * GINTSTS */ - - hprt0_modify.b.prtena = 0; - hprt0_modify.b.prtconndet = 0; - hprt0_modify.b.prtenchng = 0; - hprt0_modify.b.prtovrcurrchng = 0; - - /* Port Connect Detected - * Set flag and clear if detected */ - if (hprt0.b.prtconndet) { - DWC_DEBUGPL(DBG_HCD, "--Port Interrupt HPRT0=0x%08x " - "Port Connect Detected--\n", hprt0.d32); - dwc_otg_hcd->flags.b.port_connect_status_change = 1; - dwc_otg_hcd->flags.b.port_connect_status = 1; - hprt0_modify.b.prtconndet = 1; - - /* B-Device has connected, Delete the connection timer. */ - del_timer( &dwc_otg_hcd->conn_timer ); - - /* The Hub driver asserts a reset when it sees port connect - * status change flag */ - retval |= 1; - } - - /* Port Enable Changed - * Clear if detected - Set internal flag if disabled */ - if (hprt0.b.prtenchng) { - DWC_DEBUGPL(DBG_HCD, " --Port Interrupt HPRT0=0x%08x " - "Port Enable Changed--\n", hprt0.d32); - hprt0_modify.b.prtenchng = 1; - if (hprt0.b.prtena == 1) { - int do_reset = 0; - dwc_otg_core_params_t *params = dwc_otg_hcd->core_if->core_params; - dwc_otg_core_global_regs_t *global_regs = dwc_otg_hcd->core_if->core_global_regs; - dwc_otg_host_if_t *host_if = dwc_otg_hcd->core_if->host_if; - - /* Check if we need to adjust the PHY clock speed for - * low power and adjust it */ - if (params->host_support_fs_ls_low_power) { - gusbcfg_data_t usbcfg; - - usbcfg.d32 = dwc_read_reg32(&global_regs->gusbcfg); - - if (hprt0.b.prtspd == DWC_HPRT0_PRTSPD_LOW_SPEED || - hprt0.b.prtspd == DWC_HPRT0_PRTSPD_FULL_SPEED) { - /* - * Low power - */ - hcfg_data_t hcfg; - if (usbcfg.b.phylpwrclksel == 0) { - /* Set PHY low power clock select for FS/LS devices */ - usbcfg.b.phylpwrclksel = 1; - dwc_write_reg32(&global_regs->gusbcfg, usbcfg.d32); - do_reset = 1; - } - - hcfg.d32 = dwc_read_reg32(&host_if->host_global_regs->hcfg); - - if (hprt0.b.prtspd == DWC_HPRT0_PRTSPD_LOW_SPEED && - params->host_ls_low_power_phy_clk == - DWC_HOST_LS_LOW_POWER_PHY_CLK_PARAM_6MHZ) { - /* 6 MHZ */ - DWC_DEBUGPL(DBG_CIL, "FS_PHY programming HCFG to 6 MHz (Low Power)\n"); - if (hcfg.b.fslspclksel != DWC_HCFG_6_MHZ) { - hcfg.b.fslspclksel = DWC_HCFG_6_MHZ; - dwc_write_reg32(&host_if->host_global_regs->hcfg, - hcfg.d32); - do_reset = 1; - } - } else { - /* 48 MHZ */ - DWC_DEBUGPL(DBG_CIL, "FS_PHY programming HCFG to 48 MHz ()\n"); - if (hcfg.b.fslspclksel != DWC_HCFG_48_MHZ) { - hcfg.b.fslspclksel = DWC_HCFG_48_MHZ; - dwc_write_reg32(&host_if->host_global_regs->hcfg, - hcfg.d32); - do_reset = 1; - } - } - } else { - /* - * Not low power - */ - if (usbcfg.b.phylpwrclksel == 1) { - usbcfg.b.phylpwrclksel = 0; - dwc_write_reg32(&global_regs->gusbcfg, usbcfg.d32); - do_reset = 1; - } - } - - if (do_reset) { - tasklet_schedule(dwc_otg_hcd->reset_tasklet); - } - } - - if (!do_reset) { - /* Port has been enabled set the reset change flag */ - dwc_otg_hcd->flags.b.port_reset_change = 1; - } - } else { - dwc_otg_hcd->flags.b.port_enable_change = 1; - } - retval |= 1; - } - - /** Overcurrent Change Interrupt */ - if (hprt0.b.prtovrcurrchng) { - DWC_DEBUGPL(DBG_HCD, " --Port Interrupt HPRT0=0x%08x " - "Port Overcurrent Changed--\n", hprt0.d32); - dwc_otg_hcd->flags.b.port_over_current_change = 1; - hprt0_modify.b.prtovrcurrchng = 1; - retval |= 1; - } - - /* Clear Port Interrupts */ - dwc_write_reg32(dwc_otg_hcd->core_if->host_if->hprt0, hprt0_modify.d32); - - return retval; -} - -/** This interrupt indicates that one or more host channels has a pending - * interrupt. There are multiple conditions that can cause each host channel - * interrupt. This function determines which conditions have occurred for each - * host channel interrupt and handles them appropriately. */ -int32_t dwc_otg_hcd_handle_hc_intr(dwc_otg_hcd_t *dwc_otg_hcd) -{ - int i; - int retval = 0; - haint_data_t haint; - - /* Clear appropriate bits in HCINTn to clear the interrupt bit in - * GINTSTS */ - - haint.d32 = dwc_otg_read_host_all_channels_intr(dwc_otg_hcd->core_if); - - for (i = 0; i < dwc_otg_hcd->core_if->core_params->host_channels; i++) { - if (haint.b2.chint & (1 << i)) { - retval |= dwc_otg_hcd_handle_hc_n_intr(dwc_otg_hcd, i); - } - } - - return retval; -} - -/* Macro used to clear one channel interrupt */ -#define clear_hc_int(_hc_regs_, _intr_) \ -do { \ - hcint_data_t hcint_clear = {.d32 = 0}; \ - hcint_clear.b._intr_ = 1; \ - dwc_write_reg32(&(_hc_regs_)->hcint, hcint_clear.d32); \ -} while (0) - -/* - * Macro used to disable one channel interrupt. Channel interrupts are - * disabled when the channel is halted or released by the interrupt handler. - * There is no need to handle further interrupts of that type until the - * channel is re-assigned. In fact, subsequent handling may cause crashes - * because the channel structures are cleaned up when the channel is released. - */ -#define disable_hc_int(_hc_regs_, _intr_) \ -do { \ - hcintmsk_data_t hcintmsk = {.d32 = 0}; \ - hcintmsk.b._intr_ = 1; \ - dwc_modify_reg32(&(_hc_regs_)->hcintmsk, hcintmsk.d32, 0); \ -} while (0) - -/** - * Gets the actual length of a transfer after the transfer halts. _halt_status - * holds the reason for the halt. - * - * For IN transfers where halt_status is DWC_OTG_HC_XFER_COMPLETE, - * *short_read is set to 1 upon return if less than the requested - * number of bytes were transferred. Otherwise, *short_read is set to 0 upon - * return. short_read may also be NULL on entry, in which case it remains - * unchanged. - */ -static uint32_t get_actual_xfer_length(dwc_hc_t *hc, - dwc_otg_hc_regs_t *hc_regs, - dwc_otg_qtd_t *qtd, - dwc_otg_halt_status_e halt_status, - int *short_read) -{ - hctsiz_data_t hctsiz; - uint32_t length; - - if (short_read != NULL) { - *short_read = 0; - } - hctsiz.d32 = dwc_read_reg32(&hc_regs->hctsiz); - - if (halt_status == DWC_OTG_HC_XFER_COMPLETE) { - if (hc->ep_is_in) { - length = hc->xfer_len - hctsiz.b.xfersize; - if (short_read != NULL) { - *short_read = (hctsiz.b.xfersize != 0); - } - } else if (hc->qh->do_split) { - length = qtd->ssplit_out_xfer_count; - } else { - length = hc->xfer_len; - } - } else { - /* - * Must use the hctsiz.pktcnt field to determine how much data - * has been transferred. This field reflects the number of - * packets that have been transferred via the USB. This is - * always an integral number of packets if the transfer was - * halted before its normal completion. (Can't use the - * hctsiz.xfersize field because that reflects the number of - * bytes transferred via the AHB, not the USB). - */ - length = (hc->start_pkt_count - hctsiz.b.pktcnt) * hc->max_packet; - } - - return length; -} - -/** - * Updates the state of the URB after a Transfer Complete interrupt on the - * host channel. Updates the actual_length field of the URB based on the - * number of bytes transferred via the host channel. Sets the URB status - * if the data transfer is finished. - * - * @return 1 if the data transfer specified by the URB is completely finished, - * 0 otherwise. - */ -static int update_urb_state_xfer_comp(dwc_hc_t *hc, - dwc_otg_hc_regs_t *hc_regs, - struct urb *urb, - dwc_otg_qtd_t *qtd) -{ - int xfer_done = 0; - int short_read = 0; - int overflow_read=0; - uint32_t len = 0; - int max_packet; - - len = get_actual_xfer_length(hc, hc_regs, qtd, - DWC_OTG_HC_XFER_COMPLETE, - &short_read); - - /* Data overflow case: by Steven */ - if (len > urb->transfer_buffer_length) { - len = urb->transfer_buffer_length; - overflow_read = 1; - } - - /* non DWORD-aligned buffer case handling. */ - if (((uint32_t)hc->xfer_buff & 0x3) && len && hc->qh->dw_align_buf && hc->ep_is_in) { - memcpy(urb->transfer_buffer + urb->actual_length, hc->qh->dw_align_buf, len); - } - urb->actual_length +=len; - - max_packet = usb_maxpacket(urb->dev, urb->pipe, !usb_pipein(urb->pipe)); - if((len) && usb_pipebulk(urb->pipe) && - (urb->transfer_flags & URB_ZERO_PACKET) && - (urb->actual_length == urb->transfer_buffer_length) && - (!(urb->transfer_buffer_length % max_packet))) { - } else if (short_read || urb->actual_length == urb->transfer_buffer_length) { - xfer_done = 1; - if (short_read && (urb->transfer_flags & URB_SHORT_NOT_OK)) { - urb->status = -EREMOTEIO; - } else if (overflow_read) { - urb->status = -EOVERFLOW; - } else { - urb->status = 0; - } - } - -#ifdef DEBUG - { - hctsiz_data_t hctsiz; - hctsiz.d32 = dwc_read_reg32(&hc_regs->hctsiz); - DWC_DEBUGPL(DBG_HCDV, "DWC_otg: %s: %s, channel %d\n", - __func__, (hc->ep_is_in ? "IN" : "OUT"), hc->hc_num); - DWC_DEBUGPL(DBG_HCDV, " hc->xfer_len %d\n", hc->xfer_len); - DWC_DEBUGPL(DBG_HCDV, " hctsiz.xfersize %d\n", hctsiz.b.xfersize); - DWC_DEBUGPL(DBG_HCDV, " urb->transfer_buffer_length %d\n", - urb->transfer_buffer_length); - DWC_DEBUGPL(DBG_HCDV, " urb->actual_length %d\n", urb->actual_length); - DWC_DEBUGPL(DBG_HCDV, " short_read %d, xfer_done %d\n", - short_read, xfer_done); - } -#endif - - return xfer_done; -} - -/* - * Save the starting data toggle for the next transfer. The data toggle is - * saved in the QH for non-control transfers and it's saved in the QTD for - * control transfers. - */ -static void save_data_toggle(dwc_hc_t *hc, - dwc_otg_hc_regs_t *hc_regs, - dwc_otg_qtd_t *qtd) -{ - hctsiz_data_t hctsiz; - hctsiz.d32 = dwc_read_reg32(&hc_regs->hctsiz); - - if (hc->ep_type != DWC_OTG_EP_TYPE_CONTROL) { - dwc_otg_qh_t *qh = hc->qh; - if (hctsiz.b.pid == DWC_HCTSIZ_DATA0) { - qh->data_toggle = DWC_OTG_HC_PID_DATA0; - } else { - qh->data_toggle = DWC_OTG_HC_PID_DATA1; - } - } else { - if (hctsiz.b.pid == DWC_HCTSIZ_DATA0) { - qtd->data_toggle = DWC_OTG_HC_PID_DATA0; - } else { - qtd->data_toggle = DWC_OTG_HC_PID_DATA1; - } - } -} - -/** - * Frees the first QTD in the QH's list if free_qtd is 1. For non-periodic - * QHs, removes the QH from the active non-periodic schedule. If any QTDs are - * still linked to the QH, the QH is added to the end of the inactive - * non-periodic schedule. For periodic QHs, removes the QH from the periodic - * schedule if no more QTDs are linked to the QH. - */ -static void deactivate_qh(dwc_otg_hcd_t *hcd, - dwc_otg_qh_t *qh, - int free_qtd) -{ - int continue_split = 0; - dwc_otg_qtd_t *qtd; - - DWC_DEBUGPL(DBG_HCDV, " %s(%p,%p,%d)\n", __func__, hcd, qh, free_qtd); - - qtd = list_entry(qh->qtd_list.next, dwc_otg_qtd_t, qtd_list_entry); - - if (qtd->complete_split) { - continue_split = 1; - } else if (qtd->isoc_split_pos == DWC_HCSPLIT_XACTPOS_MID || - qtd->isoc_split_pos == DWC_HCSPLIT_XACTPOS_END) { - continue_split = 1; - } - - if (free_qtd) { - dwc_otg_hcd_qtd_remove_and_free(hcd, qtd); - continue_split = 0; - } - - qh->channel = NULL; - qh->qtd_in_process = NULL; - dwc_otg_hcd_qh_deactivate(hcd, qh, continue_split); -} - -/** - * Updates the state of an Isochronous URB when the transfer is stopped for - * any reason. The fields of the current entry in the frame descriptor array - * are set based on the transfer state and the input _halt_status. Completes - * the Isochronous URB if all the URB frames have been completed. - * - * @return DWC_OTG_HC_XFER_COMPLETE if there are more frames remaining to be - * transferred in the URB. Otherwise return DWC_OTG_HC_XFER_URB_COMPLETE. - */ -static dwc_otg_halt_status_e -update_isoc_urb_state(dwc_otg_hcd_t *hcd, - dwc_hc_t *hc, - dwc_otg_hc_regs_t *hc_regs, - dwc_otg_qtd_t *qtd, - dwc_otg_halt_status_e halt_status) -{ - struct urb *urb = qtd->urb; - dwc_otg_halt_status_e ret_val = halt_status; - struct usb_iso_packet_descriptor *frame_desc; - - frame_desc = &urb->iso_frame_desc[qtd->isoc_frame_index]; - switch (halt_status) { - case DWC_OTG_HC_XFER_COMPLETE: - frame_desc->status = 0; - frame_desc->actual_length = - get_actual_xfer_length(hc, hc_regs, qtd, - halt_status, NULL); - - /* non DWORD-aligned buffer case handling. */ - if (frame_desc->actual_length && ((uint32_t)hc->xfer_buff & 0x3) && - hc->qh->dw_align_buf && hc->ep_is_in) { - memcpy(urb->transfer_buffer + frame_desc->offset + qtd->isoc_split_offset, - hc->qh->dw_align_buf, frame_desc->actual_length); - - } - - break; - case DWC_OTG_HC_XFER_FRAME_OVERRUN: - printk("DWC_OTG_HC_XFER_FRAME_OVERRUN: %d\n", halt_status); - urb->error_count++; - if (hc->ep_is_in) { - frame_desc->status = -ENOSR; - } else { - frame_desc->status = -ECOMM; - } - frame_desc->actual_length = 0; - break; - case DWC_OTG_HC_XFER_BABBLE_ERR: - printk("DWC_OTG_HC_XFER_BABBLE_ERR: %d\n", halt_status); - urb->error_count++; - frame_desc->status = -EOVERFLOW; - /* Don't need to update actual_length in this case. */ - break; - case DWC_OTG_HC_XFER_XACT_ERR: - printk("DWC_OTG_HC_XFER_XACT_ERR: %d\n", halt_status); - urb->error_count++; - frame_desc->status = -EPROTO; - frame_desc->actual_length = - get_actual_xfer_length(hc, hc_regs, qtd, - halt_status, NULL); - - /* non DWORD-aligned buffer case handling. */ - if (frame_desc->actual_length && ((uint32_t)hc->xfer_buff & 0x3) && - hc->qh->dw_align_buf && hc->ep_is_in) { - memcpy(urb->transfer_buffer + frame_desc->offset + qtd->isoc_split_offset, - hc->qh->dw_align_buf, frame_desc->actual_length); - - } - break; - default: - - DWC_ERROR("%s: Unhandled _halt_status (%d)\n", __func__, - halt_status); - BUG(); - break; - } - - if (++qtd->isoc_frame_index == urb->number_of_packets) { - /* - * urb->status is not used for isoc transfers. - * The individual frame_desc statuses are used instead. - */ - dwc_otg_hcd_complete_urb(hcd, urb, 0); - ret_val = DWC_OTG_HC_XFER_URB_COMPLETE; - } else { - ret_val = DWC_OTG_HC_XFER_COMPLETE; - } - - return ret_val; -} - -/** - * Releases a host channel for use by other transfers. Attempts to select and - * queue more transactions since at least one host channel is available. - * - * @param hcd The HCD state structure. - * @param hc The host channel to release. - * @param qtd The QTD associated with the host channel. This QTD may be freed - * if the transfer is complete or an error has occurred. - * @param halt_status Reason the channel is being released. This status - * determines the actions taken by this function. - */ -static void release_channel(dwc_otg_hcd_t *hcd, - dwc_hc_t *hc, - dwc_otg_qtd_t *qtd, - dwc_otg_halt_status_e halt_status) -{ - dwc_otg_transaction_type_e tr_type; - int free_qtd; - - DWC_DEBUGPL(DBG_HCDV, " %s: channel %d, halt_status %d\n", - __func__, hc->hc_num, halt_status); - - switch (halt_status) { - case DWC_OTG_HC_XFER_URB_COMPLETE: - free_qtd = 1; - break; - case DWC_OTG_HC_XFER_AHB_ERR: - case DWC_OTG_HC_XFER_STALL: - case DWC_OTG_HC_XFER_BABBLE_ERR: - free_qtd = 1; - break; - case DWC_OTG_HC_XFER_XACT_ERR: - if (qtd->error_count >= 3) { - DWC_DEBUGPL(DBG_HCDV, " Complete URB with transaction error\n"); - free_qtd = 1; - qtd->urb->status = -EPROTO; - dwc_otg_hcd_complete_urb(hcd, qtd->urb, -EPROTO); - } else { - free_qtd = 0; - } - break; - case DWC_OTG_HC_XFER_URB_DEQUEUE: - /* - * The QTD has already been removed and the QH has been - * deactivated. Don't want to do anything except release the - * host channel and try to queue more transfers. - */ - goto cleanup; - case DWC_OTG_HC_XFER_NO_HALT_STATUS: - DWC_ERROR("%s: No halt_status, channel %d\n", __func__, hc->hc_num); - free_qtd = 0; - break; - default: - free_qtd = 0; - break; - } - - deactivate_qh(hcd, hc->qh, free_qtd); - - cleanup: - /* - * Release the host channel for use by other transfers. The cleanup - * function clears the channel interrupt enables and conditions, so - * there's no need to clear the Channel Halted interrupt separately. - */ - dwc_otg_hc_cleanup(hcd->core_if, hc); - list_add_tail(&hc->hc_list_entry, &hcd->free_hc_list); - - switch (hc->ep_type) { - case DWC_OTG_EP_TYPE_CONTROL: - case DWC_OTG_EP_TYPE_BULK: - hcd->non_periodic_channels--; - break; - - default: - /* - * Don't release reservations for periodic channels here. - * That's done when a periodic transfer is descheduled (i.e. - * when the QH is removed from the periodic schedule). - */ - break; - } - - /* Try to queue more transfers now that there's a free channel. */ - tr_type = dwc_otg_hcd_select_transactions(hcd); - if (tr_type != DWC_OTG_TRANSACTION_NONE) { - dwc_otg_hcd_queue_transactions(hcd, tr_type); - } -} - -/** - * Halts a host channel. If the channel cannot be halted immediately because - * the request queue is full, this function ensures that the FIFO empty - * interrupt for the appropriate queue is enabled so that the halt request can - * be queued when there is space in the request queue. - * - * This function may also be called in DMA mode. In that case, the channel is - * simply released since the core always halts the channel automatically in - * DMA mode. - */ -static void halt_channel(dwc_otg_hcd_t *hcd, - dwc_hc_t *hc, - dwc_otg_qtd_t *qtd, - dwc_otg_halt_status_e halt_status) -{ - if (hcd->core_if->dma_enable) { - release_channel(hcd, hc, qtd, halt_status); - return; - } - - /* Slave mode processing... */ - dwc_otg_hc_halt(hcd->core_if, hc, halt_status); - - if (hc->halt_on_queue) { - gintmsk_data_t gintmsk = {.d32 = 0}; - dwc_otg_core_global_regs_t *global_regs; - global_regs = hcd->core_if->core_global_regs; - - if (hc->ep_type == DWC_OTG_EP_TYPE_CONTROL || - hc->ep_type == DWC_OTG_EP_TYPE_BULK) { - /* - * Make sure the Non-periodic Tx FIFO empty interrupt - * is enabled so that the non-periodic schedule will - * be processed. - */ - gintmsk.b.nptxfempty = 1; - dwc_modify_reg32(&global_regs->gintmsk, 0, gintmsk.d32); - } else { - /* - * Move the QH from the periodic queued schedule to - * the periodic assigned schedule. This allows the - * halt to be queued when the periodic schedule is - * processed. - */ - list_move(&hc->qh->qh_list_entry, - &hcd->periodic_sched_assigned); - - /* - * Make sure the Periodic Tx FIFO Empty interrupt is - * enabled so that the periodic schedule will be - * processed. - */ - gintmsk.b.ptxfempty = 1; - dwc_modify_reg32(&global_regs->gintmsk, 0, gintmsk.d32); - } - } -} - -/** - * Performs common cleanup for non-periodic transfers after a Transfer - * Complete interrupt. This function should be called after any endpoint type - * specific handling is finished to release the host channel. - */ -static void complete_non_periodic_xfer(dwc_otg_hcd_t *hcd, - dwc_hc_t *hc, - dwc_otg_hc_regs_t *hc_regs, - dwc_otg_qtd_t *qtd, - dwc_otg_halt_status_e halt_status) -{ - hcint_data_t hcint; - - qtd->error_count = 0; - - hcint.d32 = dwc_read_reg32(&hc_regs->hcint); - if (hcint.b.nyet) { - /* - * Got a NYET on the last transaction of the transfer. This - * means that the endpoint should be in the PING state at the - * beginning of the next transfer. - */ - hc->qh->ping_state = 1; - clear_hc_int(hc_regs, nyet); - } - - /* - * Always halt and release the host channel to make it available for - * more transfers. There may still be more phases for a control - * transfer or more data packets for a bulk transfer at this point, - * but the host channel is still halted. A channel will be reassigned - * to the transfer when the non-periodic schedule is processed after - * the channel is released. This allows transactions to be queued - * properly via dwc_otg_hcd_queue_transactions, which also enables the - * Tx FIFO Empty interrupt if necessary. - */ - if (hc->ep_is_in) { - /* - * IN transfers in Slave mode require an explicit disable to - * halt the channel. (In DMA mode, this call simply releases - * the channel.) - */ - halt_channel(hcd, hc, qtd, halt_status); - } else { - /* - * The channel is automatically disabled by the core for OUT - * transfers in Slave mode. - */ - release_channel(hcd, hc, qtd, halt_status); - } -} - -/** - * Performs common cleanup for periodic transfers after a Transfer Complete - * interrupt. This function should be called after any endpoint type specific - * handling is finished to release the host channel. - */ -static void complete_periodic_xfer(dwc_otg_hcd_t *hcd, - dwc_hc_t *hc, - dwc_otg_hc_regs_t *hc_regs, - dwc_otg_qtd_t *qtd, - dwc_otg_halt_status_e halt_status) -{ - hctsiz_data_t hctsiz; - qtd->error_count = 0; - - hctsiz.d32 = dwc_read_reg32(&hc_regs->hctsiz); - if (!hc->ep_is_in || hctsiz.b.pktcnt == 0) { - /* Core halts channel in these cases. */ - release_channel(hcd, hc, qtd, halt_status); - } else { - /* Flush any outstanding requests from the Tx queue. */ - halt_channel(hcd, hc, qtd, halt_status); - } -} - -/** - * Handles a host channel Transfer Complete interrupt. This handler may be - * called in either DMA mode or Slave mode. - */ -static int32_t handle_hc_xfercomp_intr(dwc_otg_hcd_t *hcd, - dwc_hc_t *hc, - dwc_otg_hc_regs_t *hc_regs, - dwc_otg_qtd_t *qtd) -{ - int urb_xfer_done; - dwc_otg_halt_status_e halt_status = DWC_OTG_HC_XFER_COMPLETE; - struct urb *urb = qtd->urb; - int pipe_type = usb_pipetype(urb->pipe); - - DWC_DEBUGPL(DBG_HCD, "--Host Channel %d Interrupt: " - "Transfer Complete--\n", hc->hc_num); - - /* - * Handle xfer complete on CSPLIT. - */ - if (hc->qh->do_split) { - qtd->complete_split = 0; - } - - /* Update the QTD and URB states. */ - switch (pipe_type) { - case PIPE_CONTROL: - switch (qtd->control_phase) { - case DWC_OTG_CONTROL_SETUP: - if (urb->transfer_buffer_length > 0) { - qtd->control_phase = DWC_OTG_CONTROL_DATA; - } else { - qtd->control_phase = DWC_OTG_CONTROL_STATUS; - } - DWC_DEBUGPL(DBG_HCDV, " Control setup transaction done\n"); - halt_status = DWC_OTG_HC_XFER_COMPLETE; - break; - case DWC_OTG_CONTROL_DATA: { - urb_xfer_done = update_urb_state_xfer_comp(hc, hc_regs, urb, qtd); - if (urb_xfer_done) { - qtd->control_phase = DWC_OTG_CONTROL_STATUS; - DWC_DEBUGPL(DBG_HCDV, " Control data transfer done\n"); - } else { - save_data_toggle(hc, hc_regs, qtd); - } - halt_status = DWC_OTG_HC_XFER_COMPLETE; - break; - } - case DWC_OTG_CONTROL_STATUS: - DWC_DEBUGPL(DBG_HCDV, " Control transfer complete\n"); - if (urb->status == -EINPROGRESS) { - urb->status = 0; - } - dwc_otg_hcd_complete_urb(hcd, urb, urb->status); - halt_status = DWC_OTG_HC_XFER_URB_COMPLETE; - break; - } - - complete_non_periodic_xfer(hcd, hc, hc_regs, qtd, halt_status); - break; - case PIPE_BULK: - DWC_DEBUGPL(DBG_HCDV, " Bulk transfer complete\n"); - urb_xfer_done = update_urb_state_xfer_comp(hc, hc_regs, urb, qtd); - if (urb_xfer_done) { - dwc_otg_hcd_complete_urb(hcd, urb, urb->status); - halt_status = DWC_OTG_HC_XFER_URB_COMPLETE; - } else { - halt_status = DWC_OTG_HC_XFER_COMPLETE; - } - - save_data_toggle(hc, hc_regs, qtd); - complete_non_periodic_xfer(hcd, hc, hc_regs, qtd, halt_status); - break; - case PIPE_INTERRUPT: - DWC_DEBUGPL(DBG_HCDV, " Interrupt transfer complete\n"); - update_urb_state_xfer_comp(hc, hc_regs, urb, qtd); - - /* - * Interrupt URB is done on the first transfer complete - * interrupt. - */ - dwc_otg_hcd_complete_urb(hcd, urb, urb->status); - save_data_toggle(hc, hc_regs, qtd); - complete_periodic_xfer(hcd, hc, hc_regs, qtd, - DWC_OTG_HC_XFER_URB_COMPLETE); - break; - case PIPE_ISOCHRONOUS: - DWC_DEBUGPL(DBG_HCDV, " Isochronous transfer complete\n"); - if (qtd->isoc_split_pos == DWC_HCSPLIT_XACTPOS_ALL) { - halt_status = update_isoc_urb_state(hcd, hc, hc_regs, qtd, - DWC_OTG_HC_XFER_COMPLETE); - } - complete_periodic_xfer(hcd, hc, hc_regs, qtd, halt_status); - break; - } - - disable_hc_int(hc_regs, xfercompl); - - return 1; -} - -/** - * Handles a host channel STALL interrupt. This handler may be called in - * either DMA mode or Slave mode. - */ -static int32_t handle_hc_stall_intr(dwc_otg_hcd_t *hcd, - dwc_hc_t *hc, - dwc_otg_hc_regs_t *hc_regs, - dwc_otg_qtd_t *qtd) -{ - struct urb *urb = qtd->urb; - int pipe_type = usb_pipetype(urb->pipe); - - DWC_DEBUGPL(DBG_HCD, "--Host Channel %d Interrupt: " - "STALL Received--\n", hc->hc_num); - - if (pipe_type == PIPE_CONTROL) { - dwc_otg_hcd_complete_urb(hcd, urb, -EPIPE); - } - - if (pipe_type == PIPE_BULK || pipe_type == PIPE_INTERRUPT) { - dwc_otg_hcd_complete_urb(hcd, urb, -EPIPE); - /* - * USB protocol requires resetting the data toggle for bulk - * and interrupt endpoints when a CLEAR_FEATURE(ENDPOINT_HALT) - * setup command is issued to the endpoint. Anticipate the - * CLEAR_FEATURE command since a STALL has occurred and reset - * the data toggle now. - */ - hc->qh->data_toggle = 0; - } - - halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_STALL); - - disable_hc_int(hc_regs, stall); - - return 1; -} - -/* - * Updates the state of the URB when a transfer has been stopped due to an - * abnormal condition before the transfer completes. Modifies the - * actual_length field of the URB to reflect the number of bytes that have - * actually been transferred via the host channel. - */ -static void update_urb_state_xfer_intr(dwc_hc_t *hc, - dwc_otg_hc_regs_t *hc_regs, - struct urb *urb, - dwc_otg_qtd_t *qtd, - dwc_otg_halt_status_e halt_status) -{ - uint32_t bytes_transferred = get_actual_xfer_length(hc, hc_regs, qtd, - halt_status, NULL); - urb->actual_length += bytes_transferred; - -#ifdef DEBUG - { - hctsiz_data_t hctsiz; - hctsiz.d32 = dwc_read_reg32(&hc_regs->hctsiz); - DWC_DEBUGPL(DBG_HCDV, "DWC_otg: %s: %s, channel %d\n", - __func__, (hc->ep_is_in ? "IN" : "OUT"), hc->hc_num); - DWC_DEBUGPL(DBG_HCDV, " hc->start_pkt_count %d\n", hc->start_pkt_count); - DWC_DEBUGPL(DBG_HCDV, " hctsiz.pktcnt %d\n", hctsiz.b.pktcnt); - DWC_DEBUGPL(DBG_HCDV, " hc->max_packet %d\n", hc->max_packet); - DWC_DEBUGPL(DBG_HCDV, " bytes_transferred %d\n", bytes_transferred); - DWC_DEBUGPL(DBG_HCDV, " urb->actual_length %d\n", urb->actual_length); - DWC_DEBUGPL(DBG_HCDV, " urb->transfer_buffer_length %d\n", - urb->transfer_buffer_length); - } -#endif -} - -/** - * Handles a host channel NAK interrupt. This handler may be called in either - * DMA mode or Slave mode. - */ -static int32_t handle_hc_nak_intr(dwc_otg_hcd_t *hcd, - dwc_hc_t *hc, - dwc_otg_hc_regs_t *hc_regs, - dwc_otg_qtd_t *qtd) -{ - DWC_DEBUGPL(DBG_HCD, "--Host Channel %d Interrupt: " - "NAK Received--\n", hc->hc_num); - - /* - * Handle NAK for IN/OUT SSPLIT/CSPLIT transfers, bulk, control, and - * interrupt. Re-start the SSPLIT transfer. - */ - if (hc->do_split) { - if (hc->complete_split) { - qtd->error_count = 0; - } - qtd->complete_split = 0; - halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NAK); - goto handle_nak_done; - } - - switch (usb_pipetype(qtd->urb->pipe)) { - case PIPE_CONTROL: - case PIPE_BULK: - if (hcd->core_if->dma_enable && hc->ep_is_in) { - /* - * NAK interrupts are enabled on bulk/control IN - * transfers in DMA mode for the sole purpose of - * resetting the error count after a transaction error - * occurs. The core will continue transferring data. - */ - qtd->error_count = 0; - goto handle_nak_done; - } - - /* - * NAK interrupts normally occur during OUT transfers in DMA - * or Slave mode. For IN transfers, more requests will be - * queued as request queue space is available. - */ - qtd->error_count = 0; - - if (!hc->qh->ping_state) { - update_urb_state_xfer_intr(hc, hc_regs, qtd->urb, - qtd, DWC_OTG_HC_XFER_NAK); - save_data_toggle(hc, hc_regs, qtd); - if (qtd->urb->dev->speed == USB_SPEED_HIGH) { - hc->qh->ping_state = 1; - } - } - - /* - * Halt the channel so the transfer can be re-started from - * the appropriate point or the PING protocol will - * start/continue. - */ - halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NAK); - break; - case PIPE_INTERRUPT: - qtd->error_count = 0; - halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NAK); - break; - case PIPE_ISOCHRONOUS: - /* Should never get called for isochronous transfers. */ - BUG(); - break; - } - - handle_nak_done: - disable_hc_int(hc_regs, nak); - - return 1; -} - -/** - * Handles a host channel ACK interrupt. This interrupt is enabled when - * performing the PING protocol in Slave mode, when errors occur during - * either Slave mode or DMA mode, and during Start Split transactions. - */ -static int32_t handle_hc_ack_intr(dwc_otg_hcd_t *hcd, - dwc_hc_t *hc, - dwc_otg_hc_regs_t *hc_regs, - dwc_otg_qtd_t *qtd) -{ - DWC_DEBUGPL(DBG_HCD, "--Host Channel %d Interrupt: " - "ACK Received--\n", hc->hc_num); - - if (hc->do_split) { - /* - * Handle ACK on SSPLIT. - * ACK should not occur in CSPLIT. - */ - if (!hc->ep_is_in && hc->data_pid_start != DWC_OTG_HC_PID_SETUP) { - qtd->ssplit_out_xfer_count = hc->xfer_len; - } - if (!(hc->ep_type == DWC_OTG_EP_TYPE_ISOC && !hc->ep_is_in)) { - /* Don't need complete for isochronous out transfers. */ - qtd->complete_split = 1; - } - - /* ISOC OUT */ - if (hc->ep_type == DWC_OTG_EP_TYPE_ISOC && !hc->ep_is_in) { - switch (hc->xact_pos) { - case DWC_HCSPLIT_XACTPOS_ALL: - break; - case DWC_HCSPLIT_XACTPOS_END: - qtd->isoc_split_pos = DWC_HCSPLIT_XACTPOS_ALL; - qtd->isoc_split_offset = 0; - break; - case DWC_HCSPLIT_XACTPOS_BEGIN: - case DWC_HCSPLIT_XACTPOS_MID: - /* - * For BEGIN or MID, calculate the length for - * the next microframe to determine the correct - * SSPLIT token, either MID or END. - */ - { - struct usb_iso_packet_descriptor *frame_desc; - - frame_desc = &qtd->urb->iso_frame_desc[qtd->isoc_frame_index]; - qtd->isoc_split_offset += 188; - - if ((frame_desc->length - qtd->isoc_split_offset) <= 188) { - qtd->isoc_split_pos = DWC_HCSPLIT_XACTPOS_END; - } else { - qtd->isoc_split_pos = DWC_HCSPLIT_XACTPOS_MID; - } - - } - break; - } - } else { - halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_ACK); - } - } else { - qtd->error_count = 0; - - if (hc->qh->ping_state) { - hc->qh->ping_state = 0; - /* - * Halt the channel so the transfer can be re-started - * from the appropriate point. This only happens in - * Slave mode. In DMA mode, the ping_state is cleared - * when the transfer is started because the core - * automatically executes the PING, then the transfer. - */ - halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_ACK); - } - } - - /* - * If the ACK occurred when _not_ in the PING state, let the channel - * continue transferring data after clearing the error count. - */ - - disable_hc_int(hc_regs, ack); - - return 1; -} - -/** - * Handles a host channel NYET interrupt. This interrupt should only occur on - * Bulk and Control OUT endpoints and for complete split transactions. If a - * NYET occurs at the same time as a Transfer Complete interrupt, it is - * handled in the xfercomp interrupt handler, not here. This handler may be - * called in either DMA mode or Slave mode. - */ -static int32_t handle_hc_nyet_intr(dwc_otg_hcd_t *hcd, - dwc_hc_t *hc, - dwc_otg_hc_regs_t *hc_regs, - dwc_otg_qtd_t *qtd) -{ - DWC_DEBUGPL(DBG_HCD, "--Host Channel %d Interrupt: " - "NYET Received--\n", hc->hc_num); - - /* - * NYET on CSPLIT - * re-do the CSPLIT immediately on non-periodic - */ - if (hc->do_split && hc->complete_split) { - if (hc->ep_type == DWC_OTG_EP_TYPE_INTR || - hc->ep_type == DWC_OTG_EP_TYPE_ISOC) { - int frnum = dwc_otg_hcd_get_frame_number(dwc_otg_hcd_to_hcd(hcd)); - - if (dwc_full_frame_num(frnum) != - dwc_full_frame_num(hc->qh->sched_frame)) { - /* - * No longer in the same full speed frame. - * Treat this as a transaction error. - */ -#if 0 - /** @todo Fix system performance so this can - * be treated as an error. Right now complete - * splits cannot be scheduled precisely enough - * due to other system activity, so this error - * occurs regularly in Slave mode. - */ - qtd->error_count++; -#endif - qtd->complete_split = 0; - halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_XACT_ERR); - /** @todo add support for isoc release */ - goto handle_nyet_done; - } - } - - halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NYET); - goto handle_nyet_done; - } - - hc->qh->ping_state = 1; - qtd->error_count = 0; - - update_urb_state_xfer_intr(hc, hc_regs, qtd->urb, qtd, - DWC_OTG_HC_XFER_NYET); - save_data_toggle(hc, hc_regs, qtd); - - /* - * Halt the channel and re-start the transfer so the PING - * protocol will start. - */ - halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NYET); - -handle_nyet_done: - disable_hc_int(hc_regs, nyet); - return 1; -} - -/** - * Handles a host channel babble interrupt. This handler may be called in - * either DMA mode or Slave mode. - */ -static int32_t handle_hc_babble_intr(dwc_otg_hcd_t *hcd, - dwc_hc_t *hc, - dwc_otg_hc_regs_t *hc_regs, - dwc_otg_qtd_t *qtd) -{ - DWC_DEBUGPL(DBG_HCD, "--Host Channel %d Interrupt: " - "Babble Error--\n", hc->hc_num); - if (hc->ep_type != DWC_OTG_EP_TYPE_ISOC) { - dwc_otg_hcd_complete_urb(hcd, qtd->urb, -EOVERFLOW); - halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_BABBLE_ERR); - } else { - dwc_otg_halt_status_e halt_status; - halt_status = update_isoc_urb_state(hcd, hc, hc_regs, qtd, - DWC_OTG_HC_XFER_BABBLE_ERR); - halt_channel(hcd, hc, qtd, halt_status); - } - disable_hc_int(hc_regs, bblerr); - return 1; -} - -/** - * Handles a host channel AHB error interrupt. This handler is only called in - * DMA mode. - */ -static int32_t handle_hc_ahberr_intr(dwc_otg_hcd_t *hcd, - dwc_hc_t *hc, - dwc_otg_hc_regs_t *hc_regs, - dwc_otg_qtd_t *qtd) -{ - hcchar_data_t hcchar; - hcsplt_data_t hcsplt; - hctsiz_data_t hctsiz; - uint32_t hcdma; - struct urb *urb = qtd->urb; - - DWC_DEBUGPL(DBG_HCD, "--Host Channel %d Interrupt: " - "AHB Error--\n", hc->hc_num); - - hcchar.d32 = dwc_read_reg32(&hc_regs->hcchar); - hcsplt.d32 = dwc_read_reg32(&hc_regs->hcsplt); - hctsiz.d32 = dwc_read_reg32(&hc_regs->hctsiz); - hcdma = dwc_read_reg32(&hc_regs->hcdma); - - DWC_ERROR("AHB ERROR, Channel %d\n", hc->hc_num); - DWC_ERROR(" hcchar 0x%08x, hcsplt 0x%08x\n", hcchar.d32, hcsplt.d32); - DWC_ERROR(" hctsiz 0x%08x, hcdma 0x%08x\n", hctsiz.d32, hcdma); - DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD URB Enqueue\n"); - DWC_ERROR(" Device address: %d\n", usb_pipedevice(urb->pipe)); - DWC_ERROR(" Endpoint: %d, %s\n", usb_pipeendpoint(urb->pipe), - (usb_pipein(urb->pipe) ? "IN" : "OUT")); - DWC_ERROR(" Endpoint type: %s\n", - ({char *pipetype; - switch (usb_pipetype(urb->pipe)) { - case PIPE_CONTROL: pipetype = "CONTROL"; break; - case PIPE_BULK: pipetype = "BULK"; break; - case PIPE_INTERRUPT: pipetype = "INTERRUPT"; break; - case PIPE_ISOCHRONOUS: pipetype = "ISOCHRONOUS"; break; - default: pipetype = "UNKNOWN"; break; - }; pipetype;})); - DWC_ERROR(" Speed: %s\n", - ({char *speed; - switch (urb->dev->speed) { - case USB_SPEED_HIGH: speed = "HIGH"; break; - case USB_SPEED_FULL: speed = "FULL"; break; - case USB_SPEED_LOW: speed = "LOW"; break; - default: speed = "UNKNOWN"; break; - }; speed;})); - DWC_ERROR(" Max packet size: %d\n", - usb_maxpacket(urb->dev, urb->pipe, usb_pipeout(urb->pipe))); - DWC_ERROR(" Data buffer length: %d\n", urb->transfer_buffer_length); - DWC_ERROR(" Transfer buffer: %p, Transfer DMA: %p\n", - urb->transfer_buffer, (void *)urb->transfer_dma); - DWC_ERROR(" Setup buffer: %p, Setup DMA: %p\n", - urb->setup_packet, (void *)urb->setup_dma); - DWC_ERROR(" Interval: %d\n", urb->interval); - - dwc_otg_hcd_complete_urb(hcd, urb, -EIO); - - /* - * Force a channel halt. Don't call halt_channel because that won't - * write to the HCCHARn register in DMA mode to force the halt. - */ - dwc_otg_hc_halt(hcd->core_if, hc, DWC_OTG_HC_XFER_AHB_ERR); - - disable_hc_int(hc_regs, ahberr); - return 1; -} - -/** - * Handles a host channel transaction error interrupt. This handler may be - * called in either DMA mode or Slave mode. - */ -static int32_t handle_hc_xacterr_intr(dwc_otg_hcd_t *hcd, - dwc_hc_t *hc, - dwc_otg_hc_regs_t *hc_regs, - dwc_otg_qtd_t *qtd) -{ - DWC_DEBUGPL(DBG_HCD, "--Host Channel %d Interrupt: " - "Transaction Error--\n", hc->hc_num); - - switch (usb_pipetype(qtd->urb->pipe)) { - case PIPE_CONTROL: - case PIPE_BULK: - qtd->error_count++; - if (!hc->qh->ping_state) { - update_urb_state_xfer_intr(hc, hc_regs, qtd->urb, - qtd, DWC_OTG_HC_XFER_XACT_ERR); - save_data_toggle(hc, hc_regs, qtd); - if (!hc->ep_is_in && qtd->urb->dev->speed == USB_SPEED_HIGH) { - hc->qh->ping_state = 1; - } - } - - /* - * Halt the channel so the transfer can be re-started from - * the appropriate point or the PING protocol will start. - */ - halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_XACT_ERR); - break; - case PIPE_INTERRUPT: - qtd->error_count++; - if (hc->do_split && hc->complete_split) { - qtd->complete_split = 0; - } - halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_XACT_ERR); - break; - case PIPE_ISOCHRONOUS: - { - dwc_otg_halt_status_e halt_status; - halt_status = update_isoc_urb_state(hcd, hc, hc_regs, qtd, - DWC_OTG_HC_XFER_XACT_ERR); - - halt_channel(hcd, hc, qtd, halt_status); - } - break; - } - - disable_hc_int(hc_regs, xacterr); - - return 1; -} - -/** - * Handles a host channel frame overrun interrupt. This handler may be called - * in either DMA mode or Slave mode. - */ -static int32_t handle_hc_frmovrun_intr(dwc_otg_hcd_t *hcd, - dwc_hc_t *hc, - dwc_otg_hc_regs_t *hc_regs, - dwc_otg_qtd_t *qtd) -{ - DWC_DEBUGPL(DBG_HCD, "--Host Channel %d Interrupt: " - "Frame Overrun--\n", hc->hc_num); - - switch (usb_pipetype(qtd->urb->pipe)) { - case PIPE_CONTROL: - case PIPE_BULK: - break; - case PIPE_INTERRUPT: - halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_FRAME_OVERRUN); - break; - case PIPE_ISOCHRONOUS: - { - dwc_otg_halt_status_e halt_status; - halt_status = update_isoc_urb_state(hcd, hc, hc_regs, qtd, - DWC_OTG_HC_XFER_FRAME_OVERRUN); - - halt_channel(hcd, hc, qtd, halt_status); - } - break; - } - - disable_hc_int(hc_regs, frmovrun); - - return 1; -} - -/** - * Handles a host channel data toggle error interrupt. This handler may be - * called in either DMA mode or Slave mode. - */ -static int32_t handle_hc_datatglerr_intr(dwc_otg_hcd_t *hcd, - dwc_hc_t *hc, - dwc_otg_hc_regs_t *hc_regs, - dwc_otg_qtd_t *qtd) -{ - DWC_DEBUGPL(DBG_HCD, "--Host Channel %d Interrupt: " - "Data Toggle Error--\n", hc->hc_num); - - if (hc->ep_is_in) { - qtd->error_count = 0; - } else { - DWC_ERROR("Data Toggle Error on OUT transfer," - "channel %d\n", hc->hc_num); - } - - disable_hc_int(hc_regs, datatglerr); - - return 1; -} - -#ifdef DEBUG -/** - * This function is for debug only. It checks that a valid halt status is set - * and that HCCHARn.chdis is clear. If there's a problem, corrective action is - * taken and a warning is issued. - * @return 1 if halt status is ok, 0 otherwise. - */ -static inline int halt_status_ok(dwc_otg_hcd_t *hcd, - dwc_hc_t *hc, - dwc_otg_hc_regs_t *hc_regs, - dwc_otg_qtd_t *qtd) -{ - hcchar_data_t hcchar; - hctsiz_data_t hctsiz; - hcint_data_t hcint; - hcintmsk_data_t hcintmsk; - hcsplt_data_t hcsplt; - - if (hc->halt_status == DWC_OTG_HC_XFER_NO_HALT_STATUS) { - /* - * This code is here only as a check. This condition should - * never happen. Ignore the halt if it does occur. - */ - hcchar.d32 = dwc_read_reg32(&hc_regs->hcchar); - hctsiz.d32 = dwc_read_reg32(&hc_regs->hctsiz); - hcint.d32 = dwc_read_reg32(&hc_regs->hcint); - hcintmsk.d32 = dwc_read_reg32(&hc_regs->hcintmsk); - hcsplt.d32 = dwc_read_reg32(&hc_regs->hcsplt); - DWC_WARN("%s: hc->halt_status == DWC_OTG" - "channel %d, hcchar 0x%08x, hctsiz 0x%08x, " - "hcint 0x%08x, hcintmsk 0x%08x, " - "hcsplt 0x%08x, qtd->complete_split %d\n", - __func__, hc->hc_num, hcchar.d32, hctsiz.d32, - hcint.d32, hcintmsk.d32, - hcsplt.d32, qtd->complete_split); - - DWC_WARN("%s: no halt status, channel %d, ignoring interrupt\n", - __func__, hc->hc_num); - DWC_WARN("\n"); - clear_hc_int(hc_regs, chhltd); - return 0; - } - - /* - * This code is here only as a check. hcchar.chdis should - * never be set when the halt interrupt occurs. Halt the - * channel again if it does occur. - */ - hcchar.d32 = dwc_read_reg32(&hc_regs->hcchar); - if (hcchar.b.chdis) { - DWC_WARN("%s: hcchar.chdis set unexpectedly, " - "hcchar 0x%08x, trying to halt again\n", - __func__, hcchar.d32); - clear_hc_int(hc_regs, chhltd); - hc->halt_pending = 0; - halt_channel(hcd, hc, qtd, hc->halt_status); - return 0; - } - - return 1; -} -#endif - -/** - * Handles a host Channel Halted interrupt in DMA mode. This handler - * determines the reason the channel halted and proceeds accordingly. - */ -static void handle_hc_chhltd_intr_dma(dwc_otg_hcd_t *hcd, - dwc_hc_t *hc, - dwc_otg_hc_regs_t *hc_regs, - dwc_otg_qtd_t *qtd) -{ - hcint_data_t hcint; - hcintmsk_data_t hcintmsk; - int out_nak_enh = 0; - - /* For core with OUT NAK enhancement, the flow for high- - * speed CONTROL/BULK OUT is handled a little differently. - */ - if (hcd->core_if->snpsid >= 0x4F54271A) { - if (hc->speed == DWC_OTG_EP_SPEED_HIGH && !hc->ep_is_in && - (hc->ep_type == DWC_OTG_EP_TYPE_CONTROL || - hc->ep_type == DWC_OTG_EP_TYPE_BULK)) { - printk(KERN_DEBUG "OUT NAK enhancement enabled\n"); - out_nak_enh = 1; - } else { - printk(KERN_DEBUG "OUT NAK enhancement disabled, not HS Ctrl/Bulk OUT EP\n"); - } - } else { -// printk(KERN_DEBUG "OUT NAK enhancement disabled, no core support\n"); - } - - if (hc->halt_status == DWC_OTG_HC_XFER_URB_DEQUEUE || - hc->halt_status == DWC_OTG_HC_XFER_AHB_ERR) { - /* - * Just release the channel. A dequeue can happen on a - * transfer timeout. In the case of an AHB Error, the channel - * was forced to halt because there's no way to gracefully - * recover. - */ - release_channel(hcd, hc, qtd, hc->halt_status); - return; - } - - /* Read the HCINTn register to determine the cause for the halt. */ - hcint.d32 = dwc_read_reg32(&hc_regs->hcint); - hcintmsk.d32 = dwc_read_reg32(&hc_regs->hcintmsk); - - if (hcint.b.xfercomp) { - /** @todo This is here because of a possible hardware bug. Spec - * says that on SPLIT-ISOC OUT transfers in DMA mode that a HALT - * interrupt w/ACK bit set should occur, but I only see the - * XFERCOMP bit, even with it masked out. This is a workaround - * for that behavior. Should fix this when hardware is fixed. - */ - if (hc->ep_type == DWC_OTG_EP_TYPE_ISOC && !hc->ep_is_in) { - handle_hc_ack_intr(hcd, hc, hc_regs, qtd); - } - handle_hc_xfercomp_intr(hcd, hc, hc_regs, qtd); - } else if (hcint.b.stall) { - handle_hc_stall_intr(hcd, hc, hc_regs, qtd); - } else if (hcint.b.xacterr) { - if (out_nak_enh) { - if (hcint.b.nyet || hcint.b.nak || hcint.b.ack) { - printk(KERN_DEBUG "XactErr with NYET/NAK/ACK\n"); - qtd->error_count = 0; - } else { - printk(KERN_DEBUG "XactErr without NYET/NAK/ACK\n"); - } - } - - /* - * Must handle xacterr before nak or ack. Could get a xacterr - * at the same time as either of these on a BULK/CONTROL OUT - * that started with a PING. The xacterr takes precedence. - */ - handle_hc_xacterr_intr(hcd, hc, hc_regs, qtd); - } else if (!out_nak_enh) { - if (hcint.b.nyet) { - /* - * Must handle nyet before nak or ack. Could get a nyet at the - * same time as either of those on a BULK/CONTROL OUT that - * started with a PING. The nyet takes precedence. - */ - handle_hc_nyet_intr(hcd, hc, hc_regs, qtd); - } else if (hcint.b.bblerr) { - handle_hc_babble_intr(hcd, hc, hc_regs, qtd); - } else if (hcint.b.frmovrun) { - handle_hc_frmovrun_intr(hcd, hc, hc_regs, qtd); - } else if (hcint.b.nak && !hcintmsk.b.nak) { - /* - * If nak is not masked, it's because a non-split IN transfer - * is in an error state. In that case, the nak is handled by - * the nak interrupt handler, not here. Handle nak here for - * BULK/CONTROL OUT transfers, which halt on a NAK to allow - * rewinding the buffer pointer. - */ - handle_hc_nak_intr(hcd, hc, hc_regs, qtd); - } else if (hcint.b.ack && !hcintmsk.b.ack) { - /* - * If ack is not masked, it's because a non-split IN transfer - * is in an error state. In that case, the ack is handled by - * the ack interrupt handler, not here. Handle ack here for - * split transfers. Start splits halt on ACK. - */ - handle_hc_ack_intr(hcd, hc, hc_regs, qtd); - } else { - if (hc->ep_type == DWC_OTG_EP_TYPE_INTR || - hc->ep_type == DWC_OTG_EP_TYPE_ISOC) { - /* - * A periodic transfer halted with no other channel - * interrupts set. Assume it was halted by the core - * because it could not be completed in its scheduled - * (micro)frame. - */ -#ifdef DEBUG - DWC_PRINT("%s: Halt channel %d (assume incomplete periodic transfer)\n", - __func__, hc->hc_num); -#endif - halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_PERIODIC_INCOMPLETE); - } else { - DWC_ERROR("%s: Channel %d, DMA Mode -- ChHltd set, but reason " - "for halting is unknown, hcint 0x%08x, intsts 0x%08x\n", - __func__, hc->hc_num, hcint.d32, - dwc_read_reg32(&hcd->core_if->core_global_regs->gintsts)); - } - } - } else { - printk(KERN_DEBUG "NYET/NAK/ACK/other in non-error case, 0x%08x\n", hcint.d32); - } -} - -/** - * Handles a host channel Channel Halted interrupt. - * - * In slave mode, this handler is called only when the driver specifically - * requests a halt. This occurs during handling other host channel interrupts - * (e.g. nak, xacterr, stall, nyet, etc.). - * - * In DMA mode, this is the interrupt that occurs when the core has finished - * processing a transfer on a channel. Other host channel interrupts (except - * ahberr) are disabled in DMA mode. - */ -static int32_t handle_hc_chhltd_intr(dwc_otg_hcd_t *hcd, - dwc_hc_t *hc, - dwc_otg_hc_regs_t *hc_regs, - dwc_otg_qtd_t *qtd) -{ - DWC_DEBUGPL(DBG_HCD, "--Host Channel %d Interrupt: " - "Channel Halted--\n", hc->hc_num); - - if (hcd->core_if->dma_enable) { - handle_hc_chhltd_intr_dma(hcd, hc, hc_regs, qtd); - } else { -#ifdef DEBUG - if (!halt_status_ok(hcd, hc, hc_regs, qtd)) { - return 1; - } -#endif - release_channel(hcd, hc, qtd, hc->halt_status); - } - - return 1; -} - -/** Handles interrupt for a specific Host Channel */ -int32_t dwc_otg_hcd_handle_hc_n_intr(dwc_otg_hcd_t *dwc_otg_hcd, uint32_t num) -{ - int retval = 0; - hcint_data_t hcint; - hcintmsk_data_t hcintmsk; - dwc_hc_t *hc; - dwc_otg_hc_regs_t *hc_regs; - dwc_otg_qtd_t *qtd; - - DWC_DEBUGPL(DBG_HCDV, "--Host Channel Interrupt--, Channel %d\n", num); - - hc = dwc_otg_hcd->hc_ptr_array[num]; - hc_regs = dwc_otg_hcd->core_if->host_if->hc_regs[num]; - qtd = list_entry(hc->qh->qtd_list.next, dwc_otg_qtd_t, qtd_list_entry); - - hcint.d32 = dwc_read_reg32(&hc_regs->hcint); - hcintmsk.d32 = dwc_read_reg32(&hc_regs->hcintmsk); - DWC_DEBUGPL(DBG_HCDV, " hcint 0x%08x, hcintmsk 0x%08x, hcint&hcintmsk 0x%08x\n", - hcint.d32, hcintmsk.d32, (hcint.d32 & hcintmsk.d32)); - hcint.d32 = hcint.d32 & hcintmsk.d32; - - if (!dwc_otg_hcd->core_if->dma_enable) { - if (hcint.b.chhltd && hcint.d32 != 0x2) { - hcint.b.chhltd = 0; - } - } - - if (hcint.b.xfercomp) { - retval |= handle_hc_xfercomp_intr(dwc_otg_hcd, hc, hc_regs, qtd); - /* - * If NYET occurred at same time as Xfer Complete, the NYET is - * handled by the Xfer Complete interrupt handler. Don't want - * to call the NYET interrupt handler in this case. - */ - hcint.b.nyet = 0; - } - if (hcint.b.chhltd) { - retval |= handle_hc_chhltd_intr(dwc_otg_hcd, hc, hc_regs, qtd); - } - if (hcint.b.ahberr) { - retval |= handle_hc_ahberr_intr(dwc_otg_hcd, hc, hc_regs, qtd); - } - if (hcint.b.stall) { - retval |= handle_hc_stall_intr(dwc_otg_hcd, hc, hc_regs, qtd); - } - if (hcint.b.nak) { - retval |= handle_hc_nak_intr(dwc_otg_hcd, hc, hc_regs, qtd); - } - if (hcint.b.ack) { - retval |= handle_hc_ack_intr(dwc_otg_hcd, hc, hc_regs, qtd); - } - if (hcint.b.nyet) { - retval |= handle_hc_nyet_intr(dwc_otg_hcd, hc, hc_regs, qtd); - } - if (hcint.b.xacterr) { - retval |= handle_hc_xacterr_intr(dwc_otg_hcd, hc, hc_regs, qtd); - } - if (hcint.b.bblerr) { - retval |= handle_hc_babble_intr(dwc_otg_hcd, hc, hc_regs, qtd); - } - if (hcint.b.frmovrun) { - retval |= handle_hc_frmovrun_intr(dwc_otg_hcd, hc, hc_regs, qtd); - } - if (hcint.b.datatglerr) { - retval |= handle_hc_datatglerr_intr(dwc_otg_hcd, hc, hc_regs, qtd); - } - - return retval; -} - -#endif /* DWC_DEVICE_ONLY */ |