]>
Commit | Line | Data |
---|---|---|
6dca62a0 LV |
1 | /* |
2 | * QEMU m68k Macintosh VIA device support | |
3 | * | |
4 | * Copyright (c) 2011-2018 Laurent Vivier | |
5 | * Copyright (c) 2018 Mark Cave-Ayland | |
6 | * | |
7 | * Some parts from hw/misc/macio/cuda.c | |
8 | * | |
9 | * Copyright (c) 2004-2007 Fabrice Bellard | |
10 | * Copyright (c) 2007 Jocelyn Mayer | |
11 | * | |
12 | * some parts from linux-2.6.29, arch/m68k/include/asm/mac_via.h | |
13 | * | |
14 | * This work is licensed under the terms of the GNU GPL, version 2 or later. | |
15 | * See the COPYING file in the top-level directory. | |
16 | */ | |
17 | ||
18 | #include "qemu/osdep.h" | |
366d2779 | 19 | #include "exec/address-spaces.h" |
6dca62a0 LV |
20 | #include "migration/vmstate.h" |
21 | #include "hw/sysbus.h" | |
22 | #include "hw/irq.h" | |
23 | #include "qemu/timer.h" | |
24 | #include "hw/misc/mac_via.h" | |
25 | #include "hw/misc/mos6522.h" | |
26 | #include "hw/input/adb.h" | |
27 | #include "sysemu/runstate.h" | |
28 | #include "qapi/error.h" | |
29 | #include "qemu/cutils.h" | |
eb064db9 | 30 | #include "hw/qdev-properties.h" |
ce35e229 | 31 | #include "hw/qdev-properties-system.h" |
eb064db9 | 32 | #include "sysemu/block-backend.h" |
2f93d8b0 | 33 | #include "sysemu/rtc.h" |
b2619c15 | 34 | #include "trace.h" |
80aab795 | 35 | #include "qemu/log.h" |
6dca62a0 LV |
36 | |
37 | /* | |
02a68a3e | 38 | * VIAs: There are two in every machine |
6dca62a0 LV |
39 | */ |
40 | ||
6dca62a0 LV |
41 | /* |
42 | * Not all of these are true post MacII I think. | |
43 | * CSA: probably the ones CHRP marks as 'unused' change purposes | |
44 | * when the IWM becomes the SWIM. | |
45 | * http://www.rs6000.ibm.com/resource/technology/chrpio/via5.mak.html | |
46 | * ftp://ftp.austin.ibm.com/pub/technology/spec/chrp/inwork/CHRP_IORef_1.0.pdf | |
47 | * | |
48 | * also, http://developer.apple.com/technotes/hw/hw_09.html claims the | |
49 | * following changes for IIfx: | |
50 | * VIA1A_vSccWrReq not available and that VIA1A_vSync has moved to an IOP. | |
51 | * Also, "All of the functionality of VIA2 has been moved to other chips". | |
52 | */ | |
53 | ||
54 | #define VIA1A_vSccWrReq 0x80 /* | |
55 | * SCC write. (input) | |
56 | * [CHRP] SCC WREQ: Reflects the state of the | |
57 | * Wait/Request pins from the SCC. | |
58 | * [Macintosh Family Hardware] | |
59 | * as CHRP on SE/30,II,IIx,IIcx,IIci. | |
60 | * on IIfx, "0 means an active request" | |
61 | */ | |
62 | #define VIA1A_vRev8 0x40 /* | |
63 | * Revision 8 board ??? | |
64 | * [CHRP] En WaitReqB: Lets the WaitReq_L | |
65 | * signal from port B of the SCC appear on | |
66 | * the PA7 input pin. Output. | |
67 | * [Macintosh Family] On the SE/30, this | |
68 | * is the bit to flip screen buffers. | |
69 | * 0=alternate, 1=main. | |
70 | * on II,IIx,IIcx,IIci,IIfx this is a bit | |
71 | * for Rev ID. 0=II,IIx, 1=IIcx,IIci,IIfx | |
72 | */ | |
73 | #define VIA1A_vHeadSel 0x20 /* | |
74 | * Head select for IWM. | |
75 | * [CHRP] unused. | |
76 | * [Macintosh Family] "Floppy disk | |
77 | * state-control line SEL" on all but IIfx | |
78 | */ | |
79 | #define VIA1A_vOverlay 0x10 /* | |
80 | * [Macintosh Family] On SE/30,II,IIx,IIcx | |
81 | * this bit enables the "Overlay" address | |
82 | * map in the address decoders as it is on | |
83 | * reset for mapping the ROM over the reset | |
84 | * vector. 1=use overlay map. | |
85 | * On the IIci,IIfx it is another bit of the | |
86 | * CPU ID: 0=normal IIci, 1=IIci with parity | |
87 | * feature or IIfx. | |
88 | * [CHRP] En WaitReqA: Lets the WaitReq_L | |
89 | * signal from port A of the SCC appear | |
90 | * on the PA7 input pin (CHRP). Output. | |
91 | * [MkLinux] "Drive Select" | |
92 | * (with 0x20 being 'disk head select') | |
93 | */ | |
94 | #define VIA1A_vSync 0x08 /* | |
95 | * [CHRP] Sync Modem: modem clock select: | |
96 | * 1: select the external serial clock to | |
97 | * drive the SCC's /RTxCA pin. | |
98 | * 0: Select the 3.6864MHz clock to drive | |
99 | * the SCC cell. | |
100 | * [Macintosh Family] Correct on all but IIfx | |
101 | */ | |
102 | ||
103 | /* | |
104 | * Macintosh Family Hardware sez: bits 0-2 of VIA1A are volume control | |
105 | * on Macs which had the PWM sound hardware. Reserved on newer models. | |
106 | * On IIci,IIfx, bits 1-2 are the rest of the CPU ID: | |
107 | * bit 2: 1=IIci, 0=IIfx | |
108 | * bit 1: 1 on both IIci and IIfx. | |
109 | * MkLinux sez bit 0 is 'burnin flag' in this case. | |
110 | * CHRP sez: VIA1A bits 0-2 and 5 are 'unused': if programmed as | |
111 | * inputs, these bits will read 0. | |
112 | */ | |
113 | #define VIA1A_vVolume 0x07 /* Audio volume mask for PWM */ | |
114 | #define VIA1A_CPUID0 0x02 /* CPU id bit 0 on RBV, others */ | |
115 | #define VIA1A_CPUID1 0x04 /* CPU id bit 0 on RBV, others */ | |
116 | #define VIA1A_CPUID2 0x10 /* CPU id bit 0 on RBV, others */ | |
117 | #define VIA1A_CPUID3 0x40 /* CPU id bit 0 on RBV, others */ | |
0f03047c MCA |
118 | #define VIA1A_CPUID_MASK (VIA1A_CPUID0 | VIA1A_CPUID1 | \ |
119 | VIA1A_CPUID2 | VIA1A_CPUID3) | |
120 | #define VIA1A_CPUID_Q800 (VIA1A_CPUID0 | VIA1A_CPUID2) | |
6dca62a0 LV |
121 | |
122 | /* | |
123 | * Info on VIA1B is from Macintosh Family Hardware & MkLinux. | |
124 | * CHRP offers no info. | |
125 | */ | |
126 | #define VIA1B_vSound 0x80 /* | |
127 | * Sound enable (for compatibility with | |
128 | * PWM hardware) 0=enabled. | |
129 | * Also, on IIci w/parity, shows parity error | |
130 | * 0=error, 1=OK. | |
131 | */ | |
132 | #define VIA1B_vMystery 0x40 /* | |
133 | * On IIci, parity enable. 0=enabled,1=disabled | |
134 | * On SE/30, vertical sync interrupt enable. | |
135 | * 0=enabled. This vSync interrupt shows up | |
136 | * as a slot $E interrupt. | |
e976459b MCA |
137 | * On Quadra 800 this bit toggles A/UX mode which |
138 | * configures the glue logic to deliver some IRQs | |
139 | * at different levels compared to a classic | |
140 | * Mac. | |
6dca62a0 LV |
141 | */ |
142 | #define VIA1B_vADBS2 0x20 /* ADB state input bit 1 (unused on IIfx) */ | |
143 | #define VIA1B_vADBS1 0x10 /* ADB state input bit 0 (unused on IIfx) */ | |
144 | #define VIA1B_vADBInt 0x08 /* ADB interrupt 0=interrupt (unused on IIfx)*/ | |
145 | #define VIA1B_vRTCEnb 0x04 /* Enable Real time clock. 0=enabled. */ | |
146 | #define VIA1B_vRTCClk 0x02 /* Real time clock serial-clock line. */ | |
147 | #define VIA1B_vRTCData 0x01 /* Real time clock serial-data line. */ | |
148 | ||
149 | /* | |
150 | * VIA2 A register is the interrupt lines raised off the nubus | |
151 | * slots. | |
152 | * The below info is from 'Macintosh Family Hardware.' | |
153 | * MkLinux calls the 'IIci internal video IRQ' below the 'RBV slot 0 irq.' | |
154 | * It also notes that the slot $9 IRQ is the 'Ethernet IRQ' and | |
155 | * defines the 'Video IRQ' as 0x40 for the 'EVR' VIA work-alike. | |
156 | * Perhaps OSS uses vRAM1 and vRAM2 for ADB. | |
157 | */ | |
158 | ||
159 | #define VIA2A_vRAM1 0x80 /* RAM size bit 1 (IIci: reserved) */ | |
160 | #define VIA2A_vRAM0 0x40 /* RAM size bit 0 (IIci: internal video IRQ) */ | |
161 | #define VIA2A_vIRQE 0x20 /* IRQ from slot $E */ | |
162 | #define VIA2A_vIRQD 0x10 /* IRQ from slot $D */ | |
163 | #define VIA2A_vIRQC 0x08 /* IRQ from slot $C */ | |
164 | #define VIA2A_vIRQB 0x04 /* IRQ from slot $B */ | |
165 | #define VIA2A_vIRQA 0x02 /* IRQ from slot $A */ | |
166 | #define VIA2A_vIRQ9 0x01 /* IRQ from slot $9 */ | |
167 | ||
168 | /* | |
169 | * RAM size bits decoded as follows: | |
170 | * bit1 bit0 size of ICs in bank A | |
171 | * 0 0 256 kbit | |
172 | * 0 1 1 Mbit | |
173 | * 1 0 4 Mbit | |
174 | * 1 1 16 Mbit | |
175 | */ | |
176 | ||
177 | /* | |
178 | * Register B has the fun stuff in it | |
179 | */ | |
180 | ||
181 | #define VIA2B_vVBL 0x80 /* | |
182 | * VBL output to VIA1 (60.15Hz) driven by | |
183 | * timer T1. | |
184 | * on IIci, parity test: 0=test mode. | |
185 | * [MkLinux] RBV_PARODD: 1=odd,0=even. | |
186 | */ | |
187 | #define VIA2B_vSndJck 0x40 /* | |
188 | * External sound jack status. | |
189 | * 0=plug is inserted. On SE/30, always 0 | |
190 | */ | |
191 | #define VIA2B_vTfr0 0x20 /* Transfer mode bit 0 ack from NuBus */ | |
192 | #define VIA2B_vTfr1 0x10 /* Transfer mode bit 1 ack from NuBus */ | |
193 | #define VIA2B_vMode32 0x08 /* | |
194 | * 24/32bit switch - doubles as cache flush | |
195 | * on II, AMU/PMMU control. | |
196 | * if AMU, 0=24bit to 32bit translation | |
197 | * if PMMU, 1=PMMU is accessing page table. | |
198 | * on SE/30 tied low. | |
199 | * on IIx,IIcx,IIfx, unused. | |
200 | * on IIci/RBV, cache control. 0=flush cache. | |
201 | */ | |
202 | #define VIA2B_vPower 0x04 /* | |
203 | * Power off, 0=shut off power. | |
204 | * on SE/30 this signal sent to PDS card. | |
205 | */ | |
206 | #define VIA2B_vBusLk 0x02 /* | |
207 | * Lock NuBus transactions, 0=locked. | |
208 | * on SE/30 sent to PDS card. | |
209 | */ | |
210 | #define VIA2B_vCDis 0x01 /* | |
211 | * Cache control. On IIci, 1=disable cache card | |
212 | * on others, 0=disable processor's instruction | |
213 | * and data caches. | |
214 | */ | |
215 | ||
216 | /* interrupt flags */ | |
217 | ||
218 | #define IRQ_SET 0x80 | |
219 | ||
220 | /* common */ | |
221 | ||
222 | #define VIA_IRQ_TIMER1 0x40 | |
223 | #define VIA_IRQ_TIMER2 0x20 | |
224 | ||
225 | /* | |
226 | * Apple sez: http://developer.apple.com/technotes/ov/ov_04.html | |
227 | * Another example of a valid function that has no ROM support is the use | |
228 | * of the alternate video page for page-flipping animation. Since there | |
229 | * is no ROM call to flip pages, it is necessary to go play with the | |
230 | * right bit in the VIA chip (6522 Versatile Interface Adapter). | |
231 | * [CSA: don't know which one this is, but it's one of 'em!] | |
232 | */ | |
233 | ||
234 | /* | |
235 | * 6522 registers - see databook. | |
236 | * CSA: Assignments for VIA1 confirmed from CHRP spec. | |
237 | */ | |
238 | ||
239 | /* partial address decode. 0xYYXX : XX part for RBV, YY part for VIA */ | |
240 | /* Note: 15 VIA regs, 8 RBV regs */ | |
241 | ||
242 | #define vBufB 0x0000 /* [VIA/RBV] Register B */ | |
243 | #define vBufAH 0x0200 /* [VIA only] Buffer A, with handshake. DON'T USE! */ | |
244 | #define vDirB 0x0400 /* [VIA only] Data Direction Register B. */ | |
245 | #define vDirA 0x0600 /* [VIA only] Data Direction Register A. */ | |
246 | #define vT1CL 0x0800 /* [VIA only] Timer one counter low. */ | |
247 | #define vT1CH 0x0a00 /* [VIA only] Timer one counter high. */ | |
248 | #define vT1LL 0x0c00 /* [VIA only] Timer one latches low. */ | |
249 | #define vT1LH 0x0e00 /* [VIA only] Timer one latches high. */ | |
250 | #define vT2CL 0x1000 /* [VIA only] Timer two counter low. */ | |
251 | #define vT2CH 0x1200 /* [VIA only] Timer two counter high. */ | |
252 | #define vSR 0x1400 /* [VIA only] Shift register. */ | |
9b4b4e51 | 253 | #define vACR 0x1600 /* [VIA only] Auxiliary control register. */ |
6dca62a0 LV |
254 | #define vPCR 0x1800 /* [VIA only] Peripheral control register. */ |
255 | /* | |
256 | * CHRP sez never ever to *write* this. | |
257 | * Mac family says never to *change* this. | |
258 | * In fact we need to initialize it once at start. | |
259 | */ | |
260 | #define vIFR 0x1a00 /* [VIA/RBV] Interrupt flag register. */ | |
261 | #define vIER 0x1c00 /* [VIA/RBV] Interrupt enable register. */ | |
262 | #define vBufA 0x1e00 /* [VIA/RBV] register A (no handshake) */ | |
263 | ||
264 | /* from linux 2.6 drivers/macintosh/via-macii.c */ | |
265 | ||
266 | /* Bits in ACR */ | |
267 | ||
268 | #define VIA1ACR_vShiftCtrl 0x1c /* Shift register control bits */ | |
269 | #define VIA1ACR_vShiftExtClk 0x0c /* Shift on external clock */ | |
270 | #define VIA1ACR_vShiftOut 0x10 /* Shift out if 1 */ | |
271 | ||
272 | /* | |
273 | * Apple Macintosh Family Hardware Refenece | |
274 | * Table 19-10 ADB transaction states | |
275 | */ | |
276 | ||
87a34e2a LV |
277 | #define ADB_STATE_NEW 0 |
278 | #define ADB_STATE_EVEN 1 | |
279 | #define ADB_STATE_ODD 2 | |
280 | #define ADB_STATE_IDLE 3 | |
281 | ||
6dca62a0 LV |
282 | #define VIA1B_vADB_StateMask (VIA1B_vADBS1 | VIA1B_vADBS2) |
283 | #define VIA1B_vADB_StateShift 4 | |
284 | ||
285 | #define VIA_TIMER_FREQ (783360) | |
87a34e2a | 286 | #define VIA_ADB_POLL_FREQ 50 /* XXX: not real */ |
6dca62a0 | 287 | |
82ff856f MCA |
288 | /* |
289 | * Guide to the Macintosh Family Hardware ch. 12 "Displays" p. 401 gives the | |
290 | * precise 60Hz interrupt frequency as ~60.15Hz with a period of 16625.8 us | |
291 | */ | |
292 | #define VIA_60HZ_TIMER_PERIOD_NS 16625800 | |
293 | ||
6dca62a0 LV |
294 | /* VIA returns time offset from Jan 1, 1904, not 1970 */ |
295 | #define RTC_OFFSET 2082844800 | |
296 | ||
b2619c15 LV |
297 | enum { |
298 | REG_0, | |
299 | REG_1, | |
300 | REG_2, | |
301 | REG_3, | |
302 | REG_TEST, | |
303 | REG_WPROTECT, | |
304 | REG_PRAM_ADDR, | |
305 | REG_PRAM_ADDR_LAST = REG_PRAM_ADDR + 19, | |
306 | REG_PRAM_SECT, | |
307 | REG_PRAM_SECT_LAST = REG_PRAM_SECT + 7, | |
308 | REG_INVALID, | |
309 | REG_EMPTY = 0xff, | |
310 | }; | |
311 | ||
4c8f4ab4 | 312 | static void via1_sixty_hz_update(MOS6522Q800VIA1State *v1s) |
6dca62a0 | 313 | { |
6dca62a0 | 314 | /* 60 Hz irq */ |
82ff856f MCA |
315 | v1s->next_sixty_hz = (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + |
316 | VIA_60HZ_TIMER_PERIOD_NS) / | |
317 | VIA_60HZ_TIMER_PERIOD_NS * VIA_60HZ_TIMER_PERIOD_NS; | |
30ca7edd | 318 | timer_mod(v1s->sixty_hz_timer, v1s->next_sixty_hz); |
6dca62a0 LV |
319 | } |
320 | ||
321 | static void via1_one_second_update(MOS6522Q800VIA1State *v1s) | |
322 | { | |
6dca62a0 LV |
323 | v1s->next_second = (qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 1000) / |
324 | 1000 * 1000; | |
30ca7edd | 325 | timer_mod(v1s->one_second_timer, v1s->next_second); |
6dca62a0 LV |
326 | } |
327 | ||
4c8f4ab4 | 328 | static void via1_sixty_hz(void *opaque) |
6dca62a0 LV |
329 | { |
330 | MOS6522Q800VIA1State *v1s = opaque; | |
331 | MOS6522State *s = MOS6522(v1s); | |
ebe5bca2 | 332 | qemu_irq irq = qdev_get_gpio_in(DEVICE(s), VIA1_IRQ_60HZ_BIT); |
6dca62a0 | 333 | |
b793b4ef MCA |
334 | /* Negative edge trigger */ |
335 | qemu_irq_lower(irq); | |
336 | qemu_irq_raise(irq); | |
6dca62a0 | 337 | |
4c8f4ab4 | 338 | via1_sixty_hz_update(v1s); |
6dca62a0 LV |
339 | } |
340 | ||
341 | static void via1_one_second(void *opaque) | |
342 | { | |
343 | MOS6522Q800VIA1State *v1s = opaque; | |
344 | MOS6522State *s = MOS6522(v1s); | |
ebe5bca2 | 345 | qemu_irq irq = qdev_get_gpio_in(DEVICE(s), VIA1_IRQ_ONE_SECOND_BIT); |
6dca62a0 | 346 | |
b793b4ef MCA |
347 | /* Negative edge trigger */ |
348 | qemu_irq_lower(irq); | |
349 | qemu_irq_raise(irq); | |
6dca62a0 LV |
350 | |
351 | via1_one_second_update(v1s); | |
352 | } | |
353 | ||
eb064db9 | 354 | |
8064d7bb | 355 | static void pram_update(MOS6522Q800VIA1State *v1s) |
eb064db9 | 356 | { |
8064d7bb | 357 | if (v1s->blk) { |
a9262f55 | 358 | if (blk_pwrite(v1s->blk, 0, sizeof(v1s->PRAM), v1s->PRAM, 0) < 0) { |
80aab795 LV |
359 | qemu_log("pram_update: cannot write to file\n"); |
360 | } | |
eb064db9 LV |
361 | } |
362 | } | |
363 | ||
b2619c15 LV |
364 | /* |
365 | * RTC Commands | |
366 | * | |
367 | * Command byte Register addressed by the command | |
368 | * | |
53200905 MCA |
369 | * z00x0001 Seconds register 0 (lowest-order byte) |
370 | * z00x0101 Seconds register 1 | |
371 | * z00x1001 Seconds register 2 | |
372 | * z00x1101 Seconds register 3 (highest-order byte) | |
b2619c15 LV |
373 | * 00110001 Test register (write-only) |
374 | * 00110101 Write-Protect Register (write-only) | |
375 | * z010aa01 RAM address 100aa ($10-$13) (first 20 bytes only) | |
376 | * z1aaaa01 RAM address 0aaaa ($00-$0F) (first 20 bytes only) | |
377 | * z0111aaa Extended memory designator and sector number | |
378 | * | |
379 | * For a read request, z=1, for a write z=0 | |
53200905 | 380 | * The letter x indicates don't care |
b2619c15 LV |
381 | * The letter a indicates bits whose value depend on what parameter |
382 | * RAM byte you want to address | |
383 | */ | |
384 | static int via1_rtc_compact_cmd(uint8_t value) | |
385 | { | |
386 | uint8_t read = value & 0x80; | |
387 | ||
388 | value &= 0x7f; | |
389 | ||
390 | /* the last 2 bits of a command byte must always be 0b01 ... */ | |
391 | if ((value & 0x78) == 0x38) { | |
392 | /* except for the extended memory designator */ | |
393 | return read | (REG_PRAM_SECT + (value & 0x07)); | |
394 | } | |
395 | if ((value & 0x03) == 0x01) { | |
396 | value >>= 2; | |
53200905 | 397 | if ((value & 0x18) == 0) { |
b2619c15 LV |
398 | /* seconds registers */ |
399 | return read | (REG_0 + (value & 0x03)); | |
400 | } else if ((value == 0x0c) && !read) { | |
401 | return REG_TEST; | |
402 | } else if ((value == 0x0d) && !read) { | |
403 | return REG_WPROTECT; | |
404 | } else if ((value & 0x1c) == 0x08) { | |
405 | /* RAM address 0x10 to 0x13 */ | |
406 | return read | (REG_PRAM_ADDR + 0x10 + (value & 0x03)); | |
ce47d531 | 407 | } else if ((value & 0x10) == 0x10) { |
b2619c15 LV |
408 | /* RAM address 0x00 to 0x0f */ |
409 | return read | (REG_PRAM_ADDR + (value & 0x0f)); | |
410 | } | |
411 | } | |
412 | return REG_INVALID; | |
413 | } | |
414 | ||
741258b0 | 415 | static void via1_rtc_update(MOS6522Q800VIA1State *v1s) |
6dca62a0 | 416 | { |
6dca62a0 | 417 | MOS6522State *s = MOS6522(v1s); |
b2619c15 LV |
418 | int cmd, sector, addr; |
419 | uint32_t time; | |
6dca62a0 LV |
420 | |
421 | if (s->b & VIA1B_vRTCEnb) { | |
422 | return; | |
423 | } | |
424 | ||
425 | if (s->dirb & VIA1B_vRTCData) { | |
426 | /* send bits to the RTC */ | |
427 | if (!(v1s->last_b & VIA1B_vRTCClk) && (s->b & VIA1B_vRTCClk)) { | |
741258b0 MCA |
428 | v1s->data_out <<= 1; |
429 | v1s->data_out |= s->b & VIA1B_vRTCData; | |
430 | v1s->data_out_cnt++; | |
6dca62a0 | 431 | } |
741258b0 | 432 | trace_via1_rtc_update_data_out(v1s->data_out_cnt, v1s->data_out); |
6dca62a0 | 433 | } else { |
741258b0 | 434 | trace_via1_rtc_update_data_in(v1s->data_in_cnt, v1s->data_in); |
6dca62a0 LV |
435 | /* receive bits from the RTC */ |
436 | if ((v1s->last_b & VIA1B_vRTCClk) && | |
437 | !(s->b & VIA1B_vRTCClk) && | |
741258b0 | 438 | v1s->data_in_cnt) { |
6dca62a0 | 439 | s->b = (s->b & ~VIA1B_vRTCData) | |
741258b0 MCA |
440 | ((v1s->data_in >> 7) & VIA1B_vRTCData); |
441 | v1s->data_in <<= 1; | |
442 | v1s->data_in_cnt--; | |
6dca62a0 | 443 | } |
b2619c15 | 444 | return; |
6dca62a0 LV |
445 | } |
446 | ||
741258b0 | 447 | if (v1s->data_out_cnt != 8) { |
b2619c15 LV |
448 | return; |
449 | } | |
450 | ||
741258b0 | 451 | v1s->data_out_cnt = 0; |
b2619c15 | 452 | |
741258b0 | 453 | trace_via1_rtc_internal_status(v1s->cmd, v1s->alt, v1s->data_out); |
b2619c15 | 454 | /* first byte: it's a command */ |
741258b0 | 455 | if (v1s->cmd == REG_EMPTY) { |
b2619c15 | 456 | |
741258b0 | 457 | cmd = via1_rtc_compact_cmd(v1s->data_out); |
b2619c15 LV |
458 | trace_via1_rtc_internal_cmd(cmd); |
459 | ||
460 | if (cmd == REG_INVALID) { | |
741258b0 | 461 | trace_via1_rtc_cmd_invalid(v1s->data_out); |
b2619c15 LV |
462 | return; |
463 | } | |
464 | ||
465 | if (cmd & 0x80) { /* this is a read command */ | |
466 | switch (cmd & 0x7f) { | |
467 | case REG_0...REG_3: /* seconds registers */ | |
468 | /* | |
469 | * register 0 is lowest-order byte | |
470 | * register 3 is highest-order byte | |
471 | */ | |
472 | ||
741258b0 | 473 | time = v1s->tick_offset + (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) |
b2619c15 LV |
474 | / NANOSECONDS_PER_SECOND); |
475 | trace_via1_rtc_internal_time(time); | |
741258b0 MCA |
476 | v1s->data_in = (time >> ((cmd & 0x03) << 3)) & 0xff; |
477 | v1s->data_in_cnt = 8; | |
b2619c15 | 478 | trace_via1_rtc_cmd_seconds_read((cmd & 0x7f) - REG_0, |
741258b0 | 479 | v1s->data_in); |
b2619c15 LV |
480 | break; |
481 | case REG_PRAM_ADDR...REG_PRAM_ADDR_LAST: | |
482 | /* PRAM address 0x00 -> 0x13 */ | |
741258b0 MCA |
483 | v1s->data_in = v1s->PRAM[(cmd & 0x7f) - REG_PRAM_ADDR]; |
484 | v1s->data_in_cnt = 8; | |
b2619c15 | 485 | trace_via1_rtc_cmd_pram_read((cmd & 0x7f) - REG_PRAM_ADDR, |
741258b0 | 486 | v1s->data_in); |
b2619c15 LV |
487 | break; |
488 | case REG_PRAM_SECT...REG_PRAM_SECT_LAST: | |
489 | /* | |
490 | * extended memory designator and sector number | |
491 | * the only two-byte read command | |
492 | */ | |
493 | trace_via1_rtc_internal_set_cmd(cmd); | |
741258b0 | 494 | v1s->cmd = cmd; |
b2619c15 LV |
495 | break; |
496 | default: | |
497 | g_assert_not_reached(); | |
498 | break; | |
6dca62a0 | 499 | } |
b2619c15 LV |
500 | return; |
501 | } | |
502 | ||
503 | /* this is a write command, needs a parameter */ | |
741258b0 | 504 | if (cmd == REG_WPROTECT || !v1s->wprotect) { |
b2619c15 | 505 | trace_via1_rtc_internal_set_cmd(cmd); |
741258b0 | 506 | v1s->cmd = cmd; |
6dca62a0 | 507 | } else { |
b2619c15 LV |
508 | trace_via1_rtc_internal_ignore_cmd(cmd); |
509 | } | |
510 | return; | |
511 | } | |
512 | ||
513 | /* second byte: it's a parameter */ | |
741258b0 MCA |
514 | if (v1s->alt == REG_EMPTY) { |
515 | switch (v1s->cmd & 0x7f) { | |
b2619c15 LV |
516 | case REG_0...REG_3: /* seconds register */ |
517 | /* FIXME */ | |
741258b0 MCA |
518 | trace_via1_rtc_cmd_seconds_write(v1s->cmd - REG_0, v1s->data_out); |
519 | v1s->cmd = REG_EMPTY; | |
b2619c15 LV |
520 | break; |
521 | case REG_TEST: | |
522 | /* device control: nothing to do */ | |
741258b0 MCA |
523 | trace_via1_rtc_cmd_test_write(v1s->data_out); |
524 | v1s->cmd = REG_EMPTY; | |
b2619c15 LV |
525 | break; |
526 | case REG_WPROTECT: | |
527 | /* Write Protect register */ | |
741258b0 MCA |
528 | trace_via1_rtc_cmd_wprotect_write(v1s->data_out); |
529 | v1s->wprotect = !!(v1s->data_out & 0x80); | |
530 | v1s->cmd = REG_EMPTY; | |
b2619c15 LV |
531 | break; |
532 | case REG_PRAM_ADDR...REG_PRAM_ADDR_LAST: | |
533 | /* PRAM address 0x00 -> 0x13 */ | |
741258b0 MCA |
534 | trace_via1_rtc_cmd_pram_write(v1s->cmd - REG_PRAM_ADDR, |
535 | v1s->data_out); | |
536 | v1s->PRAM[v1s->cmd - REG_PRAM_ADDR] = v1s->data_out; | |
8064d7bb | 537 | pram_update(v1s); |
741258b0 | 538 | v1s->cmd = REG_EMPTY; |
b2619c15 LV |
539 | break; |
540 | case REG_PRAM_SECT...REG_PRAM_SECT_LAST: | |
741258b0 MCA |
541 | addr = (v1s->data_out >> 2) & 0x1f; |
542 | sector = (v1s->cmd & 0x7f) - REG_PRAM_SECT; | |
543 | if (v1s->cmd & 0x80) { | |
b2619c15 | 544 | /* it's a read */ |
741258b0 MCA |
545 | v1s->data_in = v1s->PRAM[sector * 32 + addr]; |
546 | v1s->data_in_cnt = 8; | |
b2619c15 LV |
547 | trace_via1_rtc_cmd_pram_sect_read(sector, addr, |
548 | sector * 32 + addr, | |
741258b0 MCA |
549 | v1s->data_in); |
550 | v1s->cmd = REG_EMPTY; | |
b2619c15 LV |
551 | } else { |
552 | /* it's a write, we need one more parameter */ | |
553 | trace_via1_rtc_internal_set_alt(addr, sector, addr); | |
741258b0 | 554 | v1s->alt = addr; |
6dca62a0 | 555 | } |
b2619c15 LV |
556 | break; |
557 | default: | |
558 | g_assert_not_reached(); | |
559 | break; | |
6dca62a0 | 560 | } |
b2619c15 | 561 | return; |
6dca62a0 | 562 | } |
b2619c15 LV |
563 | |
564 | /* third byte: it's the data of a REG_PRAM_SECT write */ | |
741258b0 MCA |
565 | g_assert(REG_PRAM_SECT <= v1s->cmd && v1s->cmd <= REG_PRAM_SECT_LAST); |
566 | sector = v1s->cmd - REG_PRAM_SECT; | |
567 | v1s->PRAM[sector * 32 + v1s->alt] = v1s->data_out; | |
8064d7bb | 568 | pram_update(v1s); |
741258b0 MCA |
569 | trace_via1_rtc_cmd_pram_sect_write(sector, v1s->alt, sector * 32 + v1s->alt, |
570 | v1s->data_out); | |
571 | v1s->alt = REG_EMPTY; | |
572 | v1s->cmd = REG_EMPTY; | |
6dca62a0 LV |
573 | } |
574 | ||
975fcedd | 575 | static void adb_via_poll(void *opaque) |
87a34e2a | 576 | { |
5f083d42 | 577 | MOS6522Q800VIA1State *v1s = MOS6522_Q800_VIA1(opaque); |
975fcedd | 578 | MOS6522State *s = MOS6522(v1s); |
5f083d42 | 579 | ADBBusState *adb_bus = &v1s->adb_bus; |
975fcedd MCA |
580 | uint8_t obuf[9]; |
581 | uint8_t *data = &s->sr; | |
582 | int olen; | |
f3d61457 | 583 | |
975fcedd MCA |
584 | /* |
585 | * Setting vADBInt below indicates that an autopoll reply has been | |
586 | * received, however we must block autopoll until the point where | |
587 | * the entire reply has been read back to the host | |
588 | */ | |
913f47ef | 589 | adb_autopoll_block(adb_bus); |
87a34e2a | 590 | |
5f083d42 | 591 | if (v1s->adb_data_in_size > 0 && v1s->adb_data_in_index == 0) { |
a67ffaf0 MCA |
592 | /* |
593 | * For older Linux kernels that switch to IDLE mode after sending the | |
594 | * ADB command, detect if there is an existing response and return that | |
a07d9df0 | 595 | * as a "fake" autopoll reply or bus timeout accordingly |
a67ffaf0 | 596 | */ |
5f083d42 MCA |
597 | *data = v1s->adb_data_out[0]; |
598 | olen = v1s->adb_data_in_size; | |
975fcedd MCA |
599 | |
600 | s->b &= ~VIA1B_vADBInt; | |
5f083d42 | 601 | qemu_irq_raise(v1s->adb_data_ready); |
975fcedd | 602 | } else { |
a67ffaf0 MCA |
603 | /* |
604 | * Otherwise poll as normal | |
605 | */ | |
5f083d42 MCA |
606 | v1s->adb_data_in_index = 0; |
607 | v1s->adb_data_out_index = 0; | |
a67ffaf0 MCA |
608 | olen = adb_poll(adb_bus, obuf, adb_bus->autopoll_mask); |
609 | ||
610 | if (olen > 0) { | |
611 | /* Autopoll response */ | |
612 | *data = obuf[0]; | |
613 | olen--; | |
5f083d42 MCA |
614 | memcpy(v1s->adb_data_in, &obuf[1], olen); |
615 | v1s->adb_data_in_size = olen; | |
975fcedd | 616 | |
975fcedd | 617 | s->b &= ~VIA1B_vADBInt; |
5f083d42 | 618 | qemu_irq_raise(v1s->adb_data_ready); |
a67ffaf0 | 619 | } else { |
5f083d42 | 620 | *data = v1s->adb_autopoll_cmd; |
975fcedd MCA |
621 | obuf[0] = 0xff; |
622 | obuf[1] = 0xff; | |
623 | olen = 2; | |
87a34e2a | 624 | |
5f083d42 MCA |
625 | memcpy(v1s->adb_data_in, obuf, olen); |
626 | v1s->adb_data_in_size = olen; | |
87a34e2a | 627 | |
a67ffaf0 | 628 | s->b &= ~VIA1B_vADBInt; |
5f083d42 | 629 | qemu_irq_raise(v1s->adb_data_ready); |
975fcedd | 630 | } |
87a34e2a LV |
631 | } |
632 | ||
975fcedd | 633 | trace_via1_adb_poll(*data, (s->b & VIA1B_vADBInt) ? "+" : "-", |
5f083d42 | 634 | adb_bus->status, v1s->adb_data_in_index, olen); |
87a34e2a LV |
635 | } |
636 | ||
975fcedd | 637 | static int adb_via_send_len(uint8_t data) |
87a34e2a | 638 | { |
975fcedd MCA |
639 | /* Determine the send length from the given ADB command */ |
640 | uint8_t cmd = data & 0xc; | |
641 | uint8_t reg = data & 0x3; | |
642 | ||
643 | switch (cmd) { | |
644 | case 0x8: | |
645 | /* Listen command */ | |
646 | switch (reg) { | |
647 | case 2: | |
648 | /* Register 2 is only used for the keyboard */ | |
649 | return 3; | |
650 | case 3: | |
651 | /* | |
652 | * Fortunately our devices only implement writes | |
653 | * to register 3 which is fixed at 2 bytes | |
654 | */ | |
655 | return 3; | |
656 | default: | |
657 | qemu_log_mask(LOG_UNIMP, "ADB unknown length for register %d\n", | |
658 | reg); | |
659 | return 1; | |
87a34e2a | 660 | } |
975fcedd MCA |
661 | default: |
662 | /* Talk, BusReset */ | |
663 | return 1; | |
87a34e2a | 664 | } |
87a34e2a LV |
665 | } |
666 | ||
5f083d42 | 667 | static void adb_via_send(MOS6522Q800VIA1State *v1s, int state, uint8_t data) |
87a34e2a | 668 | { |
975fcedd | 669 | MOS6522State *ms = MOS6522(v1s); |
5f083d42 | 670 | ADBBusState *adb_bus = &v1s->adb_bus; |
975fcedd MCA |
671 | uint16_t autopoll_mask; |
672 | ||
87a34e2a LV |
673 | switch (state) { |
674 | case ADB_STATE_NEW: | |
975fcedd MCA |
675 | /* |
676 | * Command byte: vADBInt tells host autopoll data already present | |
677 | * in VIA shift register and ADB transceiver | |
678 | */ | |
679 | adb_autopoll_block(adb_bus); | |
680 | ||
681 | if (adb_bus->status & ADB_STATUS_POLLREPLY) { | |
682 | /* Tell the host the existing data is from autopoll */ | |
683 | ms->b &= ~VIA1B_vADBInt; | |
684 | } else { | |
685 | ms->b |= VIA1B_vADBInt; | |
5f083d42 MCA |
686 | v1s->adb_data_out_index = 0; |
687 | v1s->adb_data_out[v1s->adb_data_out_index++] = data; | |
87a34e2a LV |
688 | } |
689 | ||
975fcedd | 690 | trace_via1_adb_send(" NEW", data, (ms->b & VIA1B_vADBInt) ? "+" : "-"); |
5f083d42 | 691 | qemu_irq_raise(v1s->adb_data_ready); |
87a34e2a LV |
692 | break; |
693 | ||
975fcedd | 694 | case ADB_STATE_EVEN: |
87a34e2a | 695 | case ADB_STATE_ODD: |
975fcedd | 696 | ms->b |= VIA1B_vADBInt; |
5f083d42 | 697 | v1s->adb_data_out[v1s->adb_data_out_index++] = data; |
87a34e2a | 698 | |
975fcedd MCA |
699 | trace_via1_adb_send(state == ADB_STATE_EVEN ? "EVEN" : " ODD", |
700 | data, (ms->b & VIA1B_vADBInt) ? "+" : "-"); | |
5f083d42 | 701 | qemu_irq_raise(v1s->adb_data_ready); |
87a34e2a LV |
702 | break; |
703 | ||
704 | case ADB_STATE_IDLE: | |
975fcedd MCA |
705 | return; |
706 | } | |
87a34e2a | 707 | |
975fcedd | 708 | /* If the command is complete, execute it */ |
5f083d42 MCA |
709 | if (v1s->adb_data_out_index == adb_via_send_len(v1s->adb_data_out[0])) { |
710 | v1s->adb_data_in_size = adb_request(adb_bus, v1s->adb_data_in, | |
711 | v1s->adb_data_out, | |
712 | v1s->adb_data_out_index); | |
713 | v1s->adb_data_in_index = 0; | |
87a34e2a | 714 | |
975fcedd MCA |
715 | if (adb_bus->status & ADB_STATUS_BUSTIMEOUT) { |
716 | /* | |
717 | * Bus timeout (but allow first EVEN and ODD byte to indicate | |
718 | * timeout via vADBInt and SRQ status) | |
719 | */ | |
5f083d42 MCA |
720 | v1s->adb_data_in[0] = 0xff; |
721 | v1s->adb_data_in[1] = 0xff; | |
722 | v1s->adb_data_in_size = 2; | |
87a34e2a LV |
723 | } |
724 | ||
975fcedd MCA |
725 | /* |
726 | * If last command is TALK, store it for use by autopoll and adjust | |
727 | * the autopoll mask accordingly | |
728 | */ | |
5f083d42 MCA |
729 | if ((v1s->adb_data_out[0] & 0xc) == 0xc) { |
730 | v1s->adb_autopoll_cmd = v1s->adb_data_out[0]; | |
87a34e2a | 731 | |
5f083d42 | 732 | autopoll_mask = 1 << (v1s->adb_autopoll_cmd >> 4); |
975fcedd MCA |
733 | adb_set_autopoll_mask(adb_bus, autopoll_mask); |
734 | } | |
87a34e2a | 735 | } |
87a34e2a LV |
736 | } |
737 | ||
5f083d42 | 738 | static void adb_via_receive(MOS6522Q800VIA1State *v1s, int state, uint8_t *data) |
87a34e2a | 739 | { |
975fcedd | 740 | MOS6522State *ms = MOS6522(v1s); |
5f083d42 | 741 | ADBBusState *adb_bus = &v1s->adb_bus; |
975fcedd | 742 | uint16_t pending; |
87a34e2a | 743 | |
975fcedd MCA |
744 | switch (state) { |
745 | case ADB_STATE_NEW: | |
746 | ms->b |= VIA1B_vADBInt; | |
747 | return; | |
87a34e2a | 748 | |
975fcedd | 749 | case ADB_STATE_IDLE: |
a67ffaf0 MCA |
750 | ms->b |= VIA1B_vADBInt; |
751 | adb_autopoll_unblock(adb_bus); | |
975fcedd MCA |
752 | |
753 | trace_via1_adb_receive("IDLE", *data, | |
754 | (ms->b & VIA1B_vADBInt) ? "+" : "-", adb_bus->status, | |
5f083d42 | 755 | v1s->adb_data_in_index, v1s->adb_data_in_size); |
975fcedd MCA |
756 | |
757 | break; | |
758 | ||
759 | case ADB_STATE_EVEN: | |
760 | case ADB_STATE_ODD: | |
5f083d42 | 761 | switch (v1s->adb_data_in_index) { |
975fcedd MCA |
762 | case 0: |
763 | /* First EVEN byte: vADBInt indicates bus timeout */ | |
5f083d42 | 764 | *data = v1s->adb_data_in[v1s->adb_data_in_index]; |
975fcedd MCA |
765 | if (adb_bus->status & ADB_STATUS_BUSTIMEOUT) { |
766 | ms->b &= ~VIA1B_vADBInt; | |
767 | } else { | |
768 | ms->b |= VIA1B_vADBInt; | |
769 | } | |
975fcedd | 770 | |
975fcedd MCA |
771 | trace_via1_adb_receive(state == ADB_STATE_EVEN ? "EVEN" : " ODD", |
772 | *data, (ms->b & VIA1B_vADBInt) ? "+" : "-", | |
5f083d42 MCA |
773 | adb_bus->status, v1s->adb_data_in_index, |
774 | v1s->adb_data_in_size); | |
975fcedd | 775 | |
5f083d42 | 776 | v1s->adb_data_in_index++; |
9d39ec70 MCA |
777 | break; |
778 | ||
779 | case 1: | |
780 | /* First ODD byte: vADBInt indicates SRQ */ | |
5f083d42 MCA |
781 | *data = v1s->adb_data_in[v1s->adb_data_in_index]; |
782 | pending = adb_bus->pending & ~(1 << (v1s->adb_autopoll_cmd >> 4)); | |
975fcedd MCA |
783 | if (pending) { |
784 | ms->b &= ~VIA1B_vADBInt; | |
785 | } else { | |
786 | ms->b |= VIA1B_vADBInt; | |
787 | } | |
9d39ec70 MCA |
788 | |
789 | trace_via1_adb_receive(state == ADB_STATE_EVEN ? "EVEN" : " ODD", | |
790 | *data, (ms->b & VIA1B_vADBInt) ? "+" : "-", | |
5f083d42 MCA |
791 | adb_bus->status, v1s->adb_data_in_index, |
792 | v1s->adb_data_in_size); | |
9d39ec70 | 793 | |
5f083d42 | 794 | v1s->adb_data_in_index++; |
975fcedd MCA |
795 | break; |
796 | ||
797 | default: | |
798 | /* | |
799 | * Otherwise vADBInt indicates end of data. Note that Linux | |
800 | * specifically checks for the sequence 0x0 0xff to confirm the | |
801 | * end of the poll reply, so provide these extra bytes below to | |
802 | * keep it happy | |
803 | */ | |
5f083d42 | 804 | if (v1s->adb_data_in_index < v1s->adb_data_in_size) { |
975fcedd | 805 | /* Next data byte */ |
5f083d42 | 806 | *data = v1s->adb_data_in[v1s->adb_data_in_index]; |
975fcedd | 807 | ms->b |= VIA1B_vADBInt; |
5f083d42 | 808 | } else if (v1s->adb_data_in_index == v1s->adb_data_in_size) { |
975fcedd MCA |
809 | if (adb_bus->status & ADB_STATUS_BUSTIMEOUT) { |
810 | /* Bus timeout (no more data) */ | |
811 | *data = 0xff; | |
812 | } else { | |
813 | /* Return 0x0 after reply */ | |
814 | *data = 0; | |
815 | } | |
975fcedd MCA |
816 | ms->b &= ~VIA1B_vADBInt; |
817 | } else { | |
818 | /* Bus timeout (no more data) */ | |
819 | *data = 0xff; | |
820 | ms->b &= ~VIA1B_vADBInt; | |
821 | adb_bus->status = 0; | |
822 | adb_autopoll_unblock(adb_bus); | |
823 | } | |
9d39ec70 MCA |
824 | |
825 | trace_via1_adb_receive(state == ADB_STATE_EVEN ? "EVEN" : " ODD", | |
826 | *data, (ms->b & VIA1B_vADBInt) ? "+" : "-", | |
5f083d42 MCA |
827 | adb_bus->status, v1s->adb_data_in_index, |
828 | v1s->adb_data_in_size); | |
9d39ec70 | 829 | |
5f083d42 MCA |
830 | if (v1s->adb_data_in_index <= v1s->adb_data_in_size) { |
831 | v1s->adb_data_in_index++; | |
9d39ec70 | 832 | } |
975fcedd | 833 | break; |
87a34e2a | 834 | } |
975fcedd | 835 | |
5f083d42 | 836 | qemu_irq_raise(v1s->adb_data_ready); |
975fcedd | 837 | break; |
87a34e2a LV |
838 | } |
839 | } | |
840 | ||
5f083d42 | 841 | static void via1_adb_update(MOS6522Q800VIA1State *v1s) |
87a34e2a | 842 | { |
87a34e2a | 843 | MOS6522State *s = MOS6522(v1s); |
975fcedd | 844 | int oldstate, state; |
87a34e2a | 845 | |
975fcedd MCA |
846 | oldstate = (v1s->last_b & VIA1B_vADB_StateMask) >> VIA1B_vADB_StateShift; |
847 | state = (s->b & VIA1B_vADB_StateMask) >> VIA1B_vADB_StateShift; | |
848 | ||
849 | if (state != oldstate) { | |
850 | if (s->acr & VIA1ACR_vShiftOut) { | |
851 | /* output mode */ | |
5f083d42 | 852 | adb_via_send(v1s, state, s->sr); |
975fcedd MCA |
853 | } else { |
854 | /* input mode */ | |
5f083d42 | 855 | adb_via_receive(v1s, state, &s->sr); |
87a34e2a LV |
856 | } |
857 | } | |
87a34e2a LV |
858 | } |
859 | ||
291bc180 MCA |
860 | static void via1_auxmode_update(MOS6522Q800VIA1State *v1s) |
861 | { | |
862 | MOS6522State *s = MOS6522(v1s); | |
863 | int oldirq, irq; | |
864 | ||
865 | oldirq = (v1s->last_b & VIA1B_vMystery) ? 1 : 0; | |
866 | irq = (s->b & VIA1B_vMystery) ? 1 : 0; | |
867 | ||
868 | /* Check to see if the A/UX mode bit has changed */ | |
869 | if (irq != oldirq) { | |
870 | trace_via1_auxmode(irq); | |
871 | qemu_set_irq(v1s->auxmode_irq, irq); | |
872 | } | |
873 | } | |
874 | ||
366d2779 MCA |
875 | /* |
876 | * Addresses and real values for TimeDBRA/TimeSCCB to allow timer calibration | |
877 | * to succeed (NOTE: both values have been multiplied by 3 to cope with the | |
878 | * speed of QEMU execution on a modern host | |
879 | */ | |
880 | #define MACOS_TIMEDBRA 0xd00 | |
881 | #define MACOS_TIMESCCB 0xd02 | |
882 | ||
883 | #define MACOS_TIMEDBRA_VALUE (0x2a00 * 3) | |
884 | #define MACOS_TIMESCCB_VALUE (0x079d * 3) | |
885 | ||
886 | static bool via1_is_toolbox_timer_calibrated(void) | |
887 | { | |
888 | /* | |
889 | * Indicate whether the MacOS toolbox has been calibrated by checking | |
890 | * for the value of our magic constants | |
891 | */ | |
892 | uint16_t timedbra = lduw_be_phys(&address_space_memory, MACOS_TIMEDBRA); | |
893 | uint16_t timesccdb = lduw_be_phys(&address_space_memory, MACOS_TIMESCCB); | |
894 | ||
895 | return (timedbra == MACOS_TIMEDBRA_VALUE && | |
896 | timesccdb == MACOS_TIMESCCB_VALUE); | |
897 | } | |
898 | ||
899 | static void via1_timer_calibration_hack(MOS6522Q800VIA1State *v1s, int addr, | |
900 | uint64_t val, int size) | |
901 | { | |
902 | /* | |
903 | * Work around timer calibration to ensure we that we have non-zero and | |
904 | * known good values for TIMEDRBA and TIMESCCDB. | |
905 | * | |
906 | * This works by attempting to detect the reset and calibration sequence | |
907 | * of writes to VIA1 | |
908 | */ | |
909 | int old_timer_hack_state = v1s->timer_hack_state; | |
910 | ||
911 | switch (v1s->timer_hack_state) { | |
912 | case 0: | |
913 | if (addr == VIA_REG_PCR && val == 0x22) { | |
914 | /* VIA_REG_PCR: configure VIA1 edge triggering */ | |
915 | v1s->timer_hack_state = 1; | |
916 | } | |
917 | break; | |
918 | case 1: | |
919 | if (addr == VIA_REG_T2CL && val == 0xc) { | |
920 | /* VIA_REG_T2CL: low byte of 1ms counter */ | |
921 | if (!via1_is_toolbox_timer_calibrated()) { | |
922 | v1s->timer_hack_state = 2; | |
923 | } else { | |
924 | v1s->timer_hack_state = 0; | |
925 | } | |
926 | } | |
927 | break; | |
928 | case 2: | |
929 | if (addr == VIA_REG_T2CH && val == 0x3) { | |
930 | /* | |
931 | * VIA_REG_T2CH: high byte of 1ms counter (very likely at the | |
932 | * start of SETUPTIMEK) | |
933 | */ | |
934 | if (!via1_is_toolbox_timer_calibrated()) { | |
935 | v1s->timer_hack_state = 3; | |
936 | } else { | |
937 | v1s->timer_hack_state = 0; | |
938 | } | |
939 | } | |
940 | break; | |
941 | case 3: | |
942 | if (addr == VIA_REG_IER && val == 0x20) { | |
943 | /* | |
944 | * VIA_REG_IER: update at end of SETUPTIMEK | |
945 | * | |
946 | * Timer calibration has finished: unfortunately the values in | |
947 | * TIMEDBRA (0xd00) and TIMESCCDB (0xd02) are so far out they | |
948 | * cause divide by zero errors. | |
949 | * | |
950 | * Update them with values obtained from a real Q800 but with | |
951 | * a x3 scaling factor which seems to work well | |
952 | */ | |
953 | stw_be_phys(&address_space_memory, MACOS_TIMEDBRA, | |
954 | MACOS_TIMEDBRA_VALUE); | |
955 | stw_be_phys(&address_space_memory, MACOS_TIMESCCB, | |
956 | MACOS_TIMESCCB_VALUE); | |
957 | ||
958 | v1s->timer_hack_state = 4; | |
959 | } | |
960 | break; | |
961 | case 4: | |
962 | /* | |
963 | * This is the normal post-calibration timer state: we should | |
964 | * generally remain here unless we detect the A/UX calibration | |
965 | * loop, or a write to VIA_REG_PCR suggesting a reset | |
966 | */ | |
967 | if (addr == VIA_REG_PCR && val == 0x22) { | |
968 | /* Looks like there has been a reset? */ | |
969 | v1s->timer_hack_state = 1; | |
970 | } | |
971 | break; | |
972 | default: | |
973 | g_assert_not_reached(); | |
974 | } | |
975 | ||
976 | if (old_timer_hack_state != v1s->timer_hack_state) { | |
977 | trace_via1_timer_hack_state(v1s->timer_hack_state); | |
978 | } | |
979 | } | |
980 | ||
6dca62a0 LV |
981 | static uint64_t mos6522_q800_via1_read(void *opaque, hwaddr addr, unsigned size) |
982 | { | |
983 | MOS6522Q800VIA1State *s = MOS6522_Q800_VIA1(opaque); | |
984 | MOS6522State *ms = MOS6522(s); | |
0f03047c | 985 | uint64_t ret; |
6dca62a0 LV |
986 | |
987 | addr = (addr >> 9) & 0xf; | |
0f03047c MCA |
988 | ret = mos6522_read(ms, addr, size); |
989 | switch (addr) { | |
990 | case VIA_REG_A: | |
991 | case VIA_REG_ANH: | |
992 | /* Quadra 800 Id */ | |
993 | ret = (ret & ~VIA1A_CPUID_MASK) | VIA1A_CPUID_Q800; | |
994 | break; | |
995 | } | |
996 | return ret; | |
6dca62a0 LV |
997 | } |
998 | ||
999 | static void mos6522_q800_via1_write(void *opaque, hwaddr addr, uint64_t val, | |
1000 | unsigned size) | |
1001 | { | |
1002 | MOS6522Q800VIA1State *v1s = MOS6522_Q800_VIA1(opaque); | |
1003 | MOS6522State *ms = MOS6522(v1s); | |
1004 | ||
1005 | addr = (addr >> 9) & 0xf; | |
366d2779 MCA |
1006 | |
1007 | via1_timer_calibration_hack(v1s, addr, val, size); | |
1008 | ||
6dca62a0 LV |
1009 | mos6522_write(ms, addr, val, size); |
1010 | ||
378a5034 MCA |
1011 | switch (addr) { |
1012 | case VIA_REG_B: | |
741258b0 | 1013 | via1_rtc_update(v1s); |
5f083d42 | 1014 | via1_adb_update(v1s); |
291bc180 | 1015 | via1_auxmode_update(v1s); |
378a5034 MCA |
1016 | |
1017 | v1s->last_b = ms->b; | |
1018 | break; | |
1019 | } | |
6dca62a0 LV |
1020 | } |
1021 | ||
1022 | static const MemoryRegionOps mos6522_q800_via1_ops = { | |
1023 | .read = mos6522_q800_via1_read, | |
1024 | .write = mos6522_q800_via1_write, | |
1025 | .endianness = DEVICE_BIG_ENDIAN, | |
1026 | .valid = { | |
1027 | .min_access_size = 1, | |
add4dbfb | 1028 | .max_access_size = 4, |
6dca62a0 LV |
1029 | }, |
1030 | }; | |
1031 | ||
1032 | static uint64_t mos6522_q800_via2_read(void *opaque, hwaddr addr, unsigned size) | |
1033 | { | |
1034 | MOS6522Q800VIA2State *s = MOS6522_Q800_VIA2(opaque); | |
1035 | MOS6522State *ms = MOS6522(s); | |
677a4725 | 1036 | uint64_t val; |
6dca62a0 LV |
1037 | |
1038 | addr = (addr >> 9) & 0xf; | |
677a4725 MCA |
1039 | val = mos6522_read(ms, addr, size); |
1040 | ||
1041 | switch (addr) { | |
1042 | case VIA_REG_IFR: | |
1043 | /* | |
1044 | * On a Q800 an emulated VIA2 is integrated into the onboard logic. The | |
1045 | * expectation of most OSs is that the DRQ bit is live, rather than | |
1046 | * latched as it would be on a real VIA so do the same here. | |
b793b4ef MCA |
1047 | * |
1048 | * Note: DRQ is negative edge triggered | |
677a4725 MCA |
1049 | */ |
1050 | val &= ~VIA2_IRQ_SCSI_DATA; | |
b793b4ef | 1051 | val |= (~ms->last_irq_levels & VIA2_IRQ_SCSI_DATA); |
677a4725 MCA |
1052 | break; |
1053 | } | |
1054 | ||
1055 | return val; | |
6dca62a0 LV |
1056 | } |
1057 | ||
1058 | static void mos6522_q800_via2_write(void *opaque, hwaddr addr, uint64_t val, | |
1059 | unsigned size) | |
1060 | { | |
1061 | MOS6522Q800VIA2State *s = MOS6522_Q800_VIA2(opaque); | |
1062 | MOS6522State *ms = MOS6522(s); | |
1063 | ||
1064 | addr = (addr >> 9) & 0xf; | |
1065 | mos6522_write(ms, addr, val, size); | |
1066 | } | |
1067 | ||
1068 | static const MemoryRegionOps mos6522_q800_via2_ops = { | |
1069 | .read = mos6522_q800_via2_read, | |
1070 | .write = mos6522_q800_via2_write, | |
1071 | .endianness = DEVICE_BIG_ENDIAN, | |
1072 | .valid = { | |
1073 | .min_access_size = 1, | |
add4dbfb | 1074 | .max_access_size = 4, |
6dca62a0 LV |
1075 | }, |
1076 | }; | |
1077 | ||
8064d7bb | 1078 | static void via1_postload_update_cb(void *opaque, bool running, RunState state) |
eb064db9 | 1079 | { |
8064d7bb | 1080 | MOS6522Q800VIA1State *v1s = MOS6522_Q800_VIA1(opaque); |
eb064db9 | 1081 | |
8064d7bb MCA |
1082 | qemu_del_vm_change_state_handler(v1s->vmstate); |
1083 | v1s->vmstate = NULL; | |
eb064db9 | 1084 | |
8064d7bb | 1085 | pram_update(v1s); |
eb064db9 LV |
1086 | } |
1087 | ||
8064d7bb | 1088 | static int via1_post_load(void *opaque, int version_id) |
eb064db9 | 1089 | { |
8064d7bb | 1090 | MOS6522Q800VIA1State *v1s = MOS6522_Q800_VIA1(opaque); |
eb064db9 | 1091 | |
8064d7bb MCA |
1092 | if (v1s->blk) { |
1093 | v1s->vmstate = qemu_add_vm_change_state_handler( | |
1094 | via1_postload_update_cb, v1s); | |
eb064db9 LV |
1095 | } |
1096 | ||
1097 | return 0; | |
1098 | } | |
1099 | ||
6dca62a0 | 1100 | /* VIA 1 */ |
ed053e89 | 1101 | static void mos6522_q800_via1_reset_hold(Object *obj) |
6dca62a0 | 1102 | { |
ed053e89 | 1103 | MOS6522Q800VIA1State *v1s = MOS6522_Q800_VIA1(obj); |
14562b37 | 1104 | MOS6522State *ms = MOS6522(v1s); |
9db70dac | 1105 | MOS6522DeviceClass *mdc = MOS6522_GET_CLASS(ms); |
14562b37 | 1106 | ADBBusState *adb_bus = &v1s->adb_bus; |
6dca62a0 | 1107 | |
ed053e89 PM |
1108 | if (mdc->parent_phases.hold) { |
1109 | mdc->parent_phases.hold(obj); | |
1110 | } | |
6dca62a0 LV |
1111 | |
1112 | ms->timers[0].frequency = VIA_TIMER_FREQ; | |
1113 | ms->timers[1].frequency = VIA_TIMER_FREQ; | |
1114 | ||
1115 | ms->b = VIA1B_vADB_StateMask | VIA1B_vADBInt | VIA1B_vRTCEnb; | |
14562b37 MCA |
1116 | |
1117 | /* ADB/RTC */ | |
1118 | adb_set_autopoll_enabled(adb_bus, true); | |
1119 | v1s->cmd = REG_EMPTY; | |
1120 | v1s->alt = REG_EMPTY; | |
366d2779 MCA |
1121 | |
1122 | /* Timer calibration hack */ | |
1123 | v1s->timer_hack_state = 0; | |
6dca62a0 LV |
1124 | } |
1125 | ||
846ae7c6 MCA |
1126 | static void mos6522_q800_via1_realize(DeviceState *dev, Error **errp) |
1127 | { | |
1128 | MOS6522Q800VIA1State *v1s = MOS6522_Q800_VIA1(dev); | |
1129 | ADBBusState *adb_bus = &v1s->adb_bus; | |
1130 | struct tm tm; | |
1131 | int ret; | |
1132 | ||
1133 | v1s->one_second_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, via1_one_second, | |
1134 | v1s); | |
1135 | via1_one_second_update(v1s); | |
1136 | v1s->sixty_hz_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, via1_sixty_hz, | |
1137 | v1s); | |
1138 | via1_sixty_hz_update(v1s); | |
1139 | ||
1140 | qemu_get_timedate(&tm, 0); | |
1141 | v1s->tick_offset = (uint32_t)mktimegm(&tm) + RTC_OFFSET; | |
1142 | ||
1143 | adb_register_autopoll_callback(adb_bus, adb_via_poll, v1s); | |
323f9849 | 1144 | v1s->adb_data_ready = qdev_get_gpio_in(dev, VIA1_IRQ_ADB_READY_BIT); |
846ae7c6 MCA |
1145 | |
1146 | if (v1s->blk) { | |
1147 | int64_t len = blk_getlength(v1s->blk); | |
1148 | if (len < 0) { | |
1149 | error_setg_errno(errp, -len, | |
1150 | "could not get length of backing image"); | |
1151 | return; | |
1152 | } | |
1153 | ret = blk_set_perm(v1s->blk, | |
1154 | BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE, | |
1155 | BLK_PERM_ALL, errp); | |
1156 | if (ret < 0) { | |
1157 | return; | |
1158 | } | |
1159 | ||
a9262f55 | 1160 | ret = blk_pread(v1s->blk, 0, sizeof(v1s->PRAM), v1s->PRAM, 0); |
bf5b16fa | 1161 | if (ret < 0) { |
846ae7c6 MCA |
1162 | error_setg(errp, "can't read PRAM contents"); |
1163 | return; | |
1164 | } | |
1165 | } | |
1166 | } | |
1167 | ||
6dca62a0 LV |
1168 | static void mos6522_q800_via1_init(Object *obj) |
1169 | { | |
5f083d42 | 1170 | MOS6522Q800VIA1State *v1s = MOS6522_Q800_VIA1(obj); |
02a68a3e MCA |
1171 | SysBusDevice *sbd = SYS_BUS_DEVICE(v1s); |
1172 | ||
1173 | memory_region_init_io(&v1s->via_mem, obj, &mos6522_q800_via1_ops, v1s, | |
1174 | "via1", VIA_SIZE); | |
1175 | sysbus_init_mmio(sbd, &v1s->via_mem); | |
5f083d42 MCA |
1176 | |
1177 | /* ADB */ | |
d637e1dc PM |
1178 | qbus_init((BusState *)&v1s->adb_bus, sizeof(v1s->adb_bus), |
1179 | TYPE_ADB_BUS, DEVICE(v1s), "adb.0"); | |
5f083d42 | 1180 | |
291bc180 MCA |
1181 | /* A/UX mode */ |
1182 | qdev_init_gpio_out(DEVICE(obj), &v1s->auxmode_irq, 1); | |
6dca62a0 LV |
1183 | } |
1184 | ||
17de3d57 MCA |
1185 | static const VMStateDescription vmstate_q800_via1 = { |
1186 | .name = "q800-via1", | |
1187 | .version_id = 0, | |
1188 | .minimum_version_id = 0, | |
8064d7bb | 1189 | .post_load = via1_post_load, |
17de3d57 MCA |
1190 | .fields = (VMStateField[]) { |
1191 | VMSTATE_STRUCT(parent_obj, MOS6522Q800VIA1State, 0, vmstate_mos6522, | |
1192 | MOS6522State), | |
ae6f236f | 1193 | VMSTATE_UINT8(last_b, MOS6522Q800VIA1State), |
8064d7bb MCA |
1194 | /* RTC */ |
1195 | VMSTATE_BUFFER(PRAM, MOS6522Q800VIA1State), | |
741258b0 MCA |
1196 | VMSTATE_UINT32(tick_offset, MOS6522Q800VIA1State), |
1197 | VMSTATE_UINT8(data_out, MOS6522Q800VIA1State), | |
1198 | VMSTATE_INT32(data_out_cnt, MOS6522Q800VIA1State), | |
1199 | VMSTATE_UINT8(data_in, MOS6522Q800VIA1State), | |
1200 | VMSTATE_UINT8(data_in_cnt, MOS6522Q800VIA1State), | |
1201 | VMSTATE_UINT8(cmd, MOS6522Q800VIA1State), | |
1202 | VMSTATE_INT32(wprotect, MOS6522Q800VIA1State), | |
1203 | VMSTATE_INT32(alt, MOS6522Q800VIA1State), | |
5f083d42 MCA |
1204 | /* ADB */ |
1205 | VMSTATE_INT32(adb_data_in_size, MOS6522Q800VIA1State), | |
1206 | VMSTATE_INT32(adb_data_in_index, MOS6522Q800VIA1State), | |
1207 | VMSTATE_INT32(adb_data_out_index, MOS6522Q800VIA1State), | |
1208 | VMSTATE_BUFFER(adb_data_in, MOS6522Q800VIA1State), | |
1209 | VMSTATE_BUFFER(adb_data_out, MOS6522Q800VIA1State), | |
1210 | VMSTATE_UINT8(adb_autopoll_cmd, MOS6522Q800VIA1State), | |
84e944b2 MCA |
1211 | /* Timers */ |
1212 | VMSTATE_TIMER_PTR(one_second_timer, MOS6522Q800VIA1State), | |
1213 | VMSTATE_INT64(next_second, MOS6522Q800VIA1State), | |
1214 | VMSTATE_TIMER_PTR(sixty_hz_timer, MOS6522Q800VIA1State), | |
1215 | VMSTATE_INT64(next_sixty_hz, MOS6522Q800VIA1State), | |
366d2779 MCA |
1216 | /* Timer hack */ |
1217 | VMSTATE_INT32(timer_hack_state, MOS6522Q800VIA1State), | |
17de3d57 MCA |
1218 | VMSTATE_END_OF_LIST() |
1219 | } | |
1220 | }; | |
1221 | ||
8064d7bb MCA |
1222 | static Property mos6522_q800_via1_properties[] = { |
1223 | DEFINE_PROP_DRIVE("drive", MOS6522Q800VIA1State, blk), | |
1224 | DEFINE_PROP_END_OF_LIST(), | |
1225 | }; | |
1226 | ||
6dca62a0 LV |
1227 | static void mos6522_q800_via1_class_init(ObjectClass *oc, void *data) |
1228 | { | |
1229 | DeviceClass *dc = DEVICE_CLASS(oc); | |
ed053e89 | 1230 | ResettableClass *rc = RESETTABLE_CLASS(oc); |
c697fc80 | 1231 | MOS6522DeviceClass *mdc = MOS6522_CLASS(oc); |
6dca62a0 | 1232 | |
846ae7c6 | 1233 | dc->realize = mos6522_q800_via1_realize; |
ed053e89 PM |
1234 | resettable_class_set_parent_phases(rc, NULL, mos6522_q800_via1_reset_hold, |
1235 | NULL, &mdc->parent_phases); | |
17de3d57 | 1236 | dc->vmsd = &vmstate_q800_via1; |
8064d7bb | 1237 | device_class_set_props(dc, mos6522_q800_via1_properties); |
6dca62a0 LV |
1238 | } |
1239 | ||
1240 | static const TypeInfo mos6522_q800_via1_type_info = { | |
1241 | .name = TYPE_MOS6522_Q800_VIA1, | |
1242 | .parent = TYPE_MOS6522, | |
1243 | .instance_size = sizeof(MOS6522Q800VIA1State), | |
1244 | .instance_init = mos6522_q800_via1_init, | |
1245 | .class_init = mos6522_q800_via1_class_init, | |
1246 | }; | |
1247 | ||
1248 | /* VIA 2 */ | |
1249 | static void mos6522_q800_via2_portB_write(MOS6522State *s) | |
1250 | { | |
1251 | if (s->dirb & VIA2B_vPower && (s->b & VIA2B_vPower) == 0) { | |
1252 | /* shutdown */ | |
1253 | qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN); | |
1254 | } | |
1255 | } | |
1256 | ||
ed053e89 | 1257 | static void mos6522_q800_via2_reset_hold(Object *obj) |
6dca62a0 | 1258 | { |
ed053e89 | 1259 | MOS6522State *ms = MOS6522(obj); |
9db70dac | 1260 | MOS6522DeviceClass *mdc = MOS6522_GET_CLASS(ms); |
6dca62a0 | 1261 | |
ed053e89 PM |
1262 | if (mdc->parent_phases.hold) { |
1263 | mdc->parent_phases.hold(obj); | |
1264 | } | |
6dca62a0 LV |
1265 | |
1266 | ms->timers[0].frequency = VIA_TIMER_FREQ; | |
1267 | ms->timers[1].frequency = VIA_TIMER_FREQ; | |
1268 | ||
1269 | ms->dirb = 0; | |
1270 | ms->b = 0; | |
dde602ae MCA |
1271 | ms->dira = 0; |
1272 | ms->a = 0x7f; | |
1273 | } | |
1274 | ||
ebe5bca2 | 1275 | static void via2_nubus_irq_request(void *opaque, int n, int level) |
dde602ae MCA |
1276 | { |
1277 | MOS6522Q800VIA2State *v2s = opaque; | |
1278 | MOS6522State *s = MOS6522(v2s); | |
ebe5bca2 | 1279 | qemu_irq irq = qdev_get_gpio_in(DEVICE(s), VIA2_IRQ_NUBUS_BIT); |
dde602ae MCA |
1280 | |
1281 | if (level) { | |
1282 | /* Port A nubus IRQ inputs are active LOW */ | |
ebe5bca2 | 1283 | s->a &= ~(1 << n); |
dde602ae | 1284 | } else { |
ebe5bca2 | 1285 | s->a |= (1 << n); |
dde602ae MCA |
1286 | } |
1287 | ||
b793b4ef MCA |
1288 | /* Negative edge trigger */ |
1289 | qemu_set_irq(irq, !level); | |
6dca62a0 LV |
1290 | } |
1291 | ||
1292 | static void mos6522_q800_via2_init(Object *obj) | |
1293 | { | |
02a68a3e MCA |
1294 | MOS6522Q800VIA2State *v2s = MOS6522_Q800_VIA2(obj); |
1295 | SysBusDevice *sbd = SYS_BUS_DEVICE(v2s); | |
1296 | ||
1297 | memory_region_init_io(&v2s->via_mem, obj, &mos6522_q800_via2_ops, v2s, | |
1298 | "via2", VIA_SIZE); | |
1299 | sysbus_init_mmio(sbd, &v2s->via_mem); | |
1300 | ||
dde602ae MCA |
1301 | qdev_init_gpio_in_named(DEVICE(obj), via2_nubus_irq_request, "nubus-irq", |
1302 | VIA2_NUBUS_IRQ_NB); | |
6dca62a0 LV |
1303 | } |
1304 | ||
17de3d57 MCA |
1305 | static const VMStateDescription vmstate_q800_via2 = { |
1306 | .name = "q800-via2", | |
1307 | .version_id = 0, | |
1308 | .minimum_version_id = 0, | |
1309 | .fields = (VMStateField[]) { | |
1310 | VMSTATE_STRUCT(parent_obj, MOS6522Q800VIA2State, 0, vmstate_mos6522, | |
1311 | MOS6522State), | |
1312 | VMSTATE_END_OF_LIST() | |
1313 | } | |
1314 | }; | |
1315 | ||
6dca62a0 LV |
1316 | static void mos6522_q800_via2_class_init(ObjectClass *oc, void *data) |
1317 | { | |
1318 | DeviceClass *dc = DEVICE_CLASS(oc); | |
ed053e89 | 1319 | ResettableClass *rc = RESETTABLE_CLASS(oc); |
9db70dac | 1320 | MOS6522DeviceClass *mdc = MOS6522_CLASS(oc); |
6dca62a0 | 1321 | |
ed053e89 PM |
1322 | resettable_class_set_parent_phases(rc, NULL, mos6522_q800_via2_reset_hold, |
1323 | NULL, &mdc->parent_phases); | |
17de3d57 | 1324 | dc->vmsd = &vmstate_q800_via2; |
6dca62a0 LV |
1325 | mdc->portB_write = mos6522_q800_via2_portB_write; |
1326 | } | |
1327 | ||
1328 | static const TypeInfo mos6522_q800_via2_type_info = { | |
1329 | .name = TYPE_MOS6522_Q800_VIA2, | |
1330 | .parent = TYPE_MOS6522, | |
1331 | .instance_size = sizeof(MOS6522Q800VIA2State), | |
1332 | .instance_init = mos6522_q800_via2_init, | |
1333 | .class_init = mos6522_q800_via2_class_init, | |
1334 | }; | |
1335 | ||
1336 | static void mac_via_register_types(void) | |
1337 | { | |
1338 | type_register_static(&mos6522_q800_via1_type_info); | |
1339 | type_register_static(&mos6522_q800_via2_type_info); | |
6dca62a0 LV |
1340 | } |
1341 | ||
1342 | type_init(mac_via_register_types); |