summaryrefslogtreecommitdiff
path: root/drivers/usb/host/xhci-mtk.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/host/xhci-mtk.c')
-rw-r--r--drivers/usb/host/xhci-mtk.c299
1 files changed, 173 insertions, 126 deletions
diff --git a/drivers/usb/host/xhci-mtk.c b/drivers/usb/host/xhci-mtk.c
index fe010cc61f19..b2058b3bc834 100644
--- a/drivers/usb/host/xhci-mtk.c
+++ b/drivers/usb/host/xhci-mtk.c
@@ -7,7 +7,6 @@
* Chunfeng Yun <chunfeng.yun@mediatek.com>
*/
-#include <linux/clk.h>
#include <linux/dma-mapping.h>
#include <linux/iopoll.h>
#include <linux/kernel.h>
@@ -16,6 +15,7 @@
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
+#include <linux/pm_wakeirq.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
@@ -57,12 +57,23 @@
#define CTRL_U2_FORCE_PLL_STB BIT(28)
/* usb remote wakeup registers in syscon */
+
/* mt8173 etc */
#define PERI_WK_CTRL1 0x4
#define WC1_IS_C(x) (((x) & 0xf) << 26) /* cycle debounce */
#define WC1_IS_EN BIT(25)
#define WC1_IS_P BIT(6) /* polarity for ip sleep */
+/* mt8183 */
+#define PERI_WK_CTRL0 0x0
+#define WC0_IS_C(x) ((u32)(((x) & 0xf) << 28)) /* cycle debounce */
+#define WC0_IS_P BIT(12) /* polarity */
+#define WC0_IS_EN BIT(6)
+
+/* mt8192 */
+#define WC0_SSUSB0_CDEN BIT(6)
+#define WC0_IS_SPM_EN BIT(1)
+
/* mt2712 etc */
#define PERI_SSUSB_SPM_CTRL 0x0
#define SSC_IP_SLEEP_EN BIT(4)
@@ -71,6 +82,8 @@
enum ssusb_uwk_vers {
SSUSB_UWK_V1 = 1,
SSUSB_UWK_V2,
+ SSUSB_UWK_V1_1 = 101, /* specific revision 1.01 */
+ SSUSB_UWK_V1_2, /* specific revision 1.2 */
};
static int xhci_mtk_host_enable(struct xhci_hcd_mtk *mtk)
@@ -206,89 +219,6 @@ static int xhci_mtk_ssusb_config(struct xhci_hcd_mtk *mtk)
return xhci_mtk_host_enable(mtk);
}
-static int xhci_mtk_clks_get(struct xhci_hcd_mtk *mtk)
-{
- struct device *dev = mtk->dev;
-
- mtk->sys_clk = devm_clk_get(dev, "sys_ck");
- if (IS_ERR(mtk->sys_clk)) {
- dev_err(dev, "fail to get sys_ck\n");
- return PTR_ERR(mtk->sys_clk);
- }
-
- mtk->xhci_clk = devm_clk_get_optional(dev, "xhci_ck");
- if (IS_ERR(mtk->xhci_clk))
- return PTR_ERR(mtk->xhci_clk);
-
- mtk->ref_clk = devm_clk_get_optional(dev, "ref_ck");
- if (IS_ERR(mtk->ref_clk))
- return PTR_ERR(mtk->ref_clk);
-
- mtk->mcu_clk = devm_clk_get_optional(dev, "mcu_ck");
- if (IS_ERR(mtk->mcu_clk))
- return PTR_ERR(mtk->mcu_clk);
-
- mtk->dma_clk = devm_clk_get_optional(dev, "dma_ck");
- return PTR_ERR_OR_ZERO(mtk->dma_clk);
-}
-
-static int xhci_mtk_clks_enable(struct xhci_hcd_mtk *mtk)
-{
- int ret;
-
- ret = clk_prepare_enable(mtk->ref_clk);
- if (ret) {
- dev_err(mtk->dev, "failed to enable ref_clk\n");
- goto ref_clk_err;
- }
-
- ret = clk_prepare_enable(mtk->sys_clk);
- if (ret) {
- dev_err(mtk->dev, "failed to enable sys_clk\n");
- goto sys_clk_err;
- }
-
- ret = clk_prepare_enable(mtk->xhci_clk);
- if (ret) {
- dev_err(mtk->dev, "failed to enable xhci_clk\n");
- goto xhci_clk_err;
- }
-
- ret = clk_prepare_enable(mtk->mcu_clk);
- if (ret) {
- dev_err(mtk->dev, "failed to enable mcu_clk\n");
- goto mcu_clk_err;
- }
-
- ret = clk_prepare_enable(mtk->dma_clk);
- if (ret) {
- dev_err(mtk->dev, "failed to enable dma_clk\n");
- goto dma_clk_err;
- }
-
- return 0;
-
-dma_clk_err:
- clk_disable_unprepare(mtk->mcu_clk);
-mcu_clk_err:
- clk_disable_unprepare(mtk->xhci_clk);
-xhci_clk_err:
- clk_disable_unprepare(mtk->sys_clk);
-sys_clk_err:
- clk_disable_unprepare(mtk->ref_clk);
-ref_clk_err:
- return ret;
-}
-
-static void xhci_mtk_clks_disable(struct xhci_hcd_mtk *mtk)
-{
- clk_disable_unprepare(mtk->dma_clk);
- clk_disable_unprepare(mtk->mcu_clk);
- clk_disable_unprepare(mtk->xhci_clk);
- clk_disable_unprepare(mtk->sys_clk);
- clk_disable_unprepare(mtk->ref_clk);
-}
-
/* only clocks can be turn off for ip-sleep wakeup mode */
static void usb_wakeup_ip_sleep_set(struct xhci_hcd_mtk *mtk, bool enable)
{
@@ -300,6 +230,16 @@ static void usb_wakeup_ip_sleep_set(struct xhci_hcd_mtk *mtk, bool enable)
msk = WC1_IS_EN | WC1_IS_C(0xf) | WC1_IS_P;
val = enable ? (WC1_IS_EN | WC1_IS_C(0x8)) : 0;
break;
+ case SSUSB_UWK_V1_1:
+ reg = mtk->uwk_reg_base + PERI_WK_CTRL0;
+ msk = WC0_IS_EN | WC0_IS_C(0xf) | WC0_IS_P;
+ val = enable ? (WC0_IS_EN | WC0_IS_C(0x8)) : 0;
+ break;
+ case SSUSB_UWK_V1_2:
+ reg = mtk->uwk_reg_base + PERI_WK_CTRL0;
+ msk = WC0_SSUSB0_CDEN | WC0_IS_SPM_EN;
+ val = enable ? msk : 0;
+ break;
case SSUSB_UWK_V2:
reg = mtk->uwk_reg_base + PERI_SSUSB_SPM_CTRL;
msk = SSC_IP_SLEEP_EN | SSC_SPM_INT_EN;
@@ -335,7 +275,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)
@@ -344,14 +283,18 @@ static void usb_wakeup_set(struct xhci_hcd_mtk *mtk, bool enable)
usb_wakeup_ip_sleep_set(mtk, enable);
}
-static int xhci_mtk_setup(struct usb_hcd *hcd);
-static const struct xhci_driver_overrides xhci_mtk_overrides __initconst = {
- .reset = xhci_mtk_setup,
- .check_bandwidth = xhci_mtk_check_bandwidth,
- .reset_bandwidth = xhci_mtk_reset_bandwidth,
-};
+static int xhci_mtk_clks_get(struct xhci_hcd_mtk *mtk)
+{
+ struct clk_bulk_data *clks = mtk->clks;
-static struct hc_driver __read_mostly xhci_mtk_hc_driver;
+ clks[0].id = "sys_ck";
+ clks[1].id = "xhci_ck";
+ clks[2].id = "ref_ck";
+ clks[3].id = "mcu_ck";
+ clks[4].id = "dma_ck";
+
+ return devm_clk_bulk_get_optional(mtk->dev, BULK_CLKS_NUM, clks);
+}
static int xhci_mtk_ldos_enable(struct xhci_hcd_mtk *mtk)
{
@@ -397,6 +340,15 @@ static void xhci_mtk_quirks(struct device *dev, struct xhci_hcd *xhci)
xhci->quirks |= XHCI_SPURIOUS_SUCCESS;
if (mtk->lpm_support)
xhci->quirks |= XHCI_LPM_SUPPORT;
+ if (mtk->u2_lpm_disable)
+ xhci->quirks |= XHCI_HW_LPM_DISABLE;
+
+ /*
+ * MTK xHCI 0.96: PSA is 1 by default even if doesn't support stream,
+ * and it's 3 when support it.
+ */
+ if (xhci->hci_version < 0x100 && HCC_MAX_PSA(xhci->hcc_params) == 4)
+ xhci->quirks |= XHCI_BROKEN_STREAMS;
}
/* called during probe() after chip reset completes */
@@ -424,6 +376,16 @@ static int xhci_mtk_setup(struct usb_hcd *hcd)
return ret;
}
+static const struct xhci_driver_overrides xhci_mtk_overrides __initconst = {
+ .reset = xhci_mtk_setup,
+ .add_endpoint = xhci_mtk_add_ep,
+ .drop_endpoint = xhci_mtk_drop_ep,
+ .check_bandwidth = xhci_mtk_check_bandwidth,
+ .reset_bandwidth = xhci_mtk_reset_bandwidth,
+};
+
+static struct hc_driver __read_mostly xhci_mtk_hc_driver;
+
static int xhci_mtk_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -434,6 +396,7 @@ static int xhci_mtk_probe(struct platform_device *pdev)
struct resource *res;
struct usb_hcd *hcd;
int ret = -ENODEV;
+ int wakeup_irq;
int irq;
if (usb_disabled())
@@ -461,7 +424,23 @@ static int xhci_mtk_probe(struct platform_device *pdev)
if (ret)
return ret;
+ irq = platform_get_irq_byname_optional(pdev, "host");
+ if (irq < 0) {
+ if (irq == -EPROBE_DEFER)
+ return irq;
+
+ /* for backward compatibility */
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+ }
+
+ wakeup_irq = platform_get_irq_byname_optional(pdev, "wakeup");
+ if (wakeup_irq == -EPROBE_DEFER)
+ return wakeup_irq;
+
mtk->lpm_support = of_property_read_bool(node, "usb3-lpm-capable");
+ mtk->u2_lpm_disable = of_property_read_bool(node, "usb2-lpm-disable");
/* optional property, ignore the error if it does not exist */
of_property_read_u32(node, "mediatek,u3p-dis-msk",
&mtk->u3p_dis_msk);
@@ -472,24 +451,20 @@ 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, 4000);
pm_runtime_enable(dev);
pm_runtime_get_sync(dev);
- device_enable_async_suspend(dev);
ret = xhci_mtk_ldos_enable(mtk);
if (ret)
goto disable_pm;
- ret = xhci_mtk_clks_enable(mtk);
+ ret = clk_bulk_prepare_enable(BULK_CLKS_NUM, mtk->clks);
if (ret)
goto disable_ldos;
- irq = platform_get_irq(pdev, 0);
- if (irq < 0) {
- ret = irq;
- goto disable_clk;
- }
-
hcd = usb_create_hcd(driver, dev, dev_name(dev));
if (!hcd) {
ret = -ENOMEM;
@@ -548,15 +523,34 @@ static int xhci_mtk_probe(struct platform_device *pdev)
if (ret)
goto put_usb3_hcd;
- if (HCC_MAX_PSA(xhci->hcc_params) >= 4)
+ if (HCC_MAX_PSA(xhci->hcc_params) >= 4 &&
+ !(xhci->quirks & XHCI_BROKEN_STREAMS))
xhci->shared_hcd->can_do_streams = 1;
ret = usb_add_hcd(xhci->shared_hcd, irq, IRQF_SHARED);
if (ret)
goto dealloc_usb2_hcd;
+ if (wakeup_irq > 0) {
+ ret = dev_pm_set_dedicated_wake_irq(dev, wakeup_irq);
+ if (ret) {
+ dev_err(dev, "set wakeup irq %d failed\n", wakeup_irq);
+ goto dealloc_usb3_hcd;
+ }
+ dev_info(dev, "wakeup irq %d\n", wakeup_irq);
+ }
+
+ device_enable_async_suspend(dev);
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+ pm_runtime_forbid(dev);
+
return 0;
+dealloc_usb3_hcd:
+ usb_remove_hcd(xhci->shared_hcd);
+ xhci->shared_hcd = NULL;
+
dealloc_usb2_hcd:
usb_remove_hcd(hcd);
@@ -571,53 +565,52 @@ put_usb2_hcd:
usb_put_hcd(hcd);
disable_clk:
- xhci_mtk_clks_disable(mtk);
+ clk_bulk_disable_unprepare(BULK_CLKS_NUM, mtk->clks);
disable_ldos:
xhci_mtk_ldos_disable(mtk);
disable_pm:
- pm_runtime_put_sync(dev);
+ pm_runtime_put_sync_autosuspend(dev);
pm_runtime_disable(dev);
return ret;
}
-static int xhci_mtk_remove(struct platform_device *dev)
+static int xhci_mtk_remove(struct platform_device *pdev)
{
- struct xhci_hcd_mtk *mtk = platform_get_drvdata(dev);
+ struct xhci_hcd_mtk *mtk = platform_get_drvdata(pdev);
struct usb_hcd *hcd = mtk->hcd;
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
struct usb_hcd *shared_hcd = xhci->shared_hcd;
+ struct device *dev = &pdev->dev;
- pm_runtime_put_noidle(&dev->dev);
- pm_runtime_disable(&dev->dev);
+ pm_runtime_get_sync(dev);
+ xhci->xhc_state |= XHCI_STATE_REMOVING;
+ dev_pm_clear_wake_irq(dev);
+ device_init_wakeup(dev, false);
usb_remove_hcd(shared_hcd);
xhci->shared_hcd = NULL;
- device_init_wakeup(&dev->dev, false);
-
usb_remove_hcd(hcd);
usb_put_hcd(shared_hcd);
usb_put_hcd(hcd);
xhci_mtk_sch_exit(mtk);
- xhci_mtk_clks_disable(mtk);
+ clk_bulk_disable_unprepare(BULK_CLKS_NUM, mtk->clks);
xhci_mtk_ldos_disable(mtk);
+ pm_runtime_disable(dev);
+ pm_runtime_put_noidle(dev);
+ pm_runtime_set_suspended(dev);
+
return 0;
}
-/*
- * if ip sleep fails, and all clocks are disabled, access register will hang
- * AHB bus, so stop polling roothubs to avoid regs access on bus suspend.
- * and no need to check whether ip sleep failed or not; this will cause SPM
- * to wake up system immediately after system suspend complete if ip sleep
- * fails, it is what we wanted.
- */
static int __maybe_unused xhci_mtk_suspend(struct device *dev)
{
struct xhci_hcd_mtk *mtk = dev_get_drvdata(dev);
struct usb_hcd *hcd = mtk->hcd;
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+ int ret;
xhci_dbg(xhci, "%s: stop port polling\n", __func__);
clear_bit(HCD_FLAG_POLL_RH, &hcd->flags);
@@ -625,10 +618,21 @@ static int __maybe_unused xhci_mtk_suspend(struct device *dev)
clear_bit(HCD_FLAG_POLL_RH, &xhci->shared_hcd->flags);
del_timer_sync(&xhci->shared_hcd->rh_timer);
- xhci_mtk_host_disable(mtk);
- xhci_mtk_clks_disable(mtk);
+ ret = xhci_mtk_host_disable(mtk);
+ if (ret)
+ goto restart_poll_rh;
+
+ clk_bulk_disable_unprepare(BULK_CLKS_NUM, mtk->clks);
usb_wakeup_set(mtk, true);
return 0;
+
+restart_poll_rh:
+ xhci_dbg(xhci, "%s: restart port polling\n", __func__);
+ set_bit(HCD_FLAG_POLL_RH, &xhci->shared_hcd->flags);
+ usb_hcd_poll_rh_status(xhci->shared_hcd);
+ set_bit(HCD_FLAG_POLL_RH, &hcd->flags);
+ usb_hcd_poll_rh_status(hcd);
+ return ret;
}
static int __maybe_unused xhci_mtk_resume(struct device *dev)
@@ -636,10 +640,16 @@ static int __maybe_unused xhci_mtk_resume(struct device *dev)
struct xhci_hcd_mtk *mtk = dev_get_drvdata(dev);
struct usb_hcd *hcd = mtk->hcd;
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+ int ret;
usb_wakeup_set(mtk, false);
- xhci_mtk_clks_enable(mtk);
- xhci_mtk_host_enable(mtk);
+ ret = clk_bulk_prepare_enable(BULK_CLKS_NUM, mtk->clks);
+ if (ret)
+ goto enable_wakeup;
+
+ ret = xhci_mtk_host_enable(mtk);
+ if (ret)
+ goto disable_clks;
xhci_dbg(xhci, "%s: restart port polling\n", __func__);
set_bit(HCD_FLAG_POLL_RH, &xhci->shared_hcd->flags);
@@ -647,21 +657,59 @@ static int __maybe_unused xhci_mtk_resume(struct device *dev)
set_bit(HCD_FLAG_POLL_RH, &hcd->flags);
usb_hcd_poll_rh_status(hcd);
return 0;
+
+disable_clks:
+ clk_bulk_disable_unprepare(BULK_CLKS_NUM, mtk->clks);
+enable_wakeup:
+ usb_wakeup_set(mtk, true);
+ return ret;
+}
+
+static int __maybe_unused xhci_mtk_runtime_suspend(struct device *dev)
+{
+ struct xhci_hcd_mtk *mtk = dev_get_drvdata(dev);
+ struct xhci_hcd *xhci = hcd_to_xhci(mtk->hcd);
+ int ret = 0;
+
+ if (xhci->xhc_state)
+ return -ESHUTDOWN;
+
+ if (device_may_wakeup(dev))
+ ret = xhci_mtk_suspend(dev);
+
+ /* -EBUSY: let PM automatically reschedule another autosuspend */
+ return ret ? -EBUSY : 0;
+}
+
+static int __maybe_unused xhci_mtk_runtime_resume(struct device *dev)
+{
+ struct xhci_hcd_mtk *mtk = dev_get_drvdata(dev);
+ struct xhci_hcd *xhci = hcd_to_xhci(mtk->hcd);
+ int ret = 0;
+
+ if (xhci->xhc_state)
+ return -ESHUTDOWN;
+
+ if (device_may_wakeup(dev))
+ ret = xhci_mtk_resume(dev);
+
+ 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, NULL)
};
-#define DEV_PM_OPS IS_ENABLED(CONFIG_PM) ? &xhci_mtk_pm_ops : NULL
-#ifdef CONFIG_OF
+#define DEV_PM_OPS (IS_ENABLED(CONFIG_PM) ? &xhci_mtk_pm_ops : NULL)
+
static const struct of_device_id mtk_xhci_of_match[] = {
{ .compatible = "mediatek,mt8173-xhci"},
{ .compatible = "mediatek,mtk-xhci"},
{ },
};
MODULE_DEVICE_TABLE(of, mtk_xhci_of_match);
-#endif
static struct platform_driver mtk_xhci_driver = {
.probe = xhci_mtk_probe,
@@ -669,10 +717,9 @@ static struct platform_driver mtk_xhci_driver = {
.driver = {
.name = "xhci-mtk",
.pm = DEV_PM_OPS,
- .of_match_table = of_match_ptr(mtk_xhci_of_match),
+ .of_match_table = mtk_xhci_of_match,
},
};
-MODULE_ALIAS("platform:xhci-mtk");
static int __init xhci_mtk_init(void)
{