usb: gadget: Add HSIC Power Management
Add HSIC power management to support HSIC core Low Power Mode and
integrate it with System PM and Runtime PM mechanisms.
Change-Id: Id87d5740e7f229e599f8f6bb17a70ac1d8078bf4
Signed-off-by: Ofir Cohen <ofirc@codeaurora.org>
diff --git a/drivers/usb/gadget/ci13xxx_msm_hsic.c b/drivers/usb/gadget/ci13xxx_msm_hsic.c
index 135c84d..12b6774 100644
--- a/drivers/usb/gadget/ci13xxx_msm_hsic.c
+++ b/drivers/usb/gadget/ci13xxx_msm_hsic.c
@@ -35,14 +35,17 @@
#define MSM_USB_BASE (mhsic->regs)
-#define ULPI_IO_TIMEOUT_USEC (10 * 1000)
-#define USB_PHY_VDD_DIG_VOL_MIN 1045000 /* uV */
-#define USB_PHY_VDD_DIG_VOL_MAX 1320000 /* uV */
-#define USB_PHY_VDD_DIG_LOAD 49360 /* uA */
-#define LINK_RESET_TIMEOUT_USEC (250 * 1000)
-#define HSIC_CFG_REG 0x30
-#define HSIC_IO_CAL_PER_REG 0x33
-#define HSIC_DBG1_REG 0x38
+#define ULPI_IO_TIMEOUT_USEC (10 * 1000)
+#define USB_PHY_VDD_DIG_VOL_SUSP_MIN 500000 /* uV */
+#define USB_PHY_VDD_DIG_VOL_MIN 1045000 /* uV */
+#define USB_PHY_VDD_DIG_VOL_MAX 1320000 /* uV */
+#define USB_PHY_VDD_DIG_LOAD 49360 /* uA */
+#define LINK_RESET_TIMEOUT_USEC (250 * 1000)
+#define PHY_SUSPEND_TIMEOUT_USEC (500 * 1000)
+#define PHY_RESUME_TIMEOUT_USEC (100 * 1000)
+#define HSIC_CFG_REG 0x30
+#define HSIC_IO_CAL_PER_REG 0x33
+#define HSIC_DBG1_REG 0x38
struct msm_hsic_per *the_mhsic;
@@ -57,6 +60,11 @@
bool async_int;
void __iomem *regs;
int irq;
+ atomic_t in_lpm;
+ struct wake_lock wlock;
+ struct msm_xo_voter *xo_handle;
+ struct workqueue_struct *wq;
+ struct work_struct suspend_w;
};
static int msm_hsic_init_vddcx(struct msm_hsic_per *mhsic, int init)
@@ -302,6 +310,227 @@
}
}
+
+#ifdef CONFIG_PM_SLEEP
+static int msm_hsic_suspend(struct msm_hsic_per *mhsic)
+{
+ int cnt = 0, ret;
+ u32 val;
+
+ if (atomic_read(&mhsic->in_lpm)) {
+ dev_dbg(mhsic->dev, "%s called while in lpm\n", __func__);
+ return 0;
+ }
+ disable_irq(mhsic->irq);
+
+ /*
+ * PHY may take some time or even fail to enter into low power
+ * mode (LPM). Hence poll for 500 msec and reset the PHY and link
+ * in failure case.
+ */
+ val = readl_relaxed(USB_PORTSC) | PORTSC_PHCD;
+ writel_relaxed(val, USB_PORTSC);
+
+ while (cnt < PHY_SUSPEND_TIMEOUT_USEC) {
+ if (readl_relaxed(USB_PORTSC) & PORTSC_PHCD)
+ break;
+ udelay(1);
+ cnt++;
+ }
+
+ if (cnt >= PHY_SUSPEND_TIMEOUT_USEC) {
+ dev_err(mhsic->dev, "Unable to suspend PHY\n");
+ msm_hsic_reset(mhsic);
+ }
+
+ /*
+ * PHY has capability to generate interrupt asynchronously in low
+ * power mode (LPM). This interrupt is level triggered. So USB IRQ
+ * line must be disabled till async interrupt enable bit is cleared
+ * in USBCMD register. Assert STP (ULPI interface STOP signal) to
+ * block data communication from PHY.
+ */
+ writel_relaxed(readl_relaxed(USB_USBCMD) | ASYNC_INTR_CTRL |
+ ULPI_STP_CTRL, USB_USBCMD);
+
+ /*
+ * Ensure that hardware is put in low power mode before
+ * clocks are turned OFF and VDD is allowed to minimize.
+ */
+ mb();
+
+ clk_disable(mhsic->iface_clk);
+ clk_disable(mhsic->core_clk);
+ clk_disable(mhsic->phy_clk);
+ clk_disable(mhsic->cal_clk);
+
+ ret = msm_xo_mode_vote(mhsic->xo_handle, MSM_XO_MODE_OFF);
+ if (ret)
+ dev_err(mhsic->dev, "%s failed to devote for TCXO %d\n"
+ , __func__, ret);
+
+ ret = regulator_set_voltage(mhsic->hsic_vddcx,
+ USB_PHY_VDD_DIG_VOL_SUSP_MIN,
+ USB_PHY_VDD_DIG_VOL_MAX);
+ if (ret < 0)
+ dev_err(mhsic->dev, "unable to set vddcx voltage: min:0.5v max:1.32v\n");
+
+ if (device_may_wakeup(mhsic->dev))
+ enable_irq_wake(mhsic->irq);
+
+ atomic_set(&mhsic->in_lpm, 1);
+ enable_irq(mhsic->irq);
+ wake_unlock(&mhsic->wlock);
+
+ dev_info(mhsic->dev, "HSIC-USB in low power mode\n");
+
+ return 0;
+}
+
+static int msm_hsic_resume(struct msm_hsic_per *mhsic)
+{
+ int cnt = 0, ret;
+ unsigned temp;
+
+ if (!atomic_read(&mhsic->in_lpm)) {
+ dev_dbg(mhsic->dev, "%s called while not in lpm\n", __func__);
+ return 0;
+ }
+
+ wake_lock(&mhsic->wlock);
+ ret = regulator_set_voltage(mhsic->hsic_vddcx,
+ USB_PHY_VDD_DIG_VOL_MIN,
+ USB_PHY_VDD_DIG_VOL_MAX);
+ if (ret < 0)
+ dev_err(mhsic->dev,
+ "unable to set vddcx voltage: min:1.045v max:1.32v\n");
+
+ ret = msm_xo_mode_vote(mhsic->xo_handle, MSM_XO_MODE_ON);
+ if (ret)
+ dev_err(mhsic->dev, "%s failed to vote for TCXO %d\n",
+ __func__, ret);
+
+ clk_enable(mhsic->iface_clk);
+ clk_enable(mhsic->core_clk);
+ clk_enable(mhsic->phy_clk);
+ clk_enable(mhsic->cal_clk);
+
+ temp = readl_relaxed(USB_USBCMD);
+ temp &= ~ASYNC_INTR_CTRL;
+ temp &= ~ULPI_STP_CTRL;
+ writel_relaxed(temp, USB_USBCMD);
+
+ if (!(readl_relaxed(USB_PORTSC) & PORTSC_PHCD))
+ goto skip_phy_resume;
+
+ temp = readl_relaxed(USB_PORTSC) & ~PORTSC_PHCD;
+ writel_relaxed(temp, USB_PORTSC);
+ while (cnt < PHY_RESUME_TIMEOUT_USEC) {
+ if (!(readl_relaxed(USB_PORTSC) & PORTSC_PHCD) &&
+ (readl_relaxed(USB_ULPI_VIEWPORT) & ULPI_SYNC_STATE))
+ break;
+ udelay(1);
+ cnt++;
+ }
+
+ if (cnt >= PHY_RESUME_TIMEOUT_USEC) {
+ /*
+ * This is a fatal error. Reset the link and
+ * PHY to make hsic working.
+ */
+ dev_err(mhsic->dev, "Unable to resume USB. Reset the hsic\n");
+ msm_hsic_reset(mhsic);
+ }
+skip_phy_resume:
+ if (device_may_wakeup(mhsic->dev))
+ disable_irq_wake(mhsic->irq);
+
+ atomic_set(&mhsic->in_lpm, 0);
+
+ if (mhsic->async_int) {
+ mhsic->async_int = false;
+ enable_irq(mhsic->irq);
+ }
+
+ dev_info(mhsic->dev, "HSIC-USB exited from low power mode\n");
+
+ return 0;
+}
+
+static int msm_hsic_pm_suspend(struct device *dev)
+{
+ struct msm_hsic_per *mhsic = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "MSM HSIC Peripheral PM suspend\n");
+
+ return msm_hsic_suspend(mhsic);
+}
+
+#ifdef CONFIG_PM_RUNTIME
+static int msm_hsic_pm_resume(struct device *dev)
+{
+ dev_dbg(dev, "MSM HSIC Peripheral PM resume\n");
+
+ /*
+ * Do not resume hardware as part of system resume,
+ * rather, wait for the ASYNC INT from the h/w
+ */
+ return 0;
+}
+#else
+static int msm_hsic_pm_resume(struct device *dev)
+{
+ struct msm_hsic_per *mhsic = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "MSM HSIC Peripheral PM resume\n");
+
+ return msm_hsic_resume(mhsic);
+}
+#endif
+
+static void msm_hsic_pm_suspend_work(struct work_struct *w)
+{
+ pm_runtime_put_noidle(the_mhsic->dev);
+ pm_runtime_suspend(the_mhsic->dev);
+}
+#endif /* CONFIG_PM_SLEEP */
+
+#ifdef CONFIG_PM_RUNTIME
+static int msm_hsic_runtime_idle(struct device *dev)
+{
+ dev_dbg(dev, "MSM HSIC Peripheral runtime idle\n");
+
+ return 0;
+}
+
+static int msm_hsic_runtime_suspend(struct device *dev)
+{
+ struct msm_hsic_per *mhsic = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "MSM HSIC Peripheral runtime suspend\n");
+
+ return msm_hsic_suspend(mhsic);
+}
+
+static int msm_hsic_runtime_resume(struct device *dev)
+{
+ struct msm_hsic_per *mhsic = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "MSM HSIC Peripheral runtime resume\n");
+ pm_runtime_get_noresume(mhsic->dev);
+
+ return msm_hsic_resume(mhsic);
+}
+#endif
+
+#ifdef CONFIG_PM
+static const struct dev_pm_ops msm_hsic_dev_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(msm_hsic_pm_suspend, msm_hsic_pm_resume)
+ SET_RUNTIME_PM_OPS(msm_hsic_runtime_suspend, msm_hsic_runtime_resume,
+ msm_hsic_runtime_idle)
+};
+#endif
+
/**
* Dummy match function - will be called only for HSIC msm
* device (msm_device_gadget_hsic_peripheral).
@@ -320,6 +549,15 @@
static irqreturn_t msm_udc_hsic_irq(int irq, void *data)
{
+ struct msm_hsic_per *mhsic = data;
+
+ if (atomic_read(&mhsic->in_lpm)) {
+ disable_irq_nosync(mhsic->irq);
+ mhsic->async_int = true;
+ pm_request_resume(mhsic->dev);
+ return IRQ_HANDLED;
+ }
+
return udc_irq();
}
@@ -338,6 +576,10 @@
dev_dbg(dev, "CI13XXX_CONTROLLER_CONNECT_EVENT received\n");
msm_hsic_start();
break;
+ case CI13XXX_CONTROLLER_SUSPEND_EVENT:
+ dev_dbg(dev, "CI13XXX_CONTROLLER_SUSPEND_EVENT received\n");
+ queue_work(mhsic->wq, &mhsic->suspend_w);
+ break;
default:
dev_dbg(dev, "unknown ci13xxx_udc event\n");
break;
@@ -378,6 +620,15 @@
goto error;
}
+ mhsic->wq = alloc_workqueue("mhsic_wq", WQ_UNBOUND | WQ_MEM_RECLAIM, 1);
+ if (!mhsic->wq) {
+ pr_err("%s: Unable to create workqueue mhsic wq\n",
+ __func__);
+ ret = -ENOMEM;
+ goto error;
+ }
+ INIT_WORK(&mhsic->suspend_w, msm_hsic_pm_suspend_work);
+
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "Unable to get memory resource\n");
@@ -392,6 +643,21 @@
}
dev_info(&pdev->dev, "HSIC Peripheral regs = %p\n", mhsic->regs);
+ mhsic->xo_handle = msm_xo_get(MSM_XO_CXO, "hsic_peripheral");
+ if (IS_ERR(mhsic->xo_handle)) {
+ dev_err(&pdev->dev, "%s not able to get the handle "
+ "to vote for TCXO\n", __func__);
+ ret = PTR_ERR(mhsic->xo_handle);
+ goto unmap;
+ }
+
+ ret = msm_xo_mode_vote(mhsic->xo_handle, MSM_XO_MODE_ON);
+ if (ret) {
+ dev_err(&pdev->dev, "%s failed to vote for TCXO %d\n",
+ __func__, ret);
+ goto free_xo_handle;
+ }
+
ret = msm_hsic_enable_clocks(pdev, mhsic, true);
if (ret) {
@@ -422,16 +688,21 @@
msm_hsic_connect_peripheral(&pdev->dev);
+ device_init_wakeup(&pdev->dev, 1);
+ wake_lock_init(&mhsic->wlock, WAKE_LOCK_SUSPEND, dev_name(&pdev->dev));
+ wake_lock(&mhsic->wlock);
+
ret = request_irq(mhsic->irq, msm_udc_hsic_irq,
- IRQF_SHARED, pdev->name, pdev);
+ IRQF_SHARED, pdev->name, mhsic);
if (ret < 0) {
dev_err(&pdev->dev, "request_irq failed\n");
ret = -ENODEV;
goto udc_remove;
}
- pm_runtime_no_callbacks(&pdev->dev);
+ pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
+ pm_runtime_get_sync(&pdev->dev);
return 0;
udc_remove:
@@ -440,9 +711,13 @@
msm_hsic_init_vddcx(mhsic, 0);
deinit_clocks:
msm_hsic_enable_clocks(pdev, mhsic, 0);
+ msm_xo_mode_vote(mhsic->xo_handle, MSM_XO_MODE_OFF);
+free_xo_handle:
+ msm_xo_put(mhsic->xo_handle);
unmap:
iounmap(mhsic->regs);
error:
+ destroy_workqueue(mhsic->wq);
kfree(mhsic);
return ret;
}
@@ -452,19 +727,29 @@
struct msm_hsic_per *mhsic = platform_get_drvdata(pdev);
device_init_wakeup(&pdev->dev, 0);
+ pm_runtime_disable(&pdev->dev);
+ pm_runtime_set_suspended(&pdev->dev);
+
msm_hsic_init_vddcx(mhsic, 0);
msm_hsic_enable_clocks(pdev, mhsic, 0);
+ msm_xo_put(mhsic->xo_handle);
+ wake_lock_destroy(&mhsic->wlock);
+ destroy_workqueue(mhsic->wq);
udc_remove();
iounmap(mhsic->regs);
kfree(mhsic);
return 0;
}
+
static struct platform_driver msm_hsic_peripheral_driver = {
.probe = msm_hsic_probe,
.remove = __devexit_p(hsic_msm_remove),
.driver = {
.name = "msm_hsic_peripheral",
+#ifdef CONFIG_PM
+ .pm = &msm_hsic_dev_pm_ops,
+#endif
},
};