 ed1e71dac9
			
		
	
	
		ed1e71dac9
		
	
	
	
	
		
			
			Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
		
			
				
	
	
		
			728 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			728 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * QEMU Apple Sound Chip emulation
 | |
|  *
 | |
|  * Apple Sound Chip (ASC) 344S0063
 | |
|  * Enhanced Apple Sound Chip (EASC) 343S1063
 | |
|  *
 | |
|  * Copyright (c) 2012-2018 Laurent Vivier <laurent@vivier.eu>
 | |
|  * Copyright (c) 2022 Mark Cave-Ayland <mark.cave-ayland@ilande.co.uk>
 | |
|  *
 | |
|  * 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)
 |