mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-11-29 08:01:08 +00:00
8f9052fd98
Introduce a gd->hose to save the pci hose in the early phase so that apis in drivers/pci/pci.c can be used before relocation. Architecture codes need assign a valid gd->hose in the early phase. Some variables are declared as static so change them to be either stack variable or global data member so that they can be used before relocation, except the 'indent' used by CONFIG_PCI_SCAN_SHOW which just affects some print format. Signed-off-by: Bin Meng <bmeng.cn@gmail.com> Acked-by: Simon Glass <sjg@chromium.org>
821 lines
19 KiB
C
821 lines
19 KiB
C
/*
|
|
* (C) Copyright 2001 Sysgo Real-Time Solutions, GmbH <www.elinos.com>
|
|
* Andreas Heppel <aheppel@sysgo.de>
|
|
*
|
|
* (C) Copyright 2002, 2003
|
|
* Wolfgang Denk, DENX Software Engineering, wd@denx.de.
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0+
|
|
*/
|
|
|
|
/*
|
|
* PCI routines
|
|
*/
|
|
|
|
#include <common.h>
|
|
|
|
#include <command.h>
|
|
#include <asm/processor.h>
|
|
#include <asm/io.h>
|
|
#include <pci.h>
|
|
|
|
DECLARE_GLOBAL_DATA_PTR;
|
|
|
|
#define PCI_HOSE_OP(rw, size, type) \
|
|
int pci_hose_##rw##_config_##size(struct pci_controller *hose, \
|
|
pci_dev_t dev, \
|
|
int offset, type value) \
|
|
{ \
|
|
return hose->rw##_##size(hose, dev, offset, value); \
|
|
}
|
|
|
|
PCI_HOSE_OP(read, byte, u8 *)
|
|
PCI_HOSE_OP(read, word, u16 *)
|
|
PCI_HOSE_OP(read, dword, u32 *)
|
|
PCI_HOSE_OP(write, byte, u8)
|
|
PCI_HOSE_OP(write, word, u16)
|
|
PCI_HOSE_OP(write, dword, u32)
|
|
|
|
#define PCI_OP(rw, size, type, error_code) \
|
|
int pci_##rw##_config_##size(pci_dev_t dev, int offset, type value) \
|
|
{ \
|
|
struct pci_controller *hose = pci_bus_to_hose(PCI_BUS(dev)); \
|
|
\
|
|
if (!hose) \
|
|
{ \
|
|
error_code; \
|
|
return -1; \
|
|
} \
|
|
\
|
|
return pci_hose_##rw##_config_##size(hose, dev, offset, value); \
|
|
}
|
|
|
|
PCI_OP(read, byte, u8 *, *value = 0xff)
|
|
PCI_OP(read, word, u16 *, *value = 0xffff)
|
|
PCI_OP(read, dword, u32 *, *value = 0xffffffff)
|
|
PCI_OP(write, byte, u8, )
|
|
PCI_OP(write, word, u16, )
|
|
PCI_OP(write, dword, u32, )
|
|
|
|
#define PCI_READ_VIA_DWORD_OP(size, type, off_mask) \
|
|
int pci_hose_read_config_##size##_via_dword(struct pci_controller *hose,\
|
|
pci_dev_t dev, \
|
|
int offset, type val) \
|
|
{ \
|
|
u32 val32; \
|
|
\
|
|
if (pci_hose_read_config_dword(hose, dev, offset & 0xfc, &val32) < 0) { \
|
|
*val = -1; \
|
|
return -1; \
|
|
} \
|
|
\
|
|
*val = (val32 >> ((offset & (int)off_mask) * 8)); \
|
|
\
|
|
return 0; \
|
|
}
|
|
|
|
#define PCI_WRITE_VIA_DWORD_OP(size, type, off_mask, val_mask) \
|
|
int pci_hose_write_config_##size##_via_dword(struct pci_controller *hose,\
|
|
pci_dev_t dev, \
|
|
int offset, type val) \
|
|
{ \
|
|
u32 val32, mask, ldata, shift; \
|
|
\
|
|
if (pci_hose_read_config_dword(hose, dev, offset & 0xfc, &val32) < 0)\
|
|
return -1; \
|
|
\
|
|
shift = ((offset & (int)off_mask) * 8); \
|
|
ldata = (((unsigned long)val) & val_mask) << shift; \
|
|
mask = val_mask << shift; \
|
|
val32 = (val32 & ~mask) | ldata; \
|
|
\
|
|
if (pci_hose_write_config_dword(hose, dev, offset & 0xfc, val32) < 0)\
|
|
return -1; \
|
|
\
|
|
return 0; \
|
|
}
|
|
|
|
PCI_READ_VIA_DWORD_OP(byte, u8 *, 0x03)
|
|
PCI_READ_VIA_DWORD_OP(word, u16 *, 0x02)
|
|
PCI_WRITE_VIA_DWORD_OP(byte, u8, 0x03, 0x000000ff)
|
|
PCI_WRITE_VIA_DWORD_OP(word, u16, 0x02, 0x0000ffff)
|
|
|
|
/* Get a virtual address associated with a BAR region */
|
|
void *pci_map_bar(pci_dev_t pdev, int bar, int flags)
|
|
{
|
|
pci_addr_t pci_bus_addr;
|
|
u32 bar_response;
|
|
|
|
/* read BAR address */
|
|
pci_read_config_dword(pdev, bar, &bar_response);
|
|
pci_bus_addr = (pci_addr_t)(bar_response & ~0xf);
|
|
|
|
/*
|
|
* Pass "0" as the length argument to pci_bus_to_virt. The arg
|
|
* isn't actualy used on any platform because u-boot assumes a static
|
|
* linear mapping. In the future, this could read the BAR size
|
|
* and pass that as the size if needed.
|
|
*/
|
|
return pci_bus_to_virt(pdev, pci_bus_addr, flags, 0, MAP_NOCACHE);
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
|
|
static struct pci_controller* hose_head;
|
|
|
|
struct pci_controller *pci_get_hose_head(void)
|
|
{
|
|
if (gd->hose)
|
|
return gd->hose;
|
|
|
|
return hose_head;
|
|
}
|
|
|
|
void pci_register_hose(struct pci_controller* hose)
|
|
{
|
|
struct pci_controller **phose = &hose_head;
|
|
|
|
while(*phose)
|
|
phose = &(*phose)->next;
|
|
|
|
hose->next = NULL;
|
|
|
|
*phose = hose;
|
|
}
|
|
|
|
struct pci_controller *pci_bus_to_hose(int bus)
|
|
{
|
|
struct pci_controller *hose;
|
|
|
|
for (hose = pci_get_hose_head(); hose; hose = hose->next) {
|
|
if (bus >= hose->first_busno && bus <= hose->last_busno)
|
|
return hose;
|
|
}
|
|
|
|
printf("pci_bus_to_hose() failed\n");
|
|
return NULL;
|
|
}
|
|
|
|
struct pci_controller *find_hose_by_cfg_addr(void *cfg_addr)
|
|
{
|
|
struct pci_controller *hose;
|
|
|
|
for (hose = pci_get_hose_head(); hose; hose = hose->next) {
|
|
if (hose->cfg_addr == cfg_addr)
|
|
return hose;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int pci_last_busno(void)
|
|
{
|
|
struct pci_controller *hose = pci_get_hose_head();
|
|
|
|
if (!hose)
|
|
return -1;
|
|
|
|
while (hose->next)
|
|
hose = hose->next;
|
|
|
|
return hose->last_busno;
|
|
}
|
|
|
|
pci_dev_t pci_find_devices(struct pci_device_id *ids, int index)
|
|
{
|
|
struct pci_controller * hose;
|
|
u16 vendor, device;
|
|
u8 header_type;
|
|
pci_dev_t bdf;
|
|
int i, bus, found_multi = 0;
|
|
|
|
for (hose = pci_get_hose_head(); hose; hose = hose->next) {
|
|
#ifdef CONFIG_SYS_SCSI_SCAN_BUS_REVERSE
|
|
for (bus = hose->last_busno; bus >= hose->first_busno; bus--)
|
|
#else
|
|
for (bus = hose->first_busno; bus <= hose->last_busno; bus++)
|
|
#endif
|
|
for (bdf = PCI_BDF(bus, 0, 0);
|
|
#if defined(CONFIG_ELPPC) || defined(CONFIG_PPMC7XX)
|
|
bdf < PCI_BDF(bus, PCI_MAX_PCI_DEVICES - 1,
|
|
PCI_MAX_PCI_FUNCTIONS - 1);
|
|
#else
|
|
bdf < PCI_BDF(bus + 1, 0, 0);
|
|
#endif
|
|
bdf += PCI_BDF(0, 0, 1)) {
|
|
if (pci_skip_dev(hose, bdf))
|
|
continue;
|
|
|
|
if (!PCI_FUNC(bdf)) {
|
|
pci_read_config_byte(bdf,
|
|
PCI_HEADER_TYPE,
|
|
&header_type);
|
|
|
|
found_multi = header_type & 0x80;
|
|
} else {
|
|
if (!found_multi)
|
|
continue;
|
|
}
|
|
|
|
pci_read_config_word(bdf,
|
|
PCI_VENDOR_ID,
|
|
&vendor);
|
|
pci_read_config_word(bdf,
|
|
PCI_DEVICE_ID,
|
|
&device);
|
|
|
|
for (i = 0; ids[i].vendor != 0; i++) {
|
|
if (vendor == ids[i].vendor &&
|
|
device == ids[i].device) {
|
|
if (index <= 0)
|
|
return bdf;
|
|
|
|
index--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
pci_dev_t pci_find_device(unsigned int vendor, unsigned int device, int index)
|
|
{
|
|
struct pci_device_id ids[2] = { {}, {0, 0} };
|
|
|
|
ids[0].vendor = vendor;
|
|
ids[0].device = device;
|
|
|
|
return pci_find_devices(ids, index);
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
|
|
int __pci_hose_phys_to_bus(struct pci_controller *hose,
|
|
phys_addr_t phys_addr,
|
|
unsigned long flags,
|
|
unsigned long skip_mask,
|
|
pci_addr_t *ba)
|
|
{
|
|
struct pci_region *res;
|
|
pci_addr_t bus_addr;
|
|
int i;
|
|
|
|
for (i = 0; i < hose->region_count; i++) {
|
|
res = &hose->regions[i];
|
|
|
|
if (((res->flags ^ flags) & PCI_REGION_TYPE) != 0)
|
|
continue;
|
|
|
|
if (res->flags & skip_mask)
|
|
continue;
|
|
|
|
bus_addr = phys_addr - res->phys_start + res->bus_start;
|
|
|
|
if (bus_addr >= res->bus_start &&
|
|
bus_addr < res->bus_start + res->size) {
|
|
*ba = bus_addr;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
pci_addr_t pci_hose_phys_to_bus (struct pci_controller *hose,
|
|
phys_addr_t phys_addr,
|
|
unsigned long flags)
|
|
{
|
|
pci_addr_t bus_addr = 0;
|
|
int ret;
|
|
|
|
if (!hose) {
|
|
puts("pci_hose_phys_to_bus: invalid hose\n");
|
|
return bus_addr;
|
|
}
|
|
|
|
/*
|
|
* if PCI_REGION_MEM is set we do a two pass search with preference
|
|
* on matches that don't have PCI_REGION_SYS_MEMORY set
|
|
*/
|
|
if ((flags & PCI_REGION_MEM) == PCI_REGION_MEM) {
|
|
ret = __pci_hose_phys_to_bus(hose, phys_addr,
|
|
flags, PCI_REGION_SYS_MEMORY, &bus_addr);
|
|
if (!ret)
|
|
return bus_addr;
|
|
}
|
|
|
|
ret = __pci_hose_phys_to_bus(hose, phys_addr, flags, 0, &bus_addr);
|
|
|
|
if (ret)
|
|
puts("pci_hose_phys_to_bus: invalid physical address\n");
|
|
|
|
return bus_addr;
|
|
}
|
|
|
|
int __pci_hose_bus_to_phys(struct pci_controller *hose,
|
|
pci_addr_t bus_addr,
|
|
unsigned long flags,
|
|
unsigned long skip_mask,
|
|
phys_addr_t *pa)
|
|
{
|
|
struct pci_region *res;
|
|
int i;
|
|
|
|
for (i = 0; i < hose->region_count; i++) {
|
|
res = &hose->regions[i];
|
|
|
|
if (((res->flags ^ flags) & PCI_REGION_TYPE) != 0)
|
|
continue;
|
|
|
|
if (res->flags & skip_mask)
|
|
continue;
|
|
|
|
if (bus_addr >= res->bus_start &&
|
|
(bus_addr - res->bus_start) < res->size) {
|
|
*pa = (bus_addr - res->bus_start + res->phys_start);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
phys_addr_t pci_hose_bus_to_phys(struct pci_controller* hose,
|
|
pci_addr_t bus_addr,
|
|
unsigned long flags)
|
|
{
|
|
phys_addr_t phys_addr = 0;
|
|
int ret;
|
|
|
|
if (!hose) {
|
|
puts("pci_hose_bus_to_phys: invalid hose\n");
|
|
return phys_addr;
|
|
}
|
|
|
|
/*
|
|
* if PCI_REGION_MEM is set we do a two pass search with preference
|
|
* on matches that don't have PCI_REGION_SYS_MEMORY set
|
|
*/
|
|
if ((flags & PCI_REGION_MEM) == PCI_REGION_MEM) {
|
|
ret = __pci_hose_bus_to_phys(hose, bus_addr,
|
|
flags, PCI_REGION_SYS_MEMORY, &phys_addr);
|
|
if (!ret)
|
|
return phys_addr;
|
|
}
|
|
|
|
ret = __pci_hose_bus_to_phys(hose, bus_addr, flags, 0, &phys_addr);
|
|
|
|
if (ret)
|
|
puts("pci_hose_bus_to_phys: invalid physical address\n");
|
|
|
|
return phys_addr;
|
|
}
|
|
|
|
void pci_write_bar32(struct pci_controller *hose, pci_dev_t dev, int barnum,
|
|
u32 addr_and_ctrl)
|
|
{
|
|
int bar;
|
|
|
|
bar = PCI_BASE_ADDRESS_0 + barnum * 4;
|
|
pci_hose_write_config_dword(hose, dev, bar, addr_and_ctrl);
|
|
}
|
|
|
|
u32 pci_read_bar32(struct pci_controller *hose, pci_dev_t dev, int barnum)
|
|
{
|
|
u32 addr;
|
|
int bar;
|
|
|
|
bar = PCI_BASE_ADDRESS_0 + barnum * 4;
|
|
pci_hose_read_config_dword(hose, dev, bar, &addr);
|
|
if (addr & PCI_BASE_ADDRESS_SPACE_IO)
|
|
return addr & PCI_BASE_ADDRESS_IO_MASK;
|
|
else
|
|
return addr & PCI_BASE_ADDRESS_MEM_MASK;
|
|
}
|
|
|
|
int pci_hose_config_device(struct pci_controller *hose,
|
|
pci_dev_t dev,
|
|
unsigned long io,
|
|
pci_addr_t mem,
|
|
unsigned long command)
|
|
{
|
|
u32 bar_response;
|
|
unsigned int old_command;
|
|
pci_addr_t bar_value;
|
|
pci_size_t bar_size;
|
|
unsigned char pin;
|
|
int bar, found_mem64;
|
|
|
|
debug("PCI Config: I/O=0x%lx, Memory=0x%llx, Command=0x%lx\n", io,
|
|
(u64)mem, command);
|
|
|
|
pci_hose_write_config_dword(hose, dev, PCI_COMMAND, 0);
|
|
|
|
for (bar = PCI_BASE_ADDRESS_0; bar <= PCI_BASE_ADDRESS_5; bar += 4) {
|
|
pci_hose_write_config_dword(hose, dev, bar, 0xffffffff);
|
|
pci_hose_read_config_dword(hose, dev, bar, &bar_response);
|
|
|
|
if (!bar_response)
|
|
continue;
|
|
|
|
found_mem64 = 0;
|
|
|
|
/* Check the BAR type and set our address mask */
|
|
if (bar_response & PCI_BASE_ADDRESS_SPACE) {
|
|
bar_size = ~(bar_response & PCI_BASE_ADDRESS_IO_MASK) + 1;
|
|
/* round up region base address to a multiple of size */
|
|
io = ((io - 1) | (bar_size - 1)) + 1;
|
|
bar_value = io;
|
|
/* compute new region base address */
|
|
io = io + bar_size;
|
|
} else {
|
|
if ((bar_response & PCI_BASE_ADDRESS_MEM_TYPE_MASK) ==
|
|
PCI_BASE_ADDRESS_MEM_TYPE_64) {
|
|
u32 bar_response_upper;
|
|
u64 bar64;
|
|
pci_hose_write_config_dword(hose, dev, bar + 4,
|
|
0xffffffff);
|
|
pci_hose_read_config_dword(hose, dev, bar + 4,
|
|
&bar_response_upper);
|
|
|
|
bar64 = ((u64)bar_response_upper << 32) | bar_response;
|
|
|
|
bar_size = ~(bar64 & PCI_BASE_ADDRESS_MEM_MASK) + 1;
|
|
found_mem64 = 1;
|
|
} else {
|
|
bar_size = (u32)(~(bar_response & PCI_BASE_ADDRESS_MEM_MASK) + 1);
|
|
}
|
|
|
|
/* round up region base address to multiple of size */
|
|
mem = ((mem - 1) | (bar_size - 1)) + 1;
|
|
bar_value = mem;
|
|
/* compute new region base address */
|
|
mem = mem + bar_size;
|
|
}
|
|
|
|
/* Write it out and update our limit */
|
|
pci_hose_write_config_dword (hose, dev, bar, (u32)bar_value);
|
|
|
|
if (found_mem64) {
|
|
bar += 4;
|
|
#ifdef CONFIG_SYS_PCI_64BIT
|
|
pci_hose_write_config_dword(hose, dev, bar,
|
|
(u32)(bar_value >> 32));
|
|
#else
|
|
pci_hose_write_config_dword(hose, dev, bar, 0x00000000);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/* Configure Cache Line Size Register */
|
|
pci_hose_write_config_byte(hose, dev, PCI_CACHE_LINE_SIZE, 0x08);
|
|
|
|
/* Configure Latency Timer */
|
|
pci_hose_write_config_byte(hose, dev, PCI_LATENCY_TIMER, 0x80);
|
|
|
|
/* Disable interrupt line, if device says it wants to use interrupts */
|
|
pci_hose_read_config_byte(hose, dev, PCI_INTERRUPT_PIN, &pin);
|
|
if (pin != 0) {
|
|
pci_hose_write_config_byte(hose, dev, PCI_INTERRUPT_LINE, 0xff);
|
|
}
|
|
|
|
pci_hose_read_config_dword(hose, dev, PCI_COMMAND, &old_command);
|
|
pci_hose_write_config_dword(hose, dev, PCI_COMMAND,
|
|
(old_command & 0xffff0000) | command);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
|
|
struct pci_config_table *pci_find_config(struct pci_controller *hose,
|
|
unsigned short class,
|
|
unsigned int vendor,
|
|
unsigned int device,
|
|
unsigned int bus,
|
|
unsigned int dev,
|
|
unsigned int func)
|
|
{
|
|
struct pci_config_table *table;
|
|
|
|
for (table = hose->config_table; table && table->vendor; table++) {
|
|
if ((table->vendor == PCI_ANY_ID || table->vendor == vendor) &&
|
|
(table->device == PCI_ANY_ID || table->device == device) &&
|
|
(table->class == PCI_ANY_ID || table->class == class) &&
|
|
(table->bus == PCI_ANY_ID || table->bus == bus) &&
|
|
(table->dev == PCI_ANY_ID || table->dev == dev) &&
|
|
(table->func == PCI_ANY_ID || table->func == func)) {
|
|
return table;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void pci_cfgfunc_config_device(struct pci_controller *hose,
|
|
pci_dev_t dev,
|
|
struct pci_config_table *entry)
|
|
{
|
|
pci_hose_config_device(hose, dev, entry->priv[0], entry->priv[1],
|
|
entry->priv[2]);
|
|
}
|
|
|
|
void pci_cfgfunc_do_nothing(struct pci_controller *hose,
|
|
pci_dev_t dev, struct pci_config_table *entry)
|
|
{
|
|
}
|
|
|
|
/*
|
|
* HJF: Changed this to return int. I think this is required
|
|
* to get the correct result when scanning bridges
|
|
*/
|
|
extern int pciauto_config_device(struct pci_controller *hose, pci_dev_t dev);
|
|
|
|
#if defined(CONFIG_CMD_PCI) || defined(CONFIG_PCI_SCAN_SHOW)
|
|
const char * pci_class_str(u8 class)
|
|
{
|
|
switch (class) {
|
|
case PCI_CLASS_NOT_DEFINED:
|
|
return "Build before PCI Rev2.0";
|
|
break;
|
|
case PCI_BASE_CLASS_STORAGE:
|
|
return "Mass storage controller";
|
|
break;
|
|
case PCI_BASE_CLASS_NETWORK:
|
|
return "Network controller";
|
|
break;
|
|
case PCI_BASE_CLASS_DISPLAY:
|
|
return "Display controller";
|
|
break;
|
|
case PCI_BASE_CLASS_MULTIMEDIA:
|
|
return "Multimedia device";
|
|
break;
|
|
case PCI_BASE_CLASS_MEMORY:
|
|
return "Memory controller";
|
|
break;
|
|
case PCI_BASE_CLASS_BRIDGE:
|
|
return "Bridge device";
|
|
break;
|
|
case PCI_BASE_CLASS_COMMUNICATION:
|
|
return "Simple comm. controller";
|
|
break;
|
|
case PCI_BASE_CLASS_SYSTEM:
|
|
return "Base system peripheral";
|
|
break;
|
|
case PCI_BASE_CLASS_INPUT:
|
|
return "Input device";
|
|
break;
|
|
case PCI_BASE_CLASS_DOCKING:
|
|
return "Docking station";
|
|
break;
|
|
case PCI_BASE_CLASS_PROCESSOR:
|
|
return "Processor";
|
|
break;
|
|
case PCI_BASE_CLASS_SERIAL:
|
|
return "Serial bus controller";
|
|
break;
|
|
case PCI_BASE_CLASS_INTELLIGENT:
|
|
return "Intelligent controller";
|
|
break;
|
|
case PCI_BASE_CLASS_SATELLITE:
|
|
return "Satellite controller";
|
|
break;
|
|
case PCI_BASE_CLASS_CRYPT:
|
|
return "Cryptographic device";
|
|
break;
|
|
case PCI_BASE_CLASS_SIGNAL_PROCESSING:
|
|
return "DSP";
|
|
break;
|
|
case PCI_CLASS_OTHERS:
|
|
return "Does not fit any class";
|
|
break;
|
|
default:
|
|
return "???";
|
|
break;
|
|
};
|
|
}
|
|
#endif /* CONFIG_CMD_PCI || CONFIG_PCI_SCAN_SHOW */
|
|
|
|
__weak int pci_skip_dev(struct pci_controller *hose, pci_dev_t dev)
|
|
{
|
|
/*
|
|
* Check if pci device should be skipped in configuration
|
|
*/
|
|
if (dev == PCI_BDF(hose->first_busno, 0, 0)) {
|
|
#if defined(CONFIG_PCI_CONFIG_HOST_BRIDGE) /* don't skip host bridge */
|
|
/*
|
|
* Only skip configuration if "pciconfighost" is not set
|
|
*/
|
|
if (getenv("pciconfighost") == NULL)
|
|
return 1;
|
|
#else
|
|
return 1;
|
|
#endif
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PCI_SCAN_SHOW
|
|
__weak int pci_print_dev(struct pci_controller *hose, pci_dev_t dev)
|
|
{
|
|
if (dev == PCI_BDF(hose->first_busno, 0, 0))
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
#endif /* CONFIG_PCI_SCAN_SHOW */
|
|
|
|
int pci_hose_scan_bus(struct pci_controller *hose, int bus)
|
|
{
|
|
unsigned int sub_bus, found_multi = 0;
|
|
unsigned short vendor, device, class;
|
|
unsigned char header_type;
|
|
#ifndef CONFIG_PCI_PNP
|
|
struct pci_config_table *cfg;
|
|
#endif
|
|
pci_dev_t dev;
|
|
#ifdef CONFIG_PCI_SCAN_SHOW
|
|
static int indent = 0;
|
|
#endif
|
|
|
|
sub_bus = bus;
|
|
|
|
for (dev = PCI_BDF(bus,0,0);
|
|
dev < PCI_BDF(bus, PCI_MAX_PCI_DEVICES - 1,
|
|
PCI_MAX_PCI_FUNCTIONS - 1);
|
|
dev += PCI_BDF(0, 0, 1)) {
|
|
|
|
if (pci_skip_dev(hose, dev))
|
|
continue;
|
|
|
|
if (PCI_FUNC(dev) && !found_multi)
|
|
continue;
|
|
|
|
pci_hose_read_config_byte(hose, dev, PCI_HEADER_TYPE, &header_type);
|
|
|
|
pci_hose_read_config_word(hose, dev, PCI_VENDOR_ID, &vendor);
|
|
|
|
if (vendor == 0xffff || vendor == 0x0000)
|
|
continue;
|
|
|
|
if (!PCI_FUNC(dev))
|
|
found_multi = header_type & 0x80;
|
|
|
|
debug("PCI Scan: Found Bus %d, Device %d, Function %d\n",
|
|
PCI_BUS(dev), PCI_DEV(dev), PCI_FUNC(dev));
|
|
|
|
pci_hose_read_config_word(hose, dev, PCI_DEVICE_ID, &device);
|
|
pci_hose_read_config_word(hose, dev, PCI_CLASS_DEVICE, &class);
|
|
|
|
#ifdef CONFIG_PCI_FIXUP_DEV
|
|
board_pci_fixup_dev(hose, dev, vendor, device, class);
|
|
#endif
|
|
|
|
#ifdef CONFIG_PCI_SCAN_SHOW
|
|
indent++;
|
|
|
|
/* Print leading space, including bus indentation */
|
|
printf("%*c", indent + 1, ' ');
|
|
|
|
if (pci_print_dev(hose, dev)) {
|
|
printf("%02x:%02x.%-*x - %04x:%04x - %s\n",
|
|
PCI_BUS(dev), PCI_DEV(dev), 6 - indent, PCI_FUNC(dev),
|
|
vendor, device, pci_class_str(class >> 8));
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_PCI_PNP
|
|
sub_bus = max((unsigned int)pciauto_config_device(hose, dev),
|
|
sub_bus);
|
|
#else
|
|
cfg = pci_find_config(hose, class, vendor, device,
|
|
PCI_BUS(dev), PCI_DEV(dev), PCI_FUNC(dev));
|
|
if (cfg) {
|
|
cfg->config_device(hose, dev, cfg);
|
|
sub_bus = max(sub_bus,
|
|
(unsigned int)hose->current_busno);
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_PCI_SCAN_SHOW
|
|
indent--;
|
|
#endif
|
|
|
|
if (hose->fixup_irq)
|
|
hose->fixup_irq(hose, dev);
|
|
}
|
|
|
|
return sub_bus;
|
|
}
|
|
|
|
int pci_hose_scan(struct pci_controller *hose)
|
|
{
|
|
#if defined(CONFIG_PCI_BOOTDELAY)
|
|
char *s;
|
|
int i;
|
|
|
|
if (!gd->pcidelay_done) {
|
|
/* wait "pcidelay" ms (if defined)... */
|
|
s = getenv("pcidelay");
|
|
if (s) {
|
|
int val = simple_strtoul(s, NULL, 10);
|
|
for (i = 0; i < val; i++)
|
|
udelay(1000);
|
|
}
|
|
gd->pcidelay_done = 1;
|
|
}
|
|
#endif /* CONFIG_PCI_BOOTDELAY */
|
|
|
|
/*
|
|
* Start scan at current_busno.
|
|
* PCIe will start scan at first_busno+1.
|
|
*/
|
|
/* For legacy support, ensure current >= first */
|
|
if (hose->first_busno > hose->current_busno)
|
|
hose->current_busno = hose->first_busno;
|
|
#ifdef CONFIG_PCI_PNP
|
|
pciauto_config_init(hose);
|
|
#endif
|
|
return pci_hose_scan_bus(hose, hose->current_busno);
|
|
}
|
|
|
|
void pci_init(void)
|
|
{
|
|
hose_head = NULL;
|
|
|
|
/* now call board specific pci_init()... */
|
|
pci_init_board();
|
|
}
|
|
|
|
/* Returns the address of the requested capability structure within the
|
|
* device's PCI configuration space or 0 in case the device does not
|
|
* support it.
|
|
* */
|
|
int pci_hose_find_capability(struct pci_controller *hose, pci_dev_t dev,
|
|
int cap)
|
|
{
|
|
int pos;
|
|
u8 hdr_type;
|
|
|
|
pci_hose_read_config_byte(hose, dev, PCI_HEADER_TYPE, &hdr_type);
|
|
|
|
pos = pci_hose_find_cap_start(hose, dev, hdr_type & 0x7F);
|
|
|
|
if (pos)
|
|
pos = pci_find_cap(hose, dev, pos, cap);
|
|
|
|
return pos;
|
|
}
|
|
|
|
/* Find the header pointer to the Capabilities*/
|
|
int pci_hose_find_cap_start(struct pci_controller *hose, pci_dev_t dev,
|
|
u8 hdr_type)
|
|
{
|
|
u16 status;
|
|
|
|
pci_hose_read_config_word(hose, dev, PCI_STATUS, &status);
|
|
|
|
if (!(status & PCI_STATUS_CAP_LIST))
|
|
return 0;
|
|
|
|
switch (hdr_type) {
|
|
case PCI_HEADER_TYPE_NORMAL:
|
|
case PCI_HEADER_TYPE_BRIDGE:
|
|
return PCI_CAPABILITY_LIST;
|
|
case PCI_HEADER_TYPE_CARDBUS:
|
|
return PCI_CB_CAPABILITY_LIST;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int pci_find_cap(struct pci_controller *hose, pci_dev_t dev, int pos, int cap)
|
|
{
|
|
int ttl = PCI_FIND_CAP_TTL;
|
|
u8 id;
|
|
u8 next_pos;
|
|
|
|
while (ttl--) {
|
|
pci_hose_read_config_byte(hose, dev, pos, &next_pos);
|
|
if (next_pos < CAP_START_POS)
|
|
break;
|
|
next_pos &= ~3;
|
|
pos = (int) next_pos;
|
|
pci_hose_read_config_byte(hose, dev,
|
|
pos + PCI_CAP_LIST_ID, &id);
|
|
if (id == 0xff)
|
|
break;
|
|
if (id == cap)
|
|
return pos;
|
|
pos += PCI_CAP_LIST_NEXT;
|
|
}
|
|
return 0;
|
|
}
|