From patchwork Thu Sep 3 03:34:24 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?b?Q2h1bmZlbmcgWXVuICjkupHmmKXls7Ap?= X-Patchwork-Id: 297744 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-11.4 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_PATCH, MAILING_LIST_MULTI, MIME_BASE64_TEXT, SIGNED_OFF_BY, SPF_HELO_NONE, SPF_PASS, UNPARSEABLE_RELAY, URIBL_BLOCKED, USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id E90ECC433E7 for ; Thu, 3 Sep 2020 03:36:32 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id A715B2072A for ; Thu, 3 Sep 2020 03:36:32 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (1024-bit key) header.d=mediatek.com header.i=@mediatek.com header.b="heb4dxxe" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1728148AbgICDgQ (ORCPT ); Wed, 2 Sep 2020 23:36:16 -0400 Received: from mailgw02.mediatek.com ([210.61.82.184]:19679 "EHLO mailgw02.mediatek.com" rhost-flags-OK-FAIL-OK-FAIL) by vger.kernel.org with ESMTP id S1726654AbgICDgQ (ORCPT ); Wed, 2 Sep 2020 23:36:16 -0400 X-UUID: aaae416ddfaa48a4b3bf233abbf74ca3-20200903 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=mediatek.com; s=dk; h=Content-Transfer-Encoding:Content-Type:MIME-Version:References:In-Reply-To:Message-ID:Date:Subject:CC:To:From; bh=v62vx7BQXBuQ8iLkvvwnyloq7dIOaiC438EIIeAW3M8=; b=heb4dxxe9apUBq51GplHEzMNj4I2M299XdfP8tlqoRs0Ayu1LxK39uol3yINZt3MoUL9MCir5AIJnIATTI9IreTySrikhoxOz50Bu44/0eR8Clb0zC/LUA7IBM2TxPwxK+w0dLhKODwuq97j0ckql4dIMkwIiJDkMuUZPJxHb9w=; X-UUID: aaae416ddfaa48a4b3bf233abbf74ca3-20200903 Received: from mtkcas07.mediatek.inc [(172.21.101.84)] by mailgw02.mediatek.com (envelope-from ) (Cellopoint E-mail Firewall v4.1.14 Build 0819 with TLSv1.2 ECDHE-RSA-AES256-SHA384 256/256) with ESMTP id 1559179262; Thu, 03 Sep 2020 11:36:11 +0800 Received: from MTKCAS06.mediatek.inc (172.21.101.30) by mtkmbs05n1.mediatek.inc (172.21.101.15) with Microsoft SMTP Server (TLS) id 15.0.1497.2; Thu, 3 Sep 2020 11:36:09 +0800 Received: from localhost.localdomain (10.17.3.153) by MTKCAS06.mediatek.inc (172.21.101.73) with Microsoft SMTP Server id 15.0.1497.2 via Frontend Transport; Thu, 3 Sep 2020 11:36:09 +0800 From: Chunfeng Yun To: Zhanyong Wang CC: , , , , CK Hu , Chunfeng Yun Subject: [RFC PATCH 3/4] usb: xhci-mtk: add support runtime pm Date: Thu, 3 Sep 2020 11:34:24 +0800 Message-ID: <1599104065-8009-3-git-send-email-chunfeng.yun@mediatek.com> X-Mailer: git-send-email 1.8.1.1.dirty In-Reply-To: <1599104065-8009-1-git-send-email-chunfeng.yun@mediatek.com> References: <1599104065-8009-1-git-send-email-chunfeng.yun@mediatek.com> MIME-Version: 1.0 X-MTK: N Sender: linux-usb-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org From: CK Hu add support runtime pm feature Signed-off-by: Zhanyong Wang Signed-off-by: Chunfeng Yun --- 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 -- 1.9.1 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 #include #include +#include #include #include #include @@ -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)