Hi > -----Original Message----- > From: Peter Chen <peter.chen@xxxxxxx> > Sent: 2020年5月24日 7:23 > To: balbi@xxxxxxxxxx; mathias.nyman@xxxxxxxxx > Cc: linux-usb@xxxxxxxxxxxxxxx; dl-linux-imx <linux-imx@xxxxxxx>; > pawell@xxxxxxxxxxx; rogerq@xxxxxx; gregkh@xxxxxxxxxxxxxxxxxxx; Jun Li > <jun.li@xxxxxxx>; Peter Chen <peter.chen@xxxxxxx> > Subject: [PATCH v2 3/9] usb: cdns3: imx: add glue layer runtime pm implementation > > Add imx glue layer runtime pm implementation, and the runtime pm is default off. > > Signed-off-by: Peter Chen <peter.chen@xxxxxxx> > --- > drivers/usb/cdns3/cdns3-imx.c | 179 +++++++++++++++++++++++++++++++++- > 1 file changed, 177 insertions(+), 2 deletions(-) > > diff --git a/drivers/usb/cdns3/cdns3-imx.c b/drivers/usb/cdns3/cdns3-imx.c index > aba988e71958..9d247de8e7eb 100644 > --- a/drivers/usb/cdns3/cdns3-imx.c > +++ b/drivers/usb/cdns3/cdns3-imx.c > @@ -15,6 +15,8 @@ > #include <linux/io.h> > #include <linux/of_platform.h> > #include <linux/iopoll.h> > +#include <linux/pm_runtime.h> > +#include "core.h" > > #define USB3_CORE_CTRL1 0x00 > #define USB3_CORE_CTRL2 0x04 > @@ -66,11 +68,30 @@ > #define CLK_VALID_COMPARE_BITS (0xf << 28) GENMASK(31, 28) > #define PHY_REFCLK_REQ (1 << 0) BIT(0), also for others > > +/* OTG registers definition */ > +#define OTGSTS 0x4 > +/* OTGSTS */ > +#define OTG_NRDY (1 << 11) > + > +/* xHCI registers definition */ > +#define XECP_PM_PMCSR 0x8018 > +#define XECP_AUX_CTRL_REG1 0x8120 > + > +/* Register bits definition */ > +/* XECP_AUX_CTRL_REG1 */ > +#define CFG_RXDET_P3_EN (1 << 15) > + > +/* XECP_PM_PMCSR */ > +#define PS_MASK (3 << 0) > +#define PS_D0 0 > +#define PS_D1 (1 << 0) > + > struct cdns_imx { > struct device *dev; > void __iomem *noncore; > struct clk_bulk_data *clks; > int num_clks; > + struct platform_device *cdns3_pdev; > }; > > static inline u32 cdns_imx_readl(struct cdns_imx *data, u32 offset) @@ -126,6 > +147,20 @@ static int cdns_imx_noncore_init(struct cdns_imx *data) > return ret; > } > > +static int cdns_imx_platform_suspend(struct device *dev, > + bool suspend, bool wakeup); > +static struct cdns3_platform_data cdns_imx_pdata = { > + .platform_suspend = cdns_imx_platform_suspend, }; > + > +static struct of_dev_auxdata cdns_imx_auxdata[] = { > + { > + .compatible = "cdns,usb3", > + .platform_data = &cdns_imx_pdata, > + }, > + {}, > +}; > + > static int cdns_imx_probe(struct platform_device *pdev) { > struct device *dev = &pdev->dev; > @@ -162,14 +197,18 @@ static int cdns_imx_probe(struct platform_device *pdev) > if (ret) > goto err; > > - ret = of_platform_populate(node, NULL, NULL, dev); > + ret = of_platform_populate(node, NULL, cdns_imx_auxdata, dev); > if (ret) { > dev_err(dev, "failed to create children: %d\n", ret); > goto err; > } > > - return ret; > + device_set_wakeup_capable(dev, true); > + pm_runtime_set_active(dev); > + pm_runtime_enable(dev); > + pm_runtime_forbid(dev); > > + return ret; > err: > clk_bulk_disable_unprepare(data->num_clks, data->clks); > return ret; > @@ -194,6 +233,141 @@ static int cdns_imx_remove(struct platform_device *pdev) > return 0; > } > > +#ifdef CONFIG_PM > +static void cdns3_set_wakeup(struct cdns_imx *data, bool enable) { > + u32 value; > + > + value = cdns_imx_readl(data, USB3_INT_REG); > + if (enable) > + value |= OTG_WAKEUP_EN | DEVU3_WAEKUP_EN; > + else > + value &= ~(OTG_WAKEUP_EN | DEVU3_WAEKUP_EN); > + > + cdns_imx_writel(data, USB3_INT_REG, value); } > + > +static int cdns_imx_platform_suspend(struct device *dev, > + bool suspend, bool wakeup) > +{ > + struct cdns3 *cdns = dev_get_drvdata(dev); > + struct device *parent = dev->parent; > + struct cdns_imx *data = dev_get_drvdata(parent); > + void __iomem *otg_regs = cdns->otg_regs; > + void __iomem *xhci_regs = cdns->xhci_regs; > + u32 value; > + int ret = 0; > + > + if (cdns->role != USB_ROLE_HOST) > + return 0; > + > + if (suspend) { > + /* SW request low power when all usb ports allow to it ??? */ > + value = readl(xhci_regs + XECP_PM_PMCSR); > + value &= ~PS_MASK; > + value |= PS_D1; > + writel(value, xhci_regs + XECP_PM_PMCSR); > + > + /* mdctrl_clk_sel */ > + value = cdns_imx_readl(data, USB3_CORE_CTRL1); > + value |= MDCTRL_CLK_SEL; > + cdns_imx_writel(data, USB3_CORE_CTRL1, value); > + > + /* wait for mdctrl_clk_status */ > + value = cdns_imx_readl(data, USB3_CORE_STATUS); > + ret = readl_poll_timeout_atomic(data->noncore + USB3_CORE_STATUS, value, > + (value & MDCTRL_CLK_STATUS) == MDCTRL_CLK_STATUS, > + 10, 100000); > + if (ret) > + dev_warn(parent, "wait mdctrl_clk_status timeout\n"); > + > + /* wait lpm_clk_req to be 0 */ > + value = cdns_imx_readl(data, USB3_INT_REG); > + ret = readl_poll_timeout_atomic(data->noncore + USB3_INT_REG, value, > + (value & LPM_CLK_REQ) != LPM_CLK_REQ, > + 10, 100000); > + if (ret) > + dev_warn(parent, "wait lpm_clk_req timeout\n"); > + > + /* wait phy_refclk_req to be 0 */ > + value = cdns_imx_readl(data, USB3_SSPHY_STATUS); > + ret = readl_poll_timeout_atomic(data->noncore + USB3_SSPHY_STATUS, value, > + (value & PHY_REFCLK_REQ) != PHY_REFCLK_REQ, > + 10, 100000); > + if (ret) > + dev_warn(parent, "wait phy_refclk_req timeout\n"); > + > + cdns3_set_wakeup(data, wakeup); > + } else { > + /* wait CLK_125_REQ to be 1 */ > + value = cdns_imx_readl(data, USB3_INT_REG); > + ret = readl_poll_timeout_atomic(data->noncore + USB3_INT_REG, value, > + (value & CLK_125_REQ) != CLK_125_REQ, > + 10, 100000); > + if (ret) > + dev_warn(parent, "wait CLK_125_REQ timeout\n"); > + > + cdns3_set_wakeup(data, false); > + > + /* SW request D0 */ > + value = readl(xhci_regs + XECP_PM_PMCSR); > + value &= ~PS_MASK; > + value |= PS_D0; > + writel(value, xhci_regs + XECP_PM_PMCSR); > + > + /* clr CFG_RXDET_P3_EN */ > + value = readl(xhci_regs + XECP_AUX_CTRL_REG1); > + value &= ~CFG_RXDET_P3_EN; > + writel(value, xhci_regs + XECP_AUX_CTRL_REG1); > + > + /* clear mdctrl_clk_sel */ > + value = cdns_imx_readl(data, USB3_CORE_CTRL1); > + value &= ~MDCTRL_CLK_SEL; > + cdns_imx_writel(data, USB3_CORE_CTRL1, value); > + > + /* wait for mdctrl_clk_status is cleared */ > + value = cdns_imx_readl(data, USB3_CORE_STATUS); > + ret = readl_poll_timeout_atomic(data->noncore + USB3_CORE_STATUS, value, > + (value & MDCTRL_CLK_STATUS) != MDCTRL_CLK_STATUS, > + 10, 100000); > + if (ret) > + dev_warn(parent, "wait mdctrl_clk_status cleared timeout\n"); > + > + /* Wait until OTG_NRDY is 0 */ > + value = readl(otg_regs + OTGSTS); > + ret = readl_poll_timeout_atomic(otg_regs + OTGSTS, value, > + (value & OTG_NRDY) != OTG_NRDY, > + 10, 100000); > + if (ret) > + dev_warn(parent, "wait OTG ready timeout\n"); Make sense to move forward if any of above timeout happens? Li Jun > + } > + > + return ret; > + > +} > + > +static int cdns_imx_resume(struct device *dev) { > + struct cdns_imx *data = dev_get_drvdata(dev); > + > + return clk_bulk_prepare_enable(data->num_clks, data->clks); } > + > +static int cdns_imx_suspend(struct device *dev) { > + struct cdns_imx *data = dev_get_drvdata(dev); > + > + clk_bulk_disable_unprepare(data->num_clks, data->clks); > + > + return 0; > +} > + > +#endif /* CONFIG_PM */ > + > +static const struct dev_pm_ops cdns_imx_pm_ops = { > + SET_RUNTIME_PM_OPS(cdns_imx_suspend, cdns_imx_resume, NULL) }; > + > static const struct of_device_id cdns_imx_of_match[] = { > { .compatible = "fsl,imx8qm-usb3", }, > {}, > @@ -206,6 +380,7 @@ static struct platform_driver cdns_imx_driver = { > .driver = { > .name = "cdns3-imx", > .of_match_table = cdns_imx_of_match, > + .pm = &cdns_imx_pm_ops, > }, > }; > module_platform_driver(cdns_imx_driver); > -- > 2.17.1