[PATCH] ipq40xx: add PCIe magic hack to improve VRX518 compatibility

Felix Maurer felix at felix-maurer.de
Tue Nov 21 15:37:16 PST 2023


On 30.01.23 23:40, Jan Hoffmann wrote:
> 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 at 3e8.eu>

Sorry for digging up an old thread/patch. As I own one of these devices 
where the DSL modem can only work with this hack, I applied this patch 
on top of v23.05.0 and built an image with it. I had this running for 
the last three weeks and the DSL is stable with this patch.

I agree that it is very much a hack, but given that the OEM does the 
same thing and we have no other way of getting those versions of the DSL 
modem to work, I think it's worthwhile adding the patch.

Tested-by: Felix Maurer <felix at felix-maurer.de>


> ---
>   .../boot/dts/qcom-ipq4019-fritzbox-7530.dts   |   2 +
>   .../997-pcie-qcom-host-magic.patch            | 215 ++++++++++++++++++
>   2 files changed, 217 insertions(+)
>   create mode 100644 target/linux/ipq40xx/patches-5.15/997-pcie-qcom-host-magic.patch
> 
> diff --git a/target/linux/ipq40xx/files/arch/arm/boot/dts/qcom-ipq4019-fritzbox-7530.dts b/target/linux/ipq40xx/files/arch/arm/boot/dts/qcom-ipq4019-fritzbox-7530.dts
> index 336da52f2724..bc167616d3dc 100644
> --- a/target/linux/ipq40xx/files/arch/arm/boot/dts/qcom-ipq4019-fritzbox-7530.dts
> +++ b/target/linux/ipq40xx/files/arch/arm/boot/dts/qcom-ipq4019-fritzbox-7530.dts
> @@ -306,6 +306,8 @@
>   	perst-gpio = <&tlmm 38 GPIO_ACTIVE_LOW>;
>   	wake-gpio = <&tlmm 50 GPIO_ACTIVE_LOW>;
>   
> +	avm,host_magic;
> +
>   	bridge at 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
> index 000000000000..f427bccd2f4a
> --- /dev/null
> +++ b/target/linux/ipq40xx/patches-5.15/997-pcie-qcom-host-magic.patch
> @@ -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);
> + };
> +



More information about the openwrt-devel mailing list