diff options
Diffstat (limited to 'drivers/usb/host/xhci-mtk.c')
| -rw-r--r-- | drivers/usb/host/xhci-mtk.c | 299 | 
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)  { | 
