Linux debugging

Check our new training course

Linux debugging, tracing, profiling & perf. analysis

Check our new training course
with Creative Commons CC-BY-SA
lecture and lab materials

Bootlin logo

Elixir Cross Referencer

/*
 * Copyright (c) 2009-2011, 2013-2014 Wind River Systems, Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @file
 * @brief PCI bus support
 *
 *
 * This module implements the PCI H/W access functions.
 */

#include <kernel.h>
#include <arch/cpu.h>

#include <pci/pci_mgr.h>
#include <string.h>
#include <board.h>

#if (PCI_CTRL_ADDR_REG == 0)
#error "PCI_CTRL_ADDR_REG cannot be zero"
#endif

#if (PCI_CTRL_DATA_REG == 0)
#error "PCI_CTRL_DATA_REG cannot be zero"
#endif

/**
 *
 * @brief Read a PCI controller register
 *
 * @param reg PCI register to read
 * @param data where to put the data
 * @param size size of the data to read (8/16/32 bits)
 *
 * This routine reads the specified register from the PCI controller and
 * places the data into the provided buffer.
 *
 * @return N/A
 *
 */

static void pci_ctrl_read(uint32_t reg, uint32_t *data, uint32_t size)
{
	/* read based on the size requested */

	switch (size) {
		/* long (32 bits) */
	case SYS_PCI_ACCESS_32BIT:
		*data = sys_in32(reg);
		break;
		/* word (16 bits) */
	case SYS_PCI_ACCESS_16BIT:
		*data = sys_in16(reg);
		break;
		/* byte (8 bits) */
	case SYS_PCI_ACCESS_8BIT:
		*data = sys_in8(reg);
		break;
	}
}

/**
 *
 * @brief Write a PCI controller register
 *
 * @param reg PCI register to write
 * @param data data to write
 * @param size size of the data to write (8/16/32 bits)
 *
 * This routine writes the provided data to the specified register in the PCI
 * controller.
 *
 * @return N/A
 *
 */

static void pci_ctrl_write(uint32_t reg, uint32_t data, uint32_t size)
{
	/* write based on the size requested */

	switch (size) {
		/* long (32 bits) */
	case SYS_PCI_ACCESS_32BIT:
		sys_out32(data, reg);
		break;
		/* word (16 bits) */
	case SYS_PCI_ACCESS_16BIT:
		sys_out16(data, reg);
		break;
		/* byte (8 bits) */
	case SYS_PCI_ACCESS_8BIT:
		sys_out8(data, reg);
		break;
	}
}

/**
 *
 * @brief Read the PCI controller data register
 *
 * @param controller controller number
 * @param offset is the offset within the data region
 * @param data is the returned data
 * @param size is the size of the data to read
 *
 * This routine reads the data register of the specified PCI controller.
 *
 * @return 0 or -1
 *
 */

static int pci_ctrl_data_read(uint32_t controller, uint32_t offset,
				uint32_t *data, uint32_t size)
{
	/* we only support one controller */

	if (controller != DEFAULT_PCI_CONTROLLER) {
		return (-1);
	}

	pci_ctrl_read(PCI_CTRL_DATA_REG + offset, data, size);

	return 0;
}

/**
 *
 * @brief Write the PCI controller data register
 *
 * @param controller the controller number
 * @param offset is the offset within the address register
 * @param data is the data to write
 * @param size is the size of the data
 *
 * This routine writes the provided data to the data register of the
 * specified PCI controller.
 *
 * @return 0 or -1
 *
 */

static int pci_ctrl_data_write(uint32_t controller, uint32_t offset,
			       uint32_t data, uint32_t size)
{
	/* we only support one controller */

	if (controller != DEFAULT_PCI_CONTROLLER) {
		return (-1);
	}

	pci_ctrl_write(PCI_CTRL_DATA_REG + offset, data, size);

	return 0;
}

/**
 *
 * @brief Write the PCI controller address register
 *
 * @param controller is the controller number
 * @param offset is the offset within the address register
 * @param data is the data to write
 * @param size is the size of the data
 *
 * This routine writes the provided data to the address register of the
 * specified PCI controller.
 *
 * @return 0 or -1
 *
 */

static int pci_ctrl_addr_write(uint32_t controller, uint32_t offset,
			       uint32_t data, uint32_t size)
{
	/* we only support one controller */

	if (controller != DEFAULT_PCI_CONTROLLER) {
		return (-1);
	}

	pci_ctrl_write(PCI_CTRL_ADDR_REG + offset, data, size);
	return 0;
}

/**
 *
 * @brief Read a PCI register from a device
 *
 * This routine reads data from a PCI device's configuration space.  The
 * device and register to read is specified by the address parameter ("addr")
 * and must be set appropriately by the caller.  The address is defined by
 * the structure type pci_addr_t and contains the following members:
 *
 *     bus:     PCI bus number (0-255)
 *     device:  PCI device number (0-31)
 *     func:    device function number (0-7)
 *     reg:     device 32-bit register number to read (0-63)
 *     offset:  offset within 32-bit register to read (0-3)
 *
 * The size parameter specifies the number of bytes to read from the PCI
 * configuration space, valid values are 1, 2, and 4 bytes.  A 32-bit value
 * is always returned but it will contain only the number of bytes specified
 * by the size parameter.
 *
 * If multiple PCI controllers are present in the system, the controller id
 * can be specified in the "controller" parameter.  If only one controller
 * is present, the id DEFAULT_PCI_CONTROLLER can be used to denote this
 * controller.
 *
 * Example:
 *
 *  union pci_addr_reg addr;
 *  uint32_t status;
 *
 *  addr.field.bus    = 0;    /@ PCI bus zero        @/
 *  addr.field.device = 1;    /@ PCI device one      @/
 *  addr.field.func   = 0;    /@ PCI function zero   @/
 *  addr.field.reg    = 4;    /@ PCI register 4      @/
 *  addr.field.offset = 0;    /@ PCI register offset @/
 *
 *  pci_read (DEFAULT_PCI_CONTROLLER, addr, sizeof(uint16_t), &status);
 *
 *
 * NOTE:
 *   Reading of PCI data must be performed as an atomic operation. It is up to
 *   the caller to enforce this.
 *
 * @param controller is the PCI controller number to use
 * @param addr is the PCI address to read
 * @param size is the size of the data in bytes
 * @param data is a pointer to the data read from the device
 *
 * @return N/A
 *
 */

void pci_read(uint32_t controller, union pci_addr_reg addr,
	      uint32_t size, uint32_t *data)
{
	uint32_t access_size;
	uint32_t access_offset;

	/* validate the access size */

	switch (size) {
	case 1:
		access_size = SYS_PCI_ACCESS_8BIT;
		access_offset = addr.field.offset;
		break;
	case 2:
		access_size = SYS_PCI_ACCESS_16BIT;
		access_offset = addr.field.offset;
		break;
	case 4:
	default:
		access_size = SYS_PCI_ACCESS_32BIT;
		access_offset = 0;
		break;
	}

	/* ensure enable has been set */

	addr.field.enable = 1;

	/* clear the offset for the address register */

	addr.field.offset = 0;

	/* read the data from the PCI controller */

	pci_ctrl_addr_write(
		controller, PCI_NO_OFFSET, addr.value, SYS_PCI_ACCESS_32BIT);

	pci_ctrl_data_read(controller, access_offset, data, access_size);
}

/**
 *
 * @brief Write a to a PCI register
 *
 * This routine writes data to a PCI device's configuration space.  The
 * device and register to write is specified by the address parameter ("addr")
 * and must be set appropriately by the caller.  The address is defined by
 * the structure type pci_addr_t and contains the following members:
 *
 *     bus:     PCI bus number (0-255)
 *     device:  PCI device number (0-31)
 *     func:    device function number (0-7)
 *     reg:     device register number to read (0-63)
 *     offset:  offset within 32-bit register to write (0-3)
 *
 * The size parameter specifies the number of bytes to write to the PCI
 * configuration space, valid values are 1, 2, and 4 bytes.  A 32-bit value
 * is always provided but only the number of bytes specified by the size
 * parameter will be written to the device.
 *
 * If multiple PCI controllers are present in the system, the controller id
 * can be specified in the "controller" parameter.  If only one controller
 * is present, the id DEFAULT_PCI_CONTROLLER can be used to denote this
 * controller.
 *
 * Example:
 *
 *  pci_addr_t addr;
 *  uint32_t bar0 = 0xE0000000;
 *
 *  addr.field.bus    = 0;    /@ PCI bus zero        @/
 *  addr.field.device = 1;    /@ PCI device one      @/
 *  addr.field.func   = 0;    /@ PCI function zero   @/
 *  addr.field.reg    = 16;   /@ PCI register 16     @/
 *  addr.field.offset = 0;    /@ PCI register offset @/
 *
 *  pci_write (DEFAULT_PCI_CONTROLLER, addr, sizeof(uint32_t), bar0);
 *
 * NOTE:
 *   Writing of PCI data must be performed as an atomic operation. It is up to
 *   the caller to enforce this.
 *
 * @param controller is the PCI controller to use
 * @param addr is the PCI addres to read
 * @param size is the size in bytes to write
 * @param data is the data to write
 *
 * @return N/A
 *
 */

void pci_write(uint32_t controller, union pci_addr_reg addr,
	       uint32_t size, uint32_t data)
{
	uint32_t access_size;
	uint32_t access_offset;

	/* validate the access size */

	switch (size) {
	case 1:
		access_size = SYS_PCI_ACCESS_8BIT;
		access_offset = addr.field.offset;
		break;
	case 2:
		access_size = SYS_PCI_ACCESS_16BIT;
		access_offset = addr.field.offset;
		break;
	case 4:
	default:
		access_size = SYS_PCI_ACCESS_32BIT;
		access_offset = 0;
		break;
	}

	/* ensure enable has been set */

	addr.field.enable = 1;

	/* clear the offset for the address register */

	addr.field.offset = 0;

	/* write the data to the PCI controller */

	pci_ctrl_addr_write(
		controller, PCI_NO_OFFSET, addr.value, SYS_PCI_ACCESS_32BIT);
	pci_ctrl_data_write(controller, access_offset, data, access_size);
}

/**
 *
 * @brief Get the PCI header for a device
 *
 * This routine reads the PCI header for the specified device and puts the
 * result in the supplied header structure.
 *
 * @return N/A
 */

void pci_header_get(uint32_t controller,
		    union pci_addr_reg pci_ctrl_addr,
		    union pci_dev *pci_dev_header)
{
	uint32_t i;

	/* clear out the header */

	memset(pci_dev_header, 0, sizeof(*pci_dev_header));

	/* fill in the PCI header from the device */

	for (i = 0; i < PCI_HEADER_WORDS; i++) {
		pci_ctrl_addr.field.reg = i;
		pci_read(controller,
			pci_ctrl_addr,
			sizeof(uint32_t),
			&pci_dev_header->words.word[i]);
	}
}