| #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: | |
| 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(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_GPL(chameleon_parse_cells); |