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

#include <amdblocks/amd_pci_util.h>
#include <console/console.h>
#include <device/pci_def.h>
#include <stdlib.h>
#include <types.h>

enum pcie_swizzle_pin {
	PIN_A = 0,
	PIN_B,
	PIN_C,
	PIN_D,
};

static const uint8_t pcie_swizzle_table[][4] = {
	{PIN_A, PIN_B, PIN_C, PIN_D},
	{PIN_B, PIN_C, PIN_D, PIN_A},
	{PIN_C, PIN_D, PIN_A, PIN_B},
	{PIN_D, PIN_A, PIN_B, PIN_C},
};

const struct pci_routing_info *get_pci_routing_info(unsigned int devfn)
{
	const struct pci_routing_info *routing_info;
	size_t entries = 0;

	routing_info = get_pci_routing_table(&entries);

	if (!routing_info || !entries)
		return NULL;

	for (size_t i = 0; i < entries; ++i, ++routing_info)
		if (routing_info->devfn == devfn)
			return routing_info;

	printk(BIOS_ERR, "Failed to find PCIe routing info for dev: %#x, fn: %#x\n",
	       PCI_SLOT(devfn), PCI_FUNC(devfn));

	return NULL;
}

unsigned int pci_calculate_irq(const struct pci_routing_info *routing_info,
					      unsigned int pin)
{
	unsigned int irq;

	if (routing_info->swizzle >= ARRAY_SIZE(pcie_swizzle_table))
		die("%s: swizzle %u out of bounds\n", __func__, routing_info->swizzle);

	if (pin >= ARRAY_SIZE(pcie_swizzle_table[routing_info->swizzle]))
		die("%s: pin %u out of bounds\n", __func__, pin);

	irq = routing_info->group * 4;
	irq += pcie_swizzle_table[routing_info->swizzle][pin];

	return irq;
}

void populate_pirq_data(void)
{
	const struct pci_routing_info *routing_table, *routing_entry;
	size_t entries = 0;
	struct pirq_struct *pirq;
	unsigned int irq;

	routing_table = get_pci_routing_table(&entries);

	if (!routing_table || !entries)
		return;

	pirq = calloc(entries, sizeof(*pirq));

	if (!pirq) {
		printk(BIOS_ERR, "%s: Allocation failed\n", __func__);
		return;
	}

	for (size_t i = 0; i < entries; ++i) {
		routing_entry = &routing_table[i];

		pirq[i].devfn = routing_entry->devfn;
		for (size_t j = 0; j < 4; ++j) {
			irq = pci_calculate_irq(routing_entry, j);

			pirq[i].PIN[j] = irq % 8;
		}
	}

	pirq_data_ptr = pirq;
	pirq_data_size = entries;
}
