aboutsummaryrefslogtreecommitdiffstats
path: root/roms/skiboot/hw/ocmb.c
diff options
context:
space:
mode:
Diffstat (limited to 'roms/skiboot/hw/ocmb.c')
-rw-r--r--roms/skiboot/hw/ocmb.c167
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);
+}