summaryrefslogtreecommitdiff
path: root/drivers/usb/gadget/dwc_otg/dwc_otg_pcd_linux.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/gadget/dwc_otg/dwc_otg_pcd_linux.c')
-rw-r--r--drivers/usb/gadget/dwc_otg/dwc_otg_pcd_linux.c1675
1 files changed, 1675 insertions, 0 deletions
diff --git a/drivers/usb/gadget/dwc_otg/dwc_otg_pcd_linux.c b/drivers/usb/gadget/dwc_otg/dwc_otg_pcd_linux.c
new file mode 100644
index 00000000..a3001d00
--- /dev/null
+++ b/drivers/usb/gadget/dwc_otg/dwc_otg_pcd_linux.c
@@ -0,0 +1,1675 @@
+ /* ==========================================================================
+ * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_pcd_linux.c $
+ * $Revision: #6 $
+ * $Date: 2009/02/18 $
+ * $Change: 1190679 $
+ *
+ * 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
+
+/** @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>.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/timer.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/device.h>
+
+
+#include <asm/io.h>
+# include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+#include <linux/gpio.h>
+#include <linux/irq.h>
+#include <linux/wakelock.h>
+#include <linux/switch.h>
+#include <soc/sprd/usb.h>
+
+#include "dwc_otg_driver.h"
+#include "dwc_otg_pcd_if.h"
+#include "dwc_otg_cil.h"
+#include "dwc_otg_pcd.h"
+#include "dwc_otg_dbg.h"
+#include "usb_hw.h"
+
+static struct gadget_wrapper {
+ dwc_otg_pcd_t *pcd;
+
+ struct usb_gadget gadget;
+ struct usb_gadget_driver *driver;
+
+ struct usb_ep ep0;
+ struct usb_ep in_ep[16];
+ struct usb_ep out_ep[16];
+ /*
+ * this timer is used for checking cable type, usb or ac adapter.
+ */
+ struct workqueue_struct *cable2pc_wq;
+ struct workqueue_struct *detect_wq;
+ struct workqueue_struct *reinit_wq;
+ struct work_struct detect_work;
+ struct work_struct reinit_work;
+ struct delayed_work cable2pc;
+
+ struct switch_dev sdev;
+ int udc_startup;
+ int enabled;
+ int vbus;
+ spinlock_t lock;
+} *gadget_wrapper;
+
+static struct wake_lock usb_wake_lock;
+static u64 dwc_otg_pcd_dmamask = DMA_BIT_MASK(32);
+
+static DEFINE_MUTEX(udc_lock);
+
+#define CABLE_TIMEOUT (HZ*15)
+
+/* Display the contents of the buffer */
+extern void dump_msg(const u8 * buf, unsigned int length);
+
+extern int in_calibration(void);
+extern void dwc_otg_pcd_stop(dwc_otg_pcd_t *pcd);
+static void dwc_otg_clear_all_int(dwc_otg_core_if_t *core_if);
+static struct timer_list reinit_usb_timer;
+static int factory_mode = false;
+static int charge_mode = false; /* add by lgd, fix the problem that sometime poweroff charging is so slow */
+static int __init factory_start(char *str)
+{
+ if(str)
+ pr_info("factory mode!\n");
+ factory_mode = true;
+ return 1;
+}
+__setup("factory", factory_start);
+
+int in_factory_mode(void)
+{
+ return (factory_mode == true);
+}
+
+/* < add by lgd, fix the problem that sometime poweroff charging is so slow */
+static int __init charge_start(char *str)
+{
+ if(!strncmp(str,"charger",7))
+ {
+ DWC_DEBUG("charge mode!\n");
+ charge_mode = true;
+ return 1;
+ } else {
+ DWC_DEBUG("no charge mode!\n");
+ charge_mode = false;
+ return 0;
+ }
+}
+__setup("androidboot.mode=", charge_start);
+
+int in_charge_mode(void)
+{
+ DWC_DEBUG("%s() in\n", __func__);
+ return (charge_mode == true);
+}
+/* add by lgd, fix the problem that sometime poweroff charging is so slow > */
+
+static void usb_reinit_works(void)
+{
+ struct gadget_wrapper *d;
+
+ d = gadget_wrapper;
+
+ DWC_PRINTF("reinit start ...\n");
+ dwc_otg_disable_global_interrupts(GET_CORE_IF(d->pcd));
+ dwc_otg_clear_all_int(GET_CORE_IF(d->pcd));
+ dwc_otg_pcd_stop(d->pcd);
+ udc_disable();
+ mdelay(200);
+ udc_enable();
+ dwc_otg_core_init(GET_CORE_IF(d->pcd));
+ dwc_otg_enable_global_interrupts(GET_CORE_IF(d->pcd));
+ dwc_otg_core_dev_init(GET_CORE_IF(d->pcd));
+}
+static void reinit_usb_timer_fun(unsigned long para)
+{
+ struct gadget_wrapper *d;
+
+ d = gadget_wrapper;
+ queue_work(d->reinit_wq, &d->reinit_work);
+ del_timer(&reinit_usb_timer);
+}
+void _reinit_usb_later(dwc_otg_pcd_t *pcd)
+{
+ dctl_data_t dctl = {.d32=0};
+ dwc_otg_dev_if_t *dev_if;
+
+ if (pcd->reinit_flag == 0)
+ return;
+
+ dev_if = pcd->core_if->dev_if;
+ dctl.d32 = DWC_READ_REG32(&dev_if->dev_global_regs->dctl);
+ dctl.b.sftdiscon = 1;
+ DWC_WRITE_REG32(&dev_if->dev_global_regs->dctl, dctl.d32);
+
+ DWC_PRINTF("call reinit USB controller...\n");
+
+ mod_timer(&reinit_usb_timer, jiffies + 3*HZ);
+}
+static struct timer_list setup_transfer_timer;
+static int setup_transfer_timer_start = 0;
+
+static void setup_transfer_timer_fun(unsigned long para)
+{
+ if (gadget_wrapper->pcd->reinit_flag) {
+ setup_transfer_timer_start = 0;
+ _reinit_usb_later(gadget_wrapper->pcd);
+ }
+}
+/* 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 ep_enable(struct usb_ep *usb_ep,
+ const struct usb_endpoint_descriptor *ep_desc)
+{
+ int retval;
+
+ if (!usb_ep || !ep_desc || ep_desc->bDescriptorType != USB_DT_ENDPOINT) {
+ DWC_WARN("%s, bad ep or descriptor\n", __func__);
+ return -EINVAL;
+ }
+ if (usb_ep == &gadget_wrapper->ep0) {
+ DWC_WARN("%s, bad ep(0)\n", __func__);
+ return -EINVAL;
+ }
+
+ /* Check FIFO size? */
+ if (!ep_desc->wMaxPacketSize) {
+ DWC_WARN("%s, bad %s maxpacket\n", __func__, usb_ep->name);
+ return -ERANGE;
+ }
+
+ if (!gadget_wrapper->driver ||
+ gadget_wrapper->gadget.speed == USB_SPEED_UNKNOWN) {
+ DWC_WARN("%s, bogus device state\n", __func__);
+ return -ESHUTDOWN;
+ }
+
+ retval = dwc_otg_pcd_ep_enable(gadget_wrapper->pcd,
+ (const uint8_t *)ep_desc,
+ (void *)usb_ep);
+ if (retval) {
+ DWC_WARN("dwc_otg_pcd_ep_enable failed\n");
+ return -EINVAL;
+ }
+
+ usb_ep->maxpacket = le16_to_cpu(ep_desc->wMaxPacketSize);
+
+ 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 ep_disable(struct usb_ep *usb_ep)
+{
+ int retval;
+
+ DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, usb_ep);
+ if (!usb_ep) {
+ DWC_DEBUGPL(DBG_PCD, "%s, %s not enabled\n", __func__,
+ usb_ep ? usb_ep->name : NULL);
+ return -EINVAL;
+ }
+
+ retval = dwc_otg_pcd_ep_disable(gadget_wrapper->pcd, usb_ep);
+ if (retval) {
+ retval = -EINVAL;
+ }
+
+ return retval;
+}
+
+/**
+ * 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)
+{
+ struct usb_request *usb_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;
+ }
+ usb_req = kmalloc(sizeof(*usb_req), gfp_flags);
+ if (0 == usb_req) {
+ DWC_WARN("%s() %s\n", __func__, "request allocation failed!\n");
+ return 0;
+ }
+ memset(usb_req, 0, sizeof(*usb_req));
+ usb_req->dma = DWC_DMA_ADDR_INVALID;
+
+ return usb_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_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;
+ }
+
+ 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 ep_queue(struct usb_ep *usb_ep, struct usb_request *usb_req,
+ gfp_t gfp_flags)
+{
+ dwc_otg_pcd_t *pcd;
+ int retval = 0;
+
+ //trace_printk("(%p,%p,%d)\n",
+ // usb_ep, usb_req, gfp_flags);
+
+ if (!usb_req || !usb_req->complete ||
+ (!gadget_wrapper->gadget.sg_supported &&
+ !usb_req->buf)) {
+ DWC_WARN("bad params\n");
+ return -EINVAL;
+ }
+
+ if (!usb_ep) {
+ DWC_WARN("bad ep\n");
+ return -EINVAL;
+ }
+
+ pcd = gadget_wrapper->pcd;
+ if (!gadget_wrapper->driver ||
+ gadget_wrapper->gadget.speed == USB_SPEED_UNKNOWN) {
+ DWC_DEBUGPL(DBG_PCDV, "gadget.speed=%d\n",
+ gadget_wrapper->gadget.speed);
+ DWC_WARN("bogus device state\n");
+ return -ESHUTDOWN;
+ }
+
+ //trace_printk( "%s queue req %p, len %d buf %p\n",
+ // usb_ep->name, usb_req, usb_req->length, usb_req->buf);
+
+ usb_req->status = -EINPROGRESS;
+ usb_req->actual = 0;
+
+ retval = dwc_otg_pcd_ep_queue(pcd, usb_ep, usb_req->buf, usb_req->dma/*dma_addr*/,
+ usb_req->length, usb_req->zero, usb_req->num_sgs,
+ usb_req->sg, usb_req, gfp_flags == GFP_ATOMIC ? 1 : 0);
+ if (retval) {
+ pr_err("%s, cannot enqueue a renquest, err :%d\n", __func__,
+ retval);
+ pr_info( "%s queue req %p, len %d buf %p\n",
+ usb_ep->name, usb_req, usb_req->length, usb_req->buf);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * This function cancels an I/O request from an EP.
+ */
+static int ep_dequeue(struct usb_ep *usb_ep, struct usb_request *usb_req)
+{
+
+ if (!usb_ep || !usb_req) {
+ DWC_WARN("bad argument\n");
+ return -EINVAL;
+ }
+ if (!gadget_wrapper->driver ||
+ gadget_wrapper->gadget.speed == USB_SPEED_UNKNOWN) {
+ DWC_WARN("bogus device state\n");
+ return -ESHUTDOWN;
+ }
+ if (dwc_otg_pcd_ep_dequeue(gadget_wrapper->pcd, usb_ep, usb_req)) {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * 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] usb_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 ep_halt(struct usb_ep *usb_ep, int value)
+{
+ int retval = 0;
+
+ DWC_DEBUGPL(DBG_PCD, "HALT %s %d\n", usb_ep->name, value);
+
+ if (!usb_ep) {
+ DWC_WARN("bad ep\n");
+ return -EINVAL;
+ }
+
+ retval = dwc_otg_pcd_ep_halt(gadget_wrapper->pcd, usb_ep, value);
+ if (retval == -DWC_E_AGAIN) {
+ return -EAGAIN;
+ } else if (retval) {
+ retval = -EINVAL;
+ }
+
+ return retval;
+}
+
+#ifdef DWC_EN_ISOC
+/**
+ * This function is used to submit an ISOC Transfer Request to an EP.
+ *
+ * - Every time a sync period completes the request's completion callback
+ * is called to provide data to the gadget driver.
+ * - Once submitted the request cannot be modified.
+ * - Each request is turned into periodic data packets untill ISO
+ * Transfer is stopped..
+ */
+static int iso_ep_start(struct usb_ep *usb_ep, struct usb_iso_request *req,
+ gfp_t gfp_flags)
+{
+ int retval = 0;
+
+ if (!req || !req->process_buffer || !req->buf0 || !req->buf1) {
+ DWC_WARN("bad params\n");
+ return -EINVAL;
+ }
+
+ if (!usb_ep) {
+ DWC_PRINTF("bad params\n");
+ return -EINVAL;
+ }
+
+ req->status = -EINPROGRESS;
+
+ retval =
+ dwc_otg_pcd_iso_ep_start(gadget_wrapper->pcd, usb_ep, req->buf0,
+ req->buf1, req->dma0, req->dma1,
+ req->sync_frame, req->data_pattern_frame,
+ req->data_per_frame,
+ req->flags & USB_REQ_ISO_ASAP ? -1 : req->
+ start_frame, req->buf_proc_intrvl, req,
+ gfp_flags == GFP_ATOMIC ? 1 : 0);
+
+ if (retval) {
+ return -EINVAL;
+ }
+
+ return retval;
+}
+
+/**
+ * This function stops ISO EP Periodic Data Transfer.
+ */
+static int iso_ep_stop(struct usb_ep *usb_ep, struct usb_iso_request *req)
+{
+ int retval = 0;
+ if (!usb_ep) {
+ DWC_WARN("bad ep\n");
+ }
+
+ if (!gadget_wrapper->driver ||
+ gadget_wrapper->gadget.speed == USB_SPEED_UNKNOWN) {
+ DWC_DEBUGPL(DBG_PCDV, "gadget.speed=%d\n",
+ gadget_wrapper->gadget.speed);
+ DWC_WARN("bogus device state\n");
+ }
+
+ dwc_otg_pcd_iso_ep_stop(gadget_wrapper->pcd, usb_ep, req);
+ if (retval) {
+ retval = -EINVAL;
+ }
+
+ return retval;
+}
+
+static struct usb_iso_request *alloc_iso_request(struct usb_ep *ep,
+ int packets, gfp_t gfp_flags)
+{
+ struct usb_iso_request *pReq = NULL;
+ uint32_t req_size;
+
+ req_size = sizeof(struct usb_iso_request);
+ req_size +=
+ (2 * packets * (sizeof(struct usb_gadget_iso_packet_descriptor)));
+
+ pReq = kmalloc(req_size, gfp_flags);
+ if (!pReq) {
+ DWC_WARN("Can't allocate Iso Request\n");
+ return 0;
+ }
+ pReq->iso_packet_desc0 = (void *)(pReq + 1);
+
+ pReq->iso_packet_desc1 = pReq->iso_packet_desc0 + packets;
+
+ return pReq;
+}
+
+static void free_iso_request(struct usb_ep *ep, struct usb_iso_request *req)
+{
+ kfree(req);
+}
+
+static struct usb_isoc_ep_ops dwc_otg_pcd_ep_ops = {
+ .ep_ops = {
+ .enable = ep_enable,
+ .disable = 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 = ep_queue,
+ .dequeue = ep_dequeue,
+
+ .set_halt = ep_halt,
+ .fifo_status = 0,
+ .fifo_flush = 0,
+ },
+ .iso_ep_start = iso_ep_start,
+ .iso_ep_stop = iso_ep_stop,
+ .alloc_iso_request = alloc_iso_request,
+ .free_iso_request = free_iso_request,
+};
+
+#else
+
+static struct usb_ep_ops dwc_otg_pcd_ep_ops = {
+ .enable = ep_enable,
+ .disable = ep_disable,
+
+ .alloc_request = dwc_otg_pcd_alloc_request,
+ .free_request = dwc_otg_pcd_free_request,
+
+ .queue = ep_queue,
+ .dequeue = ep_dequeue,
+
+ .set_halt = ep_halt,
+ .fifo_status = 0,
+ .fifo_flush = 0,
+
+};
+
+#endif /* _EN_ISOC_ */
+/* 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 get_frame_number(struct usb_gadget *gadget)
+{
+ struct gadget_wrapper *d;
+
+ DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, gadget);
+
+ if (gadget == 0) {
+ return -ENODEV;
+ }
+
+ d = container_of(gadget, struct gadget_wrapper, gadget);
+ return dwc_otg_pcd_get_frame_number(d->pcd);
+}
+
+#ifdef CONFIG_USB_DWC_OTG_LPM
+static int test_lpm_enabled(struct usb_gadget *gadget)
+{
+ struct gadget_wrapper *d;
+
+ d = container_of(gadget, struct gadget_wrapper, gadget);
+
+ return dwc_otg_pcd_is_lpm_enabled(d->pcd);
+}
+#endif
+
+/**
+ * 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.
+ *
+ */
+/*
+ * currently we don't need remote wakeup, for it will cause USBCV
+ * test fail;
+ */
+static int wakeup(struct usb_gadget *gadget)__attribute__((unused));
+static int wakeup(struct usb_gadget *gadget)
+{
+ struct gadget_wrapper *d;
+
+ DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, gadget);
+
+ if (gadget == 0) {
+ return -ENODEV;
+ } else {
+ d = container_of(gadget, struct gadget_wrapper, gadget);
+ }
+ dwc_otg_pcd_wakeup(d->pcd);
+ return 0;
+}
+
+extern void dwc_otg_pcd_stop(dwc_otg_pcd_t *pcd);
+
+static void __udc_startup(void);
+static void __udc_shutdown(void);
+
+static int pullup(struct usb_gadget *gadget, int is_on)
+{
+ struct gadget_wrapper *d;
+ int action = is_on;
+
+#ifndef DWC_DEVICE_ONLY
+ if(!usb_get_id_state())
+ return 0;
+#endif
+ if (gadget == 0)
+ return -ENODEV;
+ else
+ d = container_of(gadget, struct gadget_wrapper, gadget);
+
+ if (!d->enabled || !d->vbus)
+ action = 0;
+
+ mutex_lock(&udc_lock);
+ if (action) {
+ queue_delayed_work(d->cable2pc_wq, &d->cable2pc,CABLE_TIMEOUT);
+ __udc_startup();
+ } else {
+ /*
+ *this is soft disconnct, and maybe the timer started
+ *by plugin still work, need cancel this timer like plugout
+ */
+ cancel_delayed_work_sync(&d->cable2pc);
+ __udc_shutdown();
+ }
+ mutex_unlock(&udc_lock);
+
+ return 0;
+}
+
+static int dwc_usb_gadget_start(struct usb_gadget *, struct usb_gadget_driver *);
+static int dwc_usb_gadget_stop(struct usb_gadget *, struct usb_gadget_driver *);
+
+static const struct usb_gadget_ops dwc_otg_pcd_ops = {
+ .get_frame = get_frame_number,
+ //.wakeup = wakeup,
+#ifdef CONFIG_USB_DWC_OTG_LPM
+ .lpm_support = test_lpm_enabled,
+#endif
+ .pullup = pullup,
+ // current versions must always be self-powered
+ .udc_start = dwc_usb_gadget_start,
+ .udc_stop = dwc_usb_gadget_stop,
+};
+
+static int _setup(dwc_otg_pcd_t * pcd, uint8_t * bytes)
+{
+ int retval = -DWC_E_NOT_SUPPORTED;
+
+ if (pcd->reinit_flag == 1 && setup_transfer_timer_start == 0) {
+ setup_transfer_timer_start = 1;
+ mod_timer(&setup_transfer_timer, jiffies + HZ/2);
+ }
+ if (gadget_wrapper->driver && gadget_wrapper->driver->setup) {
+ retval = gadget_wrapper->driver->setup(&gadget_wrapper->gadget,
+ (struct usb_ctrlrequest
+ *)bytes);
+ }
+
+ trace_printk("setup res val: %d\n", retval);
+ //sword
+ //if (retval == -ENOTSUPP) {
+ if (retval == -EOPNOTSUPP) {
+ retval = -DWC_E_NOT_SUPPORTED;
+ } else if (retval < 0) {
+ retval = -DWC_E_INVALID;
+ }
+
+ return retval;
+}
+
+#ifdef DWC_EN_ISOC
+static int _isoc_complete(dwc_otg_pcd_t * pcd, void *ep_handle,
+ void *req_handle, int proc_buf_num)
+{
+ int i, packet_count;
+ struct usb_gadget_iso_packet_descriptor *iso_packet = 0;
+ struct usb_iso_request *iso_req = req_handle;
+
+ if (proc_buf_num) {
+ iso_packet = iso_req->iso_packet_desc1;
+ } else {
+ iso_packet = iso_req->iso_packet_desc0;
+ }
+ packet_count =
+ dwc_otg_pcd_get_iso_packet_count(pcd, ep_handle, req_handle);
+ for (i = 0; i < packet_count; ++i) {
+ int status;
+ int actual;
+ int offset;
+ dwc_otg_pcd_get_iso_packet_params(pcd, ep_handle, req_handle,
+ i, &status, &actual, &offset);
+ switch (status) {
+ case -DWC_E_NO_DATA:
+ status = -ENODATA;
+ break;
+ default:
+ if (status) {
+ DWC_PRINTF("unknown status in isoc packet\n");
+ }
+
+ }
+ iso_packet[i].status = status;
+ iso_packet[i].offset = offset;
+ iso_packet[i].actual_length = actual;
+ }
+
+ iso_req->status = 0;
+ iso_req->process_buffer(ep_handle, iso_req);
+
+ return 0;
+}
+#endif /* DWC_EN_ISOC */
+
+static int _complete(dwc_otg_pcd_t * pcd, void *ep_handle,
+ void *req_handle, int32_t status, uint32_t actual)
+{
+ struct usb_request *req = (struct usb_request *)req_handle;
+
+ if (req && req->complete) {
+ switch (status) {
+ case -DWC_E_SHUTDOWN:
+ req->status = -ESHUTDOWN;
+ break;
+ case -DWC_E_RESTART:
+ req->status = -ECONNRESET;
+ break;
+ case -DWC_E_INVALID:
+ req->status = -EINVAL;
+ break;
+ case -DWC_E_TIMEOUT:
+ req->status = -ETIMEDOUT;
+ break;
+ default:
+ req->status = status;
+
+ }
+ if (pcd->ep0.priv == ep_handle && pcd->reinit_flag == 1) {
+ pcd->reinit_flag = 3;
+ setup_transfer_timer_start = 0;
+ del_timer(&setup_transfer_timer);
+ }
+ req->actual = actual;
+ DWC_SPINUNLOCK(pcd->lock);
+ req->complete(ep_handle, req);
+ DWC_SPINLOCK(pcd->lock);
+ }
+
+ return 0;
+}
+
+static int _connect(dwc_otg_pcd_t * pcd, int speed)
+{
+ gadget_wrapper->gadget.speed = speed;
+ return 0;
+}
+
+static int _disconnect(dwc_otg_pcd_t * pcd)
+{
+ if (gadget_wrapper->driver && gadget_wrapper->driver->disconnect) {
+ gadget_wrapper->driver->disconnect(&gadget_wrapper->gadget);
+ }
+ return 0;
+}
+
+static int _resume(dwc_otg_pcd_t * pcd)
+{
+ if (gadget_wrapper->driver && gadget_wrapper->driver->resume) {
+ gadget_wrapper->driver->resume(&gadget_wrapper->gadget);
+ }
+
+ return 0;
+}
+
+static int _suspend(dwc_otg_pcd_t * pcd)
+{
+ if (gadget_wrapper->driver && gadget_wrapper->driver->suspend) {
+ gadget_wrapper->driver->suspend(&gadget_wrapper->gadget);
+ }
+ return 0;
+}
+
+/**
+ * This function updates the otg values in the gadget structure.
+ */
+static int _hnp_changed(dwc_otg_pcd_t * pcd)
+{
+
+ if (!gadget_wrapper->gadget.is_otg)
+ return 0;
+
+ gadget_wrapper->gadget.b_hnp_enable = get_b_hnp_enable(pcd);
+ gadget_wrapper->gadget.a_hnp_support = get_a_hnp_support(pcd);
+ gadget_wrapper->gadget.a_alt_hnp_support = get_a_alt_hnp_support(pcd);
+ return 0;
+}
+
+static int _reset(dwc_otg_pcd_t * pcd)
+{
+ return 0;
+}
+
+#ifdef DWC_UTE_CFI
+static int _cfi_setup(dwc_otg_pcd_t * pcd, void *cfi_req)
+{
+ int retval = -DWC_E_INVALID;
+ if (gadget_wrapper->driver->cfi_feature_setup) {
+ retval =
+ gadget_wrapper->driver->cfi_feature_setup(&gadget_wrapper->
+ gadget,
+ (struct
+ cfi_usb_ctrlrequest
+ *)cfi_req);
+ }
+
+ return retval;
+}
+#endif
+
+static const struct dwc_otg_pcd_function_ops fops = {
+ .complete = _complete,
+#ifdef DWC_EN_ISOC
+ .isoc_complete = _isoc_complete,
+#endif
+ .setup = _setup,
+ .disconnect = _disconnect,
+ .connect = _connect,
+ .resume = _resume,
+ .suspend = _suspend,
+ .hnp_changed = _hnp_changed,
+ .reset = _reset,
+#ifdef DWC_UTE_CFI
+ .cfi_setup = _cfi_setup,
+#endif
+};
+
+/**
+ * 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);
+ if (retval != 0) {
+ S3C2410X_CLEAR_EINTPEND();
+ }
+ return IRQ_RETVAL(retval);
+}
+
+/**
+ * This function initialized the usb_ep structures to there default
+ * state.
+ *
+ * @param d Pointer on gadget_wrapper.
+ */
+void gadget_add_eps(struct gadget_wrapper *d)
+{
+ 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;
+ struct usb_ep *ep;
+
+ DWC_DEBUGPL(DBG_PCDV, "%s\n", __func__);
+
+ INIT_LIST_HEAD(&d->gadget.ep_list);
+ d->gadget.ep0 = &d->ep0;
+ d->gadget.speed = USB_SPEED_UNKNOWN;
+
+ INIT_LIST_HEAD(&d->gadget.ep0->ep_list);
+
+ /**
+ * Initialize the EP0 structure.
+ */
+ ep = &d->ep0;
+
+ /* Init the usb_ep structure. */
+ ep->name = names[0];
+ ep->ops = (struct usb_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->maxpacket = MAX_PACKET_SIZE;
+ dwc_otg_pcd_ep_enable(d->pcd, NULL, ep);
+
+ list_add_tail(&ep->ep_list, &d->gadget.ep_list);
+
+ /**
+ * Initialize the EP structures.
+ */
+
+ for (i = 0; i < 15; i++) {
+ ep = &d->in_ep[i];
+
+ /* Init the usb_ep structure. */
+ ep->name = names[i + 1];
+ ep->ops = (struct usb_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->maxpacket = MAX_PACKET_SIZE;
+ list_add_tail(&ep->ep_list, &d->gadget.ep_list);
+ }
+
+ for (i = 0; i < 15; i++) {
+ ep = &d->out_ep[i];
+
+ /* Init the usb_ep structure. */
+ ep->name = names[15 + i + 1];
+ ep->ops = (struct usb_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->maxpacket = MAX_PACKET_SIZE;
+
+ list_add_tail(&ep->ep_list, &d->gadget.ep_list);
+ }
+
+ /* remove ep0 from the list. There is a ep0 pointer. */
+ list_del_init(&d->ep0.ep_list);
+
+ d->ep0.maxpacket = MAX_EP0_SIZE;
+}
+
+/**
+ * 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);
+}
+
+static struct gadget_wrapper *alloc_wrapper(
+ struct platform_device *_dev
+ )
+{
+ static char pcd_name[] = "dwc_otg";
+ dwc_otg_device_t *otg_dev = platform_get_drvdata(_dev);
+ struct gadget_wrapper *d;
+ int retval;
+
+ d = dwc_alloc(sizeof(*d));
+ if (d == NULL) {
+ return NULL;
+ }
+
+ memset(d, 0, sizeof(*d));
+
+ d->gadget.name = pcd_name;
+ d->pcd = otg_dev->pcd;
+ //sword
+ /*
+ strcpy(d->gadget.dev.bus_id, "gadget");
+ */
+ dev_set_name(&d->gadget.dev, "gadget");
+ d->gadget.dev.parent = &_dev->dev;
+ d->gadget.dev.release = dwc_otg_pcd_gadget_release;
+ d->gadget.ops = &dwc_otg_pcd_ops;
+ d->gadget.max_speed = USB_SPEED_HIGH;
+ d->gadget.is_otg = dwc_otg_pcd_is_otg(otg_dev->pcd);
+ if (d->pcd->core_if->dma_enable &&
+ d->pcd->core_if->dma_desc_enable)
+ d->gadget.sg_supported = 1;
+
+ d->driver = 0;
+ /* Register the gadget device */
+#if 0
+ retval = device_register(&d->gadget.dev);
+ if (retval != 0) {
+ DWC_ERROR("device_register failed\n");
+ dwc_free(d);
+ return NULL;
+ }
+#endif
+
+ return d;
+}
+
+static void free_wrapper(struct gadget_wrapper *d)
+{
+ if (d->driver) {
+ /* should have been done already by driver model core */
+ DWC_WARN("driver '%s' is still registered\n",
+ d->driver->driver.name);
+ usb_gadget_unregister_driver(d->driver);
+ }
+
+ device_unregister(&d->gadget.dev);
+ dwc_free(d);
+}
+
+static void __udc_startup(void)
+{
+ struct gadget_wrapper *d;
+
+ d = gadget_wrapper;
+
+ pr_info("USB:startup udc\n");
+ if (!d->udc_startup) {
+ /* < add by lgd, fix the problem that sometime poweroff charging is so slow */
+ //wake_lock(&usb_wake_lock);
+ if(!in_charge_mode()) {
+ wake_lock(&usb_wake_lock);
+ DWC_DEBUG("%s(): wake_lock", __func__);
+ }
+ /* add by lgd, fix the problem that sometime poweroff charging is so slow > */
+ udc_enable();
+ dwc_otg_core_init(GET_CORE_IF(d->pcd));
+ dwc_otg_enable_global_interrupts(GET_CORE_IF(d->pcd));
+ dwc_otg_core_dev_init(GET_CORE_IF(d->pcd));
+ d->udc_startup = 1;
+ }
+}
+static void dwc_otg_clear_all_int(dwc_otg_core_if_t *core_if)
+{
+ uint32_t inep_intr;
+ uint32_t outep_intr;
+ uint32_t gintsts;
+ uint32_t epnum = 0;
+ dwc_otg_dev_if_t *dev_if = core_if->dev_if;
+ for(epnum=0;epnum<MAX_EPS_CHANNELS;epnum++){
+ inep_intr = DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->diepint);
+ DWC_WRITE_REG32(&dev_if->in_ep_regs[epnum]->diepint, inep_intr);
+ }
+ for(epnum=0;epnum<MAX_EPS_CHANNELS;epnum++){
+ outep_intr = DWC_READ_REG32(&dev_if->out_ep_regs[epnum]->doepint);
+ DWC_WRITE_REG32(&dev_if->out_ep_regs[epnum]->doepint, outep_intr);
+ }
+ gintsts = DWC_READ_REG32(&core_if->core_global_regs->gintsts);
+ DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts);
+}
+static void __udc_shutdown(void)
+{
+ struct gadget_wrapper *d;
+
+ d = gadget_wrapper;
+
+ pr_info("USB:shutdown udc\n");
+ if (d->udc_startup) {
+ dwc_otg_disable_global_interrupts(GET_CORE_IF(d->pcd));
+ dwc_otg_clear_all_int(GET_CORE_IF(d->pcd));
+ dwc_otg_pcd_stop(d->pcd);
+ udc_disable();
+ d->udc_startup = 0;
+ /* < add by lgd, fix the problem that sometime poweroff charging is so slow */
+ //wake_unlock(&usb_wake_lock);
+ if(!in_charge_mode()) {
+ DWC_DEBUG("%s(): wake_unlock", __func__);
+ wake_unlock(&usb_wake_lock);
+ }
+ /* add by lgd, fix the problem that sometime poweroff charging is so slow > */
+ }
+}
+
+static int cable_is_connected(void);
+
+void dwc_udc_startup(void)
+{
+ //if usb cable not connect, do nothing
+ if (!cable_is_connected()) {
+ pr_warning("usb cable is not connect\n");
+ return;
+ }
+ //udc not startup, startup udc and get a wacklock
+ mutex_lock(&udc_lock);
+ __udc_startup();
+ mutex_unlock(&udc_lock);
+}
+
+void dwc_udc_shutdown(void)
+{
+ mutex_lock(&udc_lock);
+ __udc_shutdown();
+ mutex_unlock(&udc_lock);
+}
+
+int dwc_udc_state(void)
+{
+ struct gadget_wrapper *d;
+
+ d = gadget_wrapper;
+ return d->udc_startup;
+}
+
+void udc_phy_down(void)
+{
+ //struct gadget_wrapper *d = gadget_wrapper;
+
+ //dwc_otg_dev_soft_disconnect(GET_CORE_IF(d->pcd));
+}
+
+void udc_phy_up(void)
+{
+ //struct gadget_wrapper *d = gadget_wrapper;
+
+ //dwc_otg_dev_soft_connect(GET_CORE_IF(d->pcd));
+}
+static struct usb_hotplug_callback *hotplug_cb;
+static void hotplug_callback(int event, int usb_cable)
+{
+ if (unlikely(!hotplug_cb || !hotplug_cb->plugin))
+ pr_warning("%s, hotplug call back is not registered\n",
+ __func__);
+ switch (event) {
+ case VBUS_PLUG_IN:
+ if (hotplug_cb && hotplug_cb->plugin)
+ hotplug_cb->plugin(usb_cable, hotplug_cb->data);
+ break;
+ case VBUS_PLUG_OUT:
+ if (hotplug_cb && hotplug_cb->plugout)
+ hotplug_cb->plugout(usb_cable, hotplug_cb->data);
+ break;
+ default:
+ pr_warning("hotplug envent error %d\n", event);
+ break;
+ }
+ return;
+}
+
+
+static int cable_is_usb(void)
+{
+ int value;
+ struct gadget_wrapper *d = gadget_wrapper;
+
+ value = d->pcd->ep0state;
+ return value != EP0_DISCONNECT;
+}
+
+static int usb_first_enable_flag = 0;
+void usb_first_enable_store_flag(void)
+{
+ usb_first_enable_flag = 1;
+}
+
+int get_usb_first_enable_store_flag(void)
+{
+ return usb_first_enable_flag;
+}
+
+EXPORT_SYMBOL(usb_first_enable_store_flag);
+EXPORT_SYMBOL(get_usb_first_enable_store_flag);
+
+static void usb_detect_works(struct work_struct *work)
+{
+ struct gadget_wrapper *d;
+ unsigned long flags;
+ int plug_in;
+
+ d = gadget_wrapper;
+
+ spin_lock_irqsave(&d->lock, flags);
+ plug_in = d->vbus;
+ spin_unlock_irqrestore(&d->lock, flags);
+
+ mutex_lock(&udc_lock);
+ if (plug_in){
+ pr_info("usb detect plug in,vbus pull up\n");
+ hotplug_callback(VBUS_PLUG_IN, 0);
+ if(get_usb_first_enable_store_flag()){
+ queue_delayed_work(d->cable2pc_wq, &d->cable2pc,CABLE_TIMEOUT);
+ __udc_startup();
+ }
+ } else {
+ pr_info("usb detect plug out,vbus pull down\n");
+ cancel_delayed_work_sync(&d->cable2pc);
+ __udc_shutdown();
+ hotplug_callback(VBUS_PLUG_OUT, cable_is_usb());
+ }
+ mutex_unlock(&udc_lock);
+ switch_set_state(&d->sdev, !!plug_in);
+}
+
+static irqreturn_t usb_detect_handler(int irq, void *dev_id)
+{
+ struct gadget_wrapper *d;
+ int value = 0;
+
+#ifndef DWC_DEVICE_ONLY
+ /*
+ *if otg cable is connected , id state =0
+ *as host turn on vbus, in this case shouldn't call this handler
+ */
+ if(!usb_get_id_state())
+ return IRQ_HANDLED;
+#endif
+ d = gadget_wrapper;
+ if (d->driver == NULL) {
+ pr_info("too early, no gadget drive\n");
+ return IRQ_HANDLED;
+ }
+
+ value = usb_get_vbus_state();
+ if (value){
+ pr_debug("usb detect plug in\n");
+ usb_set_vbus_irq_type(irq, VBUS_PLUG_OUT);
+ } else {
+ pr_debug("usb detect plug out\n");
+ usb_set_vbus_irq_type(irq, VBUS_PLUG_IN);
+ }
+
+ d->vbus = value;
+ queue_work(d->detect_wq, &d->detect_work);
+
+ return IRQ_HANDLED;
+}
+
+int epmic_event_handler(int level)
+{
+ struct gadget_wrapper *d;
+
+#ifndef DWC_DEVICE_ONLY
+ /*
+ *if otg cable is connected , id state =0
+ *as host turn on vbus, in this case shouldn't call this handler
+ */
+ if(!usb_get_id_state())
+ return 0;
+#endif
+ d = gadget_wrapper;
+ d->vbus = level;
+ queue_work(d->detect_wq, &d->detect_work);
+ return 0;
+}
+EXPORT_SYMBOL(epmic_event_handler);
+
+static int cable_is_connected(void)
+{
+ if (usb_get_vbus_state())
+ return 1;
+ else
+ return 0;
+}
+
+/*
+ * this function will check the cable type, if it is
+ * ac adapter calbe, we disable udc for lowering the
+ * power.
+ */
+#define REENUM_CNT 1
+static void cable2pc_detect_works(struct work_struct *work)
+{
+ int usb_cable;
+ static int reenum_cnt = REENUM_CNT;
+ struct gadget_wrapper *d;
+ d = gadget_wrapper;
+ usb_cable = cable_is_usb();
+
+ /* in factory mode, we know the cable must be usb, so we enumerate
+ * again
+ */
+ if (in_factory_mode() && !usb_cable && reenum_cnt){
+ pr_info("try usb enumertation again\n");
+ reenum_cnt--;
+ mutex_lock(&udc_lock);
+ __udc_shutdown();
+ queue_delayed_work(d->cable2pc_wq, &d->cable2pc,CABLE_TIMEOUT);
+ __udc_startup();
+ mutex_unlock(&udc_lock);
+ return;
+ }
+
+ mutex_lock(&udc_lock);
+ if (!usb_cable) {
+ pr_info("cable is ac adapter\n");
+ __udc_shutdown();
+ }
+ mutex_unlock(&udc_lock);
+ return;
+}
+
+static struct device *device_dwc_otg;
+/**
+ * This function initialized the PCD portion of the driver.
+ *
+ */
+int pcd_init(
+ struct platform_device *_dev
+ )
+{
+ dwc_otg_device_t *otg_dev = platform_get_drvdata(_dev);
+ struct sprd_usb_platform_data *pdata= _dev->dev.platform_data;
+ int retval = 0;
+ int irq;
+ int plug_irq;
+
+ DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, _dev);
+
+ device_dwc_otg = &(_dev->dev);
+ if (dwc_otg_is_dma_enable(otg_dev->core_if)) {
+ _dev->dev.dma_mask = &dwc_otg_pcd_dmamask;
+ _dev->dev.coherent_dma_mask = dwc_otg_pcd_dmamask;
+ } else {
+ _dev->dev.dma_mask = (void *)0;
+ _dev->dev.coherent_dma_mask = 0;
+ }
+
+ wake_lock_init(&usb_wake_lock, WAKE_LOCK_SUSPEND, "usb_work");
+// wake_lock(&usb_wake_lock);
+ otg_dev->pcd = dwc_otg_pcd_init(otg_dev->core_if);
+
+ if (!otg_dev->pcd) {
+ DWC_ERROR("dwc_otg_pcd_init failed\n");
+ return -ENOMEM;
+ }
+
+ gadget_wrapper = alloc_wrapper(_dev);
+
+ /*
+ * Initialize EP structures
+ */
+ gadget_add_eps(gadget_wrapper);
+
+ /*
+ * Setup interupt handler
+ */
+ irq = platform_get_irq(_dev, 0);
+ DWC_DEBUGPL(DBG_ANY, "registering handler for irq%d\n", irq);
+ retval = request_irq(irq, dwc_otg_pcd_irq,
+ IRQF_SHARED, gadget_wrapper->gadget.name,
+ otg_dev->pcd);
+ //SA_SHIRQ, gadget_wrapper->gadget.name,
+ if (retval != 0) {
+ DWC_ERROR("request of irq%d failed\n", irq);
+ free_wrapper(gadget_wrapper);
+ return -EBUSY;
+ }
+ /*
+ * initialize a timer for checking cable type.
+ */
+ setup_timer(&setup_transfer_timer,
+ setup_transfer_timer_fun,
+ (unsigned long)gadget_wrapper);
+ setup_transfer_timer_start = 0;
+ setup_timer(&reinit_usb_timer, reinit_usb_timer_fun,
+ (unsigned long) gadget_wrapper);
+
+ INIT_DELAYED_WORK(&gadget_wrapper->cable2pc, cable2pc_detect_works);
+ gadget_wrapper->cable2pc_wq =
+ create_singlethread_workqueue("USB2pcWq");
+ /*
+ * setup usb cable detect interupt
+ */
+#ifndef CONFIG_MFD_SM5504
+ {
+ plug_irq = usb_alloc_vbus_irq(pdata->gpio_chgdet);
+ if (plug_irq < 0) {
+ pr_warning("cannot alloc vbus irq\n");
+ return -EBUSY;
+ }
+ usb_set_vbus_irq_type(plug_irq, VBUS_PLUG_IN);
+ #ifdef CONFIG_SC_FPGA
+ gadget_wrapper->vbus = 1;
+ #else
+ gadget_wrapper->vbus = usb_get_vbus_state();
+ #endif
+ pr_info("now usb vbus is :%d\n", gadget_wrapper->vbus);
+ retval = request_irq(plug_irq, usb_detect_handler, IRQF_SHARED | IRQF_NO_SUSPEND,
+ "usb detect", otg_dev->pcd);
+#ifndef CONFIG_MUIC_CABLE_DETECT
+ disable_irq(plug_irq);
+#endif
+ }
+ //gadget_wrapper->vbus = 1;//used when debug in FPGA, which doesn't have vbus operation
+#endif
+ spin_lock_init(&gadget_wrapper->lock);
+#ifdef CONFIG_SC_FPGA
+ gadget_wrapper->vbus = 1;
+#endif
+ INIT_WORK(&gadget_wrapper->detect_work, usb_detect_works);
+ gadget_wrapper->detect_wq =
+ create_singlethread_workqueue("USBDetectWq");
+ INIT_WORK(&gadget_wrapper->reinit_work, usb_reinit_works);
+ gadget_wrapper->reinit_wq =
+ create_singlethread_workqueue("USBReinitWq");
+ /*
+ * register a switch device for sending pnp message,
+ * for the user app need be notified immediately
+ * when plug in & plug out happen;
+ */
+ gadget_wrapper->sdev.name = "charger_cable";
+ retval = switch_dev_register(&gadget_wrapper->sdev);
+ if (retval){
+ pr_warning("register switch dev error:%s\n", __func__);
+ }
+
+ dwc_otg_pcd_start(gadget_wrapper->pcd, &fops);
+
+ /*
+ * dwc driver is ok, check if the cable is insert, if no,
+ * shutdown udc for saving power.
+ */
+ if (!gadget_wrapper->vbus){
+ pr_debug("vbus is not power now \n");
+ gadget_wrapper->udc_startup = 1;
+ __udc_shutdown();
+ }
+ gadget_wrapper->udc_startup = gadget_wrapper->vbus;
+ gadget_wrapper->enabled = 0;
+
+ retval = usb_add_gadget_udc(&_dev->dev, &gadget_wrapper->gadget);
+ if (!retval)
+ return retval;
+
+ return retval;
+}
+
+/**
+ * Cleanup the PCD.
+ */
+void pcd_remove(
+struct platform_device *_dev
+ )
+{
+ dwc_otg_device_t *otg_dev = platform_get_drvdata(_dev);
+ struct sprd_usb_platform_data *pdata= _dev->dev.platform_data;
+ dwc_otg_pcd_t *pcd = otg_dev->pcd;
+ int plug_irq;
+
+ DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, _dev);
+
+ usb_del_gadget_udc(&gadget_wrapper->gadget);
+
+ /*
+ * Free the IRQ
+ */
+ free_irq(platform_get_irq(_dev, 0), pcd);
+ plug_irq = usb_get_vbus_irq();
+ usb_free_vbus_irq(plug_irq,pdata->gpio_chgdet);
+ dwc_otg_pcd_remove(pcd);
+ destroy_workqueue(gadget_wrapper->detect_wq);
+ destroy_workqueue(gadget_wrapper->cable2pc_wq);
+ destroy_workqueue(gadget_wrapper->reinit_wq);
+ wake_lock_destroy(&usb_wake_lock);
+ switch_dev_unregister(&gadget_wrapper->sdev);
+ free_wrapper(gadget_wrapper);
+ 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
+ */
+static int dwc_usb_gadget_start(struct usb_gadget *gadget, struct usb_gadget_driver *driver)
+{
+ int retval;
+
+ DWC_DEBUGPL(DBG_PCD, "registering gadget driver '%s'\n",
+ driver->driver.name);
+ pr_info("%s\n", __func__);
+ if (gadget_wrapper == 0) {
+ DWC_DEBUGPL(DBG_PCDV, "ENODEV\n");
+ return -ENODEV;
+ }
+ if (gadget_wrapper->driver != 0) {
+ DWC_DEBUGPL(DBG_PCDV, "EBUSY (%p)\n", gadget_wrapper->driver);
+ return -EBUSY;
+ }
+
+ /* hook up the driver */
+ gadget_wrapper->driver = driver;
+ gadget_wrapper->gadget.dev.driver = &driver->driver;
+
+ gadget_wrapper->enabled = 1;
+ DWC_DEBUGPL(DBG_ANY, "registered gadget driver '%s'\n",
+ driver->driver.name);
+
+ return 0;
+}
+
+/**
+ * This function unregisters a gadget driver
+ *
+ * @param driver The driver being unregistered
+ */
+static int dwc_usb_gadget_stop(struct usb_gadget *gadget, struct usb_gadget_driver *driver)
+{
+ //DWC_DEBUGPL(DBG_PCDV,"%s(%p)\n", __func__, _driver);
+
+ if (gadget_wrapper == 0) {
+ DWC_DEBUGPL(DBG_ANY, "%s Return(%d): s_pcd==0\n", __func__,
+ -ENODEV);
+ return -ENODEV;
+ }
+ if (driver == 0 || driver != gadget_wrapper->driver) {
+ DWC_DEBUGPL(DBG_ANY, "%s Return(%d): driver?\n", __func__,
+ -EINVAL);
+ return -EINVAL;
+ }
+
+ gadget_wrapper->driver = 0;
+ gadget_wrapper->enabled = 0;
+
+ DWC_DEBUGPL(DBG_ANY, "unregistered driver '%s'\n", driver->driver.name);
+ return 0;
+}
+
+int usb_register_hotplug_callback(struct usb_hotplug_callback *cb)
+{
+ int ret = 0;
+ int plug_irq = usb_get_vbus_irq();
+ if (cb){
+ hotplug_cb = cb;
+ enable_irq(plug_irq);
+ } else {
+ pr_warning("%s, error\n", __func__);
+ ret = -EINVAL;
+ }
+ return ret;
+}
+EXPORT_SYMBOL(usb_register_hotplug_callback);
+
+struct device *get_gadget_wrapper_device()
+{
+#ifdef CONFIG_ARM64
+if(NULL != device_dwc_otg)
+ {
+ return device_dwc_otg;
+ }
+else
+ {
+ gadget_wrapper->gadget.dev.dma_mask = &dwc_otg_pcd_dmamask;
+ gadget_wrapper->gadget.dev.coherent_dma_mask = dwc_otg_pcd_dmamask;
+ return &(gadget_wrapper->gadget.dev);
+ }
+#else
+ return NULL;
+#endif
+}
+
+EXPORT_SYMBOL(get_gadget_wrapper_device);
+
+
+#endif /* DWC_HOST_ONLY */
+