blob: bc470d0ab686c1320072b0046462498b51d6f4e5 [file] [log] [blame]
// 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);
}