diff mbox series

[v2,3/9] usb: cdns3: imx: add glue layer runtime pm implementation

Message ID 20200523232304.23976-4-peter.chen@nxp.com (mailing list archive)
State Superseded
Headers show
Series usb: some PM changes for cdns3 and xhci-plat | expand

Commit Message

Peter Chen May 23, 2020, 11:22 p.m. UTC
Add imx glue layer runtime pm implementation, and the runtime
pm is default off.

Signed-off-by: Peter Chen <peter.chen@nxp.com>
---
 drivers/usb/cdns3/cdns3-imx.c | 179 +++++++++++++++++++++++++++++++++-
 1 file changed, 177 insertions(+), 2 deletions(-)

Comments

Jun Li May 24, 2020, 6:55 a.m. UTC | #1
Hi

> -----Original Message-----
> From: Peter Chen <peter.chen@nxp.com>
> Sent: 2020年5月24日 7:23
> To: balbi@kernel.org; mathias.nyman@intel.com
> Cc: linux-usb@vger.kernel.org; dl-linux-imx <linux-imx@nxp.com>;
> pawell@cadence.com; rogerq@ti.com; gregkh@linuxfoundation.org; Jun Li
> <jun.li@nxp.com>; Peter Chen <peter.chen@nxp.com>
> 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@nxp.com>
> ---
>  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
Peter Chen May 25, 2020, 2:59 a.m. UTC | #2
On 20-05-24 06:55:02, Jun Li wrote:
> > 
> >  #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

Will change for all.

> 
> > 
> > +/* 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)
> > +
> > +		/* 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?

I will return -ETIMEOUT for all timeout case, thanks.

Peter
> 
> 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
>
kernel test robot May 25, 2020, 4:53 a.m. UTC | #3
Hi Peter,

I love your patch! Perhaps something to improve:

[auto build test WARNING on usb/usb-testing]
[also build test WARNING on balbi-usb/testing/next next-20200522]
[cannot apply to shawnguo/for-next v5.7-rc7]
[if your patch is applied to the wrong git tree, please drop us a note to help
improve the system. BTW, we also suggest to use '--base' option to specify the
base tree in git format-patch, please see https://stackoverflow.com/a/37406982]

url:    https://github.com/0day-ci/linux/commits/Peter-Chen/usb-some-PM-changes-for-cdns3-and-xhci-plat/20200524-072451
base:   https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb.git usb-testing
config: s390-randconfig-s002-20200524 (attached as .config)
compiler: s390-linux-gcc (GCC) 9.3.0
reproduce:
        # apt-get install sparse
        # sparse version: v0.6.1-240-gf0fe1cd9-dirty
        # save the attached .config to linux build tree
        make W=1 C=1 ARCH=s390 CF='-fdiagnostic-prefix -D__CHECK_ENDIAN__'

If you fix the issue, kindly add following tag as appropriate
Reported-by: kbuild test robot <lkp@intel.com>

All warnings (new ones prefixed by >>, old ones prefixed by <<):

>> drivers/usb/cdns3/cdns3-imx.c:150:12: warning: 'cdns_imx_platform_suspend' used but never defined
150 | static int cdns_imx_platform_suspend(struct device *dev,
|            ^~~~~~~~~~~~~~~~~~~~~~~~~

vim +/cdns_imx_platform_suspend +150 drivers/usb/cdns3/cdns3-imx.c

   149	
 > 150	static int cdns_imx_platform_suspend(struct device *dev,
   151		bool suspend, bool wakeup);
   152	static struct cdns3_platform_data cdns_imx_pdata = {
   153		.platform_suspend = cdns_imx_platform_suspend,
   154	};
   155	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
Peter Chen May 25, 2020, 6:31 a.m. UTC | #4
On 20-05-25 12:53:11, kbuild test robot wrote:
> Hi Peter,
> 
> I love your patch! Perhaps something to improve:
> 
> [auto build test WARNING on usb/usb-testing]
> [also build test WARNING on balbi-usb/testing/next next-20200522]
> [cannot apply to shawnguo/for-next v5.7-rc7]
> [if your patch is applied to the wrong git tree, please drop us a note to help
> improve the system. BTW, we also suggest to use '--base' option to specify the
> base tree in git format-patch, please see https://eur01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fstackoverflow.com%2Fa%2F37406982&amp;data=02%7C01%7Cpeter.chen%40nxp.com%7Cd98349851abc4e0abf1408d8006799d7%7C686ea1d3bc2b4c6fa92cd99c5c301635%7C0%7C0%7C637259792390096024&amp;sdata=yRFR26%2FjlA8dg0wS4xGplfPvXtG8tYa1qb2lroSsq0Y%3D&amp;reserved=0]
> 
> url:    https://eur01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2F0day-ci%2Flinux%2Fcommits%2FPeter-Chen%2Fusb-some-PM-changes-for-cdns3-and-xhci-plat%2F20200524-072451&amp;data=02%7C01%7Cpeter.chen%40nxp.com%7Cd98349851abc4e0abf1408d8006799d7%7C686ea1d3bc2b4c6fa92cd99c5c301635%7C0%7C0%7C637259792390096024&amp;sdata=mJmxpr0897Sk415jiIGMg0Q7jIxAO93t%2FvUoJxAOXIg%3D&amp;reserved=0
> base:   https://eur01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgit.kernel.org%2Fpub%2Fscm%2Flinux%2Fkernel%2Fgit%2Fgregkh%2Fusb.git&amp;data=02%7C01%7Cpeter.chen%40nxp.com%7Cd98349851abc4e0abf1408d8006799d7%7C686ea1d3bc2b4c6fa92cd99c5c301635%7C0%7C0%7C637259792390096024&amp;sdata=nrCKZGGd4%2FFUKaIGm1PTn2OzeJ3XbSZvb9sn7GHgFBw%3D&amp;reserved=0 usb-testing
> config: s390-randconfig-s002-20200524 (attached as .config)
> compiler: s390-linux-gcc (GCC) 9.3.0
> reproduce:
>         # apt-get install sparse
>         # sparse version: v0.6.1-240-gf0fe1cd9-dirty
>         # save the attached .config to linux build tree
>         make W=1 C=1 ARCH=s390 CF='-fdiagnostic-prefix -D__CHECK_ENDIAN__'
> 
> If you fix the issue, kindly add following tag as appropriate
> Reported-by: kbuild test robot <lkp@intel.com>
> 
> All warnings (new ones prefixed by >>, old ones prefixed by <<):
> 
> >> drivers/usb/cdns3/cdns3-imx.c:150:12: warning: 'cdns_imx_platform_suspend' used but never defined

Will fix it at next revision, thanks.

Peter
> 150 | static int cdns_imx_platform_suspend(struct device *dev,
> |            ^~~~~~~~~~~~~~~~~~~~~~~~~
> 
> vim +/cdns_imx_platform_suspend +150 drivers/usb/cdns3/cdns3-imx.c
> 
>    149	
>  > 150	static int cdns_imx_platform_suspend(struct device *dev,
>    151		bool suspend, bool wakeup);
>    152	static struct cdns3_platform_data cdns_imx_pdata = {
>    153		.platform_suspend = cdns_imx_platform_suspend,
>    154	};
>    155	
>
kernel test robot May 25, 2020, 8:25 a.m. UTC | #5
Hi Peter,

I love your patch! Yet something to improve:

[auto build test ERROR on usb/usb-testing]
[also build test ERROR on balbi-usb/testing/next next-20200522]
[cannot apply to shawnguo/for-next v5.7-rc7]
[if your patch is applied to the wrong git tree, please drop us a note to help
improve the system. BTW, we also suggest to use '--base' option to specify the
base tree in git format-patch, please see https://stackoverflow.com/a/37406982]

url:    https://github.com/0day-ci/linux/commits/Peter-Chen/usb-some-PM-changes-for-cdns3-and-xhci-plat/20200524-072451
base:   https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb.git usb-testing
config: parisc-allyesconfig (attached as .config)
compiler: hppa-linux-gcc (GCC) 9.3.0
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # save the attached .config to linux build tree
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-9.3.0 make.cross ARCH=parisc 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kbuild test robot <lkp@intel.com>

All errors (new ones prefixed by >>, old ones prefixed by <<):

>> hppa-linux-ld: drivers/usb/cdns3/cdns3-imx.o:(.data+0x88): undefined reference to `cdns_imx_platform_suspend'

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
diff mbox series

Patch

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)
 #define PHY_REFCLK_REQ		(1 << 0)
 
+/* 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");
+	}
+
+	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);