/*
 * (C) Copyright 2001 Sysgo Real-Time Solutions, GmbH <www.elinos.com>
 * Andreas Heppel <aheppel@sysgo.de>
 *
 * (C) Copyright 2002
 * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */

/*
 * PCI routines
 */

#include <ppcboot.h>

#ifdef CONFIG_PCI

#include <command.h>
#include <cmd_boot.h>
#include <asm/processor.h>
#include <asm/io.h>
#include <cmd_pci.h>
#include <pci.h>

#if (CONFIG_COMMANDS & CFG_CMD_PCI)

unsigned char	ShortPCIListing = 0;

/*
 * Follows routines for the output of infos about devices on PCI bus.
 */

void pciinfo(int BusNum);

/*
 * Subroutine:  do_pciinfo
 *
 * Description: Handler for 'pciinfo' command..
 *
 * Inputs:		argv[1] may contain the number of the bus to be scanned.
 *				Default is bus 0.
 *
 * Return:      None
 *
 */
int do_pciinfo(cmd_tbl_t *cmdtp, bd_t *bd, int flag, int argc, char *argv[])
{
	char *s;
	int	bus_no = 0;

	if (argc == 2)
	{
		bus_no = (int)simple_strtoul(argv[1], NULL, 10);
	}

	if ((s = getenv("pci_listing")) != NULL)
		ShortPCIListing = (strcmp(s, "short") == 0) ? 1 : 0;
	else
		ShortPCIListing = 0;

	pciinfo(bus_no);

	return 0;
}

void pci_header_show(pci_dev_t dev);
void pci_header_show_brief(pci_dev_t dev);

/*
 * Subroutine:  pciinfo
 *
 * Description: Show information about devices on PCI bus.
 *				Depending on the define CFG_SHORT_PCI_LISTING
 *				the output will be more or less exhaustive.
 *
 * Inputs:		bus_no		the number of the bus to be scanned.
 *
 * Return:      None
 *
 */
void pciinfo(int BusNum)
{
	int Device;
	int Function;
	unsigned char HeaderType;
	unsigned short VendorID;
	pci_dev_t dev;

	printf("Scanning PCI devices on bus %d\n", BusNum);

	if (ShortPCIListing) {
		printf("No.   VendorId   DeviceId   Device Class       Sub-Class\n");
		printf("________________________________________________________\n");
	}

	for (Device = 0; Device < PCI_MAX_PCI_DEVICES; Device++) {
		HeaderType = 0;
		VendorID = 0;
		for (Function = 0; Function < PCI_MAX_PCI_FUNCTIONS; Function++) {
			/*
			 * If this is not a multi-function device, we skip the rest.
			 */
			if (Function && !(HeaderType & 0x80))
				break;

			dev = PCI_BDF(BusNum, Device, Function);

			pci_read_config_word(dev, PCI_VENDOR_ID, &VendorID);
			if ((VendorID == 0xFFFF) || (VendorID == 0x0000))
				continue;

			pci_read_config_byte(dev, PCI_HEADER_TYPE, &HeaderType);

			if (ShortPCIListing)
			{
				printf("%02d.%d  ", Device, Function);
				pci_header_show_brief(dev);
			}
			else
			{
				printf("\nFound PCI device %d, function %d:\n",
				       Device, Function);
				pci_header_show(dev);
			}
	    }
    }
}

char* pci_classes_str(u8 class)
{
	static char *pci_classes[] = {
		"Build before PCI Rev2.0",
		"Mass storage controller",
		"Network controller     ",
		"Display controller     ",
		"Multimedia device      ",
		"Memory controller      ",
		"Bridge device          ",
		"Simple comm. controller",
		"Base system peripheral ",
		"Input device           ",
		"Docking station        ",
		"Processor              ",
		"Serial bus controller  ",
		"Reserved entry         ",
		"Does not fit any class "
	};

	if (class < (sizeof pci_classes / sizeof *pci_classes))
		return pci_classes[(int) class];

	return  "???                    ";
}

/*
 * Subroutine:  pci_header_show_brief
 *
 * Description: Reads and prints the header of the 
 * 		specified PCI device in short form.
 *
 * Inputs:	dev      Bus+Device+Function number
 *
 * Return:      None
 *
 */
void pci_header_show_brief(pci_dev_t dev)
{
	u16 vendor, device;
	u8 class, subclass;

	pci_read_config_word(dev, PCI_VENDOR_ID, &vendor);
	pci_read_config_word(dev, PCI_DEVICE_ID, &device);
	pci_read_config_byte(dev, PCI_CLASS_CODE, &class);
	pci_read_config_byte(dev, PCI_CLASS_SUB_CODE, &subclass);

	printf("0x%.4x     0x%.4x     %s 0x%.2x\n",
	       vendor, device, 
	       pci_classes_str(class), subclass);
}

/*
 * Subroutine:  PCI_Header_Show
 *
 * Description: Reads the header of the specified PCI device.
 *
 * Inputs:		BusDevFunc      Bus+Device+Function number
 *
 * Return:      None
 *
 */
void pci_header_show(pci_dev_t dev)
{
	u8 _byte, header_type;
	u16 _word;
	u32 _dword;

#define PRINT(msg, type, reg) \
	pci_read_config_##type(dev, reg, &_##type); \
	printf(msg, _##type)

#define PRINT2(msg, type, reg, func) \
	pci_read_config_##type(dev, reg, &_##type); \
	printf(msg, _##type, func(_##type))

	pci_read_config_byte(dev, PCI_HEADER_TYPE, &header_type);

	PRINT ("  vendor ID =                   0x%.4x\n", word, PCI_VENDOR_ID);
	PRINT ("  device ID =                   0x%.4x\n", word, PCI_DEVICE_ID);
	PRINT ("  command register =            0x%.4x\n", word, PCI_COMMAND);
	PRINT ("  status register =             0x%.4x\n", word, PCI_STATUS);
	PRINT ("  revision ID =                 0x%.2x\n", byte, PCI_REVISION_ID);
	PRINT2("  class code =                  0x%.2x (%s)\n", byte, PCI_CLASS_CODE, 
	       							pci_classes_str);
	PRINT ("  sub class code =              0x%.2x\n", byte, PCI_CLASS_SUB_CODE);
	PRINT ("  programming interface =       0x%.2x\n", byte, PCI_CLASS_PROG);
	PRINT ("  cache line =                  0x%.2x\n", byte, PCI_CACHE_LINE_SIZE);
	PRINT ("  latency time =                0x%.2x\n", byte, PCI_LATENCY_TIMER);
	PRINT ("  header type =                 0x%.2x\n", byte, PCI_HEADER_TYPE);
	PRINT ("  BIST =                        0x%.2x\n", byte, PCI_BIST);
	PRINT ("  base address 0 =              0x%.8x\n", dword, PCI_BASE_ADDRESS_0);
	PRINT ("  base address 1 =              0x%.8x\n", dword, PCI_BASE_ADDRESS_1);

	if (header_type & 0x01) {		/* PCI-to-PCI bridge */
		PRINT ("  primary bus number =          0x%.2x\n", byte, PCI_PRIMARY_BUS);
		PRINT ("  secondary bus number =        0x%.2x\n", byte, PCI_SECONDARY_BUS);
		PRINT ("  subordinate bus number =      0x%.2x\n", byte, PCI_SUBORDINATE_BUS);
		PRINT ("  secondary latency timer =     0x%.2x\n", byte, PCI_SEC_LATENCY_TIMER);
		PRINT ("  IO base =                     0x%.2x\n", byte, PCI_IO_BASE);
		PRINT ("  IO limit =                    0x%.2x\n", byte, PCI_IO_LIMIT);
		PRINT ("  secondary status =            0x%.4x\n", word, PCI_SEC_STATUS);
		PRINT ("  memory base =                 0x%.4x\n", word, PCI_MEMORY_BASE);
		PRINT ("  memory limit =                0x%.4x\n", word, PCI_MEMORY_LIMIT);
		PRINT ("  prefetch memory base =        0x%.4x\n", word, PCI_PREF_MEMORY_BASE);
		PRINT ("  prefetch memory limit =       0x%.4x\n", word, PCI_PREF_MEMORY_LIMIT);
		PRINT ("  prefetch memory base upper =  0x%.8x\n", dword, PCI_PREF_BASE_UPPER32);
		PRINT ("  prefetch memory limit upper = 0x%.8x\n", dword, PCI_PREF_LIMIT_UPPER32);
		PRINT ("  IO base upper 16 bits =       0x%.4x\n", word, PCI_IO_BASE_UPPER16);
		PRINT ("  IO limit upper 16 bits =      0x%.4x\n", word, PCI_IO_LIMIT_UPPER16);
		PRINT ("  expansion ROM base address =  0x%.8x\n", dword, PCI_ROM_ADDRESS1);
		PRINT ("  interrupt line =              0x%.2x\n", byte, PCI_INTERRUPT_LINE);
		PRINT ("  interrupt pin =               0x%.2x\n", byte, PCI_INTERRUPT_PIN);
		PRINT ("  bridge control =              0x%.4x\n", word, PCI_BRIDGE_CONTROL);
    } else {					/* PCI device */
		PRINT("  base address 2 =              0x%.8x\n", dword, PCI_BASE_ADDRESS_2);
		PRINT("  base address 3 =              0x%.8x\n", dword, PCI_BASE_ADDRESS_3);
		PRINT("  base address 4 =              0x%.8x\n", dword, PCI_BASE_ADDRESS_4);
		PRINT("  base address 5 =              0x%.8x\n", dword, PCI_BASE_ADDRESS_5);
		PRINT("  cardBus CIS pointer =         0x%.8x\n", dword, PCI_CARDBUS_CIS);
		PRINT("  sub system vendor ID =        0x%.4x\n", word, PCI_SUBSYSTEM_VENDOR_ID);
		PRINT("  sub system ID =               0x%.4x\n", word, PCI_SUBSYSTEM_ID);
		PRINT("  expansion ROM base address =  0x%.8x\n", dword, PCI_ROM_ADDRESS);
		PRINT("  interrupt line =              0x%.2x\n", byte, PCI_INTERRUPT_LINE);
		PRINT("  interrupt pin =               0x%.2x\n", byte, PCI_INTERRUPT_PIN);
		PRINT("  min Grant =                   0x%.2x\n", byte, PCI_MIN_GNT);
		PRINT("  max Latency =                 0x%.2x\n", byte, PCI_MAX_LAT);
    }

#undef PRINT
#undef PRINT2
}

#endif /* (CONFIG_COMMANDS & CFG_CMD_PCI) */

#endif /* CONFIG_PCI */
