diff options
Diffstat (limited to 'roms/skiboot/hw/ocmb.c')
-rw-r--r-- | roms/skiboot/hw/ocmb.c | 167 |
1 files changed, 167 insertions, 0 deletions
diff --git a/roms/skiboot/hw/ocmb.c b/roms/skiboot/hw/ocmb.c new file mode 100644 index 000000000..bc470d0ab --- /dev/null +++ b/roms/skiboot/hw/ocmb.c @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* + * Open Capi Memory Buffer chip + * + * Copyright 2020 IBM Corp. + */ + + +#define pr_fmt(fmt) "OCMB: " fmt + +#include <skiboot.h> +#include <xscom.h> +#include <device.h> +#include <ocmb.h> +#include <io.h> +#include <inttypes.h> + +struct ocmb_range { + uint64_t start; + uint64_t end; + uint64_t flags; + + /* flags come from hdat */ +#define ACCESS_8B PPC_BIT(0) +#define ACCESS_4B PPC_BIT(1) +#define ACCESS_SIZE_MASK (ACCESS_8B | ACCESS_4B) +}; + +struct ocmb { + struct scom_controller scom; + int range_count; + struct ocmb_range ranges[]; +}; + +static const struct ocmb_range *find_range(const struct ocmb *o, uint64_t offset) +{ + int i; + uint64_t addr = offset & ~(HRMOR_BIT); + + for (i = 0; i < o->range_count; i++) { + uint64_t start = o->ranges[i].start; + uint64_t end = o->ranges[i].end; + + if (addr >= start && addr <= end) + return &o->ranges[i]; + } + + return NULL; +} + +static int64_t ocmb_fake_scom_write(struct scom_controller *f, + uint32_t __unused chip_id, + uint64_t offset, uint64_t val) +{ + const struct ocmb *o = f->private; + const struct ocmb_range *r; + + r = find_range(o, offset); + if (!r) { + prerror("no matching address range!\n"); + return OPAL_XSCOM_ADDR_ERROR; + } + + switch (r->flags & ACCESS_SIZE_MASK) { + case ACCESS_8B: + if (offset & 0x7) + return OPAL_XSCOM_ADDR_ERROR; + out_be64((void *) offset, val); + break; + + case ACCESS_4B: + if (offset & 0x3) + return OPAL_XSCOM_ADDR_ERROR; + out_be32((void *) offset, val); + break; + default: + prerror("bad flags? %llx\n", r->flags); + return OPAL_XSCOM_ADDR_ERROR; + } + + return OPAL_SUCCESS; +} + +static int64_t ocmb_fake_scom_read(struct scom_controller *f, + uint32_t chip_id __unused, + uint64_t offset, uint64_t *val) +{ + const struct ocmb *o = f->private; + const struct ocmb_range *r = NULL; + + r = find_range(o, offset); + if (!r) { + prerror("no matching address range!\n"); + return OPAL_XSCOM_ADDR_ERROR; + } + + + switch (r->flags & ACCESS_SIZE_MASK) { + case ACCESS_8B: + if (offset & 0x7) + return OPAL_XSCOM_ADDR_ERROR; + *val = in_be64((void *) offset); + break; + + case ACCESS_4B: + if (offset & 0x3) + return OPAL_XSCOM_ADDR_ERROR; + *val = in_be32((void *) offset); + break; + default: + prerror("bad flags? %llx\n", r->flags); + return OPAL_XSCOM_ADDR_ERROR; + } + + return OPAL_SUCCESS; +} + +static bool ocmb_probe_one(struct dt_node *ocmb_node) +{ + uint64_t chip_id = dt_prop_get_u32(ocmb_node, "ibm,chip-id"); + const struct dt_property *flags; + int i = 0, num = 0; + struct ocmb *ocmb; + + num = dt_count_addresses(ocmb_node); + + ocmb = zalloc(sizeof(*ocmb) + sizeof(*ocmb->ranges) * num); + if (!ocmb) + return false; + + ocmb->scom.private = ocmb; + ocmb->scom.part_id = chip_id; + ocmb->scom.write = ocmb_fake_scom_write; + ocmb->scom.read = ocmb_fake_scom_read; + ocmb->range_count = num; + + flags = dt_require_property(ocmb_node, "flags", sizeof(u64) * num); + + for (i = 0; i < num; i++) { + uint64_t start, size; + + start = dt_get_address(ocmb_node, i, &size); + + ocmb->ranges[i].start = start; + ocmb->ranges[i].end = start + size - 1; + ocmb->ranges[i].flags = dt_property_get_u64(flags, i); + + prlog(PR_DEBUG, "Added range: %" PRIx64 " - [%llx - %llx]\n", + chip_id, start, start + size - 1); + } + + if (scom_register(&ocmb->scom)) + prerror("Error registering fake scom\n"); + + dt_add_property(ocmb_node, "scom-controller", NULL, 0); + prlog(PR_NOTICE, "Added scom controller for %s\n", ocmb_node->name); + + return true; +} + +void ocmb_init(void) +{ + struct dt_node *dn; + + dt_for_each_compatible(dt_root, dn, "ibm,explorer") + ocmb_probe_one(dn); +} |