ipq40xx: add PCIe magic hack to improve VRX518 compatibility
authorJan Hoffmann <jan@3e8.eu>
Mon, 30 Jan 2023 22:40:20 +0000 (23:40 +0100)
committerJo-Philipp Wich <jo@mein.io>
Wed, 21 Feb 2024 21:31:36 +0000 (22:31 +0100)
Some VRX518 modems fail to initialize properly with the error message
"dc_ep_clk_on failed". As a result, the DSL data path doesn't work.

This hack, which is based on code from the FRITZ!Box 7530 GPL archive,
fixes the issue. It changes the PCIe vendor/device ID to values matching
a Lantiq SoC. It also appears to emulate a Lantiq CPU ID register for
connected PCIe devices, by remapping the matching address area to a
specially crafted buffer using the address translation unit.

The hack is only active if the "avm,host_magic" property is specified in
the device tree, so this shouldn't affect any devices other than
FRITZ!Box 7530/7520.

Signed-off-by: Jan Hoffmann <jan@3e8.eu>
target/linux/ipq40xx/files/arch/arm/boot/dts/qcom-ipq4019-fritzbox-7530.dts
target/linux/ipq40xx/patches-5.15/997-pcie-qcom-host-magic.patch [new file with mode: 0644]

index a118bdf26bd6ca6e6a2365fe35f8a2546dd141b5..6558f86a533a17fcc853e10eb215f36af73c715e 100644 (file)
        perst-gpio = <&tlmm 38 GPIO_ACTIVE_LOW>;
        wake-gpio = <&tlmm 50 GPIO_ACTIVE_LOW>;
 
+       avm,host_magic;
+
        bridge@0,0 {
                reg = <0x00000000 0 0 0 0>;
                #address-cells = <3>;
diff --git a/target/linux/ipq40xx/patches-5.15/997-pcie-qcom-host-magic.patch b/target/linux/ipq40xx/patches-5.15/997-pcie-qcom-host-magic.patch
new file mode 100644 (file)
index 0000000..f427bcc
--- /dev/null
@@ -0,0 +1,215 @@
+This hack is based on code from the FRITZ!Box 7530 GPL archive for
+firmware version 07.50.
+
+If the device tree contains the "avm,host_magic" property, it changes
+the PCIe vendor/device ID to the values from Lantiq GRX500 SoCs. It also
+programs the ATU to present a buffer containing a magic value to PCIe
+devices. This appears to emulate a Lantiq CPU ID register.
+
+Without this hack, some VRX518 modems fail to initialize properly (error
+"dc_ep_clk_on failed"), and the DSL data path doesn't work.
+--- a/drivers/pci/controller/dwc/pcie-qcom.c
++++ b/drivers/pci/controller/dwc/pcie-qcom.c
+@@ -27,6 +27,7 @@
+ #include <linux/reset.h>
+ #include <linux/slab.h>
+ #include <linux/types.h>
++#include <linux/version.h>
+ #include "../../pci.h"
+ #include "pcie-designware.h"
+@@ -102,6 +103,8 @@
+ #define QCOM_PCIE_CRC8_POLYNOMIAL (BIT(2) | BIT(1) | BIT(0))
++#define PCIE_MAGIC_SIZE               0x10000
++
+ struct qcom_pcie_resources_2_1_0 {
+       struct clk_bulk_data clks[QCOM_PCIE_2_1_0_MAX_CLOCKS];
+       struct reset_control *pci_reset;
+@@ -197,6 +200,8 @@ struct qcom_pcie {
+       struct phy *phy;
+       struct gpio_desc *reset;
+       const struct qcom_pcie_ops *ops;
++      void *magic_cpu_addr;
++      dma_addr_t magic_dma_handle;
+ };
+ #define to_qcom_pcie(x)               dev_get_drvdata((x)->dev)
+@@ -1388,8 +1393,141 @@ err_deinit:
+       return ret;
+ }
++static int qcom_pcie_magic_prog_atu(struct qcom_pcie *pcie,
++                                   u32 addr, u32 limit, u32 phys)
++{
++      struct dw_pcie *pci = pcie->pci;
++      struct device *dev = pci->dev;
++      u32 retries, val;
++      int index;
++
++      if (!pci->num_ib_windows) {
++              dev_err(dev, "No inbound ATU window available for magic\n");
++              return -1;
++      }
++
++      /*
++       * Use highest window index and reduce window count so the driver
++       * won't overwrite the entry later.
++       */
++      index = --pci->num_ib_windows;
++
++#if LINUX_VERSION_CODE < KERNEL_VERSION(6,0,0)
++      if (pci->iatu_unroll_enabled) {
++              dev_err(dev, "Programming ATU for magic not implemented for this hardware\n");
++              return -1;
++      }
++
++      dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT,
++                         PCIE_ATU_REGION_INBOUND | index);
++
++      dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_BASE, addr);
++      dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_BASE, 0);
++      dw_pcie_writel_dbi(pci, PCIE_ATU_LIMIT, limit);
++      dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_TARGET, phys);
++      dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_TARGET, 0);
++
++      dw_pcie_writel_dbi(pci, PCIE_ATU_CR1, PCIE_ATU_TYPE_MEM);
++      dw_pcie_writel_dbi(pci, PCIE_ATU_CR2, PCIE_ATU_ENABLE);
++
++      for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) {
++              val = dw_pcie_readl_dbi(pci, PCIE_ATU_CR2);
++              if (val & PCIE_ATU_ENABLE)
++                      return 0;
++
++              mdelay(LINK_WAIT_IATU);
++      }
++#else
++      dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_LOWER_BASE, addr);
++      dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_UPPER_BASE, 0);
++      dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_LIMIT, limit);
++      dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_LOWER_TARGET, phys);
++      dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_UPPER_TARGET, 0);
++
++      dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_REGION_CTRL1,
++                            PCIE_ATU_TYPE_MEM);
++      dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_REGION_CTRL2,
++                            PCIE_ATU_ENABLE);
++
++      for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) {
++              val = dw_pcie_readl_atu_ib(pci, index, PCIE_ATU_REGION_CTRL2);
++              if (val & PCIE_ATU_ENABLE)
++                      return 0;
++
++              mdelay(LINK_WAIT_IATU);
++      }
++#endif
++
++      dev_err(dev, "Failed to program ATU for magic\n");
++      return -1;
++}
++
++static void qcom_pcie_magic_deinit(struct qcom_pcie *pcie)
++{
++      struct dw_pcie *pci = pcie->pci;
++      struct device *dev = pci->dev;
++
++      if (pcie->magic_cpu_addr) {
++              dma_free_coherent(dev, PCIE_MAGIC_SIZE,
++                                pcie->magic_cpu_addr,
++                                pcie->magic_dma_handle);
++
++              pcie->magic_cpu_addr = NULL;
++      }
++}
++
++static void qcom_pcie_magic_init(struct qcom_pcie *pcie)
++{
++      struct dw_pcie *pci = pcie->pci;
++      struct device *dev = pci->dev;
++      u32 *virt;
++      u32 phys;
++      int ret;
++
++      if (!of_property_read_bool(dev->of_node, "avm,host_magic"))
++              return;
++
++      dev_info(dev, "Applying PCIe host magic\n");
++
++      virt = dma_alloc_coherent(dev, PCIE_MAGIC_SIZE, &phys, GFP_ATOMIC);
++      BUG_ON(virt == NULL);
++
++      pcie->magic_cpu_addr = virt;
++      pcie->magic_dma_handle = phys;
++
++      /*
++       * This value is the manufacturer ID of Lantiq. The address where
++       * it will be visible for the PCIe device matches the location of
++       * CPU ID registers on Lantiq SocS (MPS base address is 0x1f107000).
++       */
++      virt[0x7340/4] = 0x389 << 5;
++
++      /* Make it visible to PCIe devices using address translation unit */
++      ret = qcom_pcie_magic_prog_atu(pcie, 0x1f100000, 0x1f10ffff, phys);
++
++      dw_pcie_dbi_ro_wr_en(pci);
++
++      /* Set vendor/device ID of GRX500 PCIe host */
++      dw_pcie_writew_dbi(pci, PCI_VENDOR_ID, 0x1bef);
++      dw_pcie_writew_dbi(pci, PCI_DEVICE_ID, 0x0030);
++
++      dw_pcie_dbi_ro_wr_dis(pci);
++
++      if (ret)
++              qcom_pcie_magic_deinit(pcie);
++}
++
++static void qcom_pcie_atu_hack(struct pcie_port *pp)
++{
++      struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
++      struct qcom_pcie *pcie = to_qcom_pcie(pci);
++
++      qcom_pcie_magic_init(pcie);
++}
++
+ static const struct dw_pcie_host_ops qcom_pcie_dw_ops = {
+       .host_init = qcom_pcie_host_init,
++      .atu_hack = qcom_pcie_atu_hack,
+ };
+ /* Qcom IP rev.: 2.1.0        Synopsys IP rev.: 4.01a */
+@@ -1536,6 +1674,7 @@ static int qcom_pcie_probe(struct platfo
+ err_phy_exit:
+       phy_exit(pcie->phy);
++      qcom_pcie_magic_deinit(pcie);
+ err_pm_runtime_put:
+       pm_runtime_put(dev);
+       pm_runtime_disable(dev);
+--- a/drivers/pci/controller/dwc/pcie-designware-host.c
++++ b/drivers/pci/controller/dwc/pcie-designware-host.c
+@@ -400,6 +400,14 @@ int dw_pcie_host_init(struct pcie_port *
+       }
+       dw_pcie_iatu_detect(pci);
++      /*
++       * This needs to be called after ATU detection, but before the driver
++       * sets up any ATU entries, to avoid any ATU entry programmed in the
++       * hack being overwritten by the driver later.
++       */
++      if (pp->ops->atu_hack)
++              pp->ops->atu_hack(pp);
++
+       dw_pcie_setup_rc(pp);
+       if (!dw_pcie_link_up(pci) && pci->ops && pci->ops->start_link) {
+--- a/drivers/pci/controller/dwc/pcie-designware.h
++++ b/drivers/pci/controller/dwc/pcie-designware.h
+@@ -174,6 +174,7 @@ enum dw_pcie_device_mode {
+ struct dw_pcie_host_ops {
+       int (*host_init)(struct pcie_port *pp);
++      void (*atu_hack)(struct pcie_port *pp);
+       int (*msi_host_init)(struct pcie_port *pp);
+ };