/* SPDX-License-Identifier: GPL-2.0-or-later */

#include <cbfs.h>
#include <assert.h>
#include <post.h>
#include <device/pci.h>
#include <soc/acpi.h>
#include <soc/ramstage.h>
#include <soc/soc_util.h>

struct pci_resource {
	struct device        *dev;
	struct resource      *res;
	struct pci_resource  *next;
};

struct stack_dev_resource {
	uint8_t                   align;
	struct pci_resource       *children;
	struct stack_dev_resource *next;
};

static void assign_stack_resources(struct iiostack_resource *stack_list,
	struct device *dev, struct resource *bridge);

static void xeonsp_pci_domain_scan_bus(struct device *dev)
{
	DEV_FUNC_ENTER(dev);
	struct bus *link = dev->link_list;

	printk(BIOS_SPEW, "%s:%s scanning buses under device %s\n",
		__FILE__, __func__, dev_path(dev));
	while (link != NULL) {
		if (link->secondary == 0)  { // scan only PSTACK buses
			struct device *d;
			for (d = link->children; d; d = d->sibling)
				pci_probe_dev(d, link, d->path.pci.devfn);
			scan_bridges(link);
		} else {
			pci_scan_bus(link, PCI_DEVFN(0, 0), 0xff);
		}
		link = link->next;
	}
	DEV_FUNC_EXIT(dev);
}

static void xeonsp_pci_dev_iterator(struct bus *bus,
		void (*dev_iterator)(struct device *, void *),
		void (*res_iterator)(struct device *, struct resource *, void *),
		void *data)
{
	struct device *curdev;
	struct resource *res;

	/* Walk through all devices and find which resources they need. */
	for (curdev = bus->children; curdev; curdev = curdev->sibling) {
		struct bus *link;

		if (!curdev->enabled)
			continue;

		if (!curdev->ops || !curdev->ops->read_resources) {
			if (curdev->path.type != DEVICE_PATH_APIC)
				printk(BIOS_ERR, "%s missing read_resources\n",
					dev_path(curdev));
			continue;
		}

		if (dev_iterator)
			dev_iterator(curdev, data);

		if (res_iterator) {
			for (res = curdev->resource_list; res; res = res->next)
				res_iterator(curdev, res, data);
		}

		/* Read in the resources behind the current device's links. */
		for (link = curdev->link_list; link; link = link->next)
			xeonsp_pci_dev_iterator(link, dev_iterator, res_iterator, data);
	}
}

static void xeonsp_pci_dev_read_resources(struct device *dev, void *data)
{
	post_log_path(dev);
	dev->ops->read_resources(dev);
}

static void xeonsp_pci_dev_dummy_func(struct device *dev)
{
}

static void xeonsp_reset_pci_op(struct device *dev, void *data)
{
	if (dev->ops)
		dev->ops->read_resources = xeonsp_pci_dev_dummy_func;
}

static STACK_RES *find_stack_for_bus(struct iiostack_resource *info, uint8_t bus)
{
	for (int i = 0; i < info->no_of_stacks; ++i) {
		if (bus >= info->res[i].BusBase && bus <= info->res[i].BusLimit)
			return &info->res[i];
	}
	return NULL;
}

static void add_res_to_stack(struct stack_dev_resource **root,
	struct device *dev, struct resource *res)
{
	struct stack_dev_resource *cur = *root;
	while (cur) {
		if (cur->align == res->align || cur->next == NULL) /* equal or last record */
			break;
		else if (cur->align > res->align) {
			if (cur->next->align < res->align) /* need to insert new record here */
				break;
			cur = cur->next;
		} else {
			break;
		}
	}

	struct stack_dev_resource *nr;
	if (!cur || cur->align != res->align) { /* need to add new record */
		nr = malloc(sizeof(struct stack_dev_resource));
		if (nr == 0)
			die("assign_resource_to_stack(): out of memory.\n");
		memset(nr, 0, sizeof(struct stack_dev_resource));
		nr->align = res->align;
		if (!cur) {
			*root = nr; /* head node */
		} else if (cur->align > nr->align) {
			if (cur->next == NULL) {
				cur->next = nr;
			} else {
				nr->next = cur->next;
				cur->next = nr;
			}
		} else { /* insert in the beginning */
			nr->next = cur;
			*root = nr;
		}
	} else {
		nr = cur;
	}

	assert(nr != NULL && nr->align == res->align);

	struct pci_resource *npr = malloc(sizeof(struct pci_resource));
	if (npr == NULL)
		die("%s: out of memory.\n", __func__);
	npr->res = res;
	npr->dev = dev;
	npr->next = NULL;

	if (nr->children == NULL) {
		nr->children = npr;
	} else {
		struct pci_resource *pr = nr->children;
		while (pr->next != NULL)
			pr = pr->next;
		pr->next = npr;
	}
}

static void reserve_dev_resources(STACK_RES *stack, unsigned long res_type,
	struct stack_dev_resource *res_root, struct resource *bridge)
{
	uint8_t align;
	uint64_t orig_base, base;

	if (res_type & IORESOURCE_IO)
		orig_base = stack->PciResourceIoBase;
	else if ((res_type & IORESOURCE_MEM) && ((res_type & IORESOURCE_PCI64) ||
		(!res_root && bridge && (bridge->flags & IORESOURCE_PREFETCH))))
		orig_base = stack->PciResourceMem64Base;
	else
		orig_base = stack->PciResourceMem32Base;

	align = 0;
	base = orig_base;
	int first = 1;
	while (res_root) { /* loop through all devices grouped by alignment requirements */
		struct pci_resource *pr = res_root->children;
		while (pr) {
			if (first) {
				if (bridge) { /* takes highest alignment */
					if (bridge->align < pr->res->align)
						bridge->align = pr->res->align;
					orig_base = ALIGN_UP(orig_base, 1 << bridge->align);
				} else {
					orig_base = ALIGN_UP(orig_base, 1 << pr->res->align);
				}
				base = orig_base;

				if (bridge)
					bridge->base = base;
				pr->res->base = base;
				first = 0;
			} else {
				pr->res->base = ALIGN_UP(base, 1 << pr->res->align);
			}
			pr->res->limit = pr->res->base + pr->res->size - 1;
			base = pr->res->limit + 1;
			pr->res->flags |= (IORESOURCE_ASSIGNED);
			pr = pr->next;
		}
		res_root = res_root->next;
	}

	if (bridge) {
		/* this bridge doesn't have any resources, will set it to default window */
		if (first) {
			orig_base = ALIGN_UP(orig_base, 1 << bridge->align);
			bridge->base = orig_base;
			base = orig_base + (1ULL << bridge->gran);
		}

		bridge->size = ALIGN_UP(base, 1 << bridge->align) - bridge->base;

		bridge->limit = bridge->base + bridge->size - 1;
		bridge->flags |= (IORESOURCE_ASSIGNED);
		base = bridge->limit + 1;
	}

	/* update new limits */
	if (res_type & IORESOURCE_IO)
		stack->PciResourceIoBase = base;
	else if ((res_type & IORESOURCE_MEM) && ((res_type & IORESOURCE_PCI64) ||
		(!res_root && bridge && (bridge->flags & IORESOURCE_PREFETCH))))
		stack->PciResourceMem64Base = base;
	else
		stack->PciResourceMem32Base = base;
}

static void reclaim_resource_mem(struct stack_dev_resource *res_root)
{
	while (res_root) { /* loop through all devices grouped by alignment requirements */
		/* free pci_resource */
		struct pci_resource *pr = res_root->children;
		while (pr) {
			struct pci_resource *dpr = pr;
			pr = pr->next;
			free(dpr);
		}

		/* free stack_dev_resource */
		struct stack_dev_resource *ddr = res_root;
		res_root = res_root->next;
		free(ddr);
	}
}

static void assign_bridge_resources(struct iiostack_resource *stack_list,
	struct device *dev, struct resource *bridge)
{
	struct resource *res;
	if (!dev->enabled)
		return;

	for (res = dev->resource_list; res; res = res->next) {
		if (!(res->flags & IORESOURCE_BRIDGE) ||
			(bridge && ((bridge->flags & (IORESOURCE_IO | IORESOURCE_MEM |
				IORESOURCE_PREFETCH | IORESOURCE_PCI64)) !=
					(res->flags & (IORESOURCE_IO | IORESOURCE_MEM |
						IORESOURCE_PREFETCH | IORESOURCE_PCI64)))))
			continue;

		assign_stack_resources(stack_list, dev, res);
		if (!bridge)
			continue;
		/* for 1st time update, overlading IORESOURCE_ASSIGNED */
		if (!(bridge->flags & IORESOURCE_ASSIGNED)) {
			bridge->base = res->base;
			bridge->limit = res->limit;
			bridge->flags |= (IORESOURCE_ASSIGNED);
		} else {
			/* update bridge range from child bridge range */
			if (res->base < bridge->base)
				bridge->base = res->base;
			if (res->limit > bridge->limit)
				bridge->limit = res->limit;
		}
		bridge->size = (bridge->limit - bridge->base + 1);
	}
}

static void assign_stack_resources(struct iiostack_resource *stack_list,
	struct device *dev, struct resource *bridge)
{
	struct bus *bus;

	/* Read in the resources behind the current device's links. */
	for (bus = dev->link_list; bus; bus = bus->next) {
		struct device *curdev;
		STACK_RES *stack;

		/* get IIO stack for this bus */
		stack = find_stack_for_bus(stack_list, bus->secondary);
		assert(stack != NULL);

		/* Assign resources to bridge */
		for (curdev = bus->children; curdev; curdev = curdev->sibling)
			assign_bridge_resources(stack_list, curdev, bridge);

		/* Pick non-bridged resources for resource allocation for each resource type */
		unsigned long flags[5] = {IORESOURCE_IO, IORESOURCE_MEM,
			(IORESOURCE_PCI64|IORESOURCE_MEM), (IORESOURCE_MEM|IORESOURCE_PREFETCH),
			(IORESOURCE_PCI64|IORESOURCE_MEM|IORESOURCE_PREFETCH)};
		uint8_t no_res_types = 5;
		if (bridge) {
			flags[0] = bridge->flags &
				(IORESOURCE_IO | IORESOURCE_MEM | IORESOURCE_PREFETCH);
			if ((bridge->flags & IORESOURCE_MEM) &&
				(bridge->flags & IORESOURCE_PREFETCH))
				flags[0] |= IORESOURCE_PCI64;
			no_res_types = 1;
		}

		/* Process each resource type */
		for (int rt = 0; rt < no_res_types; ++rt) {
			struct stack_dev_resource *res_root = NULL;

			for (curdev = bus->children; curdev; curdev = curdev->sibling) {
				struct resource *res;
				if (!curdev->enabled)
					continue;

				for (res = curdev->resource_list; res; res = res->next) {
					if ((res->flags & IORESOURCE_BRIDGE) || (res->flags &
						(IORESOURCE_STORED | IORESOURCE_RESERVE |
							IORESOURCE_FIXED | IORESOURCE_ASSIGNED)
						) || ((res->flags & (IORESOURCE_IO |
							IORESOURCE_MEM | IORESOURCE_PCI64
							| IORESOURCE_PREFETCH))
							!= flags[rt]) || res->size == 0)
						continue;
					else
						add_res_to_stack(&res_root, curdev, res);
				}
			}

			/* Allocate resources and update bridge range */
			if (res_root || (bridge && !(bridge->flags & IORESOURCE_ASSIGNED))) {
				reserve_dev_resources(stack, flags[rt], res_root, bridge);
				reclaim_resource_mem(res_root);
			}
		}
	}
}

static void xeonsp_constrain_pci_resources(struct device *dev, struct resource *res, void *data)
{
	STACK_RES *stack = (STACK_RES *) data;
	if (!(res->flags & IORESOURCE_FIXED))
		return;

	uint64_t base, limit;
	if (res->flags & IORESOURCE_IO) {
		base = stack->PciResourceIoBase;
		limit = stack->PciResourceIoLimit;
	} else if ((res->flags & IORESOURCE_MEM) && (res->flags & IORESOURCE_PCI64)) {
		base = stack->PciResourceMem64Base;
		limit = stack->PciResourceMem64Limit;
	} else {
		base = stack->PciResourceMem32Base;
		limit = stack->PciResourceMem32Limit;
	}

	if (((res->base + res->size - 1) < base) || (res->base > limit)) /* outside window */
		return;

	if (res->limit > limit) /* resource end is out of limit */
		limit = res->base - 1;
	else
		base = res->base + res->size;

	if (res->flags & IORESOURCE_IO) {
		stack->PciResourceIoBase = base;
		stack->PciResourceIoLimit = limit;
	} else if ((res->flags & IORESOURCE_MEM) && (res->flags & IORESOURCE_PCI64)) {
		stack->PciResourceMem64Base = base;
		stack->PciResourceMem64Limit = limit;
	} else {
		stack->PciResourceMem32Base = base;
		stack->PciResourceMem32Limit = limit;
	}
}

static void xeonsp_pci_domain_read_resources(struct device *dev)
{
	struct bus *link;

	DEV_FUNC_ENTER(dev);

	pci_domain_read_resources(dev);

	/*
	 * Walk through all devices in this domain and read resources.
	 * Since there is no callback when read resource operation is
	 * complete for all devices, domain read resource function initiates
	 * read resources for all devices and swaps read resource operation
	 * with dummy function to avoid warning.
	 */
	for (link = dev->link_list; link; link = link->next)
		xeonsp_pci_dev_iterator(link, xeonsp_pci_dev_read_resources, NULL, NULL);

	for (link = dev->link_list; link; link = link->next)
		xeonsp_pci_dev_iterator(link, xeonsp_reset_pci_op, NULL, NULL);

	/*
	 * 1. group devices, resources for each stack
	 * 2. order resources in descending order of requested resource allocation sizes
	 */
	struct iiostack_resource stack_info = {0};
	get_iiostack_info(&stack_info);

	/* constrain stack window */
	for (link = dev->link_list; link; link = link->next) {
		STACK_RES *stack = find_stack_for_bus(&stack_info, link->secondary);
		assert(stack != 0);
		xeonsp_pci_dev_iterator(link, NULL, xeonsp_constrain_pci_resources, stack);
	}

	/* assign resources */
	assign_stack_resources(&stack_info, dev, NULL);

	DEV_FUNC_EXIT(dev);
}

static void reset_resource_to_unassigned(struct device *dev, struct resource *res, void *data)
{
	if ((res->flags & (IORESOURCE_IO | IORESOURCE_MEM)) &&
		!(res->flags & (IORESOURCE_FIXED | IORESOURCE_RESERVE))) {
		res->flags &= ~IORESOURCE_ASSIGNED;
	}
}

static void xeonsp_pci_domain_set_resources(struct device *dev)
{
	DEV_FUNC_ENTER(dev);

	print_resource_tree(dev, BIOS_SPEW, "Before xeonsp pci domain set resource");

	/* reset bus 0 dev resource assignment - need to change them to FSP IIOStack window */
	xeonsp_pci_dev_iterator(dev->link_list, NULL, reset_resource_to_unassigned, NULL);

	/* update dev resources based on IIOStack IO/Mem32/Mem64 windows */
	xeonsp_pci_domain_read_resources(dev);

	struct bus *link = dev->link_list;
	while (link != NULL) {
		assign_resources(link);
		link = link->next;
	}

	print_resource_tree(dev, BIOS_SPEW, "After xeonsp pci domain set resource");

	DEV_FUNC_EXIT(dev);
}

static struct device_operations pci_domain_ops = {
	.read_resources = &pci_domain_read_resources,
	.set_resources = &xeonsp_pci_domain_set_resources,
	.scan_bus = &xeonsp_pci_domain_scan_bus,
#if CONFIG(HAVE_ACPI_TABLES)
	.write_acpi_tables  = &northbridge_write_acpi_tables,
#endif
};

static struct device_operations cpu_bus_ops = {
	.read_resources = noop_read_resources,
	.set_resources = noop_set_resources,
	.init = xeon_sp_init_cpus,
#if CONFIG(HAVE_ACPI_TABLES)
	/* defined in src/soc/intel/common/block/acpi/acpi.c */
	.acpi_fill_ssdt = generate_cpu_entries,
#endif
};

/* Attach IIO stack bus numbers with dummy device to PCI DOMAIN 0000 device */
static void attach_iio_stacks(struct device *dev)
{
	struct bus *iiostack_bus;
	struct device dummy;
	struct iiostack_resource stack_info = {0};

	DEV_FUNC_ENTER(dev);

	get_iiostack_info(&stack_info);
	for (int s = 0; s < stack_info.no_of_stacks; ++s) {
		/* only non zero bus no. needs to be enumerated */
		if (stack_info.res[s].BusBase == 0)
			continue;

		iiostack_bus = malloc(sizeof(struct bus));
		if (iiostack_bus == NULL)
			die("%s: out of memory.\n", __func__);
		memset(iiostack_bus, 0, sizeof(*iiostack_bus));
		memcpy(iiostack_bus, dev->bus, sizeof(*iiostack_bus));
		iiostack_bus->secondary = stack_info.res[s].BusBase;
		iiostack_bus->subordinate = stack_info.res[s].BusBase;
		iiostack_bus->dev = NULL;
		iiostack_bus->children = NULL;
		iiostack_bus->next = NULL;
		iiostack_bus->link_num = 1;

		dummy.bus = iiostack_bus;
		dummy.path.type = DEVICE_PATH_PCI;
		dummy.path.pci.devfn = 0;
		uint32_t id = pci_read_config32(&dummy, PCI_VENDOR_ID);
		if (id == 0xffffffff)
			printk(BIOS_WARNING, "IIO Stack device %s not visible\n",
				dev_path(&dummy));

		if (dev->link_list == NULL) {
			dev->link_list = iiostack_bus;
		} else {
			struct bus *nlink = dev->link_list;
			while (nlink->next != NULL)
				nlink = nlink->next;
			nlink->next = iiostack_bus;
		}
	}

	DEV_FUNC_EXIT(dev);
}

static void soc_enable_dev(struct device *dev)
{
	/* Set the operations if it is a special bus type */
	if (dev->path.type == DEVICE_PATH_DOMAIN) {
		dev->ops = &pci_domain_ops;
		attach_iio_stacks(dev);
	} else if (dev->path.type == DEVICE_PATH_CPU_CLUSTER) {
		dev->ops = &cpu_bus_ops;
	}
}

static void soc_init(void *data)
{
	printk(BIOS_DEBUG, "coreboot: calling fsp_silicon_init\n");
	fsp_silicon_init(false);
}

static void soc_final(void *data)
{
	// Temp Fix - should be done by FSP, in 2S bios completion
	// is not carried out on socket 2
	set_bios_init_completion();
}

static void soc_silicon_init_params(FSPS_UPD *silupd)
{
}

void platform_fsp_silicon_init_params_cb(FSPS_UPD *silupd)
{
	const struct microcode *microcode_file;
	size_t microcode_len;

	microcode_file = cbfs_boot_map_with_leak("cpu_microcode_blob.bin",
		CBFS_TYPE_MICROCODE, &microcode_len);

	if ((microcode_file != NULL) && (microcode_len != 0)) {
		/* Update CPU Microcode patch base address/size */
		silupd->FspsConfig.PcdCpuMicrocodePatchBase =
		       (uint32_t)microcode_file;
		silupd->FspsConfig.PcdCpuMicrocodePatchSize =
		       (uint32_t)microcode_len;
	}

	soc_silicon_init_params(silupd);
	mainboard_silicon_init_params(silupd);
}

struct chip_operations soc_intel_xeon_sp_skx_ops = {
	CHIP_NAME("Intel Skylake-SP")
	.enable_dev = soc_enable_dev,
	.init = soc_init,
	.final = soc_final
};

struct pci_operations soc_pci_ops = {
	.set_subsystem = pci_dev_set_subsystem,
};