Merge tag 'pci-v4.21-changes' of git://git.kernel.org/pub/scm/linux/kernel/git/helgaa...
[sfrench/cifs-2.6.git] / drivers / pci / controller / dwc / pci-imx6.c
index 88af6bff945f36cbb9b746bb92775a9b8ca073d0..52e47dac028f7c43c7dde99945b2961b22554016 100644 (file)
@@ -27,6 +27,8 @@
 #include <linux/types.h>
 #include <linux/interrupt.h>
 #include <linux/reset.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_runtime.h>
 
 #include "pcie-designware.h"
 
@@ -59,6 +61,11 @@ struct imx6_pcie {
        u32                     tx_swing_low;
        int                     link_gen;
        struct regulator        *vpcie;
+
+       /* power domain for pcie */
+       struct device           *pd_pcie;
+       /* power domain for pcie phy */
+       struct device           *pd_pcie_phy;
 };
 
 /* Parameters for the waiting for PCIe PHY PLL to lock on i.MX7 */
@@ -67,6 +74,7 @@ struct imx6_pcie {
 #define PHY_PLL_LOCK_WAIT_USLEEP_MAX   200
 
 /* PCIe Root Complex registers (memory-mapped) */
+#define PCIE_RC_IMX6_MSI_CAP                   0x50
 #define PCIE_RC_LCR                            0x7c
 #define PCIE_RC_LCR_MAX_LINK_SPEEDS_GEN1       0x1
 #define PCIE_RC_LCR_MAX_LINK_SPEEDS_GEN2       0x2
@@ -290,6 +298,43 @@ static int imx6q_pcie_abort_handler(unsigned long addr,
        return 1;
 }
 
+static int imx6_pcie_attach_pd(struct device *dev)
+{
+       struct imx6_pcie *imx6_pcie = dev_get_drvdata(dev);
+       struct device_link *link;
+
+       /* Do nothing when in a single power domain */
+       if (dev->pm_domain)
+               return 0;
+
+       imx6_pcie->pd_pcie = dev_pm_domain_attach_by_name(dev, "pcie");
+       if (IS_ERR(imx6_pcie->pd_pcie))
+               return PTR_ERR(imx6_pcie->pd_pcie);
+       link = device_link_add(dev, imx6_pcie->pd_pcie,
+                       DL_FLAG_STATELESS |
+                       DL_FLAG_PM_RUNTIME |
+                       DL_FLAG_RPM_ACTIVE);
+       if (!link) {
+               dev_err(dev, "Failed to add device_link to pcie pd.\n");
+               return -EINVAL;
+       }
+
+       imx6_pcie->pd_pcie_phy = dev_pm_domain_attach_by_name(dev, "pcie_phy");
+       if (IS_ERR(imx6_pcie->pd_pcie_phy))
+               return PTR_ERR(imx6_pcie->pd_pcie_phy);
+
+       device_link_add(dev, imx6_pcie->pd_pcie_phy,
+                       DL_FLAG_STATELESS |
+                       DL_FLAG_PM_RUNTIME |
+                       DL_FLAG_RPM_ACTIVE);
+       if (IS_ERR(link)) {
+               dev_err(dev, "Failed to add device_link to pcie_phy pd: %ld\n", PTR_ERR(link));
+               return PTR_ERR(link);
+       }
+
+       return 0;
+}
+
 static void imx6_pcie_assert_core_reset(struct imx6_pcie *imx6_pcie)
 {
        struct device *dev = imx6_pcie->pci->dev;
@@ -765,8 +810,28 @@ static void imx6_pcie_ltssm_disable(struct device *dev)
 
 static void imx6_pcie_pm_turnoff(struct imx6_pcie *imx6_pcie)
 {
-       reset_control_assert(imx6_pcie->turnoff_reset);
-       reset_control_deassert(imx6_pcie->turnoff_reset);
+       struct device *dev = imx6_pcie->pci->dev;
+
+       /* Some variants have a turnoff reset in DT */
+       if (imx6_pcie->turnoff_reset) {
+               reset_control_assert(imx6_pcie->turnoff_reset);
+               reset_control_deassert(imx6_pcie->turnoff_reset);
+               goto pm_turnoff_sleep;
+       }
+
+       /* Others poke directly at IOMUXC registers */
+       switch (imx6_pcie->variant) {
+       case IMX6SX:
+               regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
+                               IMX6SX_GPR12_PCIE_PM_TURN_OFF,
+                               IMX6SX_GPR12_PCIE_PM_TURN_OFF);
+               regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
+                               IMX6SX_GPR12_PCIE_PM_TURN_OFF, 0);
+               break;
+       default:
+               dev_err(dev, "PME_Turn_Off not implemented\n");
+               return;
+       }
 
        /*
         * Components with an upstream port must respond to
@@ -775,6 +840,7 @@ static void imx6_pcie_pm_turnoff(struct imx6_pcie *imx6_pcie)
         * The standard recommends a 1-10ms timeout after which to
         * proceed anyway as if acks were received.
         */
+pm_turnoff_sleep:
        usleep_range(1000, 10000);
 }
 
@@ -784,18 +850,31 @@ static void imx6_pcie_clk_disable(struct imx6_pcie *imx6_pcie)
        clk_disable_unprepare(imx6_pcie->pcie_phy);
        clk_disable_unprepare(imx6_pcie->pcie_bus);
 
-       if (imx6_pcie->variant == IMX7D) {
+       switch (imx6_pcie->variant) {
+       case IMX6SX:
+               clk_disable_unprepare(imx6_pcie->pcie_inbound_axi);
+               break;
+       case IMX7D:
                regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
                                   IMX7D_GPR12_PCIE_PHY_REFCLK_SEL,
                                   IMX7D_GPR12_PCIE_PHY_REFCLK_SEL);
+               break;
+       default:
+               break;
        }
 }
 
+static inline bool imx6_pcie_supports_suspend(struct imx6_pcie *imx6_pcie)
+{
+       return (imx6_pcie->variant == IMX7D ||
+               imx6_pcie->variant == IMX6SX);
+}
+
 static int imx6_pcie_suspend_noirq(struct device *dev)
 {
        struct imx6_pcie *imx6_pcie = dev_get_drvdata(dev);
 
-       if (imx6_pcie->variant != IMX7D)
+       if (!imx6_pcie_supports_suspend(imx6_pcie))
                return 0;
 
        imx6_pcie_pm_turnoff(imx6_pcie);
@@ -811,7 +890,7 @@ static int imx6_pcie_resume_noirq(struct device *dev)
        struct imx6_pcie *imx6_pcie = dev_get_drvdata(dev);
        struct pcie_port *pp = &imx6_pcie->pci->pp;
 
-       if (imx6_pcie->variant != IMX7D)
+       if (!imx6_pcie_supports_suspend(imx6_pcie))
                return 0;
 
        imx6_pcie_assert_core_reset(imx6_pcie);
@@ -840,6 +919,7 @@ static int imx6_pcie_probe(struct platform_device *pdev)
        struct resource *dbi_base;
        struct device_node *node = dev->of_node;
        int ret;
+       u16 val;
 
        imx6_pcie = devm_kzalloc(dev, sizeof(*imx6_pcie), GFP_KERNEL);
        if (!imx6_pcie)
@@ -977,10 +1057,22 @@ static int imx6_pcie_probe(struct platform_device *pdev)
 
        platform_set_drvdata(pdev, imx6_pcie);
 
+       ret = imx6_pcie_attach_pd(dev);
+       if (ret)
+               return ret;
+
        ret = imx6_add_pcie_port(imx6_pcie, pdev);
        if (ret < 0)
                return ret;
 
+       if (pci_msi_enabled()) {
+               val = dw_pcie_readw_dbi(pci, PCIE_RC_IMX6_MSI_CAP +
+                                       PCI_MSI_FLAGS);
+               val |= PCI_MSI_FLAGS_ENABLE;
+               dw_pcie_writew_dbi(pci, PCIE_RC_IMX6_MSI_CAP + PCI_MSI_FLAGS,
+                                  val);
+       }
+
        return 0;
 }