/* SPDX-License-Identifier: GPL-2.0-only */

/*
 * Derived from Cavium's BSD-3 Clause OCTEONTX-SDK-6.2.0.
 */

#include <console/console.h>
#include <device/mmio.h>
#include <device/pci.h>
#include <device/pci_ops.h>
#include <soc/addressmap.h>
#include <soc/cavium/common/pci/chip.h>
#include <soc/ecam.h>

#define CAVM_PCCPF_XXX_VSEC_CTL 0x108
#define CAVM_PCCPF_XXX_VSEC_SCTL 0x10c

/*
 * Hide PCI device function on BUS 1 in non secure world.
 */
static void disable_func(unsigned int devfn)
{
	u64 *addr;
	printk(BIOS_DEBUG, "PCI: 01:%02x.%x is secure\n", devfn >> 3,
	       devfn & 7);

	/* disable function */
	addr = (void *)ECAM0_RSLX_SDIS;
	u64 reg = read64(&addr[devfn]);
	reg &= ~3;
	reg |= 2;
	write64(&addr[devfn], reg);
}

/*
 * Show PCI device function on BUS 1 in non secure world.
 */
static void enable_func(unsigned int devfn)
{
	u64 *addr;

	printk(BIOS_DEBUG, "PCI: 01:%02x.%x is insecure\n", devfn >> 3,
	       devfn & 7);

	/* enable function */
	addr = (void *)ECAM0_RSLX_SDIS;
	u64 reg = read64(&addr[devfn]);
	reg &= ~3;
	write64(&addr[devfn], reg);

	addr = (void *)ECAM0_RSLX_NSDIS;
	reg = read64(&addr[devfn]);
	reg &= ~1;
	write64(&addr[devfn], reg);
}

/*
 * Hide PCI device on BUS 0 in non secure world.
 */
static void disable_device(unsigned int dev)
{
	u64 *addr;

	printk(BIOS_DEBUG, "PCI: 00:%02x.0 is secure\n", dev);

	/* disable function */
	addr = (void *)ECAM0_DEVX_SDIS;
	u64 reg = read64(&addr[dev]);
	reg &= ~3;
	write64(&addr[dev], reg);

	addr = (void *)ECAM0_DEVX_NSDIS;
	reg = read64(&addr[dev]);
	reg |= 1;
	write64(&addr[dev], reg);
}

/*
 * Show PCI device on BUS 0 in non secure world.
 */
static void enable_device(unsigned int dev)
{
	u64 *addr;

	printk(BIOS_DEBUG, "PCI: 00:%02x.0 is insecure\n", dev);

	/* enable function */
	addr = (void *)ECAM0_DEVX_SDIS;
	u64 reg = read64(&addr[dev]);
	reg &= ~3;
	write64(&addr[dev], reg);

	addr = (void *)ECAM0_DEVX_NSDIS;
	reg = read64(&addr[dev]);
	reg &= ~1;
	write64(&addr[dev], reg);
}

static void ecam0_read_resources(struct device *dev)
{
	/* There are no dynamic PCI resources on Cavium SoC */
}

static void ecam0_fix_missing_devices(struct bus *link)
{
	size_t i;

	/**
	 * Cavium thinks it's a good idea to violate the PCI spec.
	 * Disabled multi-function PCI devices might have active functions.
	 * Add devices here manually, as coreboot's PCI allocator won't find
	 * them otherwise...
	 */
	for (i = 0; i <= PCI_DEVFN(0x1f, 7); i++) {
		struct device_path pci_path;
		struct device *child;

		pci_path.type = DEVICE_PATH_PCI;
		pci_path.pci.devfn = i;

		child = find_dev_path(link, &pci_path);
		if (!child)
			pci_probe_dev(NULL, link, i);
	}
}

/**
 * pci_enable_msix - configure device's MSI-X capability structure
 * @dev: pointer to the pci_dev data structure of MSI-X device function
 * @entries: pointer to an array of MSI-X entries
 * @nvec: number of MSI-X irqs requested for allocation by device driver
 *
 * Setup the MSI-X capability structure of device function with the number
 * of requested irqs upon its software driver call to request for
 * MSI-X mode enabled on its hardware device function. A return of zero
 * indicates the successful configuration of MSI-X capability structure.
 * A return of < 0 indicates a failure.
 * Or a return of > 0 indicates that driver request is exceeding the number
 * of irqs or MSI-X vectors available. Driver should use the returned value to
 * re-send its request.
 **/
static size_t ecam0_pci_enable_msix(struct device *dev,
				    struct msix_entry *entries, size_t nvec)
{
	struct msix_entry *msixtable;
	u32 offset;
	u8 bar_idx;
	u64 bar;
	size_t nr_entries;
	size_t i;
	u16 control;

	if (!entries) {
		printk(BIOS_ERR, "%s: No entries specified\n", __func__);
		return -1;
	}

	const size_t pos = pci_find_capability(dev, PCI_CAP_ID_MSIX);
	if (!pos) {
		printk(BIOS_ERR, "%s: Device not MSI-X capable\n",
		       dev_path(dev));
		return -1;
	}
	nr_entries = pci_msix_table_size(dev);
	if (nvec > nr_entries) {
		printk(BIOS_ERR, "%s: Specified to many table entries\n",
		       dev_path(dev));
		return nr_entries;
	}

	/* Ensure MSI-X is disabled while it is set up */
	control = pci_read_config16(dev, pos + PCI_MSIX_FLAGS);
	control &= ~PCI_MSIX_FLAGS_ENABLE;
	pci_write_config16(dev, pos + PCI_MSIX_FLAGS, control);

	/* Find MSI-X table region */
	offset = 0;
	bar_idx = 0;
	if (pci_msix_table_bar(dev, &offset, &bar_idx)) {
		printk(BIOS_ERR, "%s: Failed to find MSI-X entry\n",
		       dev_path(dev));
		return -1;
	}
	bar = ecam0_get_bar_val(dev, bar_idx);
	if (!bar) {
		printk(BIOS_ERR, "%s: Failed to find MSI-X bar\n",
		       dev_path(dev));
		return -1;
	}
	msixtable = (struct msix_entry *)((void *)bar + offset);

	/*
	 * Some devices require MSI-X to be enabled before we can touch the
	 * MSI-X registers.  We need to mask all the vectors to prevent
	 * interrupts coming in before they're fully set up.
	 */
	control |= PCI_MSIX_FLAGS_MASKALL | PCI_MSIX_FLAGS_ENABLE;
	pci_write_config16(dev, pos + PCI_MSIX_FLAGS, control);

	for (i = 0; i < nvec; i++) {
		write64(&msixtable[i].addr, entries[i].addr);
		write32(&msixtable[i].data, entries[i].data);
		write32(&msixtable[i].vec_control, entries[i].vec_control);
	}

	control &= ~PCI_MSIX_FLAGS_MASKALL;
	pci_write_config16(dev, pos + PCI_MSIX_FLAGS, control);

	return 0;
}

static void ecam0_init(struct device *dev)
{
	struct soc_cavium_common_pci_config *config;
	struct device *child, *child_last;
	size_t i;
	u32 reg32;

	printk(BIOS_INFO, "ECAM0: init\n");
	const struct device *bridge = pcidev_on_root(1, 0);
	if (!bridge) {
		printk(BIOS_INFO, "ECAM0: ERROR: PCI 00:01.0 not found.\n");
		return;
	}
	/**
	 * Search for missing devices on BUS 1.
	 * Only required for ARI capability programming.
	 */
	ecam0_fix_missing_devices(bridge->downstream);

	/* Program secure ARI capability on bus 1 */
	child_last = NULL;
	for (i = 0; i <= PCI_DEVFN(0x1f, 7); i++) {
		child = pcidev_path_behind(bridge->downstream, i);
		if (!child || !child->enabled)
			continue;

		if (child_last) {
			/* Program ARI capability of the previous device */
			reg32 = pci_read_config32(child_last,
						  CAVM_PCCPF_XXX_VSEC_SCTL);
			reg32 &= ~(0xffU << 24);
			reg32 |= child->path.pci.devfn << 24;
			pci_write_config32(child_last, CAVM_PCCPF_XXX_VSEC_SCTL,
					   reg32);
		}
		child_last = child;
	}

	/* Program insecure ARI capability on bus 1 */
	child_last = NULL;
	for (i = 0; i <= PCI_DEVFN(0x1f, 7); i++) {
		child = pcidev_path_behind(bridge->downstream, i);
		if (!child)
			continue;
		config = child->chip_info;
		if (!child->enabled || (config && config->secure))
			continue;

		if (child_last) {
			/* Program ARI capability of the previous device */
			reg32 = pci_read_config32(child_last,
						  CAVM_PCCPF_XXX_VSEC_CTL);
			reg32 &= ~(0xffU << 24);
			reg32 |= child->path.pci.devfn << 24;
			pci_write_config32(child_last, CAVM_PCCPF_XXX_VSEC_CTL,
					   reg32);
		}
		child_last = child;
	}

	/* Enable / disable devices on bus 0 */
	for (i = 0; i <= 0x1f; i++) {
		child = pcidev_on_root(i, 0);
		config = child ? child->chip_info : NULL;
		if (child && child->enabled && config && !config->secure)
			enable_device(i);
		else
			disable_device(i);
	}

	/* Enable / disable devices and functions on bus 1 */
	for (i = 0; i <= PCI_DEVFN(0x1f, 7); i++) {
		child = pcidev_path_behind(bridge->downstream, i);
		config = child ? child->chip_info : NULL;
		if (child && child->enabled &&
		    ((config && !config->secure) || !config))
			enable_func(i);
		else
			disable_func(i);
	}

	/* Apply IRQ on PCI devices */
	/* UUA */
	for (i = 0; i < 4; i++) {
		child = pcidev_path_behind(bridge->downstream,
				      PCI_DEVFN(8, i));
		if (!child)
			continue;

		struct msix_entry entry[2] = {
			{.addr = CAVM_GICD_SETSPI_NSR, .data = 37 + i},
			{.addr = CAVM_GICD_CLRSPI_NSR, .data = 37 + i},
		};

		ecam0_pci_enable_msix(child, entry, 2);
	}

	printk(BIOS_INFO, "ECAM0: done\n");
}

struct device_operations pci_domain_ops_ecam0 = {
	.read_resources   = ecam0_read_resources,
	.init             = ecam0_init,
	.scan_bus         = pci_host_bridge_scan_bus,
};