]>
Commit | Line | Data |
---|---|---|
eb04c35d NP |
1 | /* |
2 | * SPDX-License-Identifier: GPL-2.0-or-later | |
3 | * Copyright (C) 2024 IBM Corp. | |
4 | * | |
5 | * ASPEED APB-OPB FSI interface | |
6 | * IBM On-chip Peripheral Bus | |
7 | */ | |
8 | ||
9 | #include "qemu/osdep.h" | |
10 | #include "qemu/log.h" | |
11 | #include "qom/object.h" | |
12 | #include "qapi/error.h" | |
13 | #include "trace.h" | |
14 | ||
15 | #include "hw/fsi/aspeed_apb2opb.h" | |
16 | #include "hw/qdev-core.h" | |
17 | ||
18 | #define TO_REG(x) (x >> 2) | |
19 | ||
20 | #define APB2OPB_VERSION TO_REG(0x00) | |
21 | #define APB2OPB_TRIGGER TO_REG(0x04) | |
22 | ||
23 | #define APB2OPB_CONTROL TO_REG(0x08) | |
24 | #define APB2OPB_CONTROL_OFF BE_GENMASK(31, 13) | |
25 | ||
26 | #define APB2OPB_OPB2FSI TO_REG(0x0c) | |
27 | #define APB2OPB_OPB2FSI_OFF BE_GENMASK(31, 22) | |
28 | ||
29 | #define APB2OPB_OPB0_SEL TO_REG(0x10) | |
30 | #define APB2OPB_OPB1_SEL TO_REG(0x28) | |
31 | #define APB2OPB_OPB_SEL_EN BIT(0) | |
32 | ||
33 | #define APB2OPB_OPB0_MODE TO_REG(0x14) | |
34 | #define APB2OPB_OPB1_MODE TO_REG(0x2c) | |
35 | #define APB2OPB_OPB_MODE_RD BIT(0) | |
36 | ||
37 | #define APB2OPB_OPB0_XFER TO_REG(0x18) | |
38 | #define APB2OPB_OPB1_XFER TO_REG(0x30) | |
39 | #define APB2OPB_OPB_XFER_FULL BIT(1) | |
40 | #define APB2OPB_OPB_XFER_HALF BIT(0) | |
41 | ||
42 | #define APB2OPB_OPB0_ADDR TO_REG(0x1c) | |
43 | #define APB2OPB_OPB0_WRITE_DATA TO_REG(0x20) | |
44 | ||
45 | #define APB2OPB_OPB1_ADDR TO_REG(0x34) | |
46 | #define APB2OPB_OPB1_WRITE_DATA TO_REG(0x38) | |
47 | ||
48 | #define APB2OPB_IRQ_STS TO_REG(0x48) | |
49 | #define APB2OPB_IRQ_STS_OPB1_TX_ACK BIT(17) | |
50 | #define APB2OPB_IRQ_STS_OPB0_TX_ACK BIT(16) | |
51 | ||
52 | #define APB2OPB_OPB0_WRITE_WORD_ENDIAN TO_REG(0x4c) | |
53 | #define APB2OPB_OPB0_WRITE_WORD_ENDIAN_BE 0x0011101b | |
54 | #define APB2OPB_OPB0_WRITE_BYTE_ENDIAN TO_REG(0x50) | |
55 | #define APB2OPB_OPB0_WRITE_BYTE_ENDIAN_BE 0x0c330f3f | |
56 | #define APB2OPB_OPB1_WRITE_WORD_ENDIAN TO_REG(0x54) | |
57 | #define APB2OPB_OPB1_WRITE_BYTE_ENDIAN TO_REG(0x58) | |
58 | #define APB2OPB_OPB0_READ_BYTE_ENDIAN TO_REG(0x5c) | |
59 | #define APB2OPB_OPB1_READ_BYTE_ENDIAN TO_REG(0x60) | |
60 | #define APB2OPB_OPB0_READ_WORD_ENDIAN_BE 0x00030b1b | |
61 | ||
62 | #define APB2OPB_OPB0_READ_DATA TO_REG(0x84) | |
63 | #define APB2OPB_OPB1_READ_DATA TO_REG(0x90) | |
64 | ||
65 | /* | |
66 | * The following magic values came from AST2600 data sheet | |
67 | * The register values are defined under section "FSI controller" | |
68 | * as initial values. | |
69 | */ | |
70 | static const uint32_t aspeed_apb2opb_reset[ASPEED_APB2OPB_NR_REGS] = { | |
71 | [APB2OPB_VERSION] = 0x000000a1, | |
72 | [APB2OPB_OPB0_WRITE_WORD_ENDIAN] = 0x0044eee4, | |
73 | [APB2OPB_OPB0_WRITE_BYTE_ENDIAN] = 0x0055aaff, | |
74 | [APB2OPB_OPB1_WRITE_WORD_ENDIAN] = 0x00117717, | |
75 | [APB2OPB_OPB1_WRITE_BYTE_ENDIAN] = 0xffaa5500, | |
76 | [APB2OPB_OPB0_READ_BYTE_ENDIAN] = 0x0044eee4, | |
77 | [APB2OPB_OPB1_READ_BYTE_ENDIAN] = 0x00117717 | |
78 | }; | |
79 | ||
80 | static void fsi_opb_fsi_master_address(FSIMasterState *fsi, hwaddr addr) | |
81 | { | |
82 | memory_region_transaction_begin(); | |
83 | memory_region_set_address(&fsi->iomem, addr); | |
84 | memory_region_transaction_commit(); | |
85 | } | |
86 | ||
87 | static void fsi_opb_opb2fsi_address(FSIMasterState *fsi, hwaddr addr) | |
88 | { | |
89 | memory_region_transaction_begin(); | |
90 | memory_region_set_address(&fsi->opb2fsi, addr); | |
91 | memory_region_transaction_commit(); | |
92 | } | |
93 | ||
94 | static uint64_t fsi_aspeed_apb2opb_read(void *opaque, hwaddr addr, | |
95 | unsigned size) | |
96 | { | |
97 | AspeedAPB2OPBState *s = ASPEED_APB2OPB(opaque); | |
98 | unsigned int reg = TO_REG(addr); | |
99 | ||
100 | trace_fsi_aspeed_apb2opb_read(addr, size); | |
101 | ||
102 | if (reg >= ASPEED_APB2OPB_NR_REGS) { | |
103 | qemu_log_mask(LOG_GUEST_ERROR, | |
104 | "%s: Out of bounds read: 0x%"HWADDR_PRIx" for %u\n", | |
105 | __func__, addr, size); | |
106 | return 0; | |
107 | } | |
108 | ||
109 | return s->regs[reg]; | |
110 | } | |
111 | ||
112 | static MemTxResult fsi_aspeed_apb2opb_rw(AddressSpace *as, hwaddr addr, | |
113 | MemTxAttrs attrs, uint32_t *data, | |
114 | uint32_t size, bool is_write) | |
115 | { | |
116 | MemTxResult res; | |
117 | ||
118 | if (is_write) { | |
119 | switch (size) { | |
120 | case 4: | |
121 | address_space_stl_le(as, addr, *data, attrs, &res); | |
122 | break; | |
123 | case 2: | |
124 | address_space_stw_le(as, addr, *data, attrs, &res); | |
125 | break; | |
126 | case 1: | |
127 | address_space_stb(as, addr, *data, attrs, &res); | |
128 | break; | |
129 | default: | |
130 | g_assert_not_reached(); | |
131 | } | |
132 | } else { | |
133 | switch (size) { | |
134 | case 4: | |
135 | *data = address_space_ldl_le(as, addr, attrs, &res); | |
136 | break; | |
137 | case 2: | |
138 | *data = address_space_lduw_le(as, addr, attrs, &res); | |
139 | break; | |
140 | case 1: | |
141 | *data = address_space_ldub(as, addr, attrs, &res); | |
142 | break; | |
143 | default: | |
144 | g_assert_not_reached(); | |
145 | } | |
146 | } | |
147 | return res; | |
148 | } | |
149 | ||
150 | static void fsi_aspeed_apb2opb_write(void *opaque, hwaddr addr, uint64_t data, | |
151 | unsigned size) | |
152 | { | |
153 | AspeedAPB2OPBState *s = ASPEED_APB2OPB(opaque); | |
154 | unsigned int reg = TO_REG(addr); | |
155 | ||
156 | trace_fsi_aspeed_apb2opb_write(addr, size, data); | |
157 | ||
158 | if (reg >= ASPEED_APB2OPB_NR_REGS) { | |
159 | qemu_log_mask(LOG_GUEST_ERROR, | |
160 | "%s: Out of bounds write: %"HWADDR_PRIx" for %u\n", | |
161 | __func__, addr, size); | |
162 | return; | |
163 | } | |
164 | ||
165 | switch (reg) { | |
166 | case APB2OPB_CONTROL: | |
167 | fsi_opb_fsi_master_address(&s->fsi[0], | |
168 | data & APB2OPB_CONTROL_OFF); | |
169 | break; | |
170 | case APB2OPB_OPB2FSI: | |
171 | fsi_opb_opb2fsi_address(&s->fsi[0], | |
172 | data & APB2OPB_OPB2FSI_OFF); | |
173 | break; | |
174 | case APB2OPB_OPB0_WRITE_WORD_ENDIAN: | |
175 | if (data != APB2OPB_OPB0_WRITE_WORD_ENDIAN_BE) { | |
176 | qemu_log_mask(LOG_GUEST_ERROR, | |
177 | "%s: Bridge needs to be driven as BE (0x%x)\n", | |
178 | __func__, APB2OPB_OPB0_WRITE_WORD_ENDIAN_BE); | |
179 | } | |
180 | break; | |
181 | case APB2OPB_OPB0_WRITE_BYTE_ENDIAN: | |
182 | if (data != APB2OPB_OPB0_WRITE_BYTE_ENDIAN_BE) { | |
183 | qemu_log_mask(LOG_GUEST_ERROR, | |
184 | "%s: Bridge needs to be driven as BE (0x%x)\n", | |
185 | __func__, APB2OPB_OPB0_WRITE_BYTE_ENDIAN_BE); | |
186 | } | |
187 | break; | |
188 | case APB2OPB_OPB0_READ_BYTE_ENDIAN: | |
189 | if (data != APB2OPB_OPB0_READ_WORD_ENDIAN_BE) { | |
190 | qemu_log_mask(LOG_GUEST_ERROR, | |
191 | "%s: Bridge needs to be driven as BE (0x%x)\n", | |
192 | __func__, APB2OPB_OPB0_READ_WORD_ENDIAN_BE); | |
193 | } | |
194 | break; | |
195 | case APB2OPB_TRIGGER: | |
196 | { | |
197 | uint32_t opb, op_mode, op_size, op_addr, op_data; | |
198 | MemTxResult result; | |
199 | bool is_write; | |
200 | int index; | |
201 | AddressSpace *as; | |
202 | ||
203 | assert((s->regs[APB2OPB_OPB0_SEL] & APB2OPB_OPB_SEL_EN) ^ | |
204 | (s->regs[APB2OPB_OPB1_SEL] & APB2OPB_OPB_SEL_EN)); | |
205 | ||
206 | if (s->regs[APB2OPB_OPB0_SEL] & APB2OPB_OPB_SEL_EN) { | |
207 | opb = 0; | |
208 | op_mode = s->regs[APB2OPB_OPB0_MODE]; | |
209 | op_size = s->regs[APB2OPB_OPB0_XFER]; | |
210 | op_addr = s->regs[APB2OPB_OPB0_ADDR]; | |
211 | op_data = s->regs[APB2OPB_OPB0_WRITE_DATA]; | |
212 | } else if (s->regs[APB2OPB_OPB1_SEL] & APB2OPB_OPB_SEL_EN) { | |
213 | opb = 1; | |
214 | op_mode = s->regs[APB2OPB_OPB1_MODE]; | |
215 | op_size = s->regs[APB2OPB_OPB1_XFER]; | |
216 | op_addr = s->regs[APB2OPB_OPB1_ADDR]; | |
217 | op_data = s->regs[APB2OPB_OPB1_WRITE_DATA]; | |
218 | } else { | |
219 | qemu_log_mask(LOG_GUEST_ERROR, | |
220 | "%s: Invalid operation: 0x%"HWADDR_PRIx" for %u\n", | |
221 | __func__, addr, size); | |
222 | return; | |
223 | } | |
224 | ||
225 | if (op_size & ~(APB2OPB_OPB_XFER_HALF | APB2OPB_OPB_XFER_FULL)) { | |
226 | qemu_log_mask(LOG_GUEST_ERROR, | |
227 | "OPB transaction failed: Unrecognized access width: %d\n", | |
228 | op_size); | |
229 | return; | |
230 | } | |
231 | ||
232 | op_size += 1; | |
233 | is_write = !(op_mode & APB2OPB_OPB_MODE_RD); | |
234 | index = opb ? APB2OPB_OPB1_READ_DATA : APB2OPB_OPB0_READ_DATA; | |
235 | as = &s->opb[opb].as; | |
236 | ||
237 | result = fsi_aspeed_apb2opb_rw(as, op_addr, MEMTXATTRS_UNSPECIFIED, | |
238 | &op_data, op_size, is_write); | |
239 | if (result != MEMTX_OK) { | |
240 | qemu_log_mask(LOG_GUEST_ERROR, "%s: OPB %s failed @%08x\n", | |
241 | __func__, is_write ? "write" : "read", op_addr); | |
242 | return; | |
243 | } | |
244 | ||
245 | if (!is_write) { | |
246 | s->regs[index] = op_data; | |
247 | } | |
248 | ||
249 | s->regs[APB2OPB_IRQ_STS] |= opb ? APB2OPB_IRQ_STS_OPB1_TX_ACK | |
250 | : APB2OPB_IRQ_STS_OPB0_TX_ACK; | |
251 | break; | |
252 | } | |
253 | } | |
254 | ||
255 | s->regs[reg] = data; | |
256 | } | |
257 | ||
258 | static const struct MemoryRegionOps aspeed_apb2opb_ops = { | |
259 | .read = fsi_aspeed_apb2opb_read, | |
260 | .write = fsi_aspeed_apb2opb_write, | |
261 | .valid.max_access_size = 4, | |
262 | .valid.min_access_size = 4, | |
263 | .impl.max_access_size = 4, | |
264 | .impl.min_access_size = 4, | |
265 | .endianness = DEVICE_LITTLE_ENDIAN, | |
266 | }; | |
267 | ||
268 | static void fsi_aspeed_apb2opb_init(Object *o) | |
269 | { | |
270 | AspeedAPB2OPBState *s = ASPEED_APB2OPB(o); | |
271 | int i; | |
272 | ||
273 | for (i = 0; i < ASPEED_FSI_NUM; i++) { | |
274 | object_initialize_child(o, "fsi-master[*]", &s->fsi[i], | |
275 | TYPE_FSI_MASTER); | |
276 | } | |
277 | } | |
278 | ||
279 | static void fsi_aspeed_apb2opb_realize(DeviceState *dev, Error **errp) | |
280 | { | |
281 | SysBusDevice *sbd = SYS_BUS_DEVICE(dev); | |
282 | AspeedAPB2OPBState *s = ASPEED_APB2OPB(dev); | |
283 | int i; | |
284 | ||
285 | /* | |
286 | * TODO: The OPBus model initializes the OPB address space in | |
287 | * the .instance_init handler and this is problematic for test | |
288 | * device-introspect-test. To avoid a memory corruption and a QEMU | |
289 | * crash, qbus_init() should be called from realize(). Something to | |
290 | * improve. Possibly, OPBus could also be removed. | |
291 | */ | |
292 | for (i = 0; i < ASPEED_FSI_NUM; i++) { | |
293 | qbus_init(&s->opb[i], sizeof(s->opb[i]), TYPE_OP_BUS, DEVICE(s), | |
294 | NULL); | |
295 | } | |
296 | ||
297 | sysbus_init_irq(sbd, &s->irq); | |
298 | ||
299 | memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_apb2opb_ops, s, | |
300 | TYPE_ASPEED_APB2OPB, 0x1000); | |
301 | sysbus_init_mmio(sbd, &s->iomem); | |
302 | ||
303 | for (i = 0; i < ASPEED_FSI_NUM; i++) { | |
304 | if (!qdev_realize(DEVICE(&s->fsi[i]), BUS(&s->opb[i]), errp)) { | |
305 | return; | |
306 | } | |
307 | ||
308 | memory_region_add_subregion(&s->opb[i].mr, 0x80000000, | |
309 | &s->fsi[i].iomem); | |
310 | ||
311 | memory_region_add_subregion(&s->opb[i].mr, 0xa0000000, | |
312 | &s->fsi[i].opb2fsi); | |
313 | } | |
314 | } | |
315 | ||
316 | static void fsi_aspeed_apb2opb_reset(DeviceState *dev) | |
317 | { | |
318 | AspeedAPB2OPBState *s = ASPEED_APB2OPB(dev); | |
319 | ||
320 | memcpy(s->regs, aspeed_apb2opb_reset, ASPEED_APB2OPB_NR_REGS); | |
321 | } | |
322 | ||
323 | static void fsi_aspeed_apb2opb_class_init(ObjectClass *klass, void *data) | |
324 | { | |
325 | DeviceClass *dc = DEVICE_CLASS(klass); | |
326 | ||
327 | dc->desc = "ASPEED APB2OPB Bridge"; | |
328 | dc->realize = fsi_aspeed_apb2opb_realize; | |
329 | dc->reset = fsi_aspeed_apb2opb_reset; | |
330 | } | |
331 | ||
332 | static const TypeInfo aspeed_apb2opb_info = { | |
333 | .name = TYPE_ASPEED_APB2OPB, | |
334 | .parent = TYPE_SYS_BUS_DEVICE, | |
335 | .instance_init = fsi_aspeed_apb2opb_init, | |
336 | .instance_size = sizeof(AspeedAPB2OPBState), | |
337 | .class_init = fsi_aspeed_apb2opb_class_init, | |
338 | }; | |
339 | ||
340 | static void aspeed_apb2opb_register_types(void) | |
341 | { | |
342 | type_register_static(&aspeed_apb2opb_info); | |
343 | } | |
344 | ||
345 | type_init(aspeed_apb2opb_register_types); | |
346 | ||
347 | static void fsi_opb_init(Object *o) | |
348 | { | |
349 | OPBus *opb = OP_BUS(o); | |
350 | ||
351 | memory_region_init(&opb->mr, 0, TYPE_FSI_OPB, UINT32_MAX); | |
352 | address_space_init(&opb->as, &opb->mr, TYPE_FSI_OPB); | |
353 | } | |
354 | ||
355 | static const TypeInfo opb_info = { | |
356 | .name = TYPE_OP_BUS, | |
357 | .parent = TYPE_BUS, | |
358 | .instance_init = fsi_opb_init, | |
359 | .instance_size = sizeof(OPBus), | |
360 | }; | |
361 | ||
362 | static void fsi_opb_register_types(void) | |
363 | { | |
364 | type_register_static(&opb_info); | |
365 | } | |
366 | ||
367 | type_init(fsi_opb_register_types); |