1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 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);
}
|