| // 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 <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_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_COMPAT */ |