| 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/core/qdev.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 | qbus_init(&s->opb[i], sizeof(s->opb[i]), TYPE_OP_BUS, DEVICE(s), NULL); |
| 277 | } |
| 278 | } |
| 279 | |
| 280 | static void fsi_aspeed_apb2opb_realize(DeviceState *dev, Error **errp) |
| 281 | { |
| 282 | SysBusDevice *sbd = SYS_BUS_DEVICE(dev); |
| 283 | AspeedAPB2OPBState *s = ASPEED_APB2OPB(dev); |
| 284 | int i; |
| 285 | |
| 286 | sysbus_init_irq(sbd, &s->irq); |
| 287 | |
| 288 | memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_apb2opb_ops, s, |
| 289 | TYPE_ASPEED_APB2OPB, 0x1000); |
| 290 | sysbus_init_mmio(sbd, &s->iomem); |
| 291 | |
| 292 | for (i = 0; i < ASPEED_FSI_NUM; i++) { |
| 293 | if (!qdev_realize(DEVICE(&s->fsi[i]), BUS(&s->opb[i]), errp)) { |
| 294 | return; |
| 295 | } |
| 296 | |
| 297 | memory_region_add_subregion(&s->opb[i].mr, 0x80000000, |
| 298 | &s->fsi[i].iomem); |
| 299 | |
| 300 | memory_region_add_subregion(&s->opb[i].mr, 0xa0000000, |
| 301 | &s->fsi[i].opb2fsi); |
| 302 | } |
| 303 | } |
| 304 | |
| 305 | static void fsi_aspeed_apb2opb_reset_hold(Object *obj, ResetType type) |
| 306 | { |
| 307 | AspeedAPB2OPBState *s = ASPEED_APB2OPB(obj); |
| 308 | |
| 309 | memcpy(s->regs, aspeed_apb2opb_reset, ASPEED_APB2OPB_NR_REGS); |
| 310 | } |
| 311 | |
| 312 | static void fsi_aspeed_apb2opb_class_init(ObjectClass *klass, const void *data) |
| 313 | { |
| 314 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 315 | ResettableClass *rc = RESETTABLE_CLASS(klass); |
| 316 | |
| 317 | dc->desc = "ASPEED APB2OPB Bridge"; |
| 318 | dc->realize = fsi_aspeed_apb2opb_realize; |
| 319 | rc->phases.hold = fsi_aspeed_apb2opb_reset_hold; |
| 320 | } |
| 321 | |
| 322 | static const TypeInfo aspeed_apb2opb_types[] = { |
| 323 | { |
| 324 | .name = TYPE_ASPEED_APB2OPB, |
| 325 | .parent = TYPE_SYS_BUS_DEVICE, |
| 326 | .instance_init = fsi_aspeed_apb2opb_init, |
| 327 | .instance_size = sizeof(AspeedAPB2OPBState), |
| 328 | .class_init = fsi_aspeed_apb2opb_class_init, |
| 329 | } |
| 330 | }; |
| 331 | |
| 332 | DEFINE_TYPES(aspeed_apb2opb_types) |
| 333 | |
| 334 | static void fsi_opb_init(Object *o) |
| 335 | { |
| 336 | OPBus *opb = OP_BUS(o); |
| 337 | |
| 338 | memory_region_init(&opb->mr, o, TYPE_FSI_OPB, UINT32_MAX); |
| 339 | } |
| 340 | |
| 341 | static void fsi_opb_realize(BusState *bus, Error **errp) |
| 342 | { |
| 343 | OPBus *opb = OP_BUS(bus); |
| 344 | |
| 345 | address_space_init(&opb->as, &opb->mr, TYPE_FSI_OPB); |
| 346 | } |
| 347 | |
| 348 | static void fsi_opb_unrealize(BusState *bus) |
| 349 | { |
| 350 | OPBus *opb = OP_BUS(bus); |
| 351 | |
| 352 | address_space_destroy(&opb->as); |
| 353 | } |
| 354 | |
| 355 | static void fsi_opb_class_init(ObjectClass *klass, const void *data) |
| 356 | { |
| 357 | BusClass *bc = BUS_CLASS(klass); |
| 358 | |
| 359 | bc->realize = fsi_opb_realize; |
| 360 | bc->unrealize = fsi_opb_unrealize; |
| 361 | } |
| 362 | |
| 363 | static const TypeInfo fsi_opb_types[] = { |
| 364 | { |
| 365 | .name = TYPE_OP_BUS, |
| 366 | .parent = TYPE_BUS, |
| 367 | .instance_init = fsi_opb_init, |
| 368 | .instance_size = sizeof(OPBus), |
| 369 | .class_init = fsi_opb_class_init, |
| 370 | } |
| 371 | }; |
| 372 | |
| 373 | DEFINE_TYPES(fsi_opb_types) |