| // 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" |
| |
| #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_NS_GPL(chameleon_parse_cells, MCB); |