/* * QEMU Apple Sound Chip emulation * * Apple Sound Chip (ASC) 344S0063 * Enhanced Apple Sound Chip (EASC) 343S1063 * * Copyright (c) 2012-2018 Laurent Vivier * Copyright (c) 2022 Mark Cave-Ayland * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "qemu/osdep.h" #include "qemu/timer.h" #include "hw/sysbus.h" #include "hw/irq.h" #include "audio/audio.h" #include "hw/audio/asc.h" #include "hw/qdev-properties.h" #include "migration/vmstate.h" #include "trace.h" /* * Linux doesn't provide information about ASC, see arch/m68k/mac/macboing.c * and arch/m68k/include/asm/mac_asc.h * * best information is coming from MAME: * https://github.com/mamedev/mame/blob/master/src/devices/sound/asc.h * https://github.com/mamedev/mame/blob/master/src/devices/sound/asc.cpp * Emulation by R. Belmont * or MESS: * http://mess.redump.net/mess/driver_info/easc * * 0x800: VERSION * 0x801: MODE * 1=FIFO mode, * 2=wavetable mode * 0x802: CONTROL * bit 0=analog or PWM output, * 1=stereo/mono, * 7=processing time exceeded * 0x803: FIFO MODE * bit 7=clear FIFO, * bit 1="non-ROM companding", * bit 0="ROM companding") * 0x804: FIFO IRQ STATUS * bit 0=ch A 1/2 full, * 1=ch A full, * 2=ch B 1/2 full, * 3=ch B full) * 0x805: WAVETABLE CONTROL * bits 0-3 wavetables 0-3 start * 0x806: VOLUME * bits 2-4 = 3 bit internal ASC volume, * bits 5-7 = volume control sent to Sony sound chip * 0x807: CLOCK RATE * 0 = Mac 22257 Hz, * 1 = undefined, * 2 = 22050 Hz, * 3 = 44100 Hz * 0x80a: PLAY REC A * 0x80f: TEST * bits 6-7 = digital test, * bits 4-5 = analog test * 0x810: WAVETABLE 0 PHASE * big-endian 9.15 fixed-point, only 24 bits valid * 0x814: WAVETABLE 0 INCREMENT * big-endian 9.15 fixed-point, only 24 bits valid * 0x818: WAVETABLE 1 PHASE * 0x81C: WAVETABLE 1 INCREMENT * 0x820: WAVETABLE 2 PHASE * 0x824: WAVETABLE 2 INCREMENT * 0x828: WAVETABLE 3 PHASE * 0x82C: WAVETABLE 3 INCREMENT * 0x830: UNKNOWN START * NetBSD writes Wavetable data here (are there more * wavetables/channels than we know about?) * 0x857: UNKNOWN END */ #define ASC_SIZE 0x2000 enum { ASC_VERSION = 0x00, ASC_MODE = 0x01, ASC_CONTROL = 0x02, ASC_FIFOMODE = 0x03, ASC_FIFOIRQ = 0x04, ASC_WAVECTRL = 0x05, ASC_VOLUME = 0x06, ASC_CLOCK = 0x07, ASC_PLAYRECA = 0x0a, ASC_TEST = 0x0f, ASC_WAVETABLE = 0x10 }; #define ASC_FIFO_STATUS_HALF_FULL 1 #define ASC_FIFO_STATUS_FULL_EMPTY 2 #define ASC_EXTREGS_FIFOCTRL 0x8 #define ASC_EXTREGS_INTCTRL 0x9 #define ASC_EXTREGS_CDXA_DECOMP_FILT 0x10 #define ASC_FIFO_CYCLE_TIME ((NANOSECONDS_PER_SECOND / ASC_FREQ) * \ 0x400) static void asc_raise_irq(ASCState *s) { qemu_set_irq(s->irq, 1); } static void asc_lower_irq(ASCState *s) { qemu_set_irq(s->irq, 0); } static uint8_t asc_fifo_get(ASCFIFOState *fs) { ASCState *s = container_of(fs, ASCState, fifos[fs->index]); bool fifo_half_irq_enabled = fs->extregs[ASC_EXTREGS_INTCTRL] & 1; uint8_t val; assert(fs->cnt); val = fs->fifo[fs->rptr]; trace_asc_fifo_get('A' + fs->index, fs->rptr, fs->cnt, val); fs->rptr++; fs->rptr &= 0x3ff; fs->cnt--; if (fs->cnt <= 0x1ff) { /* FIFO less than half full */ fs->int_status |= ASC_FIFO_STATUS_HALF_FULL; } else { /* FIFO more than half full */ fs->int_status &= ~ASC_FIFO_STATUS_HALF_FULL; } if (fs->cnt == 0x1ff && fifo_half_irq_enabled) { /* Raise FIFO half full IRQ */ asc_raise_irq(s); } if (fs->cnt == 0) { /* Raise FIFO empty IRQ */ fs->int_status |= ASC_FIFO_STATUS_FULL_EMPTY; asc_raise_irq(s); } return val; } static int generate_fifo(ASCState *s, int maxsamples) { int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); uint8_t *buf = s->mixbuf; int i, wcount = 0; while (wcount < maxsamples) { uint8_t val; int16_t d, f0, f1; int32_t t; int shift, filter; bool hasdata = false; for (i = 0; i < 2; i++) { ASCFIFOState *fs = &s->fifos[i]; switch (fs->extregs[ASC_EXTREGS_FIFOCTRL] & 0x83) { case 0x82: /* * CD-XA BRR mode: decompress 15 bytes into 28 16-bit * samples */ if (!fs->cnt) { val = 0x80; break; } if (fs->xa_cnt == -1) { /* Start of packet, get flags */ fs->xa_flags = asc_fifo_get(fs); fs->xa_cnt = 0; } shift = fs->xa_flags & 0xf; filter = fs->xa_flags >> 4; f0 = (int8_t)fs->extregs[ASC_EXTREGS_CDXA_DECOMP_FILT + (filter << 1) + 1]; f1 = (int8_t)fs->extregs[ASC_EXTREGS_CDXA_DECOMP_FILT + (filter << 1)]; if ((fs->xa_cnt & 1) == 0) { if (!fs->cnt) { val = 0x80; break; } fs->xa_val = asc_fifo_get(fs); d = (fs->xa_val & 0xf) << 12; } else { d = (fs->xa_val & 0xf0) << 8; } t = (d >> shift) + (((fs->xa_last[0] * f0) + (fs->xa_last[1] * f1) + 32) >> 6); if (t < -32768) { t = -32768; } else if (t > 32767) { t = 32767; } /* * CD-XA BRR generates 16-bit signed output, so convert to * 8-bit before writing to buffer. Does real hardware do the * same? */ val = (uint8_t)(t / 256) ^ 0x80; hasdata = true; fs->xa_cnt++; fs->xa_last[1] = fs->xa_last[0]; fs->xa_last[0] = (int16_t)t; if (fs->xa_cnt == 28) { /* End of packet */ fs->xa_cnt = -1; } break; default: /* fallthrough */ case 0x80: /* Raw mode */ if (fs->cnt) { val = asc_fifo_get(fs); hasdata = true; } else { val = 0x80; } break; } buf[wcount * 2 + i] = val; } if (!hasdata) { break; } wcount++; } /* * MacOS (un)helpfully leaves the FIFO engine running even when it has * finished writing out samples, but still expects the FIFO empty * interrupts to be generated for each FIFO cycle (without these interrupts * MacOS will freeze) */ if (s->fifos[0].cnt == 0 && s->fifos[1].cnt == 0) { if (!s->fifo_empty_ns) { /* FIFO has completed first empty cycle */ s->fifo_empty_ns = now; } else if (now > (s->fifo_empty_ns + ASC_FIFO_CYCLE_TIME)) { /* FIFO has completed entire cycle with no data */ s->fifos[0].int_status |= ASC_FIFO_STATUS_HALF_FULL | ASC_FIFO_STATUS_FULL_EMPTY; s->fifos[1].int_status |= ASC_FIFO_STATUS_HALF_FULL | ASC_FIFO_STATUS_FULL_EMPTY; s->fifo_empty_ns = now; asc_raise_irq(s); } } else { /* FIFO contains data, reset empty time */ s->fifo_empty_ns = 0; } return wcount; } static int generate_wavetable(ASCState *s, int maxsamples) { uint8_t *buf = s->mixbuf; int channel, count = 0; while (count < maxsamples) { uint32_t left = 0, right = 0; uint8_t sample; for (channel = 0; channel < 4; channel++) { ASCFIFOState *fs = &s->fifos[channel >> 1]; int chanreg = ASC_WAVETABLE + (channel << 3); uint32_t phase, incr, offset; phase = ldl_be_p(&s->regs[chanreg]); incr = ldl_be_p(&s->regs[chanreg + sizeof(uint32_t)]); phase += incr; offset = (phase >> 15) & 0x1ff; sample = fs->fifo[0x200 * (channel >> 1) + offset]; stl_be_p(&s->regs[chanreg], phase); left += sample; right += sample; } buf[count * 2] = left >> 2; buf[count * 2 + 1] = right >> 2; count++; } return count; } static void asc_out_cb(void *opaque, int free_b) { ASCState *s = opaque; int samples, generated; if (free_b == 0) { return; } samples = MIN(s->samples, free_b >> s->shift); switch (s->regs[ASC_MODE] & 3) { default: /* Off */ generated = 0; break; case 1: /* FIFO mode */ generated = generate_fifo(s, samples); break; case 2: /* Wave table mode */ generated = generate_wavetable(s, samples); break; } if (!generated) { /* Workaround for audio underflow bug on Windows dsound backend */ int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); int silent_samples = muldiv64(now - s->fifo_empty_ns, NANOSECONDS_PER_SECOND, ASC_FREQ); if (silent_samples > ASC_FIFO_CYCLE_TIME / 2) { /* * No new FIFO data within half a cycle time (~23ms) so fill the * entire available buffer with silence. This prevents an issue * with the Windows dsound backend whereby the sound appears to * loop because the FIFO has run out of data, and the driver * reuses the stale content in its circular audio buffer. */ AUD_write(s->voice, s->silentbuf, samples << s->shift); } return; } AUD_write(s->voice, s->mixbuf, generated << s->shift); } static uint64_t asc_fifo_read(void *opaque, hwaddr addr, unsigned size) { ASCFIFOState *fs = opaque; trace_asc_read_fifo('A' + fs->index, addr, size, fs->fifo[addr]); return fs->fifo[addr]; } static void asc_fifo_write(void *opaque, hwaddr addr, uint64_t value, unsigned size) { ASCFIFOState *fs = opaque; ASCState *s = container_of(fs, ASCState, fifos[fs->index]); bool fifo_half_irq_enabled = fs->extregs[ASC_EXTREGS_INTCTRL] & 1; trace_asc_write_fifo('A' + fs->index, addr, size, fs->wptr, fs->cnt, value); if (s->regs[ASC_MODE] == 1) { fs->fifo[fs->wptr++] = value; fs->wptr &= 0x3ff; fs->cnt++; if (fs->cnt <= 0x1ff) { /* FIFO less than half full */ fs->int_status |= ASC_FIFO_STATUS_HALF_FULL; } else { /* FIFO at least half full */ fs->int_status &= ~ASC_FIFO_STATUS_HALF_FULL; } if (fs->cnt == 0x200 && fifo_half_irq_enabled) { /* Raise FIFO half full interrupt */ asc_raise_irq(s); } if (fs->cnt == 0x3ff) { /* Raise FIFO full interrupt */ fs->int_status |= ASC_FIFO_STATUS_FULL_EMPTY; asc_raise_irq(s); } } else { fs->fifo[addr] = value; } return; } static const MemoryRegionOps asc_fifo_ops = { .read = asc_fifo_read, .write = asc_fifo_write, .impl = { .min_access_size = 1, .max_access_size = 1, }, .endianness = DEVICE_BIG_ENDIAN, }; static void asc_fifo_reset(ASCFIFOState *fs); static uint64_t asc_read(void *opaque, hwaddr addr, unsigned size) { ASCState *s = opaque; uint64_t prev, value; switch (addr) { case ASC_VERSION: switch (s->type) { default: case ASC_TYPE_ASC: value = 0; break; case ASC_TYPE_EASC: value = 0xb0; break; } break; case ASC_FIFOIRQ: prev = (s->fifos[0].int_status & 0x3) | (s->fifos[1].int_status & 0x3) << 2; s->fifos[0].int_status = 0; s->fifos[1].int_status = 0; asc_lower_irq(s); value = prev; break; default: value = s->regs[addr]; break; } trace_asc_read_reg(addr, size, value); return value; } static void asc_write(void *opaque, hwaddr addr, uint64_t value, unsigned size) { ASCState *s = opaque; switch (addr) { case ASC_MODE: value &= 3; if (value != s->regs[ASC_MODE]) { asc_fifo_reset(&s->fifos[0]); asc_fifo_reset(&s->fifos[1]); asc_lower_irq(s); if (value != 0) { AUD_set_active_out(s->voice, 1); } else { AUD_set_active_out(s->voice, 0); } } break; case ASC_FIFOMODE: if (value & 0x80) { asc_fifo_reset(&s->fifos[0]); asc_fifo_reset(&s->fifos[1]); asc_lower_irq(s); } break; case ASC_WAVECTRL: break; case ASC_VOLUME: { int vol = (value & 0xe0); AUD_set_volume_out(s->voice, 0, vol, vol); break; } } trace_asc_write_reg(addr, size, value); s->regs[addr] = value; } static const MemoryRegionOps asc_regs_ops = { .read = asc_read, .write = asc_write, .endianness = DEVICE_BIG_ENDIAN, .impl = { .min_access_size = 1, .max_access_size = 1, } }; static uint64_t asc_ext_read(void *opaque, hwaddr addr, unsigned size) { ASCFIFOState *fs = opaque; uint64_t value; value = fs->extregs[addr]; trace_asc_read_extreg('A' + fs->index, addr, size, value); return value; } static void asc_ext_write(void *opaque, hwaddr addr, uint64_t value, unsigned size) { ASCFIFOState *fs = opaque; trace_asc_write_extreg('A' + fs->index, addr, size, value); fs->extregs[addr] = value; } static const MemoryRegionOps asc_extregs_ops = { .read = asc_ext_read, .write = asc_ext_write, .impl = { .min_access_size = 1, .max_access_size = 1, }, .endianness = DEVICE_BIG_ENDIAN, }; static int asc_post_load(void *opaque, int version) { ASCState *s = ASC(opaque); if (s->regs[ASC_MODE] != 0) { AUD_set_active_out(s->voice, 1); } return 0; } static const VMStateDescription vmstate_asc_fifo = { .name = "apple-sound-chip.fifo", .version_id = 0, .minimum_version_id = 0, .fields = (const VMStateField[]) { VMSTATE_UINT8_ARRAY(fifo, ASCFIFOState, ASC_FIFO_SIZE), VMSTATE_UINT8(int_status, ASCFIFOState), VMSTATE_INT32(cnt, ASCFIFOState), VMSTATE_INT32(wptr, ASCFIFOState), VMSTATE_INT32(rptr, ASCFIFOState), VMSTATE_UINT8_ARRAY(extregs, ASCFIFOState, ASC_EXTREG_SIZE), VMSTATE_INT32(xa_cnt, ASCFIFOState), VMSTATE_UINT8(xa_val, ASCFIFOState), VMSTATE_UINT8(xa_flags, ASCFIFOState), VMSTATE_INT16_ARRAY(xa_last, ASCFIFOState, 2), VMSTATE_END_OF_LIST() } }; static const VMStateDescription vmstate_asc = { .name = "apple-sound-chip", .version_id = 0, .minimum_version_id = 0, .post_load = asc_post_load, .fields = (const VMStateField[]) { VMSTATE_STRUCT_ARRAY(fifos, ASCState, 2, 0, vmstate_asc_fifo, ASCFIFOState), VMSTATE_UINT8_ARRAY(regs, ASCState, ASC_REG_SIZE), VMSTATE_INT64(fifo_empty_ns, ASCState), VMSTATE_END_OF_LIST() } }; static void asc_fifo_reset(ASCFIFOState *fs) { fs->wptr = 0; fs->rptr = 0; fs->cnt = 0; fs->xa_cnt = -1; fs->int_status = 0; } static void asc_fifo_init(ASCFIFOState *fs, int index) { ASCState *s = container_of(fs, ASCState, fifos[index]); char *name; fs->index = index; name = g_strdup_printf("asc.fifo%c", 'A' + index); memory_region_init_io(&fs->mem_fifo, OBJECT(s), &asc_fifo_ops, fs, name, ASC_FIFO_SIZE); g_free(name); name = g_strdup_printf("asc.extregs%c", 'A' + index); memory_region_init_io(&fs->mem_extregs, OBJECT(s), &asc_extregs_ops, fs, name, ASC_EXTREG_SIZE); g_free(name); } static void asc_reset_hold(Object *obj, ResetType type) { ASCState *s = ASC(obj); AUD_set_active_out(s->voice, 0); memset(s->regs, 0, sizeof(s->regs)); asc_fifo_reset(&s->fifos[0]); asc_fifo_reset(&s->fifos[1]); s->fifo_empty_ns = 0; if (s->type == ASC_TYPE_ASC) { /* FIFO half full IRQs enabled by default */ s->fifos[0].extregs[ASC_EXTREGS_INTCTRL] = 1; s->fifos[1].extregs[ASC_EXTREGS_INTCTRL] = 1; } } static void asc_unrealize(DeviceState *dev) { ASCState *s = ASC(dev); g_free(s->mixbuf); g_free(s->silentbuf); AUD_remove_card(&s->card); } static void asc_realize(DeviceState *dev, Error **errp) { ASCState *s = ASC(dev); struct audsettings as; if (!AUD_register_card("Apple Sound Chip", &s->card, errp)) { return; } as.freq = ASC_FREQ; as.nchannels = 2; as.fmt = AUDIO_FORMAT_U8; as.endianness = AUDIO_HOST_ENDIANNESS; s->voice = AUD_open_out(&s->card, s->voice, "asc.out", s, asc_out_cb, &as); s->shift = 1; s->samples = AUD_get_buffer_size_out(s->voice) >> s->shift; s->mixbuf = g_malloc0(s->samples << s->shift); s->silentbuf = g_malloc0(s->samples << s->shift); memset(s->silentbuf, 0x80, s->samples << s->shift); /* Add easc registers if required */ if (s->type == ASC_TYPE_EASC) { memory_region_add_subregion(&s->asc, ASC_EXTREG_OFFSET, &s->fifos[0].mem_extregs); memory_region_add_subregion(&s->asc, ASC_EXTREG_OFFSET + ASC_EXTREG_SIZE, &s->fifos[1].mem_extregs); } } static void asc_init(Object *obj) { ASCState *s = ASC(obj); SysBusDevice *sbd = SYS_BUS_DEVICE(obj); memory_region_init(&s->asc, OBJECT(obj), "asc", ASC_SIZE); asc_fifo_init(&s->fifos[0], 0); asc_fifo_init(&s->fifos[1], 1); memory_region_add_subregion(&s->asc, ASC_FIFO_OFFSET, &s->fifos[0].mem_fifo); memory_region_add_subregion(&s->asc, ASC_FIFO_OFFSET + ASC_FIFO_SIZE, &s->fifos[1].mem_fifo); memory_region_init_io(&s->mem_regs, OBJECT(obj), &asc_regs_ops, s, "asc.regs", ASC_REG_SIZE); memory_region_add_subregion(&s->asc, ASC_REG_OFFSET, &s->mem_regs); sysbus_init_irq(sbd, &s->irq); sysbus_init_mmio(sbd, &s->asc); } static const Property asc_properties[] = { DEFINE_AUDIO_PROPERTIES(ASCState, card), DEFINE_PROP_UINT8("asctype", ASCState, type, ASC_TYPE_ASC), DEFINE_PROP_END_OF_LIST(), }; static void asc_class_init(ObjectClass *oc, void *data) { DeviceClass *dc = DEVICE_CLASS(oc); ResettableClass *rc = RESETTABLE_CLASS(oc); dc->realize = asc_realize; dc->unrealize = asc_unrealize; set_bit(DEVICE_CATEGORY_SOUND, dc->categories); dc->vmsd = &vmstate_asc; device_class_set_props(dc, asc_properties); rc->phases.hold = asc_reset_hold; } static const TypeInfo asc_info_types[] = { { .name = TYPE_ASC, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(ASCState), .instance_init = asc_init, .class_init = asc_class_init, }, }; DEFINE_TYPES(asc_info_types)