From dcad7abc47f78d401519e64a6d917a2835af26d5 Mon Sep 17 00:00:00 2001 From: Lucas Stach Date: Wed, 1 Aug 2018 10:38:29 +0200 Subject: PCI: link PCI devices with potentially existing OF nodes The document "PCI Bus Binding to: IEEE Std 1275-1994 Standard for Boot (Initialization Configuration) Firmware" describes how the PCI topology can be described in the DT, in order to augment the PCI devices with additional information via DT properties. This patch links OF nodes to the corresponding PCI devices if they exist, allowing PCI device drivers to query DT information like any platform device. Signed-off-by: Lucas Stach Signed-off-by: Sascha Hauer --- drivers/pci/pci.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index b2570eb151..bd8b7278ef 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -331,6 +331,31 @@ static void postscan_setup_bridge(struct pci_dev *dev) } } +static struct device_node * +pci_of_match_device(struct device_d *parent, unsigned int devfn) +{ + struct device_node *np; + u32 reg; + + if (!IS_ENABLED(CONFIG_OFTREE) || !parent->device_node) + return NULL; + + for_each_child_of_node(parent->device_node, np) { + if (!of_property_read_u32_array(np, "reg", ®, 1)) { + /* + * Only match device/function pair of the device + * address, other properties are defined by the + * PCI/OF node topology. + */ + reg = (reg >> 8) & 0xffff; + if (reg == devfn) + return np; + } + } + + return NULL; +} + unsigned int pci_scan_bus(struct pci_bus *bus) { struct pci_dev *dev; @@ -368,6 +393,11 @@ unsigned int pci_scan_bus(struct pci_bus *bus) dev->vendor = l & 0xffff; dev->device = (l >> 16) & 0xffff; dev->dev.parent = bus->parent; + dev->dev.device_node = pci_of_match_device(bus->parent, devfn); + if (dev->dev.device_node) + pr_debug("found DT node %s for device %04x:%04x\n", + dev->dev.device_node->full_name, + dev->vendor, dev->device); /* non-destructively determine if device can be a master: */ pci_read_config_byte(dev, PCI_COMMAND, &cmd); -- cgit v1.2.3 From fc2c618c2038dda38830d1bfaea7af5b38944f3f Mon Sep 17 00:00:00 2001 From: Lucas Stach Date: Wed, 1 Aug 2018 10:38:30 +0200 Subject: pci: add quirk infrastructure This is a cut down version of the Linux kernel PCI quirk infrastructure, which allows to register and execute some fixups before the driver is loaded. Signed-off-by: Lucas Stach Signed-off-by: Sascha Hauer --- arch/arm/lib32/barebox.lds.S | 12 +++++++++ drivers/pci/pci.c | 63 +++++++++++++++++++++++++++++++++++++++++--- include/linux/pci.h | 54 +++++++++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 4 deletions(-) diff --git a/arch/arm/lib32/barebox.lds.S b/arch/arm/lib32/barebox.lds.S index 594bf56837..7230e5f31f 100644 --- a/arch/arm/lib32/barebox.lds.S +++ b/arch/arm/lib32/barebox.lds.S @@ -106,6 +106,18 @@ SECTIONS __usymtab : { BAREBOX_SYMS } __usymtab_end = .; +#ifdef CONFIG_PCI + __start_pci_fixups_early = .; + .pci_fixup_early : { KEEP(*(.pci_fixup_early)) } + __end_pci_fixups_early = .; + __start_pci_fixups_header = .; + .pci_fixup_header : { KEEP(*(.pci_fixup_header)) } + __end_pci_fixups_header = .; + __start_pci_fixups_enable = .; + .pci_fixup_enable : { KEEP(*(.pci_fixup_enable)) } + __end_pci_fixups_enable = .; +#endif + .oftables : { BAREBOX_CLK_TABLE() } .dtb : { BAREBOX_DTB() } diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index bd8b7278ef..d206c53848 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -253,6 +253,8 @@ static void setup_device(struct pci_dev *dev, int max_bar) } } + pci_fixup_device(pci_fixup_header, dev); + pci_write_config_byte(dev, PCI_COMMAND, cmd); list_add_tail(&dev->bus_list, &dev->bus->devices); } @@ -412,6 +414,10 @@ unsigned int pci_scan_bus(struct pci_bus *bus) class >>= 8; dev->hdr_type = hdr_type; + pci_fixup_device(pci_fixup_early, dev); + /* the fixup may have changed the device class */ + class = dev->class >> 8; + pr_debug("class = %08x, hdr_type = %08x\n", class, hdr_type); pr_debug("%02x:%02x [%04x:%04x]\n", bus->number, dev->devfn, dev->vendor, dev->device); @@ -519,12 +525,61 @@ EXPORT_SYMBOL(pci_clear_master); */ int pci_enable_device(struct pci_dev *dev) { + int ret; u32 t; pci_read_config_dword(dev, PCI_COMMAND, &t); - return pci_write_config_dword(dev, PCI_COMMAND, t - | PCI_COMMAND_IO - | PCI_COMMAND_MEMORY - ); + ret = pci_write_config_dword(dev, PCI_COMMAND, + t | PCI_COMMAND_IO | PCI_COMMAND_MEMORY); + if (ret) + return ret; + + pci_fixup_device(pci_fixup_enable, dev); + + return 0; } EXPORT_SYMBOL(pci_enable_device); + +static void pci_do_fixups(struct pci_dev *dev, struct pci_fixup *f, + struct pci_fixup *end) +{ + for (; f < end; f++) + if ((f->class == (u32) (dev->class >> f->class_shift) || + f->class == (u32) PCI_ANY_ID) && + (f->vendor == dev->vendor || + f->vendor == (u16) PCI_ANY_ID) && + (f->device == dev->device || + f->device == (u16) PCI_ANY_ID)) { + f->hook(dev); + } +} + +extern struct pci_fixup __start_pci_fixups_early[]; +extern struct pci_fixup __end_pci_fixups_early[]; +extern struct pci_fixup __start_pci_fixups_header[]; +extern struct pci_fixup __end_pci_fixups_header[]; +extern struct pci_fixup __start_pci_fixups_enable[]; +extern struct pci_fixup __end_pci_fixups_enable[]; + +void pci_fixup_device(enum pci_fixup_pass pass, struct pci_dev *dev) +{ + struct pci_fixup *start, *end; + + switch (pass) { + case pci_fixup_early: + start = __start_pci_fixups_early; + end = __end_pci_fixups_early; + break; + case pci_fixup_header: + start = __start_pci_fixups_header; + end = __end_pci_fixups_header; + break; + case pci_fixup_enable: + start = __start_pci_fixups_enable; + end = __end_pci_fixups_enable; + break; + default: + unreachable(); + } + pci_do_fixups(dev, start, end); +} diff --git a/include/linux/pci.h b/include/linux/pci.h index 152ba10a04..82f27f21b2 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -29,6 +29,7 @@ #include +#define PCI_ANY_ID (~0) /* * The PCI interface treats multi-function devices as independent @@ -299,4 +300,57 @@ int pci_enable_device(struct pci_dev *dev); extern void __iomem *pci_iomap(struct pci_dev *dev, int bar); +/* + * The world is not perfect and supplies us with broken PCI devices. + * For at least a part of these bugs we need a work-around, so both + * generic (drivers/pci/quirks.c) and per-architecture code can define + * fixup hooks to be called for particular buggy devices. + */ + +struct pci_fixup { + u16 vendor; /* Or PCI_ANY_ID */ + u16 device; /* Or PCI_ANY_ID */ + u32 class; /* Or PCI_ANY_ID */ + unsigned int class_shift; /* should be 0, 8, 16 */ + void (*hook)(struct pci_dev *dev); +}; + +enum pci_fixup_pass { + pci_fixup_early, /* Before probing BARs */ + pci_fixup_header, /* After reading configuration header */ + pci_fixup_enable, /* pci_enable_device() time */ +}; + +/* Anonymous variables would be nice... */ +#define DECLARE_PCI_FIXUP_SECTION(section, name, vendor, device, class, \ + class_shift, hook) \ + static const struct pci_fixup __PASTE(__pci_fixup_##name,__LINE__) __used \ + __attribute__((__section__(#section), aligned((sizeof(void *))))) \ + = { vendor, device, class, class_shift, hook }; + +#define DECLARE_PCI_FIXUP_CLASS_EARLY(vendor, device, class, \ + class_shift, hook) \ + DECLARE_PCI_FIXUP_SECTION(.pci_fixup_early, \ + hook, vendor, device, class, class_shift, hook) +#define DECLARE_PCI_FIXUP_CLASS_HEADER(vendor, device, class, \ + class_shift, hook) \ + DECLARE_PCI_FIXUP_SECTION(.pci_fixup_header, \ + hook, vendor, device, class, class_shift, hook) +#define DECLARE_PCI_FIXUP_CLASS_ENABLE(vendor, device, class, \ + class_shift, hook) \ + DECLARE_PCI_FIXUP_SECTION(.pci_fixup_enable, \ + hook, vendor, device, class, class_shift, hook) + +#define DECLARE_PCI_FIXUP_EARLY(vendor, device, hook) \ + DECLARE_PCI_FIXUP_SECTION(.pci_fixup_early, \ + hook, vendor, device, PCI_ANY_ID, 0, hook) +#define DECLARE_PCI_FIXUP_HEADER(vendor, device, hook) \ + DECLARE_PCI_FIXUP_SECTION(.pci_fixup_header, \ + hook, vendor, device, PCI_ANY_ID, 0, hook) +#define DECLARE_PCI_FIXUP_ENABLE(vendor, device, hook) \ + DECLARE_PCI_FIXUP_SECTION(.pci_fixup_enable, \ + hook, vendor, device, PCI_ANY_ID, 0, hook) + +void pci_fixup_device(enum pci_fixup_pass pass, struct pci_dev *dev); + #endif /* LINUX_PCI_H */ -- cgit v1.2.3 From f345734707954466350b9d0f8e478ed06951a402 Mon Sep 17 00:00:00 2001 From: Lucas Stach Date: Wed, 1 Aug 2018 10:38:31 +0200 Subject: ARM: imx6: gw54xx: add fixup for PCIe switch After the PCIe switch has been scanned its GPIOs need to be configured as output-high to release the devices behind the switch from their reset state and make them discoverable to the bus scan. Signed-off-by: Lucas Stach Signed-off-by: Sascha Hauer --- arch/arm/boards/gateworks-ventana/board.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/arch/arm/boards/gateworks-ventana/board.c b/arch/arm/boards/gateworks-ventana/board.c index 3ff142ee42..6f9e0343be 100644 --- a/arch/arm/boards/gateworks-ventana/board.c +++ b/arch/arm/boards/gateworks-ventana/board.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -111,3 +112,31 @@ static int gw54xx_coredevices_init(void) return 0; } coredevice_initcall(gw54xx_coredevices_init); + +/* + * fixup for PLX PEX8909 bridge to configure GPIO1-7 as output High + * as they are used for slots1-7 PERST# + */ +static void ventana_pciesw_early_fixup(struct pci_dev *dev) +{ + u32 dw; + + if (!of_machine_is_compatible("gw,ventana")) + return; + + if (dev->devfn != 0) + return; + + pci_read_config_dword(dev, 0x62c, &dw); + dw |= 0xaaa8; // GPIO1-7 outputs + pci_write_config_dword(dev, 0x62c, dw); + + pci_read_config_dword(dev, 0x644, &dw); + dw |= 0xfe; // GPIO1-7 output high + pci_write_config_dword(dev, 0x644, dw); + + mdelay(100); +} +DECLARE_PCI_FIXUP_EARLY(0x10b5, 0x8609, ventana_pciesw_early_fixup); +DECLARE_PCI_FIXUP_EARLY(0x10b5, 0x8606, ventana_pciesw_early_fixup); +DECLARE_PCI_FIXUP_EARLY(0x10b5, 0x8604, ventana_pciesw_early_fixup); -- cgit v1.2.3