forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
256 lines
5.2 KiB
256 lines
5.2 KiB
// 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: |
|
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_NS_GPL(chameleon_parse_cells, MCB);
|
|
|