blob: 5231b69dc9ad0adf67cf87739df05eaf2e5b055d [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (c) 2014 Google, Inc
*
* (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.
*/
#include <common.h>
#include <dm.h>
#include <env.h>
#include <errno.h>
#include <pci.h>
#include <asm/io.h>
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;
};
}
__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 (env_get("pciconfighost") == NULL)
return 1;
#else
return 1;
#endif
}
return 0;
}
#if !defined(CONFIG_DM_PCI) || defined(CONFIG_DM_PCI_COMPAT)
/* 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);
}
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_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_TYPE) == 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;
}
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_hose_phys_to_bus(): Convert physical address to bus address
* @hose: PCI hose of the root PCI controller
* @phys_addr: physical address to convert
* @flags: flags of pci regions
* @return bus address if OK, 0 on error
*/
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_TYPE) == 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;
}
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);
}
pci_dev_t pci_hose_find_devices(struct pci_controller *hose, int busnum,
struct pci_device_id *ids, int *indexp)
{
int found_multi = 0;
u16 vendor, device;
u8 header_type;
pci_dev_t bdf;
int i;
for (bdf = PCI_BDF(busnum, 0, 0);
bdf < PCI_BDF(busnum + 1, 0, 0);
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 ((*indexp) <= 0)
return bdf;
(*indexp)--;
}
}
}
return -1;
}
pci_dev_t pci_find_class(uint find_class, int index)
{
int bus;
int devnum;
pci_dev_t bdf;
uint32_t class;
for (bus = 0; bus <= pci_last_busno(); bus++) {
for (devnum = 0; devnum < PCI_MAX_PCI_DEVICES - 1; devnum++) {
pci_read_config_dword(PCI_BDF(bus, devnum, 0),
PCI_CLASS_REVISION, &class);
if (class >> 16 == 0xffff)
continue;
for (bdf = PCI_BDF(bus, devnum, 0);
bdf <= PCI_BDF(bus, devnum,
PCI_MAX_PCI_FUNCTIONS - 1);
bdf += PCI_BDF(0, 0, 1)) {
pci_read_config_dword(bdf, PCI_CLASS_REVISION,
&class);
class >>= 8;
if (class != find_class)
continue;
/*
* Decrement the index. We want to return the
* correct device, so index is 0 for the first
* matching device, 1 for the second, etc.
*/
if (index) {
index--;
continue;
}
/* Return index'th controller. */
return bdf;
}
}
}
return -ENODEV;
}
#endif /* !CONFIG_DM_PCI || CONFIG_DM_PCI_COMPAT */