From: Fei Yang Date: Thu, 6 Oct 2011 23:27:37 +0000 (-0700) Subject: USB: Merge the following changes from UMG 2.6.35 tree X-Git-Tag: 2.1b_release~2048 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=0daa2c5281d99cb08d8f445895a0187d2f25eb1d;p=kernel%2Fkernel-mfld-blackbay.git USB: Merge the following changes from UMG 2.6.35 tree Signed-off-by: Fengwei Yin Signed-off-by: Fei Yang commit 6a9f1076fde6cbcac68d51efe0bf8c8776d57f42 Author: jzhuan5 Date: Fri Sep 16 17:03:33 2011 -0400 usb: langwell_udc: protect access to ep->desc and clear Auto low power featu BZ: 9110 Two issue fixed: 1. When langwell_ep_queue is being called, there could be interrupts, then e is set NULL, after returning from interrupt, langwell_ep_queue() calls queue which will access ep->desc without checking it, this leads to NULL pointer k The patch adds lock protection to ep->desc to avoid this. 2. The auto low power feature is causing fabric error when calling langwell_ in some extreme timing situtation. In this bug, langwell_ep_disable() is cal after bus suspend is received by device controller. If auto low power featur PHCD bit will be set by controller automatically. Then, langwell_ep_disable( fabric error. Since langwell_udc driver has not utilized this feature, this patch simply d Change-Id: Ia95d9ee9dee22c19e55f046b62a864a2fb4bd084 Signed-off-by: jzhuan5 Reviewed-on: http://android.intel.com:8080/18772 Reviewed-by: Yin, Fengwei Tested-by: Yin, Fengwei commit cfbe81d988a2201ca0a02fa33481f0f014cab3ab Author: Wu, Hao Date: Thu Sep 1 22:24:24 2011 +0800 usb/penwell_otg: increase ulpi access timeout BZ: 7272 This patch increase the timeout for each ulpi access via PNW USB controller to MSIC ULPI registers, to increase tolerance for bad case. Change-Id: I5ee9769ba990f591744d08e4fc2147037f57fab6 Signed-off-by: Wu, Hao Reviewed-on: http://android.intel.com:8080/17034 Reviewed-by: buildbot Reviewed-by: Yin, Fengwei Tested-by: Yin, Fengwei commit 02bbb3bf258225f59fcd4f28827ae77d817db2d6 Author: jzhuan5 Date: Thu Sep 1 16:38:58 2011 -0400 usb: client: Fix the issue of test mode fail in TEST_PACKET BZ: 8285 8779 PET sends set_feature request to enable USB client to a state of TEST MODE. After the completion of status stage, USB client should enable TEST MODE by programming PORTSC1 register. Also stop TEST MODE after USB device power cycle according to USB Spec. Change-Id: Ie973dabd227ddde6fdbea30e55866ee07b9bb884 Signed-off-by: jzhuan5 Signed-off-by: Wu, Hao Reviewed-on: http://android.intel.com:8080/17146 Reviewed-by: Yin, Fengwei Tested-by: Yin, Fengwei commit 87f8a45120ee13c30100d687499be97c6d31bf04 Author: Yu Wang Date: Fri Aug 26 17:14:19 2011 +0800 usb: langwell_udc: fix flush ep met fabric error. BZ: 7888 When UDC flush ep, sometimes system met fabric error. The root cause is before flush ep, PHY would exit low power mode. And there is a latency in HW did this operation, if driver access UDC during this latency, system will meet fabric error. This patch add 3 millisecond delay to ensure the PHY HW exit low power mode, then operate UDC. Change-Id: I39992616c58e3c89789830413a323439ba785764 Signed-off-by: Yu Wang commit d4252773bed09a139437efc975c78b1d684c527e Author: jzhuan5 Date: Fri Aug 26 12:00:58 2011 -0400 usb: otg: Disable MSIC interrupt when system is shutting down BZ: 7832 This patch implements shutdown callback of penwell_otg driver. The shutdown callback disables MSIC interrupts to prevent from disturbing the process of system shutdown. Signed-off-by: jzhuan5 Change-Id: I3de7f54d09b5b9b06304775403115e5932b28b72 commit 14a840ae3eb1d4d7dc886e332abd8afb0840633d Author: Wu, Hao Date: Tue Aug 16 01:44:35 2011 +0800 usb/penwell_otg: fix PD_DCP detection compliance Test issue. BZ: 4360 This patch add 1ms sleep between Primary and Secondary Detection, and assert VDPSRC when DCP detected and attached. This resolved BC1.2 compliance test case PD_DCP issue. Change-Id: I9cf3b71e33a795748702a3cf582b8fa51abc6a11 Signed-off-by: Wu, Hao commit 1c3d68c874898e90442c2dfb541c8dd2397a3749 Author: Wu, Hao Date: Wed Aug 3 04:00:10 2011 +0800 usb/langwell_udc: fix some issue to avoid blocking into D0i1. BZ: 6378 This patch is from Borqs, resolve issue if ep flush timeout, then USB D0i1 state will be blocked. Change-Id: I9697cc2c310db20b337e8dd9032f1eb951a0f2e5 Signed-off-by: Wu, Hao commit 63e1d6fa995a95991b668d34126017629909099c Author: Wu, Hao Date: Fri Jul 29 19:02:15 2011 +0800 usb/penwell_otg: manual charger detection flow BZ: 2921 This is a SW workaround for a hardware issue which cause charging failure, using manual charger detection flow instead of original one to avoid this hardware issue. Please note that the new flow using ULPI, only works on PNW C0 or later version. Change-Id: I1941c2dd7364b13b7354995f0eb5a3a6ec2da402 Signed-off-by: Wu, Hao commit 1ad65047b445da5455da82b8b05b168516979b56 Author: Wu, Hao Date: Thu Jul 28 02:45:12 2011 +0800 usb/penwell_otg: enable OTG_VBUS_OFF test mode for compliance test BZ: 5895 This patch enabled OTG_VBUS_OFF test mode in driver for USB OTG 2.0 complian case CT_A_CAP (OTG Compliance Plan 8.1.2 A-UUT Bypass Capacitance). Change-Id: I07891cb9d1138e620361b30c7d894b0f5f4dfec4 Signed-off-by: Wu, Hao commit 105bf83d769f9b5a4fc13287595080a236df4ece Author: Wu, Hao Date: Fri Jul 15 03:50:30 2011 +0800 usb/penwell: fix incorrect charging current when USB is in unconfigured sta BZ: 5146 This patch is for USB OTG2.0 Compliance issue, after a USB reset, USB will be back to unconfigured state, then only can draw less than IB_UNCFG max (2.5mA) current from connected SDP. This patch add related action triggered by USB reset event. Change-Id: I4d325de485e468b0bd808c0ccebc2aadea95768c Signed-off-by: Wu, Hao commit 03da966f597139a67f7644a7b17631d06cda5c5e Author: Yu Wang Date: Thu Jul 14 16:57:14 2011 +0800 usb: langwell_udc: fix pull up USB met fabric error issue. BZ: 5087 After enabled the "USB tethering" from Android settings application, pulled up USB cable, board would meet fabric error. The root cause is after pulled up USB cable, UDC would go to D0I1 mode. And Android framework would find USB cabled was disconnected, then wrote zero to funtion enable interface of sysfs. But this sysfs interface hadn't resume UDC to D0 mode before operated UDC HW, then met fabric error. This patch fixes this issue by resume UDC from DOI1 before operating HW in langwell_pullup function. Change-Id: I9b7c698afec79f0ef355c3053ef9538e8b9dd4e6 Signed-off-by: Yu Wang commit ed3cd9c69c33d5a70f4918e8822413dd95001007 Author: Wu, Hao Date: Wed Jul 13 02:29:45 2011 +0800 usb/penwell: add SRP test mode support BZ: 4974 This patch is for SRP Test mode support, please refer to USB OTG 2.0 Spec 6.4.3, OTG Compliance Test 6.3.4. Change-Id: Iaba1d89b79c28862a0239c0cf5cc57a00fef8e8c Signed-off-by: Wu, Hao commit b49721682faf6a42aecbddfa71ebb10d537cf661 Author: Jiebing Li Date: Fri Jul 1 04:10:08 2011 +0800 usb: langwell_udc: fix pull-up enable issue in langwell_pullup() BZ: 4154 Sometimes USB host sends bus_suspend request to device due to some reason. When device receives the request, PHY will be put into low power suspend by device driver. If at this time langwell_pullup() is called by gadget driver to set USBCMD Run/Stop bit, the pull-up on D+ will not be enabled as expected. This patch fixes the issue by exiting PHY low power in langwell_pullup(). Change-Id: I1b9836f57e0a25e16eeaa31b3c4319e811a542ad Signed-off-by: Jiebing Li commit 2e1aa820ecc706cde4798ead132629b276bcc84f Author: Hao Wu Date: Mon Jun 20 10:43:42 2011 +0800 usb/penwell_otg: enable timer ta_wait_bcon for wait connection state. BZ: 4357 After USB A-cable is inserted into OTG port, USB driver will enable Host stack and VBUS power supply, then wait device to connect. Per spec, enable the wait timeout as 15s (< 30s max). This is for OTG2.0 Compliance Test: 6.2.2 A_UUT Power up Test. Change-Id: I4a9ae8c23c26aabb461fa7c5c77b9057cbe6290d Signed-off-by: Wu, Hao commit 1163dcda8ec382b08299bf09d01f1f76ba977da7 Author: Hao Wu Date: Mon May 30 20:33:41 2011 +0800 usb/langwell_udc: hold wakelock when USB is connected and not suspended BZ: 2824 This patch enable USB udc driver to hold wake lock when USB is connected and usb bus is active state. This will prevent system from trying to enter S3 when usb is still alive. Change-Id: Ibc2ae6a985ea6d9168265c3ce4227f4a4309eba4 Signed-off-by: Hao Wu commit 500c0f18a30a222620027446294d913fa3e9a60e Author: Shanyu Zhao Date: Tue May 17 15:31:55 2011 -0700 USB: Fix commanded stall feature in the device controller BZ: 2314 usbtest TEST CASE 13 - endpoint halt feature fails. Commanded stall occurs when host explicitly calls the endpoint's halt feature. The set_halt function at the gadget side will not halt theendpoint if the queue is not empty. Therefore when the host calls the halt feature for some gadget function such as the gadget zero, which always put a usb_request in the queue, the halt always fails. To fix the failure, for commanded stall don't check for the queue. Also when reset the endpoint the halt flag shall be cleared manually. Change-Id: I1f5789a4c8115d46c800ee5b808141cab9c2da20 Signed-off-by: Shanyu Zhao Signed-off-by: Jiebing Li commit acb6ef93331fb854408ba0748e26ae6e18c0b653 Author: Shanyu Zhao Date: Tue May 17 14:39:52 2011 -0700 USB: disable HW zero length termination for device control endpoints BZ: 2767 Since the control endpoints know the expected number of bytes in a transaction, zero length termination (zlt) is not needed. If enabled the device controller would wait indefinitely for the USB host to send an zero length packet which never happens. This fixes the failure of the usbtest TEST CASE 14. Change-Id: Ib31f2c6dd87b60c669c265bb87afbead056d536f Signed-off-by: Shanyu Zhao commit 35eaa2fdf470e9b6bf922ba67ca5ca48fdb8bfd1 Author: Hao Wu Date: Fri May 6 13:26:25 2011 +0800 usb: penwell_otg: use separate suspend/resume functions for D3 BZ: 1424 This patch updated penwell_otg driver to use separate resume/suspend function for D3 instead of reuse runtime pm functions, they contain more state machine handling for USB D3 state, including PHY Low power mode setting, timer delete and state transition. It resolved unstable issue caused by incorrect handle on USB states and events when D3 suspend/resume. Change-Id: I94d395a2851b5cfda0c8e2dd49ebd83c92e6aa83 Signed-off-by: Hao Wu commit e29a44d2438cd13521af9b36719f00ebe8527593 Author: Sundar Iyer Date: Mon Apr 25 18:53:52 2011 +0530 usb/penwell_otg: use dev_pm_ops interfaces for D3 suspend/resume. BZ:#1378/#1301 This patch updates the penwell_otg to use .suspend/.resume in the dev_pm_ops interface instead of the legacy pci driver suspend/resume functions. This patch also removes duplicate pci related opeartions in suspend/resume functions. Change-Id: I66f2bf841979bc40787d3b3b884583c7b30511d8 Signed-off-by: Sundar Iyer Signed-off-by: Hao Wu commit 6e2bf9dc73b990dc693ed6413d2f7ddc33df8615 Author: Jiebing Li Date: Tue Apr 19 15:37:46 2011 +0800 usb: langwell_udc: fix cable unplug issues BZ: 1001 This patch fixed stop USB peripheral stack issue after received unplug event Two issues are fixed in this patch: 1. add dtd_pool pointer check in order to avoid dtd pool free error 2. remove PHY low power set in order to avoid EP flush timeout Change-Id: Ib259268cdffcb0ad30a19dd3258992efe63720cb Signed-off-by: Jiebing Li commit 9e6b9018a137bb68d8c55bcd9a9096a1134693d3 Author: Hao Wu Date: Tue Apr 19 14:59:34 2011 +0800 usb: penwell_otg: fix missing unplug USB cable event. BZ: 1001 This patch is for BZ1001, mainly solve missing unplug USB wall charger (DCP) event when USB is in D0i3 and charging from DCP. Main change in penwell_otg: - Use PNW USB OTGSC status register to check wakeup event. - Ensure mode is set and PHY is in low power mode before enter D0i3 - Remove duplicate PHY low power mode setting. Change-Id: I4c842a7b8a10332360b7b7695139666870094284 Signed-off-by: Hao Wu commit c2846926367114bead0e6cd0f1b4dff6fc6a121f Author: Hao Wu Date: Fri Apr 15 11:36:30 2011 +0800 usb: penwell_otg: fixing SRP failure. Fixing patch for Sighting 3742776: SRP failed Change-Id: I64b9446b7ba8b5e87b1e95e87ea20b2682c071f4 Signed-off-by: Hao Wu commit 3f35456f90c7ff997e078cc9083cfefda4bc4f02 Author: Jiebing Li Date: Thu Mar 10 16:56:26 2011 +0800 runtime PM mode change in gadget unregister operation put usb client to D0 mode in gagdet unregister operation and put it back to D0i3 after the operation done Change-Id: Ib8fe33f9f46947ed4a9e26458db75f2ae16318d3 Signed-off-by: Jiebing Li commit d17c4c027c5c8e0e2e42d9c637cf4452c204b443 Author: Jiebing Li Date: Fri Aug 20 13:49:09 2010 -0700 linux-2.6.36-usb-penwell-otg-add-aca-device-support.patch commit 80900b4c8976ec78b12393d64244e2f186998736 Author: Hao Wu Date: Fri Aug 20 13:49:09 2010 -0700 linux-2.6.36-usb-penwell_otg-HACK-resume-controller-before-reg-access.patch commit f5ec09219efa41149a38930e84cd77dd402ed9dc Author: Hao Wu Date: Fri Aug 20 13:49:09 2010 -0700 linux-2.6.36-usb-penwell_otg-add-phy-config.patch commit ede32384e6a8021d6fa9627ead3ac378bafe60e7 Author: Hao Wu Date: Fri Aug 20 13:49:09 2010 -0700 linux-2.6.36-usb-penwell_otg-update-otg-state-machine-for-runtime_pm.patch commit 7f1584ff3eac1fa4c01762c071c0b5bdeaba0da9 Author: Hao Wu Date: Fri Aug 20 13:49:09 2010 -0700 linux-2.6.36-usb-langwell_udc-add-runtime-pm-support-for-otg.patch Change-Id: I06a291cee179b1efe450eadf96e278434246742a --- diff --git a/drivers/usb/gadget/langwell_udc.c b/drivers/usb/gadget/langwell_udc.c index b2fa281..d942564 100644 --- a/drivers/usb/gadget/langwell_udc.c +++ b/drivers/usb/gadget/langwell_udc.c @@ -45,8 +45,10 @@ #include #include #include +#include #include #include +#include #include #include @@ -73,6 +75,7 @@ langwell_ep0_desc = { .wMaxPacketSize = EP0_MAX_PKT_SIZE, }; +static void ep_set_halt(struct langwell_ep *ep, int value); /*-------------------------------------------------------------------------*/ /* debugging */ @@ -192,6 +195,21 @@ static char *type_string(const struct usb_endpoint_descriptor *desc) return "control"; } +#ifdef OTG_TRANSCEIVER +static void langwell_udc_notify_otg(unsigned long event) +{ + struct langwell_udc *dev = the_controller; + + if (dev && dev->iotg && event) + atomic_notifier_call_chain(&dev->iotg->iotg_notifier, + event, dev->iotg); +} +#else +static inline void langwell_udc_notify_otg(unsigned long event); +{ + return ; +} +#endif /* configure endpoint control registers */ static void ep_reset(struct langwell_ep *ep, unsigned char ep_num, @@ -218,6 +236,9 @@ static void ep_reset(struct langwell_ep *ep, unsigned char ep_num, writel(endptctrl, &dev->op_regs->endptctrl[ep_num]); + /* has to manually clear halt for non-control endpoints */ + ep_set_halt(ep, 0); + dev_vdbg(&dev->pdev->dev, "<--- %s()\n", __func__); } @@ -242,9 +263,12 @@ static void ep0_reset(struct langwell_udc *dev) ep->dqh->dqh_ios = 1; ep->dqh->dqh_mpl = EP0_MAX_PKT_SIZE; - /* enable ep0-in HW zero length termination select */ - if (is_in(ep)) - ep->dqh->dqh_zlt = 0; + /* + * Disable HW zero length termination + * select for control endpoints. + */ + ep->dqh->dqh_zlt = 1; + ep->dqh->dqh_mult = 0; ep->dqh->dtd_next = DTD_TERM; @@ -411,12 +435,14 @@ static void done(struct langwell_ep *ep, struct langwell_request *req, status = req->req.status; /* free dTD for the request */ - next_dtd = req->head; - for (i = 0; i < req->dtd_count; i++) { - curr_dtd = next_dtd; - if (i != req->dtd_count - 1) - next_dtd = curr_dtd->next_dtd_virt; - dma_pool_free(dev->dtd_pool, curr_dtd, curr_dtd->dtd_dma); + if (dev->dtd_pool) { + next_dtd = req->head; + for (i = 0; i < req->dtd_count; i++) { + curr_dtd = next_dtd; + if (i != req->dtd_count - 1) + next_dtd = curr_dtd->next_dtd_virt; + dma_pool_free(dev->dtd_pool, curr_dtd, curr_dtd->dtd_dma); + } } if (req->mapped) { @@ -490,6 +516,8 @@ static int langwell_ep_disable(struct usb_ep *_ep) if (!_ep || !ep->desc) return -EINVAL; + pm_runtime_get_sync(&dev->pdev->dev); + spin_lock_irqsave(&dev->lock, flags); /* disable endpoint control register */ @@ -509,6 +537,8 @@ static int langwell_ep_disable(struct usb_ep *_ep) spin_unlock_irqrestore(&dev->lock, flags); + pm_runtime_put_sync(&dev->pdev->dev); + dev_dbg(&dev->pdev->dev, "disabled %s\n", _ep->name); dev_vdbg(&dev->pdev->dev, "<--- %s()\n", __func__); @@ -660,7 +690,7 @@ static struct langwell_dtd *build_dtd(struct langwell_request *req, unsigned *length, dma_addr_t *dma, int *is_last) { u32 buf_ptr; - struct langwell_dtd *dtd; + struct langwell_dtd *dtd = NULL; struct langwell_udc *dev; int i; @@ -672,7 +702,9 @@ static struct langwell_dtd *build_dtd(struct langwell_request *req, (unsigned)DTD_MAX_TRANSFER_LENGTH); /* create dTD dma_pool resource */ - dtd = dma_pool_alloc(dev->dtd_pool, GFP_ATOMIC, dma); + if (dev->dtd_pool) + dtd = dma_pool_alloc(dev->dtd_pool, GFP_ATOMIC, dma); + if (dtd == NULL) return dtd; dtd->dtd_dma = *dma; @@ -767,7 +799,7 @@ static int langwell_ep_queue(struct usb_ep *_ep, struct usb_request *_req, struct langwell_ep *ep; struct langwell_udc *dev; unsigned long flags; - int is_iso = 0, zlflag = 0; + int is_iso = 0, zlflag = 0, in = 0; /* always require a cpu-view buffer */ req = container_of(_req, struct langwell_request, req); @@ -785,11 +817,29 @@ static int langwell_ep_queue(struct usb_ep *_ep, struct usb_request *_req, req->ep = ep; dev_vdbg(&dev->pdev->dev, "---> %s()\n", __func__); + /* Put all accesses to ep->desc here. + * + * ep->desc can be disabled by interrupt handler + * ( i.e. ep->desc == NULL ) , + * but it's safe to access ep->desc if we lock it first. + */ + spin_lock_irqsave(&dev->lock, flags); + if (!ep->desc) { + spin_unlock_irqrestore(&dev->lock, flags); + dev_err(&dev->pdev->dev, "ep disabled while enqueuing a request\n"); + return -EINVAL; + } + if (usb_endpoint_xfer_isoc(ep->desc)) { - if (req->req.length > ep->ep.maxpacket) + if (req->req.length > ep->ep.maxpacket) { + spin_unlock_irqrestore(&dev->lock, flags); return -EMSGSIZE; + } is_iso = 1; } + in = is_in(ep); + + spin_unlock_irqrestore(&dev->lock, flags); if (unlikely(!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN)) return -ESHUTDOWN; @@ -805,7 +855,7 @@ static int langwell_ep_queue(struct usb_ep *_ep, struct usb_request *_req, _req->dma = dma_map_single(&dev->pdev->dev, _req->buf, _req->length, - is_in(ep) ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + in ? DMA_TO_DEVICE : DMA_FROM_DEVICE); if (zlflag && (_req->length == 1)) { dev_vdbg(&dev->pdev->dev, "req->length: 1->0\n"); zlflag = 0; @@ -817,7 +867,7 @@ static int langwell_ep_queue(struct usb_ep *_ep, struct usb_request *_req, } else { dma_sync_single_for_device(&dev->pdev->dev, _req->dma, _req->length, - is_in(ep) ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + in ? DMA_TO_DEVICE : DMA_FROM_DEVICE); req->mapped = 0; dev_vdbg(&dev->pdev->dev, "req->mapped = 0\n"); } @@ -833,6 +883,16 @@ static int langwell_ep_queue(struct usb_ep *_ep, struct usb_request *_req, spin_lock_irqsave(&dev->lock, flags); + /* Needs to check ep->desc again, ep might be disabled + * during lock is not held + */ + if (!ep->desc) { + spin_unlock_irqrestore(&dev->lock, flags); + /* TODO:unmap req buffer if it was mapped */ + dev_err(&dev->pdev->dev, "ep disabled while enqueuing a request\n"); + return -EINVAL; + } + /* build and put dTDs to endpoint queue */ if (!req_to_dtd(req)) { queue_dtd(ep, req); @@ -877,6 +937,8 @@ static int langwell_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) if (!dev->driver) return -ESHUTDOWN; + pm_runtime_get_sync(&dev->pdev->dev); + spin_lock_irqsave(&dev->lock, flags); stopped = ep->stopped; @@ -944,6 +1006,8 @@ done: ep->stopped = stopped; spin_unlock_irqrestore(&dev->lock, flags); + pm_runtime_put_sync(&dev->pdev->dev); + dev_vdbg(&dev->pdev->dev, "<--- %s()\n", __func__); return retval; } @@ -987,7 +1051,7 @@ static void ep_set_halt(struct langwell_ep *ep, int value) /* set the endpoint halt feature */ -static int langwell_ep_set_halt(struct usb_ep *_ep, int value) +static int _langwell_ep_set_halt(struct usb_ep *_ep, int value, int chq) { struct langwell_ep *ep; struct langwell_udc *dev; @@ -1008,13 +1072,15 @@ static int langwell_ep_set_halt(struct usb_ep *_ep, int value) if (usb_endpoint_xfer_isoc(ep->desc)) return -EOPNOTSUPP; + pm_runtime_get_sync(&dev->pdev->dev); + spin_lock_irqsave(&dev->lock, flags); /* * attempt to halt IN ep will fail if any transfer requests * are still queue */ - if (!list_empty(&ep->queue) && is_in(ep) && value) { + if (chq && !list_empty(&ep->queue) && is_in(ep) && value) { /* IN endpoint FIFO holds bytes */ dev_dbg(&dev->pdev->dev, "%s FIFO holds bytes\n", _ep->name); retval = -EAGAIN; @@ -1030,12 +1096,19 @@ static int langwell_ep_set_halt(struct usb_ep *_ep, int value) } done: spin_unlock_irqrestore(&dev->lock, flags); + + pm_runtime_put_sync(&dev->pdev->dev); + dev_dbg(&dev->pdev->dev, "%s %s halt\n", _ep->name, value ? "set" : "clear"); dev_vdbg(&dev->pdev->dev, "<--- %s()\n", __func__); return retval; } +static int langwell_ep_set_halt(struct usb_ep *_ep, int value) +{ + return _langwell_ep_set_halt(_ep, value, 1); +} /* set the halt feature and ignores clear requests */ static int langwell_ep_set_wedge(struct usb_ep *_ep) @@ -1055,6 +1128,34 @@ static int langwell_ep_set_wedge(struct usb_ep *_ep) return usb_ep_set_halt(_ep); } +/* enter or exit PHY low power state */ +static void langwell_phy_low_power(struct langwell_udc *dev, bool flag) +{ + u32 devlc; + u8 devlc_byte2; + dev_dbg(&dev->pdev->dev, "---> %s()\n", __func__); + + pm_runtime_get_sync(&dev->pdev->dev); + devlc = readl(&dev->op_regs->devlc); + dev_vdbg(&dev->pdev->dev, "devlc = 0x%08x\n", devlc); + + if (flag) + devlc |= LPM_PHCD; + else + devlc &= ~LPM_PHCD; + + /* FIXME: workaround for Langwell A1/A2/A3 sighting */ + devlc_byte2 = (devlc >> 16) & 0xff; + writeb(devlc_byte2, (u8 *)&dev->op_regs->devlc + 2); + + devlc = readl(&dev->op_regs->devlc); + dev_vdbg(&dev->pdev->dev, + "%s PHY low power suspend, devlc = 0x%08x\n", + flag ? "enter" : "exit", devlc); + + pm_runtime_put_sync(&dev->pdev->dev); +} + /* flush contents of a fifo */ static void langwell_ep_fifo_flush(struct usb_ep *_ep) @@ -1078,6 +1179,19 @@ static void langwell_ep_fifo_flush(struct usb_ep *_ep) dev_vdbg(&dev->pdev->dev, "%s-%s fifo flush\n", _ep->name, DIR_STRING(ep)); + pm_runtime_get_sync(&dev->pdev->dev); + + langwell_phy_low_power(dev, 0); + /* delay 3 millisecond to wait for phy exiting low power mode, + otherwise it easy to cause fabric error. the function can't use + msleep in here, because composite device will call this function with + holding spin lock. although it call pm_runtime_get_sync before which + would cause schedule too, but composite device would call + pm_runtime_get_sync prior to this function, so hereby it would + not cause schedule, just count plug one. + */ + mdelay(3); + /* flush endpoint buffer */ if (ep->ep_num == 0) flush_bit = (1 << 16) | 1; @@ -1099,6 +1213,8 @@ static void langwell_ep_fifo_flush(struct usb_ep *_ep) } } while (readl(&dev->op_regs->endptstat) & flush_bit); done: + pm_runtime_put_sync(&dev->pdev->dev); + dev_vdbg(&dev->pdev->dev, "<--- %s()\n", __func__); } @@ -1158,32 +1274,6 @@ static int langwell_get_frame(struct usb_gadget *_gadget) } -/* enter or exit PHY low power state */ -static void langwell_phy_low_power(struct langwell_udc *dev, bool flag) -{ - u32 devlc; - u8 devlc_byte2; - dev_dbg(&dev->pdev->dev, "---> %s()\n", __func__); - - devlc = readl(&dev->op_regs->devlc); - dev_vdbg(&dev->pdev->dev, "devlc = 0x%08x\n", devlc); - - if (flag) - devlc |= LPM_PHCD; - else - devlc &= ~LPM_PHCD; - - /* FIXME: workaround for Langwell A1/A2/A3 sighting */ - devlc_byte2 = (devlc >> 16) & 0xff; - writeb(devlc_byte2, (u8 *)&dev->op_regs->devlc + 2); - - devlc = readl(&dev->op_regs->devlc); - dev_vdbg(&dev->pdev->dev, - "%s PHY low power suspend, devlc = 0x%08x\n", - flag ? "enter" : "exit", devlc); -} - - /* tries to wake up the host connected to this gadget */ static int langwell_wakeup(struct usb_gadget *_gadget) { @@ -1302,6 +1392,10 @@ static int langwell_pullup(struct usb_gadget *_gadget, int is_on) dev_vdbg(&dev->pdev->dev, "---> %s()\n", __func__); + pm_runtime_get_sync(&dev->pdev->dev); + /* PHY should exit low power before set USBCMD Run/Stop */ + langwell_phy_low_power(dev, 0); + spin_lock_irqsave(&dev->lock, flags); dev->softconnected = (is_on != 0); @@ -1316,6 +1410,8 @@ static int langwell_pullup(struct usb_gadget *_gadget, int is_on) } spin_unlock_irqrestore(&dev->lock, flags); + pm_runtime_put_sync(&dev->pdev->dev); + dev_vdbg(&dev->pdev->dev, "<--- %s()\n", __func__); return 0; } @@ -1365,6 +1461,11 @@ static int langwell_udc_reset(struct langwell_udc *dev) dev_dbg(&dev->pdev->dev, "---> %s()\n", __func__); + /* clear devlc's auto low power bit */ + devlc = readl(&dev->op_regs->devlc); + devlc &= ~LPM_ASUS; + writel(devlc, &dev->op_regs->devlc); + /* set controller to stop state */ usbcmd = readl(&dev->op_regs->usbcmd); usbcmd &= ~CMD_RUNSTOP; @@ -1507,6 +1608,24 @@ static void langwell_udc_start(struct langwell_udc *dev) dev_dbg(&dev->pdev->dev, "<--- %s()\n", __func__); } +/* disable test mode */ +static void langwell_udc_stop_testmode(struct langwell_udc *dev) +{ + u32 portsc1; + + dev_dbg(&dev->pdev->dev, "---> %s()\n", __func__); + + portsc1 = readl(&dev->op_regs->portsc1); + + if (PORTS_PTC(portsc1)) { + dev_info(&dev->pdev->dev, "Exit USB Test mode\n"); + portsc1 &= ~PORTS_PTC_MASK; + writel(portsc1, &dev->op_regs->portsc1); + } + + dev_dbg(&dev->pdev->dev, "<--- %s()\n", __func__); + return; +} /* disable interrupt and set controller to stop state */ static void langwell_udc_stop(struct langwell_udc *dev) @@ -1862,6 +1981,8 @@ static int langwell_start(struct usb_gadget_driver *driver, driver->driver.bus = NULL; dev->driver = driver; dev->gadget.dev.driver = &driver->driver; + driver->ep_max = dev->ep_max; + driver->drv_state = REGISTERED; spin_unlock_irqrestore(&dev->lock, flags); @@ -1910,6 +2031,10 @@ static int langwell_stop(struct usb_gadget_driver *driver) if (unlikely(!driver || !driver->unbind || !driver->disconnect)) return -EINVAL; +#ifdef OTG_TRANSCEIVER + pm_runtime_get_sync(&dev->pdev->dev); +#endif + /* exit PHY low power suspend */ langwell_phy_low_power(dev, 0); @@ -1935,6 +2060,11 @@ static int langwell_stop(struct usb_gadget_driver *driver) driver->unbind(&dev->gadget); dev->gadget.dev.driver = NULL; dev->driver = NULL; + driver->drv_state = UNREGISTERED; + +#ifdef OTG_TRANSCEIVER + pm_runtime_put_sync(&dev->pdev->dev); +#endif dev_info(&dev->pdev->dev, "unregistered driver '%s'\n", driver->driver.name); @@ -2052,6 +2182,74 @@ static int prime_status_phase(struct langwell_udc *dev, int dir) return status; } +static void test_mode_complete(struct usb_ep *ep, struct usb_request *_req) +{ + struct langwell_udc *dev = the_controller; + struct langwell_request *req; + u32 portsc1; + + req = container_of(_req, struct langwell_request, req); + + switch (req->test_mode) { + case TEST_J: + case TEST_K: + case TEST_SE0_NAK: + case TEST_PACKET: + case TEST_FORCE_EN: + portsc1 = readl(&dev->op_regs->portsc1); + portsc1 |= req->test_mode << 16; + writel(portsc1, &dev->op_regs->portsc1); + dev_info(&dev->pdev->dev, + "Enter USB Test Mode 0x%x\n", req->test_mode); + break; + default: + dev_warn(&dev->pdev->dev, "unknown test mode\n"); + break; + } +} + +/* prime_status_phase_test_mode - PRIME a status phase for test mode request + */ +static int prime_status_phase_test_mode(struct langwell_udc *dev, + int dir, unsigned test_mode) +{ + struct langwell_request *req; + struct langwell_ep *ep; + int status = 0; + + dev_vdbg(&dev->pdev->dev, "---> %s()\n", __func__); + + if (dir == EP_DIR_IN) + dev->ep0_dir = USB_DIR_IN; + else + dev->ep0_dir = USB_DIR_OUT; + + ep = &dev->ep[0]; + dev->ep0_state = WAIT_FOR_OUT_STATUS; + + req = dev->status_req; + + req->ep = ep; + req->test_mode = test_mode; + req->req.length = 0; + req->req.status = -EINPROGRESS; + req->req.actual = 0; + req->req.complete = test_mode_complete; + req->dtd_count = 0; + + if (!req_to_dtd(req)) + status = queue_dtd(ep, req); + else + return -ENOMEM; + + if (status) + dev_err(&dev->pdev->dev, "can't queue ep0 status request\n"); + + list_add_tail(&req->queue, &ep->queue); + + dev_vdbg(&dev->pdev->dev, "<--- %s()\n", __func__); + return status; +} /* SET_ADDRESS request routine */ static void set_address(struct langwell_udc *dev, u16 value, @@ -2208,7 +2406,6 @@ static void handle_setup_packet(struct langwell_udc *dev, u16 wValue = le16_to_cpu(setup->wValue); u16 wIndex = le16_to_cpu(setup->wIndex); u16 wLength = le16_to_cpu(setup->wLength); - u32 portsc1; dev_vdbg(&dev->pdev->dev, "---> %s()\n", __func__); @@ -2278,9 +2475,9 @@ static void handle_setup_packet(struct langwell_udc *dev, break; spin_unlock(&dev->lock); - rc = langwell_ep_set_halt(&epn->ep, + rc = _langwell_ep_set_halt(&epn->ep, (setup->bRequest == USB_REQ_SET_FEATURE) - ? 1 : 0); + ? 1 : 0, 0); spin_lock(&dev->lock); } else if ((setup->bRequestType & (USB_RECIP_MASK @@ -2309,16 +2506,19 @@ static void handle_setup_packet(struct langwell_udc *dev, case TEST_SE0_NAK: case TEST_PACKET: case TEST_FORCE_EN: - if (prime_status_phase(dev, EP_DIR_IN)) + if (prime_status_phase_test_mode(dev, + EP_DIR_IN, wIndex >> 8)) ep0_stall(dev); - portsc1 = readl(&dev->op_regs->portsc1); - portsc1 |= (wIndex & 0xf00) << 8; - writel(portsc1, &dev->op_regs->portsc1); goto end; + case TEST_SRP_REQD: + langwell_udc_notify_otg( + MID_OTG_NOTIFY_TEST_SRP_REQD); + break; default: rc = -EOPNOTSUPP; } break; + case USB_DEVICE_B_HNP_ENABLE: dev->gadget.b_hnp_enable = 1; dev->dev_status |= (1 << wValue); break; @@ -2714,6 +2914,9 @@ static void handle_usb_reset(struct langwell_udc *dev) dev_vdbg(&dev->pdev->dev, "---> %s()\n", __func__); + /* notify reset event to OTG */ + langwell_udc_notify_otg(MID_OTG_NOTIFY_CRESET); + /* Write-Clear the device address */ deviceaddr = readl(&dev->op_regs->deviceaddr); writel(deviceaddr & ~USBADR_MASK, &dev->op_regs->deviceaddr); @@ -2783,10 +2986,6 @@ static void handle_usb_reset(struct langwell_udc *dev) dev->usb_state = USB_STATE_ATTACHED; } - if (dev->iotg) - atomic_notifier_call_chain(&dev->iotg->iotg_notifier, - MID_OTG_NOTIFY_CLIENTFS, dev->iotg); - dev_vdbg(&dev->pdev->dev, "<--- %s()\n", __func__); } @@ -2796,13 +2995,13 @@ static void handle_bus_suspend(struct langwell_udc *dev) { dev_dbg(&dev->pdev->dev, "---> %s()\n", __func__); + if (dev->usb_state == USB_STATE_SUSPENDED) + return; + dev->resume_state = dev->usb_state; dev->usb_state = USB_STATE_SUSPENDED; -#ifdef OTG_TRANSCEIVER - atomic_notifier_call_chain(&dev->iotg->iotg_notifier, - MID_OTG_NOTIFY_CSUSPEND, dev->iotg); -#endif + langwell_udc_notify_otg(MID_OTG_NOTIFY_CSUSPEND); /* report suspend to the driver */ if (dev->driver) { @@ -2832,10 +3031,7 @@ static void handle_bus_resume(struct langwell_udc *dev) /* exit PHY low power suspend */ langwell_phy_low_power(dev, 0); -#ifdef OTG_TRANSCEIVER - atomic_notifier_call_chain(&dev->iotg->iotg_notifier, - MID_OTG_NOTIFY_CRESUME, dev->iotg); -#endif + langwell_udc_notify_otg(MID_OTG_NOTIFY_CRESUME); /* report resume to the driver */ if (dev->driver) { @@ -2968,6 +3164,8 @@ static void gadget_release(struct device *_dev) complete(dev->done); + wake_lock_destroy(&dev->wake_lock); + dev_dbg(&dev->pdev->dev, "<--- %s()\n", __func__); kfree(dev); } @@ -3049,6 +3247,8 @@ static void langwell_udc_remove(struct pci_dev *pdev) dev->done = &done; #ifndef OTG_TRANSCEIVER + pm_runtime_get_noresume(&pdev->dev); + /* free dTD dma_pool and dQH */ if (dev->dtd_pool) dma_pool_destroy(dev->dtd_pool); @@ -3155,6 +3355,12 @@ static int langwell_udc_probe(struct pci_dev *pdev, dev->iotg = otg_to_mid_xceiv(dev->transceiver); base = dev->iotg->base; + + /* + * In OTG case, OTG Transceiver driver initialize itself early + * so if udc want to access hardware, just get_sync it back. + */ + pm_runtime_get_sync(&dev->pdev->dev); #else pci_set_drvdata(pdev, dev); @@ -3338,6 +3544,9 @@ static int langwell_udc_probe(struct pci_dev *pdev, } #endif + wake_lock_init(&dev->wake_lock, WAKE_LOCK_SUSPEND, + pci_name(dev->pdev)); + /* done */ dev_info(&dev->pdev->dev, "%s\n", driver_desc); dev_info(&dev->pdev->dev, "irq %d, pci mem %p\n", pdev->irq, base); @@ -3372,6 +3581,12 @@ static int langwell_udc_probe(struct pci_dev *pdev, if (retval) goto error_attr1; +#ifdef OTG_TRANSCEIVER + pm_runtime_put_sync(&pdev->dev); +#else + pm_runtime_put_noidle(&pdev->dev); +#endif + dev_vdbg(&dev->pdev->dev, "<--- %s()\n", __func__); return 0; @@ -3412,11 +3627,6 @@ static int langwell_udc_suspend(struct pci_dev *pdev, pm_message_t state) /* save PCI state */ pci_save_state(pdev); - spin_lock_irq(&dev->lock); - /* stop all usb activities */ - stop_activity(dev, dev->driver); - spin_unlock_irq(&dev->lock); - /* free dTD dma_pool and dQH */ if (dev->dtd_pool) dma_pool_destroy(dev->dtd_pool); @@ -3520,6 +3730,66 @@ static int langwell_udc_resume(struct pci_dev *pdev) return 0; } +#ifdef CONFIG_PM_RUNTIME +/* device controller runtime suspend */ +static int langwell_udc_runtime_suspend(struct device *device) +{ + struct langwell_udc *dev = the_controller; + struct pci_dev *pdev; + + dev_dbg(&dev->pdev->dev, "---> %s()\n", __func__); + + pdev = to_pci_dev(device); + + /* save PCI state */ + pci_save_state(pdev); + + /* disable PCI device */ + pci_disable_device(pdev); + + /* set device power state */ + pci_set_power_state(pdev, PCI_D3hot); + + dev->vbus_active = 0; + + dev_dbg(&dev->pdev->dev, "<--- %s()\n", __func__); + return 0; +} + + +/* device controller runtime resume */ +static int langwell_udc_runtime_resume(struct device *device) +{ + struct langwell_udc *dev = the_controller; + struct pci_dev *pdev; + + dev_dbg(&dev->pdev->dev, "---> %s()\n", __func__); + + pdev = to_pci_dev(device); + + /* set device D0 power state */ + pci_set_power_state(pdev, PCI_D0); + + /* restore PCI state */ + pci_restore_state(pdev); + + /* enable PCI device */ + if (pci_enable_device(pdev) < 0) + return -ENODEV; + + dev->vbus_active = 1; + + dev_dbg(&dev->pdev->dev, "<--- %s()\n", __func__); + return 0; +} + +#else + +#define langwell_udc_runtime_suspend NULL +#define langwell_udc_runtime_resume NULL + +#endif + /* pci driver shutdown */ static void langwell_udc_shutdown(struct pci_dev *pdev) @@ -3559,8 +3829,15 @@ static const struct pci_device_id pci_ids[] = { { MODULE_DEVICE_TABLE(pci, pci_ids); +static const struct dev_pm_ops langwell_udc_pm_ops = { + .runtime_suspend = langwell_udc_runtime_suspend, + .runtime_resume = langwell_udc_runtime_resume, +}; static struct pci_driver langwell_pci_driver = { + .driver = { + .pm = &langwell_udc_pm_ops, + }, .name = (char *) driver_name, .id_table = pci_ids, @@ -3578,18 +3855,77 @@ static struct pci_driver langwell_pci_driver = { static int intel_mid_start_peripheral(struct intel_mid_otg_xceiv *iotg) { struct langwell_udc *dev = the_controller; - struct pci_dev *pdev; + size_t size; unsigned long flags; - int retval; - if (iotg == NULL) - return -EINVAL; + dev_dbg(&dev->pdev->dev, "---> %s()\n", __func__); - pdev = to_pci_dev(iotg->otg.dev); + wake_lock(&dev->wake_lock); + pm_runtime_get(&dev->pdev->dev); - retval = langwell_udc_resume(pdev); - if (retval) - dev_dbg(&pdev->dev, "Failed to start peripheral driver\n"); + /* exit PHY low power suspend */ + langwell_phy_low_power(dev, 0); + + /* enable SRAM caching if detected */ + if (dev->has_sram && !dev->got_sram) + sram_init(dev); + + /* allocate device dQH memory */ + size = dev->ep_max * sizeof(struct langwell_dqh); + dev_vdbg(&dev->pdev->dev, "orig size = %d\n", size); + if (size < DQH_ALIGNMENT) + size = DQH_ALIGNMENT; + else if ((size % DQH_ALIGNMENT) != 0) { + size += DQH_ALIGNMENT + 1; + size &= ~(DQH_ALIGNMENT - 1); + } + dev->ep_dqh = dma_alloc_coherent(&dev->pdev->dev, size, + &dev->ep_dqh_dma, GFP_KERNEL); + if (!dev->ep_dqh) { + dev_err(&dev->pdev->dev, "allocate dQH memory failed\n"); + return -ENOMEM; + } + dev->ep_dqh_size = size; + dev_vdbg(&dev->pdev->dev, "ep_dqh_size = %d\n", dev->ep_dqh_size); + + /* create dTD dma_pool resource */ + dev->dtd_pool = dma_pool_create("langwell_dtd", + &dev->pdev->dev, + sizeof(struct langwell_dtd), + DTD_ALIGNMENT, + DMA_BOUNDARY); + + if (!dev->dtd_pool) + return -ENOMEM; + + /* enable IRQ handler */ + if (request_irq(dev->pdev->irq, langwell_irq, IRQF_SHARED, + driver_name, dev) != 0) { + dev_err(&dev->pdev->dev, "request interrupt %d failed\n", + dev->pdev->irq); + return -EBUSY; + } + dev->got_irq = 1; + + if ((!dev->driver) || (dev->driver->drv_state != BIND_UNBIND)) { + /* reset and start controller to run state */ + if (dev->stopped) { + /* reset device controller */ + langwell_udc_reset(dev); + + /* reset ep0 dQH and endptctrl */ + ep0_reset(dev); + + /* start device if gadget is loaded */ + if (dev->driver) + langwell_udc_start(dev); + + /* reset USB status */ + dev->usb_state = USB_STATE_ATTACHED; + dev->ep0_state = WAIT_FOR_SETUP; + dev->ep0_dir = USB_DIR_OUT; + } + } if (dev) { spin_lock_irqsave(&dev->lock, flags); @@ -3597,24 +3933,46 @@ static int intel_mid_start_peripheral(struct intel_mid_otg_xceiv *iotg) spin_unlock_irqrestore(&dev->lock, flags); } - return retval; + dev_dbg(&dev->pdev->dev, "<--- %s()\n", __func__); + return 0; } static int intel_mid_stop_peripheral(struct intel_mid_otg_xceiv *iotg) { struct langwell_udc *dev = the_controller; - struct pci_dev *pdev; unsigned long flags; - int retval; - if (iotg == NULL) - return -EINVAL; + dev_dbg(&dev->pdev->dev, "---> %s()\n", __func__); - pdev = to_pci_dev(iotg->otg.dev); + /* disable test mode */ + langwell_udc_stop_testmode(dev); - retval = langwell_udc_suspend(pdev, PMSG_FREEZE); - if (retval) - dev_dbg(&pdev->dev, "Failed to stop peripheral driver\n"); + /* disable interrupt and set controller to stop state */ + langwell_udc_stop(dev); + + /* diable IRQ handler */ + if (dev->got_irq) + free_irq(dev->pdev->irq, dev); + dev->got_irq = 0; + + spin_lock_irqsave(&dev->lock, flags); + /* stop all usb activities */ + stop_activity(dev, dev->driver); + spin_unlock_irqrestore(&dev->lock, flags); + + /* free dTD dma_pool and dQH */ + if (dev->dtd_pool) { + dma_pool_destroy(dev->dtd_pool); + dev->dtd_pool = NULL; + } + + if (dev->ep_dqh) + dma_free_coherent(&dev->pdev->dev, dev->ep_dqh_size, + dev->ep_dqh, dev->ep_dqh_dma); + + /* release SRAM caching */ + if (dev->has_sram && dev->got_sram) + sram_deinit(dev); if (dev) { spin_lock_irqsave(&dev->lock, flags); @@ -3622,7 +3980,11 @@ static int intel_mid_stop_peripheral(struct intel_mid_otg_xceiv *iotg) spin_unlock_irqrestore(&dev->lock, flags); } - return retval; + pm_runtime_put(&dev->pdev->dev); + wake_unlock(&dev->wake_lock); + + dev_dbg(&dev->pdev->dev, "<--- %s()\n", __func__); + return 0; } static int intel_mid_register_peripheral(struct pci_driver *peripheral_driver) @@ -3651,8 +4013,7 @@ static int intel_mid_register_peripheral(struct pci_driver *peripheral_driver) iotg->start_peripheral = intel_mid_start_peripheral; iotg->stop_peripheral = intel_mid_stop_peripheral; - atomic_notifier_call_chain(&iotg->iotg_notifier, - MID_OTG_NOTIFY_CLIENTADD, iotg); + langwell_udc_notify_otg(MID_OTG_NOTIFY_CLIENTADD); otg_put_transceiver(otg); diff --git a/drivers/usb/gadget/langwell_udc.h b/drivers/usb/gadget/langwell_udc.h index cf77d63..396f23f 100644 --- a/drivers/usb/gadget/langwell_udc.h +++ b/drivers/usb/gadget/langwell_udc.h @@ -19,6 +19,7 @@ #include #include +#include /*-------------------------------------------------------------------------*/ @@ -144,6 +145,7 @@ struct langwell_request { struct list_head queue; unsigned dtd_count; unsigned mapped:1; + unsigned test_mode; }; @@ -229,5 +231,7 @@ struct langwell_udc { /* device status data for get_status request */ u16 dev_status; + + struct wake_lock wake_lock; }; diff --git a/drivers/usb/otg/penwell_otg.c b/drivers/usb/otg/penwell_otg.c index 4a702ce..0112e1f 100644 --- a/drivers/usb/otg/penwell_otg.c +++ b/drivers/usb/otg/penwell_otg.c @@ -54,8 +54,8 @@ static const char driver_name[] = "penwell_otg"; static int penwell_otg_probe(struct pci_dev *pdev, const struct pci_device_id *id); static void penwell_otg_remove(struct pci_dev *pdev); -static int penwell_otg_suspend(struct pci_dev *pdev, pm_message_t message); -static int penwell_otg_resume(struct pci_dev *pdev); +static int penwell_otg_suspend(struct device *dev); +static int penwell_otg_resume(struct device *pdev); static int penwell_otg_set_host(struct otg_transceiver *otg, struct usb_bus *host); @@ -107,6 +107,8 @@ static const char *charger_string(enum usb_charger_type charger) return "Charging Downstream Port"; case CHRG_DCP: return "Dedicated Charging Port"; + case CHRG_ACA: + return "Accessory Charger Adaptor"; case CHRG_UNKNOWN: return "Unknown"; default: @@ -307,7 +309,7 @@ static int penwell_otg_set_power(struct otg_transceiver *otg, spin_lock_irqsave(&pnw->charger_lock, flags); if (pnw->charging_cap.chrg_type != CHRG_SDP) { - spin_unlock(&pnw->charger_lock); + spin_unlock_irqrestore(&pnw->charger_lock, flags); return 0; } @@ -465,14 +467,14 @@ penwell_otg_ulpi_read(struct intel_mid_otg_xceiv *iotg, u8 reg, u8 *val) val32 = ULPI_RUN | reg << 16; writel(val32, pnw->iotg.base + CI_ULPIVP); - /* Polling for write operation to complete*/ - count = 10; + /* Polling at least 1ms for read operation to complete*/ + count = 200; while (count) { val32 = readl(pnw->iotg.base + CI_ULPIVP); if (val32 & ULPI_RUN) { count--; - udelay(20); + udelay(5); } else { *val = (u8)((val32 & ULPI_DATRD) >> 8); dev_dbg(pnw->dev, @@ -501,12 +503,12 @@ penwell_otg_ulpi_write(struct intel_mid_otg_xceiv *iotg, u8 reg, u8 val) val32 = ULPI_RUN | ULPI_RW | reg << 16 | val; writel(val32, pnw->iotg.base + CI_ULPIVP); - /* Polling for write operation to complete*/ - count = 10; + /* Polling at least 1ms for write operation to complete*/ + count = 200; while (count && penwell_otg_ulpi_run()) { count--; - udelay(20); + udelay(5); } dev_dbg(pnw->dev, @@ -674,7 +676,7 @@ static void penwell_otg_loc_sof(int on) struct usb_hcd *hcd; int err; - dev_dbg(pnw->dev, "%s ---> %s\n", __func__, on ? "suspend" : "resume"); + dev_dbg(pnw->dev, "%s ---> %s\n", __func__, on ? "resume" : "suspend"); hcd = bus_to_hcd(pnw->iotg.otg.host); if (on) @@ -718,6 +720,31 @@ static void penwell_otg_phy_low_power(int on) dev_dbg(pnw->dev, "%s <---\n", __func__); } +/* + * VBUS330 is the power rail to otg transceiver, set it into low power mode + * or normal mode according to pm state. Call this function when spi access + * to MSIC registers is enabled. + */ +static int penwell_otg_vusb330_low_power(int on) +{ + struct penwell_otg *pnw = the_transceiver; + u8 data; + int retval; + + dev_dbg(pnw->dev, "%s ---> %s\n", __func__, on ? "on" : "off"); + + if (on) + data = 0x5; /* Low power mode */ + else + data = 0x7; /* Normal mode */ + + retval = penwell_otg_msic_write(MSIC_VUSB330CNT, data); + + dev_dbg(pnw->dev, "%s <---\n", __func__); + + return retval; +} + /* Enable/Disable OTG interrupt */ static void penwell_otg_intr(int on) { @@ -777,10 +804,8 @@ static int penwell_otg_msic_write(u16 addr, u8 data) int retval = 0; retval = intel_scu_ipc_iowrite8(addr, data); - if (retval) { + if (retval) dev_warn(pnw->dev, "Failed to write MSIC register %x\n", addr); - return retval; - } return retval; } @@ -957,6 +982,196 @@ static int penwell_otg_charger_type_detect(void) return charger; } +/* manual charger detection by ULPI access */ +static int penwell_otg_manual_charger_detection(void) +{ + struct penwell_otg *pnw = the_transceiver; + struct intel_mid_otg_xceiv *iotg; + int retval; + u8 data, data1, data2; + unsigned long timeout, interval; + + dev_dbg(pnw->dev, "%s --->\n", __func__); + + iotg = &pnw->iotg; + + dev_info(pnw->dev, "USB charger detection start...\n"); + + /* config PHY for DCD + * - OPMODE/TERMSEL/XCVRSEL=01/0/01 + * - enable DP/DM pulldowns (ensure RX's don't float) */ + + /* ulpi_write(0x0b, 0x06) */ + retval = penwell_otg_ulpi_write(iotg, ULPI_OTGCTRLSET, + DMPULLDOWN | DPPULLDOWN); + if (retval) + return retval; + + /* ulpi_write(0x05, 0x09) */ + retval = penwell_otg_ulpi_write(iotg, ULPI_FUNCTRLSET, + OPMODE0 | XCVRSELECT0); + if (retval) + return retval; + + /* ulpi_write(0x06, 0x16) */ + retval = penwell_otg_ulpi_write(iotg, ULPI_FUNCTRLCLR, + OPMODE1 | TERMSELECT | XCVRSELECT1); + if (retval) + return retval; + + /* ulpi_write(0x0c, 0x02) */ + retval = penwell_otg_ulpi_write(iotg, ULPI_OTGCTRLCLR, DPPULLDOWN); + if (retval) + return retval; + + /* ulpi_write(0x3e, 0x01) */ + retval = penwell_otg_ulpi_write(iotg, ULPI_PWRCTRLSET, SWCNTRL); + if (retval) + return retval; + + /* ulpi_write(0x86, 0x40) */ + retval = penwell_otg_ulpi_write(iotg, ULPI_VS3SET, CHGD_IDP_SRC); + if (retval) + return retval; + + dev_info(pnw->dev, "charger detection DCD start...\n"); + + /* Check DCD result, use same polling parameter */ + timeout = jiffies + msecs_to_jiffies(DATACON_TIMEOUT); + interval = DATACON_INTERVAL * 1000; /* us */ + + while (!time_after(jiffies, timeout)) { + retval = penwell_otg_ulpi_read(iotg, ULPI_VS4, &data); + if (retval) { + dev_warn(pnw->dev, "Failed to read ULPI register\n"); + return retval; + } + + if (data & !CHRG_SERX_DP) { + dev_info(pnw->dev, "Data contact detected!\n"); + break; + } + + /* Polling interval */ + usleep_range(interval, interval + 2000); + } + + /* ulpi_write(0x87, 0x40)*/ + retval = penwell_otg_ulpi_write(iotg, ULPI_VS3CLR, CHGD_IDP_SRC); + if (retval) + return retval; + + dev_info(pnw->dev, "DCD complete\n"); + dev_info(pnw->dev, "Primary Detection start...\n"); + + /* Primary Dection config */ + /* ulpi_write(0x0b, 0x06) */ + retval = penwell_otg_ulpi_write(iotg, ULPI_OTGCTRLSET, + DMPULLDOWN | DPPULLDOWN); + if (retval) + return retval; + + retval = penwell_otg_ulpi_write(iotg, ULPI_PWRCTRLSET, DPVSRCEN); + if (retval) + return retval; + + msleep(125); + + /* Check result SDP vs CDP/DCP */ + retval = penwell_otg_ulpi_read(iotg, ULPI_PWRCTRL, &data1); + if (retval) + return retval; + + data1 = data1 & VDATDET; + + retval = penwell_otg_ulpi_read(iotg, ULPI_VS4, &data2); + if (retval) + return retval; + + data2 = data2 & CHRG_SERX_DM; + + if (!data1 || data2) { + retval = penwell_otg_ulpi_write(iotg, ULPI_PWRCTRLCLR, + DPVSRCEN); + if (retval) + return retval; + + dev_info(pnw->dev, "USB Charger Detection done\n"); + return CHRG_SDP; + } + + /* start detection on CDP vs DCP */ + retval = penwell_otg_ulpi_write(iotg, ULPI_PWRCTRLCLR, DPVSRCEN); + if (retval) + return retval; + + /* sleep 1ms between Primary and Secondary detection */ + usleep_range(1000, 1200); + + retval = penwell_otg_ulpi_write(iotg, ULPI_VS1CLR, DATAPOLARITY); + if (retval) + return retval; + + retval = penwell_otg_ulpi_write(iotg, ULPI_PWRCTRLSET, DPVSRCEN); + if (retval) + return retval; + + msleep(85); + + /* read result on CDP vs DCP */ + retval = penwell_otg_ulpi_read(iotg, ULPI_PWRCTRL, &data); + if (retval) + return retval; + + data = data & VDATDET; + + retval = penwell_otg_ulpi_write(iotg, ULPI_PWRCTRLCLR, DPVSRCEN); + if (retval) + return retval; + + retval = penwell_otg_ulpi_write(iotg, ULPI_VS1SET, DATAPOLARITY); + if (retval) + return retval; + + dev_info(pnw->dev, "USB Charger Detection done\n"); + + if (data) { + retval = penwell_otg_ulpi_write(iotg, ULPI_PWRCTRLSET, + DPVSRCEN); + if (retval) + return retval; + + return CHRG_DCP; + } else + return CHRG_CDP; + + dev_dbg(pnw->dev, "%s <---\n", __func__); +}; + +void penwell_otg_phy_vbus_wakeup(bool on) +{ + struct penwell_otg *pnw = the_transceiver; + u8 flag = 0; + + dev_dbg(pnw->dev, "%s --->\n", __func__); + + penwell_otg_msic_spi_access(true); + + flag = VBUSVLD | SESSVLD | SESSEND; + + if (on) { + penwell_otg_msic_write(MSIC_USBINTEN_RISESET, flag); + penwell_otg_msic_write(MSIC_USBINTEN_FALLSET, flag); + } else { + penwell_otg_msic_write(MSIC_USBINTEN_RISECLR, flag); + penwell_otg_msic_write(MSIC_USBINTEN_FALLCLR, flag); + } + + penwell_otg_msic_spi_access(false); + + dev_dbg(pnw->dev, "%s --->\n", __func__); +} + void penwell_otg_nsf_msg(unsigned long indicator) { switch (indicator) { @@ -1053,6 +1268,21 @@ static void penwell_otg_add_timer(enum penwell_otg_timer_type timers) dev_dbg(pnw->dev, "Add timer TB_SRP_FAIL = %d\n", TB_SRP_FAIL); break; + /* support OTG test mode */ + case TTST_MAINT_TMR: + iotg->hsm.tst_maint_tmout = 0; + data = (unsigned long)&iotg->hsm.tst_maint_tmout; + time = TTST_MAINT; + dev_dbg(pnw->dev, + "Add timer TTST_MAINT = %d\n", TTST_MAINT); + break; + case TTST_NOADP_TMR: + iotg->hsm.tst_noadp_tmout = 0; + data = (unsigned long)&iotg->hsm.tst_noadp_tmout; + time = TTST_NOADP; + dev_dbg(pnw->dev, + "Add timer TTST_NOADP = %d\n", TTST_NOADP); + break; default: dev_dbg(pnw->dev, "unkown timer, can not enable such timer\n"); @@ -1078,6 +1308,15 @@ static inline void penwell_otg_del_timer(enum penwell_otg_timer_type timers) case TB_SRP_FAIL_TMR: iotg->hsm.b_srp_fail_tmr = 0; break; + case TA_WAIT_BCON_TMR: + iotg->hsm.a_wait_bcon_tmout = 0; + break; + case TTST_MAINT_TMR: + iotg->hsm.tst_maint_tmout = 0; + break; + case TTST_NOADP_TMR: + iotg->hsm.tst_noadp_tmout = 0; + break; default: break; } @@ -1163,7 +1402,7 @@ static void init_hsm(void) iotg->hsm.a_bus_req = 1; iotg->hsm.a_bus_drop = 0; /* init hsm means power_up case */ - iotg->hsm.power_up = 1; + iotg->hsm.power_up = 0; /* defautly don't request bus as B device */ iotg->hsm.b_bus_req = 0; /* no system error */ @@ -1220,10 +1459,9 @@ static irqreturn_t otg_dummy_irq(int irq, void *_dev) return IRQ_HANDLED; } -static irqreturn_t otg_irq(int irq, void *_dev) +static irqreturn_t otg_irq_handle(struct penwell_otg *pnw, + struct intel_mid_otg_xceiv *iotg) { - struct penwell_otg *pnw = _dev; - struct intel_mid_otg_xceiv *iotg = &pnw->iotg; int flag = 0; u32 int_sts, int_en, int_mask = 0; @@ -1287,11 +1525,104 @@ static irqreturn_t otg_irq(int irq, void *_dev) return IRQ_HANDLED; } +static irqreturn_t otg_irq(int irq, void *_dev) +{ + struct penwell_otg *pnw = _dev; + struct intel_mid_otg_xceiv *iotg = &pnw->iotg; + +#ifdef CONFIG_PM_RUNTIME + if (pnw->rt_resuming) { + pnw->rt_resuming++; + return IRQ_HANDLED; + } else if (pnw->dev->power.runtime_status == RPM_RESUMING) + return IRQ_HANDLED; + + /* If it's not active, resume device first before access regs */ + if (pnw->dev->power.runtime_status != RPM_ACTIVE) { + dev_dbg(pnw->dev, "Wake up? Interrupt detected in suspended\n"); + pnw->rt_resuming = 1; + + pm_runtime_get(pnw->dev); + + return IRQ_HANDLED; + } +#endif /* CONFIG_PM_RUNTIME */ + + return otg_irq_handle(pnw, iotg); +} + +static void penwell_otg_check_wakeup_event(struct penwell_otg *pnw) +{ + u32 int_sts, int_en, int_mask = 0; + int flag = 0; + int id, a_vbus_vld, b_sess_end; + int b_sess_vld, a_sess_vld; + + pnw = the_transceiver; + + /* OTGSC needs 1ms debounce time to get synced with MSIC + * after back from clock gated state to D0, sleep 2ms + * will be enough */ + usleep_range(1800, 2000); + + /* Check VBUS/SRP interrup */ + int_sts = readl(pnw->iotg.base + CI_OTGSC); + int_en = (int_sts & OTGSC_INTEN_MASK) >> 8; + int_mask = int_sts & int_en; + + /* won't use OTGSC INTSTS bit, because controller is + * reset during runtime suspend, just check current status */ + + /* get current id/a_vbus_vld/b_sess_end/b_sess_vld/a_sess_vld */ + id = !!(int_sts & OTGSC_ID) ? ID_B : ID_A; + b_sess_end = !!(int_sts & OTGSC_BSE); + b_sess_vld = !!(int_sts & OTGSC_BSV); + a_sess_vld = !!(int_sts & OTGSC_ASV); + a_vbus_vld = !!(int_sts & OTGSC_AVV); + + if (id != pnw->iotg.hsm.id) { + pnw->iotg.hsm.id = id; + dev_dbg(pnw->dev, "ID Wake up id = %d\n", id); + flag = 1; + } + + if (b_sess_end != pnw->iotg.hsm.b_sess_end) { + pnw->iotg.hsm.b_sess_end = b_sess_end; + dev_dbg(pnw->dev, "B_sess_end Wake up = %d\n", b_sess_end); + flag = 1; + } + + if (b_sess_vld != pnw->iotg.hsm.b_sess_vld) { + pnw->iotg.hsm.b_sess_vld = b_sess_vld; + dev_dbg(pnw->dev, "B_sess_vld Wake up = %d\n", b_sess_vld); + flag = 1; + } + + if (a_sess_vld != pnw->iotg.hsm.a_sess_vld) { + pnw->iotg.hsm.a_sess_vld = a_sess_vld; + dev_dbg(pnw->dev, "A_sess_vld Wake up = %d\n", a_sess_vld); + flag = 1; + } + + if (a_vbus_vld != pnw->iotg.hsm.a_vbus_vld) { + pnw->iotg.hsm.a_vbus_vld = a_vbus_vld; + dev_dbg(pnw->dev, "A_vbus_vld Wake up = %d\n", a_vbus_vld); + flag = 1; + } + + if (int_mask) + writel((int_sts & ~OTGSC_INTSTS_MASK) | int_mask, + pnw->iotg.base + CI_OTGSC); + if (flag) + penwell_update_transceiver(); +} + static int penwell_otg_iotg_notify(struct notifier_block *nb, unsigned long action, void *data) { struct penwell_otg *pnw = the_transceiver; struct intel_mid_otg_xceiv *iotg = data; + unsigned long flags; int flag = 0; if (iotg == NULL) @@ -1319,17 +1650,13 @@ static int penwell_otg_iotg_notify(struct notifier_block *nb, break; case MID_OTG_NOTIFY_HSUSPEND: dev_dbg(pnw->dev, "PNW OTG Notify Host Bus suspend Event\n"); - if (iotg->otg.default_a == 1) - iotg->hsm.a_bus_req = 0; - else - iotg->hsm.b_bus_req = 0; - flag = 1; break; case MID_OTG_NOTIFY_HRESUME: dev_dbg(pnw->dev, "PNW OTG Notify Host Bus resume Event\n"); - if (iotg->otg.default_a == 1) + if (iotg->otg.default_a == 1 && iotg->hsm.a_bus_req == 0) { iotg->hsm.a_bus_req = 1; - flag = 1; + flag = 1; + } break; case MID_OTG_NOTIFY_CSUSPEND: dev_dbg(pnw->dev, "PNW OTG Notify Client Bus suspend Event\n"); @@ -1356,6 +1683,11 @@ static int penwell_otg_iotg_notify(struct notifier_block *nb, flag = 0; } break; + case MID_OTG_NOTIFY_CRESET: + dev_dbg(pnw->dev, "PNW OTG Notify Client Bus reset Event\n"); + penwell_otg_set_power(&pnw->iotg.otg, CHRG_CURR_SDP_SUSP); + flag = 0; + break; case MID_OTG_NOTIFY_HOSTADD: dev_dbg(pnw->dev, "PNW OTG Nofity Host Driver Add\n"); flag = 1; @@ -1382,18 +1714,36 @@ static int penwell_otg_iotg_notify(struct notifier_block *nb, penwell_otg_update_chrg_cap(CHRG_CDP, CHRG_CURR_CDP_HS); flag = 0; break; + /* Test mode support */ + case MID_OTG_NOTIFY_TEST_SRP_REQD: + dev_dbg(pnw->dev, "PNW OTG Notfiy Client SRP REQD\n"); + iotg->hsm.otg_srp_reqd = 1; + flag = 1; + break; + case MID_OTG_NOTIFY_TEST: + dev_dbg(pnw->dev, "PNW OTG Notfiy Test device detected\n"); + iotg->hsm.test_device = 1; + flag = 0; + break; + case MID_OTG_NOTIFY_TEST_VBUS_OFF: + dev_dbg(pnw->dev, "PNW OTG Notfiy Test device Vbus off mode\n"); + iotg->hsm.test_device = 1; + iotg->hsm.otg_vbus_off = 1; + flag = 1; + break; default: dev_dbg(pnw->dev, "PNW OTG Nofity unknown notify message\n"); return NOTIFY_DONE; } - if (flag) + spin_lock_irqsave(&pnw->notify_lock, flags); + if (flag && pnw->queue_stop == 0) penwell_update_transceiver(); + spin_unlock_irqrestore(&pnw->notify_lock, flags); return NOTIFY_OK; } - static void penwell_otg_hnp_poll_work(struct work_struct *work) { struct penwell_otg *pnw = the_transceiver; @@ -1458,6 +1808,7 @@ static void penwell_otg_work(struct work_struct *work) enum usb_charger_type charger_type; int retval; struct pci_dev *pdev; + unsigned long flags; dev_dbg(pnw->dev, "old state = %s\n", state_string(iotg->otg.state)); @@ -1524,11 +1875,15 @@ static void penwell_otg_work(struct work_struct *work) /* Start USB Battery charger detection flow */ mutex_lock(&pnw->msic_mutex); - /* Enable data contact detection */ - penwell_otg_data_contact_detect(); - /* Enable charger detection functionality */ - penwell_otg_charger_detect(); - retval = penwell_otg_charger_type_detect(); + if (pdev->revision >= 0x8) { + retval = penwell_otg_manual_charger_detection(); + } else { + /* Enable data contact detection */ + penwell_otg_data_contact_detect(); + /* Enable charger detection functionality */ + penwell_otg_charger_detect(); + retval = penwell_otg_charger_type_detect(); + } mutex_unlock(&pnw->msic_mutex); if (retval < 0) { dev_warn(pnw->dev, "Charger detect failure\n"); @@ -1542,6 +1897,7 @@ static void penwell_otg_work(struct work_struct *work) /* DCP: set charger type, current, notify EM */ penwell_otg_update_chrg_cap(CHRG_DCP, CHRG_CURR_DCP); + set_client_mode(); penwell_otg_phy_low_power(1); break; @@ -1574,6 +1930,9 @@ static void penwell_otg_work(struct work_struct *work) if (iotg->otg.gadget) iotg->otg.gadget->host_request_flag = 0; + penwell_otg_phy_low_power(0); + set_client_mode(); + if (iotg->start_peripheral) { iotg->start_peripheral(iotg); } else { @@ -1592,8 +1951,8 @@ static void penwell_otg_work(struct work_struct *work) iotg->otg.state = OTG_STATE_B_PERIPHERAL; - } else if ((hsm->b_bus_req || hsm->power_up || - hsm->adp_change) && !hsm->b_srp_fail_tmr) { + } else if ((hsm->b_bus_req || hsm->power_up || hsm->adp_change + || hsm->otg_srp_reqd) && !hsm->b_srp_fail_tmr) { penwell_otg_mon_bus(); @@ -1617,10 +1976,27 @@ static void penwell_otg_work(struct work_struct *work) dev_info(pnw->dev, "BUS is active, try SRP later\n"); } + + /* clear after SRP attemp */ + if (hsm->otg_srp_reqd) { + dev_dbg(pnw->dev, "Test mode: SRP done\n"); + hsm->otg_srp_reqd = 0; + } } else if (!hsm->b_sess_vld && hsm->id == ID_B) { - /* Notify EM charger remove event */ - penwell_otg_update_chrg_cap(CHRG_UNKNOWN, + spin_lock_irqsave(&pnw->charger_lock, flags); + charger_type = pnw->charging_cap.chrg_type; + spin_unlock_irqrestore(&pnw->charger_lock, flags); + + if (charger_type == CHRG_DCP) { + /* Notify EM charger remove event */ + penwell_otg_update_chrg_cap(CHRG_UNKNOWN, CHRG_CURR_DISCONN); + + retval = penwell_otg_ulpi_write(iotg, + ULPI_PWRCTRLCLR, DPVSRCEN); + if (retval) + dev_warn(pnw->dev, "ulpi failed\n"); + } } break; @@ -1656,14 +2032,14 @@ static void penwell_otg_work(struct work_struct *work) CHRG_CURR_DISCONN); } + hsm->b_bus_req = 0; + if (iotg->stop_peripheral) iotg->stop_peripheral(iotg); else dev_dbg(pnw->dev, "client driver has been removed.\n"); - penwell_otg_phy_low_power(1); - iotg->otg.state = OTG_STATE_B_IDLE; } else if (hsm->b_bus_req && hsm->a_bus_suspend && iotg->otg.gadget @@ -1893,6 +2269,8 @@ static void penwell_otg_work(struct work_struct *work) penwell_otg_update_chrg_cap(CHRG_ACA, CHRG_CURR_ACA); + hsm->b_bus_req = 0; + set_client_mode(); msleep(5); penwell_otg_phy_low_power(1); @@ -1932,12 +2310,16 @@ static void penwell_otg_work(struct work_struct *work) if (hsm->adp_change) hsm->adp_change = 0; - if (hsm->a_srp_det) + if (hsm->a_srp_det) { hsm->a_srp_det = 0; + /* wait SRP done, then enable VBUS */ + usleep_range(10000, 11000); + } if (iotg->otg.set_vbus) iotg->otg.set_vbus(&iotg->otg, true); + pm_runtime_get(pnw->dev); penwell_otg_add_timer(TA_WAIT_VRISE_TMR); iotg->otg.state = OTG_STATE_A_WAIT_VRISE; @@ -1963,6 +2345,7 @@ static void penwell_otg_work(struct work_struct *work) if (iotg->otg.set_vbus) iotg->otg.set_vbus(&iotg->otg, false); + pm_runtime_get(pnw->dev); penwell_otg_add_timer(TA_WAIT_VFALL_TMR); iotg->otg.state = OTG_STATE_A_WAIT_VFALL; } else if (hsm->a_vbus_vld || hsm->a_wait_vrise_tmout @@ -1975,6 +2358,8 @@ static void penwell_otg_work(struct work_struct *work) /* Turn off VBUS */ if (iotg->otg.set_vbus) iotg->otg.set_vbus(&iotg->otg, false); + + pm_runtime_get(pnw->dev); penwell_otg_add_timer(TA_WAIT_VFALL_TMR); iotg->otg.state = OTG_STATE_A_WAIT_VFALL; break; @@ -2000,11 +2385,14 @@ static void penwell_otg_work(struct work_struct *work) break; } + pm_runtime_put(pnw->dev); + penwell_otg_add_timer(TA_WAIT_BCON_TMR); iotg->otg.state = OTG_STATE_A_WAIT_BCON; } break; case OTG_STATE_A_WAIT_BCON: - if (hsm->id == ID_B || hsm->id == ID_ACA_B || hsm->a_bus_drop) { + if (hsm->id == ID_B || hsm->id == ID_ACA_B || hsm->a_bus_drop || + hsm->a_wait_bcon_tmout) { /* Move to A_WAIT_VFALL state, user request */ /* Delete current timer and clear flags for B-Device */ @@ -2022,6 +2410,7 @@ static void penwell_otg_work(struct work_struct *work) if (iotg->otg.set_vbus) iotg->otg.set_vbus(&iotg->otg, false); + pm_runtime_get(pnw->dev); penwell_otg_add_timer(TA_WAIT_VFALL_TMR); iotg->otg.state = OTG_STATE_A_WAIT_VFALL; } else if (!hsm->a_vbus_vld) { @@ -2051,10 +2440,13 @@ static void penwell_otg_work(struct work_struct *work) /* Start HNP polling */ iotg->start_hnp_poll(iotg); - iotg->otg.state = OTG_STATE_A_HOST; - if (!hsm->a_bus_req) hsm->a_bus_req = 1; + + if (hsm->test_device) + penwell_otg_add_timer(TTST_MAINT_TMR); + + iotg->otg.state = OTG_STATE_A_HOST; } else if (hsm->id == ID_ACA_A) { penwell_otg_update_chrg_cap(CHRG_ACA, CHRG_CURR_ACA); @@ -2067,6 +2459,13 @@ static void penwell_otg_work(struct work_struct *work) case OTG_STATE_A_HOST: if (hsm->id == ID_B || hsm->id == ID_ACA_B || hsm->a_bus_drop) { /* Move to A_WAIT_VFALL state, timeout/user request */ + + /* Delete current timer and clear flags */ + if (hsm->test_device) { + hsm->test_device = 0; + penwell_otg_del_timer(TTST_MAINT_TMR); + } + if (hsm->id == ID_ACA_B) penwell_otg_update_chrg_cap(CHRG_ACA, CHRG_CURR_ACA); @@ -2086,11 +2485,41 @@ static void penwell_otg_work(struct work_struct *work) if (iotg->otg.set_vbus) iotg->otg.set_vbus(&iotg->otg, false); + pm_runtime_get(pnw->dev); penwell_otg_add_timer(TA_WAIT_VFALL_TMR); iotg->otg.state = OTG_STATE_A_WAIT_VFALL; + } else if (hsm->test_device && hsm->tst_maint_tmout) { + + hsm->test_device = 0; + + /* Stop HNP polling */ + iotg->stop_hnp_poll(iotg); + + penwell_otg_phy_low_power(0); + + if (iotg->stop_host) + iotg->stop_host(iotg); + else + dev_dbg(pnw->dev, + "host driver has been removed.\n"); + + /* Turn off VBUS */ + if (iotg->otg.set_vbus) + iotg->otg.set_vbus(&iotg->otg, false); + + /* Clear states and wait for SRP */ + hsm->a_srp_det = 0; + hsm->a_bus_req = 0; + iotg->otg.state = OTG_STATE_A_IDLE; } else if (!hsm->a_vbus_vld) { /* Move to A_VBUS_ERR state */ + /* Delete current timer and clear flags */ + if (hsm->test_device) { + hsm->test_device = 0; + penwell_otg_del_timer(TTST_MAINT_TMR); + } + /* Stop HNP polling */ iotg->stop_hnp_poll(iotg); @@ -2106,38 +2535,70 @@ static void penwell_otg_work(struct work_struct *work) penwell_otg_phy_low_power(1); iotg->otg.state = OTG_STATE_A_VBUS_ERR; - } else if (!hsm->a_bus_req) { + } else if (!hsm->a_bus_req && iotg->otg.host->b_hnp_enable) { /* Move to A_SUSPEND state */ /* Stop HNP polling */ iotg->stop_hnp_poll(iotg); - if (iotg->otg.host->b_hnp_enable) { - /* According to Spec 7.1.5 */ - penwell_otg_add_timer(TA_AIDL_BDIS_TMR); - - /* Set HABA to enable hardware assistance to - * signal A-connect after receiver B-disconnect - * Hardware will then set client mode and - * enable URE, SLE and PCE after the assistance - * otg_dummy_irq is used to clean these ints - * when client driver is not resumed. - */ - if (request_irq(pdev->irq, otg_dummy_irq, - IRQF_SHARED, driver_name, - iotg->base) != 0) { + /* According to Spec 7.1.5 */ + penwell_otg_add_timer(TA_AIDL_BDIS_TMR); + + /* Set HABA to enable hardware assistance to + * signal A-connect after receiver B-disconnect + * Hardware will then set client mode and + * enable URE, SLE and PCE after the assistance + * otg_dummy_irq is used to clean these ints + * when client driver is not resumed. + */ + if (request_irq(pdev->irq, otg_dummy_irq, + IRQF_SHARED, driver_name, + iotg->base) != 0) { dev_dbg(pnw->dev, - "request interrupt %d failed\n", - pdev->irq); - } - penwell_otg_HABA(1); + "request interrupt %d failed\n", + pdev->irq); } + penwell_otg_HABA(1); penwell_otg_loc_sof(0); penwell_otg_phy_low_power(0); iotg->otg.state = OTG_STATE_A_SUSPEND; + } else if (!hsm->b_conn && hsm->test_device + && hsm->otg_vbus_off) { + /* If it is a test device with otg_vbus_off bit set, + * turn off VBUS on disconnect event and stay for + * TTST_NOADP without ADP */ + + penwell_otg_del_timer(TTST_MAINT_TMR); + + /* Stop HNP polling */ + iotg->stop_hnp_poll(iotg); + + penwell_otg_phy_low_power(0); + + if (iotg->stop_host) + iotg->stop_host(iotg); + else + dev_dbg(pnw->dev, + "host driver has been removed.\n"); + + /* Turn off VBUS */ + if (iotg->otg.set_vbus) + iotg->otg.set_vbus(&iotg->otg, false); + + pm_runtime_get(pnw->dev); + penwell_otg_add_timer(TTST_NOADP_TMR); + iotg->otg.state = OTG_STATE_A_WAIT_VFALL; + } else if (!hsm->b_conn) { + + /* Delete current timer and clear flags */ + if (hsm->test_device) { + hsm->test_device = 0; + penwell_otg_del_timer(TTST_MAINT_TMR); + } + /* Stop HNP polling */ iotg->stop_hnp_poll(iotg); @@ -2181,6 +2642,7 @@ static void penwell_otg_work(struct work_struct *work) if (iotg->otg.set_vbus) iotg->otg.set_vbus(&iotg->otg, false); + pm_runtime_get(pnw->dev); penwell_otg_add_timer(TA_WAIT_VFALL_TMR); iotg->otg.state = OTG_STATE_A_WAIT_VFALL; } else if (!hsm->a_vbus_vld) { @@ -2204,8 +2666,6 @@ static void penwell_otg_work(struct work_struct *work) iotg->otg.state = OTG_STATE_A_VBUS_ERR; } else if (!hsm->b_conn && !pnw->iotg.otg.host->b_hnp_enable) { /* Move to A_WAIT_BCON */ - penwell_otg_HABA(0); - free_irq(pdev->irq, iotg->base); /* delete current timer */ penwell_otg_del_timer(TA_AIDL_BDIS_TMR); @@ -2285,6 +2745,7 @@ static void penwell_otg_work(struct work_struct *work) iotg->otg.set_vbus(&iotg->otg, false); set_host_mode(); + pm_runtime_get(pnw->dev); penwell_otg_add_timer(TA_WAIT_VFALL_TMR); iotg->otg.state = OTG_STATE_A_WAIT_VFALL; } else if (!hsm->a_vbus_vld) { @@ -2352,6 +2813,7 @@ static void penwell_otg_work(struct work_struct *work) if (hsm->a_clr_err) hsm->a_clr_err = 0; + pm_runtime_get(pnw->dev); penwell_otg_add_timer(TA_WAIT_VFALL_TMR); iotg->otg.state = OTG_STATE_A_WAIT_VFALL; } @@ -2359,6 +2821,7 @@ static void penwell_otg_work(struct work_struct *work) case OTG_STATE_A_WAIT_VFALL: if (hsm->a_wait_vfall_tmout) { hsm->a_srp_det = 0; + hsm->a_wait_vfall_tmout = 0; /* Move to A_IDLE state, vbus falls */ penwell_otg_phy_low_power(1); @@ -2366,6 +2829,21 @@ static void penwell_otg_work(struct work_struct *work) /* Always set a_bus_req to 1, in case no ADP */ hsm->a_bus_req = 1; + pm_runtime_put(pnw->dev); + iotg->otg.state = OTG_STATE_A_IDLE; + penwell_update_transceiver(); + } else if (hsm->test_device && hsm->otg_vbus_off + && hsm->tst_noadp_tmout) { + /* After noadp timeout, switch back to normal mode */ + hsm->test_device = 0; + hsm->otg_vbus_off = 0; + hsm->tst_noadp_tmout = 0; + + penwell_otg_phy_low_power(1); + + hsm->a_bus_req = 1; + + pm_runtime_put(pnw->dev); iotg->otg.state = OTG_STATE_A_IDLE; penwell_update_transceiver(); } @@ -2391,6 +2869,8 @@ show_registers(struct device *_dev, struct device_attribute *attr, char *buf) next = buf; size = PAGE_SIZE; + pm_runtime_get_sync(pnw->dev); + t = scnprintf(next, size, "\n" "USBCMD = 0x%08x\n" @@ -2410,6 +2890,9 @@ show_registers(struct device *_dev, struct device_attribute *attr, char *buf) readl(pnw->iotg.base + 0xf4), readl(pnw->iotg.base + 0xf8) ); + + pm_runtime_put_sync(pnw->dev); + size -= t; next += t; @@ -2467,6 +2950,8 @@ show_hsm(struct device *_dev, struct device_attribute *attr, char *buf) "b_srp_fail_tmout = \t%d\n" "b_srp_fail_tmr = \t%d\n" "b_adp_sense_tmout = \t%d\n" + "tst_maint_tmout = \t%d\n" + "tst_noadp_tmout = \t%d\n" "a_bus_drop = \t%d\n" "a_bus_req = \t%d\n" "a_clr_err = \t%d\n" @@ -2502,6 +2987,8 @@ show_hsm(struct device *_dev, struct device_attribute *attr, char *buf) iotg->hsm.b_srp_fail_tmout, iotg->hsm.b_srp_fail_tmr, iotg->hsm.b_adp_sense_tmout, + iotg->hsm.tst_maint_tmout, + iotg->hsm.tst_noadp_tmout, iotg->hsm.a_bus_drop, iotg->hsm.a_bus_req, iotg->hsm.a_clr_err, @@ -2742,6 +3229,31 @@ static struct attribute_group debug_dev_attr_group = { .attrs = inputs_attrs, }; +static int penwell_otg_aca_enable(void) +{ + int retval = 0; + struct penwell_otg *pnw = the_transceiver; + + penwell_otg_msic_spi_access(true); + + retval = intel_scu_ipc_update_register(SPI_TI_VS4, + TI_ACA_DET_EN, TI_ACA_DET_EN); + if (retval) + goto done; + + retval = intel_scu_ipc_update_register(SPI_TI_VS5, + TI_ID_FLOAT_EN | TI_ID_RES_EN, + TI_ID_FLOAT_EN | TI_ID_RES_EN); + +done: + penwell_otg_msic_spi_access(false); + + if (retval) + dev_warn(pnw->dev, "Failed to enable ACA device detection\n"); + + return retval; +} + static int penwell_otg_probe(struct pci_dev *pdev, const struct pci_device_id *id) { @@ -2819,6 +3331,8 @@ static int penwell_otg_probe(struct pci_dev *pdev, pnw->iotg.start_hnp_poll = penwell_otg_start_hnp_poll; pnw->iotg.stop_hnp_poll = penwell_otg_stop_hnp_poll; pnw->iotg.otg.state = OTG_STATE_UNDEFINED; + pnw->rt_resuming = 0; + pnw->queue_stop = 0; if (otg_set_transceiver(&pnw->iotg.otg)) { dev_dbg(pnw->dev, "can't set transceiver\n"); retval = -EBUSY; @@ -2829,6 +3343,7 @@ static int penwell_otg_probe(struct pci_dev *pdev, pnw->iotg.ulpi_ops.write = penwell_otg_ulpi_write; spin_lock_init(&pnw->iotg.hnp_poll_lock); + spin_lock_init(&pnw->notify_lock); init_timer(&pnw->hsm_timer); init_timer(&pnw->bus_mon_timer); @@ -2854,8 +3369,24 @@ static int penwell_otg_probe(struct pci_dev *pdev, pnw->msic = penwell_otg_check_msic(); penwell_otg_phy_low_power(0); + /* Workaround for ULPI lockup issue, need turn off PHY 4ms */ + penwell_otg_phy_enable(0); + usleep_range(4000, 4500); penwell_otg_phy_enable(1); + /* Enable ID pullup immediately after reeable PHY */ + val32 = readl(pnw->iotg.base + CI_OTGSC); + writel(val32 | OTGSC_IDPU, pnw->iotg.base + CI_OTGSC); + + /* Wait correct value to be synced */ + set_host_mode(); + usleep_range(2000, 3000); + penwell_otg_phy_low_power(1); + msleep(100); + + /* enable ACA device detection */ + penwell_otg_aca_enable(); + reset_otg(); init_hsm(); @@ -2900,12 +3431,11 @@ static int penwell_otg_probe(struct pci_dev *pdev, goto err; } - if (pnw->iotg.otg.state == OTG_STATE_A_IDLE) - queue_work(pnw->qwork, &pnw->work); - pm_runtime_put_noidle(&pdev->dev); pm_runtime_allow(&pdev->dev); + penwell_update_transceiver(); + return 0; err: @@ -2950,40 +3480,61 @@ static void penwell_otg_remove(struct pci_dev *pdev) pnw = NULL; } -static void transceiver_suspend(struct pci_dev *pdev) +void penwell_otg_shutdown(struct pci_dev *pdev) { - penwell_otg_phy_low_power(1); - pci_save_state(pdev); - pci_set_power_state(pdev, PCI_D3hot); + struct penwell_otg *pnw = the_transceiver; + + dev_dbg(pnw->dev, "%s --->\n", __func__); + + /* Disable MSIC Interrupt Notifications */ + penwell_otg_msic_spi_access(true); + + penwell_otg_msic_write(MSIC_INT_EN_RISE_CLR, 0x1F); + penwell_otg_msic_write(MSIC_INT_EN_FALL_CLR, 0x1F); + + penwell_otg_msic_spi_access(false); + + dev_dbg(pnw->dev, "%s <---\n", __func__); } -static int penwell_otg_suspend(struct pci_dev *pdev, pm_message_t message) +static int penwell_otg_suspend(struct device *dev) { struct penwell_otg *pnw = the_transceiver; struct intel_mid_otg_xceiv *iotg = &pnw->iotg; + struct pci_dev *pdev; + unsigned long flags; int ret = 0; + pdev = to_pci_dev(dev); + + dev_dbg(pnw->dev, "%s --->\n", __func__); + + if (iotg->otg.state == OTG_STATE_B_PERIPHERAL) { + dev_dbg(pnw->dev, "still alive, don't suspend\n"); + return -EBUSY; + } + /* Disbale OTG interrupts */ penwell_otg_intr(0); - if (pdev->irq) - free_irq(pdev->irq, pnw); + /* Stop queue work from notifier */ + spin_lock_irqsave(&pnw->notify_lock, flags); + pnw->queue_stop = 1; + spin_unlock_irqrestore(&pnw->notify_lock, flags); - /* Prevent more otg_work */ flush_workqueue(pnw->qwork); - destroy_workqueue(pnw->qwork); - pnw->qwork = NULL; /* start actions */ switch (iotg->otg.state) { + case OTG_STATE_A_VBUS_ERR: + set_host_mode(); + iotg->otg.state = OTG_STATE_A_IDLE; + break; case OTG_STATE_A_WAIT_VFALL: + penwell_otg_del_timer(TA_WAIT_VFALL_TMR); iotg->otg.state = OTG_STATE_A_IDLE; case OTG_STATE_A_IDLE: - case OTG_STATE_A_VBUS_ERR: - transceiver_suspend(pdev); - break; case OTG_STATE_B_IDLE: - transceiver_suspend(pdev); break; case OTG_STATE_A_WAIT_VRISE: penwell_otg_del_timer(TA_WAIT_VRISE_TMR); @@ -2992,7 +3543,6 @@ static int penwell_otg_suspend(struct pci_dev *pdev, pm_message_t message) /* Turn off VBus */ iotg->otg.set_vbus(&iotg->otg, false); iotg->otg.state = OTG_STATE_A_IDLE; - transceiver_suspend(pdev); break; case OTG_STATE_A_WAIT_BCON: penwell_otg_del_timer(TA_WAIT_BCON_TMR); @@ -3003,27 +3553,15 @@ static int penwell_otg_suspend(struct pci_dev *pdev, pm_message_t message) iotg->hsm.a_srp_det = 0; + penwell_otg_phy_vbus_wakeup(false); + /* Turn off VBus */ iotg->otg.set_vbus(&iotg->otg, false); iotg->otg.state = OTG_STATE_A_IDLE; - transceiver_suspend(pdev); break; case OTG_STATE_A_HOST: - /* Stop HNP polling */ - iotg->stop_hnp_poll(iotg); - - if (pnw->iotg.stop_host) - pnw->iotg.stop_host(&pnw->iotg); - else - dev_dbg(pnw->dev, "host driver has been stopped.\n"); - - iotg->hsm.a_srp_det = 0; - - /* Turn off VBus */ - iotg->otg.set_vbus(&iotg->otg, false); - - iotg->otg.state = OTG_STATE_A_IDLE; - transceiver_suspend(pdev); + dev_dbg(pnw->dev, "don't suspend, host still alive\n"); + ret = -EBUSY; break; case OTG_STATE_A_SUSPEND: penwell_otg_del_timer(TA_AIDL_BDIS_TMR); @@ -3034,10 +3572,11 @@ static int penwell_otg_suspend(struct pci_dev *pdev, pm_message_t message) dev_dbg(pnw->dev, "host driver has been removed.\n"); iotg->hsm.a_srp_det = 0; + penwell_otg_phy_vbus_wakeup(false); + /* Turn off VBus */ iotg->otg.set_vbus(&iotg->otg, false); iotg->otg.state = OTG_STATE_A_IDLE; - transceiver_suspend(pdev); break; case OTG_STATE_A_PERIPHERAL: penwell_otg_del_timer(TA_BIDL_ADIS_TMR); @@ -3051,7 +3590,6 @@ static int penwell_otg_suspend(struct pci_dev *pdev, pm_message_t message) iotg->otg.set_vbus(&iotg->otg, false); iotg->hsm.a_srp_det = 0; iotg->otg.state = OTG_STATE_A_IDLE; - transceiver_suspend(pdev); break; case OTG_STATE_B_HOST: /* Stop HNP polling */ @@ -3063,15 +3601,10 @@ static int penwell_otg_suspend(struct pci_dev *pdev, pm_message_t message) dev_dbg(pnw->dev, "host driver has been stopped.\n"); iotg->hsm.b_bus_req = 0; iotg->otg.state = OTG_STATE_B_IDLE; - transceiver_suspend(pdev); break; case OTG_STATE_B_PERIPHERAL: - if (pnw->iotg.stop_peripheral) - pnw->iotg.stop_peripheral(&pnw->iotg); - else - dev_dbg(pnw->dev, "client driver has been stopped.\n"); - iotg->otg.state = OTG_STATE_B_IDLE; - transceiver_suspend(pdev); + dev_dbg(pnw->dev, "don't suspend, client still alive\n"); + ret = -EBUSY; break; case OTG_STATE_B_WAIT_ACON: penwell_otg_del_timer(TB_ASE0_BRST_TMR); @@ -3084,50 +3617,63 @@ static int penwell_otg_suspend(struct pci_dev *pdev, pm_message_t message) dev_dbg(pnw->dev, "host driver has been stopped.\n"); iotg->hsm.b_bus_req = 0; iotg->otg.state = OTG_STATE_B_IDLE; - transceiver_suspend(pdev); break; default: dev_dbg(pnw->dev, "error state before suspend\n"); break; } - return ret; -} + if (ret) { + /* allow queue work from notifier */ + spin_lock_irqsave(&pnw->notify_lock, flags); + pnw->queue_stop = 0; + spin_unlock_irqrestore(&pnw->notify_lock, flags); -static void transceiver_resume(struct pci_dev *pdev) -{ - pci_restore_state(pdev); - pci_set_power_state(pdev, PCI_D0); + penwell_otg_intr(1); + + penwell_update_transceiver(); + } else { + penwell_otg_phy_low_power(1); + penwell_otg_vusb330_low_power(1); + } + + dev_dbg(pnw->dev, "%s <---\n", __func__); + return ret; } -static int penwell_otg_resume(struct pci_dev *pdev) +static int penwell_otg_resume(struct device *dev) { struct penwell_otg *pnw = the_transceiver; + struct pci_dev *pdev; int ret = 0; + unsigned long flags; - transceiver_resume(pdev); + pdev = to_pci_dev(dev); - pnw->qwork = create_singlethread_workqueue("penwell_otg_queue"); - if (!pnw->qwork) { - dev_dbg(pnw->dev, "cannot create penwell otg workqueue\n"); - ret = -ENOMEM; - goto error; - } + dev_dbg(pnw->dev, "%s --->\n", __func__); - if (request_irq(pdev->irq, otg_irq, IRQF_SHARED, - driver_name, pnw) != 0) { - dev_dbg(pnw->dev, "request irq %d failed\n", pdev->irq); - ret = -EBUSY; - goto error; - } + penwell_otg_vusb330_low_power(0); + + /* D3->D0 controller will be reset, so reset work mode and PHY state + * which is cleared by the reset */ switch (pnw->iotg.otg.state) { case OTG_STATE_B_IDLE: break; + case OTG_STATE_A_IDLE: + penwell_otg_phy_vbus_wakeup(true); + /* Provide power as default */ + pnw->iotg.hsm.a_bus_req = 1; + break; default: break; } + /* allow queue work from notifier */ + spin_lock_irqsave(&pnw->notify_lock, flags); + pnw->queue_stop = 0; + spin_unlock_irqrestore(&pnw->notify_lock, flags); + /* enable OTG interrupts */ penwell_otg_intr(1); @@ -3135,10 +3681,7 @@ static int penwell_otg_resume(struct pci_dev *pdev) penwell_update_transceiver(); - return ret; -error: - penwell_otg_intr(0); - transceiver_suspend(pdev); + dev_dbg(pnw->dev, "%s <---\n", __func__); return ret; } @@ -3149,6 +3692,7 @@ static int penwell_otg_runtime_suspend(struct device *dev) struct penwell_otg *pnw = the_transceiver; struct pci_dev *pdev; int ret = 0; + u32 val; dev_dbg(dev, "%s --->\n", __func__); @@ -3156,12 +3700,14 @@ static int penwell_otg_runtime_suspend(struct device *dev) switch (pnw->iotg.otg.state) { case OTG_STATE_A_IDLE: + break; case OTG_STATE_B_IDLE: - /* Transceiver handle it itself */ - penwell_otg_phy_low_power(1); - pci_save_state(pdev); - pci_disable_device(pdev); - pci_set_power_state(pdev, PCI_D3hot); + val = readl(pnw->iotg.base + CI_USBMODE); + if (!(val & USBMODE_CM)) { + /* Controller needs to reset & set mode */ + dev_dbg(dev, "reset to client mode\n"); + set_client_mode(); + } break; case OTG_STATE_A_WAIT_BCON: case OTG_STATE_A_HOST: @@ -3178,6 +3724,12 @@ static int penwell_otg_runtime_suspend(struct device *dev) break; } + penwell_otg_phy_low_power(1); + + msleep(2); + + penwell_otg_vusb330_low_power(1); + dev_dbg(dev, "%s <---\n", __func__); return ret; } @@ -3187,30 +3739,38 @@ static int penwell_otg_runtime_resume(struct device *dev) struct penwell_otg *pnw = the_transceiver; struct pci_dev *pdev; int ret = 0; + u32 val; dev_dbg(dev, "%s --->\n", __func__); pdev = to_pci_dev(dev); + if (pnw->rt_resuming) { + dev_dbg(pnw->dev, "%s rt_resuming--->\n", __func__); + penwell_otg_check_wakeup_event(pnw); + } + + penwell_otg_vusb330_low_power(0); penwell_otg_intr(1); - penwell_otg_phy_low_power(0); switch (pnw->iotg.otg.state) { case OTG_STATE_A_IDLE: + break; case OTG_STATE_B_IDLE: - /* Transceiver handle it itself */ - pci_set_power_state(pdev, PCI_D0); - pci_restore_state(pdev); - ret = pci_enable_device(pdev); - if (ret) - dev_err(&pdev->dev, "device cant be enabled\n"); - penwell_otg_phy_low_power(0); + val = readl(pnw->iotg.base + CI_USBMODE); + if (!(val & USBMODE_CM)) { + /* Controller needs to reset & set mode */ + dev_dbg(dev, "reset to client mode\n"); + set_client_mode(); + } break; case OTG_STATE_A_WAIT_BCON: case OTG_STATE_A_HOST: case OTG_STATE_A_SUSPEND: - if (pnw->iotg.runtime_resume_host) + if (pnw->iotg.runtime_resume_host) { + penwell_otg_phy_low_power(0); ret = pnw->iotg.runtime_resume_host(&pnw->iotg); + } break; case OTG_STATE_A_PERIPHERAL: case OTG_STATE_B_PERIPHERAL: @@ -3221,6 +3781,13 @@ static int penwell_otg_runtime_resume(struct device *dev) break; } + if (pnw->rt_resuming) { + dev_dbg(pnw->dev, "irq num: %d\n", pnw->rt_resuming); + pnw->rt_resuming = 0; + + pm_runtime_put(pnw->dev); + } + dev_dbg(dev, "%s <---\n", __func__); return 0; } @@ -3243,9 +3810,12 @@ static int penwell_otg_runtime_idle(struct device *dev) break; } + /* some delay for stability */ + pm_schedule_suspend(dev, 500); + dev_dbg(dev, "%s <---\n", __func__); - return 0; + return -EBUSY; } #else @@ -3272,6 +3842,8 @@ static const struct dev_pm_ops penwell_otg_pm_ops = { .runtime_suspend = penwell_otg_runtime_suspend, .runtime_resume = penwell_otg_runtime_resume, .runtime_idle = penwell_otg_runtime_idle, + .suspend = penwell_otg_suspend, + .resume = penwell_otg_resume, }; static struct pci_driver otg_pci_driver = { @@ -3280,10 +3852,7 @@ static struct pci_driver otg_pci_driver = { .probe = penwell_otg_probe, .remove = penwell_otg_remove, - - .suspend = penwell_otg_suspend, - .resume = penwell_otg_resume, - + .shutdown = penwell_otg_shutdown, .driver = { .pm = &penwell_otg_pm_ops }, diff --git a/include/linux/usb/ch9.h b/include/linux/usb/ch9.h index 6ec27fb..c00fb37 100644 --- a/include/linux/usb/ch9.h +++ b/include/linux/usb/ch9.h @@ -134,6 +134,13 @@ #define TEST_FORCE_EN 5 /* + * USB OTG 2.0 Test Mode + * See OTG 2.0 spec Table 6-8 + */ +#define TEST_SRP_REQD 6 +#define TEST_HNP_REQD 7 + +/* * OTG 2.0 * Section 6.2 & 6.3 */ diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h index 0b818ab..a3205b7 100644 --- a/include/linux/usb/gadget.h +++ b/include/linux/usb/gadget.h @@ -703,6 +703,11 @@ static inline int usb_gadget_disconnect(struct usb_gadget *gadget) return gadget->ops->pullup(gadget, 0); } +enum gadget_driver_state { + REGISTERED, + UNREGISTERED, + BIND_UNBIND, +}; /*-------------------------------------------------------------------------*/ @@ -774,6 +779,8 @@ static inline int usb_gadget_disconnect(struct usb_gadget *gadget) struct usb_gadget_driver { char *function; enum usb_device_speed speed; + enum gadget_driver_state drv_state; + unsigned ep_max; void (*unbind)(struct usb_gadget *); int (*setup)(struct usb_gadget *, const struct usb_ctrlrequest *); diff --git a/include/linux/usb/intel_mid_otg.h b/include/linux/usb/intel_mid_otg.h index ca2c4a6..013e6ee 100644 --- a/include/linux/usb/intel_mid_otg.h +++ b/include/linux/usb/intel_mid_otg.h @@ -73,6 +73,8 @@ struct otg_hsm { int b_srp_fail_tmout; int b_srp_fail_tmr; int b_adp_sense_tmout; + int tst_maint_tmout; + int tst_noadp_tmout; /* Informative variables */ int a_bus_drop; @@ -89,6 +91,11 @@ struct otg_hsm { /* Others */ int vbus_srp_up; + + /* Test Mode */ + int otg_srp_reqd; + int otg_hnp_reqd; + int otg_vbus_off; }; /* must provide ULPI access function to read/write registers implemented in @@ -178,6 +185,11 @@ struct intel_mid_otg_xceiv *otg_to_mid_xceiv(struct otg_transceiver *otg) #define MID_OTG_NOTIFY_CLIENTREMOVE 0x000a #define MID_OTG_NOTIFY_CLIENTFS 0x000b #define MID_OTG_NOTIFY_CLIENTHS 0x000c +#define MID_OTG_NOTIFY_CRESET 0x000d + +#define MID_OTG_NOTIFY_TEST_SRP_REQD 0x0101 +#define MID_OTG_NOTIFY_TEST_VBUS_OFF 0x0102 +#define MID_OTG_NOTIFY_TEST 0x0103 static inline int intel_mid_otg_register_notifier(struct intel_mid_otg_xceiv *iotg, diff --git a/include/linux/usb/langwell_udc.h b/include/linux/usb/langwell_udc.h index ab959cb..7372415 100644 --- a/include/linux/usb/langwell_udc.h +++ b/include/linux/usb/langwell_udc.h @@ -178,6 +178,7 @@ struct langwell_op_regs { #define PORTS_WKDS BIT(21) /* wake on disconnect enable */ #define PORTS_WKCN BIT(20) /* wake on connect enable */ #define PORTS_PTC(p) (((p)>>16)&0xf) /* bits 19:16, port test control */ +#define PORTS_PTC_MASK (BIT(16) | BIT(17) | BIT(18) | BIT(19)) #define PORTS_PIC (BIT(15) | BIT(14)) /* port indicator control */ #define PORTS_PO BIT(13) /* port owner */ #define PORTS_PP BIT(12) /* port power */ @@ -207,6 +208,7 @@ struct langwell_op_regs { #define LPM_SRT BIT(24) /* shorten reset time */ #define LPM_PFSC BIT(23) /* port force full speed connect */ #define LPM_PHCD BIT(22) /* PHY low power suspend clock disable */ +#define LPM_ASUS BIT(17) /* auto Low Power */ #define LPM_STL BIT(16) /* STALL reply to LPM token */ #define LPM_BA(d) \ (((d)>>1)&0x7ff) /* bits 11:1, BmAttributes */ diff --git a/include/linux/usb/penwell_otg.h b/include/linux/usb/penwell_otg.h index e77db06..8035d91 100644 --- a/include/linux/usb/penwell_otg.h +++ b/include/linux/usb/penwell_otg.h @@ -122,29 +122,94 @@ #define MSIC_OTGCTRL 0x39c #define MSIC_OTGCTRLSET 0x340 #define MSIC_OTGCTRLCLR 0x341 +#define ULPI_OTGCTRL 0x0a +#define ULPI_OTGCTRLSET 0x0b +#define ULPI_OTGCTRLCLR 0x0c # define DMPULLDOWN BIT(2) # define DPPULLDOWN BIT(1) +#define MSIC_USBINTEN_RISE 0x39d +#define MSIC_USBINTEN_RISESET 0x39e +#define MSIC_USBINTEN_RISECLR 0x39f +#define MSIC_USBINTEN_FALL 0x3a0 +#define MSIC_USBINTEN_FALLSET 0x3a1 +#define MSIC_USBINTEN_FALLCLR 0x3a2 +# define IDGND BIT(4) +# define SESSEND BIT(3) +# define SESSVLD BIT(2) +# define VBUSVLD BIT(1) +# define HOSTDISCON BIT(0) #define MSIC_PWRCTRL 0x3b5 #define MSIC_PWRCTRLSET 0x342 #define MSIC_PWRCTRLCLR 0x343 +#define ULPI_PWRCTRL 0x3d +#define ULPI_PWRCTRLSET 0x3e +#define ULPI_PWRCTRLCLR 0x3f # define HWDET BIT(7) # define DPVSRCEN BIT(6) +# define VDATDET BIT(5) # define DPWKPUEN BIT(4) # define SWCNTRL BIT(0) #define MSIC_FUNCTRL 0x398 #define MSIC_FUNCTRLSET 0x344 #define MSIC_FUNCTRLCLR 0x345 +#define ULPI_FUNCTRL 0x04 +#define ULPI_FUNCTRLSET 0x05 +#define ULPI_FUNCTRLCLR 0x06 # define OPMODE1 BIT(4) # define OPMODE0 BIT(3) +# define TERMSELECT BIT(2) +# define XCVRSELECT1 BIT(1) +# define XCVRSELECT0 BIT(0) +#define MSIC_VS1 0x3b6 +#define MSIC_VS1SET 0x3a9 +#define MSIC_VS1CLR 0x3aa +#define ULPI_VS1 0x80 +#define ULPI_VS1SET 0x81 +#define ULPI_VS1CLR 0x82 +# define DATAPOLARITY BIT(6) #define MSIC_VS3 0x3b9 #define MSIC_VS3SET 0x346 /* Vendor Specific */ #define MSIC_VS3CLR 0x347 # define SWUSBDET BIT(4) # define DATACONEN BIT(3) +#define ULPI_VS3 0x85 +#define ULPI_VS3SET 0x86 +#define ULPI_VS3CLR 0x87 +# define CHGD_IDP_SRC BIT(6) +# define IDPULLUP_WK BIT(5) +# define SWUSBDET BIT(4) +# define DATACONEN BIT(3) +#define MSIC_VS4 0x3ba +#define MSIC_VS4SET 0x3ab +#define MSIC_VS4CLR 0x3ac +#define ULPI_VS4 0x88 +#define ULPI_VS4SET 0x89 +#define ULPI_VS4CLR 0x8a +# define ACADET BIT(6) +# define RABUSIN BIT(5) +# define R1KERIES BIT(4) +# define CHRG_SERX_DP BIT(1) +# define CHRG_SERX_DM BIT(0) #define MSIC_ULPIACCESSMODE 0x348 # define SPIMODE BIT(0) +#define MSIC_INT_EN_RISE 0x39D +#define MSIC_INT_EN_RISE_SET 0x39E +#define MSIC_INT_EN_RISE_CLR 0x39F +#define MSIC_INT_EN_FALL 0x3A0 +#define MSIC_INT_EN_FALL_SET 0x3A1 +#define MSIC_INT_EN_FALL_CLR 0x3A2 /* MSIC TI implementation for ADP/ACA */ +#define SPI_TI_VS2 0x3B7 +#define SPI_TI_VS2_LATCH 0x3B8 +#define SPI_TI_VS4 0x3BA +#define SPI_TI_VS5 0x3BB +#define ULPI_TI_USB_INT_STS 0x13 +#define ULPI_TI_USB_INT_LAT 0x14 +# define USB_INT_IDGND BIT(4) +# define USB_INT_SESSEND BIT(3) +# define USB_INT_SESSVLD BIT(2) +# define USB_INT_VBUSVLD BIT(1) #define ULPI_TI_VS2 0x83 # define TI_ID_FLOAT_STS BIT(4) # define TI_ID_RARBRC_STS(d) (((d)>>2)&3) @@ -227,7 +292,9 @@ enum penwell_otg_timer_type { TB_ASE0_BRST_TMR, TB_SE0_SRP_TMR, TB_SRP_FAIL_TMR, /* wait for response of SRP */ - TB_BUS_SUSPEND_TMR + TB_BUS_SUSPEND_TMR, + TTST_MAINT_TMR, + TTST_NOADP_TMR, }; #define TA_WAIT_VRISE 100 @@ -242,6 +309,9 @@ enum penwell_otg_timer_type { #define TB_SRP_FAIL 5500 #define TB_BUS_SUSPEND 500 #define THOS_REQ_POL 1500 +/* Test mode */ +#define TTST_MAINT 9900 +#define TTST_NOADP 5000 /* MSIC vendor information */ enum msic_vendor { @@ -314,6 +384,8 @@ struct penwell_otg { enum msic_vendor msic; struct notifier_block iotg_notifier; + spinlock_t notify_lock; + int queue_stop; struct adp_status adp; @@ -321,6 +393,8 @@ struct penwell_otg { struct otg_bc_cap charging_cap; int (*bc_callback)(void *arg, int event, struct otg_bc_cap *cap); void *bc_arg; + + unsigned rt_resuming; }; static inline