// 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 #include #include #include #include #include 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); }