From: CK Hu <ck...@mediatek.com>

add support runtime pm feature

Signed-off-by: Zhanyong Wang <zhanyong.w...@mediatek.com>
Signed-off-by: Chunfeng Yun <chunfeng....@mediatek.com>
---
 drivers/usb/host/xhci-mtk.c | 446 +++++++++++++++++++++++++++++++++++++++++++-
 drivers/usb/host/xhci-mtk.h |  14 ++
 2 files changed, 455 insertions(+), 5 deletions(-)
 mode change 100644 => 100755 drivers/usb/host/xhci-mtk.h

diff --git a/drivers/usb/host/xhci-mtk.c b/drivers/usb/host/xhci-mtk.c
index d95221f..ec1adb4 100755
--- a/drivers/usb/host/xhci-mtk.c
+++ b/drivers/usb/host/xhci-mtk.c
@@ -14,6 +14,7 @@
 #include <linux/mfd/syscon.h>
 #include <linux/module.h>
 #include <linux/of.h>
+#include <linux/of_irq.h>
 #include <linux/platform_device.h>
 #include <linux/pm_runtime.h>
 #include <linux/regmap.h>
@@ -77,6 +78,228 @@ enum ssusb_uwk_vers {
        SSUSB_UWK_V3,
 };
 
+int xhci_mtk_runtime_ready;
+
+#if IS_ENABLED(CONFIG_PM)
+static int xhci_mtk_runtime_suspend(struct device *dev);
+static int xhci_mtk_runtime_resume(struct device *dev);
+static int xhci_mtk_runtime_idle(struct device *dev);
+static ssize_t xhci_mtk_runtime_show(struct device *dev,
+                                    struct device_attribute *attr,
+                                    char *buf)
+{
+       int i;
+       int ret = 0;
+       int num_ports;
+       struct xhci_hcd_mtk *mtk = dev_get_drvdata(dev);
+       struct usb_hcd *hcd;
+       struct xhci_hcd *xhci;
+       struct xhci_hub *usb2_rhub;
+       struct xhci_hub *usb3_rhub;
+       struct xhci_bus_state *bus_state;
+       struct xhci_port *port;
+       u32 usb2_suspended_ports = -1;
+       u32 usb3_suspended_ports = -1;
+       u16 status;
+
+       if (!mtk->hcd)
+               return -ESHUTDOWN;
+
+       if (!xhci_mtk_runtime_ready)
+               return  sprintf(buf,
+                       "xhci_mtk_runtime_ready is not ready yet!\n");
+
+       if (!mtk->hcd)
+               return -ESHUTDOWN;
+
+       hcd = mtk->hcd;
+       xhci = hcd_to_xhci(hcd);
+       if ((xhci->xhc_state & XHCI_STATE_REMOVING) ||
+                       (xhci->xhc_state & XHCI_STATE_HALTED)) {
+               return -ESHUTDOWN;
+       }
+
+       usb2_rhub = &xhci->usb2_rhub;
+       if (usb2_rhub) {
+               bus_state  = &usb2_rhub->bus_state;
+               num_ports  = usb2_rhub->num_ports;
+               usb2_suspended_ports  = bus_state->suspended_ports;
+               usb2_suspended_ports ^= (BIT(num_ports) - 1);
+               usb2_suspended_ports &= (BIT(num_ports) - 1);
+               for (i = 0; i < num_ports; i++) {
+                       if (usb2_suspended_ports & BIT(i)) {
+                               port = usb2_rhub->ports[i];
+                               status = readl(port->addr);
+
+                               xhci_info(xhci, "USB2: portsc[%i]: 0x%04X\n",
+                                         i, status);
+
+                               if (!(status & PORT_CONNECT))
+                                       usb2_suspended_ports &= ~BIT(i);
+                       }
+               }
+       }
+
+       usb3_rhub = &xhci->usb3_rhub;
+       if (usb3_rhub) {
+               bus_state  = &usb3_rhub->bus_state;
+               num_ports  = usb3_rhub->num_ports;
+               usb3_suspended_ports  = bus_state->suspended_ports;
+               usb3_suspended_ports ^= (BIT(num_ports) - 1);
+               usb3_suspended_ports &= (BIT(num_ports) - 1);
+               for (i = 0; i < num_ports; i++) {
+                       if (usb3_suspended_ports & BIT(i)) {
+                               port = usb3_rhub->ports[i];
+                               status = readl(port->addr);
+
+                               xhci_info(xhci, "USB3: portsc[%i]: 0x%04X\n",
+                                         i, status);
+
+                               if (!(status & PORT_CONNECT))
+                                       usb3_suspended_ports &= ~BIT(i);
+                       }
+               }
+       }
+
+       return sprintf(buf, "USB20: 0x%08X, USB30: 0x%08X ret: %i\n",
+                       usb2_suspended_ports, usb3_suspended_ports, ret);
+}
+
+static ssize_t xhci_mtk_runtime_store(struct device *dev,
+                                     struct device_attribute *attr,
+                                     const char *buf, size_t count)
+{
+       int len = count;
+       char *cp;
+       int rc = count;
+       static const char suspend_string[] = "suspend";
+       static const char resume_string[]  = "resume";
+       static const char idle_string[]    = "idle";
+
+       cp = memchr(buf, '\n', count);
+       if (cp)
+               len = cp - buf;
+
+       if (!xhci_mtk_runtime_ready) {
+               rc = -EAGAIN;
+               dev_info(dev,
+                       "xhci_mtk_runtime_ready is not ready yet!\n");
+               goto exit;
+       }
+
+       if (len == sizeof(suspend_string) - 1 &&
+                       strncmp(buf, suspend_string, len) == 0)
+               xhci_mtk_runtime_suspend(dev);
+       else if (len == sizeof(resume_string) - 1 &&
+                       strncmp(buf, resume_string, len) == 0)
+               xhci_mtk_runtime_resume(dev);
+       else if (len == sizeof(idle_string) - 1 &&
+                       strncmp(buf, idle_string, len) == 0)
+               xhci_mtk_runtime_idle(dev);
+       else
+               rc = -EINVAL;
+
+exit:
+       return rc;
+}
+static DEVICE_ATTR_RW(xhci_mtk_runtime);
+
+static ssize_t seal_runtime_status_show(struct device *dev,
+                               struct device_attribute *attr, char *buf)
+{
+       static char *const seal_status[] = {
+               "SEAL_BUSY",
+               "SEAL_SUSPENDING",
+               "SEAL_SUSPENDED",
+               "SEAL_RESUMING",
+               "SEAL_RESUMED"
+       };
+       struct xhci_hcd_mtk  *mtk = dev_get_drvdata(dev);
+
+       return sprintf(buf, "status: %s(%i)\n",
+                       seal_status[mtk->seal_status], mtk->seal_status);
+}
+static DEVICE_ATTR_RO(seal_runtime_status);
+
+static struct attribute *power_attrs[] = {
+       &dev_attr_xhci_mtk_runtime.attr,
+       &dev_attr_seal_runtime_status.attr,
+       NULL,
+};
+
+static struct attribute_group power_attr_group = {
+       .name   = power_group_name,
+       .attrs  = power_attrs,
+};
+
+static int add_power_attributes(struct device *dev)
+{
+       int rc = 0;
+
+       rc = sysfs_merge_group(&dev->kobj, &power_attr_group);
+
+       return rc;
+}
+
+static void remove_power_attributes(struct device *dev)
+{
+       sysfs_unmerge_group(&dev->kobj, &power_attr_group);
+}
+#else
+#define add_power_attributes(dev)      do {} while (0)
+#define remove_power_attributes(dev)   do {} while (0)
+#endif
+
+static void xhci_mtk_seal_work(struct work_struct *work)
+{
+       struct xhci_hcd_mtk *mtk =
+                       container_of(work, struct xhci_hcd_mtk, seal.work);
+       struct usb_hcd *hcd = mtk->hcd;
+       struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+
+       xhci_dbg(xhci, "spm unseals xHCI controller %i\n", mtk->seal_status);
+       if (mtk->seal_status == SEAL_SUSPENDED) {
+               mtk->seal_status = SEAL_RESUMING;
+               pm_runtime_put_sync_autosuspend(mtk->dev);
+       } else {
+               xhci_warn(xhci,
+                "Ignore seal wakeup source disordered in xHCI controller\n");
+       }
+}
+
+static irqreturn_t xhci_mtk_seal_irq(int irq, void *data)
+{
+       struct xhci_hcd_mtk *mtk = data;
+       struct usb_hcd *hcd = mtk->hcd;
+       struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+
+       xhci_dbg(xhci, "seal irq ISR invoked\n");
+
+       schedule_delayed_work(&mtk->seal, 0);
+
+       return IRQ_HANDLED;
+}
+
+static void xhci_mtk_seal_wakeup_enable(struct xhci_hcd_mtk *mtk, bool enable)
+{
+       struct irq_desc *desc;
+       struct device *dev = mtk->dev;
+
+       if (mtk && mtk->seal_irq) {
+               desc = irq_to_desc(mtk->seal_irq);
+               if (enable) {
+                       desc->irq_data.chip->irq_ack(&desc->irq_data);
+                       enable_irq(mtk->seal_irq);
+                       dev_dbg(dev, "%s: enable_irq %i\n",
+                                __func__, mtk->seal_irq);
+               } else {
+                       disable_irq(mtk->seal_irq);
+                       dev_dbg(dev, "%s: disable_irq %i\n",
+                                __func__, mtk->seal_irq);
+               }
+       }
+}
+
 static int xhci_mtk_host_enable(struct xhci_hcd_mtk *mtk)
 {
        struct mu3c_ippc_regs __iomem *ippc = mtk->ippc_regs;
@@ -344,7 +567,6 @@ static int usb_wakeup_of_property_parse(struct xhci_hcd_mtk 
*mtk,
                        mtk->uwk_reg_base, mtk->uwk_vers);
 
        return PTR_ERR_OR_ZERO(mtk->uwk);
-
 }
 
 static void usb_wakeup_set(struct xhci_hcd_mtk *mtk, bool enable)
@@ -479,9 +701,11 @@ static int xhci_mtk_probe(struct platform_device *pdev)
                return ret;
        }
 
+       pm_runtime_set_active(dev);
+       pm_runtime_use_autosuspend(dev);
+       pm_runtime_set_autosuspend_delay(dev,
+                               CONFIG_USB_AUTOSUSPEND_DELAY * 1000);
        pm_runtime_enable(dev);
-       pm_runtime_get_sync(dev);
-       device_enable_async_suspend(dev);
 
        ret = xhci_mtk_ldos_enable(mtk);
        if (ret)
@@ -496,6 +720,14 @@ static int xhci_mtk_probe(struct platform_device *pdev)
                ret = irq;
                goto disable_clk;
        }
+       dev_dbg(dev, "irq %i\n", irq);
+
+       mtk->seal_irq = platform_get_irq_optional(pdev, 1);
+       if (mtk->seal_irq < 0) {
+               ret = mtk->seal_irq;
+               goto disable_clk;
+       }
+       dev_dbg(dev, "seal_irq %i\n", mtk->seal_irq);
 
        hcd = usb_create_hcd(driver, dev, dev_name(dev));
        if (!hcd) {
@@ -562,6 +794,31 @@ static int xhci_mtk_probe(struct platform_device *pdev)
        if (ret)
                goto dealloc_usb2_hcd;
 
+       INIT_DELAYED_WORK(&mtk->seal, xhci_mtk_seal_work);
+       snprintf(mtk->seal_descr, sizeof(mtk->seal_descr), "seal%s:usb%d",
+                hcd->driver->description, hcd->self.busnum);
+       ret = devm_request_irq(mtk->seal_irq, &xhci_mtk_seal_irq,
+                         IRQF_TRIGGER_FALLING, mtk->seal_descr, mtk);
+       if (ret != 0) {
+               dev_err(dev, "seal request interrupt %d failed\n",
+                       mtk->seal_irq);
+               goto dealloc_usb2_hcd;
+       }
+       xhci_mtk_seal_wakeup_enable(mtk, false);
+
+       device_enable_async_suspend(dev);
+       xhci_mtk_runtime_ready = 1;
+
+       ret = add_power_attributes(dev);
+       if (ret)
+               goto dealloc_usb2_hcd;
+
+       pm_runtime_mark_last_busy(dev);
+       pm_runtime_put_autosuspend(dev);
+
+       dev_dbg(dev, "%s: xhci_mtk_runtime_ready %i",
+                __func__, xhci_mtk_runtime_ready);
+
        return 0;
 
 dealloc_usb2_hcd:
@@ -584,7 +841,7 @@ static int xhci_mtk_probe(struct platform_device *pdev)
        xhci_mtk_ldos_disable(mtk);
 
 disable_pm:
-       pm_runtime_put_sync(dev);
+       pm_runtime_put_sync_autosuspend(dev);
        pm_runtime_disable(dev);
        return ret;
 }
@@ -598,7 +855,9 @@ static int xhci_mtk_remove(struct platform_device *dev)
 
        pm_runtime_put_noidle(&dev->dev);
        pm_runtime_disable(&dev->dev);
+       remove_power_attributes(&dev->dev);
 
+       xhci_mtk_runtime_ready = 0;
        usb_remove_hcd(shared_hcd);
        xhci->shared_hcd = NULL;
        device_init_wakeup(&dev->dev, false);
@@ -635,6 +894,7 @@ static int __maybe_unused xhci_mtk_suspend(struct device 
*dev)
        xhci_mtk_host_disable(mtk);
        xhci_mtk_clks_disable(mtk);
        usb_wakeup_set(mtk, true);
+
        return 0;
 }
 
@@ -656,10 +916,185 @@ static int __maybe_unused xhci_mtk_resume(struct device 
*dev)
        return 0;
 }
 
+static int __maybe_unused xhci_mtk_bus_status(struct device *dev)
+{
+       struct xhci_hcd_mtk *mtk = dev_get_drvdata(dev);
+       struct usb_hcd *hcd;
+       struct xhci_hcd *xhci;
+       struct xhci_hub *usb2_rhub;
+       struct xhci_hub *usb3_rhub;
+       struct xhci_bus_state *bus_state;
+       struct xhci_port *port;
+       u32     usb2_suspended_ports = -1;
+       u32     usb3_suspended_ports = -1;
+       u16 status;
+       int num_ports;
+       int ret = 0;
+       int i;
+
+       if (!mtk->hcd)
+               return -ESHUTDOWN;
+
+       hcd = mtk->hcd;
+       xhci = hcd_to_xhci(hcd);
+       if ((xhci->xhc_state & XHCI_STATE_REMOVING) ||
+                       (xhci->xhc_state & XHCI_STATE_HALTED)) {
+               return -ESHUTDOWN;
+       }
+
+       usb2_rhub = &xhci->usb2_rhub;
+       if (usb2_rhub) {
+               bus_state  = &usb2_rhub->bus_state;
+               num_ports  = usb2_rhub->num_ports;
+               usb2_suspended_ports  = bus_state->suspended_ports;
+               usb2_suspended_ports ^= (BIT(num_ports) - 1);
+               usb2_suspended_ports &= (BIT(num_ports) - 1);
+               for (i = 0; i < num_ports; i++) {
+                       if (usb2_suspended_ports & (1UL << i)) {
+                               port = usb2_rhub->ports[i];
+                               status = readl(port->addr);
+
+                               xhci_dbg(xhci,
+                                         "USB20: portsc[%i]: 0x%04X\n",
+                                         i, status);
+
+                               if (!(status & PORT_CONNECT))
+                                       usb2_suspended_ports &= ~(1UL << i);
+                       }
+               }
+
+               if (usb2_suspended_ports) {
+                       ret = -EBUSY;
+                       goto ebusy;
+               }
+       }
+
+       usb3_rhub = &xhci->usb3_rhub;
+       if (usb3_rhub) {
+               bus_state  = &usb3_rhub->bus_state;
+               num_ports  = usb3_rhub->num_ports;
+               usb3_suspended_ports  = bus_state->suspended_ports;
+               usb3_suspended_ports ^= (BIT(num_ports) - 1);
+               usb3_suspended_ports &= (BIT(num_ports) - 1);
+               for (i = 0; i < num_ports; i++) {
+                       if (usb3_suspended_ports & BIT(i)) {
+                               port = usb3_rhub->ports[i];
+                               status = readl(port->addr);
+
+                               xhci_dbg(xhci, "USB3: portsc[%i]: 0x%04X\n",
+                                         i, status);
+
+                               if (!(status & PORT_CONNECT))
+                                       usb3_suspended_ports &= ~BIT(i);
+                       }
+               }
+
+               if (usb3_suspended_ports) {
+                       ret = -EBUSY;
+                       goto ebusy;
+               }
+       }
+
+ebusy:
+       xhci_dbg(xhci, "%s: USB2: 0x%08X, USB3: 0x%08X ret: %i\n",
+                 __func__, usb2_suspended_ports,
+                 usb3_suspended_ports, ret);
+
+       return ret;
+}
+
+static int __maybe_unused xhci_mtk_runtime_suspend(struct device *dev)
+{
+       bool wakeup = device_may_wakeup(dev);
+       struct xhci_hcd_mtk  *mtk = dev_get_drvdata(dev);
+       struct usb_hcd *hcd;
+       struct xhci_hcd *xhci;
+       int ret = 0;
+
+       if (!mtk->hcd)
+               return -ESHUTDOWN;
+
+       hcd = mtk->hcd;
+       xhci = hcd_to_xhci(hcd);
+       if ((xhci->xhc_state & XHCI_STATE_REMOVING) ||
+                       (xhci->xhc_state & XHCI_STATE_HALTED)) {
+               return -ESHUTDOWN;
+       }
+
+       mtk->seal_status = SEAL_BUSY;
+       ret = xhci_mtk_bus_status(dev);
+       if (wakeup && !ret) {
+               mtk->seal_status = SEAL_SUSPENDING;
+               xhci_mtk_suspend(dev);
+               xhci_mtk_seal_wakeup_enable(mtk, true);
+               mtk->seal_status = SEAL_SUSPENDED;
+               xhci_dbg(xhci, "%s: seals xHCI controller\n", __func__);
+       }
+
+       xhci_dbg(xhci, "%s: seals wakeup = %i, ret = %i!\n",
+                 __func__, wakeup, ret);
+
+       return ret;
+}
+
+static int __maybe_unused xhci_mtk_runtime_resume(struct device *dev)
+{
+       bool wakeup = device_may_wakeup(dev);
+       struct xhci_hcd_mtk  *mtk = dev_get_drvdata(dev);
+       struct usb_hcd *hcd;
+       struct xhci_hcd *xhci;
+
+       if (!mtk->hcd)
+               return -ESHUTDOWN;
+
+       hcd = mtk->hcd;
+       xhci = hcd_to_xhci(hcd);
+       if ((xhci->xhc_state & XHCI_STATE_REMOVING) ||
+                       (xhci->xhc_state & XHCI_STATE_HALTED)) {
+               return -ESHUTDOWN;
+       }
+
+       /*
+        *  list cases by one extra interrupt named seal to process!!!
+        *  Who to process these module reinitilization after SPM wakeup
+        *  case 1: usb remote wakeup, therefore xHCI need reinitilizate also.
+        *  case 2: other-wakeup-source wakeup, therefore, xHCI need reinit
+        *  case 3: usb client driver can invoke it in runtime mechanism
+        *  case 4: user active
+        */
+       if (wakeup) {
+               xhci_mtk_seal_wakeup_enable(mtk, false);
+               xhci_mtk_resume(dev);
+               xhci_dbg(xhci, "%s: unseals xHCI controller\n", __func__);
+       }
+       mtk->seal_status = SEAL_RESUMED;
+
+       xhci_dbg(xhci, "%s: unseals wakeup = %i\n", __func__, wakeup);
+
+       return 0;
+}
+
+static int __maybe_unused xhci_mtk_runtime_idle(struct device *dev)
+{
+       int ret = 0;
+
+       dev_dbg(dev, "%s: xhci_mtk_runtime_ready %i",
+                __func__, xhci_mtk_runtime_ready);
+
+       if (!xhci_mtk_runtime_ready)
+               ret = -EAGAIN;
+
+       return ret;
+}
+
 static const struct dev_pm_ops xhci_mtk_pm_ops = {
        SET_SYSTEM_SLEEP_PM_OPS(xhci_mtk_suspend, xhci_mtk_resume)
+       SET_RUNTIME_PM_OPS(xhci_mtk_runtime_suspend,
+                          xhci_mtk_runtime_resume,
+                          xhci_mtk_runtime_idle)
 };
-#define DEV_PM_OPS IS_ENABLED(CONFIG_PM) ? &xhci_mtk_pm_ops : NULL
+
+#define DEV_PM_OPS (IS_ENABLED(CONFIG_PM) ? &xhci_mtk_pm_ops : NULL)
 
 #ifdef CONFIG_OF
 static const struct of_device_id mtk_xhci_of_match[] = {
@@ -683,6 +1118,7 @@ static int __maybe_unused xhci_mtk_resume(struct device 
*dev)
 
 static int __init xhci_mtk_init(void)
 {
+       xhci_mtk_runtime_ready = 0;
        xhci_init_driver(&xhci_mtk_hc_driver, &xhci_mtk_overrides);
        return platform_driver_register(&mtk_xhci_driver);
 }
diff --git a/drivers/usb/host/xhci-mtk.h b/drivers/usb/host/xhci-mtk.h
old mode 100644
new mode 100755
index 323b281..103d83c
--- a/drivers/usb/host/xhci-mtk.h
+++ b/drivers/usb/host/xhci-mtk.h
@@ -133,6 +133,14 @@ struct mu3c_ippc_regs {
        __le32 reserved3[33]; /* 0x80 ~ 0xff */
 };
 
+enum xhci_mtk_seal {
+       SEAL_BUSY = 0,
+       SEAL_SUSPENDING,
+       SEAL_SUSPENDED,
+       SEAL_RESUMING,
+       SEAL_RESUMED
+};
+
 struct xhci_hcd_mtk {
        struct device *dev;
        struct usb_hcd *hcd;
@@ -158,6 +166,12 @@ struct xhci_hcd_mtk {
        struct regmap *uwk;
        u32 uwk_reg_base;
        u32 uwk_vers;
+
+       /* usb eint wakeup source */
+       int seal_irq;
+       enum xhci_mtk_seal seal_status;
+       struct delayed_work  seal;
+       char   seal_descr[32];  /* "seal" + driver + bus # */
 };
 
 static inline struct xhci_hcd_mtk *hcd_to_mtk(struct usb_hcd *hcd)
-- 
1.9.1

Reply via email to