1 | /* |
2 | * QEMU PowerPC PowerNV Emulation of a few OCC related registers |
3 | * |
4 | * Copyright (c) 2015-2017, IBM Corporation. |
5 | * |
6 | * This program is free software; you can redistribute it and/or modify |
7 | * it under the terms of the GNU General Public License, version 2, as |
8 | * published by the Free Software Foundation. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU General Public License |
16 | * along with this program; if not, see <http://www.gnu.org/licenses/>. |
17 | */ |
18 | |
19 | #include "qemu/osdep.h" |
20 | #include "target/ppc/cpu.h" |
21 | #include "qapi/error.h" |
22 | #include "qemu/log.h" |
23 | #include "qemu/module.h" |
24 | |
25 | #include "hw/ppc/pnv.h" |
26 | #include "hw/ppc/pnv_xscom.h" |
27 | #include "hw/ppc/pnv_occ.h" |
28 | |
29 | #define OCB_OCI_OCCMISC 0x4020 |
30 | #define OCB_OCI_OCCMISC_AND 0x4021 |
31 | #define OCB_OCI_OCCMISC_OR 0x4022 |
32 | |
33 | static void pnv_occ_set_misc(PnvOCC *occ, uint64_t val) |
34 | { |
35 | bool irq_state; |
36 | PnvOCCClass *poc = PNV_OCC_GET_CLASS(occ); |
37 | |
38 | val &= 0xffff000000000000ull; |
39 | |
40 | occ->occmisc = val; |
41 | irq_state = !!(val >> 63); |
42 | pnv_psi_irq_set(occ->psi, poc->psi_irq, irq_state); |
43 | } |
44 | |
45 | static uint64_t pnv_occ_power8_xscom_read(void *opaque, hwaddr addr, |
46 | unsigned size) |
47 | { |
48 | PnvOCC *occ = PNV_OCC(opaque); |
49 | uint32_t offset = addr >> 3; |
50 | uint64_t val = 0; |
51 | |
52 | switch (offset) { |
53 | case OCB_OCI_OCCMISC: |
54 | val = occ->occmisc; |
55 | break; |
56 | default: |
57 | qemu_log_mask(LOG_UNIMP, "OCC Unimplemented register: Ox%" |
58 | HWADDR_PRIx "\n" , addr >> 3); |
59 | } |
60 | return val; |
61 | } |
62 | |
63 | static void pnv_occ_power8_xscom_write(void *opaque, hwaddr addr, |
64 | uint64_t val, unsigned size) |
65 | { |
66 | PnvOCC *occ = PNV_OCC(opaque); |
67 | uint32_t offset = addr >> 3; |
68 | |
69 | switch (offset) { |
70 | case OCB_OCI_OCCMISC_AND: |
71 | pnv_occ_set_misc(occ, occ->occmisc & val); |
72 | break; |
73 | case OCB_OCI_OCCMISC_OR: |
74 | pnv_occ_set_misc(occ, occ->occmisc | val); |
75 | break; |
76 | case OCB_OCI_OCCMISC: |
77 | pnv_occ_set_misc(occ, val); |
78 | break; |
79 | default: |
80 | qemu_log_mask(LOG_UNIMP, "OCC Unimplemented register: Ox%" |
81 | HWADDR_PRIx "\n" , addr >> 3); |
82 | } |
83 | } |
84 | |
85 | static const MemoryRegionOps pnv_occ_power8_xscom_ops = { |
86 | .read = pnv_occ_power8_xscom_read, |
87 | .write = pnv_occ_power8_xscom_write, |
88 | .valid.min_access_size = 8, |
89 | .valid.max_access_size = 8, |
90 | .impl.min_access_size = 8, |
91 | .impl.max_access_size = 8, |
92 | .endianness = DEVICE_BIG_ENDIAN, |
93 | }; |
94 | |
95 | static void pnv_occ_power8_class_init(ObjectClass *klass, void *data) |
96 | { |
97 | PnvOCCClass *poc = PNV_OCC_CLASS(klass); |
98 | |
99 | poc->xscom_size = PNV_XSCOM_OCC_SIZE; |
100 | poc->xscom_ops = &pnv_occ_power8_xscom_ops; |
101 | poc->psi_irq = PSIHB_IRQ_OCC; |
102 | } |
103 | |
104 | static const TypeInfo pnv_occ_power8_type_info = { |
105 | .name = TYPE_PNV8_OCC, |
106 | .parent = TYPE_PNV_OCC, |
107 | .instance_size = sizeof(PnvOCC), |
108 | .class_init = pnv_occ_power8_class_init, |
109 | }; |
110 | |
111 | #define P9_OCB_OCI_OCCMISC 0x6080 |
112 | #define P9_OCB_OCI_OCCMISC_CLEAR 0x6081 |
113 | #define P9_OCB_OCI_OCCMISC_OR 0x6082 |
114 | |
115 | |
116 | static uint64_t pnv_occ_power9_xscom_read(void *opaque, hwaddr addr, |
117 | unsigned size) |
118 | { |
119 | PnvOCC *occ = PNV_OCC(opaque); |
120 | uint32_t offset = addr >> 3; |
121 | uint64_t val = 0; |
122 | |
123 | switch (offset) { |
124 | case P9_OCB_OCI_OCCMISC: |
125 | val = occ->occmisc; |
126 | break; |
127 | default: |
128 | qemu_log_mask(LOG_UNIMP, "OCC Unimplemented register: Ox%" |
129 | HWADDR_PRIx "\n" , addr >> 3); |
130 | } |
131 | return val; |
132 | } |
133 | |
134 | static void pnv_occ_power9_xscom_write(void *opaque, hwaddr addr, |
135 | uint64_t val, unsigned size) |
136 | { |
137 | PnvOCC *occ = PNV_OCC(opaque); |
138 | uint32_t offset = addr >> 3; |
139 | |
140 | switch (offset) { |
141 | case P9_OCB_OCI_OCCMISC_CLEAR: |
142 | pnv_occ_set_misc(occ, 0); |
143 | break; |
144 | case P9_OCB_OCI_OCCMISC_OR: |
145 | pnv_occ_set_misc(occ, occ->occmisc | val); |
146 | break; |
147 | case P9_OCB_OCI_OCCMISC: |
148 | pnv_occ_set_misc(occ, val); |
149 | break; |
150 | default: |
151 | qemu_log_mask(LOG_UNIMP, "OCC Unimplemented register: Ox%" |
152 | HWADDR_PRIx "\n" , addr >> 3); |
153 | } |
154 | } |
155 | |
156 | static const MemoryRegionOps pnv_occ_power9_xscom_ops = { |
157 | .read = pnv_occ_power9_xscom_read, |
158 | .write = pnv_occ_power9_xscom_write, |
159 | .valid.min_access_size = 8, |
160 | .valid.max_access_size = 8, |
161 | .impl.min_access_size = 8, |
162 | .impl.max_access_size = 8, |
163 | .endianness = DEVICE_BIG_ENDIAN, |
164 | }; |
165 | |
166 | static void pnv_occ_power9_class_init(ObjectClass *klass, void *data) |
167 | { |
168 | PnvOCCClass *poc = PNV_OCC_CLASS(klass); |
169 | |
170 | poc->xscom_size = PNV9_XSCOM_OCC_SIZE; |
171 | poc->xscom_ops = &pnv_occ_power9_xscom_ops; |
172 | poc->psi_irq = PSIHB9_IRQ_OCC; |
173 | } |
174 | |
175 | static const TypeInfo pnv_occ_power9_type_info = { |
176 | .name = TYPE_PNV9_OCC, |
177 | .parent = TYPE_PNV_OCC, |
178 | .instance_size = sizeof(PnvOCC), |
179 | .class_init = pnv_occ_power9_class_init, |
180 | }; |
181 | |
182 | static void pnv_occ_realize(DeviceState *dev, Error **errp) |
183 | { |
184 | PnvOCC *occ = PNV_OCC(dev); |
185 | PnvOCCClass *poc = PNV_OCC_GET_CLASS(occ); |
186 | Object *obj; |
187 | Error *local_err = NULL; |
188 | |
189 | occ->occmisc = 0; |
190 | |
191 | obj = object_property_get_link(OBJECT(dev), "psi" , &local_err); |
192 | if (!obj) { |
193 | error_propagate(errp, local_err); |
194 | error_prepend(errp, "required link 'psi' not found: " ); |
195 | return; |
196 | } |
197 | occ->psi = PNV_PSI(obj); |
198 | |
199 | /* XScom region for OCC registers */ |
200 | pnv_xscom_region_init(&occ->xscom_regs, OBJECT(dev), poc->xscom_ops, |
201 | occ, "xscom-occ" , poc->xscom_size); |
202 | } |
203 | |
204 | static void pnv_occ_class_init(ObjectClass *klass, void *data) |
205 | { |
206 | DeviceClass *dc = DEVICE_CLASS(klass); |
207 | |
208 | dc->realize = pnv_occ_realize; |
209 | dc->desc = "PowerNV OCC Controller" ; |
210 | } |
211 | |
212 | static const TypeInfo pnv_occ_type_info = { |
213 | .name = TYPE_PNV_OCC, |
214 | .parent = TYPE_DEVICE, |
215 | .instance_size = sizeof(PnvOCC), |
216 | .class_init = pnv_occ_class_init, |
217 | .class_size = sizeof(PnvOCCClass), |
218 | .abstract = true, |
219 | }; |
220 | |
221 | static void pnv_occ_register_types(void) |
222 | { |
223 | type_register_static(&pnv_occ_type_info); |
224 | type_register_static(&pnv_occ_power8_type_info); |
225 | type_register_static(&pnv_occ_power9_type_info); |
226 | } |
227 | |
228 | type_init(pnv_occ_register_types); |
229 | |