1 /* 2 * Aspeed PECI Controller 3 * 4 * Copyright (c) Meta Platforms, Inc. and affiliates. (http://www.meta.com) 5 * 6 * This code is licensed under the GPL version 2 or later. See the COPYING 7 * file in the top-level directory. 8 */ 9 10 #include "qemu/osdep.h" 11 #include "qemu/log.h" 12 #include "hw/irq.h" 13 #include "hw/misc/aspeed_peci.h" 14 #include "hw/registerfields.h" 15 #include "trace.h" 16 17 #define ASPEED_PECI_CC_RSP_SUCCESS (0x40U) 18 19 /* Command Register */ 20 REG32(PECI_CMD, 0x08) 21 FIELD(PECI_CMD, FIRE, 0, 1) 22 23 /* Interrupt Control Register */ 24 REG32(PECI_INT_CTRL, 0x18) 25 26 /* Interrupt Status Register */ 27 REG32(PECI_INT_STS, 0x1C) 28 FIELD(PECI_INT_STS, CMD_DONE, 0, 1) 29 30 /* Rx/Tx Data Buffer Registers */ 31 REG32(PECI_WR_DATA0, 0x20) 32 REG32(PECI_RD_DATA0, 0x30) 33 34 static void aspeed_peci_raise_interrupt(AspeedPECIState *s, uint32_t status) 35 { 36 trace_aspeed_peci_raise_interrupt(s->regs[R_PECI_INT_CTRL], status); 37 38 s->regs[R_PECI_INT_STS] = s->regs[R_PECI_INT_CTRL] & status; 39 if (!s->regs[R_PECI_INT_STS]) { 40 return; 41 } 42 qemu_irq_raise(s->irq); 43 } 44 45 static uint64_t aspeed_peci_read(void *opaque, hwaddr offset, unsigned size) 46 { 47 AspeedPECIState *s = ASPEED_PECI(opaque); 48 uint64_t data; 49 50 if (offset >= ASPEED_PECI_NR_REGS << 2) { 51 qemu_log_mask(LOG_GUEST_ERROR, 52 "%s: Out-of-bounds read at offset 0x%" HWADDR_PRIx "\n", 53 __func__, offset); 54 return 0; 55 } 56 data = s->regs[offset >> 2]; 57 58 trace_aspeed_peci_read(offset, data); 59 return data; 60 } 61 62 static void aspeed_peci_write(void *opaque, hwaddr offset, uint64_t data, 63 unsigned size) 64 { 65 AspeedPECIState *s = ASPEED_PECI(opaque); 66 67 trace_aspeed_peci_write(offset, data); 68 69 if (offset >= ASPEED_PECI_NR_REGS << 2) { 70 qemu_log_mask(LOG_GUEST_ERROR, 71 "%s: Out-of-bounds write at offset 0x%" HWADDR_PRIx "\n", 72 __func__, offset); 73 return; 74 } 75 76 switch (offset) { 77 case A_PECI_INT_STS: 78 s->regs[R_PECI_INT_STS] &= ~data; 79 if (!s->regs[R_PECI_INT_STS]) { 80 qemu_irq_lower(s->irq); 81 } 82 break; 83 case A_PECI_CMD: 84 /* 85 * Only the FIRE bit is writable. Once the command is complete, it 86 * should be cleared. Since we complete the command immediately, the 87 * value is not stored in the register array. 88 */ 89 if (!FIELD_EX32(data, PECI_CMD, FIRE)) { 90 break; 91 } 92 if (s->regs[R_PECI_INT_STS]) { 93 qemu_log_mask(LOG_GUEST_ERROR, "%s: Interrupt status must be " 94 "cleared before firing another command: 0x%08x\n", 95 __func__, s->regs[R_PECI_INT_STS]); 96 break; 97 } 98 s->regs[R_PECI_RD_DATA0] = ASPEED_PECI_CC_RSP_SUCCESS; 99 s->regs[R_PECI_WR_DATA0] = ASPEED_PECI_CC_RSP_SUCCESS; 100 aspeed_peci_raise_interrupt(s, 101 FIELD_DP32(0, PECI_INT_STS, CMD_DONE, 1)); 102 break; 103 default: 104 s->regs[offset / sizeof(s->regs[0])] = data; 105 break; 106 } 107 } 108 109 static const MemoryRegionOps aspeed_peci_ops = { 110 .read = aspeed_peci_read, 111 .write = aspeed_peci_write, 112 .endianness = DEVICE_LITTLE_ENDIAN, 113 }; 114 115 static void aspeed_peci_realize(DeviceState *dev, Error **errp) 116 { 117 AspeedPECIState *s = ASPEED_PECI(dev); 118 SysBusDevice *sbd = SYS_BUS_DEVICE(dev); 119 120 memory_region_init_io(&s->mmio, OBJECT(s), &aspeed_peci_ops, s, 121 TYPE_ASPEED_PECI, 0x1000); 122 sysbus_init_mmio(sbd, &s->mmio); 123 sysbus_init_irq(sbd, &s->irq); 124 } 125 126 static void aspeed_peci_reset(DeviceState *dev) 127 { 128 AspeedPECIState *s = ASPEED_PECI(dev); 129 130 memset(s->regs, 0, sizeof(s->regs)); 131 } 132 133 static void aspeed_peci_class_init(ObjectClass *klass, void *data) 134 { 135 DeviceClass *dc = DEVICE_CLASS(klass); 136 137 dc->realize = aspeed_peci_realize; 138 dc->reset = aspeed_peci_reset; 139 dc->desc = "Aspeed PECI Controller"; 140 } 141 142 static const TypeInfo aspeed_peci_types[] = { 143 { 144 .name = TYPE_ASPEED_PECI, 145 .parent = TYPE_SYS_BUS_DEVICE, 146 .instance_size = sizeof(AspeedPECIState), 147 .class_init = aspeed_peci_class_init, 148 .abstract = false, 149 }, 150 }; 151 152 DEFINE_TYPES(aspeed_peci_types); 153