]>
Commit | Line | Data |
---|---|---|
1ae41f44 HP |
1 | /* |
2 | * QEMU National Semiconductor PC87312 (Super I/O) | |
3 | * | |
4 | * Copyright (c) 2010-2012 Herve Poussineau | |
5 | * Copyright (c) 2011-2012 Andreas Färber | |
6 | * | |
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
8 | * of this software and associated documentation files (the "Software"), to deal | |
9 | * in the Software without restriction, including without limitation the rights | |
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
11 | * copies of the Software, and to permit persons to whom the Software is | |
12 | * furnished to do so, subject to the following conditions: | |
13 | * | |
14 | * The above copyright notice and this permission notice shall be included in | |
15 | * all copies or substantial portions of the Software. | |
16 | * | |
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
20 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
23 | * THE SOFTWARE. | |
24 | */ | |
25 | ||
0d75590d | 26 | #include "qemu/osdep.h" |
0d09e41a | 27 | #include "hw/isa/pc87312.h" |
da34e65c | 28 | #include "qapi/error.h" |
b4a42f81 | 29 | #include "qemu/error-report.h" |
1ae41f44 HP |
30 | #include "trace.h" |
31 | ||
32 | ||
33 | #define REG_FER 0 | |
34 | #define REG_FAR 1 | |
35 | #define REG_PTR 2 | |
36 | ||
1ae41f44 HP |
37 | #define FER_PARALLEL_EN 0x01 |
38 | #define FER_UART1_EN 0x02 | |
39 | #define FER_UART2_EN 0x04 | |
40 | #define FER_FDC_EN 0x08 | |
41 | #define FER_FDC_4 0x10 | |
42 | #define FER_FDC_ADDR 0x20 | |
43 | #define FER_IDE_EN 0x40 | |
44 | #define FER_IDE_ADDR 0x80 | |
45 | ||
46 | #define FAR_PARALLEL_ADDR 0x03 | |
47 | #define FAR_UART1_ADDR 0x0C | |
48 | #define FAR_UART2_ADDR 0x30 | |
49 | #define FAR_UART_3_4 0xC0 | |
50 | ||
51 | #define PTR_POWER_DOWN 0x01 | |
52 | #define PTR_CLOCK_DOWN 0x02 | |
53 | #define PTR_PWDN 0x04 | |
54 | #define PTR_IRQ_5_7 0x08 | |
55 | #define PTR_UART1_TEST 0x10 | |
56 | #define PTR_UART2_TEST 0x20 | |
57 | #define PTR_LOCK_CONF 0x40 | |
58 | #define PTR_EPP_MODE 0x80 | |
59 | ||
60 | ||
61 | /* Parallel port */ | |
62 | ||
4c3119a6 | 63 | static bool is_parallel_enabled(ISASuperIODevice *sio, uint8_t index) |
1ae41f44 | 64 | { |
4c3119a6 PMD |
65 | PC87312State *s = PC87312(sio); |
66 | return index ? false : s->regs[REG_FER] & FER_PARALLEL_EN; | |
1ae41f44 HP |
67 | } |
68 | ||
4e00105a | 69 | static const uint16_t parallel_base[] = { 0x378, 0x3bc, 0x278, 0x00 }; |
1ae41f44 | 70 | |
4c3119a6 | 71 | static uint16_t get_parallel_iobase(ISASuperIODevice *sio, uint8_t index) |
1ae41f44 | 72 | { |
4c3119a6 | 73 | PC87312State *s = PC87312(sio); |
08bb4a7c | 74 | return parallel_base[s->regs[REG_FAR] & FAR_PARALLEL_ADDR]; |
1ae41f44 HP |
75 | } |
76 | ||
818c9d99 | 77 | static const unsigned int parallel_irq[] = { 5, 7, 5, 0 }; |
1ae41f44 | 78 | |
4c3119a6 | 79 | static unsigned int get_parallel_irq(ISASuperIODevice *sio, uint8_t index) |
1ae41f44 | 80 | { |
4c3119a6 | 81 | PC87312State *s = PC87312(sio); |
1ae41f44 | 82 | int idx; |
08bb4a7c | 83 | idx = (s->regs[REG_FAR] & FAR_PARALLEL_ADDR); |
1ae41f44 | 84 | if (idx == 0) { |
08bb4a7c | 85 | return (s->regs[REG_PTR] & PTR_IRQ_5_7) ? 7 : 5; |
1ae41f44 HP |
86 | } else { |
87 | return parallel_irq[idx]; | |
88 | } | |
89 | } | |
90 | ||
1ae41f44 HP |
91 | |
92 | /* UARTs */ | |
93 | ||
4e00105a | 94 | static const uint16_t uart_base[2][4] = { |
1ae41f44 HP |
95 | { 0x3e8, 0x338, 0x2e8, 0x220 }, |
96 | { 0x2e8, 0x238, 0x2e0, 0x228 } | |
97 | }; | |
98 | ||
cd9526ab | 99 | static uint16_t get_uart_iobase(ISASuperIODevice *sio, uint8_t i) |
1ae41f44 | 100 | { |
cd9526ab | 101 | PC87312State *s = PC87312(sio); |
1ae41f44 | 102 | int idx; |
08bb4a7c | 103 | idx = (s->regs[REG_FAR] >> (2 * i + 2)) & 0x3; |
1ae41f44 HP |
104 | if (idx == 0) { |
105 | return 0x3f8; | |
106 | } else if (idx == 1) { | |
107 | return 0x2f8; | |
108 | } else { | |
08bb4a7c | 109 | return uart_base[idx & 1][(s->regs[REG_FAR] & FAR_UART_3_4) >> 6]; |
1ae41f44 HP |
110 | } |
111 | } | |
112 | ||
cd9526ab | 113 | static unsigned int get_uart_irq(ISASuperIODevice *sio, uint8_t i) |
1ae41f44 | 114 | { |
cd9526ab | 115 | PC87312State *s = PC87312(sio); |
1ae41f44 | 116 | int idx; |
08bb4a7c | 117 | idx = (s->regs[REG_FAR] >> (2 * i + 2)) & 0x3; |
1ae41f44 HP |
118 | return (idx & 1) ? 3 : 4; |
119 | } | |
120 | ||
cd9526ab | 121 | static bool is_uart_enabled(ISASuperIODevice *sio, uint8_t i) |
1ae41f44 | 122 | { |
cd9526ab | 123 | PC87312State *s = PC87312(sio); |
08bb4a7c | 124 | return s->regs[REG_FER] & (FER_UART1_EN << i); |
1ae41f44 HP |
125 | } |
126 | ||
127 | ||
128 | /* Floppy controller */ | |
129 | ||
6f6695b1 | 130 | static bool is_fdc_enabled(ISASuperIODevice *sio, uint8_t index) |
1ae41f44 | 131 | { |
6f6695b1 PMD |
132 | PC87312State *s = PC87312(sio); |
133 | assert(!index); | |
08bb4a7c | 134 | return s->regs[REG_FER] & FER_FDC_EN; |
1ae41f44 HP |
135 | } |
136 | ||
6f6695b1 | 137 | static uint16_t get_fdc_iobase(ISASuperIODevice *sio, uint8_t index) |
1ae41f44 | 138 | { |
6f6695b1 PMD |
139 | PC87312State *s = PC87312(sio); |
140 | assert(!index); | |
08bb4a7c | 141 | return (s->regs[REG_FER] & FER_FDC_ADDR) ? 0x370 : 0x3f0; |
1ae41f44 HP |
142 | } |
143 | ||
6f6695b1 PMD |
144 | static unsigned int get_fdc_irq(ISASuperIODevice *sio, uint8_t index) |
145 | { | |
146 | assert(!index); | |
147 | return 6; | |
148 | } | |
149 | ||
1ae41f44 HP |
150 | |
151 | /* IDE controller */ | |
152 | ||
c16a4e1b | 153 | static bool is_ide_enabled(ISASuperIODevice *sio, uint8_t index) |
1ae41f44 | 154 | { |
c16a4e1b PMD |
155 | PC87312State *s = PC87312(sio); |
156 | ||
08bb4a7c | 157 | return s->regs[REG_FER] & FER_IDE_EN; |
1ae41f44 HP |
158 | } |
159 | ||
c16a4e1b | 160 | static uint16_t get_ide_iobase(ISASuperIODevice *sio, uint8_t index) |
1ae41f44 | 161 | { |
c16a4e1b PMD |
162 | PC87312State *s = PC87312(sio); |
163 | ||
164 | if (index == 1) { | |
165 | return get_ide_iobase(sio, 0) + 0x206; | |
166 | } | |
08bb4a7c | 167 | return (s->regs[REG_FER] & FER_IDE_ADDR) ? 0x170 : 0x1f0; |
1ae41f44 HP |
168 | } |
169 | ||
c16a4e1b PMD |
170 | static unsigned int get_ide_irq(ISASuperIODevice *sio, uint8_t index) |
171 | { | |
172 | assert(index == 0); | |
173 | return 14; | |
174 | } | |
1ae41f44 HP |
175 | |
176 | static void reconfigure_devices(PC87312State *s) | |
177 | { | |
178 | error_report("pc87312: unsupported device reconfiguration (%02x %02x %02x)", | |
08bb4a7c | 179 | s->regs[REG_FER], s->regs[REG_FAR], s->regs[REG_PTR]); |
1ae41f44 HP |
180 | } |
181 | ||
182 | static void pc87312_soft_reset(PC87312State *s) | |
183 | { | |
184 | static const uint8_t fer_init[] = { | |
185 | 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4b, 0x4b, | |
186 | 0x4b, 0x4b, 0x4b, 0x4b, 0x0f, 0x0f, 0x0f, 0x0f, | |
187 | 0x49, 0x49, 0x49, 0x49, 0x07, 0x07, 0x07, 0x07, | |
188 | 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x08, 0x00, | |
189 | }; | |
190 | static const uint8_t far_init[] = { | |
191 | 0x10, 0x11, 0x11, 0x39, 0x24, 0x38, 0x00, 0x01, | |
192 | 0x01, 0x09, 0x08, 0x08, 0x10, 0x11, 0x39, 0x24, | |
193 | 0x00, 0x01, 0x01, 0x00, 0x10, 0x11, 0x39, 0x24, | |
194 | 0x10, 0x11, 0x11, 0x39, 0x24, 0x38, 0x10, 0x10, | |
195 | }; | |
196 | static const uint8_t ptr_init[] = { | |
197 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
198 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
199 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
200 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, | |
201 | }; | |
202 | ||
203 | s->read_id_step = 0; | |
204 | s->selected_index = REG_FER; | |
205 | ||
08bb4a7c BS |
206 | s->regs[REG_FER] = fer_init[s->config & 0x1f]; |
207 | s->regs[REG_FAR] = far_init[s->config & 0x1f]; | |
208 | s->regs[REG_PTR] = ptr_init[s->config & 0x1f]; | |
1ae41f44 HP |
209 | } |
210 | ||
211 | static void pc87312_hard_reset(PC87312State *s) | |
212 | { | |
213 | pc87312_soft_reset(s); | |
214 | } | |
215 | ||
328c24a9 AF |
216 | static void pc87312_io_write(void *opaque, hwaddr addr, uint64_t val, |
217 | unsigned int size) | |
1ae41f44 HP |
218 | { |
219 | PC87312State *s = opaque; | |
220 | ||
221 | trace_pc87312_io_write(addr, val); | |
222 | ||
223 | if ((addr & 1) == 0) { | |
224 | /* Index register */ | |
225 | s->read_id_step = 2; | |
226 | s->selected_index = val; | |
227 | } else { | |
228 | /* Data register */ | |
229 | if (s->selected_index < 3) { | |
230 | s->regs[s->selected_index] = val; | |
231 | reconfigure_devices(s); | |
232 | } | |
233 | } | |
234 | } | |
235 | ||
328c24a9 | 236 | static uint64_t pc87312_io_read(void *opaque, hwaddr addr, unsigned int size) |
1ae41f44 HP |
237 | { |
238 | PC87312State *s = opaque; | |
239 | uint32_t val; | |
240 | ||
241 | if ((addr & 1) == 0) { | |
242 | /* Index register */ | |
243 | if (s->read_id_step++ == 0) { | |
244 | val = 0x88; | |
245 | } else if (s->read_id_step++ == 1) { | |
246 | val = 0; | |
247 | } else { | |
248 | val = s->selected_index; | |
249 | } | |
250 | } else { | |
251 | /* Data register */ | |
252 | if (s->selected_index < 3) { | |
253 | val = s->regs[s->selected_index]; | |
254 | } else { | |
255 | /* Invalid selected index */ | |
256 | val = 0; | |
257 | } | |
258 | } | |
259 | ||
260 | trace_pc87312_io_read(addr, val); | |
261 | return val; | |
262 | } | |
263 | ||
328c24a9 AF |
264 | static const MemoryRegionOps pc87312_io_ops = { |
265 | .read = pc87312_io_read, | |
266 | .write = pc87312_io_write, | |
267 | .endianness = DEVICE_LITTLE_ENDIAN, | |
268 | .valid = { | |
269 | .min_access_size = 1, | |
270 | .max_access_size = 1, | |
271 | }, | |
272 | }; | |
273 | ||
1ae41f44 HP |
274 | static int pc87312_post_load(void *opaque, int version_id) |
275 | { | |
276 | PC87312State *s = opaque; | |
277 | ||
278 | reconfigure_devices(s); | |
279 | return 0; | |
280 | } | |
281 | ||
282 | static void pc87312_reset(DeviceState *d) | |
283 | { | |
284 | PC87312State *s = PC87312(d); | |
285 | ||
286 | pc87312_soft_reset(s); | |
287 | } | |
288 | ||
db895a1e | 289 | static void pc87312_realize(DeviceState *dev, Error **errp) |
1ae41f44 HP |
290 | { |
291 | PC87312State *s; | |
1ae41f44 | 292 | ISADevice *isa; |
63f01a74 | 293 | Error *local_err = NULL; |
1ae41f44 HP |
294 | |
295 | s = PC87312(dev); | |
db895a1e | 296 | isa = ISA_DEVICE(dev); |
db895a1e | 297 | isa_register_ioport(isa, &s->io, s->iobase); |
1ae41f44 HP |
298 | pc87312_hard_reset(s); |
299 | ||
63f01a74 PMD |
300 | ISA_SUPERIO_GET_CLASS(dev)->parent_realize(dev, &local_err); |
301 | if (local_err) { | |
302 | error_propagate(errp, local_err); | |
303 | return; | |
304 | } | |
1ae41f44 HP |
305 | } |
306 | ||
328c24a9 AF |
307 | static void pc87312_initfn(Object *obj) |
308 | { | |
309 | PC87312State *s = PC87312(obj); | |
310 | ||
1437c94b | 311 | memory_region_init_io(&s->io, obj, &pc87312_io_ops, s, "pc87312", 2); |
328c24a9 AF |
312 | } |
313 | ||
1ae41f44 HP |
314 | static const VMStateDescription vmstate_pc87312 = { |
315 | .name = "pc87312", | |
316 | .version_id = 1, | |
317 | .minimum_version_id = 1, | |
318 | .post_load = pc87312_post_load, | |
319 | .fields = (VMStateField[]) { | |
320 | VMSTATE_UINT8(read_id_step, PC87312State), | |
321 | VMSTATE_UINT8(selected_index, PC87312State), | |
322 | VMSTATE_UINT8_ARRAY(regs, PC87312State, 3), | |
323 | VMSTATE_END_OF_LIST() | |
324 | } | |
325 | }; | |
326 | ||
327 | static Property pc87312_properties[] = { | |
4e00105a | 328 | DEFINE_PROP_UINT16("iobase", PC87312State, iobase, 0x398), |
1ae41f44 HP |
329 | DEFINE_PROP_UINT8("config", PC87312State, config, 1), |
330 | DEFINE_PROP_END_OF_LIST() | |
331 | }; | |
332 | ||
333 | static void pc87312_class_init(ObjectClass *klass, void *data) | |
334 | { | |
335 | DeviceClass *dc = DEVICE_CLASS(klass); | |
63f01a74 | 336 | ISASuperIOClass *sc = ISA_SUPERIO_CLASS(klass); |
1ae41f44 | 337 | |
63f01a74 | 338 | sc->parent_realize = dc->realize; |
db895a1e | 339 | dc->realize = pc87312_realize; |
1ae41f44 HP |
340 | dc->reset = pc87312_reset; |
341 | dc->vmsd = &vmstate_pc87312; | |
342 | dc->props = pc87312_properties; | |
4c3119a6 PMD |
343 | |
344 | sc->parallel = (ISASuperIOFuncs){ | |
345 | .count = 1, | |
346 | .is_enabled = is_parallel_enabled, | |
347 | .get_iobase = get_parallel_iobase, | |
348 | .get_irq = get_parallel_irq, | |
349 | }; | |
cd9526ab PMD |
350 | sc->serial = (ISASuperIOFuncs){ |
351 | .count = 2, | |
352 | .is_enabled = is_uart_enabled, | |
353 | .get_iobase = get_uart_iobase, | |
354 | .get_irq = get_uart_irq, | |
355 | }; | |
6f6695b1 PMD |
356 | sc->floppy = (ISASuperIOFuncs){ |
357 | .count = 1, | |
358 | .is_enabled = is_fdc_enabled, | |
359 | .get_iobase = get_fdc_iobase, | |
360 | .get_irq = get_fdc_irq, | |
361 | }; | |
c16a4e1b PMD |
362 | sc->ide = (ISASuperIOFuncs){ |
363 | .count = 1, | |
364 | .is_enabled = is_ide_enabled, | |
365 | .get_iobase = get_ide_iobase, | |
366 | .get_irq = get_ide_irq, | |
367 | }; | |
1ae41f44 HP |
368 | } |
369 | ||
370 | static const TypeInfo pc87312_type_info = { | |
010d2dc4 | 371 | .name = TYPE_PC87312_SUPERIO, |
63f01a74 | 372 | .parent = TYPE_ISA_SUPERIO, |
1ae41f44 | 373 | .instance_size = sizeof(PC87312State), |
328c24a9 | 374 | .instance_init = pc87312_initfn, |
1ae41f44 | 375 | .class_init = pc87312_class_init, |
6f6695b1 | 376 | /* FIXME use a qdev drive property instead of drive_get() */ |
1ae41f44 HP |
377 | }; |
378 | ||
379 | static void pc87312_register_types(void) | |
380 | { | |
381 | type_register_static(&pc87312_type_info); | |
382 | } | |
383 | ||
384 | type_init(pc87312_register_types) |