diff --git a/arch/arm/boards/gateworks-ventana/board.c b/arch/arm/boards/gateworks-ventana/board.c index 3ff142e..6f9e034 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 @@ 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); diff --git a/arch/arm/lib32/barebox.lds.S b/arch/arm/lib32/barebox.lds.S index 594bf56..7230e5f 100644 --- a/arch/arm/lib32/barebox.lds.S +++ b/arch/arm/lib32/barebox.lds.S @@ -106,6 +106,18 @@ __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 b2570eb..d206c53 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -253,6 +253,8 @@ } } + pci_fixup_device(pci_fixup_header, dev); + pci_write_config_byte(dev, PCI_COMMAND, cmd); list_add_tail(&dev->bus_list, &dev->bus->devices); } @@ -331,6 +333,31 @@ } } +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 +395,11 @@ 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); @@ -382,6 +414,10 @@ 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); @@ -489,12 +525,61 @@ */ 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 152ba10..82f27f2 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 @@ 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 */