#include <linux/usb/gadget.h>
#include <linux/usb/otg.h>
#include <linux/pm.h>
+#include <linux/pm_runtime.h>
#include <linux/io.h>
#include <linux/irq.h>
+#include <linux/wakelock.h>
#include <asm/system.h>
#include <asm/unaligned.h>
.wMaxPacketSize = EP0_MAX_PKT_SIZE,
};
+static void ep_set_halt(struct langwell_ep *ep, int value);
/*-------------------------------------------------------------------------*/
/* debugging */
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,
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__);
}
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;
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) {
if (!_ep || !ep->desc)
return -EINVAL;
+ pm_runtime_get_sync(&dev->pdev->dev);
+
spin_lock_irqsave(&dev->lock, flags);
/* disable endpoint control register */
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__);
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;
(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;
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);
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;
_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;
} 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");
}
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);
if (!dev->driver)
return -ESHUTDOWN;
+ pm_runtime_get_sync(&dev->pdev->dev);
+
spin_lock_irqsave(&dev->lock, flags);
stopped = ep->stopped;
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;
}
/* 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;
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;
}
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)
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)
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;
}
} while (readl(&dev->op_regs->endptstat) & flush_bit);
done:
+ pm_runtime_put_sync(&dev->pdev->dev);
+
dev_vdbg(&dev->pdev->dev, "<--- %s()\n", __func__);
}
}
-/* 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)
{
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);
}
spin_unlock_irqrestore(&dev->lock, flags);
+ pm_runtime_put_sync(&dev->pdev->dev);
+
dev_vdbg(&dev->pdev->dev, "<--- %s()\n", __func__);
return 0;
}
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;
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)
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);
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);
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);
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,
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__);
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
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;
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);
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__);
}
{
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) {
/* 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) {
complete(dev->done);
+ wake_lock_destroy(&dev->wake_lock);
+
dev_dbg(&dev->pdev->dev, "<--- %s()\n", __func__);
kfree(dev);
}
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);
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);
}
#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);
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;
/* 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);
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)
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,
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);
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);
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)
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);
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);
return "Charging Downstream Port";
case CHRG_DCP:
return "Dedicated Charging Port";
+ case CHRG_ACA:
+ return "Accessory Charger Adaptor";
case CHRG_UNKNOWN:
return "Unknown";
default:
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;
}
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,
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,
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)
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)
{
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;
}
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) {
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");
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;
}
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 */
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;
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)
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");
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;
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;
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));
/* 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");
/* 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;
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 {
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();
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;
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
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);
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;
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
/* 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;
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 */
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) {
/* 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);
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);
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);
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);
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) {
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);
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) {
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;
}
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);
/* 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();
}
next = buf;
size = PAGE_SIZE;
+ pm_runtime_get_sync(pnw->dev);
+
t = scnprintf(next, size,
"\n"
"USBCMD = 0x%08x\n"
readl(pnw->iotg.base + 0xf4),
readl(pnw->iotg.base + 0xf8)
);
+
+ pm_runtime_put_sync(pnw->dev);
+
size -= t;
next += t;
"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"
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,
.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)
{
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;
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);
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();
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:
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);
/* 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);
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);
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);
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 */
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);
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);
penwell_update_transceiver();
- return ret;
-error:
- penwell_otg_intr(0);
- transceiver_suspend(pdev);
+ dev_dbg(pnw->dev, "%s <---\n", __func__);
return ret;
}
struct penwell_otg *pnw = the_transceiver;
struct pci_dev *pdev;
int ret = 0;
+ u32 val;
dev_dbg(dev, "%s --->\n", __func__);
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:
break;
}
+ penwell_otg_phy_low_power(1);
+
+ msleep(2);
+
+ penwell_otg_vusb330_low_power(1);
+
dev_dbg(dev, "%s <---\n", __func__);
return ret;
}
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:
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;
}
break;
}
+ /* some delay for stability */
+ pm_schedule_suspend(dev, 500);
+
dev_dbg(dev, "%s <---\n", __func__);
- return 0;
+ return -EBUSY;
}
#else
.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 = {
.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
},