| /* |
| * Copyright (c) 2013-2014 Wind River Systems, Inc. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| /** |
| * @file |
| * @brief PCI probe and information routines |
| * |
| * Module implements routines for PCI bus initialization and query. |
| * |
| * USAGE |
| * To use the driver, the platform must define: |
| * - Numbers of BUSes: |
| * - PCI_BUS_NUMBERS; |
| * - Register addresses: |
| * - PCI_CTRL_ADDR_REG; |
| * - PCI_CTRL_DATA_REG; |
| * - pci_pin2irq() - the routine that converts the PCI interrupt pin |
| * number to IRQ number. |
| * |
| * About scanning the PCI buses: |
| * At every new usage of this API, the code should call pci_bus_scan_init(). |
| * It should own a struct pci_dev_info, filled in with the parameters it is |
| * interested to look for: class and/or vendor_id/device_id. |
| * |
| * Then it can loop on pci_bus_scan() providing a pointer on that structure. |
| * Such function can be called as long as it returns 1. At every successful |
| * return of pci_bus_scan() it means the provided structure pointer will have |
| * been updated with the current scan result which the code might be interested |
| * in. On pci_bus_scan() returning 0, the code should discard the result and |
| * stop calling pci_bus_scan(). If it wants to retrieve the result, it will |
| * have to restart the procedure all over again. |
| * |
| * EXAMPLE |
| * struct pci_dev_info info = { |
| * .class_type = PCI_CLASS_COMM_CTLR |
| * }; |
| * |
| * pci_bus_scan_init(); |
| * |
| * while (pci_bus_scan(&info)) { |
| * // do something with "info" which holds a valid result, i.e. some |
| * // device information matching the PCI class PCI_CLASS_COMM_CTLR |
| * } |
| * |
| * INTERNALS |
| * The whole logic runs around a structure: struct lookup_data, which exists |
| * on one instanciation called 'lookup'. |
| * Such structure is used for 2 distinct roles: |
| * - to match devices the caller is looking for |
| * - to loop on PCI bus, devices, function and BARs |
| * |
| * The search criterias are the class and/or the vendor_id/device_id of a PCI |
| * device. The caller first initializes the lookup structure by calling |
| * pci_bus_scan_init(), which will reset the search criterias as well as the |
| * loop paramaters to 0. At the very first subsequent call of pci_bus_scan() |
| * the lookup structure will store the search criterias. Then the loop starts. |
| * For each bus it will run through each device on which it will loop on each |
| * function and BARs, as long as the criterias does not match or until it hit |
| * the limit of bus/dev/functions to scan. |
| * |
| * On a successful match, it will stop the loop, fill in the caller's |
| * pci_dev_info structure with the found device information, and return 1. |
| * Hopefully, the lookup structure still remembers where it stopped and the |
| * original search criterias. Thus, when the caller asks to scan again for |
| * a possible result next, the loop will restart where it stopped. |
| * That will work as long as there are relevant results found. |
| */ |
| |
| #include <kernel.h> |
| #include <arch/cpu.h> |
| #include <misc/printk.h> |
| #include <toolchain.h> |
| #include <sections.h> |
| |
| #include <board.h> |
| |
| #include <pci/pci_mgr.h> |
| #include <pci/pci.h> |
| |
| #ifdef CONFIG_PCI_ENUMERATION |
| |
| /* NOTE. These parameters may need to be configurable */ |
| #define LSPCI_MAX_BUS PCI_BUS_NUMBERS /* maximum number of buses to scan */ |
| #define LSPCI_MAX_DEV 32 /* maximum number of devices to scan */ |
| #define LSPCI_MAX_FUNC PCI_MAX_FUNCTIONS /* maximum functions to scan */ |
| #define LSPCI_MAX_REG 64 /* maximum device registers to read */ |
| |
| /* Base Address Register configuration fields */ |
| |
| #define BAR_SPACE(x) ((x) & 0x00000001) |
| |
| #define BAR_TYPE(x) ((x) & 0x00000006) |
| #define BAR_TYPE_32BIT 0 |
| #define BAR_TYPE_64BIT 4 |
| |
| #define BAR_PREFETCH(x) (((x) >> 3) & 0x00000001) |
| #define BAR_ADDR(x) (((x) >> 4) & 0x0fffffff) |
| |
| #define BAR_IO_MASK(x) ((x) & ~0x3) |
| #define BAR_MEM_MASK(x) ((x) & ~0xf) |
| |
| |
| struct lookup_data { |
| struct pci_dev_info info; |
| uint32_t bus:9; |
| uint32_t dev:6; |
| uint32_t func:4; |
| uint32_t baridx:3; |
| uint32_t barofs:3; |
| uint32_t unused:7; |
| }; |
| |
| static struct lookup_data __noinit lookup; |
| |
| /** |
| * |
| * @brief Return the configuration for the specified BAR |
| * |
| * @return 0 if BAR is implemented, -1 if not. |
| */ |
| |
| static inline int pci_bar_config_get(union pci_addr_reg pci_ctrl_addr, |
| uint32_t *config) |
| { |
| uint32_t old_value; |
| |
| /* save the current setting */ |
| pci_read(DEFAULT_PCI_CONTROLLER, |
| pci_ctrl_addr, |
| sizeof(old_value), |
| &old_value); |
| |
| /* write to the BAR to see how large it is */ |
| pci_write(DEFAULT_PCI_CONTROLLER, |
| pci_ctrl_addr, |
| sizeof(uint32_t), |
| 0xffffffff); |
| |
| pci_read(DEFAULT_PCI_CONTROLLER, |
| pci_ctrl_addr, |
| sizeof(*config), |
| config); |
| |
| /* put back the old configuration */ |
| pci_write(DEFAULT_PCI_CONTROLLER, |
| pci_ctrl_addr, |
| sizeof(old_value), |
| old_value); |
| |
| /* check if this BAR is implemented */ |
| if (*config != 0xffffffff && *config != 0) { |
| return 0; |
| } |
| |
| /* BAR not supported */ |
| |
| return -1; |
| } |
| |
| /** |
| * |
| * @brief Retrieve the I/O address and IRQ of the specified BAR |
| * |
| * @return -1 on error, 0 if 32 bit BAR retrieved or 1 if 64 bit BAR retrieved |
| * |
| * NOTE: Routine does not set up parameters for 64 bit BARS, they are ignored. |
| */ |
| static inline int pci_bar_params_get(union pci_addr_reg pci_ctrl_addr, |
| struct pci_dev_info *dev_info, |
| int max_bars) |
| { |
| uint32_t bar_value; |
| uint32_t bar_config; |
| uint32_t bar_hival; |
| uint32_t addr; |
| uint32_t mask; |
| |
| pci_ctrl_addr.field.reg = 4 + lookup.barofs; |
| |
| pci_read(DEFAULT_PCI_CONTROLLER, |
| pci_ctrl_addr, |
| sizeof(bar_value), |
| &bar_value); |
| if (pci_bar_config_get(pci_ctrl_addr, &bar_config) != 0) { |
| return -1; |
| } |
| |
| if (BAR_SPACE(bar_config) == BAR_SPACE_MEM) { |
| dev_info->mem_type = BAR_SPACE_MEM; |
| mask = ~0xf; |
| if (BAR_TYPE(bar_config) == BAR_TYPE_64BIT) { |
| /* Last BAR register cannot be 64-bit */ |
| if (++lookup.barofs >= max_bars) |
| return 1; |
| |
| /* Make sure the address is accessible */ |
| pci_ctrl_addr.field.reg++; |
| pci_read(DEFAULT_PCI_CONTROLLER, |
| pci_ctrl_addr, |
| sizeof(bar_hival), |
| &bar_hival); |
| if (bar_hival) |
| return 1; /* Inaccessible memory */ |
| } |
| } else { |
| dev_info->mem_type = BAR_SPACE_IO; |
| mask = ~0x3; |
| } |
| |
| dev_info->addr = bar_value & mask; |
| |
| addr = bar_config & mask; |
| if (addr != 0) { |
| /* calculate the size of the BAR memory required */ |
| dev_info->size = 1 << (find_lsb_set(addr) - 1); |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * |
| * @brief Scan the specified PCI device for all sub functions |
| * |
| * @return 1 if a device has been found, 0 otherwise. |
| */ |
| static inline int pci_dev_scan(union pci_addr_reg pci_ctrl_addr, |
| struct pci_dev_info *dev_info) |
| { |
| static union pci_dev pci_dev_header; |
| uint32_t pci_data; |
| int max_bars; |
| |
| /* verify first if there is a valid device at this point */ |
| pci_ctrl_addr.field.func = 0; |
| |
| pci_read(DEFAULT_PCI_CONTROLLER, |
| pci_ctrl_addr, |
| sizeof(pci_data), |
| &pci_data); |
| |
| if (pci_data == 0xffffffff) { |
| return 0; |
| } |
| |
| /* scan all the possible functions for this device */ |
| for (; lookup.func < LSPCI_MAX_FUNC; |
| lookup.baridx = 0, lookup.barofs = 0, lookup.func++) { |
| if (lookup.info.function != PCI_FUNCTION_ANY && |
| lookup.func != lookup.info.function) { |
| return 0; |
| } |
| |
| pci_ctrl_addr.field.func = lookup.func; |
| |
| if (lookup.func != 0) { |
| pci_read(DEFAULT_PCI_CONTROLLER, |
| pci_ctrl_addr, |
| sizeof(pci_data), |
| &pci_data); |
| |
| if (pci_data == 0xffffffff) { |
| continue; |
| } |
| } |
| |
| /* get the PCI header from the device */ |
| pci_header_get(DEFAULT_PCI_CONTROLLER, |
| pci_ctrl_addr, |
| &pci_dev_header); |
| |
| /* |
| * Skip a device if its class is specified by the |
| * caller and does not match |
| */ |
| if (lookup.info.class_type && |
| pci_dev_header.field.class != lookup.info.class_type) { |
| continue; |
| } |
| |
| if (lookup.info.vendor_id && lookup.info.device_id && |
| (lookup.info.vendor_id != pci_dev_header.field.vendor_id || |
| lookup.info.device_id != pci_dev_header.field.device_id)) { |
| continue; |
| } |
| |
| /* Get memory and interrupt information */ |
| if ((pci_dev_header.field.hdr_type & 0x7f) == 1) { |
| max_bars = 2; |
| } else { |
| max_bars = PCI_MAX_BARS; |
| } |
| |
| for (; lookup.barofs < max_bars; |
| lookup.baridx++, lookup.barofs++) { |
| /* Ignore BARs with errors */ |
| if (pci_bar_params_get(pci_ctrl_addr, dev_info, |
| max_bars) != 0) { |
| continue; |
| } else if (lookup.info.bar != PCI_BAR_ANY && |
| lookup.baridx != lookup.info.bar) { |
| continue; |
| } else { |
| dev_info->bus = lookup.bus; |
| dev_info->dev = lookup.dev; |
| dev_info->vendor_id = |
| pci_dev_header.field.vendor_id; |
| dev_info->device_id = |
| pci_dev_header.field.device_id; |
| dev_info->class_type = |
| pci_dev_header.field.class; |
| dev_info->irq = pci_pin2irq(dev_info->bus, |
| dev_info->dev, |
| pci_dev_header.field.interrupt_pin); |
| dev_info->function = lookup.func; |
| dev_info->bar = lookup.baridx; |
| |
| lookup.baridx++; |
| lookup.barofs++; |
| if (lookup.barofs >= max_bars) { |
| lookup.baridx = 0; |
| lookup.barofs = 0; |
| } |
| |
| return 1; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| void pci_bus_scan_init(void) |
| { |
| lookup.info.class_type = 0; |
| lookup.info.vendor_id = 0; |
| lookup.info.device_id = 0; |
| lookup.info.function = PCI_FUNCTION_ANY; |
| lookup.info.bar = PCI_BAR_ANY; |
| lookup.bus = 0; |
| lookup.dev = 0; |
| lookup.func = 0; |
| lookup.baridx = 0; |
| lookup.barofs = 0; |
| } |
| |
| |
| /** |
| * |
| * @brief Scans PCI bus for devices |
| * |
| * The routine scans the PCI bus for the devices on criterias provided in the |
| * given dev_info at first call. Which criterias can be class and/or |
| * vendor_id/device_id. |
| * |
| * @return 1 on success, 0 otherwise. On success, dev_info is filled in with |
| * currently found device information |
| */ |
| int pci_bus_scan(struct pci_dev_info *dev_info) |
| { |
| union pci_addr_reg pci_ctrl_addr; |
| |
| int init_from_dev_info = |
| !lookup.info.class_type && |
| !lookup.info.vendor_id && |
| !lookup.info.device_id && |
| lookup.info.bar == PCI_BAR_ANY && |
| lookup.info.function == PCI_FUNCTION_ANY; |
| |
| if (init_from_dev_info) { |
| lookup.info.class_type = dev_info->class_type; |
| lookup.info.vendor_id = dev_info->vendor_id; |
| lookup.info.device_id = dev_info->device_id; |
| lookup.info.function = dev_info->function; |
| lookup.info.bar = dev_info->bar; |
| } |
| |
| /* initialise the PCI controller address register value */ |
| pci_ctrl_addr.value = 0; |
| |
| if (lookup.info.function != PCI_FUNCTION_ANY) { |
| lookup.func = lookup.info.function; |
| } |
| |
| /* run through the buses and devices */ |
| for (; lookup.bus < LSPCI_MAX_BUS; lookup.bus++) { |
| for (; (lookup.dev < LSPCI_MAX_DEV); lookup.dev++) { |
| pci_ctrl_addr.field.bus = lookup.bus; |
| pci_ctrl_addr.field.device = lookup.dev; |
| |
| if (pci_dev_scan(pci_ctrl_addr, dev_info)) { |
| return 1; |
| } |
| |
| if (lookup.info.function != PCI_FUNCTION_ANY) { |
| lookup.func = lookup.info.function; |
| } else { |
| lookup.func = 0; |
| } |
| } |
| lookup.dev = 0; |
| } |
| |
| return 0; |
| } |
| #endif /* CONFIG_PCI_ENUMERATION */ |
| |
| static void pci_set_command_bits(struct pci_dev_info *dev_info, uint32_t bits) |
| { |
| union pci_addr_reg pci_ctrl_addr; |
| uint32_t pci_data; |
| |
| pci_ctrl_addr.value = 0; |
| pci_ctrl_addr.field.func = dev_info->function; |
| pci_ctrl_addr.field.bus = dev_info->bus; |
| pci_ctrl_addr.field.device = dev_info->dev; |
| pci_ctrl_addr.field.reg = 1; |
| |
| #ifdef CONFIG_PCI_DEBUG |
| printk("pci_set_command_bits 0x%x\n", pci_ctrl_addr.value); |
| #endif |
| |
| pci_read(DEFAULT_PCI_CONTROLLER, |
| pci_ctrl_addr, |
| sizeof(uint16_t), |
| &pci_data); |
| |
| pci_data = pci_data | bits; |
| |
| pci_write(DEFAULT_PCI_CONTROLLER, |
| pci_ctrl_addr, |
| sizeof(uint16_t), |
| pci_data); |
| } |
| |
| void pci_enable_regs(struct pci_dev_info *dev_info) |
| { |
| pci_set_command_bits(dev_info, PCI_CMD_MEM_ENABLE); |
| } |
| |
| void pci_enable_bus_master(struct pci_dev_info *dev_info) |
| { |
| pci_set_command_bits(dev_info, PCI_CMD_MASTER_ENABLE); |
| } |
| |
| #ifdef CONFIG_PCI_DEBUG |
| /** |
| * |
| * @brief Show PCI device |
| * |
| * Shows the PCI device found provided as parameter. |
| * |
| * @return N/A |
| */ |
| |
| void pci_show(struct pci_dev_info *dev_info) |
| { |
| printk("PCI device:\n"); |
| printk("%u:%u %X:%X class: 0x%X, %u, %u, %s," |
| "addrs: 0x%X-0x%X, IRQ %d\n", |
| dev_info->bus, |
| dev_info->dev, |
| dev_info->vendor_id, |
| dev_info->device_id, |
| dev_info->class_type, |
| dev_info->function, |
| dev_info->bar, |
| (dev_info->mem_type == BAR_SPACE_MEM) ? "MEM" : "I/O", |
| (uint32_t)dev_info->addr, |
| (uint32_t)(dev_info->addr + dev_info->size - 1), |
| dev_info->irq); |
| } |
| #endif /* CONFIG_PCI_DEBUG */ |