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

#include <assert.h>
#include <console/console.h>
#include <post.h>
#include <device/pci.h>
#include <soc/chip_common.h>
#include <soc/soc_util.h>
#include <soc/util.h>
#include <stdlib.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;
};

typedef enum {
	RES_TYPE_IO = 0,
	RES_TYPE_NONPREF_MEM,
	RES_TYPE_PREF_MEM,
	MAX_RES_TYPES
} RES_TYPE;

static RES_TYPE get_res_type(uint64_t flags)
{
	if (flags & IORESOURCE_IO)
		return RES_TYPE_IO;
	if (flags & IORESOURCE_MEM) {
		if (flags & IORESOURCE_PREFETCH) {
			printk(BIOS_DEBUG, "%s:%d flags: 0x%llx\n", __func__, __LINE__, flags);
			return RES_TYPE_PREF_MEM;
		}
		/* both 64-bit and 32-bit use below 4GB address space */
		return RES_TYPE_NONPREF_MEM;
	}
	printk(BIOS_ERR, "Invalid resource type 0x%llx\n", flags);
	die("");
}

static bool need_assignment(uint64_t flags)
{
	if (flags & (IORESOURCE_STORED | IORESOURCE_RESERVE | IORESOURCE_FIXED |
		IORESOURCE_ASSIGNED))
		return false;
	else
		return true;
}

static uint64_t get_resource_base(STACK_RES *stack, RES_TYPE res_type)
{
	if (res_type == RES_TYPE_IO) {
		assert(stack->PciResourceIoBase <= stack->PciResourceIoLimit);
		return stack->PciResourceIoBase;
	}
	if (res_type == RES_TYPE_NONPREF_MEM) {
		assert(stack->PciResourceMem32Base <= stack->PciResourceMem32Limit);
		return stack->PciResourceMem32Base;
	}
	assert(stack->PciResourceMem64Base <= stack->PciResourceMem64Limit);
	return stack->PciResourceMem64Base;
}

static void set_resource_base(STACK_RES *stack, RES_TYPE res_type, uint64_t base)
{
	if (res_type == RES_TYPE_IO) {
		assert(base <= (stack->PciResourceIoLimit + 1));
		stack->PciResourceIoBase = base;
	} else if (res_type == RES_TYPE_NONPREF_MEM) {
		assert(base <= (stack->PciResourceMem32Limit + 1));
		stack->PciResourceMem32Base = base;
	} else {
		assert(base <= (stack->PciResourceMem64Limit + 1));
		stack->PciResourceMem64Base = base;
	}
}

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

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, RES_TYPE res_type,
	struct stack_dev_resource *res_root, struct resource *bridge)
{
	uint64_t orig_base, base;

	orig_base = get_resource_base(stack, res_type);

	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;
	}

	set_resource_base(stack, res_type, 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 && (get_res_type(bridge->flags) != get_res_type(res->flags))))
			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 */
		RES_TYPE res_types[MAX_RES_TYPES] = {
			RES_TYPE_IO,
			RES_TYPE_NONPREF_MEM,
			RES_TYPE_PREF_MEM
		};

		uint8_t no_res_types = MAX_RES_TYPES;

		/* if it is a bridge, only process matching bridge resource type */
		if (bridge) {
			res_types[0] = get_res_type(bridge->flags);
			no_res_types = 1;
		}

		printk(BIOS_DEBUG, "%s:%d no_res_types: %d\n", __func__, __LINE__,
			no_res_types);

		/* Process each resource type */
		for (int rt = 0; rt < no_res_types; ++rt) {
			struct stack_dev_resource *res_root = NULL;
			printk(BIOS_DEBUG, "%s:%d rt: %d\n", __func__, __LINE__, rt);
			for (curdev = bus->children; curdev; curdev = curdev->sibling) {
				struct resource *res;
				printk(BIOS_DEBUG, "%s:%d dev: %s\n",
					__func__, __LINE__, dev_path(curdev));
				if (!curdev->enabled)
					continue;

				for (res = curdev->resource_list; res; res = res->next) {
					printk(BIOS_DEBUG, "%s:%d dev: %s, flags: 0x%lx\n",
						__func__, __LINE__,
						dev_path(curdev), res->flags);
					if (res->size == 0                            ||
					    get_res_type(res->flags) != res_types[rt] ||
					    (res->flags & IORESOURCE_BRIDGE)          ||
					    !need_assignment(res->flags))
						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, res_types[rt], res_root, bridge);
				reclaim_resource_mem(res_root);
			}
		}
	}
}

static uint8_t is_pci64bit_alloc(void)
{
	const IIO_UDS *hob = get_iio_uds();

	return hob->PlatformData.Pci64BitResourceAllocation;
}

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);

	struct iiostack_resource stack_info = {0};
	get_iiostack_info(&stack_info);
	if (!is_pci64bit_alloc()) {
		/*
		 * Split 32 bit address space between prefetchable and
		 * non-prefetchable windows
		 */
		for (int s = 0; s < stack_info.no_of_stacks; ++s) {
			STACK_RES *res = &stack_info.res[s];
			uint64_t length = (res->PciResourceMem32Limit -
				res->PciResourceMem32Base + 1)/2;
			res->PciResourceMem64Limit = res->PciResourceMem32Limit;
			res->PciResourceMem32Limit = (res->PciResourceMem32Base + length - 1);
			res->PciResourceMem64Base = res->PciResourceMem32Limit + 1;
		}
	}

	/* 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;
	}
}

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);
}

/* Attach IIO stack bus numbers with dummy device to PCI DOMAIN 0000 device */
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);
}