Skip to content
Snippets Groups Projects
pci_common.c 8.07 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*
     * 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.
     *
     * SPDX-License-Identifier:	GPL-2.0+
     */
    
    #include <common.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 (getenv("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) {
    
    /*
     * 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 */