?? dwc_otg_pcd.c
字號:
/* ========================================================================== * * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless * otherwise expressly agreed to in writing between Synopsys and you. * * The Software IS NOT an item of Licensed Software or Licensed Product under * any End User Software License Agreement or Agreement for Licensed Product * with Synopsys or any supplement thereto. You are permitted to use and * redistribute this Software in source and binary forms, with or without * modification, provided that redistributions of source code must retain this * notice. You may not view, use, disclose, copy or distribute this file or * any information contained herein except pursuant to this license grant from * Synopsys. If you do not agree with this notice, including the disclaimer * below, then you are not authorized to use the Software. * * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. * ========================================================================== *///#ifndef DWC_HOST_ONLY#if 0/** @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 * <code><linux/usb_gadget.h></code>. The USB EP operations API is * defined in the structure <code>usb_ep_ops</code> and the USB * Controller API is defined in the structure * <code>usb_gadget_ops</code>. * * 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 * */#include <linux/kernel.h>#include <linux/module.h>#include <linux/moduleparam.h>#include <linux/init.h>#include <linux/device.h>#include <linux/errno.h>#include <linux/list.h>#include <linux/interrupt.h>#include <linux/string.h>#include <linux/dma-mapping.h>#include <linux/version.h>#include <asm/arch/lm.h>#include <asm/arch/irqs.h>#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,21)# include <linux/usb/ch9.h>#else# include <linux/usb_ch9.h>#endif#include <linux/usb_gadget.h>#include "dwc_otg_driver.h"#include "dwc_otg_pcd.h"/** * 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;/* Display the contents of the buffer */extern void dump_msg(const u8 * buf, unsigned int length);/** * This function completes a request. It call's the request call back. */void request_done(dwc_otg_pcd_ep_t * _ep, dwc_otg_pcd_request_t * _req, int _status){ unsigned stopped = _ep->stopped; 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; } /* don't modify queue heads during completion callback */ _ep->stopped = 1; 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; } _ep->stopped = stopped;}/** * 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 assigns periodic Tx FIFO to an periodic EP * in shared Tx FIFO mode */static uint32_t assign_perio_tx_fifo(dwc_otg_core_if_t * core_if){ uint32_t PerTxMsk = 1; int i; for (i = 0; i < core_if->hwcfg4.b.num_dev_perio_in_ep; ++i) { if ((PerTxMsk & core_if->p_tx_msk) == 0) { core_if->p_tx_msk |= PerTxMsk; return i + 1; } PerTxMsk <<= 1; } return 0;}/** * This function releases periodic Tx FIFO * in shared Tx FIFO mode */static void release_perio_tx_fifo(dwc_otg_core_if_t * core_if, uint32_t fifo_num){ core_if->p_tx_msk = (core_if->p_tx_msk & (1 << (fifo_num - 1))) ^ core_if->p_tx_msk;}/** * This function assigns periodic Tx FIFO to an periodic EP * in shared Tx FIFO mode */static uint32_t assign_tx_fifo(dwc_otg_core_if_t * core_if){ uint32_t TxMsk = 1; int i; for (i = 0; i < core_if->hwcfg4.b.num_in_eps; ++i) { if ((TxMsk & core_if->tx_msk) == 0) { core_if->tx_msk |= TxMsk; return i + 1; } TxMsk <<= 1; } return 0;}/** * This function releases periodic Tx FIFO * in shared Tx FIFO mode */static void release_tx_fifo(dwc_otg_core_if_t * core_if, uint32_t fifo_num){ core_if->tx_msk = (core_if->tx_msk & (1 << (fifo_num - 1))) ^ core_if->tx_msk;}/** * This function allocates a DMA Descriptor chain for the Endpoint * buffer to be used for a transfer to/from the specified endpoint. * * @param _ep The endpoint for which is allocated Descriptor chain * @param count The desired number of Descriptors in the chain */static dwc_otg_dma_desc_t *ep_alloc_desc(uint32_t * dma_desc_addr){ return dma_alloc_coherent(NULL, sizeof(dwc_otg_dma_desc_t), dma_desc_addr, GFP_KERNEL);}/** * This function frees a DMA Descriptor chain that was allocated by ep_alloc_desc. * * @param _ep The endpoint - the owner of the Descriptor chain * @param count The desired number of Descriptors in the chain */static void ep_free_desc(dwc_otg_dma_desc_t * desc_addr, uint32_t dma_desc_addr){ dma_free_coherent(NULL, sizeof(dwc_otg_dma_desc_t), desc_addr, dma_desc_addr);}#ifdef _EN_ISOC_/** * This function allocates a DMA Descriptor chain for the Endpoint * buffer to be used for a transfer to/from the specified endpoint. * * @param _ep The endpoint for which is allocated Descriptor chain * @param count The desired number of Descriptors in the chain */static dwc_otg_iso_dma_desc_t *ep_alloc_iso_desc_chain(uint32_t * dma_desc_addr, uint32_t count){ return dma_alloc_coherent(NULL, count * sizeof(dwc_otg_iso_dma_desc_t), dma_desc_addr, GFP_KERNEL);}/** * This function frees a DMA Descriptor chain that was allocated by ep_alloc_desc. * * @param _ep The endpoint - the owner of the Descriptor chain * @param count The desired number of Descriptors in the chain */static void ep_free_iso_desc_chain(dwc_otg_iso_dma_desc_t * desc_addr, uint32_t dma_desc_addr, uint32_t count){ dma_free_coherent(NULL, count * sizeof(dwc_otg_dma_desc_t), desc_addr, dma_desc_addr);}#endif //_EN_ISOC_/** * 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) { DWC_WARN("%s, bad ep or descriptor\n", __func__); return -EINVAL; } if (ep == &ep->pcd->ep0) { 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); /* * 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.type = _desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; if (ep->dwc_ep.is_in) { if (!pcd->otg_dev->core_if->en_multiple_tx_fifo) { ep->dwc_ep.tx_fifo_num = 0; if (ep->dwc_ep.type == USB_ENDPOINT_XFER_ISOC) { /* * if ISOC EP then assign a Periodic Tx FIFO. */ ep->dwc_ep.tx_fifo_num = assign_perio_tx_fifo(pcd->otg_dev->core_if); } } else { /* * if Dedicated FIFOs mode is on then assign a Tx FIFO. */ ep->dwc_ep.tx_fifo_num = assign_tx_fifo(pcd->otg_dev->core_if); } } /* Set initial data PID. */ if (ep->dwc_ep.type == USB_ENDPOINT_XFER_BULK) { ep->dwc_ep.data_pid_start = 0; } /* Alloc DMA Descriptors */ if (GET_CORE_IF(pcd)->dma_desc_enable) { if (ep->dwc_ep.type != USB_ENDPOINT_XFER_ISOC) { ep->dwc_ep.desc_addr = ep_alloc_desc(&ep->dwc_ep.dma_desc_addr); } } 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; dwc_otg_pcd_t *pcd = 0; unsigned long flags; dwc_otg_dma_desc_t *desc_addr; uint32_t dma_desc_addr; 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 (ep->dwc_ep.is_in) { dwc_otg_flush_tx_fifo(GET_CORE_IF(ep->pcd), ep->dwc_ep.tx_fifo_num); release_perio_tx_fifo(GET_CORE_IF(ep->pcd), ep->dwc_ep.tx_fifo_num); release_tx_fifo(GET_CORE_IF(ep->pcd), ep->dwc_ep.tx_fifo_num); } /* Free DMA Descriptors */ pcd = ep->pcd; if (GET_CORE_IF(pcd)->dma_desc_enable) { if (ep->dwc_ep.type != USB_ENDPOINT_XFER_ISOC) { desc_addr = ep->dwc_ep.desc_addr; dma_desc_addr = ep->dwc_ep.dma_desc_addr; /* Cannot call dma_free_coherent() with IRQs disabled */ SPIN_UNLOCK_IRQRESTORE(&ep->pcd->lock, flags); ep_free_desc(desc_addr, dma_desc_addr); goto out_unlocked; } } SPIN_UNLOCK_IRQRESTORE(&ep->pcd->lock, flags); out_unlocked: 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. */static struct usb_request *dwc_otg_pcd_alloc_request(struct usb_ep *_ep,#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) int _gfp_flags#else gfp_t _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_WARN("%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); 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); 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. */static void *dwc_otg_pcd_alloc_buffer(struct usb_ep *_ep, unsigned _bytes, dma_addr_t * _dma,#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) int _gfp_flags#else gfp_t _gfp_flags#endif ){ void *buf; dwc_otg_pcd_ep_t *ep; dwc_otg_pcd_t *pcd = 0; ep = container_of(_ep, dwc_otg_pcd_ep_t, ep); pcd = ep->pcd; DWC_DEBUGPL(DBG_PCDV, "%s(%p,%d,%p,%0x)\n", __func__, _ep, _bytes, _dma, _gfp_flags); /* Check dword alignment */ if ((_bytes & 0x3UL) != 0) { DWC_WARN("%s() Buffer size is not a multiple of" "DWORD size (%d)", __func__, _bytes); } if (GET_CORE_IF(pcd)->dma_enable) { buf = dma_alloc_coherent(NULL, _bytes, _dma, _gfp_flags); } else { buf = kmalloc(_bytes, _gfp_flags); } /* Check dword alignment */ if (((int) buf & 0x3UL) != 0) { DWC_WARN("%s() Buffer is not DWORD aligned (%p)", __func__, 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){ dwc_otg_pcd_ep_t *ep; dwc_otg_pcd_t *pcd = 0;
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -