aboutsummaryrefslogtreecommitdiff
path: root/hw/ocmb.c
blob: bc470d0ab686c1320072b0046462498b51d6f4e5 (plain)
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);
}