/* ========================================================================== * $File: //dwh/usb_iip/dev/software/otg_ipmate/linux/drivers/dwc_otg_pcd.c $ * $Revision: #8 $ * $Date: 2006/04/21 $ * $Change: 631780 $ * * 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. * ========================================================================== */ #include #include "ifxusb_version.h" #ifdef DWC_IS_DEVICE /** @file * This file implements the Peripheral Controller Driver. * * The Peripheral Controller Driver (PCD) is responsible for * translating requests from the Function Driver into the appropriate * actions on the DWC_otg controller. It isolates the Function Driver * from the specifics of the controller by providing an API to the * Function Driver. * * The Peripheral Controller Driver for Linux will implement the * Gadget API, so that the existing Gadget drivers can be used. * (Gadget Driver is the Linux terminology for a Function Driver.) * * The Linux Gadget API is defined in the header file * . The USB EP operations API is * defined in the structure usb_ep_ops and the USB * Controller API is defined in the structure * usb_gadget_ops. * * An important function of the PCD is managing interrupts generated * by the DWC_otg controller. The implementation of the DWC_otg device * mode interrupt service routines is in dwc_otg_pcd_intr.c. * * @todo Add Device Mode test modes (Test J mode, Test K mode, etc). * @todo Does it work when the request size is greater than DEPTSIZ * transfer size * */ //Make options //#define USE_ORIG #include #include #include #include #if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0) #include #endif #include #include #include #include #if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0) #include //#include //#include #include #endif #include #include #include "dwc_otg_driver.h" #include "dwc_otg_pcd.h" #include "dwc_otg_ifx.h" // for Infineon platform specific. /** * Static PCD pointer for use in usb_gadget_register_driver and * usb_gadget_unregister_driver. Initialized in dwc_otg_pcd_init. */ static dwc_otg_pcd_t *s_pcd = 0; static int period_allowed=1; /* Display the contents of the buffer */ extern void dump_msg(const u8 *buf, unsigned int length); struct list_head txing_queue; static int dwc_otg_pcd_usb_led(struct usb_gadget *_gadget, int mode); /** * This function completes a request. It call's the request call back. */ unsigned long txing_sno=0; int request_done(dwc_otg_pcd_ep_t *_ep, dwc_otg_pcd_request_t *_req, int _status) { unsigned stopped = _ep->stopped; int rt=0; DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, _ep); list_del_init(&_req->queue); if (_req->req.status == -EINPROGRESS) _req->req.status = _status; else _status = _req->req.status; #if 0 #ifdef TxQueueTimer if(_req->req_timeout_on) del_timer(&_req->req_timeout); _req->req_timeout_on=0; #endif #endif /* don't modify queue heads during completion callback */ _ep->stopped = 1; if(_req->txing) { dwc_otg_pcd_request_t *req2=NULL; _req->txing=0; if (list_empty(&txing_queue)) printk(KERN_INFO "%s()%d txing_queue EMPTY\n",__func__,__LINE__); else req2 = list_entry(txing_queue.next, dwc_otg_pcd_request_t, txing_queue); while(req2 && req2 != _req) { list_del_init(&req2->txing_queue); list_del_init(&req2->queue); SPIN_UNLOCK(&_ep->pcd->lock); req2->req.complete(&_ep->ep, &req2->req); SPIN_LOCK(&_ep->pcd->lock); if (_ep->pcd->request_pending > 0) --_ep->pcd->request_pending; if(_ep->request_pending > 0) --_ep->request_pending; req2->txing=0; req2=NULL; if (list_empty(&txing_queue)) printk(KERN_INFO "%s()%d txing_queue EMPTY\n",__func__,__LINE__); else { req2 = list_entry(txing_queue.next, dwc_otg_pcd_request_t, txing_queue); } } if (list_empty(&txing_queue)) printk(KERN_INFO "%s()%d txing_queue EMPTY\n",__func__,__LINE__); else { list_del_init(&_req->txing_queue); } } SPIN_UNLOCK(&_ep->pcd->lock); _req->req.complete(&_ep->ep, &_req->req); SPIN_LOCK(&_ep->pcd->lock); if (_ep->pcd->request_pending > 0) --_ep->pcd->request_pending; if(_ep->request_pending > 0) --_ep->request_pending; _ep->stopped = stopped; return rt; } /** * This function terminates all the requsts in the EP request queue. */ void request_nuke( dwc_otg_pcd_ep_t *_ep ) { dwc_otg_pcd_request_t *req; _ep->stopped = 1; /* called with irqs blocked?? */ while (!list_empty(&_ep->queue)) { req = list_entry(_ep->queue.next, dwc_otg_pcd_request_t, queue); request_done(_ep, req, -ESHUTDOWN ); } } /* USB Endpoint Operations */ /* * The following sections briefly describe the behavior of the Gadget * API endpoint operations implemented in the DWC_otg driver * software. Detailed descriptions of the generic behavior of each of * these functions can be found in the Linux header file * include/linux/usb_gadget.h. * * The Gadget API provides wrapper functions for each of the function * pointers defined in usb_ep_ops. The Gadget Driver calls the wrapper * function, which then calls the underlying PCD function. The * following sections are named according to the wrapper * functions. Within each section, the corresponding DWC_otg PCD * function name is specified. * */ /** * This function is called by the Gadget Driver for each EP to be * configured for the current configuration (SET_CONFIGURATION). * * This function initializes the dwc_otg_ep_t data structure, and then * calls dwc_otg_ep_activate. */ static int dwc_otg_pcd_ep_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *_desc) { dwc_otg_pcd_ep_t *ep = 0; dwc_otg_pcd_t *pcd = 0; unsigned long flags; DWC_DEBUGPL(DBG_PCDV,"%s(%p,%p)\n", __func__, _ep, _desc ); ep = container_of(_ep, dwc_otg_pcd_ep_t, ep); if (!_ep || !_desc || ep->desc || _desc->bDescriptorType != USB_DT_ENDPOINT) { if (!_ep ) DWC_WARN( "%s, bad ep or descriptor(EP=NULL)\n", __func__); if (!_desc) DWC_WARN( "%s, bad ep or descriptor(DESC=NULL)\n", __func__); if (ep->desc) DWC_WARN( "%s, bad ep or descriptor(EP-DESC=NULL)\n", __func__); if (_desc->bDescriptorType != USB_DT_ENDPOINT) DWC_WARN( "%s, bad ep or descriptor(WRONG TYPE[%d]\n", __func__,_desc->bDescriptorType); return -EINVAL; } if (ep == &ep->pcd->ep[0]) { DWC_WARN("%s, bad ep(0)\n", __func__); return -EINVAL; } /* Check FIFO size? */ if (!_desc->wMaxPacketSize) { DWC_WARN("%s, bad %s maxpacket\n", __func__, _ep->name); return -ERANGE; } pcd = ep->pcd; if (!pcd->driver || pcd->gadget.speed == USB_SPEED_UNKNOWN) { DWC_WARN("%s, bogus device state\n", __func__); return -ESHUTDOWN; } SPIN_LOCK_IRQSAVE(&pcd->lock, flags); ep->desc = _desc; ep->ep.maxpacket = le16_to_cpu (_desc->wMaxPacketSize); //unaligned #if defined(GADGET_UNALIGNED_BUFFER_ADJUST) ep->using_aligned_tx_buf=0; ep->using_aligned_rx_buf=0; if(ep->aligned_tx_buf) usb_free_buf(ep->aligned_tx_buf); if(ep->aligned_rx_buf) usb_free_buf(ep->aligned_rx_buf); ep->aligned_tx_buf=NULL; ep->aligned_tx_buf_len=0 ep->aligned_rx_buf=NULL; ep->aligned_rx_buf_len=0; #endif ep->request_pending=0; /* * Activate the EP */ ep->stopped = 0; ep->dwc_ep.is_in = (USB_DIR_IN & _desc->bEndpointAddress) != 0; ep->dwc_ep.maxpacket = ep->ep.maxpacket; ep->dwc_ep.tx_fifo_num = 0; ep->dwc_ep.type = _desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; if ((_desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_ISOC ) /* if ISOC EP then assign a Periodic Tx FIFO. */ ep->dwc_ep.tx_fifo_num = 1; #if defined(USE_PERIODIC_EP) else if ((_desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT ) /* if INTR EP then assign a Periodic Tx FIFO. */ ep->dwc_ep.tx_fifo_num = 1; #endif /* Set initial data PID. */ if ((_desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_BULK ) ep->dwc_ep.data_pid_start = 0; DWC_DEBUGPL(DBG_PCD, "Activate %s-%s: type=%d, mps=%d desc=%p\n", ep->ep.name, (ep->dwc_ep.is_in ?"IN":"OUT"), ep->dwc_ep.type, ep->dwc_ep.maxpacket, ep->desc ); dwc_otg_ep_activate( GET_CORE_IF(pcd), &ep->dwc_ep ); SPIN_UNLOCK_IRQRESTORE(&pcd->lock, flags); return 0; } /** * This function is called when an EP is disabled due to disconnect or * change in configuration. Any pending requests will terminate with a * status of -ESHUTDOWN. * * This function modifies the dwc_otg_ep_t data structure for this EP, * and then calls dwc_otg_ep_deactivate. */ static int dwc_otg_pcd_ep_disable(struct usb_ep *_ep) { dwc_otg_pcd_ep_t *ep; unsigned long flags; DWC_DEBUGPL(DBG_PCDV,"%s(%p)\n", __func__, _ep); ep = container_of(_ep, dwc_otg_pcd_ep_t, ep); if (!_ep || !ep->desc) { DWC_DEBUGPL(DBG_PCD, "%s, %s not enabled\n", __func__, _ep ? ep->ep.name : NULL); return -EINVAL; } SPIN_LOCK_IRQSAVE(&ep->pcd->lock, flags); request_nuke( ep ); dwc_otg_ep_deactivate( GET_CORE_IF(ep->pcd), &ep->dwc_ep ); ep->desc = 0; ep->stopped = 1; #if defined(GADGET_UNALIGNED_BUFFER_ADJUST) if(ep->aligned_tx_buf) usb_free_buf(ep->aligned_tx_buf); if(ep->aligned_rx_buf) usb_free_buf(ep->aligned_rx_buf); ep->using_aligned_tx_buf=0; ep->using_aligned_rx_buf=0; ep->aligned_tx_buf=NULL; ep->aligned_tx_buf_len=0 ep->aligned_rx_buf=NULL; ep->aligned_rx_buf_len=0; #endif SPIN_UNLOCK_IRQRESTORE(&ep->pcd->lock, flags); DWC_DEBUGPL(DBG_PCD, "%s disabled\n", _ep->name); return 0; } /** * This function allocates a request object to use with the specified * endpoint. * * @param _ep The endpoint to be used with with the request * @param _gfp_flags the GFP_* flags to use. */ #if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0) static struct usb_request *dwc_otg_pcd_alloc_request(struct usb_ep *_ep, gfp_t _gfp_flags) #else static struct usb_request *dwc_otg_pcd_alloc_request(struct usb_ep *_ep, int _gfp_flags) #endif { dwc_otg_pcd_request_t *req; DWC_DEBUGPL(DBG_PCDV,"%s(%p,%d)\n", __func__, _ep, _gfp_flags); if (0 == _ep ) { DWC_WARN("%s() %s\n", __func__, "Invalid EP!\n"); return 0; } req = kmalloc( sizeof(dwc_otg_pcd_request_t), _gfp_flags); if (0 == req) { DWC_ERROR("%s() %s\n", __func__, "request allocation failed!\n"); return 0; } memset(req, 0, sizeof(dwc_otg_pcd_request_t)); req->req.dma = DMA_ADDR_INVALID; INIT_LIST_HEAD(&req->queue); INIT_LIST_HEAD(&req->txing_queue); return &req->req; } /** * This function frees a request object. * * @param _ep The endpoint associated with the request * @param _req The request being freed */ static void dwc_otg_pcd_free_request(struct usb_ep *_ep, struct usb_request *_req) { dwc_otg_pcd_request_t *req; DWC_DEBUGPL(DBG_PCDV,"%s(%p,%p)\n", __func__, _ep, _req); if (0 == _ep || 0 == _req) { DWC_WARN("%s() %s\n", __func__, "Invalid ep or req argument!\n"); return; } req = container_of(_req, dwc_otg_pcd_request_t, req); if(_req->buf) DWC_WARN("%s() %s\n", __func__, "REQ buf not freed!\n"); kfree(req); } /** * This function allocates an I/O buffer to be used for a transfer * to/from the specified endpoint. * * @param _ep The endpoint to be used with with the request * @param _bytes The desired number of bytes for the buffer * @param _dma Pointer to the buffer's DMA address; must be valid * @param _gfp_flags the GFP_* flags to use. * @return address of a new buffer or null is buffer could not be allocated. */ #if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0) static void *dwc_otg_pcd_alloc_buffer(struct usb_ep *_ep, unsigned _bytes, dma_addr_t *_dma, gfp_t _gfp_flags) #else static void *dwc_otg_pcd_alloc_buffer(struct usb_ep *_ep, unsigned _bytes, dma_addr_t *_dma, int _gfp_flags) #endif { void *buf; /* Check dword alignment */ if ((_bytes & 0x3UL) != 0) DWC_WARN("%s() Buffer size is not a multiple of DWORD size (%d)",__func__, _bytes); buf=usb_alloc_buf(_bytes, 1); if(0==buf) DWC_ERROR("%s() %s\n", __func__, "buffer allocation failed!\n"); /* Check dword alignment */ if (((int)buf & 0x3UL) != 0) DWC_WARN("%s() Buffer is not DWORD aligned (%p)",__func__, buf); if(_dma) *_dma=CPHYSADDR(buf); return buf; } /** * This function frees an I/O buffer that was allocated by alloc_buffer. * * @param _ep the endpoint associated with the buffer * @param _buf address of the buffer * @param _dma The buffer's DMA address * @param _bytes The number of bytes of the buffer */ static void dwc_otg_pcd_free_buffer(struct usb_ep *_ep, void *_buf, dma_addr_t _dma, unsigned _bytes) { usb_free_buf(_buf); } /** * This function is used to submit an I/O Request to an EP. * * - When the request completes the request's completion callback * is called to return the request to the driver. * - An EP, except control EPs, may have multiple requests * pending. * - Once submitted the request cannot be examined or modified. * - Each request is turned into one or more packets. * - A BULK EP can queue any amount of data; the transfer is * packetized. * - Zero length Packets are specified with the request 'zero' * flag. */ int dwc_otg_pcd_ep_stop(dwc_otg_pcd_ep_t *_ep) { dwc_otg_pcd_t *pcd; depctl_data_t ctl; deptsiz_data_t sz; pcd = _ep->pcd; if(_ep->dwc_ep.tx_fifo_num) { ctl.d32=dwc_read_reg32( &GET_CORE_IF(pcd)->dev_if->in_ep_regs[_ep->dwc_ep.num]->diepctl); sz.d32 =dwc_read_reg32( &GET_CORE_IF(pcd)->dev_if->in_ep_regs[_ep->dwc_ep.num]->dieptsiz); if(ctl.b.epena) { ctl.b.snak = 1; ctl.b.cnak = 0; ctl.b.epena = 0; ctl.b.epdis = 1; dwc_write_reg32(&GET_CORE_IF(pcd)->dev_if->in_ep_regs[_ep->dwc_ep.num]->diepctl, ctl.d32); do{ ctl.d32 = dwc_read_reg32(&GET_CORE_IF(pcd)->dev_if->in_ep_regs[_ep->dwc_ep.num]->diepctl); }while(ctl.b.epdis); dwc_otg_flush_tx_fifo( GET_CORE_IF(pcd),_ep->dwc_ep.tx_fifo_num); } } return 0; } #if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0) static int dwc_otg_pcd_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t _gfp_flags) #else static int dwc_otg_pcd_ep_queue(struct usb_ep *_ep, struct usb_request *_req, int _gfp_flags) #endif { dwc_otg_pcd_request_t *req; dwc_otg_pcd_ep_t *ep; dwc_otg_pcd_t *pcd; unsigned long flags = 0; int DoSent=0; DWC_DEBUGPL(DBG_PCDV,"%s(%p,%p,%d)\n", __func__, _ep, _req, _gfp_flags); req = container_of(_req, dwc_otg_pcd_request_t, req); if (!_req || !_req->complete || !_req->buf || !list_empty(&req->queue)) { DWC_WARN("%s, bad params\n", __func__); return -EINVAL; } ep = container_of(_ep, dwc_otg_pcd_ep_t, ep); if (!_ep || (!ep->desc && ep->dwc_ep.num != 0)) { if(!_ep) DWC_WARN("%s, null ep\n", __func__); else DWC_WARN("%s, Null Descriptor\n", __func__); return -EINVAL; } pcd = ep->pcd; if (!pcd->driver || pcd->gadget.speed == USB_SPEED_UNKNOWN) { DWC_DEBUGPL(DBG_PCDV, "gadget.speed=%d\n", pcd->gadget.speed); DWC_WARN("%s, bogus device state\n", __func__); return -ESHUTDOWN; } DWC_DEBUGPL(DBG_PCD, "%s queue req %p, len %d buf %p\n", _ep->name, _req, _req->length, _req->buf); if (!GET_CORE_IF(pcd)->core_params->opt) if (ep->dwc_ep.num != 0) DWC_ERROR("%s queue req %p, len %d buf %p\n", _ep->name, _req, _req->length, _req->buf); SPIN_LOCK_IRQSAVE(&ep->pcd->lock, flags); #if defined(DEBUG) & defined(VERBOSE) dump_msg(_req->buf, _req->length); #endif _req->status = -EINPROGRESS; _req->actual = 0; /* * For EP0 IN without premature status, zlp is required? */ if (ep->dwc_ep.num == 0 && ep->dwc_ep.is_in) { DWC_DEBUGPL(DBG_PCDV, "%s-OUT ZLP\n", _ep->name); //_req->zero = 1; //TESTING } /* When using Periodic, invalid the previous request , Howard*/ if(ep->dwc_ep.tx_fifo_num && !list_empty(&ep->queue)) { dwc_otg_pcd_request_t *reqtoremove; while (!list_empty(&ep->queue)) { reqtoremove = list_entry(ep->queue.next, dwc_otg_pcd_request_t, queue); request_done(ep, reqtoremove, -ESHUTDOWN ); } } /* Start the transfer */ if (list_empty(&ep->queue) && !ep->stopped) { ep->dwc_ep.start_xfer_buff=NULL; #if defined(GADGET_UNALIGNED_BUFFER_ADJUST) if( ep->dwc_ep.is_in) { ep->using_aligned_tx_buf=0; if (_req->length && (((unsigned long)_req->buf) & 3)) { if(ep->aligned_tx_buf && ep->aligned_tx_buf_len && ep->aligned_tx_buf_len < _req->length) { usb_free_buf(ep->aligned_tx_buf); ep->aligned_tx_buf=NULL; ep->aligned_tx_buf_len=0; } if(! ep->aligned_tx_buf || !ep->aligned_tx_buf_len) { ep->aligned_tx_buf = usb_alloc_buf(_req->length, 0); if(ep->aligned_tx_buf) ep->aligned_tx_buf_len = _req->length; } if(ep->aligned_tx_buf && ep->aligned_tx_buf_len >= _req->length) { ep->dwc_ep.start_xfer_buff=ep->aligned_tx_buf; memcpy(ep->aligned_tx_buf, _req->buf, _req->length); ep->using_aligned_tx_buf=1; } else DWC_WARN("%s():%d\n",__func__,__LINE__); } else ep->dwc_ep.start_xfer_buff = _req->buf; } else //is out { ep->using_aligned_rx_buf=0; if (((unsigned long)_req->buf) & 3) { if( ep->aligned_rx_buf && ep->aligned_rx_buf_len && _req->length > ep->aligned_rx_buf_len ) { usb_free_buf(ep->aligned_rx_buf); ep->aligned_rx_buf=NULL; ep->aligned_rx_buf_len=0; } if(! ep->aligned_rx_buf || !ep->aligned_rx_buf_len) { if(_req->length > ep->dwc_ep.maxpacket) { ep->aligned_rx_buf = usb_alloc_buf(_req->length, 1); if(ep->aligned_rx_buf) ep->aligned_rx_buf_len = _req->length; } else { ep->aligned_rx_buf = usb_alloc_buf(ep->dwc_ep.maxpacket, 1); if(ep->aligned_rx_buf) ep->aligned_rx_buf_len = ep->dwc_ep.maxpacket; } } if(ep->aligned_rx_buf) { ep->dwc_ep.start_xfer_buff=ep->aligned_rx_buf; ep->using_aligned_rx_buf=1; } else DWC_WARN("%s():%d\n",__func__,__LINE__); } else ep->dwc_ep.start_xfer_buff = _req->buf; } #elif defined(GADGET_UNALIGNED_BUFFER_CHECK) if ( ep->dwc_ep.is_in && _req->length && (((unsigned long)_req->buf) & 3)) { DWC_WARN("UNALIGNED BUFFER in REQUEST\n"); } else if ( !ep->dwc_ep.is_in && (((unsigned long)_req->buf) & 3)) { DWC_WARN("UNALIGNED BUFFER in REQUEST\n"); } else { ep->dwc_ep.start_xfer_buff = _req->buf; } #else ep->dwc_ep.start_xfer_buff = _req->buf; #endif /* EP0 Transfer? */ if (ep->dwc_ep.num == 0) { if(ep->dwc_ep.is_in) DoSent=1; else DoSent=2; switch (pcd->ep0state) { case EP0_IN_DATA_PHASE: DWC_DEBUGPL(DBG_PCD, "%s ep0: EP0_IN_DATA_PHASE\n", __func__); break; case EP0_OUT_DATA_PHASE: DWC_DEBUGPL(DBG_PCD, "%s ep0: EP0_OUT_DATA_PHASE\n", __func__); if (pcd->request_config) { /* Complete STATUS PHASE */ ep->dwc_ep.is_in = 1; pcd->ep0state = EP0_IN_STATUS; } break; default: DWC_DEBUGPL(DBG_ANY, "ep0: odd state %d\n",pcd->ep0state); SPIN_UNLOCK_IRQRESTORE(&pcd->lock, flags); return -EL2HLT; } ep->dwc_ep.xfer_buff = ep->dwc_ep.start_xfer_buff; ep->dwc_ep.len_in_xfer = 0; ep->dwc_ep.xfer_count = 0; ep->dwc_ep.sent_zlp = _req->zero; ep->dwc_ep.xfer_len = _req->length; dwc_otg_ep_start_transfer( GET_CORE_IF(pcd), &ep->dwc_ep ); } else { if(ep->dwc_ep.is_in) DoSent=1; else DoSent=2; /* Setup and start the Transfer */ ep->dwc_ep.xfer_buff = ep->dwc_ep.start_xfer_buff; ep->dwc_ep.len_in_xfer = 0; ep->dwc_ep.xfer_count = 0; ep->dwc_ep.sent_zlp = 0; ep->dwc_ep.xfer_len = _req->length; //if(ep->dwc_ep.num==1) printk(KERN_INFO "%s() context=%lu\n",__func__,_req->context); dwc_otg_ep_start_transfer( GET_CORE_IF(pcd),&ep->dwc_ep ); } } else { if(ep->dwc_ep.is_in) DoSent=3; else DoSent=4; } if (req != 0) { ++pcd->request_pending; ++ep->request_pending; list_add_tail(&req->queue, &ep->queue); #ifndef USE_INTERNAL_DMA if (ep->dwc_ep.is_in && ep->stopped) { diepmsk_data_t diepmsk = { .d32 = 0}; diepmsk.b.intktxfemp = 1; dwc_modify_reg32( &GET_CORE_IF(pcd)->dev_if->dev_global_regs->diepmsk, 0, diepmsk.d32 ); } #endif req->ep=ep; #ifdef TxQueueTimer req->req_timeout_on=0; if(ep->dwc_ep.num && ep->dwc_ep.is_in) { init_timer( &req->req_timeout ); req->req_timeout.function = dwc_otg_req_timeout; req->req_timeout.data = (unsigned long)req; req->req_timeout.expires = jiffies + (HZ*2); add_timer(&req->req_timeout); req->req_timeout_on=1; } #endif if(ep->dwc_ep.num==1) { req->txing=1; list_add_tail(&req->txing_queue, &txing_queue); } } SPIN_UNLOCK_IRQRESTORE(&pcd->lock, flags); return 0; } /** * This function cancels an I/O request from an EP. */ static int dwc_otg_pcd_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) { dwc_otg_pcd_request_t *req; dwc_otg_pcd_ep_t *ep; dwc_otg_pcd_t *pcd; unsigned long flags; DWC_DEBUGPL(DBG_PCDV,"%s(%p,%p)\n", __func__, _ep, _req); ep = container_of(_ep, dwc_otg_pcd_ep_t, ep); if (!_ep || !_req || (!ep->desc && ep->dwc_ep.num != 0)) { DWC_WARN("%s, bad argument\n", __func__); return -EINVAL; } pcd = ep->pcd; if (!pcd->driver || pcd->gadget.speed == USB_SPEED_UNKNOWN) { DWC_WARN("%s, bogus device state\n", __func__); return -ESHUTDOWN; } SPIN_LOCK_IRQSAVE(&pcd->lock, flags); DWC_DEBUGPL(DBG_PCDV, "%s %s %s %p\n", __func__, _ep->name, ep->dwc_ep.is_in ? "IN" : "OUT", _req); /* make sure it's actually queued on this endpoint */ list_for_each_entry( req, &ep->queue, queue) { if (&req->req == _req) break; } if (&req->req != _req) { SPIN_UNLOCK_IRQRESTORE(&pcd->lock, flags); return -EINVAL; } if (!list_empty(&req->queue)) request_done(ep, req, -ECONNRESET); else req = 0; SPIN_UNLOCK_IRQRESTORE(&pcd->lock, flags); return req ? 0 : -EOPNOTSUPP; } /** * usb_ep_set_halt stalls an endpoint. * * usb_ep_clear_halt clears an endpoint halt and resets its data * toggle. * * Both of these functions are implemented with the same underlying * function. The behavior depends on the value argument. * * @param[in] _ep the Endpoint to halt or clear halt. * @param[in] _value * - 1 means set_halt, * - 0 means clear_halt. */ static int dwc_otg_pcd_ep_set_halt(struct usb_ep *_ep, int _value) { int retval = 0; unsigned long flags; dwc_otg_pcd_ep_t *ep = 0; DWC_DEBUGPL(DBG_PCD,"HALT %s %d\n", _ep->name, _value); ep = container_of(_ep, dwc_otg_pcd_ep_t, ep); if (!_ep || (!ep->desc && ep != &ep->pcd->ep[0]) || ep->desc->bmAttributes == USB_ENDPOINT_XFER_ISOC) { if (!_ep ) DWC_WARN("%s, null ep\n", __func__); if ( !ep->desc && ep != &ep->pcd->ep[0]) DWC_WARN("%s, ep not equal to ep0\n", __func__); if( ep->desc->bmAttributes == USB_ENDPOINT_XFER_ISOC) DWC_WARN("%s, bad ep ISOC\n", __func__); return -EINVAL; } SPIN_LOCK_IRQSAVE(&ep->pcd->lock, flags); if (!list_empty(&ep->queue)) { DWC_WARN("%s() %s XFer In process\n", __func__, _ep->name); retval = -EAGAIN; } else if (_value == 0) dwc_otg_ep_clear_stall( ep->pcd->otg_dev->core_if, &ep->dwc_ep ); else { if (ep->dwc_ep.num == 0) ep->pcd->ep0state = EP0_STALL; ep->stopped = 1; dwc_otg_ep_set_stall( ep->pcd->otg_dev->core_if,&ep->dwc_ep ); } SPIN_UNLOCK_IRQRESTORE(&ep->pcd->lock, flags); return retval; } static void dwc_otg_pcd_fifo_flush (struct usb_ep *_ep) { dwc_otg_pcd_ep_t *ep; // dwc_otg_core_if_t *_core_if; ep = container_of(_ep, dwc_otg_pcd_ep_t, ep); //if(ep->dwc_ep.active) { if(ep->dwc_ep.is_in)//Tx { dwc_otg_flush_tx_fifo(ep->pcd->otg_dev->core_if, ep->dwc_ep.num); /* all Tx FIFOs */ } } } static struct usb_ep_ops dwc_otg_pcd_ep_ops = { .enable = dwc_otg_pcd_ep_enable, .disable = dwc_otg_pcd_ep_disable, .alloc_request = dwc_otg_pcd_alloc_request, .free_request = dwc_otg_pcd_free_request, .alloc_buffer = dwc_otg_pcd_alloc_buffer, .free_buffer = dwc_otg_pcd_free_buffer, .queue = dwc_otg_pcd_ep_queue, .dequeue = dwc_otg_pcd_ep_dequeue, .set_halt = dwc_otg_pcd_ep_set_halt, .fifo_status = 0, .fifo_flush = dwc_otg_pcd_fifo_flush, }; /* Gadget Operations */ /** * The following gadget operations will be implemented in the DWC_otg * PCD. Functions in the API that are not described below are not * implemented. * * The Gadget API provides wrapper functions for each of the function * pointers defined in usb_gadget_ops. The Gadget Driver calls the * wrapper function, which then calls the underlying PCD function. The * following sections are named according to the wrapper functions * (except for ioctl, which doesn't have a wrapper function). Within * each section, the corresponding DWC_otg PCD function name is * specified. * */ /** *Gets the USB Frame number of the last SOF. */ static int dwc_otg_pcd_get_frame(struct usb_gadget *_gadget) { dwc_otg_pcd_t *pcd; DWC_DEBUGPL(DBG_PCDV,"%s(%p)\n", __func__, _gadget); if (_gadget == 0) return -ENODEV; else { pcd = container_of(_gadget, dwc_otg_pcd_t, gadget); dwc_otg_get_frame_number( GET_CORE_IF(pcd) ); } return 0; } /** * Initiates Session Request Protocol (SRP) to wakeup the host if no * session is in progress. If a session is already in progress, but * the device is suspended, remote wakeup signaling is started. * */ #if defined(_USB_LED_) /** *Sets the USB LED ON/OFF. */ static int dwc_otg_pcd_usb_led(struct usb_gadget *_gadget, int is_on) { dwc_otg_pcd_t *pcd; DWC_DEBUGPL(DBG_PCDV,"%s(%p)\n", __func__, _gadget); if (_gadget == 0) return -ENODEV; else { pcd = container_of(_gadget, dwc_otg_pcd_t, gadget); //dwc_otg_get_frame_number( GET_CORE_IF(pcd) ); dwc_otg_set_usb_led( GET_CORE_IF(pcd), is_on ); } return 0; } #endif //_USB_LED_ #ifdef CONFIG_IFX_USB_LED /** *Sets the USB LED ON/OFF. */ static int dwc_otg_pcd_usb_led(struct usb_gadget *_gadget, int mode) { dwc_otg_pcd_t *pcd; DWC_DEBUGPL(DBG_PCDV,"%s(%p)\n", __func__, _gadget); if (_gadget == 0){ return -ENODEV; } else { pcd = container_of(_gadget, dwc_otg_pcd_t, gadget); //dwc_otg_get_frame_number( GET_CORE_IF(pcd) ); dwc_otg_set_usb_led( GET_CORE_IF(pcd), mode ); } return 0; } #endif // CONFIG_IFX_USB_LED static const struct usb_gadget_ops dwc_otg_pcd_ops = { .get_frame = dwc_otg_pcd_get_frame, // current versions must always be self-powered #if defined(_USB_LED_) .usb_led = dwc_otg_pcd_usb_led, #endif //_USB_LED_ #ifdef CONFIG_IFX_USB_LED .usbdev_led = dwc_otg_pcd_usb_led, #endif // CONFIG_IFX_USB_LED }; /** * This function is the top level PCD interrupt handler. */ #if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0) static irqreturn_t dwc_otg_pcd_irq(int _irq, void *_dev) #else static irqreturn_t dwc_otg_pcd_irq(int _irq, void *_dev, struct pt_regs *_r) #endif { dwc_otg_pcd_t *pcd = _dev; int32_t retval=0; mask_and_ack_ifx_irq (_irq); retval = dwc_otg_pcd_handle_intr( pcd ); return IRQ_RETVAL(retval); } /** * PCD Callback function for initializing the PCD when switching to * device mode. * * @param _p void pointer to the dwc_otg_pcd_t */ static int32_t dwc_otg_pcd_start_cb( void *_p ) { dwc_otg_pcd_t *pcd = (dwc_otg_pcd_t *)_p; /* * Initialized the Core for Device mode. */ dwc_otg_core_dev_init(GET_CORE_IF(pcd)); return 1; } /** * PCD Callback function for stopping the PCD when switching to Host * mode. * * @param _p void pointer to the dwc_otg_pcd_t */ static int32_t dwc_otg_pcd_stop_cb( void *_p ) { dwc_otg_pcd_t *pcd = (dwc_otg_pcd_t *)_p; extern void dwc_otg_pcd_stop(dwc_otg_pcd_t *_pcd); dwc_otg_pcd_stop( pcd ); return 1; } int32_t dwc_otg_pcd_handle_usb_reset_intr( dwc_otg_pcd_t * _pcd); /** * PCD Callback function for notifying the PCD when resuming from * suspend. * * @param _p void pointer to the dwc_otg_pcd_t */ static int32_t dwc_otg_pcd_suspend_cb( void *_p ) { dwc_otg_pcd_t *pcd = (dwc_otg_pcd_t *)_p; dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd); dwc_otg_dev_if_t *dev_if = core_if->dev_if; doepmsk_data_t doepmsk = { .d32 = 0}; diepmsk_data_t diepmsk = { .d32 = 0}; daint_data_t daintmsk = { .d32 = 0}; dcfg_data_t dcfg = { .d32=0 }; // if (pcd->driver && pcd->driver->suspend) // { // SPIN_UNLOCK(&pcd->lock); // pcd->driver->suspend(&pcd->gadget); // SPIN_LOCK(&pcd->lock); // } dwc_otg_pcd_stop( pcd );/* Howard */ dwc_otg_core_dev_init(core_if); //start // //dwc_otg_pcd_resume_cb( _p ); // if (pcd->driver && pcd->driver->resume) // { // SPIN_UNLOCK(&pcd->lock); // pcd->driver->resume(&pcd->gadget); // SPIN_LOCK(&pcd->lock); // } //dwc_otg_pcd_handle_usb_reset_intr( _p ); { dctl_data_t dctl = {.d32=0}; depctl_data_t doepctl = { .d32 = 0}; // gintsts_data_t gintsts; int i = 0; /* 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 < dev_if->num_eps; i++) dwc_write_reg32( &dev_if->out_ep_regs[i]->doepctl, doepctl.d32 ); /* Flush FIFO */ dwc_otg_flush_tx_fifo(core_if, 0x10 ); dwc_otg_flush_rx_fifo(core_if); 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; 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 ); } return 1; } /** * PCD Callback function for notifying the PCD when resuming from * suspend. * * @param _p void pointer to the dwc_otg_pcd_t */ static int32_t dwc_otg_pcd_resume_cb( void *_p ) { dwc_otg_pcd_t *pcd = (dwc_otg_pcd_t *)_p; if (pcd->driver && pcd->driver->resume) { SPIN_UNLOCK(&pcd->lock); pcd->driver->resume(&pcd->gadget); SPIN_LOCK(&pcd->lock); } return 1; } /** * PCD Callback structure for handling mode switching. */ static dwc_otg_cil_callbacks_t pcd_callbacks = { .start = dwc_otg_pcd_start_cb, .stop = dwc_otg_pcd_stop_cb, .suspend = dwc_otg_pcd_suspend_cb, .resume_wakeup = dwc_otg_pcd_resume_cb, .p = 0, /* Set at registration */ }; /** * Tasklet * */ extern void start_next_request( dwc_otg_pcd_ep_t *_ep ); static void start_xfer_tasklet_func (unsigned long data) { dwc_otg_pcd_t *pcd = (dwc_otg_pcd_t*)data; dwc_otg_core_if_t *core_if = pcd->otg_dev->core_if; int i; DWC_DEBUGPL(DBG_PCDV, "Start xfer tasklet\n"); for (i=0; idev_if->num_eps; i++) { depctl_data_t diepctl; diepctl.d32 = dwc_read_reg32( &core_if->dev_if->in_ep_regs[i]->diepctl); if (pcd->ep[i].queue_sof) { pcd->ep[i].queue_sof = 0; start_next_request (&pcd->ep[i]); // break; } } return; } static struct tasklet_struct start_xfer_tasklet = { .next = NULL, .state = 0, .count = ATOMIC_INIT(0), .func = start_xfer_tasklet_func, .data = 0, }; /** * This function initialized the pcd Dp structures to there default * state. * * @param _pcd the pcd structure. */ void dwc_otg_pcd_reinit(dwc_otg_pcd_t *_pcd) { static const char * names[] = { "ep0", "ep1", "ep2", "ep3", "ep4", "ep5", "ep6", "ep7", "ep8", "ep9", "ep10", "ep11", "ep12", "ep13", "ep14", "ep15", }; uint32_t i; DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, _pcd); INIT_LIST_HEAD (&_pcd->gadget.ep_list); _pcd->gadget.ep0 = &_pcd->ep [0].ep; _pcd->gadget.speed = USB_SPEED_UNKNOWN; INIT_LIST_HEAD (&_pcd->gadget.ep0->ep_list); /** * Initialize the EP structures. */ for (i = 0; i < _pcd->num_eps; i++) { dwc_otg_pcd_ep_t *ep = &_pcd->ep[i]; /* Init EP structure */ ep->desc = 0; ep->pcd = _pcd; ep->stopped = 1; #if defined(GADGET_UNALIGNED_BUFFER_ADJUST) ep->using_aligned_tx_buf=0; ep->using_aligned_rx_buf=0; ep->aligned_tx_buf=NULL; ep->aligned_tx_buf_len=0; ep->aligned_rx_buf=NULL; ep->aligned_rx_buf_len=0; #endif /* Init DWC ep structure */ ep->dwc_ep.num = i; ep->dwc_ep.active = 0; ep->dwc_ep.tx_fifo_num = 0; /* Control until ep is actvated */ ep->dwc_ep.type = DWC_OTG_EP_TYPE_CONTROL; ep->dwc_ep.maxpacket = MAX_PACKET_SIZE; ep->dwc_ep.start_xfer_buff = 0; ep->dwc_ep.xfer_buff = 0; ep->dwc_ep.xfer_len = 0; ep->dwc_ep.xfer_count = 0; ep->dwc_ep.sent_zlp = 0; ep->dwc_ep.len_in_xfer = 0; ep->queue_sof = 0; /* Init the usb_ep structure. */ /** * @todo NGS: Add direction to EP, based on contents * of HWCFG1. Need a copy of HWCFG1 in pcd structure? * sprintf( ";r */ ep->ep.name = names[i]; ep->ep.ops = &dwc_otg_pcd_ep_ops; /** * @todo NGS: What should the max packet size be set to * here? Before EP type is set? */ ep->ep.maxpacket = MAX_PACKET_SIZE; list_add_tail (&ep->ep.ep_list, &_pcd->gadget.ep_list); INIT_LIST_HEAD (&ep->queue); } /* remove ep0 from the list. There is a ep0 pointer.*/ list_del_init (&_pcd->ep[0].ep.ep_list); _pcd->ep0state = EP0_DISCONNECT; _pcd->ep[0].ep.maxpacket = MAX_EP0_SIZE; _pcd->ep[0].dwc_ep.maxpacket = MAX_EP0_SIZE; _pcd->ep[0].dwc_ep.type = DWC_OTG_EP_TYPE_CONTROL; } /** * This function releases the Gadget device. * required by device_unregister(). * * @todo Should this do something? Should it free the PCD? */ #if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0) static void dwc_otg_pcd_gadget_release(struct device *_dev) { DWC_DEBUGPL(DBG_PCDV,"%s(%p)\n", __func__, _dev); } #endif /** * This function initialized the PCD portion of the driver. */ #if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0) int dwc_otg_pcd_init(struct device *_dev) #else int dwc_otg_pcd_init(void) #endif { static char pcd_name[] = "dwc_otg_pcd"; dwc_otg_pcd_t *pcd; dwc_otg_device_t *otg_dev = dev_get_drvdata(_dev); int retval = 0; DWC_DEBUGPL(DBG_PCDV,"%s(%p)\n",__func__, otg_dev ); /* * Allocate PCD structure */ pcd = kmalloc( sizeof(dwc_otg_pcd_t), GFP_KERNEL); if (pcd == 0) { return -ENOMEM; } memset( pcd, 0, sizeof(dwc_otg_pcd_t)); spin_lock_init( &pcd->lock ); otg_dev->pcd = pcd; s_pcd = pcd; pcd->gadget.name = pcd_name; #if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0) strcpy(pcd->gadget.dev.bus_id, "gadget"); #endif pcd->otg_dev = otg_dev; #if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0) //pcd->gadget.dev.parent = &_dev->parent; pcd->gadget.dev.parent = _dev->parent; pcd->gadget.dev.release = dwc_otg_pcd_gadget_release; #endif pcd->gadget.ops = &dwc_otg_pcd_ops; /* If the module is set to FS or if the PHY_TYPE is FS then the gadget * should not report as dual-speed capable. replace the following line * with the block of code below it once the software is debugged for * this. If is_dualspeed = 0 then the gadget driver should not report * a device qualifier descriptor when queried. */ if (GET_CORE_IF(pcd)->core_params->speed == DWC_SPEED_PARAM_FULL) pcd->gadget.is_dualspeed = 0; else pcd->gadget.is_dualspeed = 1; pcd->gadget.is_otg = 0; pcd->driver = 0; #if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0) /* Register the gadget device */ device_register( &pcd->gadget.dev ); #endif /* * Initialized the Core for Device mode. */ dwc_otg_core_dev_init( GET_CORE_IF(pcd) ); /* * Initialize EP structures */ pcd->num_eps = otg_dev->core_if->hwcfg2.b.num_dev_ep; dwc_otg_pcd_reinit( pcd ); /* * Register the PCD Callbacks. */ dwc_otg_cil_register_pcd_callbacks( otg_dev->core_if, &pcd_callbacks, pcd ); /* * Setup interupt handler */ DWC_DEBUGPL( DBG_ANY, "registering handler for irq%d\n", otg_dev->irq); retval = request_irq(otg_dev->irq, &dwc_otg_pcd_irq, SA_SHIRQ, pcd->gadget.name, pcd); if (retval != 0) { DWC_ERROR("request of irq%d failed\n", otg_dev->irq); kfree (pcd); return -EBUSY; } /* * Initialize the DMA buffer for SETUP packets */ pcd->setup_pkt = usb_alloc_buf(sizeof (*pcd->setup_pkt), 1); pcd->setup_pkt_buf = usb_alloc_buf(sizeof (*pcd->setup_pkt) * 10, 1); pcd->status_buf = usb_alloc_buf(sizeof (uint16_t) *4, 1); if(0==pcd->setup_pkt) DWC_ERROR("%s() %s\n", __func__, "pcd->setup_pkt allocation failed!\n"); if(0==pcd->setup_pkt_buf) DWC_ERROR("%s() %s\n", __func__, "pcd->setup_pkt_buf allocation failed!\n"); if(0==pcd->status_buf) DWC_ERROR("%s() %s\n", __func__, "pcd->status_buf allocation failed!\n"); if (pcd->setup_pkt == 0 ||pcd->setup_pkt_buf == 0 || pcd->status_buf == 0) { if (pcd->setup_pkt) kfree (pcd->setup_pkt); if (pcd->setup_pkt_buf) kfree (pcd->setup_pkt_buf); if (pcd->status_buf) kfree (pcd->status_buf); kfree (pcd); return -ENOMEM; } /* Initialize tasklet */ start_xfer_tasklet.data = (unsigned long)pcd; pcd->start_xfer_tasklet = &start_xfer_tasklet; INIT_LIST_HEAD(&txing_queue); return 0; } int usb_gadget_unregister_driver(struct usb_gadget_driver *_driver); /** * Cleanup the PCD. */ #if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0) void dwc_otg_pcd_remove(struct device *_dev) #else void dwc_otg_pcd_remove(void) #endif { dwc_otg_device_t *otg_dev = dev_get_drvdata(_dev); dwc_otg_pcd_t *pcd = otg_dev->pcd; DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, otg_dev); /* * Free the IRQ */ free_irq( otg_dev->irq, pcd ); /* start with the driver above us */ if (pcd->driver) { /* should have been done already by driver model core */ DWC_WARN("driver '%s' is still registered\n", pcd->driver->driver.name); usb_gadget_unregister_driver( pcd->driver); } #if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0) device_unregister(&pcd->gadget.dev); #endif usb_free_buf(pcd->setup_pkt); usb_free_buf(pcd->setup_pkt_buf); usb_free_buf(pcd->status_buf); //power off dwc_otg_power_off (); kfree( pcd ); otg_dev->pcd = 0; } /** * This function registers a gadget driver with the PCD. * * When a driver is successfully registered, it will receive control * requests including set_configuration(), which enables non-control * requests. then usb traffic follows until a disconnect is reported. * then a host may connect again, or the driver might get unbound. * * @param _driver The driver being registered */ int usb_gadget_register_driver(struct usb_gadget_driver *_driver) { int retval; DWC_DEBUGPL(DBG_PCD, "registering gadget driver '%s'\n", _driver->driver.name); if (!_driver || _driver->speed == USB_SPEED_UNKNOWN || !_driver->bind || !_driver->unbind || !_driver->disconnect || !_driver->setup) { DWC_DEBUGPL(DBG_PCDV,"EINVAL\n"); return -EINVAL; } if (s_pcd == 0) { DWC_DEBUGPL(DBG_PCDV,"ENODEV\n"); return -ENODEV; } if (s_pcd->driver != 0) { DWC_DEBUGPL(DBG_PCDV,"EBUSY (%p)\n", s_pcd->driver); return -EBUSY; } /* hook up the driver */ s_pcd->driver = _driver; #if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0) s_pcd->gadget.dev.driver = &_driver->driver; #endif DWC_DEBUGPL(DBG_PCD, "bind to driver %s\n", _driver->driver.name); retval = _driver->bind(&s_pcd->gadget); if (retval) { DWC_ERROR("bind to driver %s --> error %d\n", _driver->driver.name, retval); s_pcd->driver = 0; #if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0) s_pcd->gadget.dev.driver = 0; #endif return retval; } DWC_DEBUGPL(DBG_ANY, "registered gadget driver '%s'\n", _driver->driver.name); #ifndef USE_ORIG dwc_otg_enable_global_interrupts( GET_CORE_IF(s_pcd) ); //dwc_otg_phy_power_on(); #endif return 0; } EXPORT_SYMBOL(usb_gadget_register_driver); /** * This function unregisters a gadget driver * * @param _driver The driver being unregistered */ int usb_gadget_unregister_driver(struct usb_gadget_driver *_driver) { //DWC_DEBUGPL(DBG_PCDV,"%s(%p)\n", __func__, _driver); if (s_pcd == 0) { DWC_DEBUGPL(DBG_ANY, "%s Return(%d): s_pcd==0\n", __func__, -ENODEV); return -ENODEV; } if (_driver == 0 || _driver != s_pcd->driver) { DWC_DEBUGPL( DBG_ANY, "%s Return(%d): driver?\n", __func__, -EINVAL); return -EINVAL; } _driver->unbind(&s_pcd->gadget); s_pcd->driver = 0; DWC_DEBUGPL(DBG_ANY, "unregistered driver '%s'\n", _driver->driver.name); return 0; } EXPORT_SYMBOL(usb_gadget_unregister_driver); /** * This function unregisters a gadget driver * * @param _driver The driver being unregistered */ void usb_gadget_switch_periodic(int on_off) { period_allowed=on_off; } EXPORT_SYMBOL(usb_gadget_switch_periodic); #ifdef MAC_ECM_FIX void usb_gadget_set_reset() { if(s_pcd) { dctl_data_t dctl = {.d32=0}; dctl.d32 = dwc_read_reg32(&GET_CORE_IF(s_pcd)->dev_if->dev_global_regs->dctl); dctl.b.sftdiscon = 1; dwc_write_reg32(&GET_CORE_IF(s_pcd)->dev_if->dev_global_regs->dctl, dctl.d32); MDELAY(100); dctl.b.sftdiscon=0; dwc_write_reg32(&GET_CORE_IF(s_pcd)->dev_if->dev_global_regs->dctl, dctl.d32); } } EXPORT_SYMBOL(usb_gadget_set_reset); #endif #endif /* DWC_IS_DEVICE */