167 lines
3.5 KiB
C
167 lines
3.5 KiB
C
// 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);
|
|
}
|