imx: ventana: add dt fixup for GW16082 irq mapping
authorTim Harvey <tharvey@gateworks.com>
Fri, 17 Jun 2016 13:10:41 +0000 (06:10 -0700)
committerStefano Babic <sbabic@denx.de>
Thu, 28 Jul 2016 11:27:16 +0000 (13:27 +0200)
The GW16082 mini-PCI expansion mezzanine uses a TI XIO2001 PCIe-to-PCI
bridge with legacy INTA/B/C/D interrupts. These interrupts are assigned
in the reverse order according to the PCI spec.

If the TI bridge is found on the Ventana PCI bus, add device-tree nodes
according to bus enumeration explicitly defining the interrupt mapping
to override the default PCI mapping in the Linux kernel. This allows
the GW16082 to work with upstream kernels that support device-tree
irq parsing.

Signed-off-by: Tim Harvey <tharvey@gateworks.com>
board/gateworks/gw_ventana/gw_ventana.c

index 70395ac91db69ec5659f4fad93138e6f4ec84bec..7d569ad625be30eb1c5aa195c2d72996243e76c3 100644 (file)
@@ -491,14 +491,54 @@ int imx6_pcie_toggle_reset(void)
  * GPIO's as PERST# signals for its downstream ports - configure the GPIO's
  * properly and assert reset for 100ms.
  */
+#define MAX_PCI_DEVS   32
+struct pci_dev {
+       pci_dev_t devfn;
+       unsigned short vendor;
+       unsigned short device;
+       unsigned short class;
+       unsigned short busno; /* subbordinate busno */
+       struct pci_dev *ppar;
+};
+struct pci_dev pci_devs[MAX_PCI_DEVS];
+int pci_devno;
+int pci_bridgeno;
+
 void board_pci_fixup_dev(struct pci_controller *hose, pci_dev_t dev,
                         unsigned short vendor, unsigned short device,
                         unsigned short class)
 {
+       int i;
        u32 dw;
+       struct pci_dev *pdev = &pci_devs[pci_devno++];
 
        debug("%s: %02d:%02d.%02d: %04x:%04x\n", __func__,
              PCI_BUS(dev), PCI_DEV(dev), PCI_FUNC(dev), vendor, device);
+
+       /* store array of devs for later use in device-tree fixup */
+       pdev->devfn = dev;
+       pdev->vendor = vendor;
+       pdev->device = device;
+       pdev->class = class;
+       pdev->ppar = NULL;
+       if (class == PCI_CLASS_BRIDGE_PCI)
+               pdev->busno = ++pci_bridgeno;
+       else
+               pdev->busno = 0;
+
+       /* fixup RC - it should be 00:00.0 not 00:01.0 */
+       if (PCI_BUS(dev) == 0)
+               pdev->devfn = 0;
+
+       /* find dev's parent */
+       for (i = 0; i < pci_devno; i++) {
+               if (pci_devs[i].busno == PCI_BUS(pdev->devfn)) {
+                       pdev->ppar = &pci_devs[i];
+                       break;
+               }
+       }
+
+       /* assert downstream PERST# */
        if (vendor == PCI_VENDOR_ID_PLX &&
            (device & 0xfff0) == 0x8600 &&
            PCI_DEV(dev) == 0 && PCI_FUNC(dev) == 0) {
@@ -802,6 +842,189 @@ static inline void ft_delprop_path(void *blob, const char *path,
        }
 }
 
+#if defined(CONFIG_CMD_PCI)
+#define PCI_ID(x) ( \
+       (PCI_BUS(x->devfn)<<16)| \
+       (PCI_DEV(x->devfn)<<11)| \
+       (PCI_FUNC(x->devfn)<<8) \
+       )
+#define PCIE_PATH      "/soc/pcie@0x01000000"
+int fdt_add_pci_node(void *blob, int par, struct pci_dev *dev)
+{
+       uint32_t reg[5];
+       char node[32];
+       int np;
+
+       sprintf(node, "pcie@%d,%d,%d", PCI_BUS(dev->devfn),
+               PCI_DEV(dev->devfn), PCI_FUNC(dev->devfn));
+
+       np = fdt_subnode_offset(blob, par, node);
+       if (np >= 0)
+               return np;
+       np = fdt_add_subnode(blob, par, node);
+       if (np < 0) {
+               printf("   %s failed: no space\n", __func__);
+               return np;
+       }
+
+       memset(reg, 0, sizeof(reg));
+       reg[0] = cpu_to_fdt32(PCI_ID(dev));
+       fdt_setprop(blob, np, "reg", reg, sizeof(reg));
+
+       return np;
+}
+
+/* build a path of nested PCI devs for all bridges passed through */
+int fdt_add_pci_path(void *blob, struct pci_dev *dev)
+{
+       struct pci_dev *bridges[MAX_PCI_DEVS];
+       int k, np;
+
+       /* build list of parents */
+       np = fdt_path_offset(blob, PCIE_PATH);
+       if (np < 0)
+               return np;
+
+       k = 0;
+       while (dev) {
+               bridges[k++] = dev;
+               dev = dev->ppar;
+       };
+
+       /* now add them the to DT in reverse order */
+       while (k--) {
+               np = fdt_add_pci_node(blob, np, bridges[k]);
+               if (np < 0)
+                       break;
+       }
+
+       return np;
+}
+
+/*
+ * The GW16082 has a hardware errata errata such that it's
+ * INTA/B/C/D are mis-mapped to its four slots (slot12-15). Because
+ * of this normal PCI interrupt swizzling will not work so we will
+ * provide an irq-map via device-tree.
+ */
+int fdt_fixup_gw16082(void *blob, int np, struct pci_dev *dev)
+{
+       int len;
+       int host;
+       uint32_t imap_new[8*4*4];
+       const uint32_t *imap;
+       uint32_t irq[4];
+       uint32_t reg[4];
+       int i;
+
+       /* build irq-map based on host controllers map */
+       host = fdt_path_offset(blob, PCIE_PATH);
+       if (host < 0) {
+               printf("   %s failed: missing host\n", __func__);
+               return host;
+       }
+
+       /* use interrupt data from root complex's node */
+       imap = fdt_getprop(blob, host, "interrupt-map", &len);
+       if (!imap || len != 128) {
+               printf("   %s failed: invalid interrupt-map\n",
+                      __func__);
+               return -FDT_ERR_NOTFOUND;
+       }
+
+       /* obtain irq's of host controller in pin order */
+       for (i = 0; i < 4; i++)
+               irq[(fdt32_to_cpu(imap[(i*8)+3])-1)%4] = imap[(i*8)+6];
+
+       /*
+        * determine number of swizzles necessary:
+        *   For each bridge we pass through we need to swizzle
+        *   the number of the slot we are on.
+        */
+       struct pci_dev *d;
+       int b;
+       b = 0;
+       d = dev->ppar;
+       while(d && d->ppar) {
+               b += PCI_DEV(d->devfn);
+               d = d->ppar;
+       }
+
+       /* create new irq mappings for slots12-15
+        * <skt> <idsel> <slot> <skt-inta> <skt-intb>
+        * J3    AD28    12     INTD      INTA
+        * J4    AD29    13     INTC      INTD
+        * J5    AD30    14     INTB      INTC
+        * J2    AD31    15     INTA      INTB
+        */
+       for (i = 0; i < 4; i++) {
+               /* addr matches bus:dev:func */
+               u32 addr = dev->busno << 16 | (12+i) << 11;
+
+               /* default cells from root complex */
+               memcpy(&imap_new[i*32], imap, 128);
+               /* first cell is PCI device address (BDF) */
+               imap_new[(i*32)+(0*8)+0] = cpu_to_fdt32(addr);
+               imap_new[(i*32)+(1*8)+0] = cpu_to_fdt32(addr);
+               imap_new[(i*32)+(2*8)+0] = cpu_to_fdt32(addr);
+               imap_new[(i*32)+(3*8)+0] = cpu_to_fdt32(addr);
+               /* third cell is pin */
+               imap_new[(i*32)+(0*8)+3] = cpu_to_fdt32(1);
+               imap_new[(i*32)+(1*8)+3] = cpu_to_fdt32(2);
+               imap_new[(i*32)+(2*8)+3] = cpu_to_fdt32(3);
+               imap_new[(i*32)+(3*8)+3] = cpu_to_fdt32(4);
+               /* sixth cell is relative interrupt */
+               imap_new[(i*32)+(0*8)+6] = irq[(15-(12+i)+b+0)%4];
+               imap_new[(i*32)+(1*8)+6] = irq[(15-(12+i)+b+1)%4];
+               imap_new[(i*32)+(2*8)+6] = irq[(15-(12+i)+b+2)%4];
+               imap_new[(i*32)+(3*8)+6] = irq[(15-(12+i)+b+3)%4];
+       }
+       fdt_setprop(blob, np, "interrupt-map", imap_new,
+                   sizeof(imap_new));
+       reg[0] = cpu_to_fdt32(0xfff00);
+       reg[1] = 0;
+       reg[2] = 0;
+       reg[3] = cpu_to_fdt32(0x7);
+       fdt_setprop(blob, np, "interrupt-map-mask", reg, sizeof(reg));
+       fdt_setprop_cell(blob, np, "#interrupt-cells", 1);
+       fdt_setprop_string(blob, np, "device_type", "pci");
+       fdt_setprop_cell(blob, np, "#address-cells", 3);
+       fdt_setprop_cell(blob, np, "#size-cells", 2);
+       printf("   Added custom interrupt-map for GW16082\n");
+
+       return 0;
+}
+
+/*
+ * PCI DT nodes must be nested therefore if we need to apply a DT fixup
+ * we will walk the PCI bus and add bridge nodes up to the device receiving
+ * the fixup.
+ */
+void ft_board_pci_fixup(void *blob, bd_t *bd)
+{
+       int i, np;
+       struct pci_dev *dev;
+
+       for (i = 0; i < pci_devno; i++) {
+               dev = &pci_devs[i];
+
+               /*
+                * The GW16082 consists of a TI XIO2001 PCIe-to-PCI bridge and
+                * an EEPROM at i2c1-0x50.
+                */
+               if ((dev->vendor == PCI_VENDOR_ID_TI) &&
+                   (dev->device == 0x8240) &&
+                   (i2c_set_bus_num(1) == 0) &&
+                   (i2c_probe(0x50) == 0))
+               {
+                       np = fdt_add_pci_path(blob, dev);
+                       if (np > 0)
+                               fdt_fixup_gw16082(blob, np, dev);
+               }
+       }
+}
+#endif /* if defined(CONFIG_CMD_PCI) */
+
 /*
  * called prior to booting kernel or by 'fdt boardsetup' command
  *
@@ -976,6 +1199,11 @@ int ft_board_setup(void *blob, bd_t *bd)
                                "no-1-8-v");
        }
 
+#if defined(CONFIG_CMD_PCI)
+       if (!getenv("nopcifixup"))
+               ft_board_pci_fixup(blob, bd);
+#endif
+
        /*
         * Peripheral Config:
         *  remove nodes by alias path if EEPROM config tells us the