zephyr/drivers/pci/pci.c

399 lines
11 KiB
C

/* pci.c - PCI probe and information routines */
/*
* Copyright (c) 2013-2014 Wind River Systems, Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1) Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2) Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3) Neither the name of Wind River Systems nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
DESCRIPTION
Module implements routines for PCI bus initialization and query.
Note that the BSP must call pci_bus_scan() before any other PCI
API is called.
USAGE
In order to use the driver, BSP has to define:
- Register addresses:
- PCI_CTRL_ADDR_REG;
- PCI_CTRL_DATA_REG;
- Register read/write routines:
- PLB_LONG_REG_READ() / PLB_LONG_REG_WRITE();
- PLB_WORD_REG_READ() / PLB_WORD_REG_WRITE();
- PLB_BYTE_REG_READ() / PLB_BYTE_REG_WRITE();
- pci_pin2irq() - the routine that converts the PCI interrupt pin
number to IRQ number.
*/
#include <nanokernel.h>
#include <nanokernel/cpu.h>
#include <misc/printk.h>
#include <toolchain.h>
#include <sections.h>
#include <board.h>
#include <pci/pci_mgr.h>
#include <pci/pci.h>
/* NOTE. These parameters may need to be configurable */
#define LSPCI_MAX_BUS 256 /* maximum number of buses to scan */
#define LSPCI_MAX_DEV 32 /* maximum number of devices to scan */
#define LSPCI_MAX_FUNC 8 /* maximum device 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)
#define MAX_BARS 6
static struct pci_dev_info __noinit dev_info[CONFIG_MAX_PCI_DEVS];
static int dev_info_index = 0;
/*******************************************************************************
*
* pci_get_bar_config - return the configuration for the specified BAR
*
* RETURNS: 0 if BAR is implemented, -1 if not.
*/
static int pci_bar_config_get(uint32_t bus,
uint32_t dev,
uint32_t func,
uint32_t bar,
uint32_t *config)
{
union pci_addr_reg pci_ctrl_addr;
uint32_t old_value;
pci_ctrl_addr.value = 0;
pci_ctrl_addr.field.enable = 1;
pci_ctrl_addr.field.bus = bus;
pci_ctrl_addr.field.device = dev;
pci_ctrl_addr.field.func = func;
pci_ctrl_addr.field.reg = 4 + bar;
/* 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;
}
/*******************************************************************************
*
* pci_bar_params_get - 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.
*
* \NOMANUAL
*/
static inline int pci_bar_params_get(uint32_t bus,
uint32_t dev,
uint32_t func,
uint32_t bar,
struct pci_dev_info *dev_info)
{
static union pci_addr_reg pci_ctrl_addr;
uint32_t bar_value;
uint32_t bar_config;
uint32_t addr;
uint32_t mask;
pci_ctrl_addr.value = 0;
pci_ctrl_addr.field.enable = 1;
pci_ctrl_addr.field.bus = bus;
pci_ctrl_addr.field.device = dev;
pci_ctrl_addr.field.func = func;
pci_ctrl_addr.field.reg = 4 + bar;
pci_read(DEFAULT_PCI_CONTROLLER,
pci_ctrl_addr,
sizeof(bar_value),
&bar_value);
if (pci_bar_config_get(bus, dev, func, bar, &bar_config) != 0)
return -1;
if (BAR_SPACE(bar_config) == BAR_SPACE_MEM) {
dev_info->mem_type = BAR_SPACE_MEM;
mask = ~0xf;
if (bar < 5 && BAR_TYPE(bar_config) == BAR_TYPE_64BIT)
return 1; /* 64-bit MEM */
} 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_first_set_inline(addr) - 1);
}
return 0;
}
/*******************************************************************************
*
* pci_dev_scan - scan the specified PCI device for all sub functions
*
* RETURNS: N/A
*/
static void pci_dev_scan(uint32_t bus,
uint32_t dev,
uint32_t class_mask /* bitmask, bits set for each needed
class */
)
{
uint32_t func;
uint32_t pci_data;
static union pci_addr_reg pci_ctrl_addr;
static union pci_dev pci_dev_header;
int i;
int max_bars;
if (dev_info_index == CONFIG_MAX_PCI_DEVS) {
/* No more room in the table */
return;
}
/* initialise the PCI controller address register value */
pci_ctrl_addr.value = 0;
pci_ctrl_addr.field.enable = 1;
pci_ctrl_addr.field.bus = bus;
pci_ctrl_addr.field.device = dev;
/* scan all the possible functions for this device */
for (func = 0; func < LSPCI_MAX_FUNC; func++) {
pci_ctrl_addr.field.func = func;
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, bus, dev, func, &pci_dev_header);
/* Skip a device if it's class is not specified by the caller */
if (!((1 << pci_dev_header.field.class) & class_mask))
continue;
/* Get memory and interrupt information */
if ((pci_dev_header.field.hdr_type & 0x7f) == 1)
max_bars = 2;
else
max_bars = MAX_BARS;
for (i = 0; i < max_bars; ++i) {
/* Ignore BARs with errors and 64 bit BARs */
if (pci_bar_params_get(
bus, dev, func, i, dev_info + dev_info_index) !=
0)
continue;
else {
dev_info[dev_info_index].vendor_id =
pci_dev_header.field.vendor_id;
dev_info[dev_info_index].device_id =
pci_dev_header.field.device_id;
dev_info[dev_info_index].class =
pci_dev_header.field.class;
dev_info[dev_info_index].irq = pci_pin2irq(
pci_dev_header.field.interrupt_pin);
dev_info_index++;
if (dev_info_index == CONFIG_MAX_PCI_DEVS) {
/* No more room in the table */
return;
}
}
}
}
}
/*******************************************************************************
*
* pci_bus_scan - scans PCI bus for devices
*
* The routine scans the PCI bus for the devices, which classes are provided
* in the classMask argument.
* classMask is constructed as:
* (1 << class1) | (1 << class2) | ... | (1 << classN)
*
* \NOMANUAL
*/
void pci_bus_scan(uint32_t class_mask /* bitmask, bits set for each needed class */
)
{
uint32_t bus;
uint32_t dev;
union pci_addr_reg pci_ctrl_addr;
uint32_t pci_data;
/* initialise the PCI controller address register value */
pci_ctrl_addr.value = 0;
pci_ctrl_addr.field.enable = 1;
/* run through the buses and devices */
for (bus = 0; bus < LSPCI_MAX_BUS; bus++) {
for (dev = 0; (dev < LSPCI_MAX_DEV) &&
(dev_info_index < CONFIG_MAX_PCI_DEVS);
dev++) {
pci_ctrl_addr.field.bus = bus;
pci_ctrl_addr.field.device = dev;
/* try and read register zero of the first function */
pci_read(DEFAULT_PCI_CONTROLLER,
pci_ctrl_addr,
sizeof(pci_data),
&pci_data);
/* scan the device if we found something */
if (pci_data != 0xffffffff)
pci_dev_scan(bus, dev, class_mask);
}
}
}
/*******************************************************************************
*
* pci_info_get - returns list of PCI devices
*
* \NOMANUAL
*/
struct pci_dev_info *pci_info_get(void)
{
return dev_info;
}
/*******************************************************************************
*
* pci_dev_find - find PCI device of a specified class and specified index
*
* Routine looks through the list of detected PCI devices and if the device
* of specified <class> and <index> exists, sets up <address>, <size> and <irq>.
*
* This function can return error if the specified device is not found. In most
* cases the device class and index are known to exist and therefore will be
* found. However, due to the somewhat dynamic nature of PCI, we allow for the
* fact the device may not be found and another attempt with different
* parameters maybe made.
*
* RETURNS: 0, if device is found, -1 otherwise
*/
int pci_dev_find(int class, int idx, uint32_t *addr, uint32_t *size, int *irq)
{
int i;
int j;
for (i = 0, j = 0; i < dev_info_index; i++) {
if (dev_info[i].class != class)
continue;
if (j == idx) {
*addr = dev_info[i].addr;
*size = dev_info[i].size;
*irq = dev_info[i].irq;
return 0;
}
j++;
}
return -1;
}
#ifdef PCI_DEBUG
/*******************************************************************************
*
* pci_show - Show PCI devices
*
* Shows the PCI devices found.
*
* RETURNS: N/A
*/
void pci_show(void)
{
int i;
printk("PCI devices:\n");
for (i = 0; i < dev_info_index; i++) {
printk("%X:%X class: 0x%X, %s, addrs: 0x%X-0x%X, IRQ %d\n",
dev_info[i].vendor_id,
dev_info[i].device_id,
dev_info[i].class,
(dev_info[i].mem_type == BAR_SPACE_MEM) ? "MEM" : "I/O",
(uint32_t)dev_info[i].addr,
(uint32_t)(dev_info[i].addr + dev_info[i].size - 1),
dev_info[i].irq);
}
}
#endif /* PCI_DEBUG */