PCI: imx: Add workaround for e10728, IMX7d PCIe PLL failure
authorTrent Piepho <tpiepho@impinj.com>
Tue, 5 Feb 2019 00:17:41 +0000 (00:17 +0000)
committerLorenzo Pieralisi <lorenzo.pieralisi@arm.com>
Tue, 12 Feb 2019 19:17:34 +0000 (19:17 +0000)
This implements the workound described in the NXP IMX7d erratum e10728.

Initial VCO oscillation may fail under corner conditions such as cold
temperature. It causes PCIe PLL to fail to lock in the initialization
phase, which results in the PCIe link failing to come up.

The workaround is to disable Duty-Cycle Corrector (DCC) calibration
after G_RST.

To do this it is necessary to gain access to the undocumented and
currently unused PCIe PHY register bank. A new device tree node of type
"fsl,imx7d-pcie-phy" is created for the PHY block and the existing PCIe
device uses a phandle named "fsl,imx7d-pcie-phy" to point to it.

Signed-off-by: Trent Piepho <tpiepho@impinj.com>
[lorenzo.pieralisi@arm.com: updated log string, commit log]
Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
Reviewed-by: Lucas Stach <l.stach@pengutronix.de>
drivers/pci/controller/dwc/pci-imx6.c

index c1d434b..64c7433 100644 (file)
@@ -19,6 +19,7 @@
 #include <linux/module.h>
 #include <linux/of_gpio.h>
 #include <linux/of_device.h>
+#include <linux/of_address.h>
 #include <linux/pci.h>
 #include <linux/platform_device.h>
 #include <linux/regmap.h>
@@ -77,6 +78,7 @@ struct imx6_pcie {
        u32                     tx_swing_low;
        int                     link_gen;
        struct regulator        *vpcie;
+       void __iomem            *phy_base;
 
        /* power domain for pcie */
        struct device           *pd_pcie;
@@ -134,6 +136,23 @@ struct imx6_pcie {
 #define PCIE_PHY_RX_ASIC_OUT 0x100D
 #define PCIE_PHY_RX_ASIC_OUT_VALID     (1 << 0)
 
+/* iMX7 PCIe PHY registers */
+#define PCIE_PHY_CMN_REG4              0x14
+/* These are probably the bits that *aren't* DCC_FB_EN */
+#define PCIE_PHY_CMN_REG4_DCC_FB_EN    0x29
+
+#define PCIE_PHY_CMN_REG15             0x54
+#define PCIE_PHY_CMN_REG15_DLY_4       BIT(2)
+#define PCIE_PHY_CMN_REG15_PLL_PD      BIT(5)
+#define PCIE_PHY_CMN_REG15_OVRD_PLL_PD BIT(7)
+
+#define PCIE_PHY_CMN_REG24             0x90
+#define PCIE_PHY_CMN_REG24_RX_EQ       BIT(6)
+#define PCIE_PHY_CMN_REG24_RX_EQ_SEL   BIT(3)
+
+#define PCIE_PHY_CMN_REG26             0x98
+#define PCIE_PHY_CMN_REG26_ATT_MODE    0xBC
+
 #define PHY_RX_OVRD_IN_LO 0x1005
 #define PHY_RX_OVRD_IN_LO_RX_DATA_EN (1 << 5)
 #define PHY_RX_OVRD_IN_LO_RX_PLL_EN (1 << 3)
@@ -533,6 +552,26 @@ static void imx6_pcie_deassert_core_reset(struct imx6_pcie *imx6_pcie)
                break;
        case IMX7D:
                reset_control_deassert(imx6_pcie->pciephy_reset);
+
+               /* Workaround for ERR010728, failure of PCI-e PLL VCO to
+                * oscillate, especially when cold.  This turns off "Duty-cycle
+                * Corrector" and other mysterious undocumented things.
+                */
+               if (likely(imx6_pcie->phy_base)) {
+                       /* De-assert DCC_FB_EN */
+                       writel(PCIE_PHY_CMN_REG4_DCC_FB_EN,
+                              imx6_pcie->phy_base + PCIE_PHY_CMN_REG4);
+                       /* Assert RX_EQS and RX_EQS_SEL */
+                       writel(PCIE_PHY_CMN_REG24_RX_EQ_SEL
+                               | PCIE_PHY_CMN_REG24_RX_EQ,
+                              imx6_pcie->phy_base + PCIE_PHY_CMN_REG24);
+                       /* Assert ATT_MODE */
+                       writel(PCIE_PHY_CMN_REG26_ATT_MODE,
+                              imx6_pcie->phy_base + PCIE_PHY_CMN_REG26);
+               } else {
+                       dev_warn(dev, "Unable to apply ERR010728 workaround. DT missing fsl,imx7d-pcie-phy phandle ?\n");
+               }
+
                imx7d_pcie_wait_for_phy_pll_lock(imx6_pcie);
                break;
        case IMX6SX:
@@ -994,6 +1033,7 @@ static int imx6_pcie_probe(struct platform_device *pdev)
        struct device *dev = &pdev->dev;
        struct dw_pcie *pci;
        struct imx6_pcie *imx6_pcie;
+       struct device_node *np;
        struct resource *dbi_base;
        struct device_node *node = dev->of_node;
        int ret;
@@ -1013,6 +1053,23 @@ static int imx6_pcie_probe(struct platform_device *pdev)
        imx6_pcie->pci = pci;
        imx6_pcie->drvdata = of_device_get_match_data(dev);
 
+       /* Find the PHY if one is defined, only imx7d uses it */
+       np = of_parse_phandle(node, "fsl,imx7d-pcie-phy", 0);
+       if (np) {
+               struct resource res;
+
+               ret = of_address_to_resource(np, 0, &res);
+               if (ret) {
+                       dev_err(dev, "Unable to map PCIe PHY\n");
+                       return ret;
+               }
+               imx6_pcie->phy_base = devm_ioremap_resource(dev, &res);
+               if (IS_ERR(imx6_pcie->phy_base)) {
+                       dev_err(dev, "Unable to map PCIe PHY\n");
+                       return PTR_ERR(imx6_pcie->phy_base);
+               }
+       }
+
        dbi_base = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        pci->dbi_base = devm_ioremap_resource(dev, dbi_base);
        if (IS_ERR(pci->dbi_base))