diff options
Diffstat (limited to 'drivers/usb/gadget/dwc_otg/dwc_otg_pcd.c')
-rw-r--r-- | drivers/usb/gadget/dwc_otg/dwc_otg_pcd.c | 1408 |
1 files changed, 1408 insertions, 0 deletions
diff --git a/drivers/usb/gadget/dwc_otg/dwc_otg_pcd.c b/drivers/usb/gadget/dwc_otg/dwc_otg_pcd.c new file mode 100644 index 00000000000..4bd17de7939 --- /dev/null +++ b/drivers/usb/gadget/dwc_otg/dwc_otg_pcd.c @@ -0,0 +1,1408 @@ + /* ========================================================================== + * $File: //dwh/usb_iip/dev/software/otg_ipmate/linux/drivers/dwc_otg_pcd.c $ + * $Revision: #18 $ + * $Date: 2007/02/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 CONFIG_DWC_HOST_ONLY + +/** @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/usb/ch9.h> +#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); + if (_req->mapped) { + dma_unmap_single(_ep->pcd->gadget.dev.parent, + _req->req.dma, _req->req.length, + _ep->dwc_ep.is_in + ? DMA_TO_DEVICE + : DMA_FROM_DEVICE); + _req->req.dma = DMA_ADDR_INVALID; + _req->mapped = 0; + } else + dma_sync_single_for_cpu(_ep->pcd->gadget.dev.parent, + _req->req.dma, _req->req.length, + _ep->dwc_ep.is_in + ? DMA_TO_DEVICE + : DMA_FROM_DEVICE); + + 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; + +#ifdef CONFIG_405EZ + /* + * Added-sr: 2007-07-26 + * + * Finally, when the current request is done, mark this endpoint + * as not active, so that new requests can be processed. + */ + _ep->dwc_ep.active = 0; +#endif +} + +/** + * 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 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 ((_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 = 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 ((_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 (ep->dwc_ep.is_in) { + 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); + } + 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. + */ +static struct usb_request *dwc_otg_pcd_alloc_request(struct usb_ep *_ep, + gfp_t _gfp_flags) +{ + 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 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. + */ +static int dwc_otg_pcd_ep_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t _gfp_flags) +{ + int prevented = 0; + dwc_otg_pcd_request_t * req; + dwc_otg_pcd_ep_t * ep; + dwc_otg_pcd_t * pcd; + unsigned long flags = 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)) { + DWC_WARN("%s, bad ep\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(CONFIG_DWC_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; + } + + /* map virtual address to hardware */ + if (_req->dma == DMA_ADDR_INVALID) { + _req->dma = dma_map_single(ep->pcd->gadget.dev.parent, + _req->buf, + _req->length, + ep->dwc_ep.is_in + ? DMA_TO_DEVICE : + DMA_FROM_DEVICE); + req->mapped = 1; + } else { + dma_sync_single_for_device(ep->pcd->gadget.dev.parent, + _req->dma, _req->length, + ep->dwc_ep.is_in + ? DMA_TO_DEVICE : + DMA_FROM_DEVICE); + req->mapped = 0; + } + + /* Start the transfer */ + if (list_empty(&ep->queue) && !ep->stopped) { + /* EP0 Transfer? */ + if (ep->dwc_ep.num == 0) { + 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_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.dma_addr = _req->dma; + ep->dwc_ep.start_xfer_buff = _req->buf; + ep->dwc_ep.xfer_buff = _req->buf; + ep->dwc_ep.xfer_len = _req->length; + ep->dwc_ep.xfer_count = 0; + ep->dwc_ep.sent_zlp = 0; + ep->dwc_ep.total_len = ep->dwc_ep.xfer_len; + dwc_otg_ep0_start_transfer(GET_CORE_IF(pcd), + &ep->dwc_ep); + } else { + /* Setup and start the Transfer */ + ep->dwc_ep.dma_addr = _req->dma; + ep->dwc_ep.start_xfer_buff = _req->buf; + ep->dwc_ep.xfer_buff = _req->buf; + ep->dwc_ep.xfer_len = _req->length; + ep->dwc_ep.xfer_count = 0; + ep->dwc_ep.sent_zlp = 0; + ep->dwc_ep.total_len = ep->dwc_ep.xfer_len; + dwc_otg_ep_start_transfer(GET_CORE_IF(pcd), + &ep->dwc_ep); + } + } + if ((req != 0) || prevented) { + ++pcd->request_pending; + list_add_tail(&req->queue, &ep->queue); + if (ep->dwc_ep.is_in && ep->stopped + && !(GET_CORE_IF(pcd)->dma_enable)) { + /** @todo NGS Create a function for this. */ + 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); + } + } + 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 + * - 0 means clear_halt. + * - 1 means set_halt, + * - 2 means clear stall lock flag. + * - 3 means set stall lock flag. + */ +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->ep0) + || ep->desc->bmAttributes == USB_ENDPOINT_XFER_ISOC) { + DWC_WARN("%s, bad ep\n", __func__); + return -EINVAL; + } + SPIN_LOCK_IRQSAVE(&ep->pcd->lock, flags); + if (ep->dwc_ep.is_in && !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 (_value == 1) { + 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); + } else if (_value == 2) { + ep->dwc_ep.stall_clear_flag = 0; + } else if (_value == 3) { + ep->dwc_ep.stall_clear_flag = 1; + } + SPIN_UNLOCK_IRQRESTORE(&ep->pcd->lock, flags); + return retval; +} + +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, + .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 = 0, +}; + +/* 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; +} +void dwc_otg_pcd_initiate_srp(dwc_otg_pcd_t * _pcd) +{ + uint32_t * addr = (uint32_t *) &(GET_CORE_IF(_pcd)->core_global_regs->gotgctl); + gotgctl_data_t mem; + gotgctl_data_t val; + val.d32 = dwc_read_reg32(addr); + if (val.b.sesreq) { + DWC_ERROR("Session Request Already active!\n"); + return; + } + DWC_NOTICE("Session Request Initated\n"); + mem.d32 = dwc_read_reg32(addr); + mem.b.sesreq = 1; + dwc_write_reg32(addr, mem.d32); + + /* Start the SRP timer */ + dwc_otg_pcd_start_srp_timer(_pcd); + return; +} +void dwc_otg_pcd_remote_wakeup(dwc_otg_pcd_t * _pcd, int set) +{ + dctl_data_t dctl = {.d32 = 0}; + volatile uint32_t *addr = &(GET_CORE_IF(_pcd)->dev_if->dev_global_regs->dctl); + if (dwc_otg_is_device_mode(GET_CORE_IF(_pcd))) { + if (_pcd->remote_wakeup_enable) { + if (set) { + dctl.b.rmtwkupsig = 1; + dwc_modify_reg32(addr, 0, dctl.d32); + DWC_DEBUGPL(DBG_PCD, "Set Remote Wakeup\n"); + mdelay(1); + dwc_modify_reg32(addr, dctl.d32, 0); + DWC_DEBUGPL(DBG_PCD, "Clear Remote Wakeup\n"); + } else { + } + } else { + DWC_DEBUGPL(DBG_PCD, "Remote Wakeup is disabled\n"); + } + } + return; +} + + +/** + * 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. + * + */ +static int dwc_otg_pcd_wakeup(struct usb_gadget *_gadget) +{ + unsigned long flags; + dwc_otg_pcd_t * pcd; + dsts_data_t dsts; + gotgctl_data_t gotgctl; + DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, _gadget); + if (_gadget == 0) { + return -ENODEV; + } else { + pcd = container_of(_gadget, dwc_otg_pcd_t, gadget); + } + SPIN_LOCK_IRQSAVE(&pcd->lock, flags); + + /* + * This function starts the Protocol if no session is in progress. If + * a session is already in progress, but the device is suspended, + * remote wakeup signaling is started. + */ + + /* Check if valid session */ + gotgctl.d32 = dwc_read_reg32(&(GET_CORE_IF(pcd)->core_global_regs->gotgctl)); + if (gotgctl.b.bsesvld) { + + /* Check if suspend state */ + dsts.d32 = dwc_read_reg32(&(GET_CORE_IF(pcd)->dev_if->dev_global_regs->dsts)); + if (dsts.b.suspsts) { + dwc_otg_pcd_remote_wakeup(pcd, 1); + } + } else { + dwc_otg_pcd_initiate_srp(pcd); + } + SPIN_UNLOCK_IRQRESTORE(&pcd->lock, flags); + return 0; +} + +static const struct usb_gadget_ops dwc_otg_pcd_ops = +{ + .get_frame = dwc_otg_pcd_get_frame, + .wakeup = dwc_otg_pcd_wakeup, + // current versions must always be self-powered +}; + +/** + * This function updates the otg values in the gadget structure. + */ +void dwc_otg_pcd_update_otg(dwc_otg_pcd_t * _pcd, const unsigned _reset) +{ + if (!_pcd->gadget.is_otg) + return; + if (_reset) { + _pcd->b_hnp_enable = 0; + _pcd->a_hnp_support = 0; + _pcd->a_alt_hnp_support = 0; + } + _pcd->gadget.b_hnp_enable = _pcd->b_hnp_enable; + _pcd->gadget.a_hnp_support = _pcd->a_hnp_support; + _pcd->gadget.a_alt_hnp_support = _pcd->a_alt_hnp_support; +} + +/** + * This function is the top level PCD interrupt handler. + */ +static irqreturn_t dwc_otg_pcd_irq(int _irq, void *_dev) +{ + dwc_otg_pcd_t * pcd = _dev; + int32_t retval = IRQ_NONE; + 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 <code>dwc_otg_pcd_t</code> + */ +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. + */ + if (dwc_otg_is_device_mode(GET_CORE_IF(pcd))) { + 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 <code>dwc_otg_pcd_t</code> + */ +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; +} + +/** + * PCD Callback function for notifying the PCD when resuming from + * suspend. + * + * @param _p void pointer to the <code>dwc_otg_pcd_t</code> + */ +static int32_t dwc_otg_pcd_suspend_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->suspend(&pcd->gadget); + SPIN_LOCK(&pcd->lock); + } + return 1; +} + + +/** + * PCD Callback function for notifying the PCD when resuming from + * suspend. + * + * @param _p void pointer to the <code>dwc_otg_pcd_t</code> + */ +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); + } + + /* Stop the SRP timeout timer. */ + if ((GET_CORE_IF(pcd)->core_params->phy_type != + DWC_PHY_TYPE_PARAM_FS) || (!GET_CORE_IF(pcd)->core_params->i2c_enable)) { + if (GET_CORE_IF(pcd)->srp_timer_started) { + GET_CORE_IF(pcd)->srp_timer_started = 0; + del_timer(&pcd->srp_timer); + } + } + 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 */ +}; + +/** + * This function is called when the SRP timer expires. The SRP should + * complete within 6 seconds. + */ +static void srp_timeout(unsigned long _ptr) +{ + gotgctl_data_t gotgctl; + dwc_otg_core_if_t * core_if = (dwc_otg_core_if_t *) _ptr; + volatile uint32_t *addr = &core_if->core_global_regs->gotgctl; + gotgctl.d32 = dwc_read_reg32(addr); + core_if->srp_timer_started = 0; + if ((core_if->core_params->phy_type == DWC_PHY_TYPE_PARAM_FS) && + (core_if->core_params->i2c_enable)) { + DWC_PRINT("SRP Timeout\n"); + if ((core_if->srp_success) && (gotgctl.b.bsesvld)) { + if (core_if->pcd_cb && core_if->pcd_cb->resume_wakeup) { + core_if->pcd_cb->resume_wakeup(core_if-> + pcd_cb->p); + } + + /* Clear Session Request */ + gotgctl.d32 = 0; + gotgctl.b.sesreq = 1; + dwc_modify_reg32(&core_if->core_global_regs->gotgctl,gotgctl.d32, 0); + core_if->srp_success = 0; + } else { + DWC_ERROR("Device not connected/responding\n"); + gotgctl.b.sesreq = 0; + dwc_write_reg32(addr, gotgctl.d32); + } + } else if (gotgctl.b.sesreq) { + DWC_PRINT("SRP Timeout\n"); + DWC_ERROR("Device not connected/responding\n"); + gotgctl.b.sesreq = 0; + dwc_write_reg32(addr, gotgctl.d32); + } else { + DWC_PRINT(" SRP GOTGCTL=%0x\n", gotgctl.d32); + } +} + +/** + * Start the SRP timer to detect when the SRP does not complete within + * 6 seconds. + * + * @param _pcd the pcd structure. + */ +void dwc_otg_pcd_start_srp_timer(dwc_otg_pcd_t * _pcd) +{ + struct timer_list *srp_timer = &_pcd->srp_timer; + GET_CORE_IF(_pcd)->srp_timer_started = 1; + init_timer(srp_timer); + srp_timer->function = srp_timeout; + srp_timer->data = (unsigned long)GET_CORE_IF(_pcd); + srp_timer->expires = jiffies + (HZ * 6); + add_timer(srp_timer); +} + +/** + * 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; + depctl_data_t diepctl; + DWC_DEBUGPL(DBG_PCDV, "Start xfer tasklet\n"); + 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); + // break; + } + for (i = 0; i < core_if->dev_if->num_in_eps; i++) { + depctl_data_t diepctl; + diepctl.d32 = + dwc_read_reg32(&core_if->dev_if->in_ep_regs[i]->diepctl); + if (pcd->in_ep[i].queue_sof) { + pcd->in_ep[i].queue_sof = 0; + start_next_request(&pcd->in_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", "ep1in", "ep2in", "ep3in", "ep4in", "ep5in", + "ep6in", "ep7in", "ep8in", "ep9in", "ep10in", "ep11in", "ep12in", "ep13in", + "ep14in", "ep15in", "ep1out", "ep2out", "ep3out", "ep4out", "ep5out", + "ep6out", "ep7out", "ep8out", "ep9out", "ep10out", "ep11out", "ep12out", + "ep13out", "ep14out", "ep15out" + }; + + int i; + int in_ep_cntr, out_ep_cntr; + uint32_t hwcfg1; + uint32_t num_in_eps = (GET_CORE_IF(_pcd))->dev_if->num_in_eps; + uint32_t num_out_eps = (GET_CORE_IF(_pcd))->dev_if->num_out_eps; + dwc_otg_pcd_ep_t * ep; + DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, _pcd); + INIT_LIST_HEAD(&_pcd->gadget.ep_list); + _pcd->gadget.ep0 = &_pcd->ep0.ep; + _pcd->gadget.speed = USB_SPEED_UNKNOWN; + INIT_LIST_HEAD(&_pcd->gadget.ep0->ep_list); + + /** + * Initialize the EP0 structure. + */ + ep = &_pcd->ep0; + + /* Init EP structure */ + ep->desc = 0; + ep->pcd = _pcd; + ep->stopped = 1; + + /* Init DWC ep structure */ + ep->dwc_ep.num = 0; + 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.dma_addr = 0; + 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.total_len = 0; + ep->queue_sof = 0; + + /* Init the usb_ep structure. */ + ep->ep.name = names[0]; + 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); + + /** + * Initialize the EP structures. + */ + in_ep_cntr = 0; + hwcfg1 = (GET_CORE_IF(_pcd))->hwcfg1.d32 >> 3; + for (i = 1; in_ep_cntr < num_in_eps; i++) { + if ((hwcfg1 & 0x1) == 0) { + dwc_otg_pcd_ep_t * ep = &_pcd->in_ep[in_ep_cntr]; + in_ep_cntr++; + + /* Init EP structure */ + ep->desc = 0; + ep->pcd = _pcd; + ep->stopped = 1; + + /* Init DWC ep structure */ + ep->dwc_ep.is_in = 1; + 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.dma_addr = 0; + 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.total_len = 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); + } + hwcfg1 >>= 2; + } + out_ep_cntr = 0; + hwcfg1 = (GET_CORE_IF(_pcd))->hwcfg1.d32 >> 2; + for (i = 1; out_ep_cntr < num_out_eps; i++) { + if ((hwcfg1 & 0x1) == 0) { + dwc_otg_pcd_ep_t * ep = &_pcd->out_ep[out_ep_cntr]; + out_ep_cntr++; + + /* Init EP structure */ + ep->desc = 0; + ep->pcd = _pcd; + ep->stopped = 1; + + /* Init DWC ep structure */ + ep->dwc_ep.is_in = 0; + 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.dma_addr = 0; + 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.total_len = 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[15 + 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); + } + hwcfg1 >>= 2; + } + + /* remove ep0 from the list. There is a ep0 pointer. */ + list_del_init(&_pcd->ep0.ep.ep_list); + _pcd->ep0state = EP0_DISCONNECT; + _pcd->ep0.ep.maxpacket = MAX_EP0_SIZE; + _pcd->ep0.dwc_ep.maxpacket = MAX_EP0_SIZE; + _pcd->ep0.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? + */ +static void dwc_otg_pcd_gadget_release(struct device *_dev) +{ + DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, _dev); +} + +/** + * This function initialized the PCD portion of the driver. + * + */ +int __init dwc_otg_pcd_init(struct device *_dev) +{ + 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__, _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; + dev_set_name(&pcd->gadget.dev, "gadget"); + pcd->otg_dev = dev_get_drvdata(_dev); + pcd->gadget.dev.parent = _dev; + pcd->gadget.dev.release = dwc_otg_pcd_gadget_release; + pcd->gadget.ops = &dwc_otg_pcd_ops; + if (GET_CORE_IF(pcd)->hwcfg4.b.ded_fifo_en) { + DWC_PRINT("Dedicated Tx FIFOs mode\n"); + } else { + DWC_PRINT("Shared Tx FIFO mode\n"); + } + + /* 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) + || ((GET_CORE_IF(pcd)->hwcfg2.b.hs_phy_type == 2) + && (GET_CORE_IF(pcd)->hwcfg2.b.fs_phy_type == 1) + && (GET_CORE_IF(pcd)->core_params->ulpi_fs_ls))) { + pcd->gadget.is_dualspeed = 0; + } else { + pcd->gadget.is_dualspeed = 1; + } + if ((otg_dev->core_if->hwcfg2.b.op_mode == + DWC_HWCFG2_OP_MODE_NO_SRP_CAPABLE_DEVICE) + || (otg_dev->core_if->hwcfg2.b.op_mode == + DWC_HWCFG2_OP_MODE_NO_SRP_CAPABLE_HOST) + || (otg_dev->core_if->hwcfg2.b.op_mode == + DWC_HWCFG2_OP_MODE_SRP_CAPABLE_DEVICE) + || (otg_dev->core_if->hwcfg2.b.op_mode == + DWC_HWCFG2_OP_MODE_SRP_CAPABLE_HOST)) { + pcd->gadget.is_otg = 0; + } else { + pcd->gadget.is_otg = 1; + } + pcd->driver = 0; + + /* Register the gadget device */ + retval = device_register(&pcd->gadget.dev); + + /* + * Initialized the Core for Device mode. + */ + if (dwc_otg_is_device_mode(GET_CORE_IF(pcd))) { + dwc_otg_core_dev_init(GET_CORE_IF(pcd)); + } + + /* + * Initialize EP structures + */ + 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, IRQF_SHARED, + 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 + */ + if (GET_CORE_IF(pcd)->dma_enable) { + pcd->setup_pkt = dma_alloc_coherent(_dev, sizeof(*pcd->setup_pkt) * 5, + &pcd->setup_pkt_dma_handle, 0); + pcd->status_buf = dma_alloc_coherent(_dev, sizeof(uint16_t), + &pcd->status_buf_dma_handle, 0); + } else { + pcd->setup_pkt = kmalloc(sizeof(*pcd->setup_pkt) * 5, GFP_KERNEL); + pcd->status_buf = kmalloc(sizeof(uint16_t), GFP_KERNEL); + } + if (pcd->setup_pkt == 0) { + kfree(pcd); + return -ENOMEM; + } + + /* Initialize tasklet */ + start_xfer_tasklet.data = (unsigned long)pcd; + pcd->start_xfer_tasklet = &start_xfer_tasklet; + return 0; +} + +/** + * Cleanup the PCD. + */ +void dwc_otg_pcd_remove(struct device *_dev) +{ + 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__, _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); + } + device_unregister(&pcd->gadget.dev); + if (GET_CORE_IF(pcd)->dma_enable) { + dma_free_coherent(NULL, sizeof(*pcd->setup_pkt) * 5, + pcd->setup_pkt, pcd->setup_pkt_dma_handle); + dma_free_coherent(NULL, sizeof(uint16_t), pcd->status_buf, + pcd->status_buf_dma_handle); + } else { + kfree(pcd->setup_pkt); + kfree(pcd->status_buf); + } + 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->disconnect || !_driver->setup) { + DWC_DEBUGPL(DBG_PCDV, "EINVAL\n"); +#if 1 + printk("_driver=0x%p speed=0x%x bind=0x%p unbind=0x%p disconnect=0x%p setup=0x%p\n", _driver, _driver->speed, _driver->bind, _driver->unbind, _driver->disconnect, _driver->setup); +#endif + 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; + s_pcd->gadget.dev.driver = &_driver->driver; + { + dwc_otg_core_if_t *_core_if = s_pcd->otg_dev->core_if; + if(_core_if) { + dwc_otg_disable_global_interrupts(_core_if); + dwc_otg_core_init(_core_if); + dwc_otg_pcd_reinit(s_pcd); + dwc_otg_enable_global_interrupts(_core_if); + if (_core_if->pcd_cb) + dwc_otg_pcd_start_cb(_core_if->pcd_cb->p); + } + + } + 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; + s_pcd->gadget.dev.driver = 0; + return retval; + } + DWC_DEBUGPL(DBG_ANY, "registered gadget driver '%s'\n", + _driver->driver.name); + 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); + +#endif /* DWC_HOST_ONLY */ |