18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci#include <linux/types.h>
38c2ecf20Sopenharmony_ci#include <linux/ioport.h>
48c2ecf20Sopenharmony_ci#include <linux/slab.h>
58c2ecf20Sopenharmony_ci#include <linux/export.h>
68c2ecf20Sopenharmony_ci#include <linux/io.h>
78c2ecf20Sopenharmony_ci#include <linux/mcb.h>
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include "mcb-internal.h"
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_cistruct mcb_parse_priv {
128c2ecf20Sopenharmony_ci	phys_addr_t mapbase;
138c2ecf20Sopenharmony_ci	void __iomem *base;
148c2ecf20Sopenharmony_ci};
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci#define for_each_chameleon_cell(dtype, p)	\
178c2ecf20Sopenharmony_ci	for ((dtype) = get_next_dtype((p));	\
188c2ecf20Sopenharmony_ci	     (dtype) != CHAMELEON_DTYPE_END;	\
198c2ecf20Sopenharmony_ci	     (dtype) = get_next_dtype((p)))
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_cistatic inline uint32_t get_next_dtype(void __iomem *p)
228c2ecf20Sopenharmony_ci{
238c2ecf20Sopenharmony_ci	uint32_t dtype;
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci	dtype = readl(p);
268c2ecf20Sopenharmony_ci	return dtype >> 28;
278c2ecf20Sopenharmony_ci}
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_cistatic int chameleon_parse_bdd(struct mcb_bus *bus,
308c2ecf20Sopenharmony_ci			struct chameleon_bar *cb,
318c2ecf20Sopenharmony_ci			void __iomem *base)
328c2ecf20Sopenharmony_ci{
338c2ecf20Sopenharmony_ci	return 0;
348c2ecf20Sopenharmony_ci}
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_cistatic int chameleon_parse_gdd(struct mcb_bus *bus,
378c2ecf20Sopenharmony_ci			struct chameleon_bar *cb,
388c2ecf20Sopenharmony_ci			void __iomem *base, int bar_count)
398c2ecf20Sopenharmony_ci{
408c2ecf20Sopenharmony_ci	struct chameleon_gdd __iomem *gdd =
418c2ecf20Sopenharmony_ci		(struct chameleon_gdd __iomem *) base;
428c2ecf20Sopenharmony_ci	struct mcb_device *mdev;
438c2ecf20Sopenharmony_ci	u32 dev_mapbase;
448c2ecf20Sopenharmony_ci	u32 offset;
458c2ecf20Sopenharmony_ci	u32 size;
468c2ecf20Sopenharmony_ci	int ret;
478c2ecf20Sopenharmony_ci	__le32 reg1;
488c2ecf20Sopenharmony_ci	__le32 reg2;
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ci	mdev = mcb_alloc_dev(bus);
518c2ecf20Sopenharmony_ci	if (!mdev)
528c2ecf20Sopenharmony_ci		return -ENOMEM;
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci	reg1 = readl(&gdd->reg1);
558c2ecf20Sopenharmony_ci	reg2 = readl(&gdd->reg2);
568c2ecf20Sopenharmony_ci	offset = readl(&gdd->offset);
578c2ecf20Sopenharmony_ci	size = readl(&gdd->size);
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_ci	mdev->id = GDD_DEV(reg1);
608c2ecf20Sopenharmony_ci	mdev->rev = GDD_REV(reg1);
618c2ecf20Sopenharmony_ci	mdev->var = GDD_VAR(reg1);
628c2ecf20Sopenharmony_ci	mdev->bar = GDD_BAR(reg2);
638c2ecf20Sopenharmony_ci	mdev->group = GDD_GRP(reg2);
648c2ecf20Sopenharmony_ci	mdev->inst = GDD_INS(reg2);
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci	/*
678c2ecf20Sopenharmony_ci	 * If the BAR is missing, dev_mapbase is zero, or if the
688c2ecf20Sopenharmony_ci	 * device is IO mapped we just print a warning and go on with the
698c2ecf20Sopenharmony_ci	 * next device, instead of completely stop the gdd parser
708c2ecf20Sopenharmony_ci	 */
718c2ecf20Sopenharmony_ci	if (mdev->bar > bar_count - 1) {
728c2ecf20Sopenharmony_ci		pr_info("No BAR for 16z%03d\n", mdev->id);
738c2ecf20Sopenharmony_ci		ret = 0;
748c2ecf20Sopenharmony_ci		goto err;
758c2ecf20Sopenharmony_ci	}
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci	dev_mapbase = cb[mdev->bar].addr;
788c2ecf20Sopenharmony_ci	if (!dev_mapbase) {
798c2ecf20Sopenharmony_ci		pr_info("BAR not assigned for 16z%03d\n", mdev->id);
808c2ecf20Sopenharmony_ci		ret = 0;
818c2ecf20Sopenharmony_ci		goto err;
828c2ecf20Sopenharmony_ci	}
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	if (dev_mapbase & 0x01) {
858c2ecf20Sopenharmony_ci		pr_info("IO mapped Device (16z%03d) not yet supported\n",
868c2ecf20Sopenharmony_ci			mdev->id);
878c2ecf20Sopenharmony_ci		ret = 0;
888c2ecf20Sopenharmony_ci		goto err;
898c2ecf20Sopenharmony_ci	}
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	pr_debug("Found a 16z%03d\n", mdev->id);
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_ci	mdev->irq.start = GDD_IRQ(reg1);
948c2ecf20Sopenharmony_ci	mdev->irq.end = GDD_IRQ(reg1);
958c2ecf20Sopenharmony_ci	mdev->irq.flags = IORESOURCE_IRQ;
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci	mdev->mem.start = dev_mapbase + offset;
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	mdev->mem.end = mdev->mem.start + size - 1;
1008c2ecf20Sopenharmony_ci	mdev->mem.flags = IORESOURCE_MEM;
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	ret = mcb_device_register(bus, mdev);
1038c2ecf20Sopenharmony_ci	if (ret < 0)
1048c2ecf20Sopenharmony_ci		goto err;
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci	return 0;
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_cierr:
1098c2ecf20Sopenharmony_ci	mcb_free_dev(mdev);
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	return ret;
1128c2ecf20Sopenharmony_ci}
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_cistatic void chameleon_parse_bar(void __iomem *base,
1158c2ecf20Sopenharmony_ci				struct chameleon_bar *cb, int bar_count)
1168c2ecf20Sopenharmony_ci{
1178c2ecf20Sopenharmony_ci	char __iomem *p = base;
1188c2ecf20Sopenharmony_ci	int i;
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci	/* skip reg1 */
1218c2ecf20Sopenharmony_ci	p += sizeof(__le32);
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	for (i = 0; i < bar_count; i++) {
1248c2ecf20Sopenharmony_ci		cb[i].addr = readl(p);
1258c2ecf20Sopenharmony_ci		cb[i].size = readl(p + 4);
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci		p += sizeof(struct chameleon_bar);
1288c2ecf20Sopenharmony_ci	}
1298c2ecf20Sopenharmony_ci}
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_cistatic int chameleon_get_bar(void __iomem **base, phys_addr_t mapbase,
1328c2ecf20Sopenharmony_ci			     struct chameleon_bar **cb)
1338c2ecf20Sopenharmony_ci{
1348c2ecf20Sopenharmony_ci	struct chameleon_bar *c;
1358c2ecf20Sopenharmony_ci	int bar_count;
1368c2ecf20Sopenharmony_ci	__le32 reg;
1378c2ecf20Sopenharmony_ci	u32 dtype;
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci	/*
1408c2ecf20Sopenharmony_ci	 * For those devices which are not connected
1418c2ecf20Sopenharmony_ci	 * to the PCI Bus (e.g. LPC) there is a bar
1428c2ecf20Sopenharmony_ci	 * descriptor located directly after the
1438c2ecf20Sopenharmony_ci	 * chameleon header. This header is comparable
1448c2ecf20Sopenharmony_ci	 * to a PCI header.
1458c2ecf20Sopenharmony_ci	 */
1468c2ecf20Sopenharmony_ci	dtype = get_next_dtype(*base);
1478c2ecf20Sopenharmony_ci	if (dtype == CHAMELEON_DTYPE_BAR) {
1488c2ecf20Sopenharmony_ci		reg = readl(*base);
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci		bar_count = BAR_CNT(reg);
1518c2ecf20Sopenharmony_ci		if (bar_count <= 0 || bar_count > CHAMELEON_BAR_MAX)
1528c2ecf20Sopenharmony_ci			return -ENODEV;
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci		c = kcalloc(bar_count, sizeof(struct chameleon_bar),
1558c2ecf20Sopenharmony_ci			    GFP_KERNEL);
1568c2ecf20Sopenharmony_ci		if (!c)
1578c2ecf20Sopenharmony_ci			return -ENOMEM;
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_ci		chameleon_parse_bar(*base, c, bar_count);
1608c2ecf20Sopenharmony_ci		*base += BAR_DESC_SIZE(bar_count);
1618c2ecf20Sopenharmony_ci	} else {
1628c2ecf20Sopenharmony_ci		c = kzalloc(sizeof(struct chameleon_bar), GFP_KERNEL);
1638c2ecf20Sopenharmony_ci		if (!c)
1648c2ecf20Sopenharmony_ci			return -ENOMEM;
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_ci		bar_count = 1;
1678c2ecf20Sopenharmony_ci		c->addr = mapbase;
1688c2ecf20Sopenharmony_ci	}
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci	*cb = c;
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci	return bar_count;
1738c2ecf20Sopenharmony_ci}
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ciint chameleon_parse_cells(struct mcb_bus *bus, phys_addr_t mapbase,
1768c2ecf20Sopenharmony_ci			void __iomem *base)
1778c2ecf20Sopenharmony_ci{
1788c2ecf20Sopenharmony_ci	struct chameleon_fpga_header *header;
1798c2ecf20Sopenharmony_ci	struct chameleon_bar *cb;
1808c2ecf20Sopenharmony_ci	void __iomem *p = base;
1818c2ecf20Sopenharmony_ci	int num_cells = 0;
1828c2ecf20Sopenharmony_ci	uint32_t dtype;
1838c2ecf20Sopenharmony_ci	int bar_count;
1848c2ecf20Sopenharmony_ci	int ret;
1858c2ecf20Sopenharmony_ci	u32 hsize;
1868c2ecf20Sopenharmony_ci	u32 table_size;
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci	hsize = sizeof(struct chameleon_fpga_header);
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_ci	header = kzalloc(hsize, GFP_KERNEL);
1918c2ecf20Sopenharmony_ci	if (!header)
1928c2ecf20Sopenharmony_ci		return -ENOMEM;
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci	/* Extract header information */
1958c2ecf20Sopenharmony_ci	memcpy_fromio(header, p, hsize);
1968c2ecf20Sopenharmony_ci	/* We only support chameleon v2 at the moment */
1978c2ecf20Sopenharmony_ci	header->magic = le16_to_cpu(header->magic);
1988c2ecf20Sopenharmony_ci	if (header->magic != CHAMELEONV2_MAGIC) {
1998c2ecf20Sopenharmony_ci		pr_err("Unsupported chameleon version 0x%x\n",
2008c2ecf20Sopenharmony_ci				header->magic);
2018c2ecf20Sopenharmony_ci		ret = -ENODEV;
2028c2ecf20Sopenharmony_ci		goto free_header;
2038c2ecf20Sopenharmony_ci	}
2048c2ecf20Sopenharmony_ci	p += hsize;
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci	bus->revision = header->revision;
2078c2ecf20Sopenharmony_ci	bus->model = header->model;
2088c2ecf20Sopenharmony_ci	bus->minor = header->minor;
2098c2ecf20Sopenharmony_ci	snprintf(bus->name, CHAMELEON_FILENAME_LEN + 1, "%s",
2108c2ecf20Sopenharmony_ci		 header->filename);
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci	bar_count = chameleon_get_bar(&p, mapbase, &cb);
2138c2ecf20Sopenharmony_ci	if (bar_count < 0) {
2148c2ecf20Sopenharmony_ci		ret = bar_count;
2158c2ecf20Sopenharmony_ci		goto free_header;
2168c2ecf20Sopenharmony_ci	}
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_ci	for_each_chameleon_cell(dtype, p) {
2198c2ecf20Sopenharmony_ci		switch (dtype) {
2208c2ecf20Sopenharmony_ci		case CHAMELEON_DTYPE_GENERAL:
2218c2ecf20Sopenharmony_ci			ret = chameleon_parse_gdd(bus, cb, p, bar_count);
2228c2ecf20Sopenharmony_ci			if (ret < 0)
2238c2ecf20Sopenharmony_ci				goto free_bar;
2248c2ecf20Sopenharmony_ci			p += sizeof(struct chameleon_gdd);
2258c2ecf20Sopenharmony_ci			break;
2268c2ecf20Sopenharmony_ci		case CHAMELEON_DTYPE_BRIDGE:
2278c2ecf20Sopenharmony_ci			chameleon_parse_bdd(bus, cb, p);
2288c2ecf20Sopenharmony_ci			p += sizeof(struct chameleon_bdd);
2298c2ecf20Sopenharmony_ci			break;
2308c2ecf20Sopenharmony_ci		case CHAMELEON_DTYPE_END:
2318c2ecf20Sopenharmony_ci			break;
2328c2ecf20Sopenharmony_ci		default:
2338c2ecf20Sopenharmony_ci			pr_err("Invalid chameleon descriptor type 0x%x\n",
2348c2ecf20Sopenharmony_ci				dtype);
2358c2ecf20Sopenharmony_ci			ret = -EINVAL;
2368c2ecf20Sopenharmony_ci			goto free_bar;
2378c2ecf20Sopenharmony_ci		}
2388c2ecf20Sopenharmony_ci		num_cells++;
2398c2ecf20Sopenharmony_ci	}
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_ci	if (num_cells == 0) {
2428c2ecf20Sopenharmony_ci		ret = -EINVAL;
2438c2ecf20Sopenharmony_ci		goto free_bar;
2448c2ecf20Sopenharmony_ci	}
2458c2ecf20Sopenharmony_ci
2468c2ecf20Sopenharmony_ci	table_size = p - base;
2478c2ecf20Sopenharmony_ci	pr_debug("%d cell(s) found. Chameleon table size: 0x%04x bytes\n", num_cells, table_size);
2488c2ecf20Sopenharmony_ci	kfree(cb);
2498c2ecf20Sopenharmony_ci	kfree(header);
2508c2ecf20Sopenharmony_ci	return table_size;
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_cifree_bar:
2538c2ecf20Sopenharmony_ci	kfree(cb);
2548c2ecf20Sopenharmony_cifree_header:
2558c2ecf20Sopenharmony_ci	kfree(header);
2568c2ecf20Sopenharmony_ci
2578c2ecf20Sopenharmony_ci	return ret;
2588c2ecf20Sopenharmony_ci}
2598c2ecf20Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(chameleon_parse_cells, MCB);
260