?? dwc_otg_hcd_intr.c
字號:
/* ========================================================================== * * 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 = IRQ_NONE; 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 (likely(dwc_otg_is_host_mode(core_if))) { gintsts.d32 = dwc_otg_read_core_intr(core_if); if (unlikely(!gintsts.d32)) { return IRQ_NONE; } if (gintsts.b.sofintr) { core_if->irq_pat[0]++; retval |= dwc_otg_hcd_handle_sof_intr(_dwc_otg_hcd); } if (gintsts.b.rxstsqlvl) { core_if->irq_pat[1]++; retval |= dwc_otg_hcd_handle_rx_status_q_level_intr(_dwc_otg_hcd); } if (gintsts.b.nptxfempty) { core_if->irq_pat[2]++; retval |= dwc_otg_hcd_handle_np_tx_fifo_empty_intr(_dwc_otg_hcd); } if (gintsts.b.i2cintr) { core_if->irq_pat[3]++; /** @todo Implement i2cintr handler. */ } if (gintsts.b.portintr) { core_if->irq_pat[4]++; retval |= dwc_otg_hcd_handle_port_intr(_dwc_otg_hcd); } if (gintsts.b.hcintr) { core_if->irq_pat[5]++; retval |= dwc_otg_hcd_handle_hc_intr(_dwc_otg_hcd); } if (gintsts.b.ptxfempty) { core_if->irq_pat[6]++; retval |= dwc_otg_hcd_handle_perio_tx_fifo_empty_intr(_dwc_otg_hcd); } } 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) { dbg_otg("%s() for queue_trans\n", __FUNCTION__); _hcd->core_if->irq_pat[9]++; 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 */#if 0 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); } }#else haint.d32 = readl(S3C_UDC_OTG_HAINT); do { i = ffs(haint.d32) - 1; retval |= dwc_otg_hcd_handle_hc_n_intr(_dwc_otg_hcd, i); dbg_otg("haint.d32: %08x, %d\n", haint.d32, i); haint.d32 &= ~(1 << i); } while (haint.d32);#endif 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)
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -