|  | // SPDX-License-Identifier: GPL-2.0-only | 
|  | #include <linux/types.h> | 
|  | #include <linux/ioport.h> | 
|  | #include <linux/slab.h> | 
|  | #include <linux/export.h> | 
|  | #include <linux/io.h> | 
|  | #include <linux/mcb.h> | 
|  |  | 
|  | #include "mcb-internal.h" | 
|  |  | 
|  | struct mcb_parse_priv { | 
|  | phys_addr_t mapbase; | 
|  | void __iomem *base; | 
|  | }; | 
|  |  | 
|  | #define for_each_chameleon_cell(dtype, p)	\ | 
|  | for ((dtype) = get_next_dtype((p));	\ | 
|  | (dtype) != CHAMELEON_DTYPE_END;	\ | 
|  | (dtype) = get_next_dtype((p))) | 
|  |  | 
|  | static inline uint32_t get_next_dtype(void __iomem *p) | 
|  | { | 
|  | uint32_t dtype; | 
|  |  | 
|  | dtype = readl(p); | 
|  | return dtype >> 28; | 
|  | } | 
|  |  | 
|  | static int chameleon_parse_bdd(struct mcb_bus *bus, | 
|  | struct chameleon_bar *cb, | 
|  | void __iomem *base) | 
|  | { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int chameleon_parse_gdd(struct mcb_bus *bus, | 
|  | struct chameleon_bar *cb, | 
|  | void __iomem *base, int bar_count) | 
|  | { | 
|  | struct chameleon_gdd __iomem *gdd = | 
|  | (struct chameleon_gdd __iomem *) base; | 
|  | struct mcb_device *mdev; | 
|  | u32 dev_mapbase; | 
|  | u32 offset; | 
|  | u32 size; | 
|  | int ret; | 
|  | __le32 reg1; | 
|  | __le32 reg2; | 
|  |  | 
|  | mdev = mcb_alloc_dev(bus); | 
|  | if (!mdev) | 
|  | return -ENOMEM; | 
|  |  | 
|  | reg1 = readl(&gdd->reg1); | 
|  | reg2 = readl(&gdd->reg2); | 
|  | offset = readl(&gdd->offset); | 
|  | size = readl(&gdd->size); | 
|  |  | 
|  | mdev->id = GDD_DEV(reg1); | 
|  | mdev->rev = GDD_REV(reg1); | 
|  | mdev->var = GDD_VAR(reg1); | 
|  | mdev->bar = GDD_BAR(reg2); | 
|  | mdev->group = GDD_GRP(reg2); | 
|  | mdev->inst = GDD_INS(reg2); | 
|  |  | 
|  | /* | 
|  | * If the BAR is missing, dev_mapbase is zero, or if the | 
|  | * device is IO mapped we just print a warning and go on with the | 
|  | * next device, instead of completely stop the gdd parser | 
|  | */ | 
|  | if (mdev->bar > bar_count - 1) { | 
|  | pr_info("No BAR for 16z%03d\n", mdev->id); | 
|  | ret = 0; | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | dev_mapbase = cb[mdev->bar].addr; | 
|  | if (!dev_mapbase) { | 
|  | pr_info("BAR not assigned for 16z%03d\n", mdev->id); | 
|  | ret = 0; | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | if (dev_mapbase & 0x01) { | 
|  | pr_info("IO mapped Device (16z%03d) not yet supported\n", | 
|  | mdev->id); | 
|  | ret = 0; | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | pr_debug("Found a 16z%03d\n", mdev->id); | 
|  |  | 
|  | mdev->irq.start = GDD_IRQ(reg1); | 
|  | mdev->irq.end = GDD_IRQ(reg1); | 
|  | mdev->irq.flags = IORESOURCE_IRQ; | 
|  |  | 
|  | mdev->mem.start = dev_mapbase + offset; | 
|  |  | 
|  | mdev->mem.end = mdev->mem.start + size - 1; | 
|  | mdev->mem.flags = IORESOURCE_MEM; | 
|  |  | 
|  | mdev->is_added = false; | 
|  |  | 
|  | ret = mcb_device_register(bus, mdev); | 
|  | if (ret < 0) | 
|  | goto err; | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | err: | 
|  | put_device(&mdev->dev); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static void chameleon_parse_bar(void __iomem *base, | 
|  | struct chameleon_bar *cb, int bar_count) | 
|  | { | 
|  | char __iomem *p = base; | 
|  | int i; | 
|  |  | 
|  | /* skip reg1 */ | 
|  | p += sizeof(__le32); | 
|  |  | 
|  | for (i = 0; i < bar_count; i++) { | 
|  | cb[i].addr = readl(p); | 
|  | cb[i].size = readl(p + 4); | 
|  |  | 
|  | p += sizeof(struct chameleon_bar); | 
|  | } | 
|  | } | 
|  |  | 
|  | static int chameleon_get_bar(char __iomem **base, phys_addr_t mapbase, | 
|  | struct chameleon_bar **cb) | 
|  | { | 
|  | struct chameleon_bar *c; | 
|  | int bar_count; | 
|  | __le32 reg; | 
|  | u32 dtype; | 
|  |  | 
|  | /* | 
|  | * For those devices which are not connected | 
|  | * to the PCI Bus (e.g. LPC) there is a bar | 
|  | * descriptor located directly after the | 
|  | * chameleon header. This header is comparable | 
|  | * to a PCI header. | 
|  | */ | 
|  | dtype = get_next_dtype(*base); | 
|  | if (dtype == CHAMELEON_DTYPE_BAR) { | 
|  | reg = readl(*base); | 
|  |  | 
|  | bar_count = BAR_CNT(reg); | 
|  | if (bar_count <= 0 || bar_count > CHAMELEON_BAR_MAX) | 
|  | return -ENODEV; | 
|  |  | 
|  | c = kcalloc(bar_count, sizeof(struct chameleon_bar), | 
|  | GFP_KERNEL); | 
|  | if (!c) | 
|  | return -ENOMEM; | 
|  |  | 
|  | chameleon_parse_bar(*base, c, bar_count); | 
|  | *base += BAR_DESC_SIZE(bar_count); | 
|  | } else { | 
|  | c = kzalloc(sizeof(struct chameleon_bar), GFP_KERNEL); | 
|  | if (!c) | 
|  | return -ENOMEM; | 
|  |  | 
|  | bar_count = 1; | 
|  | c->addr = mapbase; | 
|  | } | 
|  |  | 
|  | *cb = c; | 
|  |  | 
|  | return bar_count; | 
|  | } | 
|  |  | 
|  | int chameleon_parse_cells(struct mcb_bus *bus, phys_addr_t mapbase, | 
|  | void __iomem *base) | 
|  | { | 
|  | struct chameleon_fpga_header *header; | 
|  | struct chameleon_bar *cb; | 
|  | char __iomem *p = base; | 
|  | int num_cells = 0; | 
|  | uint32_t dtype; | 
|  | int bar_count; | 
|  | int ret; | 
|  | u32 hsize; | 
|  |  | 
|  | hsize = sizeof(struct chameleon_fpga_header); | 
|  |  | 
|  | header = kzalloc(hsize, GFP_KERNEL); | 
|  | if (!header) | 
|  | return -ENOMEM; | 
|  |  | 
|  | /* Extract header information */ | 
|  | memcpy_fromio(header, p, hsize); | 
|  | /* We only support chameleon v2 at the moment */ | 
|  | header->magic = le16_to_cpu(header->magic); | 
|  | if (header->magic != CHAMELEONV2_MAGIC) { | 
|  | pr_err("Unsupported chameleon version 0x%x\n", | 
|  | header->magic); | 
|  | ret = -ENODEV; | 
|  | goto free_header; | 
|  | } | 
|  | p += hsize; | 
|  |  | 
|  | bus->revision = header->revision; | 
|  | bus->model = header->model; | 
|  | bus->minor = header->minor; | 
|  | snprintf(bus->name, CHAMELEON_FILENAME_LEN + 1, "%s", | 
|  | header->filename); | 
|  |  | 
|  | bar_count = chameleon_get_bar(&p, mapbase, &cb); | 
|  | if (bar_count < 0) { | 
|  | ret = bar_count; | 
|  | goto free_header; | 
|  | } | 
|  |  | 
|  | for_each_chameleon_cell(dtype, p) { | 
|  | switch (dtype) { | 
|  | case CHAMELEON_DTYPE_GENERAL: | 
|  | ret = chameleon_parse_gdd(bus, cb, p, bar_count); | 
|  | if (ret < 0) | 
|  | goto free_bar; | 
|  | p += sizeof(struct chameleon_gdd); | 
|  | break; | 
|  | case CHAMELEON_DTYPE_BRIDGE: | 
|  | chameleon_parse_bdd(bus, cb, p); | 
|  | p += sizeof(struct chameleon_bdd); | 
|  | break; | 
|  | case CHAMELEON_DTYPE_END: | 
|  | break; | 
|  | default: | 
|  | pr_err("Invalid chameleon descriptor type 0x%x\n", | 
|  | dtype); | 
|  | ret = -EINVAL; | 
|  | goto free_bar; | 
|  | } | 
|  | num_cells++; | 
|  | } | 
|  |  | 
|  | if (num_cells == 0) | 
|  | num_cells = -EINVAL; | 
|  |  | 
|  | kfree(cb); | 
|  | kfree(header); | 
|  | return num_cells; | 
|  |  | 
|  | free_bar: | 
|  | kfree(cb); | 
|  | free_header: | 
|  | kfree(header); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  | EXPORT_SYMBOL_NS_GPL(chameleon_parse_cells, MCB); |