// SPDX-License-Identifier: GPL-2.0-only #include #include #include #include #include #include #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; ret = mcb_device_register(bus, mdev); if (ret < 0) goto err; return 0; err: mcb_free_dev(mdev); 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(void __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; void __iomem *p = base; int num_cells = 0; uint32_t dtype; int bar_count; int ret; u32 hsize; u32 table_size; 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) { ret = -EINVAL; goto free_bar; } table_size = p - base; pr_debug("%d cell(s) found. Chameleon table size: 0x%04x bytes\n", num_cells, table_size); kfree(cb); kfree(header); return table_size; free_bar: kfree(cb); free_header: kfree(header); return ret; } EXPORT_SYMBOL_GPL(chameleon_parse_cells);