FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );

#include <stdint.h>
#include <ipxe/timer.h>
#include <ipxe/pci.h>
#include <ipxe/pcibackup.h>

static int pci_find_capability_common ( struct pci_device *pci,
					uint8_t pos, int cap ) {
	uint8_t id;
	int ttl = 48;

	while ( ttl-- && pos >= 0x40 ) {
		pos &= ~3;
		pci_read_config_byte ( pci, pos + PCI_CAP_ID, &id );
		DBG ( "PCI Capability: %d\n", id );
		if ( id == 0xff )
			break;
		if ( id == cap )
			return pos;
		pci_read_config_byte ( pci, pos + PCI_CAP_NEXT, &pos );
	}
	return 0;
}

/**
 * Look for a PCI capability
 *
 * @v pci		PCI device to query
 * @v cap		Capability code
 * @ret address		Address of capability, or 0 if not found
 *
 * Determine whether or not a device supports a given PCI capability.
 * Returns the address of the requested capability structure within
 * the device's PCI configuration space, or 0 if the device does not
 * support it.
 */
int pci_find_capability ( struct pci_device *pci, int cap ) {
	uint16_t status;
	uint8_t pos;
	uint8_t hdr_type;

	pci_read_config_word ( pci, PCI_STATUS, &status );
	if ( ! ( status & PCI_STATUS_CAP_LIST ) )
		return 0;

	pci_read_config_byte ( pci, PCI_HEADER_TYPE, &hdr_type );
	switch ( hdr_type & PCI_HEADER_TYPE_MASK ) {
	case PCI_HEADER_TYPE_NORMAL:
	case PCI_HEADER_TYPE_BRIDGE:
	default:
		pci_read_config_byte ( pci, PCI_CAPABILITY_LIST, &pos );
		break;
	case PCI_HEADER_TYPE_CARDBUS:
		pci_read_config_byte ( pci, PCI_CB_CAPABILITY_LIST, &pos );
		break;
	}
	return pci_find_capability_common ( pci, pos, cap );
}

/**
 * Look for another PCI capability
 *
 * @v pci		PCI device to query
 * @v pos		Address of the current capability
 * @v cap		Capability code
 * @ret address		Address of capability, or 0 if not found
 *
 * Determine whether or not a device supports a given PCI capability
 * starting the search at a given address within the device's PCI
 * configuration space. Returns the address of the next capability
 * structure within the device's PCI configuration space, or 0 if the
 * device does not support another such capability.
 */
int pci_find_next_capability ( struct pci_device *pci, int pos, int cap ) {
	uint8_t new_pos;

	pci_read_config_byte ( pci, pos + PCI_CAP_NEXT, &new_pos );
	return pci_find_capability_common ( pci, new_pos, cap );
}

/**
 * Find the size of a PCI BAR
 *
 * @v pci		PCI device
 * @v reg		PCI register number
 * @ret size		BAR size
 *
 * It should not be necessary for any Etherboot code to call this
 * function.
 */
unsigned long pci_bar_size ( struct pci_device *pci, unsigned int reg ) {
	uint16_t cmd;
	uint32_t start, size;

	/* Save the original command register */
	pci_read_config_word ( pci, PCI_COMMAND, &cmd );
	/* Save the original bar */
	pci_read_config_dword ( pci, reg, &start );
	/* Compute which bits can be set */
	pci_write_config_dword ( pci, reg, ~0 );
	pci_read_config_dword ( pci, reg, &size );
	/* Restore the original size */
	pci_write_config_dword ( pci, reg, start );
	/* Find the significant bits */
	/* Restore the original command register. This reenables decoding. */
	pci_write_config_word ( pci, PCI_COMMAND, cmd );
	if ( start & PCI_BASE_ADDRESS_SPACE_IO ) {
		size &= ~PCI_BASE_ADDRESS_IO_MASK;
	} else {
		size &= ~PCI_BASE_ADDRESS_MEM_MASK;
	}
	/* Find the lowest bit set */
	size = size & ~( size - 1 );
	return size;
}

/**
 * Perform PCI Express function-level reset (FLR)
 *
 * @v pci		PCI device
 * @v exp		PCI Express Capability address
 */
void pci_reset ( struct pci_device *pci, unsigned int exp ) {
	struct pci_config_backup backup;
	uint16_t control;

	/* Back up configuration space */
	pci_backup ( pci, &backup, PCI_CONFIG_BACKUP_STANDARD, NULL );

	/* Perform a PCIe function-level reset */
	pci_read_config_word ( pci, ( exp + PCI_EXP_DEVCTL ), &control );
	control |= PCI_EXP_DEVCTL_FLR;
	pci_write_config_word ( pci, ( exp + PCI_EXP_DEVCTL ), control );

	/* Allow time for reset to complete */
	mdelay ( PCI_EXP_FLR_DELAY_MS );

	/* Restore configuration */
	pci_restore ( pci, &backup, PCI_CONFIG_BACKUP_STANDARD, NULL );
}
