| 1 | /* |
| 2 | * QEMU Apple Sound Chip emulation |
| 3 | * |
| 4 | * Apple Sound Chip (ASC) 344S0063 |
| 5 | * Enhanced Apple Sound Chip (EASC) 343S1063 |
| 6 | * |
| 7 | * Copyright (c) 2012-2018 Laurent Vivier <laurent@vivier.eu> |
| 8 | * Copyright (c) 2022 Mark Cave-Ayland <mark.cave-ayland@ilande.co.uk> |
| 9 | * |
| 10 | * SPDX-License-Identifier: GPL-2.0-or-later |
| 11 | */ |
| 12 | |
| 13 | #include "qemu/osdep.h" |
| 14 | #include "qemu/timer.h" |
| 15 | #include "qapi/error.h" |
| 16 | #include "hw/core/sysbus.h" |
| 17 | #include "hw/core/irq.h" |
| 18 | #include "qemu/audio.h" |
| 19 | #include "hw/audio/asc.h" |
| 20 | #include "hw/core/qdev-properties.h" |
| 21 | #include "migration/vmstate.h" |
| 22 | #include "trace.h" |
| 23 | |
| 24 | /* |
| 25 | * Linux doesn't provide information about ASC, see arch/m68k/mac/macboing.c |
| 26 | * and arch/m68k/include/asm/mac_asc.h |
| 27 | * |
| 28 | * best information is coming from MAME: |
| 29 | * https://github.com/mamedev/mame/blob/master/src/devices/sound/asc.h |
| 30 | * https://github.com/mamedev/mame/blob/master/src/devices/sound/asc.cpp |
| 31 | * Emulation by R. Belmont |
| 32 | * or MESS: |
| 33 | * http://mess.redump.net/mess/driver_info/easc |
| 34 | * |
| 35 | * 0x800: VERSION |
| 36 | * 0x801: MODE |
| 37 | * 1=FIFO mode, |
| 38 | * 2=wavetable mode |
| 39 | * 0x802: CONTROL |
| 40 | * bit 0=analog or PWM output, |
| 41 | * 1=stereo/mono, |
| 42 | * 7=processing time exceeded |
| 43 | * 0x803: FIFO MODE |
| 44 | * bit 7=clear FIFO, |
| 45 | * bit 1="non-ROM companding", |
| 46 | * bit 0="ROM companding") |
| 47 | * 0x804: FIFO IRQ STATUS |
| 48 | * bit 0=ch A 1/2 full, |
| 49 | * 1=ch A full, |
| 50 | * 2=ch B 1/2 full, |
| 51 | * 3=ch B full) |
| 52 | * 0x805: WAVETABLE CONTROL |
| 53 | * bits 0-3 wavetables 0-3 start |
| 54 | * 0x806: VOLUME |
| 55 | * bits 2-4 = 3 bit internal ASC volume, |
| 56 | * bits 5-7 = volume control sent to Sony sound chip |
| 57 | * 0x807: CLOCK RATE |
| 58 | * 0 = Mac 22257 Hz, |
| 59 | * 1 = undefined, |
| 60 | * 2 = 22050 Hz, |
| 61 | * 3 = 44100 Hz |
| 62 | * 0x80a: PLAY REC A |
| 63 | * 0x80f: TEST |
| 64 | * bits 6-7 = digital test, |
| 65 | * bits 4-5 = analog test |
| 66 | * 0x810: WAVETABLE 0 PHASE |
| 67 | * big-endian 9.15 fixed-point, only 24 bits valid |
| 68 | * 0x814: WAVETABLE 0 INCREMENT |
| 69 | * big-endian 9.15 fixed-point, only 24 bits valid |
| 70 | * 0x818: WAVETABLE 1 PHASE |
| 71 | * 0x81C: WAVETABLE 1 INCREMENT |
| 72 | * 0x820: WAVETABLE 2 PHASE |
| 73 | * 0x824: WAVETABLE 2 INCREMENT |
| 74 | * 0x828: WAVETABLE 3 PHASE |
| 75 | * 0x82C: WAVETABLE 3 INCREMENT |
| 76 | * 0x830: UNKNOWN START |
| 77 | * NetBSD writes Wavetable data here (are there more |
| 78 | * wavetables/channels than we know about?) |
| 79 | * 0x857: UNKNOWN END |
| 80 | */ |
| 81 | |
| 82 | #define ASC_SIZE 0x2000 |
| 83 | |
| 84 | enum { |
| 85 | ASC_VERSION = 0x00, |
| 86 | ASC_MODE = 0x01, |
| 87 | ASC_CONTROL = 0x02, |
| 88 | ASC_FIFOMODE = 0x03, |
| 89 | ASC_FIFOIRQ = 0x04, |
| 90 | ASC_WAVECTRL = 0x05, |
| 91 | ASC_VOLUME = 0x06, |
| 92 | ASC_CLOCK = 0x07, |
| 93 | ASC_PLAYRECA = 0x0a, |
| 94 | ASC_TEST = 0x0f, |
| 95 | ASC_WAVETABLE = 0x10 |
| 96 | }; |
| 97 | |
| 98 | #define ASC_FIFO_STATUS_HALF_FULL 1 |
| 99 | #define ASC_FIFO_STATUS_FULL_EMPTY 2 |
| 100 | |
| 101 | #define ASC_EXTREGS_FIFOCTRL 0x8 |
| 102 | #define ASC_EXTREGS_INTCTRL 0x9 |
| 103 | #define ASC_EXTREGS_CDXA_DECOMP_FILT 0x10 |
| 104 | |
| 105 | #define ASC_FIFO_CYCLE_TIME ((NANOSECONDS_PER_SECOND / ASC_FREQ) * \ |
| 106 | 0x400) |
| 107 | |
| 108 | static void asc_raise_irq(ASCState *s) |
| 109 | { |
| 110 | qemu_set_irq(s->irq, 1); |
| 111 | } |
| 112 | |
| 113 | static void asc_lower_irq(ASCState *s) |
| 114 | { |
| 115 | qemu_set_irq(s->irq, 0); |
| 116 | } |
| 117 | |
| 118 | static uint8_t asc_fifo_get(ASCFIFOState *fs) |
| 119 | { |
| 120 | ASCState *s = container_of(fs, ASCState, fifos[fs->index]); |
| 121 | bool fifo_half_irq_enabled = fs->extregs[ASC_EXTREGS_INTCTRL] & 1; |
| 122 | uint8_t val; |
| 123 | |
| 124 | assert(fs->cnt); |
| 125 | |
| 126 | val = fs->fifo[fs->rptr]; |
| 127 | trace_asc_fifo_get('A' + fs->index, fs->rptr, fs->cnt, val); |
| 128 | |
| 129 | fs->rptr++; |
| 130 | fs->rptr &= 0x3ff; |
| 131 | fs->cnt--; |
| 132 | |
| 133 | if (fs->cnt <= 0x1ff) { |
| 134 | /* FIFO less than half full */ |
| 135 | fs->int_status |= ASC_FIFO_STATUS_HALF_FULL; |
| 136 | } else { |
| 137 | /* FIFO more than half full */ |
| 138 | fs->int_status &= ~ASC_FIFO_STATUS_HALF_FULL; |
| 139 | } |
| 140 | |
| 141 | if (fs->cnt == 0x1ff && fifo_half_irq_enabled) { |
| 142 | /* Raise FIFO half full IRQ */ |
| 143 | asc_raise_irq(s); |
| 144 | } |
| 145 | |
| 146 | if (fs->cnt == 0) { |
| 147 | /* Raise FIFO empty IRQ */ |
| 148 | fs->int_status |= ASC_FIFO_STATUS_FULL_EMPTY; |
| 149 | asc_raise_irq(s); |
| 150 | } |
| 151 | |
| 152 | return val; |
| 153 | } |
| 154 | |
| 155 | static int generate_fifo(ASCState *s, int maxsamples) |
| 156 | { |
| 157 | int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); |
| 158 | uint8_t *buf = s->mixbuf; |
| 159 | int i, wcount = 0; |
| 160 | |
| 161 | while (wcount < maxsamples) { |
| 162 | uint8_t val; |
| 163 | int16_t d, f0, f1; |
| 164 | int32_t t; |
| 165 | int shift, filter; |
| 166 | bool hasdata = false; |
| 167 | |
| 168 | for (i = 0; i < 2; i++) { |
| 169 | ASCFIFOState *fs = &s->fifos[i]; |
| 170 | |
| 171 | switch (fs->extregs[ASC_EXTREGS_FIFOCTRL] & 0x83) { |
| 172 | case 0x82: |
| 173 | /* |
| 174 | * CD-XA BRR mode: decompress 15 bytes into 28 16-bit |
| 175 | * samples |
| 176 | */ |
| 177 | if (!fs->cnt) { |
| 178 | val = 0x80; |
| 179 | break; |
| 180 | } |
| 181 | |
| 182 | if (fs->xa_cnt == -1) { |
| 183 | /* Start of packet, get flags */ |
| 184 | fs->xa_flags = asc_fifo_get(fs); |
| 185 | fs->xa_cnt = 0; |
| 186 | } |
| 187 | |
| 188 | shift = fs->xa_flags & 0xf; |
| 189 | filter = fs->xa_flags >> 4; |
| 190 | f0 = (int8_t)fs->extregs[ASC_EXTREGS_CDXA_DECOMP_FILT + |
| 191 | (filter << 1) + 1]; |
| 192 | f1 = (int8_t)fs->extregs[ASC_EXTREGS_CDXA_DECOMP_FILT + |
| 193 | (filter << 1)]; |
| 194 | |
| 195 | if ((fs->xa_cnt & 1) == 0) { |
| 196 | if (!fs->cnt) { |
| 197 | val = 0x80; |
| 198 | break; |
| 199 | } |
| 200 | |
| 201 | fs->xa_val = asc_fifo_get(fs); |
| 202 | d = (fs->xa_val & 0xf) << 12; |
| 203 | } else { |
| 204 | d = (fs->xa_val & 0xf0) << 8; |
| 205 | } |
| 206 | t = (d >> shift) + (((fs->xa_last[0] * f0) + |
| 207 | (fs->xa_last[1] * f1) + 32) >> 6); |
| 208 | if (t < -32768) { |
| 209 | t = -32768; |
| 210 | } else if (t > 32767) { |
| 211 | t = 32767; |
| 212 | } |
| 213 | |
| 214 | /* |
| 215 | * CD-XA BRR generates 16-bit signed output, so convert to |
| 216 | * 8-bit before writing to buffer. Does real hardware do the |
| 217 | * same? |
| 218 | */ |
| 219 | val = (uint8_t)(t / 256) ^ 0x80; |
| 220 | hasdata = true; |
| 221 | fs->xa_cnt++; |
| 222 | |
| 223 | fs->xa_last[1] = fs->xa_last[0]; |
| 224 | fs->xa_last[0] = (int16_t)t; |
| 225 | |
| 226 | if (fs->xa_cnt == 28) { |
| 227 | /* End of packet */ |
| 228 | fs->xa_cnt = -1; |
| 229 | } |
| 230 | break; |
| 231 | |
| 232 | default: |
| 233 | /* fallthrough */ |
| 234 | case 0x80: |
| 235 | /* Raw mode */ |
| 236 | if (fs->cnt) { |
| 237 | val = asc_fifo_get(fs); |
| 238 | hasdata = true; |
| 239 | } else { |
| 240 | val = 0x80; |
| 241 | } |
| 242 | break; |
| 243 | } |
| 244 | |
| 245 | buf[wcount * 2 + i] = val; |
| 246 | } |
| 247 | |
| 248 | if (!hasdata) { |
| 249 | break; |
| 250 | } |
| 251 | |
| 252 | wcount++; |
| 253 | } |
| 254 | |
| 255 | /* |
| 256 | * MacOS (un)helpfully leaves the FIFO engine running even when it has |
| 257 | * finished writing out samples, but still expects the FIFO empty |
| 258 | * interrupts to be generated for each FIFO cycle (without these interrupts |
| 259 | * MacOS will freeze) |
| 260 | */ |
| 261 | if (s->fifos[0].cnt == 0 && s->fifos[1].cnt == 0) { |
| 262 | if (!s->fifo_empty_ns) { |
| 263 | /* FIFO has completed first empty cycle */ |
| 264 | s->fifo_empty_ns = now; |
| 265 | } else if (now > (s->fifo_empty_ns + ASC_FIFO_CYCLE_TIME)) { |
| 266 | /* FIFO has completed entire cycle with no data */ |
| 267 | s->fifos[0].int_status |= ASC_FIFO_STATUS_HALF_FULL | |
| 268 | ASC_FIFO_STATUS_FULL_EMPTY; |
| 269 | s->fifos[1].int_status |= ASC_FIFO_STATUS_HALF_FULL | |
| 270 | ASC_FIFO_STATUS_FULL_EMPTY; |
| 271 | s->fifo_empty_ns = now; |
| 272 | asc_raise_irq(s); |
| 273 | } |
| 274 | } else { |
| 275 | /* FIFO contains data, reset empty time */ |
| 276 | s->fifo_empty_ns = 0; |
| 277 | } |
| 278 | |
| 279 | return wcount; |
| 280 | } |
| 281 | |
| 282 | static int generate_wavetable(ASCState *s, int maxsamples) |
| 283 | { |
| 284 | uint8_t *buf = s->mixbuf; |
| 285 | int channel, count = 0; |
| 286 | |
| 287 | while (count < maxsamples) { |
| 288 | uint32_t left = 0, right = 0; |
| 289 | uint8_t sample; |
| 290 | |
| 291 | for (channel = 0; channel < 4; channel++) { |
| 292 | ASCFIFOState *fs = &s->fifos[channel >> 1]; |
| 293 | int chanreg = ASC_WAVETABLE + (channel << 3); |
| 294 | uint32_t phase, incr, offset; |
| 295 | |
| 296 | phase = ldl_be_p(&s->regs[chanreg]); |
| 297 | incr = ldl_be_p(&s->regs[chanreg + sizeof(uint32_t)]); |
| 298 | |
| 299 | phase += incr; |
| 300 | offset = (phase >> 15) & 0x1ff; |
| 301 | sample = fs->fifo[0x200 * (channel >> 1) + offset]; |
| 302 | |
| 303 | stl_be_p(&s->regs[chanreg], phase); |
| 304 | |
| 305 | left += sample; |
| 306 | right += sample; |
| 307 | } |
| 308 | |
| 309 | buf[count * 2] = left >> 2; |
| 310 | buf[count * 2 + 1] = right >> 2; |
| 311 | |
| 312 | count++; |
| 313 | } |
| 314 | |
| 315 | return count; |
| 316 | } |
| 317 | |
| 318 | static void asc_out_cb(void *opaque, int free_b) |
| 319 | { |
| 320 | ASCState *s = opaque; |
| 321 | int samples, generated; |
| 322 | |
| 323 | if (free_b == 0) { |
| 324 | return; |
| 325 | } |
| 326 | |
| 327 | samples = MIN(s->samples, free_b >> s->shift); |
| 328 | |
| 329 | switch (s->regs[ASC_MODE] & 3) { |
| 330 | default: |
| 331 | /* Off */ |
| 332 | generated = 0; |
| 333 | break; |
| 334 | case 1: |
| 335 | /* FIFO mode */ |
| 336 | generated = generate_fifo(s, samples); |
| 337 | break; |
| 338 | case 2: |
| 339 | /* Wave table mode */ |
| 340 | generated = generate_wavetable(s, samples); |
| 341 | break; |
| 342 | } |
| 343 | |
| 344 | if (!generated) { |
| 345 | /* Workaround for audio underflow bug on Windows dsound backend */ |
| 346 | int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); |
| 347 | int silent_samples = muldiv64(now - s->fifo_empty_ns, |
| 348 | NANOSECONDS_PER_SECOND, ASC_FREQ); |
| 349 | |
| 350 | if (silent_samples > ASC_FIFO_CYCLE_TIME / 2) { |
| 351 | /* |
| 352 | * No new FIFO data within half a cycle time (~23ms) so fill the |
| 353 | * entire available buffer with silence. This prevents an issue |
| 354 | * with the Windows dsound backend whereby the sound appears to |
| 355 | * loop because the FIFO has run out of data, and the driver |
| 356 | * reuses the stale content in its circular audio buffer. |
| 357 | */ |
| 358 | audio_be_write(s->audio_be, s->voice, s->silentbuf, samples << s->shift); |
| 359 | } |
| 360 | return; |
| 361 | } |
| 362 | |
| 363 | audio_be_write(s->audio_be, s->voice, s->mixbuf, generated << s->shift); |
| 364 | } |
| 365 | |
| 366 | static uint64_t asc_fifo_read(void *opaque, hwaddr addr, |
| 367 | unsigned size) |
| 368 | { |
| 369 | ASCFIFOState *fs = opaque; |
| 370 | |
| 371 | trace_asc_read_fifo('A' + fs->index, addr, size, fs->fifo[addr]); |
| 372 | return fs->fifo[addr]; |
| 373 | } |
| 374 | |
| 375 | static void asc_fifo_write(void *opaque, hwaddr addr, uint64_t value, |
| 376 | unsigned size) |
| 377 | { |
| 378 | ASCFIFOState *fs = opaque; |
| 379 | ASCState *s = container_of(fs, ASCState, fifos[fs->index]); |
| 380 | bool fifo_half_irq_enabled = fs->extregs[ASC_EXTREGS_INTCTRL] & 1; |
| 381 | |
| 382 | trace_asc_write_fifo('A' + fs->index, addr, size, fs->wptr, fs->cnt, value); |
| 383 | |
| 384 | if (s->regs[ASC_MODE] == 1) { |
| 385 | fs->fifo[fs->wptr++] = value; |
| 386 | fs->wptr &= 0x3ff; |
| 387 | fs->cnt++; |
| 388 | |
| 389 | if (fs->cnt <= 0x1ff) { |
| 390 | /* FIFO less than half full */ |
| 391 | fs->int_status |= ASC_FIFO_STATUS_HALF_FULL; |
| 392 | } else { |
| 393 | /* FIFO at least half full */ |
| 394 | fs->int_status &= ~ASC_FIFO_STATUS_HALF_FULL; |
| 395 | } |
| 396 | |
| 397 | if (fs->cnt == 0x200 && fifo_half_irq_enabled) { |
| 398 | /* Raise FIFO half full interrupt */ |
| 399 | asc_raise_irq(s); |
| 400 | } |
| 401 | |
| 402 | if (fs->cnt == 0x3ff) { |
| 403 | /* Raise FIFO full interrupt */ |
| 404 | fs->int_status |= ASC_FIFO_STATUS_FULL_EMPTY; |
| 405 | asc_raise_irq(s); |
| 406 | } |
| 407 | } else { |
| 408 | fs->fifo[addr] = value; |
| 409 | } |
| 410 | } |
| 411 | |
| 412 | static const MemoryRegionOps asc_fifo_ops = { |
| 413 | .read = asc_fifo_read, |
| 414 | .write = asc_fifo_write, |
| 415 | .impl = { |
| 416 | .min_access_size = 1, |
| 417 | .max_access_size = 1, |
| 418 | }, |
| 419 | .endianness = DEVICE_BIG_ENDIAN, |
| 420 | }; |
| 421 | |
| 422 | static void asc_fifo_reset(ASCFIFOState *fs); |
| 423 | |
| 424 | static uint64_t asc_read(void *opaque, hwaddr addr, |
| 425 | unsigned size) |
| 426 | { |
| 427 | ASCState *s = opaque; |
| 428 | uint64_t prev, value; |
| 429 | |
| 430 | switch (addr) { |
| 431 | case ASC_VERSION: |
| 432 | switch (s->type) { |
| 433 | default: |
| 434 | case ASC_TYPE_ASC: |
| 435 | value = 0; |
| 436 | break; |
| 437 | case ASC_TYPE_EASC: |
| 438 | value = 0xb0; |
| 439 | break; |
| 440 | } |
| 441 | break; |
| 442 | case ASC_FIFOIRQ: |
| 443 | prev = (s->fifos[0].int_status & 0x3) | |
| 444 | (s->fifos[1].int_status & 0x3) << 2; |
| 445 | |
| 446 | s->fifos[0].int_status = 0; |
| 447 | s->fifos[1].int_status = 0; |
| 448 | asc_lower_irq(s); |
| 449 | value = prev; |
| 450 | break; |
| 451 | default: |
| 452 | value = s->regs[addr]; |
| 453 | break; |
| 454 | } |
| 455 | |
| 456 | trace_asc_read_reg(addr, size, value); |
| 457 | return value; |
| 458 | } |
| 459 | |
| 460 | static void asc_write(void *opaque, hwaddr addr, uint64_t value, |
| 461 | unsigned size) |
| 462 | { |
| 463 | ASCState *s = opaque; |
| 464 | |
| 465 | switch (addr) { |
| 466 | case ASC_MODE: |
| 467 | value &= 3; |
| 468 | if (value != s->regs[ASC_MODE]) { |
| 469 | asc_fifo_reset(&s->fifos[0]); |
| 470 | asc_fifo_reset(&s->fifos[1]); |
| 471 | asc_lower_irq(s); |
| 472 | if (value != 0) { |
| 473 | audio_be_set_active_out(s->audio_be, s->voice, 1); |
| 474 | } else { |
| 475 | audio_be_set_active_out(s->audio_be, s->voice, 0); |
| 476 | } |
| 477 | } |
| 478 | break; |
| 479 | case ASC_FIFOMODE: |
| 480 | if (value & 0x80) { |
| 481 | asc_fifo_reset(&s->fifos[0]); |
| 482 | asc_fifo_reset(&s->fifos[1]); |
| 483 | asc_lower_irq(s); |
| 484 | } |
| 485 | break; |
| 486 | case ASC_WAVECTRL: |
| 487 | break; |
| 488 | case ASC_VOLUME: |
| 489 | { |
| 490 | int vol = (value & 0xe0); |
| 491 | |
| 492 | audio_be_set_volume_out_lr(s->audio_be, s->voice, 0, vol, vol); |
| 493 | break; |
| 494 | } |
| 495 | } |
| 496 | |
| 497 | trace_asc_write_reg(addr, size, value); |
| 498 | s->regs[addr] = value; |
| 499 | } |
| 500 | |
| 501 | static const MemoryRegionOps asc_regs_ops = { |
| 502 | .read = asc_read, |
| 503 | .write = asc_write, |
| 504 | .endianness = DEVICE_BIG_ENDIAN, |
| 505 | .impl = { |
| 506 | .min_access_size = 1, |
| 507 | .max_access_size = 1, |
| 508 | } |
| 509 | }; |
| 510 | |
| 511 | static uint64_t asc_ext_read(void *opaque, hwaddr addr, |
| 512 | unsigned size) |
| 513 | { |
| 514 | ASCFIFOState *fs = opaque; |
| 515 | uint64_t value; |
| 516 | |
| 517 | value = fs->extregs[addr]; |
| 518 | |
| 519 | trace_asc_read_extreg('A' + fs->index, addr, size, value); |
| 520 | return value; |
| 521 | } |
| 522 | |
| 523 | static void asc_ext_write(void *opaque, hwaddr addr, uint64_t value, |
| 524 | unsigned size) |
| 525 | { |
| 526 | ASCFIFOState *fs = opaque; |
| 527 | |
| 528 | trace_asc_write_extreg('A' + fs->index, addr, size, value); |
| 529 | |
| 530 | fs->extregs[addr] = value; |
| 531 | } |
| 532 | |
| 533 | static const MemoryRegionOps asc_extregs_ops = { |
| 534 | .read = asc_ext_read, |
| 535 | .write = asc_ext_write, |
| 536 | .impl = { |
| 537 | .min_access_size = 1, |
| 538 | .max_access_size = 1, |
| 539 | }, |
| 540 | .endianness = DEVICE_BIG_ENDIAN, |
| 541 | }; |
| 542 | |
| 543 | static int asc_post_load(void *opaque, int version) |
| 544 | { |
| 545 | ASCState *s = ASC(opaque); |
| 546 | |
| 547 | if (s->regs[ASC_MODE] != 0) { |
| 548 | audio_be_set_active_out(s->audio_be, s->voice, 1); |
| 549 | } |
| 550 | |
| 551 | return 0; |
| 552 | } |
| 553 | |
| 554 | static const VMStateDescription vmstate_asc_fifo = { |
| 555 | .name = "apple-sound-chip.fifo", |
| 556 | .version_id = 0, |
| 557 | .minimum_version_id = 0, |
| 558 | .fields = (const VMStateField[]) { |
| 559 | VMSTATE_UINT8_ARRAY(fifo, ASCFIFOState, ASC_FIFO_SIZE), |
| 560 | VMSTATE_UINT8(int_status, ASCFIFOState), |
| 561 | VMSTATE_INT32(cnt, ASCFIFOState), |
| 562 | VMSTATE_INT32(wptr, ASCFIFOState), |
| 563 | VMSTATE_INT32(rptr, ASCFIFOState), |
| 564 | VMSTATE_UINT8_ARRAY(extregs, ASCFIFOState, ASC_EXTREG_SIZE), |
| 565 | VMSTATE_INT32(xa_cnt, ASCFIFOState), |
| 566 | VMSTATE_UINT8(xa_val, ASCFIFOState), |
| 567 | VMSTATE_UINT8(xa_flags, ASCFIFOState), |
| 568 | VMSTATE_INT16_ARRAY(xa_last, ASCFIFOState, 2), |
| 569 | VMSTATE_END_OF_LIST() |
| 570 | } |
| 571 | }; |
| 572 | |
| 573 | static const VMStateDescription vmstate_asc = { |
| 574 | .name = "apple-sound-chip", |
| 575 | .version_id = 0, |
| 576 | .minimum_version_id = 0, |
| 577 | .post_load = asc_post_load, |
| 578 | .fields = (const VMStateField[]) { |
| 579 | VMSTATE_STRUCT_ARRAY(fifos, ASCState, 2, 0, vmstate_asc_fifo, |
| 580 | ASCFIFOState), |
| 581 | VMSTATE_UINT8_ARRAY(regs, ASCState, ASC_REG_SIZE), |
| 582 | VMSTATE_INT64(fifo_empty_ns, ASCState), |
| 583 | VMSTATE_END_OF_LIST() |
| 584 | } |
| 585 | }; |
| 586 | |
| 587 | static void asc_fifo_reset(ASCFIFOState *fs) |
| 588 | { |
| 589 | fs->wptr = 0; |
| 590 | fs->rptr = 0; |
| 591 | fs->cnt = 0; |
| 592 | fs->xa_cnt = -1; |
| 593 | fs->int_status = 0; |
| 594 | } |
| 595 | |
| 596 | static void asc_fifo_init(ASCFIFOState *fs, int index) |
| 597 | { |
| 598 | ASCState *s = container_of(fs, ASCState, fifos[index]); |
| 599 | char *name; |
| 600 | |
| 601 | fs->index = index; |
| 602 | name = g_strdup_printf("asc.fifo%c", 'A' + index); |
| 603 | memory_region_init_io(&fs->mem_fifo, OBJECT(s), &asc_fifo_ops, fs, |
| 604 | name, ASC_FIFO_SIZE); |
| 605 | g_free(name); |
| 606 | |
| 607 | name = g_strdup_printf("asc.extregs%c", 'A' + index); |
| 608 | memory_region_init_io(&fs->mem_extregs, OBJECT(s), &asc_extregs_ops, |
| 609 | fs, name, ASC_EXTREG_SIZE); |
| 610 | g_free(name); |
| 611 | } |
| 612 | |
| 613 | static void asc_reset_hold(Object *obj, ResetType type) |
| 614 | { |
| 615 | ASCState *s = ASC(obj); |
| 616 | |
| 617 | audio_be_set_active_out(s->audio_be, s->voice, 0); |
| 618 | |
| 619 | memset(s->regs, 0, sizeof(s->regs)); |
| 620 | asc_fifo_reset(&s->fifos[0]); |
| 621 | asc_fifo_reset(&s->fifos[1]); |
| 622 | s->fifo_empty_ns = 0; |
| 623 | |
| 624 | if (s->type == ASC_TYPE_ASC) { |
| 625 | /* FIFO half full IRQs enabled by default */ |
| 626 | s->fifos[0].extregs[ASC_EXTREGS_INTCTRL] = 1; |
| 627 | s->fifos[1].extregs[ASC_EXTREGS_INTCTRL] = 1; |
| 628 | } |
| 629 | } |
| 630 | |
| 631 | static void asc_unrealize(DeviceState *dev) |
| 632 | { |
| 633 | ASCState *s = ASC(dev); |
| 634 | |
| 635 | g_free(s->mixbuf); |
| 636 | g_free(s->silentbuf); |
| 637 | } |
| 638 | |
| 639 | static void asc_realize(DeviceState *dev, Error **errp) |
| 640 | { |
| 641 | ASCState *s = ASC(dev); |
| 642 | struct audsettings as; |
| 643 | |
| 644 | if (!audio_be_check(&s->audio_be, errp)) { |
| 645 | return; |
| 646 | } |
| 647 | |
| 648 | as.freq = ASC_FREQ; |
| 649 | as.nchannels = 2; |
| 650 | as.fmt = AUDIO_FORMAT_U8; |
| 651 | as.big_endian = HOST_BIG_ENDIAN; |
| 652 | |
| 653 | s->voice = audio_be_open_out(s->audio_be, s->voice, "asc.out", s, asc_out_cb, |
| 654 | &as); |
| 655 | if (!s->voice) { |
| 656 | error_setg(errp, "Initializing audio stream failed"); |
| 657 | return; |
| 658 | } |
| 659 | |
| 660 | s->shift = 1; |
| 661 | s->samples = audio_be_get_buffer_size_out(s->audio_be, s->voice) >> s->shift; |
| 662 | s->mixbuf = g_malloc0(s->samples << s->shift); |
| 663 | |
| 664 | s->silentbuf = g_malloc(s->samples << s->shift); |
| 665 | memset(s->silentbuf, 0x80, s->samples << s->shift); |
| 666 | |
| 667 | /* Add easc registers if required */ |
| 668 | if (s->type == ASC_TYPE_EASC) { |
| 669 | memory_region_add_subregion(&s->asc, ASC_EXTREG_OFFSET, |
| 670 | &s->fifos[0].mem_extregs); |
| 671 | memory_region_add_subregion(&s->asc, |
| 672 | ASC_EXTREG_OFFSET + ASC_EXTREG_SIZE, |
| 673 | &s->fifos[1].mem_extregs); |
| 674 | } |
| 675 | } |
| 676 | |
| 677 | static void asc_init(Object *obj) |
| 678 | { |
| 679 | ASCState *s = ASC(obj); |
| 680 | SysBusDevice *sbd = SYS_BUS_DEVICE(obj); |
| 681 | |
| 682 | memory_region_init(&s->asc, OBJECT(obj), "asc", ASC_SIZE); |
| 683 | |
| 684 | asc_fifo_init(&s->fifos[0], 0); |
| 685 | asc_fifo_init(&s->fifos[1], 1); |
| 686 | |
| 687 | memory_region_add_subregion(&s->asc, ASC_FIFO_OFFSET, |
| 688 | &s->fifos[0].mem_fifo); |
| 689 | memory_region_add_subregion(&s->asc, |
| 690 | ASC_FIFO_OFFSET + ASC_FIFO_SIZE, |
| 691 | &s->fifos[1].mem_fifo); |
| 692 | |
| 693 | memory_region_init_io(&s->mem_regs, OBJECT(obj), &asc_regs_ops, s, |
| 694 | "asc.regs", ASC_REG_SIZE); |
| 695 | memory_region_add_subregion(&s->asc, ASC_REG_OFFSET, &s->mem_regs); |
| 696 | |
| 697 | sysbus_init_irq(sbd, &s->irq); |
| 698 | sysbus_init_mmio(sbd, &s->asc); |
| 699 | } |
| 700 | |
| 701 | static const Property asc_properties[] = { |
| 702 | DEFINE_AUDIO_PROPERTIES(ASCState, audio_be), |
| 703 | DEFINE_PROP_UINT8("asctype", ASCState, type, ASC_TYPE_ASC), |
| 704 | }; |
| 705 | |
| 706 | static void asc_class_init(ObjectClass *oc, const void *data) |
| 707 | { |
| 708 | DeviceClass *dc = DEVICE_CLASS(oc); |
| 709 | ResettableClass *rc = RESETTABLE_CLASS(oc); |
| 710 | |
| 711 | dc->realize = asc_realize; |
| 712 | dc->unrealize = asc_unrealize; |
| 713 | set_bit(DEVICE_CATEGORY_SOUND, dc->categories); |
| 714 | dc->vmsd = &vmstate_asc; |
| 715 | device_class_set_props(dc, asc_properties); |
| 716 | rc->phases.hold = asc_reset_hold; |
| 717 | } |
| 718 | |
| 719 | static const TypeInfo asc_info_types[] = { |
| 720 | { |
| 721 | .name = TYPE_ASC, |
| 722 | .parent = TYPE_SYS_BUS_DEVICE, |
| 723 | .instance_size = sizeof(ASCState), |
| 724 | .instance_init = asc_init, |
| 725 | .class_init = asc_class_init, |
| 726 | }, |
| 727 | }; |
| 728 | |
| 729 | DEFINE_TYPES(asc_info_types) |