diff --git a/common/cmd_tsi148.c b/common/cmd_tsi148.c index dc488b2..ea96d0f 100644 --- a/common/cmd_tsi148.c +++ b/common/cmd_tsi148.c @@ -16,8 +16,8 @@ #include -#define PCI_VENDOR PCI_VENDOR_ID_TUNDRA -#define PCI_DEVICE PCI_DEVICE_ID_TUNDRA_TSI148 +#define LPCI_VENDOR PCI_VENDOR_ID_TUNDRA +#define LPCI_DEVICE PCI_DEVICE_ID_TUNDRA_TSI148 typedef struct _TSI148_DEV TSI148_DEV; @@ -41,7 +41,7 @@ int tsi148_init(void) pci_dev_t busdevfn; unsigned int val; - busdevfn = pci_find_device(PCI_VENDOR, PCI_DEVICE, 0); + busdevfn = pci_find_device(LPCI_VENDOR, LPCI_DEVICE, 0); if (busdevfn == -1) { puts("Tsi148: No Tundra Tsi148 found!\n"); return -1; @@ -68,7 +68,7 @@ int tsi148_init(void) /* check mapping */ debug("Tsi148: Read via mapping, PCI_ID = %08X\n", readl(&dev->uregs->pci_id)); - if (((PCI_DEVICE << 16) | PCI_VENDOR) != readl(&dev->uregs->pci_id)) { + if (((LPCI_DEVICE << 16) | LPCI_VENDOR) != readl(&dev->uregs->pci_id)) { printf("Tsi148: Cannot read PCI-ID via Mapping: %08x\n", readl(&dev->uregs->pci_id)); result = -1; diff --git a/drivers/pci/pci-uclass.c b/drivers/pci/pci-uclass.c index 5b91fe3..41daa0d 100644 --- a/drivers/pci/pci-uclass.c +++ b/drivers/pci/pci-uclass.c @@ -353,6 +353,101 @@ int dm_pci_hose_probe_bus(struct pci_controller *hose, pci_dev_t bdf) return sub_bus; } +/** + * pci_match_one_device - Tell if a PCI device structure has a matching + * PCI device id structure + * @id: single PCI device id structure to match + * @dev: the PCI device structure to match against + * + * Returns the matching pci_device_id structure or %NULL if there is no match. + */ +static bool pci_match_one_id(const struct pci_device_id *id, + const struct pci_device_id *find) +{ + if ((id->vendor == PCI_ANY_ID || id->vendor == find->vendor) && + (id->device == PCI_ANY_ID || id->device == find->device) && + (id->subvendor == PCI_ANY_ID || id->subvendor == find->subvendor) && + (id->subdevice == PCI_ANY_ID || id->subdevice == find->subdevice) && + !((id->class ^ find->class) & id->class_mask)) + return true; + + return false; +} + +/** + * pci_find_and_bind_driver() - Find and bind the right PCI driver + * + * This only looks at certain fields in the descriptor. + */ +static int pci_find_and_bind_driver(struct udevice *parent, + struct pci_device_id *find_id, int devfn, + struct udevice **devp) +{ + struct pci_driver_entry *start, *entry; + const char *drv; + int n_ents; + int ret; + char name[30], *str; + + *devp = NULL; + + debug("%s: Searching for driver: vendor=%x, device=%x\n", __func__, + find_id->vendor, find_id->device); + start = ll_entry_start(struct pci_driver_entry, pci_driver_entry); + n_ents = ll_entry_count(struct pci_driver_entry, pci_driver_entry); + for (entry = start; entry != start + n_ents; entry++) { + const struct pci_device_id *id; + struct udevice *dev; + const struct driver *drv; + + for (id = entry->match; + id->vendor || id->subvendor || id->class_mask; + id++) { + if (!pci_match_one_id(id, find_id)) + continue; + + drv = entry->driver; + /* + * We could pass the descriptor to the driver as + * platdata (instead of NULL) and allow its bind() + * method to return -ENOENT if it doesn't support this + * device. That way we could continue the search to + * find another driver. For now this doesn't seem + * necesssary, so just bind the first match. + */ + ret = device_bind(parent, drv, drv->name, NULL, -1, + &dev); + if (ret) + goto error; + debug("%s: Match found: %s\n", __func__, drv->name); + dev->driver_data = find_id->driver_data; + *devp = dev; + return 0; + } + } + + /* Bind a generic driver so that the device can be used */ + sprintf(name, "pci_%x:%x.%x", parent->seq, PCI_DEV(devfn), + PCI_FUNC(devfn)); + str = strdup(name); + if (!str) + return -ENOMEM; + drv = (find_id->class >> 8) == PCI_CLASS_BRIDGE_PCI ? "pci_bridge_drv" : + "pci_generic_drv"; + ret = device_bind_driver(parent, drv, str, devp); + if (ret) { + debug("%s: Failed to bind generic driver: %d", __func__, ret); + return ret; + } + debug("%s: No match found: bound generic driver instead\n", __func__); + + return 0; + +error: + debug("%s: No match found: error %d\n", __func__, ret); + return ret; +} + int pci_bind_bus_devices(struct udevice *bus) { ulong vendor, device; @@ -387,25 +482,33 @@ int pci_bind_bus_devices(struct udevice *bus) bus->seq, bus->name, PCI_DEV(devfn), PCI_FUNC(devfn)); pci_bus_read_config(bus, devfn, PCI_DEVICE_ID, &device, PCI_SIZE_16); - pci_bus_read_config(bus, devfn, PCI_CLASS_DEVICE, &class, - PCI_SIZE_16); + pci_bus_read_config(bus, devfn, PCI_CLASS_REVISION, &class, + PCI_SIZE_32); + class >>= 8; /* Find this device in the device tree */ ret = pci_bus_find_devfn(bus, devfn, &dev); + /* Search for a driver */ + /* If nothing in the device tree, bind a generic device */ if (ret == -ENODEV) { - char name[30], *str; - const char *drv; - - sprintf(name, "pci_%x:%x.%x", bus->seq, - PCI_DEV(devfn), PCI_FUNC(devfn)); - str = strdup(name); - if (!str) - return -ENOMEM; - drv = class == PCI_CLASS_BRIDGE_PCI ? - "pci_bridge_drv" : "pci_generic_drv"; - ret = device_bind_driver(bus, drv, str, &dev); + struct pci_device_id find_id; + ulong val; + + memset(&find_id, '\0', sizeof(find_id)); + find_id.vendor = vendor; + find_id.device = device; + find_id.class = class; + if ((header_type & 0x7f) == PCI_HEADER_TYPE_NORMAL) { + pci_bus_read_config(bus, devfn, + PCI_SUBSYSTEM_VENDOR_ID, + &val, PCI_SIZE_32); + find_id.subvendor = val & 0xffff; + find_id.subdevice = val >> 16; + } + ret = pci_find_and_bind_driver(bus, &find_id, devfn, + &dev); } if (ret) return ret; diff --git a/include/pci.h b/include/pci.h index 542e68b..021928f 100644 --- a/include/pci.h +++ b/include/pci.h @@ -468,7 +468,10 @@ typedef int pci_dev_t; #define PCI_ANY_ID (~0) struct pci_device_id { - unsigned int vendor, device; /* Vendor and device ID or PCI_ANY_ID */ + unsigned int vendor, device; /* Vendor and device ID or PCI_ANY_ID */ + unsigned int subvendor, subdevice; /* Subsystem ID's or PCI_ANY_ID */ + unsigned int class, class_mask; /* (class,subclass,prog-if) triplet */ + unsigned long driver_data; /* Data private to the driver */ }; struct pci_controller; @@ -1101,7 +1104,79 @@ struct dm_pci_emul_ops { int sandbox_pci_get_emul(struct udevice *bus, pci_dev_t find_devfn, struct udevice **emulp); -#endif +#endif /* CONFIG_DM_PCI */ + +/** + * PCI_DEVICE - macro used to describe a specific pci device + * @vend: the 16 bit PCI Vendor ID + * @dev: the 16 bit PCI Device ID + * + * This macro is used to create a struct pci_device_id that matches a + * specific device. The subvendor and subdevice fields will be set to + * PCI_ANY_ID. + */ +#define PCI_DEVICE(vend, dev) \ + .vendor = (vend), .device = (dev), \ + .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID + +/** + * PCI_DEVICE_SUB - macro used to describe a specific pci device with subsystem + * @vend: the 16 bit PCI Vendor ID + * @dev: the 16 bit PCI Device ID + * @subvend: the 16 bit PCI Subvendor ID + * @subdev: the 16 bit PCI Subdevice ID + * + * This macro is used to create a struct pci_device_id that matches a + * specific device with subsystem information. + */ +#define PCI_DEVICE_SUB(vend, dev, subvend, subdev) \ + .vendor = (vend), .device = (dev), \ + .subvendor = (subvend), .subdevice = (subdev) + +/** + * PCI_DEVICE_CLASS - macro used to describe a specific pci device class + * @dev_class: the class, subclass, prog-if triple for this device + * @dev_class_mask: the class mask for this device + * + * This macro is used to create a struct pci_device_id that matches a + * specific PCI class. The vendor, device, subvendor, and subdevice + * fields will be set to PCI_ANY_ID. + */ +#define PCI_DEVICE_CLASS(dev_class, dev_class_mask) \ + .class = (dev_class), .class_mask = (dev_class_mask), \ + .vendor = PCI_ANY_ID, .device = PCI_ANY_ID, \ + .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID + +/** + * PCI_VDEVICE - macro used to describe a specific pci device in short form + * @vend: the vendor name + * @dev: the 16 bit PCI Device ID + * + * This macro is used to create a struct pci_device_id that matches a + * specific PCI device. The subvendor, and subdevice fields will be set + * to PCI_ANY_ID. The macro allows the next field to follow as the device + * private data. + */ + +#define PCI_VDEVICE(vend, dev) \ + .vendor = PCI_VENDOR_ID_##vend, .device = (dev), \ + .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, 0, 0 + +/** + * struct pci_driver_entry - Matches a driver to its pci_device_id list + * @driver: Driver to use + * @match: List of match records for this driver, terminated by {} + */ +struct pci_driver_entry { + struct driver *driver; + const struct pci_device_id *match; +}; + +#define U_BOOT_PCI_DEVICE(__name, __match) \ + ll_entry_declare(struct pci_driver_entry, __name, pci_driver_entry) = {\ + .driver = llsym(struct driver, __name, driver), \ + .match = __match, \ + } #endif /* __ASSEMBLY__ */ #endif /* _PCI_H */