xref: /openbmc/qemu/hw/misc/aspeed_peci.c (revision 990d2c18)
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     device_class_set_legacy_reset(dc, 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