368 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
		
		
			
		
	
	
			368 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
|   | /*
 | ||
|  |  * SPDX-License-Identifier: GPL-2.0-or-later | ||
|  |  * Copyright (C) 2024 IBM Corp. | ||
|  |  * | ||
|  |  * ASPEED APB-OPB FSI interface | ||
|  |  * IBM On-chip Peripheral Bus | ||
|  |  */ | ||
|  | 
 | ||
|  | #include "qemu/osdep.h"
 | ||
|  | #include "qemu/log.h"
 | ||
|  | #include "qom/object.h"
 | ||
|  | #include "qapi/error.h"
 | ||
|  | #include "trace.h"
 | ||
|  | 
 | ||
|  | #include "hw/fsi/aspeed_apb2opb.h"
 | ||
|  | #include "hw/qdev-core.h"
 | ||
|  | 
 | ||
|  | #define TO_REG(x) (x >> 2)
 | ||
|  | 
 | ||
|  | #define APB2OPB_VERSION                    TO_REG(0x00)
 | ||
|  | #define APB2OPB_TRIGGER                    TO_REG(0x04)
 | ||
|  | 
 | ||
|  | #define APB2OPB_CONTROL                    TO_REG(0x08)
 | ||
|  | #define   APB2OPB_CONTROL_OFF              BE_GENMASK(31, 13)
 | ||
|  | 
 | ||
|  | #define APB2OPB_OPB2FSI                    TO_REG(0x0c)
 | ||
|  | #define   APB2OPB_OPB2FSI_OFF              BE_GENMASK(31, 22)
 | ||
|  | 
 | ||
|  | #define APB2OPB_OPB0_SEL                   TO_REG(0x10)
 | ||
|  | #define APB2OPB_OPB1_SEL                   TO_REG(0x28)
 | ||
|  | #define   APB2OPB_OPB_SEL_EN               BIT(0)
 | ||
|  | 
 | ||
|  | #define APB2OPB_OPB0_MODE                  TO_REG(0x14)
 | ||
|  | #define APB2OPB_OPB1_MODE                  TO_REG(0x2c)
 | ||
|  | #define   APB2OPB_OPB_MODE_RD              BIT(0)
 | ||
|  | 
 | ||
|  | #define APB2OPB_OPB0_XFER                  TO_REG(0x18)
 | ||
|  | #define APB2OPB_OPB1_XFER                  TO_REG(0x30)
 | ||
|  | #define   APB2OPB_OPB_XFER_FULL            BIT(1)
 | ||
|  | #define   APB2OPB_OPB_XFER_HALF            BIT(0)
 | ||
|  | 
 | ||
|  | #define APB2OPB_OPB0_ADDR                  TO_REG(0x1c)
 | ||
|  | #define APB2OPB_OPB0_WRITE_DATA            TO_REG(0x20)
 | ||
|  | 
 | ||
|  | #define APB2OPB_OPB1_ADDR                  TO_REG(0x34)
 | ||
|  | #define APB2OPB_OPB1_WRITE_DATA                  TO_REG(0x38)
 | ||
|  | 
 | ||
|  | #define APB2OPB_IRQ_STS                    TO_REG(0x48)
 | ||
|  | #define   APB2OPB_IRQ_STS_OPB1_TX_ACK      BIT(17)
 | ||
|  | #define   APB2OPB_IRQ_STS_OPB0_TX_ACK      BIT(16)
 | ||
|  | 
 | ||
|  | #define APB2OPB_OPB0_WRITE_WORD_ENDIAN     TO_REG(0x4c)
 | ||
|  | #define   APB2OPB_OPB0_WRITE_WORD_ENDIAN_BE 0x0011101b
 | ||
|  | #define APB2OPB_OPB0_WRITE_BYTE_ENDIAN     TO_REG(0x50)
 | ||
|  | #define   APB2OPB_OPB0_WRITE_BYTE_ENDIAN_BE 0x0c330f3f
 | ||
|  | #define APB2OPB_OPB1_WRITE_WORD_ENDIAN     TO_REG(0x54)
 | ||
|  | #define APB2OPB_OPB1_WRITE_BYTE_ENDIAN     TO_REG(0x58)
 | ||
|  | #define APB2OPB_OPB0_READ_BYTE_ENDIAN      TO_REG(0x5c)
 | ||
|  | #define APB2OPB_OPB1_READ_BYTE_ENDIAN      TO_REG(0x60)
 | ||
|  | #define   APB2OPB_OPB0_READ_WORD_ENDIAN_BE  0x00030b1b
 | ||
|  | 
 | ||
|  | #define APB2OPB_OPB0_READ_DATA         TO_REG(0x84)
 | ||
|  | #define APB2OPB_OPB1_READ_DATA         TO_REG(0x90)
 | ||
|  | 
 | ||
|  | /*
 | ||
|  |  * The following magic values came from AST2600 data sheet | ||
|  |  * The register values are defined under section "FSI controller" | ||
|  |  * as initial values. | ||
|  |  */ | ||
|  | static const uint32_t aspeed_apb2opb_reset[ASPEED_APB2OPB_NR_REGS] = { | ||
|  |      [APB2OPB_VERSION]                = 0x000000a1, | ||
|  |      [APB2OPB_OPB0_WRITE_WORD_ENDIAN] = 0x0044eee4, | ||
|  |      [APB2OPB_OPB0_WRITE_BYTE_ENDIAN] = 0x0055aaff, | ||
|  |      [APB2OPB_OPB1_WRITE_WORD_ENDIAN] = 0x00117717, | ||
|  |      [APB2OPB_OPB1_WRITE_BYTE_ENDIAN] = 0xffaa5500, | ||
|  |      [APB2OPB_OPB0_READ_BYTE_ENDIAN]  = 0x0044eee4, | ||
|  |      [APB2OPB_OPB1_READ_BYTE_ENDIAN]  = 0x00117717 | ||
|  | }; | ||
|  | 
 | ||
|  | static void fsi_opb_fsi_master_address(FSIMasterState *fsi, hwaddr addr) | ||
|  | { | ||
|  |     memory_region_transaction_begin(); | ||
|  |     memory_region_set_address(&fsi->iomem, addr); | ||
|  |     memory_region_transaction_commit(); | ||
|  | } | ||
|  | 
 | ||
|  | static void fsi_opb_opb2fsi_address(FSIMasterState *fsi, hwaddr addr) | ||
|  | { | ||
|  |     memory_region_transaction_begin(); | ||
|  |     memory_region_set_address(&fsi->opb2fsi, addr); | ||
|  |     memory_region_transaction_commit(); | ||
|  | } | ||
|  | 
 | ||
|  | static uint64_t fsi_aspeed_apb2opb_read(void *opaque, hwaddr addr, | ||
|  |                                         unsigned size) | ||
|  | { | ||
|  |     AspeedAPB2OPBState *s = ASPEED_APB2OPB(opaque); | ||
|  |     unsigned int reg = TO_REG(addr); | ||
|  | 
 | ||
|  |     trace_fsi_aspeed_apb2opb_read(addr, size); | ||
|  | 
 | ||
|  |     if (reg >= ASPEED_APB2OPB_NR_REGS) { | ||
|  |         qemu_log_mask(LOG_GUEST_ERROR, | ||
|  |                       "%s: Out of bounds read: 0x%"HWADDR_PRIx" for %u\n", | ||
|  |                       __func__, addr, size); | ||
|  |         return 0; | ||
|  |     } | ||
|  | 
 | ||
|  |     return s->regs[reg]; | ||
|  | } | ||
|  | 
 | ||
|  | static MemTxResult fsi_aspeed_apb2opb_rw(AddressSpace *as, hwaddr addr, | ||
|  |                                          MemTxAttrs attrs, uint32_t *data, | ||
|  |                                          uint32_t size, bool is_write) | ||
|  | { | ||
|  |     MemTxResult res; | ||
|  | 
 | ||
|  |     if (is_write) { | ||
|  |         switch (size) { | ||
|  |         case 4: | ||
|  |             address_space_stl_le(as, addr, *data, attrs, &res); | ||
|  |             break; | ||
|  |         case 2: | ||
|  |             address_space_stw_le(as, addr, *data, attrs, &res); | ||
|  |             break; | ||
|  |         case 1: | ||
|  |             address_space_stb(as, addr, *data, attrs, &res); | ||
|  |             break; | ||
|  |         default: | ||
|  |             g_assert_not_reached(); | ||
|  |         } | ||
|  |     } else { | ||
|  |         switch (size) { | ||
|  |         case 4: | ||
|  |             *data = address_space_ldl_le(as, addr, attrs, &res); | ||
|  |             break; | ||
|  |         case 2: | ||
|  |             *data = address_space_lduw_le(as, addr, attrs, &res); | ||
|  |             break; | ||
|  |         case 1: | ||
|  |             *data = address_space_ldub(as, addr, attrs, &res); | ||
|  |             break; | ||
|  |         default: | ||
|  |             g_assert_not_reached(); | ||
|  |         } | ||
|  |     } | ||
|  |     return res; | ||
|  | } | ||
|  | 
 | ||
|  | static void fsi_aspeed_apb2opb_write(void *opaque, hwaddr addr, uint64_t data, | ||
|  |                                      unsigned size) | ||
|  | { | ||
|  |     AspeedAPB2OPBState *s = ASPEED_APB2OPB(opaque); | ||
|  |     unsigned int reg = TO_REG(addr); | ||
|  | 
 | ||
|  |     trace_fsi_aspeed_apb2opb_write(addr, size, data); | ||
|  | 
 | ||
|  |     if (reg >= ASPEED_APB2OPB_NR_REGS) { | ||
|  |         qemu_log_mask(LOG_GUEST_ERROR, | ||
|  |                       "%s: Out of bounds write: %"HWADDR_PRIx" for %u\n", | ||
|  |                       __func__, addr, size); | ||
|  |         return; | ||
|  |     } | ||
|  | 
 | ||
|  |     switch (reg) { | ||
|  |     case APB2OPB_CONTROL: | ||
|  |         fsi_opb_fsi_master_address(&s->fsi[0], | ||
|  |                 data & APB2OPB_CONTROL_OFF); | ||
|  |         break; | ||
|  |     case APB2OPB_OPB2FSI: | ||
|  |         fsi_opb_opb2fsi_address(&s->fsi[0], | ||
|  |                 data & APB2OPB_OPB2FSI_OFF); | ||
|  |         break; | ||
|  |     case APB2OPB_OPB0_WRITE_WORD_ENDIAN: | ||
|  |         if (data != APB2OPB_OPB0_WRITE_WORD_ENDIAN_BE) { | ||
|  |             qemu_log_mask(LOG_GUEST_ERROR, | ||
|  |                           "%s: Bridge needs to be driven as BE (0x%x)\n", | ||
|  |                           __func__, APB2OPB_OPB0_WRITE_WORD_ENDIAN_BE); | ||
|  |         } | ||
|  |         break; | ||
|  |     case APB2OPB_OPB0_WRITE_BYTE_ENDIAN: | ||
|  |         if (data != APB2OPB_OPB0_WRITE_BYTE_ENDIAN_BE) { | ||
|  |             qemu_log_mask(LOG_GUEST_ERROR, | ||
|  |                           "%s: Bridge needs to be driven as BE (0x%x)\n", | ||
|  |                           __func__, APB2OPB_OPB0_WRITE_BYTE_ENDIAN_BE); | ||
|  |         } | ||
|  |         break; | ||
|  |     case APB2OPB_OPB0_READ_BYTE_ENDIAN: | ||
|  |         if (data != APB2OPB_OPB0_READ_WORD_ENDIAN_BE) { | ||
|  |             qemu_log_mask(LOG_GUEST_ERROR, | ||
|  |                           "%s: Bridge needs to be driven as BE (0x%x)\n", | ||
|  |                           __func__, APB2OPB_OPB0_READ_WORD_ENDIAN_BE); | ||
|  |         } | ||
|  |         break; | ||
|  |     case APB2OPB_TRIGGER: | ||
|  |     { | ||
|  |         uint32_t opb, op_mode, op_size, op_addr, op_data; | ||
|  |         MemTxResult result; | ||
|  |         bool is_write; | ||
|  |         int index; | ||
|  |         AddressSpace *as; | ||
|  | 
 | ||
|  |         assert((s->regs[APB2OPB_OPB0_SEL] & APB2OPB_OPB_SEL_EN) ^ | ||
|  |                (s->regs[APB2OPB_OPB1_SEL] & APB2OPB_OPB_SEL_EN)); | ||
|  | 
 | ||
|  |         if (s->regs[APB2OPB_OPB0_SEL] & APB2OPB_OPB_SEL_EN) { | ||
|  |             opb = 0; | ||
|  |             op_mode = s->regs[APB2OPB_OPB0_MODE]; | ||
|  |             op_size = s->regs[APB2OPB_OPB0_XFER]; | ||
|  |             op_addr = s->regs[APB2OPB_OPB0_ADDR]; | ||
|  |             op_data = s->regs[APB2OPB_OPB0_WRITE_DATA]; | ||
|  |         } else if (s->regs[APB2OPB_OPB1_SEL] & APB2OPB_OPB_SEL_EN) { | ||
|  |             opb = 1; | ||
|  |             op_mode = s->regs[APB2OPB_OPB1_MODE]; | ||
|  |             op_size = s->regs[APB2OPB_OPB1_XFER]; | ||
|  |             op_addr = s->regs[APB2OPB_OPB1_ADDR]; | ||
|  |             op_data = s->regs[APB2OPB_OPB1_WRITE_DATA]; | ||
|  |         } else { | ||
|  |             qemu_log_mask(LOG_GUEST_ERROR, | ||
|  |                           "%s: Invalid operation: 0x%"HWADDR_PRIx" for %u\n", | ||
|  |                           __func__, addr, size); | ||
|  |             return; | ||
|  |         } | ||
|  | 
 | ||
|  |         if (op_size & ~(APB2OPB_OPB_XFER_HALF | APB2OPB_OPB_XFER_FULL)) { | ||
|  |             qemu_log_mask(LOG_GUEST_ERROR, | ||
|  |                           "OPB transaction failed: Unrecognized access width: %d\n", | ||
|  |                           op_size); | ||
|  |             return; | ||
|  |         } | ||
|  | 
 | ||
|  |         op_size += 1; | ||
|  |         is_write = !(op_mode & APB2OPB_OPB_MODE_RD); | ||
|  |         index = opb ? APB2OPB_OPB1_READ_DATA : APB2OPB_OPB0_READ_DATA; | ||
|  |         as = &s->opb[opb].as; | ||
|  | 
 | ||
|  |         result = fsi_aspeed_apb2opb_rw(as, op_addr, MEMTXATTRS_UNSPECIFIED, | ||
|  |                                        &op_data, op_size, is_write); | ||
|  |         if (result != MEMTX_OK) { | ||
|  |             qemu_log_mask(LOG_GUEST_ERROR, "%s: OPB %s failed @%08x\n", | ||
|  |                           __func__, is_write ? "write" : "read", op_addr); | ||
|  |             return; | ||
|  |         } | ||
|  | 
 | ||
|  |         if (!is_write) { | ||
|  |             s->regs[index] = op_data; | ||
|  |         } | ||
|  | 
 | ||
|  |         s->regs[APB2OPB_IRQ_STS] |= opb ? APB2OPB_IRQ_STS_OPB1_TX_ACK | ||
|  |             : APB2OPB_IRQ_STS_OPB0_TX_ACK; | ||
|  |         break; | ||
|  |     } | ||
|  |     } | ||
|  | 
 | ||
|  |     s->regs[reg] = data; | ||
|  | } | ||
|  | 
 | ||
|  | static const struct MemoryRegionOps aspeed_apb2opb_ops = { | ||
|  |     .read = fsi_aspeed_apb2opb_read, | ||
|  |     .write = fsi_aspeed_apb2opb_write, | ||
|  |     .valid.max_access_size = 4, | ||
|  |     .valid.min_access_size = 4, | ||
|  |     .impl.max_access_size = 4, | ||
|  |     .impl.min_access_size = 4, | ||
|  |     .endianness = DEVICE_LITTLE_ENDIAN, | ||
|  | }; | ||
|  | 
 | ||
|  | static void fsi_aspeed_apb2opb_init(Object *o) | ||
|  | { | ||
|  |     AspeedAPB2OPBState *s = ASPEED_APB2OPB(o); | ||
|  |     int i; | ||
|  | 
 | ||
|  |     for (i = 0; i < ASPEED_FSI_NUM; i++) { | ||
|  |         object_initialize_child(o, "fsi-master[*]", &s->fsi[i], | ||
|  |                                 TYPE_FSI_MASTER); | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | static void fsi_aspeed_apb2opb_realize(DeviceState *dev, Error **errp) | ||
|  | { | ||
|  |     SysBusDevice *sbd = SYS_BUS_DEVICE(dev); | ||
|  |     AspeedAPB2OPBState *s = ASPEED_APB2OPB(dev); | ||
|  |     int i; | ||
|  | 
 | ||
|  |     /*
 | ||
|  |      * TODO: The OPBus model initializes the OPB address space in | ||
|  |      * the .instance_init handler and this is problematic for test | ||
|  |      * device-introspect-test. To avoid a memory corruption and a QEMU | ||
|  |      * crash, qbus_init() should be called from realize(). Something to | ||
|  |      * improve. Possibly, OPBus could also be removed. | ||
|  |      */ | ||
|  |     for (i = 0; i < ASPEED_FSI_NUM; i++) { | ||
|  |         qbus_init(&s->opb[i], sizeof(s->opb[i]), TYPE_OP_BUS, DEVICE(s), | ||
|  |                   NULL); | ||
|  |     } | ||
|  | 
 | ||
|  |     sysbus_init_irq(sbd, &s->irq); | ||
|  | 
 | ||
|  |     memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_apb2opb_ops, s, | ||
|  |                           TYPE_ASPEED_APB2OPB, 0x1000); | ||
|  |     sysbus_init_mmio(sbd, &s->iomem); | ||
|  | 
 | ||
|  |     for (i = 0; i < ASPEED_FSI_NUM; i++) { | ||
|  |         if (!qdev_realize(DEVICE(&s->fsi[i]), BUS(&s->opb[i]), errp)) { | ||
|  |             return; | ||
|  |         } | ||
|  | 
 | ||
|  |         memory_region_add_subregion(&s->opb[i].mr, 0x80000000, | ||
|  |                 &s->fsi[i].iomem); | ||
|  | 
 | ||
|  |         memory_region_add_subregion(&s->opb[i].mr, 0xa0000000, | ||
|  |                 &s->fsi[i].opb2fsi); | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | static void fsi_aspeed_apb2opb_reset(DeviceState *dev) | ||
|  | { | ||
|  |     AspeedAPB2OPBState *s = ASPEED_APB2OPB(dev); | ||
|  | 
 | ||
|  |     memcpy(s->regs, aspeed_apb2opb_reset, ASPEED_APB2OPB_NR_REGS); | ||
|  | } | ||
|  | 
 | ||
|  | static void fsi_aspeed_apb2opb_class_init(ObjectClass *klass, void *data) | ||
|  | { | ||
|  |     DeviceClass *dc = DEVICE_CLASS(klass); | ||
|  | 
 | ||
|  |     dc->desc = "ASPEED APB2OPB Bridge"; | ||
|  |     dc->realize = fsi_aspeed_apb2opb_realize; | ||
|  |     dc->reset = fsi_aspeed_apb2opb_reset; | ||
|  | } | ||
|  | 
 | ||
|  | static const TypeInfo aspeed_apb2opb_info = { | ||
|  |     .name = TYPE_ASPEED_APB2OPB, | ||
|  |     .parent = TYPE_SYS_BUS_DEVICE, | ||
|  |     .instance_init = fsi_aspeed_apb2opb_init, | ||
|  |     .instance_size = sizeof(AspeedAPB2OPBState), | ||
|  |     .class_init = fsi_aspeed_apb2opb_class_init, | ||
|  | }; | ||
|  | 
 | ||
|  | static void aspeed_apb2opb_register_types(void) | ||
|  | { | ||
|  |     type_register_static(&aspeed_apb2opb_info); | ||
|  | } | ||
|  | 
 | ||
|  | type_init(aspeed_apb2opb_register_types); | ||
|  | 
 | ||
|  | static void fsi_opb_init(Object *o) | ||
|  | { | ||
|  |     OPBus *opb = OP_BUS(o); | ||
|  | 
 | ||
|  |     memory_region_init(&opb->mr, 0, TYPE_FSI_OPB, UINT32_MAX); | ||
|  |     address_space_init(&opb->as, &opb->mr, TYPE_FSI_OPB); | ||
|  | } | ||
|  | 
 | ||
|  | static const TypeInfo opb_info = { | ||
|  |     .name = TYPE_OP_BUS, | ||
|  |     .parent = TYPE_BUS, | ||
|  |     .instance_init = fsi_opb_init, | ||
|  |     .instance_size = sizeof(OPBus), | ||
|  | }; | ||
|  | 
 | ||
|  | static void fsi_opb_register_types(void) | ||
|  | { | ||
|  |     type_register_static(&opb_info); | ||
|  | } | ||
|  | 
 | ||
|  | type_init(fsi_opb_register_types); |