diff options
Diffstat (limited to 'drivers/pci/probe.c')
-rw-r--r-- | drivers/pci/probe.c | 293 |
1 files changed, 265 insertions, 28 deletions
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 446e4a94d7d..2a943090a3b 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -89,6 +89,7 @@ static void release_pcibus_dev(struct device *dev) if (pci_bus->bridge) put_device(pci_bus->bridge); + pci_bus_remove_resources(pci_bus); kfree(pci_bus); } @@ -281,26 +282,12 @@ static void pci_read_bases(struct pci_dev *dev, unsigned int howmany, int rom) } } -void __devinit pci_read_bridge_bases(struct pci_bus *child) +static void __devinit pci_read_bridge_io(struct pci_bus *child) { struct pci_dev *dev = child->self; u8 io_base_lo, io_limit_lo; - u16 mem_base_lo, mem_limit_lo; unsigned long base, limit; struct resource *res; - int i; - - if (pci_is_root_bus(child)) /* It's a host bus, nothing to read */ - return; - - dev_info(&dev->dev, "PCI bridge to [bus %02x-%02x]%s\n", - child->secondary, child->subordinate, - dev->transparent ? " (subtractive decode)": ""); - - if (dev->transparent) { - for(i = 3; i < PCI_BUS_NUM_RESOURCES; i++) - child->resource[i] = child->parent->resource[i - 3]; - } res = child->resource[0]; pci_read_config_byte(dev, PCI_IO_BASE, &io_base_lo); @@ -316,26 +303,50 @@ void __devinit pci_read_bridge_bases(struct pci_bus *child) limit |= (io_limit_hi << 16); } - if (base <= limit) { + if (base && base <= limit) { res->flags = (io_base_lo & PCI_IO_RANGE_TYPE_MASK) | IORESOURCE_IO; if (!res->start) res->start = base; if (!res->end) res->end = limit + 0xfff; dev_printk(KERN_DEBUG, &dev->dev, " bridge window %pR\n", res); + } else { + dev_printk(KERN_DEBUG, &dev->dev, + " bridge window [io %04lx - %04lx] reg reading\n", + base, limit); } +} + +static void __devinit pci_read_bridge_mmio(struct pci_bus *child) +{ + struct pci_dev *dev = child->self; + u16 mem_base_lo, mem_limit_lo; + unsigned long base, limit; + struct resource *res; res = child->resource[1]; pci_read_config_word(dev, PCI_MEMORY_BASE, &mem_base_lo); pci_read_config_word(dev, PCI_MEMORY_LIMIT, &mem_limit_lo); base = (mem_base_lo & PCI_MEMORY_RANGE_MASK) << 16; limit = (mem_limit_lo & PCI_MEMORY_RANGE_MASK) << 16; - if (base <= limit) { + if (base && base <= limit) { res->flags = (mem_base_lo & PCI_MEMORY_RANGE_TYPE_MASK) | IORESOURCE_MEM; res->start = base; res->end = limit + 0xfffff; dev_printk(KERN_DEBUG, &dev->dev, " bridge window %pR\n", res); + } else { + dev_printk(KERN_DEBUG, &dev->dev, + " bridge window [mem 0x%08lx - 0x%08lx] reg reading\n", + base, limit + 0xfffff); } +} + +static void __devinit pci_read_bridge_mmio_pref(struct pci_bus *child) +{ + struct pci_dev *dev = child->self; + u16 mem_base_lo, mem_limit_lo; + unsigned long base, limit; + struct resource *res; res = child->resource[2]; pci_read_config_word(dev, PCI_PREF_MEMORY_BASE, &mem_base_lo); @@ -366,7 +377,7 @@ void __devinit pci_read_bridge_bases(struct pci_bus *child) #endif } } - if (base <= limit) { + if (base && base <= limit) { res->flags = (mem_base_lo & PCI_PREF_RANGE_TYPE_MASK) | IORESOURCE_MEM | IORESOURCE_PREFETCH; if (res->flags & PCI_PREF_RANGE_TYPE_64) @@ -374,6 +385,44 @@ void __devinit pci_read_bridge_bases(struct pci_bus *child) res->start = base; res->end = limit + 0xfffff; dev_printk(KERN_DEBUG, &dev->dev, " bridge window %pR\n", res); + } else { + dev_printk(KERN_DEBUG, &dev->dev, + " bridge window [mem 0x%08lx - %08lx pref] reg reading\n", + base, limit + 0xfffff); + } +} + +void __devinit pci_read_bridge_bases(struct pci_bus *child) +{ + struct pci_dev *dev = child->self; + struct resource *res; + int i; + + if (pci_is_root_bus(child)) /* It's a host bus, nothing to read */ + return; + + dev_info(&dev->dev, "PCI bridge to [bus %02x-%02x]%s\n", + child->secondary, child->subordinate, + dev->transparent ? " (subtractive decode)" : ""); + + pci_bus_remove_resources(child); + for (i = 0; i < PCI_BRIDGE_RESOURCE_NUM; i++) + child->resource[i] = &dev->resource[PCI_BRIDGE_RESOURCES+i]; + + pci_read_bridge_io(child); + pci_read_bridge_mmio(child); + pci_read_bridge_mmio_pref(child); + + if (dev->transparent) { + pci_bus_for_each_resource(child->parent, res, i) { + if (res) { + pci_bus_add_resource(child, res, + PCI_SUBTRACTIVE_DECODE); + dev_printk(KERN_DEBUG, &dev->dev, + " bridge window %pR (subtractive decode)\n", + res); + } + } } } @@ -387,10 +436,147 @@ static struct pci_bus * pci_alloc_bus(void) INIT_LIST_HEAD(&b->children); INIT_LIST_HEAD(&b->devices); INIT_LIST_HEAD(&b->slots); + INIT_LIST_HEAD(&b->resources); + b->max_bus_speed = PCI_SPEED_UNKNOWN; + b->cur_bus_speed = PCI_SPEED_UNKNOWN; } return b; } +static unsigned char pcix_bus_speed[] = { + PCI_SPEED_UNKNOWN, /* 0 */ + PCI_SPEED_66MHz_PCIX, /* 1 */ + PCI_SPEED_100MHz_PCIX, /* 2 */ + PCI_SPEED_133MHz_PCIX, /* 3 */ + PCI_SPEED_UNKNOWN, /* 4 */ + PCI_SPEED_66MHz_PCIX_ECC, /* 5 */ + PCI_SPEED_100MHz_PCIX_ECC, /* 6 */ + PCI_SPEED_133MHz_PCIX_ECC, /* 7 */ + PCI_SPEED_UNKNOWN, /* 8 */ + PCI_SPEED_66MHz_PCIX_266, /* 9 */ + PCI_SPEED_100MHz_PCIX_266, /* A */ + PCI_SPEED_133MHz_PCIX_266, /* B */ + PCI_SPEED_UNKNOWN, /* C */ + PCI_SPEED_66MHz_PCIX_533, /* D */ + PCI_SPEED_100MHz_PCIX_533, /* E */ + PCI_SPEED_133MHz_PCIX_533 /* F */ +}; + +static unsigned char pcie_link_speed[] = { + PCI_SPEED_UNKNOWN, /* 0 */ + PCIE_SPEED_2_5GT, /* 1 */ + PCIE_SPEED_5_0GT, /* 2 */ + PCIE_SPEED_8_0GT, /* 3 */ + PCI_SPEED_UNKNOWN, /* 4 */ + PCI_SPEED_UNKNOWN, /* 5 */ + PCI_SPEED_UNKNOWN, /* 6 */ + PCI_SPEED_UNKNOWN, /* 7 */ + PCI_SPEED_UNKNOWN, /* 8 */ + PCI_SPEED_UNKNOWN, /* 9 */ + PCI_SPEED_UNKNOWN, /* A */ + PCI_SPEED_UNKNOWN, /* B */ + PCI_SPEED_UNKNOWN, /* C */ + PCI_SPEED_UNKNOWN, /* D */ + PCI_SPEED_UNKNOWN, /* E */ + PCI_SPEED_UNKNOWN /* F */ +}; + +void pcie_update_link_speed(struct pci_bus *bus, u16 linksta) +{ + bus->cur_bus_speed = pcie_link_speed[linksta & 0xf]; +} +EXPORT_SYMBOL_GPL(pcie_update_link_speed); + +static unsigned char agp_speeds[] = { + AGP_UNKNOWN, + AGP_1X, + AGP_2X, + AGP_4X, + AGP_8X +}; + +static enum pci_bus_speed agp_speed(int agp3, int agpstat) +{ + int index = 0; + + if (agpstat & 4) + index = 3; + else if (agpstat & 2) + index = 2; + else if (agpstat & 1) + index = 1; + else + goto out; + + if (agp3) { + index += 2; + if (index == 5) + index = 0; + } + + out: + return agp_speeds[index]; +} + + +static void pci_set_bus_speed(struct pci_bus *bus) +{ + struct pci_dev *bridge = bus->self; + int pos; + + pos = pci_find_capability(bridge, PCI_CAP_ID_AGP); + if (!pos) + pos = pci_find_capability(bridge, PCI_CAP_ID_AGP3); + if (pos) { + u32 agpstat, agpcmd; + + pci_read_config_dword(bridge, pos + PCI_AGP_STATUS, &agpstat); + bus->max_bus_speed = agp_speed(agpstat & 8, agpstat & 7); + + pci_read_config_dword(bridge, pos + PCI_AGP_COMMAND, &agpcmd); + bus->cur_bus_speed = agp_speed(agpstat & 8, agpcmd & 7); + } + + pos = pci_find_capability(bridge, PCI_CAP_ID_PCIX); + if (pos) { + u16 status; + enum pci_bus_speed max; + pci_read_config_word(bridge, pos + 2, &status); + + if (status & 0x8000) { + max = PCI_SPEED_133MHz_PCIX_533; + } else if (status & 0x4000) { + max = PCI_SPEED_133MHz_PCIX_266; + } else if (status & 0x0002) { + if (((status >> 12) & 0x3) == 2) { + max = PCI_SPEED_133MHz_PCIX_ECC; + } else { + max = PCI_SPEED_133MHz_PCIX; + } + } else { + max = PCI_SPEED_66MHz_PCIX; + } + + bus->max_bus_speed = max; + bus->cur_bus_speed = pcix_bus_speed[(status >> 6) & 0xf]; + + return; + } + + pos = pci_find_capability(bridge, PCI_CAP_ID_EXP); + if (pos) { + u32 linkcap; + u16 linksta; + + pci_read_config_dword(bridge, pos + PCI_EXP_LNKCAP, &linkcap); + bus->max_bus_speed = pcie_link_speed[linkcap & 0xf]; + + pci_read_config_word(bridge, pos + PCI_EXP_LNKSTA, &linksta); + pcie_update_link_speed(bus, linksta); + } +} + + static struct pci_bus *pci_alloc_child_bus(struct pci_bus *parent, struct pci_dev *bridge, int busnr) { @@ -430,6 +616,8 @@ static struct pci_bus *pci_alloc_child_bus(struct pci_bus *parent, child->self = bridge; child->bridge = get_device(&bridge->dev); + pci_set_bus_speed(child); + /* Set up default resource pointers and names.. */ for (i = 0; i < PCI_BRIDGE_RESOURCE_NUM; i++) { child->resource[i] = &bridge->resource[PCI_BRIDGE_RESOURCES+i]; @@ -1081,6 +1269,45 @@ struct pci_dev *__ref pci_scan_single_device(struct pci_bus *bus, int devfn) } EXPORT_SYMBOL(pci_scan_single_device); +static unsigned next_ari_fn(struct pci_dev *dev, unsigned fn) +{ + u16 cap; + unsigned pos, next_fn; + + if (!dev) + return 0; + + pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ARI); + if (!pos) + return 0; + pci_read_config_word(dev, pos + 4, &cap); + next_fn = cap >> 8; + if (next_fn <= fn) + return 0; + return next_fn; +} + +static unsigned next_trad_fn(struct pci_dev *dev, unsigned fn) +{ + return (fn + 1) % 8; +} + +static unsigned no_next_fn(struct pci_dev *dev, unsigned fn) +{ + return 0; +} + +static int only_one_child(struct pci_bus *bus) +{ + struct pci_dev *parent = bus->self; + if (!parent || !pci_is_pcie(parent)) + return 0; + if (parent->pcie_type == PCI_EXP_TYPE_ROOT_PORT || + parent->pcie_type == PCI_EXP_TYPE_DOWNSTREAM) + return 1; + return 0; +} + /** * pci_scan_slot - scan a PCI slot on a bus for devices. * @bus: PCI bus to scan @@ -1094,21 +1321,30 @@ EXPORT_SYMBOL(pci_scan_single_device); */ int pci_scan_slot(struct pci_bus *bus, int devfn) { - int fn, nr = 0; + unsigned fn, nr = 0; struct pci_dev *dev; + unsigned (*next_fn)(struct pci_dev *, unsigned) = no_next_fn; + + if (only_one_child(bus) && (devfn > 0)) + return 0; /* Already scanned the entire slot */ dev = pci_scan_single_device(bus, devfn); - if (dev && !dev->is_added) /* new device? */ + if (!dev) + return 0; + if (!dev->is_added) nr++; - if (dev && dev->multifunction) { - for (fn = 1; fn < 8; fn++) { - dev = pci_scan_single_device(bus, devfn + fn); - if (dev) { - if (!dev->is_added) - nr++; - dev->multifunction = 1; - } + if (pci_ari_enabled(bus)) + next_fn = next_ari_fn; + else if (dev->multifunction) + next_fn = next_trad_fn; + + for (fn = next_fn(dev, 0); fn > 0; fn = next_fn(dev, fn)) { + dev = pci_scan_single_device(bus, devfn + fn); + if (dev) { + if (!dev->is_added) + nr++; + dev->multifunction = 1; } } @@ -1200,6 +1436,7 @@ struct pci_bus * pci_create_bus(struct device *parent, if (error) goto dev_reg_err; b->bridge = get_device(dev); + device_enable_async_suspend(b->bridge); if (!parent) set_dev_node(b->bridge, pcibus_to_node(b)); |