diff options
-rw-r--r-- | drivers/pci/pci_tegra.c | 3 | ||||
-rw-r--r-- | include/fdtdec.h | 109 | ||||
-rw-r--r-- | lib/fdtdec.c | 171 |
3 files changed, 257 insertions, 26 deletions
diff --git a/drivers/pci/pci_tegra.c b/drivers/pci/pci_tegra.c index a03ad5f..f9e05ad 100644 --- a/drivers/pci/pci_tegra.c +++ b/drivers/pci/pci_tegra.c @@ -458,6 +458,7 @@ static int tegra_pcie_parse_port_info(const void *fdt, int node, unsigned int *index, unsigned int *lanes) { + struct fdt_pci_addr addr; pci_dev_t bdf; int err; @@ -469,7 +470,7 @@ static int tegra_pcie_parse_port_info(const void *fdt, int node, *lanes = err; - err = fdtdec_pci_get_bdf(fdt, node, &bdf); + err = fdtdec_get_pci_bdf(fdt, node, &addr, &bdf); if (err < 0) { error("failed to parse \"reg\" property"); return err; diff --git a/include/fdtdec.h b/include/fdtdec.h index 5effa24..75af750 100644 --- a/include/fdtdec.h +++ b/include/fdtdec.h @@ -15,6 +15,7 @@ */ #include <libfdt.h> +#include <pci.h> /* * A typedef for a physical address. Note that fdt data is always big @@ -50,6 +51,49 @@ struct fdt_resource { fdt_addr_t end; }; +enum fdt_pci_space { + FDT_PCI_SPACE_CONFIG = 0, + FDT_PCI_SPACE_IO = 0x01000000, + FDT_PCI_SPACE_MEM32 = 0x02000000, + FDT_PCI_SPACE_MEM64 = 0x03000000, + FDT_PCI_SPACE_MEM32_PREF = 0x42000000, + FDT_PCI_SPACE_MEM64_PREF = 0x43000000, +}; + +#define FDT_PCI_ADDR_CELLS 3 +#define FDT_PCI_SIZE_CELLS 2 +#define FDT_PCI_REG_SIZE \ + ((FDT_PCI_ADDR_CELLS + FDT_PCI_SIZE_CELLS) * sizeof(u32)) + +/* + * The Open Firmware spec defines PCI physical address as follows: + * + * bits# 31 .... 24 23 .... 16 15 .... 08 07 .... 00 + * + * phys.hi cell: npt000ss bbbbbbbb dddddfff rrrrrrrr + * phys.mid cell: hhhhhhhh hhhhhhhh hhhhhhhh hhhhhhhh + * phys.lo cell: llllllll llllllll llllllll llllllll + * + * where: + * + * n: is 0 if the address is relocatable, 1 otherwise + * p: is 1 if addressable region is prefetchable, 0 otherwise + * t: is 1 if the address is aliased (for non-relocatable I/O) below 1MB + * (for Memory), or below 64KB (for relocatable I/O) + * ss: is the space code, denoting the address space + * bbbbbbbb: is the 8-bit Bus Number + * ddddd: is the 5-bit Device Number + * fff: is the 3-bit Function Number + * rrrrrrrr: is the 8-bit Register Number + * hhhhhhhh: is a 32-bit unsigned number + * llllllll: is a 32-bit unsigned number + */ +struct fdt_pci_addr { + u32 phys_hi; + u32 phys_mid; + u32 phys_lo; +}; + /** * Compute the size of a resource. * @@ -258,6 +302,60 @@ fdt_addr_t fdtdec_get_addr_size(const void *blob, int node, const char *prop_name, fdt_size_t *sizep); /** + * Look at an address property in a node and return the pci address which + * corresponds to the given type in the form of fdt_pci_addr. + * The property must hold one fdt_pci_addr with a lengh. + * + * @param blob FDT blob + * @param node node to examine + * @param type pci address type (FDT_PCI_SPACE_xxx) + * @param prop_name name of property to find + * @param addr returns pci address in the form of fdt_pci_addr + * @return 0 if ok, negative on error + */ +int fdtdec_get_pci_addr(const void *blob, int node, enum fdt_pci_space type, + const char *prop_name, struct fdt_pci_addr *addr); + +/** + * Look at the compatible property of a device node that represents a PCI + * device and extract pci vendor id and device id from it. + * + * @param blob FDT blob + * @param node node to examine + * @param vendor vendor id of the pci device + * @param device device id of the pci device + * @return 0 if ok, negative on error + */ +int fdtdec_get_pci_vendev(const void *blob, int node, + u16 *vendor, u16 *device); + +/** + * Look at the pci address of a device node that represents a PCI device + * and parse the bus, device and function number from it. + * + * @param blob FDT blob + * @param node node to examine + * @param addr pci address in the form of fdt_pci_addr + * @param bdf returns bus, device, function triplet + * @return 0 if ok, negative on error + */ +int fdtdec_get_pci_bdf(const void *blob, int node, + struct fdt_pci_addr *addr, pci_dev_t *bdf); + +/** + * Look at the pci address of a device node that represents a PCI device + * and return base address of the pci device's registers. + * + * @param blob FDT blob + * @param node node to examine + * @param addr pci address in the form of fdt_pci_addr + * @param bar returns base address of the pci device's registers + * @return 0 if ok, negative on error + */ +int fdtdec_get_pci_bar32(const void *blob, int node, + struct fdt_pci_addr *addr, u32 *bar); + +/** * Look up a 32-bit integer property in a node and return it. The property * must have at least 4 bytes of data. The value of the first cell is * returned. @@ -683,17 +781,6 @@ int fdt_get_named_resource(const void *fdt, int node, const char *property, struct fdt_resource *res); /** - * Look at the reg property of a device node that represents a PCI device - * and parse the bus, device and function number from it. - * - * @param fdt FDT blob - * @param node node to examine - * @param bdf returns bus, device, function triplet - * @return 0 if ok, negative on error - */ -int fdtdec_pci_get_bdf(const void *fdt, int node, int *bdf); - -/** * Decode a named region within a memory bank of a given type. * * This function handles selection of a memory region. The region is diff --git a/lib/fdtdec.c b/lib/fdtdec.c index 745b390..487122e 100644 --- a/lib/fdtdec.c +++ b/lib/fdtdec.c @@ -126,6 +126,163 @@ fdt_addr_t fdtdec_get_addr(const void *blob, int node, return fdtdec_get_addr_size(blob, node, prop_name, NULL); } +#ifdef CONFIG_PCI +int fdtdec_get_pci_addr(const void *blob, int node, enum fdt_pci_space type, + const char *prop_name, struct fdt_pci_addr *addr) +{ + const u32 *cell; + int len; + int ret = -ENOENT; + + debug("%s: %s: ", __func__, prop_name); + + /* + * If we follow the pci bus bindings strictly, we should check + * the value of the node's parent node's #address-cells and + * #size-cells. They need to be 3 and 2 accordingly. However, + * for simplicity we skip the check here. + */ + cell = fdt_getprop(blob, node, prop_name, &len); + if (!cell) + goto fail; + + if ((len % FDT_PCI_REG_SIZE) == 0) { + int num = len / FDT_PCI_REG_SIZE; + int i; + + for (i = 0; i < num; i++) { + debug("pci address #%d: %08lx %08lx %08lx\n", i, + (ulong)fdt_addr_to_cpu(cell[0]), + (ulong)fdt_addr_to_cpu(cell[1]), + (ulong)fdt_addr_to_cpu(cell[2])); + if ((fdt_addr_to_cpu(*cell) & type) == type) { + addr->phys_hi = fdt_addr_to_cpu(cell[0]); + addr->phys_mid = fdt_addr_to_cpu(cell[1]); + addr->phys_lo = fdt_addr_to_cpu(cell[2]); + break; + } else { + cell += (FDT_PCI_ADDR_CELLS + + FDT_PCI_SIZE_CELLS); + } + } + + if (i == num) + goto fail; + + return 0; + } else { + ret = -EINVAL; + } + +fail: + debug("(not found)\n"); + return ret; +} + +int fdtdec_get_pci_vendev(const void *blob, int node, u16 *vendor, u16 *device) +{ + const char *list, *end; + int len; + + list = fdt_getprop(blob, node, "compatible", &len); + if (!list) + return -ENOENT; + + end = list + len; + while (list < end) { + char *s; + + len = strlen(list); + if (len >= strlen("pciVVVV,DDDD")) { + s = strstr(list, "pci"); + + /* + * check if the string is something like pciVVVV,DDDD.RR + * or just pciVVVV,DDDD + */ + if (s && s[7] == ',' && + (s[12] == '.' || s[12] == 0)) { + s += 3; + *vendor = simple_strtol(s, NULL, 16); + + s += 5; + *device = simple_strtol(s, NULL, 16); + + return 0; + } + } else { + list += (len + 1); + } + } + + return -ENOENT; +} + +int fdtdec_get_pci_bdf(const void *blob, int node, + struct fdt_pci_addr *addr, pci_dev_t *bdf) +{ + u16 dt_vendor, dt_device, vendor, device; + int ret; + + /* get vendor id & device id from the compatible string */ + ret = fdtdec_get_pci_vendev(blob, node, &dt_vendor, &dt_device); + if (ret) + return ret; + + /* extract the bdf from fdt_pci_addr */ + *bdf = addr->phys_hi & 0xffff00; + + /* read vendor id & device id based on bdf */ + pci_read_config_word(*bdf, PCI_VENDOR_ID, &vendor); + pci_read_config_word(*bdf, PCI_DEVICE_ID, &device); + + /* + * Note there are two places in the device tree to fully describe + * a pci device: one is via compatible string with a format of + * "pciVVVV,DDDD" and the other one is the bdf numbers encoded in + * the device node's reg address property. We read the vendor id + * and device id based on bdf and compare the values with the + * "VVVV,DDDD". If they are the same, then we are good to use bdf + * to read device's bar. But if they are different, we have to rely + * on the vendor id and device id extracted from the compatible + * string and locate the real bdf by pci_find_device(). This is + * because normally we may only know device's device number and + * function number when writing device tree. The bus number is + * dynamically assigned during the pci enumeration process. + */ + if ((dt_vendor != vendor) || (dt_device != device)) { + *bdf = pci_find_device(dt_vendor, dt_device, 0); + if (*bdf == -1) + return -ENODEV; + } + + return 0; +} + +int fdtdec_get_pci_bar32(const void *blob, int node, + struct fdt_pci_addr *addr, u32 *bar) +{ + pci_dev_t bdf; + int barnum; + int ret; + + /* get pci devices's bdf */ + ret = fdtdec_get_pci_bdf(blob, node, addr, &bdf); + if (ret) + return ret; + + /* extract the bar number from fdt_pci_addr */ + barnum = addr->phys_hi & 0xff; + if ((barnum < PCI_BASE_ADDRESS_0) || (barnum > PCI_CARDBUS_CIS)) + return -EINVAL; + + barnum = (barnum - PCI_BASE_ADDRESS_0) / 4; + *bar = pci_read_bar32(pci_bus_to_hose(PCI_BUS(bdf)), bdf, barnum); + + return 0; +} +#endif + uint64_t fdtdec_get_uint64(const void *blob, int node, const char *prop_name, uint64_t default_val) { @@ -795,20 +952,6 @@ int fdt_get_named_resource(const void *fdt, int node, const char *property, return fdt_get_resource(fdt, node, property, index, res); } -int fdtdec_pci_get_bdf(const void *fdt, int node, int *bdf) -{ - const fdt32_t *prop; - int len; - - prop = fdt_getprop(fdt, node, "reg", &len); - if (!prop) - return len; - - *bdf = fdt32_to_cpu(*prop) & 0xffffff; - - return 0; -} - int fdtdec_decode_memory_region(const void *blob, int config_node, const char *mem_type, const char *suffix, fdt_addr_t *basep, fdt_size_t *sizep) |