/* ========================================================================== * $File: //dwh/usb_iip/dev/software/otg_ipmate/linux/drivers/dwc_otg_pcd_intr.c $ * $Revision: 1.1 $ * $Date: 2007-11-19 05:39:07 $ * $Change: 791271 $ * * 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_HOST_ONLY #include #include #include "dwc_otg_driver.h" #include "dwc_otg_pcd.h" #include #if 1//defined(CONFIG_RTL_ULINKER_BRSC) #include "linux/ulinker_brsc.h" #endif #define DEBUG_EP0 /* request functions defined in "dwc_otg_pcd.c" */ extern void request_done( dwc_otg_pcd_ep_t *_ep, dwc_otg_pcd_request_t *_req, int _status); extern void request_nuke( dwc_otg_pcd_ep_t *_ep ); extern void dwc_otg_pcd_update_otg( dwc_otg_pcd_t *_pcd, const unsigned _reset ); /** @file * This file contains the implementation of the PCD Interrupt handlers. * * The PCD handles the device interrupts. Many conditions can cause a * device interrupt. When an interrupt occurs, the device interrupt * service routine determines the cause of the interrupt and * dispatches handling to the appropriate function. These interrupt * handling functions are described below. * All interrupt registers are processed from LSB to MSB. */ /** * This function prints the ep0 state for debug purposes. */ static inline void print_ep0_state( dwc_otg_pcd_t *_pcd ) { #ifdef DEBUG char str[40]; switch (_pcd->ep0state) { case EP0_DISCONNECT: strcpy(str, "EP0_DISCONNECT"); break; case EP0_IDLE: strcpy(str, "EP0_IDLE"); break; case EP0_IN_DATA_PHASE: strcpy(str, "EP0_IN_DATA_PHASE"); break; case EP0_OUT_DATA_PHASE: strcpy(str, "EP0_OUT_DATA_PHASE"); break; case EP0_STATUS: strcpy(str,"EP0_STATUS"); break; case EP0_STALL: strcpy(str,"EP0_STALL"); break; default: strcpy(str,"EP0_INVALID"); } DWC_DEBUGPL(DBG_ANY, "%s(%d)\n", str, _pcd->ep0state); #endif } /** * This function returns pointer to in ep struct with number ep_num */ static inline dwc_otg_pcd_ep_t* get_in_ep( dwc_otg_pcd_t *_pcd, uint32_t ep_num) { int i; //int num_in_eps = GET_CORE_IF(_pcd)->dev_if->num_in_eps; if(ep_num == 0) { return &_pcd->ep0; } else { for(i = 0; i < MAX_EPS_CHANNELS; ++i) { if( (i!=0) && (_pcd->otg_dev->core_if->dev_if->isIN_EP[i]==0) ) continue; if(_pcd->in_ep[i].dwc_ep.num == ep_num) return &_pcd->in_ep[i]; } return 0; } } /** * This function returns pointer to out ep struct with number ep_num */ static inline dwc_otg_pcd_ep_t* get_out_ep( dwc_otg_pcd_t *_pcd, uint32_t ep_num) { int i; //int num_out_eps = GET_CORE_IF(_pcd)->dev_if->num_out_eps; if(ep_num == 0) { return &_pcd->ep0; } else { for(i = 0; i < MAX_EPS_CHANNELS; ++i) { if( (i!=0) && (_pcd->otg_dev->core_if->dev_if->isIN_EP[i]!=0) ) continue; if(_pcd->out_ep[i].dwc_ep.num == ep_num) return &_pcd->out_ep[i]; } return 0; } } /** * This functions gets a pointer to an EP from the wIndex address * value of the control request. */ static dwc_otg_pcd_ep_t *get_ep_by_addr (dwc_otg_pcd_t *_pcd, u16 _wIndex) { dwc_otg_pcd_ep_t *ep; if ((_wIndex & USB_ENDPOINT_NUMBER_MASK) == 0) return &_pcd->ep0; list_for_each_entry( ep, &_pcd->gadget.ep_list, ep.ep_list) { u8 bEndpointAddress; if (!ep->desc) continue; bEndpointAddress = ep->desc->bEndpointAddress; if ((_wIndex ^ bEndpointAddress) & USB_DIR_IN) continue; if ((_wIndex & 0x0f) == (bEndpointAddress & 0x0f)) return ep; } return NULL; } /** * This function checks the EP request queue, if the queue is not * empty the next request is started. */ void start_next_request( dwc_otg_pcd_ep_t *_ep ) { dwc_otg_pcd_request_t *req = 0; if (!list_empty(&_ep->queue)) { req = list_entry(_ep->queue.next, dwc_otg_pcd_request_t, queue); /* Setup and start the Transfer */ _ep->dwc_ep.start_xfer_buff = req->req.buf; _ep->dwc_ep.xfer_buff = req->req.buf; _ep->dwc_ep.xfer_len = req->req.length; _ep->dwc_ep.xfer_count = 0; //_ep->dwc_ep.dma_addr = req->req.dma; //cathy _ep->dwc_ep.dma_addr = (u32)req->req.buf & ~Uncache_Mask; _ep->dwc_ep.sent_zlp = 0; _ep->dwc_ep.total_len = _ep->dwc_ep.xfer_len; //DWC_ERROR(" -> starting transfer (start_next_req) %s %s\n", //_ep->ep.name, _ep->dwc_ep.is_in?"IN":"OUT"); dwc_otg_ep_start_transfer( GET_CORE_IF(_ep->pcd), &_ep->dwc_ep ); } } /** * This function handles the SOF Interrupts. At this time the SOF * Interrupt is disabled. */ int32_t dwc_otg_pcd_handle_sof_intr(dwc_otg_pcd_t *_pcd) { dwc_otg_core_if_t *core_if = GET_CORE_IF(_pcd); gintsts_data_t gintsts; //DWC_DEBUGPL(DBG_PCD, "SOF\n"); /* Clear interrupt */ gintsts.d32 = 0; gintsts.b.sofintr = 1; dwc_write_reg32 (&core_if->core_global_regs->gintsts, gintsts.d32); return 1; } /** * This function handles the Rx Status Queue Level Interrupt, which * indicates that there is a least one packet in the Rx FIFO. The * packets are moved from the FIFO to memory, where they will be * processed when the Endpoint Interrupt Register indicates Transfer * Complete or SETUP Phase Done. * * Repeat the following until the Rx Status Queue is empty: * -# Read the Receive Status Pop Register (GRXSTSP) to get Packet * info * -# If Receive FIFO is empty then skip to step Clear the interrupt * and exit * -# If SETUP Packet call dwc_otg_read_setup_packet to copy the * SETUP data to the buffer * -# If OUT Data Packet call dwc_otg_read_packet to copy the data * to the destination buffer */ int32_t dwc_otg_pcd_handle_rx_status_q_level_intr(dwc_otg_pcd_t *_pcd) { dwc_otg_core_if_t *core_if = GET_CORE_IF(_pcd); dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; gintmsk_data_t gintmask = {.d32=0}; device_grxsts_data_t status; dwc_otg_pcd_ep_t *ep; gintsts_data_t gintsts; #ifdef DEBUG static char *dpid_str[] ={ "D0", "D2", "D1", "MDATA" }; #endif //DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, _pcd); /* Disable the Rx Status Queue Level interrupt */ gintmask.b.rxstsqlvl= 1; dwc_modify_reg32( &global_regs->gintmsk, gintmask.d32, 0); /* Get the Status from the top of the FIFO */ status.d32 = dwc_read_reg32( &global_regs->grxstsp ); DWC_DEBUGPL(DBG_PCD, "EP:%d BCnt:%d DPID:%s " "pktsts:%x Frame:%d(0x%0x)\n", status.b.epnum, status.b.bcnt, dpid_str[status.b.dpid], status.b.pktsts, status.b.fn, status.b.fn); /* Get pointer to EP structure */ ep = get_out_ep(_pcd, status.b.epnum); // ep = &_pcd->out_ep[ status.b.epnum - 1]; switch (status.b.pktsts) { case DWC_DSTS_GOUT_NAK: DWC_DEBUGPL(DBG_PCDV, "Global OUT NAK\n"); break; case DWC_STS_DATA_UPDT: DWC_DEBUGPL(DBG_PCDV, "OUT Data Packet\n"); if (status.b.bcnt && ep->dwc_ep.xfer_buff) { /** @todo NGS Check for buffer overflow? */ dwc_otg_read_packet( core_if, ep->dwc_ep.xfer_buff, status.b.bcnt); ep->dwc_ep.xfer_count += status.b.bcnt; ep->dwc_ep.xfer_buff += status.b.bcnt; } break; case DWC_STS_XFER_COMP: DWC_DEBUGPL(DBG_PCDV, "OUT Complete\n"); break; case DWC_DSTS_SETUP_COMP: #ifdef DEBUG_EP0 DWC_DEBUGPL(DBG_PCDV, "Setup Complete\n"); #endif break; case DWC_DSTS_SETUP_UPDT: dwc_otg_read_setup_packet( core_if, _pcd->setup_pkt->d32); #ifdef DEBUG_EP0 DWC_DEBUGPL(DBG_PCD, "SETUP PKT: %02x.%02x v%04x i%04x l%04x\n", _pcd->setup_pkt->req.bRequestType, _pcd->setup_pkt->req.bRequest, _pcd->setup_pkt->req.wValue, _pcd->setup_pkt->req.wIndex, _pcd->setup_pkt->req.wLength); #endif ep->dwc_ep.xfer_count += status.b.bcnt; break; default: DWC_DEBUGPL(DBG_PCDV, "Invalid Packet Status (0x%0x)\n", status.b.pktsts); break; } /* Enable the Rx Status Queue Level interrupt */ dwc_modify_reg32( &global_regs->gintmsk, 0, gintmask.d32); /* Clear interrupt */ gintsts.d32 = 0; gintsts.b.rxstsqlvl = 1; dwc_write_reg32 (&global_regs->gintsts, gintsts.d32); //DWC_DEBUGPL(DBG_PCDV, "EXIT: %s\n", __func__); return 1; } /** * This function examines the Device IN Token Learning Queue to * determine the EP number of the last IN token received. This * implementation is for the Mass Storage device where there are only * 2 IN EPs (Control-IN and BULK-IN). * * The EP numbers for the first six IN Tokens are in DTKNQR1 and there * are 8 EP Numbers in each of the other possible DTKNQ Registers. * * @param _core_if Programming view of DWC_otg controller. * */ static inline int get_ep_of_last_in_token(dwc_otg_core_if_t *_core_if) { dwc_otg_device_global_regs_t *dev_global_regs = _core_if->dev_if->dev_global_regs; const uint32_t TOKEN_Q_DEPTH = _core_if->hwcfg2.b.dev_token_q_depth; /* Number of Token Queue Registers */ const int DTKNQ_REG_CNT = (TOKEN_Q_DEPTH + 7) / 8; dtknq1_data_t dtknqr1; uint32_t in_tkn_epnums[4]; int ndx = 0; int i = 0; volatile uint32_t *addr = &dev_global_regs->dtknqr1; int epnum = 0; //DWC_DEBUGPL(DBG_PCD,"dev_token_q_depth=%d\n",TOKEN_Q_DEPTH); /* Read the DTKNQ Registers */ for (i = 0; i < DTKNQ_REG_CNT; i++) { in_tkn_epnums[ i ] = dwc_read_reg32(addr); DWC_DEBUGPL(DBG_PCDV, "DTKNQR%d=0x%08x\n", i+1, in_tkn_epnums[i]); if (addr == &dev_global_regs->dvbusdis) { addr = &dev_global_regs->dtknqr3_dthrctl; } else { ++addr; } } /* Copy the DTKNQR1 data to the bit field. */ dtknqr1.d32 = in_tkn_epnums[0]; /* Get the EP numbers */ in_tkn_epnums[0] = dtknqr1.b.epnums0_5; ndx = dtknqr1.b.intknwptr - 1; //DWC_DEBUGPL(DBG_PCDV,"ndx=%d\n",ndx); if (ndx == -1) { /** @todo Find a simpler way to calculate the max * queue position.*/ int cnt = TOKEN_Q_DEPTH; if (TOKEN_Q_DEPTH <= 6) { cnt = TOKEN_Q_DEPTH - 1; } else if (TOKEN_Q_DEPTH <= 14) { cnt = TOKEN_Q_DEPTH - 7; } else if (TOKEN_Q_DEPTH <= 22) { cnt = TOKEN_Q_DEPTH - 15; } else { cnt = TOKEN_Q_DEPTH - 23; } epnum = (in_tkn_epnums[ DTKNQ_REG_CNT - 1 ] >> (cnt * 4)) & 0xF; } else { if (ndx <= 5) { epnum = (in_tkn_epnums[0] >> (ndx * 4)) & 0xF; } else if (ndx <= 13 ) { ndx -= 6; epnum = (in_tkn_epnums[1] >> (ndx * 4)) & 0xF; } else if (ndx <= 21 ) { ndx -= 14; epnum = (in_tkn_epnums[2] >> (ndx * 4)) & 0xF; } else if (ndx <= 29 ) { ndx -= 22; epnum = (in_tkn_epnums[3] >> (ndx * 4)) & 0xF; } } //DWC_DEBUGPL(DBG_PCD,"epnum=%d\n",epnum); return epnum; } /** * This interrupt occurs when the non-periodic Tx FIFO is half-empty. * The active request is checked for the next packet to be loaded into * the non-periodic Tx FIFO. */ int32_t dwc_otg_pcd_handle_np_tx_fifo_empty_intr(dwc_otg_pcd_t *_pcd) { dwc_otg_core_if_t *core_if = GET_CORE_IF(_pcd); dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; dwc_otg_dev_in_ep_regs_t *ep_regs; gnptxsts_data_t txstatus = {.d32 = 0}; gintsts_data_t gintsts; int epnum = 0; dwc_otg_pcd_ep_t *ep = 0; uint32_t len = 0; int dwords; /* Get the epnum from the IN Token Learning Queue. */ epnum = get_ep_of_last_in_token(core_if); ep = get_in_ep(_pcd, epnum); /* if(epnum != 0) ep = &_pcd->in_ep[epnum-1]; else ep = &_pcd->ep0; */ DWC_DEBUGPL(DBG_PCD, "NP TxFifo Empty: %s(%d) \n", ep->ep.name, epnum ); ep_regs = core_if->dev_if->in_ep_regs[epnum]; len = ep->dwc_ep.xfer_len - ep->dwc_ep.xfer_count; if (len > ep->dwc_ep.maxpacket) { len = ep->dwc_ep.maxpacket; } dwords = (len + 3)/4; /* While there is space in the queue and space in the FIFO and * More data to tranfer, Write packets to the Tx FIFO */ txstatus.d32 = dwc_read_reg32( &global_regs->gnptxsts ); DWC_DEBUGPL(DBG_PCDV, "b4 GNPTXSTS=0x%08x\n",txstatus.d32); while (txstatus.b.nptxqspcavail > 0 && txstatus.b.nptxfspcavail > dwords && ep->dwc_ep.xfer_count < ep->dwc_ep.xfer_len) { /* Write the FIFO */ dwc_otg_ep_write_packet( core_if, &ep->dwc_ep, 0 ); len = ep->dwc_ep.xfer_len - ep->dwc_ep.xfer_count; if (len > ep->dwc_ep.maxpacket) { len = ep->dwc_ep.maxpacket; } dwords = (len + 3)/4; txstatus.d32 = dwc_read_reg32(&global_regs->gnptxsts); DWC_DEBUGPL(DBG_PCDV,"GNPTXSTS=0x%08x\n",txstatus.d32); } DWC_DEBUGPL(DBG_PCDV, "GNPTXSTS=0x%08x\n", dwc_read_reg32( &global_regs->gnptxsts)); /* Clear interrupt */ gintsts.d32 = 0; gintsts.b.nptxfempty = 1; dwc_write_reg32 (&global_regs->gintsts, gintsts.d32); return 1; } /** * This function is called when dedicated Tx FIFO Empty interrupt occurs. * The active request is checked for the next packet to be loaded into * apropriate Tx FIFO. */ static int32_t write_empty_tx_fifo(dwc_otg_pcd_t *_pcd, uint32_t epnum) { dwc_otg_core_if_t *core_if = GET_CORE_IF(_pcd); dwc_otg_dev_if_t* dev_if = core_if->dev_if; dwc_otg_dev_in_ep_regs_t *ep_regs; dtxfsts_data_t txstatus = {.d32 = 0}; dwc_otg_pcd_ep_t *ep = 0; uint32_t len = 0; int dwords; ep = get_in_ep(_pcd, epnum); /* if(epnum != 0) ep = &_pcd->in_ep[epnum-1]; else ep = &_pcd->ep0; */ DWC_DEBUGPL(DBG_PCD, "Dedicated TxFifo Empty: %s(%d) \n", ep->ep.name, epnum ); ep_regs = core_if->dev_if->in_ep_regs[epnum]; len = ep->dwc_ep.xfer_len - ep->dwc_ep.xfer_count; if (len > ep->dwc_ep.maxpacket) { len = ep->dwc_ep.maxpacket; } dwords = (len + 3)/4; /* While there is space in the queue and space in the FIFO and * More data to tranfer, Write packets to the Tx FIFO */ txstatus.d32 = dwc_read_reg32( &dev_if->in_ep_regs[epnum]->dtxfsts); DWC_DEBUGPL(DBG_PCDV, "b4 dtxfsts[%d]=0x%08x\n",epnum,txstatus.d32); while (txstatus.b.txfspcavail > dwords && ep->dwc_ep.xfer_count < ep->dwc_ep.xfer_len && ep->dwc_ep.xfer_len != 0) { /* Write the FIFO */ dwc_otg_ep_write_packet( core_if, &ep->dwc_ep, 0); len = ep->dwc_ep.xfer_len - ep->dwc_ep.xfer_count; if (len > ep->dwc_ep.maxpacket) { len = ep->dwc_ep.maxpacket; } dwords = (len + 3)/4; txstatus.d32 = dwc_read_reg32(&dev_if->in_ep_regs[epnum]->dtxfsts); DWC_DEBUGPL(DBG_PCDV,"dtxfsts[%d]=0x%08x\n", epnum, txstatus.d32); } DWC_DEBUGPL(DBG_PCDV, "b4 dtxfsts[%d]=0x%08x\n",epnum,dwc_read_reg32(&dev_if->in_ep_regs[epnum]->dtxfsts)); return 1; } /** * This function is called when the Device is disconnected. It stops * any active requests and informs the Gadget driver of the * disconnect. */ void dwc_otg_pcd_stop(dwc_otg_pcd_t *_pcd) { int i; //, num_in_eps, num_out_eps; dwc_otg_pcd_ep_t *ep; gintmsk_data_t intr_mask = {.d32 = 0}; //num_in_eps = GET_CORE_IF(_pcd)->dev_if->num_in_eps; //num_out_eps = GET_CORE_IF(_pcd)->dev_if->num_out_eps; DWC_DEBUGPL(DBG_PCDV, "%s() \n", __func__ ); /* don't disconnect drivers more than once */ if (_pcd->ep0state == EP0_DISCONNECT) { DWC_DEBUGPL(DBG_ANY, "%s() Already Disconnected\n", __func__ ); return; } _pcd->ep0state = EP0_DISCONNECT; /* Reset the OTG state. */ dwc_otg_pcd_update_otg( _pcd, 1); /* Disable the NP Tx Fifo Empty Interrupt. */ intr_mask.b.nptxfempty = 1; dwc_modify_reg32(&GET_CORE_IF(_pcd)->core_global_regs->gintmsk, intr_mask.d32, 0); /* Flush the FIFOs */ /**@todo NGS Flush Periodic FIFOs */ dwc_otg_flush_tx_fifo( GET_CORE_IF(_pcd), 0); dwc_otg_flush_rx_fifo( GET_CORE_IF(_pcd) ); /* prevent new request submissions, kill any outstanding requests */ ep = &_pcd->ep0; request_nuke(ep); /* prevent new request submissions, kill any outstanding requests */ for (i = 0; i < MAX_EPS_CHANNELS; i++) { if( (i!=0) && (_pcd->otg_dev->core_if->dev_if->isIN_EP[i]==0) ) continue; dwc_otg_pcd_ep_t *ep = &_pcd->in_ep[i]; request_nuke(ep); } /* prevent new request submissions, kill any outstanding requests */ for (i = 0; i < MAX_EPS_CHANNELS; i++) { if( (i!=0) && (_pcd->otg_dev->core_if->dev_if->isIN_EP[i]!=0) ) continue; dwc_otg_pcd_ep_t *ep = &_pcd->out_ep[i]; request_nuke(ep); } /* report disconnect; the driver is already quiesced */ if (_pcd->driver && _pcd->driver->disconnect) { SPIN_UNLOCK(&_pcd->lock); _pcd->driver->disconnect(&_pcd->gadget); SPIN_LOCK(&_pcd->lock); } } /** * This interrupt indicates that ... */ int32_t dwc_otg_pcd_handle_i2c_intr(dwc_otg_pcd_t *_pcd) { gintmsk_data_t intr_mask = { .d32 = 0}; gintsts_data_t gintsts; DWC_PRINT("INTERRUPT Handler not implemented for %s\n", "i2cintr"); intr_mask.b.i2cintr = 1; dwc_modify_reg32( &GET_CORE_IF(_pcd)->core_global_regs->gintmsk, intr_mask.d32, 0 ); /* Clear interrupt */ gintsts.d32 = 0; gintsts.b.i2cintr = 1; dwc_write_reg32 (&GET_CORE_IF(_pcd)->core_global_regs->gintsts, gintsts.d32); return 1; } /** * This interrupt indicates that ... */ int32_t dwc_otg_pcd_handle_early_suspend_intr(dwc_otg_pcd_t *_pcd) { gintsts_data_t gintsts; #if defined(VERBOSE) DWC_PRINT("Early Suspend Detected\n"); #endif /* Clear interrupt */ gintsts.d32 = 0; gintsts.b.erlysuspend = 1; GET_CORE_IF(_pcd)->dev_if->suspended = 1; dwc_write_reg32( &GET_CORE_IF(_pcd)->core_global_regs->gintsts, gintsts.d32); return 1; } /** * This function configures EPO to receive SETUP packets. * * @todo NGS: Update the comments from the HW FS. * * -# Program the following fields in the endpoint specific registers * for Control OUT EP 0, in order to receive a setup packet * - DOEPTSIZ0.Packet Count = 3 (To receive up to 3 back to back * setup packets) * - DOEPTSIZE0.Transfer Size = 24 Bytes (To receive up to 3 back * to back setup packets) * - In DMA mode, DOEPDMA0 Register with a memory address to * store any setup packets received * * @param _core_if Programming view of DWC_otg controller. * @param _pcd Programming view of the PCD. */ static inline void ep0_out_start( dwc_otg_core_if_t *_core_if, dwc_otg_pcd_t *_pcd ) { dwc_otg_dev_if_t *dev_if = _core_if->dev_if; deptsiz0_data_t doeptsize0 = { .d32 = 0}; #ifdef VERBOSE DWC_DEBUGPL(DBG_PCDV,"%s() doepctl0=%0x\n", __func__, dwc_read_reg32(&dev_if->out_ep_regs[0]->doepctl)); #endif doeptsize0.b.supcnt = 3; doeptsize0.b.pktcnt = 1; doeptsize0.b.xfersize = 8*3; dwc_write_reg32( &dev_if->out_ep_regs[0]->doeptsiz, doeptsize0.d32 ); if (_core_if->dma_enable) { depctl_data_t doepctl = { .d32 = 0 }; /** @todo dma needs to handle multiple setup packets (up to 3) */ dwc_write_reg32(&dev_if->out_ep_regs[0]->doepdma, _pcd->setup_pkt_dma_handle); // EP enable doepctl.d32 = dwc_read_reg32(&dev_if->out_ep_regs[0]->doepctl); doepctl.b.epena = 1; doepctl.d32 = 0x80008000; dwc_write_reg32(&dev_if->out_ep_regs[0]->doepctl, doepctl.d32); } #ifdef VERBOSE DWC_DEBUGPL(DBG_PCDV,"doepctl0=%0x\n", dwc_read_reg32(&dev_if->out_ep_regs[0]->doepctl)); DWC_DEBUGPL(DBG_PCDV,"diepctl0=%0x\n", dwc_read_reg32(&dev_if->in_ep_regs[0]->diepctl)); #endif } /** * This interrupt occurs when a USB Reset is detected. When the USB * Reset Interrupt occurs the device state is set to DEFAULT and the * EP0 state is set to IDLE. * -# Set the NAK bit for all OUT endpoints (DOEPCTLn.SNAK = 1) * -# Unmask the following interrupt bits * - DAINTMSK.INEP0 = 1 (Control 0 IN endpoint) * - DAINTMSK.OUTEP0 = 1 (Control 0 OUT endpoint) * - DOEPMSK.SETUP = 1 * - DOEPMSK.XferCompl = 1 * - DIEPMSK.XferCompl = 1 * - DIEPMSK.TimeOut = 1 * -# Program the following fields in the endpoint specific registers * for Control OUT EP 0, in order to receive a setup packet * - DOEPTSIZ0.Packet Count = 3 (To receive up to 3 back to back * setup packets) * - DOEPTSIZE0.Transfer Size = 24 Bytes (To receive up to 3 back * to back setup packets) * - In DMA mode, DOEPDMA0 Register with a memory address to * store any setup packets received * At this point, all the required initialization, except for enabling * the control 0 OUT endpoint is done, for receiving SETUP packets. */ int32_t dwc_otg_pcd_handle_usb_reset_intr( dwc_otg_pcd_t * _pcd) { dwc_otg_core_if_t *core_if = GET_CORE_IF(_pcd); dwc_otg_dev_if_t *dev_if = core_if->dev_if; depctl_data_t doepctl = { .d32 = 0}; depctl_data_t diepctl = { .d32 = 0}; daint_data_t daintmsk = { .d32 = 0}; doepmsk_data_t doepmsk = { .d32 = 0}; diepmsk_data_t diepmsk = { .d32 = 0}; dcfg_data_t dcfg = { .d32=0 }; grstctl_t resetctl = { .d32=0 }; dctl_data_t dctl = {.d32=0}; int i = 0; gintsts_data_t gintsts; #if defined(CONFIG_RTL_ULINKER) extern unsigned long rst_jiffies; rst_jiffies = jiffies; BDBG_GADGET_MODE_SWITCH("[%s:%d] rst_jiffies[%lu]\n", __FUNCTION__, __LINE__, rst_jiffies); #endif DWC_PRINT("USB RESET\n"); /* reset the HNP settings */ dwc_otg_pcd_update_otg( _pcd, 1); /* Clear the Remote Wakeup Signalling */ dctl.b.rmtwkupsig = 1; dwc_modify_reg32( &core_if->dev_if->dev_global_regs->dctl, dctl.d32, 0 ); /* Set NAK for all OUT EPs */ doepctl.b.snak = 1; for (i=0; i < MAX_EPS_CHANNELS; i++) { if( (i!=0) && (dev_if->isIN_EP[i]!=0) ) continue; dwc_write_reg32( &dev_if->out_ep_regs[i]->doepctl, doepctl.d32 ); } //cathy, disable IN EPs if enable bit is set for(i=0; iisIN_EP[i]==0) ) continue; diepctl.d32 = dwc_read_reg32(&dev_if->in_ep_regs[i]->diepctl); if(diepctl.b.epena == 1) { printk("usb reset: ep%d diepctl = 0x%08x\n", i, diepctl.d32); diepctl.b.snak = 1; diepctl.b.epdis = 1; dwc_write_reg32(&dev_if->in_ep_regs[i]->diepctl, diepctl.d32); } } /* Flush the NP Tx FIFO */ dwc_otg_flush_tx_fifo( core_if, 0x10 ); //flush all tx FIFO /* Flush the Learning Queue */ resetctl.b.intknqflsh = 1; dwc_write_reg32( &core_if->core_global_regs->grstctl, resetctl.d32); daintmsk.b.inep0 = 1; daintmsk.b.outep0 = 1; dwc_write_reg32( &dev_if->dev_global_regs->daintmsk, daintmsk.d32 ); doepmsk.b.setup = 1; doepmsk.b.xfercompl = 1; doepmsk.b.ahberr = 1; doepmsk.b.epdisabled = 1; dwc_write_reg32( &dev_if->dev_global_regs->doepmsk, doepmsk.d32 ); diepmsk.b.xfercompl = 1; diepmsk.b.timeout = 1; diepmsk.b.epdisabled = 1; diepmsk.b.ahberr = 1; //cathy //diepmsk.b.intknepmis = 1; dwc_write_reg32( &dev_if->dev_global_regs->diepmsk, diepmsk.d32 ); /* Reset Device Address */ dcfg.d32 = dwc_read_reg32( &dev_if->dev_global_regs->dcfg); dcfg.b.devaddr = 0; dwc_write_reg32( &dev_if->dev_global_regs->dcfg, dcfg.d32); /* setup EP0 to receive SETUP packets */ ep0_out_start( core_if, _pcd ); /* Clear interrupt */ gintsts.d32 = 0; gintsts.b.usbreset = 1; dwc_write_reg32 (&core_if->core_global_regs->gintsts, gintsts.d32); return 1; } /** * Get the device speed from the device status register and convert it * to USB speed constant. * * @param _core_if Programming view of DWC_otg controller. */ static int get_device_speed( dwc_otg_core_if_t *_core_if ) { dsts_data_t dsts; enum usb_device_speed speed = USB_SPEED_UNKNOWN; dsts.d32 = dwc_read_reg32(&_core_if->dev_if->dev_global_regs->dsts); switch (dsts.b.enumspd) { case DWC_DSTS_ENUMSPD_HS_PHY_30MHZ_OR_60MHZ: speed = USB_SPEED_HIGH; break; case DWC_DSTS_ENUMSPD_FS_PHY_30MHZ_OR_60MHZ: case DWC_DSTS_ENUMSPD_FS_PHY_48MHZ: speed = USB_SPEED_FULL; break; case DWC_DSTS_ENUMSPD_LS_PHY_6MHZ: speed = USB_SPEED_LOW; break; } return speed; } /** * Read the device status register and set the device speed in the * data structure. * Set up EP0 to receive SETUP packets by calling dwc_ep0_activate. */ int32_t dwc_otg_pcd_handle_enum_done_intr(dwc_otg_pcd_t *_pcd) { dwc_otg_pcd_ep_t *ep0 = &_pcd->ep0; gintsts_data_t gintsts; gusbcfg_data_t gusbcfg; dwc_otg_core_global_regs_t *global_regs = GET_CORE_IF(_pcd)->core_global_regs; uint32_t gsnpsid = global_regs->gsnpsid; uint8_t utmi16b, utmi8b; gintmsk_data_t intr_mask = { .d32 = 0}; DWC_DEBUGPL(DBG_PCD, "SPEED ENUM\n"); if(gsnpsid >= (uint32_t)0x4f54260a) { utmi16b = 5; utmi8b = 9; } else { utmi16b = 4; utmi8b = 8; } dwc_otg_ep0_activate( GET_CORE_IF(_pcd), &ep0->dwc_ep ); #ifdef DEBUG_EP0 print_ep0_state(_pcd); #endif if (_pcd->ep0state == EP0_DISCONNECT) { _pcd->ep0state = EP0_IDLE; } else if (_pcd->ep0state == EP0_STALL) { _pcd->ep0state = EP0_IDLE; } _pcd->ep0state = EP0_IDLE; ep0->stopped = 0; _pcd->gadget.speed = get_device_speed(GET_CORE_IF(_pcd)); /* Set USB turnaround time based on device speed and PHY interface. */ gusbcfg.d32 = dwc_read_reg32(&global_regs->gusbcfg); if (_pcd->gadget.speed == USB_SPEED_HIGH) { if (GET_CORE_IF(_pcd)->hwcfg2.b.hs_phy_type == DWC_HWCFG2_HS_PHY_TYPE_ULPI) { /* ULPI interface */ gusbcfg.b.usbtrdtim = 9; } if (GET_CORE_IF(_pcd)->hwcfg2.b.hs_phy_type == DWC_HWCFG2_HS_PHY_TYPE_UTMI) { /* UTMI+ interface */ if (GET_CORE_IF(_pcd)->hwcfg4.b.utmi_phy_data_width == 0) { gusbcfg.b.usbtrdtim = utmi8b; } else if (GET_CORE_IF(_pcd)->hwcfg4.b.utmi_phy_data_width == 1) { gusbcfg.b.usbtrdtim = utmi16b; } else if (GET_CORE_IF(_pcd)->core_params->phy_utmi_width == 8) { gusbcfg.b.usbtrdtim = utmi8b; } else { gusbcfg.b.usbtrdtim = utmi16b; } } if (GET_CORE_IF(_pcd)->hwcfg2.b.hs_phy_type == DWC_HWCFG2_HS_PHY_TYPE_UTMI_ULPI) { /* UTMI+ OR ULPI interface */ if (gusbcfg.b.ulpi_utmi_sel == 1) { /* ULPI interface */ gusbcfg.b.usbtrdtim = 9; } else { /* UTMI+ interface */ if (GET_CORE_IF(_pcd)->core_params->phy_utmi_width == 16) { gusbcfg.b.usbtrdtim = utmi16b; } else { gusbcfg.b.usbtrdtim = utmi8b; } } } } else { /* Full or low speed */ gusbcfg.b.usbtrdtim = 9; } dwc_write_reg32(&global_regs->gusbcfg, gusbcfg.d32); /* Clear interrupt */ gintsts.d32 = 0; gintsts.b.enumdone = 1; gintsts.b.usbsuspend = 1; //cathy, clear suspend state dwc_write_reg32( &GET_CORE_IF(_pcd)->core_global_regs->gintsts, gintsts.d32 ); intr_mask.b.usbsuspend = 1; //cathy, enable suspend intr dwc_modify_reg32( &global_regs->gintmsk, intr_mask.d32, intr_mask.d32); return 1; } /** * This interrupt indicates that the ISO OUT Packet was dropped due to * Rx FIFO full or Rx Status Queue Full. If this interrupt occurs * read all the data from the Rx FIFO. */ int32_t dwc_otg_pcd_handle_isoc_out_packet_dropped_intr(dwc_otg_pcd_t *_pcd ) { gintmsk_data_t intr_mask = { .d32 = 0}; gintsts_data_t gintsts; DWC_PRINT("INTERRUPT Handler not implemented for %s\n", "ISOC Out Dropped"); intr_mask.b.isooutdrop = 1; dwc_modify_reg32( &GET_CORE_IF(_pcd)->core_global_regs->gintmsk, intr_mask.d32, 0 ); /* Clear interrupt */ gintsts.d32 = 0; gintsts.b.isooutdrop = 1; dwc_write_reg32 (&GET_CORE_IF(_pcd)->core_global_regs->gintsts, gintsts.d32); return 1; } /** * This interrupt indicates the end of the portion of the micro-frame * for periodic transactions. If there is a periodic transaction for * the next frame, load the packets into the EP periodic Tx FIFO. */ int32_t dwc_otg_pcd_handle_end_periodic_frame_intr(dwc_otg_pcd_t *_pcd ) { gintmsk_data_t intr_mask = { .d32 = 0}; gintsts_data_t gintsts; DWC_PRINT("INTERRUPT Handler not implemented for %s\n", "EOP"); intr_mask.b.eopframe = 1; dwc_modify_reg32( &GET_CORE_IF(_pcd)->core_global_regs->gintmsk, intr_mask.d32, 0 ); /* Clear interrupt */ gintsts.d32 = 0; gintsts.b.eopframe = 1; dwc_write_reg32( &GET_CORE_IF(_pcd)->core_global_regs->gintsts, gintsts.d32); return 1; } /** * This interrupt indicates that EP of the packet on the top of the * non-periodic Tx FIFO does not match EP of the IN Token received. * * The "Device IN Token Queue" Registers are read to determine the * order the IN Tokens have been received. The non-periodic Tx FIFO * is flushed, so it can be reloaded in the order seen in the IN Token * Queue. */ int32_t dwc_otg_pcd_handle_ep_mismatch_intr(dwc_otg_core_if_t *_core_if) { gintsts_data_t gintsts; DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, _core_if); /* Clear interrupt */ gintsts.d32 = 0; gintsts.b.epmismatch = 1; dwc_write_reg32 (&_core_if->core_global_regs->gintsts, gintsts.d32); return 1; } /** * This funcion stalls EP0. */ static inline void ep0_do_stall( dwc_otg_pcd_t *_pcd, const int err_val ) { dwc_otg_pcd_ep_t *ep0 = &_pcd->ep0; struct usb_ctrlrequest *ctrl = &_pcd->setup_pkt->req; DWC_WARN("req %02x.%02x protocol STALL; err %d\n", ctrl->bRequestType, ctrl->bRequest, err_val); ep0->dwc_ep.is_in = 1; dwc_otg_ep_set_stall( _pcd->otg_dev->core_if, &ep0->dwc_ep ); _pcd->ep0.stopped = 1; _pcd->ep0state = EP0_IDLE; ep0_out_start( GET_CORE_IF(_pcd), _pcd ); } /** * This functions delegates the setup command to the gadget driver. */ static inline void do_gadget_setup( dwc_otg_pcd_t *_pcd, struct usb_ctrlrequest * _ctrl) { int ret = 0; if (_pcd->driver && _pcd->driver->setup) { SPIN_UNLOCK(&_pcd->lock); ret = _pcd->driver->setup(&_pcd->gadget, _ctrl); SPIN_LOCK(&_pcd->lock); if (ret < 0) { ep0_do_stall( _pcd, ret ); } /** @todo This is a g_file_storage gadget driver specific * workaround: a DELAYED_STATUS result from the fsg_setup * routine will result in the gadget queueing a EP0 IN status * phase for a two-stage control transfer. Exactly the same as * a SET_CONFIGURATION/SET_INTERFACE except that this is a class * specific request. Need a generic way to know when the gadget * driver will queue the status phase. Can we assume when we * call the gadget driver setup() function that it will always * queue and require the following flag? Need to look into * this. */ if (ret == 256 + 999) { _pcd->request_config = 1; } }else { printk("%s : NO gadget driver and setup phase function installed!\n", __func__); //shlee } } /** * This function starts the Zero-Length Packet for the IN status phase * of a 2 stage control transfer. */ static inline void do_setup_in_status_phase( dwc_otg_pcd_t *_pcd) { dwc_otg_pcd_ep_t *ep0 = &_pcd->ep0; if (_pcd->ep0state == EP0_STALL) { return; } _pcd->ep0state = EP0_STATUS; /* Prepare for more SETUP Packets */ DWC_DEBUGPL(DBG_PCD, "EP0 IN ZLP\n"); ep0->dwc_ep.xfer_len = 0; ep0->dwc_ep.xfer_count = 0; ep0->dwc_ep.is_in = 1; ep0->dwc_ep.dma_addr = _pcd->setup_pkt_dma_handle; dwc_otg_ep0_start_transfer( GET_CORE_IF(_pcd), &ep0->dwc_ep ); /* Prepare for more SETUP Packets */ ep0_out_start( GET_CORE_IF(_pcd), _pcd ); } /** * This function starts the Zero-Length Packet for the OUT status phase * of a 2 stage control transfer. */ static inline void do_setup_out_status_phase( dwc_otg_pcd_t *_pcd) { dwc_otg_pcd_ep_t *ep0 = &_pcd->ep0; if (_pcd->ep0state == EP0_STALL) { DWC_DEBUGPL(DBG_PCD, "EP0 STALLED\n"); return; } _pcd->ep0state = EP0_STATUS; /* Prepare for more SETUP Packets */ //ep0_out_start( GET_CORE_IF(_pcd), _pcd ); DWC_DEBUGPL(DBG_PCD, "EP0 OUT ZLP\n"); ep0->dwc_ep.xfer_len = 0; ep0->dwc_ep.xfer_count = 0; ep0->dwc_ep.is_in = 0; //ep0->dwc_ep.dma_addr = 0xffffffff; ep0->dwc_ep.dma_addr = _pcd->setup_pkt_dma_handle; dwc_otg_ep0_start_transfer( GET_CORE_IF(_pcd), &ep0->dwc_ep ); /* Prepare for more SETUP Packets */ ep0_out_start( GET_CORE_IF(_pcd), _pcd ); } /** * Clear the EP halt (STALL) and if pending requests start the * transfer. */ static inline void pcd_clear_halt( dwc_otg_pcd_t *_pcd, dwc_otg_pcd_ep_t *_ep ) { if(_ep->dwc_ep.stall_clear_flag == 0) dwc_otg_ep_clear_stall( GET_CORE_IF(_pcd), &_ep->dwc_ep ); /* Reactive the EP */ dwc_otg_ep_activate( GET_CORE_IF(_pcd), &_ep->dwc_ep ); if (_ep->stopped) { _ep->stopped = 0; /* If there is a request in the EP queue start it */ /** @todo FIXME: this causes an EP mismatch in DMA mode. * epmismatch not yet implemented. */ /* * Above fixme is solved by implmenting a tasklet to call the * start_next_request(), outside of interrupt context at some * time after the current time, after a clear-halt setup packet. * Still need to implement ep mismatch in the future if a gadget * ever uses more than one endpoint at once */ if (GET_CORE_IF(_pcd)->dma_enable) { _ep->queue_sof = 1; tasklet_schedule (_pcd->start_xfer_tasklet); } else { #if 0 _ep->queue_sof = 1; DWC_ERROR("tasklet schedule\n"); tasklet_schedule (_pcd->start_xfer_tasklet); if (GET_CORE_IF(_pcd)->core_params->opt) { start_next_request( _ep ); } #endif } } /* Start Control Status Phase */ do_setup_in_status_phase( _pcd ); } /** * This function is called when the SET_FEATURE TEST_MODE Setup packet * is sent from the host. The Device Control register is written with * the Test Mode bits set to the specified Test Mode. This is done as * a tasklet so that the "Status" phase of the control transfer * completes before transmitting the TEST packets. * * @todo This has not been tested since the tasklet struct was put * into the PCD struct! * */ static void do_test_mode( unsigned long _data ) { dctl_data_t dctl; dwc_otg_pcd_t *pcd = (dwc_otg_pcd_t *)_data; dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); int test_mode = pcd->test_mode; // DWC_WARN("%s() has not been tested since being rewritten!\n", __func__); dctl.d32 = dwc_read_reg32(&core_if->dev_if->dev_global_regs->dctl); switch (test_mode) { case 1: // TEST_J dctl.b.tstctl = 1; break; case 2: // TEST_K dctl.b.tstctl = 2; break; case 3: // TEST_SE0_NAK dctl.b.tstctl = 3; break; case 4: // TEST_PACKET dctl.b.tstctl = 4; break; case 5: // TEST_FORCE_ENABLE dctl.b.tstctl = 5; break; } dwc_write_reg32(&core_if->dev_if->dev_global_regs->dctl, dctl.d32); } /** * This function process the SET_FEATURE Setup Commands. */ static inline void do_set_feature( dwc_otg_pcd_t *_pcd ) { dwc_otg_core_if_t *core_if = GET_CORE_IF(_pcd); dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; struct usb_ctrlrequest ctrl = _pcd->setup_pkt->req; dwc_otg_pcd_ep_t *ep = 0; int32_t otg_cap_param = core_if->core_params->otg_cap; gotgctl_data_t gotgctl = { .d32 = 0 }; //cathy uint16_t wValue, wIndex; DWC_DEBUGPL(DBG_PCD, "SET_FEATURE:%02x.%02x v%04x i%04x l%04x\n", ctrl.bRequestType, ctrl.bRequest, ctrl.wValue, ctrl.wIndex, ctrl.wLength); DWC_DEBUGPL(DBG_PCD,"otg_cap=%d\n", otg_cap_param); switch (ctrl.bRequestType & USB_RECIP_MASK) { case USB_RECIP_DEVICE: wValue = cpu_to_le16(ctrl.wValue); //switch (ctrl.wValue) //cathy switch( wValue ) { case USB_DEVICE_REMOTE_WAKEUP: _pcd->remote_wakeup_enable = 1; break; case USB_DEVICE_TEST_MODE: /* Setup the Test Mode tasklet to do the Test * Packet generation after the SETUP Status * phase has completed. */ /** @todo This has not been tested since the * tasklet struct was put into the PCD * struct! */ wIndex = cpu_to_le16(ctrl.wIndex); //cathy _pcd->test_mode_tasklet.next = 0; _pcd->test_mode_tasklet.state = 0; atomic_set( &_pcd->test_mode_tasklet.count, 0); _pcd->test_mode_tasklet.func = do_test_mode; _pcd->test_mode_tasklet.data = (unsigned long)_pcd; //_pcd->test_mode = ctrl.wIndex >> 8; //cathy _pcd->test_mode = wIndex >> 8; tasklet_schedule(&_pcd->test_mode_tasklet); break; case USB_DEVICE_B_HNP_ENABLE: DWC_DEBUGPL(DBG_PCDV, "SET_FEATURE: USB_DEVICE_B_HNP_ENABLE\n"); /* dev may initiate HNP */ if (otg_cap_param == DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE) { _pcd->b_hnp_enable = 1; dwc_otg_pcd_update_otg( _pcd, 0 ); DWC_DEBUGPL(DBG_PCD, "Request B HNP\n"); /**@todo Is the gotgctl.devhnpen cleared * by a USB Reset? */ gotgctl.b.devhnpen = 1; gotgctl.b.hnpreq = 1; dwc_write_reg32( &global_regs->gotgctl, gotgctl.d32 ); } else { ep0_do_stall( _pcd, -EOPNOTSUPP); } break; case USB_DEVICE_A_HNP_SUPPORT: /* RH port supports HNP */ DWC_DEBUGPL(DBG_PCDV, "SET_FEATURE: USB_DEVICE_A_HNP_SUPPORT\n"); if (otg_cap_param == DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE) { _pcd->a_hnp_support = 1; dwc_otg_pcd_update_otg( _pcd, 0 ); } else { ep0_do_stall( _pcd, -EOPNOTSUPP); } break; case USB_DEVICE_A_ALT_HNP_SUPPORT: /* other RH port does */ DWC_DEBUGPL(DBG_PCDV, "SET_FEATURE: USB_DEVICE_A_ALT_HNP_SUPPORT\n"); if (otg_cap_param == DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE) { _pcd->a_alt_hnp_support = 1; dwc_otg_pcd_update_otg( _pcd, 0 ); } else { ep0_do_stall( _pcd, -EOPNOTSUPP); } break; } do_setup_in_status_phase( _pcd ); break; case USB_RECIP_INTERFACE: do_gadget_setup(_pcd, &ctrl ); break; case USB_RECIP_ENDPOINT: //cathy wValue = cpu_to_le16(ctrl.wValue); wIndex = cpu_to_le16(ctrl.wIndex); //if (ctrl.wValue == USB_ENDPOINT_HALT) if (wValue == USB_ENDPOINT_HALT) { //ep = get_ep_by_addr(_pcd, ctrl.wIndex); ep = get_ep_by_addr(_pcd, wIndex); if (ep == 0) { ep0_do_stall(_pcd, -EOPNOTSUPP); return; } ep->stopped = 1; dwc_otg_ep_set_stall( core_if, &ep->dwc_ep ); } do_setup_in_status_phase( _pcd ); break; } } /** * This function process the CLEAR_FEATURE Setup Commands. */ static inline void do_clear_feature( dwc_otg_pcd_t *_pcd ) { struct usb_ctrlrequest ctrl = _pcd->setup_pkt->req; dwc_otg_pcd_ep_t *ep = 0; //cathy uint16_t wValue, wIndex; DWC_DEBUGPL(DBG_PCD, "CLEAR_FEATURE:%02x.%02x v%04x i%04x l%04x\n", ctrl.bRequestType, ctrl.bRequest, ctrl.wValue, ctrl.wIndex, ctrl.wLength); switch (ctrl.bRequestType & USB_RECIP_MASK) { case USB_RECIP_DEVICE: //cathy wValue = cpu_to_le16(ctrl.wValue); //switch (ctrl.wValue) switch (wValue) { case USB_DEVICE_REMOTE_WAKEUP: _pcd->remote_wakeup_enable = 0; break; case USB_DEVICE_TEST_MODE: /** @todo Add CLEAR_FEATURE for TEST modes. */ break; } do_setup_in_status_phase( _pcd ); break; case USB_RECIP_ENDPOINT: //cathy wIndex = cpu_to_le16(ctrl.wIndex); //ep = get_ep_by_addr(_pcd, ctrl.wIndex); ep = get_ep_by_addr(_pcd, wIndex); if (ep == 0) { ep0_do_stall(_pcd, -EOPNOTSUPP); return; } pcd_clear_halt(_pcd, ep ); break; } } /** * This function processes SETUP commands. In Linux, the USB Command * processing is done in two places - the first being the PCD and the * second in the Gadget Driver (for example, the File-Backed Storage * Gadget Driver). * * * * * * * * * * * * * * * * * * * * * * * * * *
Command Driver Description
GET_STATUS PCD Command is processed as * defined in chapter 9 of the USB 2.0 Specification chapter 9 *
CLEAR_FEATURE PCD The Device and Endpoint * requests are the ENDPOINT_HALT feature is procesed, all others the * interface requests are ignored.
SET_FEATURE PCD The Device and Endpoint * requests are processed by the PCD. Interface requests are passed * to the Gadget Driver.
SET_ADDRESS PCD Program the DCFG reg, * with device address received
GET_DESCRIPTOR Gadget Driver Return the * requested descriptor
SET_DESCRIPTOR Gadget Driver Optional - * not implemented by any of the existing Gadget Drivers.
SET_CONFIGURATION Gadget Driver Disable * all EPs and enable EPs for new configuration.
GET_CONFIGURATION Gadget Driver Return * the current configuration
SET_INTERFACE Gadget Driver Disable all * EPs and enable EPs for new configuration.
GET_INTERFACE Gadget Driver Return the * current interface.
SYNC_FRAME PCD Display debug * message.
* * When the SETUP Phase Done interrupt occurs, the PCD SETUP commands are * processed by pcd_setup. Calling the Function Driver's setup function from * pcd_setup processes the gadget SETUP commands. */ static inline void pcd_setup( dwc_otg_pcd_t *_pcd ) { dwc_otg_core_if_t *core_if = GET_CORE_IF(_pcd); dwc_otg_dev_if_t *dev_if = core_if->dev_if; struct usb_ctrlrequest ctrl = _pcd->setup_pkt->req; dwc_otg_pcd_ep_t *ep; dwc_otg_pcd_ep_t *ep0 = &_pcd->ep0; uint16_t *status = _pcd->status_buf; deptsiz0_data_t doeptsize0 = { .d32 = 0}; //cathy, add for endian problem uint16_t wvalue, windex, wlength; #ifdef DEBUG_EP0 DWC_DEBUGPL(DBG_PCD, "SETUP %02x.%02x v%04x i%04x l%04x\n", ctrl.bRequestType, ctrl.bRequest, ctrl.wValue, ctrl.wIndex, ctrl.wLength); #endif doeptsize0.d32 = dwc_read_reg32( &dev_if->out_ep_regs[0]->doeptsiz ); /** @todo handle > 1 setup packet , assert error for now */ if (core_if->dma_enable && (doeptsize0.b.supcnt < 2)) { DWC_ERROR ("\n\n----------- CANNOT handle > 1 setup packet in DMA mode(doeptsiz= %d)\n\n", doeptsize0.b.supcnt); } /* Clean up the request queue */ request_nuke( ep0 ); ep0->stopped = 0; if (ctrl.bRequestType & USB_DIR_IN) { ep0->dwc_ep.is_in = 1; _pcd->ep0state = EP0_IN_DATA_PHASE; } else { ep0->dwc_ep.is_in = 0; _pcd->ep0state = EP0_OUT_DATA_PHASE; } if ((ctrl.bRequestType & USB_TYPE_MASK) != USB_TYPE_STANDARD) { /* handle non-standard (class/vendor) requests in the gadget driver */ if (ctrl.bRequest == USB_CDC_SET_ETHERNET_PACKET_FILTER) _pcd->request_config = 1; do_gadget_setup(_pcd, &ctrl ); return; } /** @todo NGS: Handle bad setup packet? */ switch (ctrl.bRequest) { case USB_REQ_GET_STATUS: #ifdef DEBUG_EP0 DWC_DEBUGPL(DBG_PCD, "GET_STATUS %02x.%02x v%04x i%04x l%04x\n", ctrl.bRequestType, ctrl.bRequest, ctrl.wValue, ctrl.wIndex, ctrl.wLength); #endif switch (ctrl.bRequestType & USB_RECIP_MASK) { case USB_RECIP_DEVICE: //cathy *status = cpu_to_le16(0x1); /* Self powered */ *status |= cpu_to_le16( _pcd->remote_wakeup_enable << 1 ); break; case USB_RECIP_INTERFACE: *status = 0; break; case USB_RECIP_ENDPOINT: //cathy windex = cpu_to_le16(ctrl.wIndex); ep = get_ep_by_addr(_pcd, windex); wlength = cpu_to_le16(ctrl.wLength); if ( ep == 0 || wlength > 2) { ep0_do_stall(_pcd, -EOPNOTSUPP); return; } /** @todo check for EP stall */ *status = cpu_to_le16(ep->stopped); break; } _pcd->ep0_pending = 1; ep0->dwc_ep.start_xfer_buff = (uint8_t *)status; ep0->dwc_ep.xfer_buff = (uint8_t *)status; ep0->dwc_ep.dma_addr = _pcd->status_buf_dma_handle; ep0->dwc_ep.xfer_len = 2; ep0->dwc_ep.xfer_count = 0; ep0->dwc_ep.total_len = ep0->dwc_ep.xfer_len; dwc_otg_ep0_start_transfer( GET_CORE_IF(_pcd), &ep0->dwc_ep ); break; case USB_REQ_CLEAR_FEATURE: do_clear_feature( _pcd ); break; case USB_REQ_SET_FEATURE: do_set_feature( _pcd ); break; case USB_REQ_SET_ADDRESS: if (ctrl.bRequestType == USB_RECIP_DEVICE) { dcfg_data_t dcfg = {.d32=0}; wvalue = cpu_to_le16(ctrl.wValue); //cathy #ifdef DEBUG_EP0 //DWC_DEBUGPL(DBG_PCDV, "SET_ADDRESS:%d\n", ctrl.wValue); //cathy DWC_DEBUGPL(DBG_PCDV, "SET_ADDRESS:%d\n", wvalue); #endif //dcfg.b.devaddr = ctrl.wValue; //cathy dcfg.b.devaddr = wvalue; dwc_modify_reg32( &dev_if->dev_global_regs->dcfg, 0, dcfg.d32); do_setup_in_status_phase( _pcd ); return; } break; case USB_REQ_SET_INTERFACE: case USB_REQ_SET_CONFIGURATION: _pcd->request_config = 1; /* Configuration changed */ do_gadget_setup(_pcd, &ctrl ); break; case USB_REQ_SYNCH_FRAME: do_gadget_setup(_pcd, &ctrl ); break; default: /* Call the Gadget Driver's setup functions */ do_gadget_setup(_pcd, &ctrl ); break; } } /** * This function completes the ep0 control transfer. */ static int32_t ep0_complete_request( dwc_otg_pcd_ep_t *_ep ) { dwc_otg_core_if_t *core_if = GET_CORE_IF(_ep->pcd); dwc_otg_dev_if_t *dev_if = core_if->dev_if; dwc_otg_dev_in_ep_regs_t *in_ep_regs = dev_if->in_ep_regs[_ep->dwc_ep.num]; #ifdef DEBUG_EP0 dwc_otg_dev_out_ep_regs_t *out_ep_regs = dev_if->out_ep_regs[_ep->dwc_ep.num]; #endif deptsiz0_data_t deptsiz; dwc_otg_pcd_request_t *req; int is_last = 0; dwc_otg_pcd_t *pcd = _ep->pcd; //DWC_DEBUGPL(DBG_PCDV, "%s() %s\n", __func__, _ep->ep.name); if (pcd->ep0_pending && list_empty(&_ep->queue)) { if (_ep->dwc_ep.is_in) { #ifdef DEBUG_EP0 DWC_DEBUGPL(DBG_PCDV, "Do setup OUT status phase\n"); #endif do_setup_out_status_phase(pcd); } else { #ifdef DEBUG_EP0 DWC_DEBUGPL(DBG_PCDV, "Do setup IN status phase\n"); #endif do_setup_in_status_phase(pcd); } pcd->ep0_pending = 0; pcd->ep0state = EP0_STATUS; return 1; } if (list_empty(&_ep->queue)) { return 0; } req = list_entry(_ep->queue.next, dwc_otg_pcd_request_t, queue); if (pcd->ep0state == EP0_STATUS) { is_last = 1; } else if (req->req.zero) { req->req.actual = _ep->dwc_ep.xfer_count; //do_setup_in_status_phase (pcd); req->req.zero = 0; _ep->dwc_ep.xfer_len = 0; _ep->dwc_ep.xfer_count = 0; _ep->dwc_ep.sent_zlp = 1; dwc_otg_ep0_start_transfer( GET_CORE_IF(pcd), &_ep->dwc_ep ); return 1; } else if (_ep->dwc_ep.is_in) { deptsiz.d32 = dwc_read_reg32( &in_ep_regs->dieptsiz); #ifdef DEBUG_EP0 DWC_DEBUGPL(DBG_PCDV, "%s len=%d xfersize=%d pktcnt=%d\n", _ep->ep.name, _ep->dwc_ep.xfer_len, deptsiz.b.xfersize, deptsiz.b.pktcnt); #endif if (deptsiz.b.xfersize == 0) { req->req.actual = _ep->dwc_ep.xfer_count; /* Is a Zero Len Packet needed? */ //if (req->req.zero) { #ifdef DEBUG_EP0 DWC_DEBUGPL(DBG_PCD, "Setup Rx ZLP\n"); #endif do_setup_out_status_phase(pcd); } } else { /* ep0-OUT */ #ifdef DEBUG_EP0 deptsiz.d32 = dwc_read_reg32( &out_ep_regs->doeptsiz); DWC_DEBUGPL(DBG_PCDV, "%s len=%d xsize=%d pktcnt=%d\n", _ep->ep.name, _ep->dwc_ep.xfer_len, deptsiz.b.xfersize, deptsiz.b.pktcnt); #endif req->req.actual = _ep->dwc_ep.xfer_count; /* Is a Zero Len Packet needed? */ //if (req->req.zero) { #ifdef DEBUG_EP0 DWC_DEBUGPL(DBG_PCDV, "Setup Tx ZLP\n"); #endif do_setup_in_status_phase(pcd); } /* Complete the request */ if (is_last) { request_done(_ep, req, 0); _ep->dwc_ep.start_xfer_buff = 0; _ep->dwc_ep.xfer_buff = 0; _ep->dwc_ep.xfer_len = 0; return 1; } return 0; } static int ep_in_zlp( dwc_otg_pcd_ep_t *_ep ) { dwc_otg_pcd_request_t *req = 0; int ret = 0; if(_ep->dwc_ep.sent_zlp && _ep->dwc_ep.is_in) { dwc_otg_ep_start_transfer( GET_CORE_IF(_ep->pcd), &_ep->dwc_ep ); } else { ret = 1; //should not sent zlp } return ret; } /** * This function completes the request for the EP. If there are * additional requests for the EP in the queue they will be started. */ static void complete_ep( dwc_otg_pcd_ep_t *_ep ) { dwc_otg_core_if_t *core_if = GET_CORE_IF(_ep->pcd); dwc_otg_dev_if_t *dev_if = core_if->dev_if; dwc_otg_dev_in_ep_regs_t *in_ep_regs = dev_if->in_ep_regs[_ep->dwc_ep.num]; deptsiz_data_t deptsiz; dwc_otg_pcd_request_t *req = 0; int is_last = 0; DWC_DEBUGPL(DBG_PCDV,"%s() %s-%s\n", __func__, _ep->ep.name, (_ep->dwc_ep.is_in?"IN":"OUT")); /* Get any pending requests */ if (!list_empty(&_ep->queue)) { req = list_entry(_ep->queue.next, dwc_otg_pcd_request_t, queue); } DWC_DEBUGPL(DBG_PCD, "Requests %d\n",_ep->pcd->request_pending); if (_ep->dwc_ep.is_in) { deptsiz.d32 = dwc_read_reg32( &in_ep_regs->dieptsiz); if (core_if->dma_enable) { if (deptsiz.b.xfersize == 0) _ep->dwc_ep.xfer_count = _ep->dwc_ep.xfer_len; } DWC_DEBUGPL(DBG_PCDV, "%s len=%d xfersize=%d pktcnt=%d\n", _ep->ep.name, _ep->dwc_ep.xfer_len, deptsiz.b.xfersize, deptsiz.b.pktcnt); if (deptsiz.b.xfersize == 0 && deptsiz.b.pktcnt == 0 && _ep->dwc_ep.xfer_count == _ep->dwc_ep.xfer_len) { #if !defined(CONFIG_RTL_ULINKER) //only support zlp on endpoint 1 (bulk in) if((_ep->dwc_ep.num == 1) && !_ep->dwc_ep.sent_zlp && !(_ep->dwc_ep.xfer_len & (_ep->dwc_ep.maxpacket-1))){ _ep->dwc_ep.sent_zlp = 1; is_last = ep_in_zlp(_ep); } else #endif is_last = 1; } else { DWC_WARN("Incomplete transfer (%s-%s [siz=%d pkt=%d])\n", _ep->ep.name, (_ep->dwc_ep.is_in?"IN":"OUT"), deptsiz.b.xfersize, deptsiz.b.pktcnt); } } else { dwc_otg_dev_out_ep_regs_t *out_ep_regs = dev_if->out_ep_regs[_ep->dwc_ep.num]; deptsiz.d32 = 0; deptsiz.d32 = dwc_read_reg32( &out_ep_regs->doeptsiz); #ifdef DEBUG DWC_DEBUGPL(DBG_PCDV, "addr %p, %s len=%d cnt=%d xsize=%d pktcnt=%d\n", &out_ep_regs->doeptsiz, _ep->ep.name, _ep->dwc_ep.xfer_len, _ep->dwc_ep.xfer_count, deptsiz.b.xfersize, deptsiz.b.pktcnt); #endif is_last = 1; } /* Complete the request */ if (is_last) { if (core_if->dma_enable) { req->req.actual = _ep->dwc_ep.xfer_len - deptsiz.b.xfersize; } else { req->req.actual = _ep->dwc_ep.xfer_count; } request_done(_ep, req, 0); _ep->dwc_ep.start_xfer_buff = 0; _ep->dwc_ep.xfer_buff = 0; _ep->dwc_ep.xfer_len = 0; /* If there is a request in the queue start it.*/ start_next_request( _ep ); } } /** * This function handles EP0 Control transfers. * * The state of the control tranfers are tracked in * ep0state. */ static void handle_ep0( dwc_otg_pcd_t *_pcd ) { dwc_otg_core_if_t *core_if = GET_CORE_IF(_pcd); dwc_otg_pcd_ep_t *ep0 = &_pcd->ep0; #ifdef DEBUG_EP0 DWC_DEBUGPL(DBG_PCDV, "%s()\n", __func__); print_ep0_state(_pcd); #endif switch (_pcd->ep0state) { case EP0_DISCONNECT: break; case EP0_IDLE: _pcd->request_config = 0; pcd_setup( _pcd ); break; case EP0_IN_DATA_PHASE: #ifdef DEBUG_EP0 DWC_DEBUGPL(DBG_PCD, "DATA_IN EP%d-%s: type=%d, mps=%d\n", ep0->dwc_ep.num, (ep0->dwc_ep.is_in ?"IN":"OUT"), ep0->dwc_ep.type, ep0->dwc_ep.maxpacket ); #endif if (core_if->dma_enable) { /* * For EP0 we can only program 1 packet at a time so we * need to do the make calculations after each complete. * Call write_packet to make the calculations, as in * slave mode, and use those values to determine if we * can complete. */ dwc_otg_ep_write_packet (core_if, &ep0->dwc_ep, 1); } if (ep0->dwc_ep.xfer_count < ep0->dwc_ep.total_len) { dwc_otg_ep0_continue_transfer ( GET_CORE_IF(_pcd), &ep0->dwc_ep ); DWC_DEBUGPL(DBG_PCD, "CONTINUE TRANSFER\n"); } else { ep0_complete_request( ep0 ); DWC_DEBUGPL(DBG_PCD, "COMPLETE TRANSFER\n"); } break; case EP0_OUT_DATA_PHASE: #ifdef DEBUG_EP0 DWC_DEBUGPL(DBG_PCD, "DATA_OUT EP%d-%s: type=%d, mps=%d\n", ep0->dwc_ep.num, (ep0->dwc_ep.is_in ?"IN":"OUT"), ep0->dwc_ep.type, ep0->dwc_ep.maxpacket ); #endif ep0_complete_request( ep0 ); break; case EP0_STATUS: DWC_DEBUGPL(DBG_PCD, "CASE: EP0_STATUS\n"); ep0_complete_request( ep0 ); _pcd->ep0state = EP0_IDLE; ep0->stopped = 1; ep0->dwc_ep.is_in = 0; /* OUT for next SETUP */ /* Prepare for more SETUP Packets */ if (core_if->dma_enable) { ep0_out_start( core_if, _pcd ); } if (!GET_CORE_IF(_pcd)->dma_enable) { int i; depctl_data_t diepctl; diepctl.d32 = dwc_read_reg32( &core_if->dev_if->in_ep_regs[0]->diepctl); if (_pcd->ep0.queue_sof) { _pcd->ep0.queue_sof = 0; start_next_request (&_pcd->ep0); } diepctl.d32 = dwc_read_reg32( &core_if->dev_if->in_ep_regs[0]->diepctl); if (_pcd->ep0.queue_sof) { _pcd->ep0.queue_sof = 0; start_next_request (&_pcd->ep0); } for (i=0; i < MAX_EPS_CHANNELS; i++) { if( (i!=0) && (_pcd->otg_dev->core_if->dev_if->isIN_EP[i]==0) ) continue; diepctl.d32 = dwc_read_reg32( &core_if->dev_if->in_ep_regs[i+1]->diepctl); if (_pcd->in_ep[i].queue_sof) { _pcd->in_ep[i].queue_sof = 0; start_next_request (&_pcd->in_ep[i]); } } } break; case EP0_STALL: DWC_ERROR("EP0 STALLed, should not get here pcd_setup()\n"); break; } #ifdef DEBUG_EP0 print_ep0_state(_pcd); #endif } /** * Restart transfer */ static void restart_transfer( dwc_otg_pcd_t *_pcd, const uint32_t _epnum) { dwc_otg_core_if_t *core_if = GET_CORE_IF(_pcd); dwc_otg_dev_if_t *dev_if = core_if->dev_if; deptsiz_data_t dieptsiz = {.d32=0}; //depctl_data_t diepctl = {.d32=0}; dwc_otg_pcd_ep_t *ep; dieptsiz.d32 = dwc_read_reg32(&dev_if->in_ep_regs[_epnum]->dieptsiz); ep = get_in_ep(_pcd, _epnum); /* if(_epnum != 0) ep = &_pcd->in_ep[ _epnum - 1]; else ep = &_pcd->ep0; */ DWC_DEBUGPL(DBG_PCD,"xfer_buff=%p xfer_count=%0x xfer_len=%0x" " stopped=%d\n", ep->dwc_ep.xfer_buff, ep->dwc_ep.xfer_count, ep->dwc_ep.xfer_len , ep->stopped); /* * If xfersize is 0 and pktcnt in not 0, resend the last packet. */ if ( dieptsiz.b.pktcnt && dieptsiz.b.xfersize == 0 && ep->dwc_ep.start_xfer_buff != 0) { if ( ep->dwc_ep.xfer_len <= ep->dwc_ep.maxpacket ) { ep->dwc_ep.xfer_count = 0; ep->dwc_ep.xfer_buff = ep->dwc_ep.start_xfer_buff; } else { ep->dwc_ep.xfer_count -= ep->dwc_ep.maxpacket; /* convert packet size to dwords. */ ep->dwc_ep.xfer_buff -= ep->dwc_ep.maxpacket; } ep->stopped = 0; DWC_DEBUGPL(DBG_PCD,"xfer_buff=%p xfer_count=%0x " "xfer_len=%0x stopped=%d\n", ep->dwc_ep.xfer_buff, ep->dwc_ep.xfer_count, ep->dwc_ep.xfer_len , ep->stopped ); if (_epnum == 0) { dwc_otg_ep0_start_transfer(core_if, &ep->dwc_ep); } else { dwc_otg_ep_start_transfer(core_if, &ep->dwc_ep); } } } /** * handle the IN EP disable interrupt. */ static inline void handle_in_ep_disable_intr(dwc_otg_pcd_t *_pcd, const uint32_t _epnum) { dwc_otg_core_if_t *core_if = GET_CORE_IF(_pcd); dwc_otg_dev_if_t *dev_if = core_if->dev_if; deptsiz_data_t dieptsiz = {.d32=0}; dctl_data_t dctl = {.d32=0}; dwc_otg_pcd_ep_t *ep; dwc_ep_t *dwc_ep; extern USBPhyReset(int); USBPhyReset(1); //wei add USBPhyReset(0); //wei add ep = get_in_ep(_pcd, _epnum); dwc_ep = &ep->dwc_ep; printk("ep_disable_intr: diepctl%d=%0x\n", _epnum, dwc_read_reg32(&dev_if->in_ep_regs[_epnum]->diepctl)); /* if(_epnum != 0) { ep = &_pcd->in_ep[ _epnum - 1]; dwc_ep = &_pcd->in_ep[ _epnum - 1].dwc_ep; } else { ep = &_pcd->ep0; dwc_ep = &_pcd->ep0.dwc_ep; } */ DWC_DEBUGPL(DBG_PCD,"diepctl%d=%0x\n", _epnum, dwc_read_reg32(&dev_if->in_ep_regs[_epnum]->diepctl)); dieptsiz.d32 = dwc_read_reg32(&dev_if->in_ep_regs[_epnum]->dieptsiz); DWC_DEBUGPL(DBG_ANY, "pktcnt=%d size=%d\n", dieptsiz.b.pktcnt, dieptsiz.b.xfersize ); dwc_otg_flush_tx_fifo( core_if, 0x10 ); return; //cathy, if ep is disable, do not re-send pkt if (ep->stopped) { /* Flush the Tx FIFO */ /** @todo NGS: This is not the correct FIFO */ dwc_otg_flush_tx_fifo( core_if, 0 ); /* Clear the Global IN NP NAK */ dctl.d32 = 0; dctl.b.cgnpinnak = 1; dwc_modify_reg32(&dev_if->dev_global_regs->dctl, dctl.d32, 0); /* Restart the transaction */ if (dieptsiz.b.pktcnt != 0 || dieptsiz.b.xfersize != 0) { restart_transfer( _pcd, _epnum ); } } else { /* Restart the transaction */ if (dieptsiz.b.pktcnt != 0 || dieptsiz.b.xfersize != 0) { restart_transfer( _pcd, _epnum ); } DWC_DEBUGPL(DBG_ANY, "STOPPED!!!\n"); } } /** * Handler for the IN EP timeout handshake interrupt. */ static inline void handle_in_ep_timeout_intr(dwc_otg_pcd_t *_pcd, const uint32_t _epnum) { dwc_otg_core_if_t *core_if = GET_CORE_IF(_pcd); dwc_otg_dev_if_t *dev_if = core_if->dev_if; #ifdef DEBUG deptsiz_data_t dieptsiz = {.d32=0}; uint32_t epnum = 0; #endif dctl_data_t dctl = {.d32=0}; dwc_otg_pcd_ep_t *ep; gintmsk_data_t intr_mask = {.d32 = 0}; ep = get_in_ep(_pcd, _epnum); /* if(_epnum != 0) ep = &_pcd->in_ep[ _epnum - 1]; else ep = &_pcd->ep0; */ /* Disable the NP Tx Fifo Empty Interrrupt */ if (!core_if->dma_enable) { intr_mask.b.nptxfempty = 1; dwc_modify_reg32( &core_if->core_global_regs->gintmsk, intr_mask.d32, 0); } /** @todo NGS Check EP type. * Implement for Periodic EPs */ /* * Non-periodic EP */ /* Enable the Global IN NAK Effective Interrupt */ intr_mask.b.ginnakeff = 1; dwc_modify_reg32( &core_if->core_global_regs->gintmsk, 0, intr_mask.d32); /* Set Global IN NAK */ dctl.b.sgnpinnak = 1; dwc_modify_reg32(&dev_if->dev_global_regs->dctl, dctl.d32, dctl.d32); ep->stopped = 1; #ifdef DEBUG dieptsiz.d32 = dwc_read_reg32(&dev_if->in_ep_regs[epnum]->dieptsiz); DWC_DEBUGPL(DBG_ANY, "pktcnt=%d size=%d\n", dieptsiz.b.pktcnt, dieptsiz.b.xfersize ); #endif #ifdef DISABLE_PERIODIC_EP /* * Set the NAK bit for this EP to * start the disable process. */ diepctl.d32 = 0; diepctl.b.snak = 1; dwc_modify_reg32(&dev_if->in_ep_regs[epnum]->diepctl, diepctl.d32, diepctl.d32); ep->disabling = 1; ep->stopped = 1; #endif } //cathy extern u8 ep1xferincomplete; extern int ep0xferfinished; extern int ep1block; /** * This interrupt indicates that an IN EP has a pending Interrupt. * The sequence for handling the IN EP interrupt is shown below: * -# Read the Device All Endpoint Interrupt register * -# Repeat the following for each IN EP interrupt bit set (from * LSB to MSB). * -# Read the Device Endpoint Interrupt (DIEPINTn) register * -# If "Transfer Complete" call the request complete function * -# If "Endpoint Disabled" complete the EP disable procedure. * -# If "AHB Error Interrupt" log error * -# If "Time-out Handshake" log error * -# If "IN Token Received when TxFIFO Empty" write packet to Tx * FIFO. * -# If "IN Token EP Mismatch" (disable, this is handled by EP * Mismatch Interrupt) */ static int32_t dwc_otg_pcd_handle_in_ep_intr(dwc_otg_pcd_t *_pcd) { #define CLEAR_IN_EP_INTR(__core_if,__epnum,__intr) \ do { \ diepint_data_t diepint = {.d32=0}; \ diepint.b.__intr = 1; \ dwc_write_reg32(&__core_if->dev_if->in_ep_regs[__epnum]->diepint, \ diepint.d32); \ } while (0) dwc_otg_core_if_t *core_if = GET_CORE_IF(_pcd); dwc_otg_dev_if_t *dev_if = core_if->dev_if; diepint_data_t diepint = {.d32=0}; depctl_data_t diepctl = {.d32=0}; uint32_t ep_intr; uint32_t epnum = 0; dwc_otg_pcd_ep_t *ep; dwc_ep_t *dwc_ep; uint32_t _empty_msk, _diepctl; gintmsk_data_t intr_mask = {.d32 = 0}; DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, _pcd); /* Read in the device interrupt bits */ ep_intr = dwc_otg_read_dev_all_in_ep_intr( core_if ); /* Service the Device IN interrupts for each endpoint */ while( ep_intr ) { if (ep_intr&0x1) { /* Get EP pointer */ ep = get_in_ep(_pcd, epnum); dwc_ep = &ep->dwc_ep; _diepctl = dwc_read_reg32(&dev_if->in_ep_regs[epnum]->diepctl); _empty_msk = dwc_read_reg32(&dev_if->dev_global_regs->dtknqr4_fifoemptymsk); DWC_DEBUGPL(DBG_PCDV, "IN EP INTERRUPT-%d, epmty_msk-%x diepctl-%x\n",epnum, _empty_msk, _diepctl); DWC_DEBUGPL(DBG_PCD, "EP%d-%s: type=%d, mps=%d\n", dwc_ep->num, (dwc_ep->is_in ?"IN":"OUT"), dwc_ep->type, dwc_ep->maxpacket ); diepint.d32 = dwc_otg_read_dev_in_ep_intr( core_if, dwc_ep ); DWC_DEBUGPL(DBG_PCDV, "EP %d Interrupt Register - 0x%x\n", epnum, diepint.d32); /* Transfer complete */ if ( diepint.b.xfercompl ) { DWC_DEBUGPL(DBG_PCD,"EP%d IN Xfer Complete\n", epnum); /* Disable the NP Tx FIFO Empty * Interrrupt */ if(core_if->en_multiple_tx_fifo == 0) { intr_mask.b.nptxfempty = 1; dwc_modify_reg32( &core_if->core_global_regs->gintmsk, intr_mask.d32, 0); } else { /* Disable the Tx FIFO Empty Interrupt for this EP */ uint32_t fifoemptymsk = 0x1 << dwc_ep->num; dwc_modify_reg32(&core_if->dev_if->dev_global_regs->dtknqr4_fifoemptymsk, fifoemptymsk, 0); } /* Clear the bit in DIEPINTn for this interrupt */ CLEAR_IN_EP_INTR(core_if,epnum,xfercompl); /* Complete the transfer */ if (epnum == 0) { handle_ep0( _pcd ); if( _pcd->ep0state == EP0_IDLE ) { ep0xferfinished = 1; if( ep1xferincomplete>0 ) { gnptxsts_data_t gtxstatus; gtxstatus.d32 = dwc_read_reg32(&core_if->core_global_regs->gnptxsts); depctl_data_t diepctl0; diepctl0.d32 = dwc_read_reg32(&core_if->dev_if->in_ep_regs[0]->diepctl); if ( diepctl0.b.nextep!=1 && gtxstatus.b.nptxqtop_chnep!=0) { diepctl0.b.nextep = 1; dwc_write_reg32(&core_if->dev_if->in_ep_regs[0]->diepctl, diepctl0.d32); } } } } else { //cathy if( epnum == 1 ) { ep1xferincomplete--; } else { //cathy, ep3, to fix ep3 wrong fifo pointer problem //dwc_otg_flush_tx_fifo(core_if, 1); } complete_ep( ep ); } } /* Endpoint disable */ if ( diepint.b.epdisabled ) { DWC_DEBUGPL(DBG_ANY,"EP%d IN disabled\n", epnum); handle_in_ep_disable_intr( _pcd, epnum ); /* Clear the bit in DIEPINTn for this interrupt */ CLEAR_IN_EP_INTR(core_if,epnum,epdisabled); } /* AHB Error */ if ( diepint.b.ahberr ) { DWC_DEBUGPL(DBG_ANY,"EP%d IN AHB Error\n", epnum); /* Clear the bit in DIEPINTn for this interrupt */ CLEAR_IN_EP_INTR(core_if,epnum,ahberr); } /* TimeOUT Handshake (non-ISOC IN EPs) */ if ( diepint.b.timeout ) { DWC_DEBUGPL(DBG_ANY,"EP%d IN Time-out\n", epnum); handle_in_ep_timeout_intr( _pcd, epnum ); CLEAR_IN_EP_INTR(core_if,epnum,timeout); } /** IN Token received with TxF Empty */ if (diepint.b.intktxfemp) { DWC_DEBUGPL(DBG_ANY,"EP%d IN TKN TxFifo Empty\n", epnum); if (!ep->stopped && epnum == 1) { //cathy, do not mask tx fifo empty interrupt //diepmsk_data_t diepmsk = { .d32 = 0}; //diepmsk.b.intktxfemp = 1; //dwc_modify_reg32( &dev_if->dev_global_regs->diepmsk, diepmsk.d32, 0 ); //cathy, if ep1 is blocked due to ep0's xfer, restart ep1's xfer after ep0 is finished if( ep0xferfinished && ep1block) { diepmsk_data_t diepmsk = { .d32 = 0}; diepmsk.b.intktxfemp = 1; dwc_modify_reg32( &dev_if->dev_global_regs->diepmsk, diepmsk.d32, 0 ); ep1block = 0; start_next_request(ep); } } CLEAR_IN_EP_INTR(core_if,epnum,intktxfemp); } /** IN Endpoint NAK Effective */ if (diepint.b.inepnakeff) { DWC_DEBUGPL(DBG_ANY,"EP%d IN EP NAK Effective\n", epnum); /* Periodic EP */ if (ep->disabling) { diepctl.d32 = 0; diepctl.b.snak = 1; diepctl.b.epdis = 1; dwc_modify_reg32(&dev_if->in_ep_regs[epnum]->diepctl, diepctl.d32, diepctl.d32); } CLEAR_IN_EP_INTR(core_if,epnum,inepnakeff); } /** IN EP Tx FIFO Empty Intr */ if (diepint.b.emptyintr) { DWC_DEBUGPL(DBG_ANY,"EP%d Tx FIFO Empty Intr \n", epnum); write_empty_tx_fifo(_pcd, epnum); CLEAR_IN_EP_INTR(core_if,epnum,emptyintr); } } epnum++; ep_intr >>=1; } return 1; #undef CLEAR_IN_EP_INTR } /** * This interrupt indicates that an OUT EP has a pending Interrupt. * The sequence for handling the OUT EP interrupt is shown below: * -# Read the Device All Endpoint Interrupt register * -# Repeat the following for each OUT EP interrupt bit set (from * LSB to MSB). * -# Read the Device Endpoint Interrupt (DOEPINTn) register * -# If "Transfer Complete" call the request complete function * -# If "Endpoint Disabled" complete the EP disable procedure. * -# If "AHB Error Interrupt" log error * -# If "Setup Phase Done" process Setup Packet (See Standard USB * Command Processing) */ static int32_t dwc_otg_pcd_handle_out_ep_intr(dwc_otg_pcd_t *_pcd) { #define CLEAR_OUT_EP_INTR(__core_if,__epnum,__intr) \ do { \ doepint_data_t doepint = {.d32=0}; \ doepint.b.__intr = 1; \ dwc_write_reg32(&__core_if->dev_if->out_ep_regs[__epnum]->doepint, \ doepint.d32); \ } while (0) dwc_otg_core_if_t *core_if = GET_CORE_IF(_pcd); uint32_t ep_intr; doepint_data_t doepint = {.d32=0}; uint32_t epnum = 0; dwc_ep_t *dwc_ep; DWC_DEBUGPL(DBG_PCDV, "%s()\n", __func__); /* Read in the device interrupt bits */ ep_intr = dwc_otg_read_dev_all_out_ep_intr( core_if ); while( ep_intr ) { if (ep_intr&0x1) { /* Get EP pointer */ dwc_ep = &((get_out_ep(_pcd, epnum))->dwc_ep); // dwc_ep = &_pcd->out_ep[ epnum - 1].dwc_ep; #ifdef VERBOSE DWC_DEBUGPL(DBG_PCDV, "EP%d-%s: type=%d, mps=%d\n", dwc_ep->num, (dwc_ep->is_in ?"IN":"OUT"), dwc_ep->type, dwc_ep->maxpacket ); #endif doepint.d32 = dwc_otg_read_dev_out_ep_intr(core_if, dwc_ep); /* Transfer complete */ if ( doepint.b.xfercompl ) { DWC_DEBUGPL(DBG_PCD,"EP%d OUT Xfer Complete\n", epnum); /* Clear the bit in DOEPINTn for this interrupt */ CLEAR_OUT_EP_INTR(core_if,epnum,xfercompl); if (epnum == 0) { handle_ep0( _pcd ); if( _pcd->ep0state == EP0_IDLE ) { ep0xferfinished = 1; if( ep1xferincomplete>0 ) { gnptxsts_data_t gtxstatus; gtxstatus.d32 = dwc_read_reg32(&core_if->core_global_regs->gnptxsts); depctl_data_t diepctl0; diepctl0.d32 = dwc_read_reg32(&core_if->dev_if->in_ep_regs[0]->diepctl); if( diepctl0.b.nextep!=1 && gtxstatus.b.nptxqtop_chnep!=0) { diepctl0.b.nextep = 1; dwc_write_reg32(&core_if->dev_if->in_ep_regs[0]->diepctl, diepctl0.d32); } } core_if->dev_if->suspended = 0; } } else { complete_ep( get_out_ep(_pcd, epnum) ); // complete_ep( &_pcd->out_ep[ epnum - 1] ); } } /* Endpoint disable */ if ( doepint.b.epdisabled ) { DWC_DEBUGPL(DBG_PCD,"EP%d OUT disabled\n", epnum); /* Clear the bit in DOEPINTn for this interrupt */ CLEAR_OUT_EP_INTR(core_if,epnum,epdisabled); } /* AHB Error */ if ( doepint.b.ahberr ) { DWC_DEBUGPL(DBG_PCD,"EP%d OUT AHB Error\n", epnum); DWC_DEBUGPL(DBG_PCD,"EP DMA REG %d \n", core_if->dev_if->out_ep_regs[epnum]->doepdma); CLEAR_OUT_EP_INTR(core_if,epnum,ahberr); } /* Setup Phase Done (contorl EPs) */ if ( doepint.b.setup ) { #ifdef DEBUG_EP0 DWC_DEBUGPL(DBG_PCD,"EP%d SETUP Done\n", epnum); #endif ep0xferfinished = 0; handle_ep0( _pcd ); CLEAR_OUT_EP_INTR(core_if,epnum,setup); } } epnum++; ep_intr >>=1; } return 1; #undef CLEAR_OUT_EP_INTR } /** * Incomplete ISO IN Transfer Interrupt. * This interrupt indicates one of the following conditions occurred * while transmitting an ISOC transaction. * - Corrupted IN Token for ISOC EP. * - Packet not complete in FIFO. * The follow actions will be taken: * -# Determine the EP * -# Set incomplete flag in dwc_ep structure * -# Disable EP; when "Endpoint Disabled" interrupt is received * Flush FIFO */ int32_t dwc_otg_pcd_handle_incomplete_isoc_in_intr(dwc_otg_pcd_t *_pcd) { gintmsk_data_t intr_mask = { .d32 = 0}; gintsts_data_t gintsts; DWC_PRINT("INTERRUPT Handler not implemented for %s\n", "IN ISOC Incomplete"); intr_mask.b.incomplisoin = 1; dwc_modify_reg32( &GET_CORE_IF(_pcd)->core_global_regs->gintmsk, intr_mask.d32, 0 ); /* Clear interrupt */ gintsts.d32 = 0; gintsts.b.incomplisoin = 1; dwc_write_reg32 (&GET_CORE_IF(_pcd)->core_global_regs->gintsts, gintsts.d32); return 1; } /** * Incomplete ISO OUT Transfer Interrupt. * * This interrupt indicates that the core has dropped an ISO OUT * packet. The following conditions can be the cause: * - FIFO Full, the entire packet would not fit in the FIFO. * - CRC Error * - Corrupted Token * The follow actions will be taken: * -# Determine the EP * -# Set incomplete flag in dwc_ep structure * -# Read any data from the FIFO * -# Disable EP. when "Endpoint Disabled" interrupt is received * re-enable EP. */ int32_t dwc_otg_pcd_handle_incomplete_isoc_out_intr(dwc_otg_pcd_t *_pcd) { /** @todo implement ISR */ gintmsk_data_t intr_mask = { .d32 = 0}; gintsts_data_t gintsts; DWC_PRINT("INTERRUPT Handler not implemented for %s\n", "OUT ISOC Incomplete"); intr_mask.b.incomplisoout = 1; dwc_modify_reg32( &GET_CORE_IF(_pcd)->core_global_regs->gintmsk, intr_mask.d32, 0 ); /* Clear interrupt */ gintsts.d32 = 0; gintsts.b.incomplisoout = 1; dwc_write_reg32 (&GET_CORE_IF(_pcd)->core_global_regs->gintsts, gintsts.d32); return 1; } /** * This function handles the Global IN NAK Effective interrupt. * */ int32_t dwc_otg_pcd_handle_in_nak_effective( dwc_otg_pcd_t *_pcd ) { dwc_otg_dev_if_t *dev_if = GET_CORE_IF(_pcd)->dev_if; depctl_data_t diepctl = { .d32 = 0}; depctl_data_t diepctl_rd = { .d32 = 0}; gintmsk_data_t intr_mask = { .d32 = 0}; gintsts_data_t gintsts; int i; DWC_DEBUGPL(DBG_PCD, "Global IN NAK Effective\n"); /* Disable all active IN EPs */ diepctl.b.epdis = 1; diepctl.b.snak = 1; for (i=0; i < MAX_EPS_CHANNELS; i++) { if( (i!=0) && (dev_if->isIN_EP[i]==0) ) continue; diepctl_rd.d32 = dwc_read_reg32(&dev_if->in_ep_regs[i]->diepctl); if (diepctl_rd.b.epena) { dwc_write_reg32( &dev_if->in_ep_regs[i]->diepctl, diepctl.d32 ); } } /* Disable the Global IN NAK Effective Interrupt */ intr_mask.b.ginnakeff = 1; dwc_modify_reg32( &GET_CORE_IF(_pcd)->core_global_regs->gintmsk, intr_mask.d32, 0); /* Clear interrupt */ gintsts.d32 = 0; gintsts.b.ginnakeff = 1; dwc_write_reg32( &GET_CORE_IF(_pcd)->core_global_regs->gintsts, gintsts.d32); return 1; } /** * OUT NAK Effective. * */ int32_t dwc_otg_pcd_handle_out_nak_effective( dwc_otg_pcd_t *_pcd ) { gintmsk_data_t intr_mask = { .d32 = 0}; gintsts_data_t gintsts; DWC_PRINT("INTERRUPT Handler not implemented for %s\n", "Global IN NAK Effective\n"); /* Disable the Global IN NAK Effective Interrupt */ intr_mask.b.goutnakeff = 1; dwc_modify_reg32( &GET_CORE_IF(_pcd)->core_global_regs->gintmsk, intr_mask.d32, 0); /* Clear interrupt */ gintsts.d32 = 0; gintsts.b.goutnakeff = 1; dwc_write_reg32 (&GET_CORE_IF(_pcd)->core_global_regs->gintsts, gintsts.d32); return 1; } /** * PCD interrupt handler. * * The PCD handles the device interrupts. Many conditions can cause a * device interrupt. When an interrupt occurs, the device interrupt * service routine determines the cause of the interrupt and * dispatches handling to the appropriate function. These interrupt * handling functions are described below. * * All interrupt registers are processed from LSB to MSB. * */ #if defined(CONFIG_RTL_ULINKER) int fsg_init_once = 0; #endif #if ULINKER_BRSC_RECOVER_TX_REQ dwc_otg_pcd_t *my_pcd = NULL; #endif int32_t dwc_otg_pcd_handle_intr( dwc_otg_pcd_t *_pcd ) { dwc_otg_core_if_t *core_if = GET_CORE_IF(_pcd); #ifdef VERBOSE dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; #endif gintsts_data_t gintr_status; int32_t retval = 0; #if ULINKER_BRSC_RECOVER_TX_REQ if (my_pcd == NULL) my_pcd = _pcd; else if (my_pcd != _pcd) { printk("\n\t\t[%s:%d] my_pcd != _pcd\n\n", __FUNCTION__, __LINE__); } #endif #ifdef VERBOSE DWC_DEBUGPL(DBG_ANY, "%s() gintsts=%08x gintmsk=%08x\n", __func__, dwc_read_reg32( &global_regs->gintsts), dwc_read_reg32( &global_regs->gintmsk)); #endif if (dwc_otg_is_device_mode(core_if)) { SPIN_LOCK(&_pcd->lock); #ifdef VERBOSE DWC_DEBUGPL(DBG_PCDV, "%s() gintsts=%08x gintmsk=%08x\n", __func__, dwc_read_reg32( &global_regs->gintsts), dwc_read_reg32( &global_regs->gintmsk)); #endif gintr_status.d32 = dwc_otg_read_core_intr(core_if); if (!gintr_status.d32) { return 0; } //printk("---2-------------------------\n"); DWC_DEBUGPL(DBG_PCDV, "%s: gintsts&gintmsk=%08x\n", __func__, gintr_status.d32 ); if (gintr_status.b.sofintr) { retval |= dwc_otg_pcd_handle_sof_intr( _pcd ); } if (gintr_status.b.rxstsqlvl) { retval |= dwc_otg_pcd_handle_rx_status_q_level_intr( _pcd ); } if (gintr_status.b.nptxfempty) { retval |= dwc_otg_pcd_handle_np_tx_fifo_empty_intr( _pcd ); } if (gintr_status.b.ginnakeff) { retval |= dwc_otg_pcd_handle_in_nak_effective( _pcd ); } if (gintr_status.b.goutnakeff) { retval |= dwc_otg_pcd_handle_out_nak_effective( _pcd ); } if (gintr_status.b.i2cintr) { retval |= dwc_otg_pcd_handle_i2c_intr( _pcd ); } if (gintr_status.b.erlysuspend) { retval |= dwc_otg_pcd_handle_early_suspend_intr( _pcd ); } if (gintr_status.b.usbreset) { retval |= dwc_otg_pcd_handle_usb_reset_intr( _pcd ); } if (gintr_status.b.enumdone) { retval |= dwc_otg_pcd_handle_enum_done_intr( _pcd ); } if (gintr_status.b.isooutdrop) { retval |= dwc_otg_pcd_handle_isoc_out_packet_dropped_intr( _pcd ); } if (gintr_status.b.eopframe) { retval |= dwc_otg_pcd_handle_end_periodic_frame_intr( _pcd ); } if (gintr_status.b.epmismatch) { retval |= dwc_otg_pcd_handle_ep_mismatch_intr( core_if ); } if (gintr_status.b.inepint) { retval |= dwc_otg_pcd_handle_in_ep_intr( _pcd ); } if (gintr_status.b.outepintr) { retval |= dwc_otg_pcd_handle_out_ep_intr( _pcd ); } if (gintr_status.b.incomplisoin) { retval |= dwc_otg_pcd_handle_incomplete_isoc_in_intr( _pcd ); } if (gintr_status.b.incomplisoout) { retval |= dwc_otg_pcd_handle_incomplete_isoc_out_intr( _pcd ); } #ifdef VERBOSE DWC_DEBUGPL(DBG_PCDV, "%s() gintsts=%0x\n", __func__, dwc_read_reg32( &global_regs->gintsts)); #endif SPIN_UNLOCK(&_pcd->lock); } return retval; } #if ULINKER_BRSC_RECOVER_TX_REQ int early_tx_complete (void) { int32_t retval = 0; int inepint = 0; gintsts_data_t gintr_status; dwc_otg_core_global_regs_t *global_regs = my_pcd->otg_dev->core_if->core_global_regs; gintr_status.d32 = dwc_read_reg32( &global_regs->gintsts) & dwc_read_reg32( &global_regs->gintmsk); if (!gintr_status.d32) { BRSC_COUNTER_UPDATE(otg_status_fail); return 0; } if (gintr_status.b.inepint) { BRSC_COUNTER_UPDATE(otg_inepint); inepint = 1; retval |= dwc_otg_pcd_handle_in_ep_intr( my_pcd ); } if (gintr_status.b.outepintr) { BRSC_COUNTER_UPDATE(otg_outepintr); retval |= dwc_otg_pcd_handle_out_ep_intr( my_pcd ); } if (inepint) retval = 11; return retval; } #endif /* #if ULINKER_BRSC_RECOVER_TX_REQ */ #endif /* DWC_HOST_ONLY */