On Sun, Feb 16, 2020 at 5:32 PM John Arbuckle wrote: > Signed-off-by: John Arbuckle > --- > v3 changes: > - Updated the location of patched code in hw/ppc/kconfig. > - Removed setting the props variable in screamer.c. > - Removed the screamer_properties variable in screamer.c. > > v2 changes: > - Fixed a bug that prevented the sampling rate from being changed. > > hw/audio/Kconfig | 3 + > hw/audio/Makefile.objs | 2 + > hw/audio/screamer.c | 983 > ++++++++++++++++++++++++++++++++++++++++++ > hw/misc/macio/macio.c | 35 +- > hw/ppc/Kconfig | 1 + > hw/ppc/mac.h | 5 + > include/hw/audio/screamer.h | 42 ++ > include/hw/misc/macio/macio.h | 2 + > 8 files changed, 1072 insertions(+), 1 deletion(-) > create mode 100644 hw/audio/screamer.c > create mode 100644 include/hw/audio/screamer.h > > diff --git a/hw/audio/Kconfig b/hw/audio/Kconfig > index e9c6fed826..196da6c3fe 100644 > --- a/hw/audio/Kconfig > +++ b/hw/audio/Kconfig > @@ -50,3 +50,6 @@ config CS4231 > > config MARVELL_88W8618 > bool > + > +config SCREAMER > + bool > diff --git a/hw/audio/Makefile.objs b/hw/audio/Makefile.objs > index 63db383709..55906886bc 100644 > --- a/hw/audio/Makefile.objs > +++ b/hw/audio/Makefile.objs > @@ -15,4 +15,6 @@ common-obj-$(CONFIG_CS4231) += cs4231.o > common-obj-$(CONFIG_MARVELL_88W8618) += marvell_88w8618.o > common-obj-$(CONFIG_MILKYMIST) += milkymist-ac97.o > > +common-obj-$(CONFIG_SCREAMER) += screamer.o > + > common-obj-y += soundhw.o > diff --git a/hw/audio/screamer.c b/hw/audio/screamer.c > new file mode 100644 > index 0000000000..ad4aba12eb > --- /dev/null > +++ b/hw/audio/screamer.c > @@ -0,0 +1,983 @@ > +/* > + * File: Screamer.c > + * Description: Implement the Screamer sound chip used in Apple > Macintoshes. > + * It works by filling a buffer, then playing the buffer. > + */ > + > +#include "qemu/osdep.h" > +#include "audio/audio.h" > +#include "hw/hw.h" > +#include "hw/irq.h" > +#include > +#include "hw/ppc/mac.h" > +#include "hw/qdev-properties.h" > +#include "migration/vmstate.h" > +#include "include/hw/audio/screamer.h" > + > +#define DEBUG_SCREAMER 0 > +#define DPRINTF(fmt, ...) \ > +do { if (DEBUG_SCREAMER) { printf(fmt , ## __VA_ARGS__); } } while (0) > + > +#define SOUND_CONTROL_REG 0 > +#define CODEC_CONTROL_REG 1 > +#define CODEC_STATUS_REG 2 > +#define CLIP_COUNT_REG 3 > +#define BYTE_SWAP_REG 4 > +#define FRAME_COUNT_REG 5 > + > +#define AWACS_BUSY 0x01000000 > + > +/* Used with AWACS register 1 */ > +#define RECALIBRATE 0x004 > +#define LOOPTHRU 0x040 > +#define SPEAKER_MUTE 0x080 > +#define HEADPHONE_MUTE 0x200 > +#define OUTPUT_ZERO 0x400 > +#define OUTPUT_ONE 0x800 > +#define PARALLEL_OUTPUT 0xc00 > + > +/* Function prototypes */ > +static uint32_t set_busy_bit(uint32_t value, int bit); > +static uint32_t set_part_ready_bit(uint32_t value, int bit_value); > +static uint32_t set_revision(uint32_t input_value); > +static uint32_t set_manufacturer(uint32_t input_value); > +static int get_sampling_rate(ScreamerState *s); > +static uint32_t get_frame_count_reg(ScreamerState *s); > +static void add_to_speaker_buffer(DBDMA_io *io); > +static void dma_request(DBDMA_io *io); > + > + > +/**************************** Getters *************************/ > + > +/* Returns the codec control register's encoded AWACS address */ > +static uint8_t get_codec_control_address(uint32_t value) > +{ > + uint8_t return_value; > + return_value = (value >> 12) & 0x00000fff; > + return return_value; > +} > + > + > +static uint32_t get_sound_control_reg(ScreamerState *s) > +{ > + DPRINTF("%s() called - returned 0x%x\n", __func__, s->sound_control); > + return s->sound_control; > +} > + > +/* The AWACS registers are accessed thru this register */ > +static uint32_t get_codec_control_reg(ScreamerState *s) > +{ > + int awacs_register = get_codec_control_address(s->codec_control); > + uint32_t return_value = s->awacs[awacs_register]; > + return_value = set_busy_bit(return_value, 0); /* Tell CPU we are > ready */ > + DPRINTF("%s() called - returned 0x%x\tAWACS register: %d\n", __func__, > + return_value, awacs_register); > + return return_value; > +} > + > +/* > + * Determines if the readback bit is set. > + * It is used by the Codec Control register. > + */ > +static bool readback_enabled(ScreamerState *s) > +{ > +/* Note: bit zero is the readback enabled bit */ > + if (s->awacs[7] & 1) { > + return true; > + } else { > + return false; > + } > +} > + > +static uint32_t get_codec_status_reg(ScreamerState *s) > +{ > + uint32_t return_value; > + > + /* if in readback mode - return AWACS register value */ > + if (readback_enabled(s)) { > + int awacs_register = (s->awacs[7] & 0xe) >> 1; > + s->awacs[7] = s->awacs[7] & 0xfffffffe; /* turn off readback mode > */ > + return_value = s->awacs[awacs_register] << 4; > + DPRINTF("readback enable bit is set, returning AWACS register > %d\t" > + "value:0x%x\n", awacs_register, return_value); > + > + return return_value; > + } > + > + /* Tell CPU we are ready */ > + return_value = set_part_ready_bit(s->codec_status, 1); > + > + /* Set Revision to Screamer */ > + return_value = set_revision(return_value); > + > + /* Set the Manufacturer to Crystal */ > + return_value = set_manufacturer(return_value); > + DPRINTF("%s() called - returned 0x%x\n", __func__, return_value); > + > + return return_value; > +} > + > +static uint32_t get_clip_count_reg(ScreamerState *s) > +{ > + DPRINTF("%s() called - returned 0x%x\n", __func__, s->clip_count); > + uint32_t return_value; > + return_value = s->clip_count; > + /* This is reset everytime it is read */ > + s->clip_count = 0; > + return return_value; > +} > + > +static uint32_t get_byte_swap_reg(ScreamerState *s) > +{ > + DPRINTF("%s() called - returned 0x%x\n", __func__, s->byte_swap); > + /* > + * If all you hear is noise, it could be this register reporting the > + * wrong value. > + */ > + return s->byte_swap ? 0 : 1; > +} > + > +/* > + * Returns the frame (sample) count > + */ > +static uint32_t get_frame_count_reg(ScreamerState *s) > +{ > + DPRINTF("%s() called - returned 0x%x\n", __func__, s->frame_count); > + return s->frame_count; > +} > + > +static uint8_t get_left_vol(uint32_t value) > +{ > + return value & 0xf; > +} > + > +static uint8_t get_right_vol(uint32_t value) > +{ > + return value & 0x3c0 >> 6; > +} > + > +/* > + * Returns the sampling rate. > + * If the audio is playing back too fast or too slow, this function may > be the > + * cause. > + */ > +static int get_sampling_rate(ScreamerState *s) > +{ > + uint32_t screamer_rate = s->sound_control & 0x700; > + int return_value; > + > + /* All return values are in Hertz */ > + switch (screamer_rate) { > + case 0x0: > + return_value = 44100; > + break; > + case 0x100: > + return_value = 29400; > + break; > + case 0x200: > + return_value = 22050; > + break; > + case 0x300: > + return_value = 17640; > + break; > + case 0x400: > + return_value = 14700; > + break; > + case 0x500: > + return_value = 11025; > + break; > + case 0x600: > + return_value = 8820; > + break; > + case 0x700: > + return_value = 7350; > + break; > + default: > + DPRINTF("get_sampling_rate() unknown value: 0x%x\nDefaulting to" > + " 44100 Hz.\n", screamer_rate); > + return 44100; > +} > + DPRINTF("%s() called - returning %dHz\n", __func__, return_value); > + return return_value; > +} > + > +/**************************** End of getters *************************/ > + > +/***************************** Speaker call back > *************************/ > + > +/* resets the play and buffer position markers */ > +static void reset_markers(ScreamerState *s) > +{ > + s->spk_play_position = 0; > + s->spk_buffer_position = 0; > +} > + > + > +/* Sends the samples to the host for playing */ > +static void send_samples_to_host(ScreamerState *s, int max_samples) > +{ > + int write_length, requested_length; > + requested_length = MIN(max_samples, (s->spk_buffer_position - > + s->spk_play_position)); > + write_length = AUD_write(s->speaker_voice, > + &s->spk_buffer[s->spk_play_position], > + requested_length); > + DPRINTF("requested length: %d\twrite length: %d\t", > + requested_length, write_length); > + s->spk_play_position += write_length; > + DPRINTF("AUD_write %d/%d\n", s->spk_play_position, > s->spk_buffer_position); > + s->frame_count += write_length; > +} > + > + > +/* > + * Called by QEMU's audio system to tell the output backend to send > samples > + * from the buffer to the host sound system. > + * opaque: a pointer to the ScreamerState instance. > + * max_samples: the number of samples that can be sent to the hardware > buffer. > + */ > +static void speaker_callback(void *opaque, int max_samples) > +{ > + ScreamerState *s = (ScreamerState *) opaque; > + > + /* if we have more samples to play */ > + if (s->spk_buffer_position > 0) { > + if (s->spk_buffer_position > s->spk_play_position) { > + DPRINTF("%s() called - max_samples: %d\n", __func__, > max_samples); > + send_samples_to_host(s, max_samples); > + } > + if (s->spk_play_position >= s->spk_buffer_position) { > + DPRINTF("done playing buffer\n"); > + DPRINTF("pp: %d\tbp: %d\n", s->spk_play_position, > + s->spk_buffer_position); > + if (s->spk_play_position > s->spk_buffer_position) { > + DPRINTF("Error detected! - pp > bp\n\a"); > + } > + reset_markers(s); > + /* play postponed samples */ > + if (s->dma_io.len > 0) { > + DPRINTF("playing postponed samples\n"); > + add_to_speaker_buffer(&s->dma_io); > + return; > + } > + } > + } > +} > + > +/************************* End of speaker call back > *************************/ > + > + > +/* Opens the speaker's voice */ > +static void open_speaker_voice(ScreamerState *s) > +{ > + DPRINTF("%s() called\n", __func__); > + > + /* if voice is already open return from function */ > + if (s->speaker_voice != NULL) { > + DPRINTF("closing speaker voice\n"); > + AUD_close_out(&s->card, s->speaker_voice); > + s->speaker_voice = NULL; > + } > + struct audsettings audio_settings; > + audio_settings.freq = get_sampling_rate(s); /* in hertz */ > + audio_settings.nchannels = 2; /* stereo output */ > + audio_settings.fmt = AUDIO_FORMAT_S16; /* signed 16 bit */ > + audio_settings.endianness = get_byte_swap_reg(s); /* endianness */ > + s->speaker_voice = AUD_open_out(&s->card, s->speaker_voice, > SOUND_CHIP_NAME > + " speaker", s, speaker_callback, > + &audio_settings); > + if (!s->speaker_voice) { > + AUD_log(SOUND_CHIP_NAME, "Out voice could not be opened\n"); > + } else { > + AUD_set_active_out(s->speaker_voice, true); > + } > +} > + > + > +/******************************* Setters > *************************************/ > + > + > +/* Updates QEMU's audio backend settings */ > +static void set_QEMU_audio_settings(ScreamerState *s) > +{ > + DPRINTF("%s() called\n", __func__); > + open_speaker_voice(s); > +} > + > + > +/* Return value: 1 = muted 0 = not muted */ > +static int is_muted(ScreamerState *s) > +{ > + int mute_state = s->awacs[1] & SPEAKER_MUTE ? 1 : 0; > + if (s->awacs[1] & SPEAKER_MUTE) { > + DPRINTF("speaker is muted\n"); > + } else { > + DPRINTF("speaker is unmuted\n"); > + } > + > + if (s->awacs[1] & HEADPHONE_MUTE) { > + DPRINTF("headphone is muted\n"); > + } else { > + DPRINTF("headphone is unmuted\n"); > + } > + return mute_state; > +} > + > + > +/* Converts Screamer's volume system to QEMU's system */ > +static int screamer_to_qemu_volume(int x) > +{ > + return -16 * x + 240; > +} > + > + > +/* Sets QEMU's volume. */ > +static void set_volume(ScreamerState *s) > +{ > + int should_mute = is_muted(s); > + > + /* Get Screamer volume values */ > + uint8_t left_vol = get_left_vol(s->awacs[4]); > + uint8_t right_vol = get_right_vol(s->awacs[4]); > + DPRINTF("set_volume() called - M:%d\tL:%d\tR:%d\n", should_mute, > left_vol, > + right_vol); > + > + /* Convert Screamer to QEMU volume values */ > + left_vol = screamer_to_qemu_volume(left_vol); > + right_vol = screamer_to_qemu_volume(right_vol); > + DPRINTF("QEMU volume: L:%d\tR:%d\n", left_vol, right_vol); > + AUD_set_volume_out(s->speaker_voice, should_mute, left_vol, > right_vol); > +} > + > + > +/* Sets the sound control register */ > +static void set_sound_control_reg(ScreamerState *s, uint32_t value) > +{ > + DPRINTF("set_sound_control_reg() called - value: 0x%x\n", value); > + s->sound_control = value; > + set_QEMU_audio_settings(s); > +} > + > + > +/* Used for input gain only - can be ignored for now. */ > +static void set_awacs_0_reg(ScreamerState *s, uint32_t new_value) > +{ > + DPRINTF("Settings AWACS register 0 to 0x%x\n", s->awacs[0]); > + s->awacs[0] = new_value; > +} > + > + > +static void set_awacs_1_reg(ScreamerState *s, uint32_t new_value) > +{ > + DPRINTF("Settings AWACS register 1 to 0x%x\n", new_value); > + > + s->awacs[1] = new_value; > + > + /* If recalibration requested */ > + if (new_value & RECALIBRATE) { > + DPRINTF("Recalibration requested - unimplemented\n"); > + new_value = new_value ^ RECALIBRATE; /* Turn off recalibrate bit > */ > + } > + > + /* If loop thru set - what does this mean? */ > + if (new_value & LOOPTHRU) { > + DPRINTF("Loopthru enabled - doing nothing\n"); > + } > + > + /* Set headphone jack mute state */ > + if (new_value & HEADPHONE_MUTE) { > + DPRINTF("Headphone muted\n"); > + } > + > + else { > + DPRINTF("Headphone unmuted\n"); > + } > + > + if (new_value & SPEAKER_MUTE) { > + DPRINTF("Speaker muted\n"); > + } > + > + else { > + DPRINTF("Speaker unmuted\n"); > + } > + > + if (new_value & OUTPUT_ZERO) { > + DPRINTF("output zero set - not sure what this means\n"); > + } > + > + if (new_value & OUTPUT_ONE) { > + DPRINTF("output one set - not sure what this means\n"); > + } > + > + if (new_value & PARALLEL_OUTPUT) { > + DPRINTF("parallel port enabled - but no parallel port here\n"); > + } > + > + set_volume(s); > +} > + > + > +/* This is used for headphone volume - not needed */ > +static void set_awacs_2_reg(ScreamerState *s, uint32_t new_value) > +{ > + DPRINTF("Settings AWACS register 2 to 0x%x\n" > + "Ignoring change in headphone volume.\n", s->awacs[2]); > + s->awacs[2] = new_value; > +} > + > + > +/* Unknown register purpose */ > +static void set_awacs_3_reg(ScreamerState *s, uint32_t new_value) > +{ > + DPRINTF("Settings AWACS register 3 to 0x%x\n" > + "This register has an unknown purpose and does not do > anything\n", > + s->awacs[3]); > + s->awacs[3] = new_value; > +} > + > + > +/* Mostly deals with speaker volume */ > +static void set_awacs_4_reg(ScreamerState *s, uint32_t new_value) > +{ > + DPRINTF("AWACS register 4 write: 0x%x\n", new_value); > + s->awacs[4] = new_value; > + set_volume(s); > +} > + > + > +/* This register is about loop thru stuff I don't understand */ > +static void set_awacs_5_reg(ScreamerState *s, uint32_t new_value) > +{ > + DPRINTF("Settings AWACS register 5 to 0x%x\n" > + "Loop thru update ignored.\n", s->awacs[5]); > + s->awacs[5] = new_value; > +} > + > + > +/* Prints the states of the AWACS power register */ > +static void print_power_reg_values(uint32_t value) > +{ > + if ((value & 0x3) == 0) { > + printf("Screamer run state set\n"); > + } > + if ((value & 0x3) == 1) { > + printf("Screamer doze state set\n"); > + } > + if ((value & 0x3) == 2) { > + printf("Screamer idle state set\n"); > + } > +} > + > + > +/* Power Magement register */ > +static void set_awacs_6_reg(ScreamerState *s, uint32_t new_value) > +{ > + DPRINTF("Settings AWACS register 6 to 0x%x\n" > + "Power management update ignored.\n", s->awacs[6]); > + if (DEBUG_SCREAMER) { > + print_power_reg_values(new_value); > + } > + s->awacs[6] = new_value; > +} > + > + > +/* Read Back - repeating something that was sent to this chip? */ > +static void set_awacs_7_reg(ScreamerState *s, uint32_t new_value) > +{ > + DPRINTF("Settings AWACS register 7 to 0x%x\n", new_value); > + s->awacs[7] = new_value; > +} > + > + > +/* Sets the AWACs registers - a.k.a. shadow registers */ > +static void set_awacs_register(ScreamerState *s, uint32_t value) > +{ > + int the_register = get_codec_control_address(value); > + > + switch (the_register) { > + case 0: > + set_awacs_0_reg(s, value); > + break; > + case 1: > + set_awacs_1_reg(s, value); > + break; > + case 2: > + set_awacs_2_reg(s, value); > + break; > + case 3: > + set_awacs_3_reg(s, value); > + break; > + case 4: > + set_awacs_4_reg(s, value); > + break; > + case 5: > + set_awacs_5_reg(s, value); > + break; > + case 6: > + set_awacs_6_reg(s, value); > + break; > + case 7: > + set_awacs_7_reg(s, value); > + break; > + default: > + DPRINTF("Unhandled awacs registers %d\n", the_register); > + } > +} > + > + > +/* Used to set the AWACS registers */ > +static void set_codec_control_reg(ScreamerState *s, uint32_t value) > +{ > + DPRINTF("set_codec_control_reg() called - value: 0x%x\n", value); > + s->codec_control = value; > + set_awacs_register(s, value); > +} > + > +static void set_codec_status_reg(ScreamerState *s, uint32_t value) > +{ > + DPRINTF("set_codec_status_reg() called - value: 0x%x\n", value); > + s->codec_status = value; > +} > + > +static void set_clip_count_reg(ScreamerState *s, uint32_t new_value) > +{ > + DPRINTF("set_clip_count_reg() called - value: 0x%x\n", new_value); > + s->clip_count = new_value; > +} > + > +static void set_byte_swap_reg(ScreamerState *s, uint32_t value) > +{ > + DPRINTF("set_byte_swap_reg() called - value: 0x%x\n", value); > + s->byte_swap = value; > +} > + > +static void set_frame_count_reg(ScreamerState *s, uint32_t new_value) > +{ > + DPRINTF("%s() called - value: 0x%x\n", __func__, new_value); > + s->frame_count = new_value; > +} > + > +/* > + * Sets the busy bit of codec control register. > + * It is used to tell the CPU to wait. > + * value: the codec control register's value > + * bit_value: used to set or disable the busy bit > + */ > +static uint32_t set_busy_bit(uint32_t value, int bit_value) > +{ > + const int busy_bit = 0x01000000; > + uint32_t return_value; > + if (bit_value == 1) /* Set this bit */ > + return_value = (value | busy_bit); > + else /* bit_value == 0 Disable this bit */ > + return_value = (value & ~busy_bit); > + return return_value; > +} > + > + > +/* > + * Sets the part ready bit of the codec status register > + * value: the codec status register's value > + * bit_value: used to set or disable the part ready bit > + */ > +static uint32_t set_part_ready_bit(uint32_t value, int bit_value) > +{ > + const int part_ready_bit = 0x00400000; > + uint32_t return_value; > + if (bit_value == 1) /* Set this bit */ > + return_value = (value | part_ready_bit); > + else /* bit_value == 0 Disable this bit */ > + return_value = (value & ~part_ready_bit); > + return return_value; > +} > + > +/* Sets bits 12 and 13 to 1 to indicate the Screamer revision */ > +static uint32_t set_revision(uint32_t input_value) > +{ > + uint32_t return_value; > + return_value = input_value | 0x3000; > + return return_value; > +} > + > +/* Sets bit 8 to indicate Crystal as the manufacturer */ > +static uint32_t set_manufacturer(uint32_t input_value) > +{ > + uint32_t return_value; > + return_value = input_value | 0x100; > + return return_value; > +} > + > + > +/************************** End of Setters > *********************************/ > + > + > +/*************************** DMA functions > *********************************/ > + > +/* > + * Sends audio samples from a microphone or line-in to memory. > + * Used for sound input. > + * Currently only prevents a deadlock condition with Mac OS 9. > + */ > +static void screamer_to_dma(DBDMA_io *io) > +{ > + DPRINTF("%s() called\n", __func__); > + ScreamerState *s = (ScreamerState *)io->opaque; > + DBDMAState *dbs = s->dbdma; > + DBDMA_channel *ch = &dbs->channels[0x12]; > + ch->regs[DBDMA_STATUS] |= DEAD; > + ch->regs[DBDMA_STATUS] &= ~ACTIVE; > + io->dma_end(io); > + return; > +} > + > + > +static void print_dma_info(DBDMA_io *io) > +{ > + #define RUN 0x8000 > + #define PAUSE 0x4000 > + #define FLUSH 0x2000 > + #define WAKE 0x1000 > + #define DEAD 0x0800 > + #define ACTIVE 0x0400 > + #define BT 0x0100 > + #define DEVSTAT 0x00ff > + > + /* > + * RUN and PAUSE are bits under software control only. > + * FLUSH and WAKE are set by SW and cleared by hardware. > + * DEAD, ACTIVE and BT are only under hardware control. > + */ > + > + DBDMA_channel *ch = io->channel; > + printf("DMA FLAGS: "); > + > + if (ch->regs[DBDMA_STATUS] & RUN) { > + printf("RUN "); > + } > + > + if (ch->regs[DBDMA_STATUS] & ACTIVE) { > + printf("ACTIVE "); > + } > + > + if (ch->regs[DBDMA_STATUS] & PAUSE) { > + printf("PAUSE "); > + } > + > + if (ch->regs[DBDMA_STATUS] & DEAD) { > + printf("DEAD "); > + } > + > + if (ch->regs[DBDMA_STATUS] & WAKE) { > + printf("WAKE "); > + } > + > + if (ch->regs[DBDMA_STATUS] & BT) { > + printf("BT "); > + } > + > + if (ch->regs[DBDMA_STATUS] & DEVSTAT) { > + printf("DEVSTAT "); > + } > + > + if (ch->regs[DBDMA_STATUS] & FLUSH) { > + printf("FLUSH "); > + } > + > + if (ch->io.processing == true) { > + printf("processing "); > + } > + > + printf("\n"); > +} > + > +/* Tell the DMA controller we request more samples */ > +static void dma_request(DBDMA_io *io) > +{ > + DPRINTF("%s() called\n", __func__); > + if (DEBUG_SCREAMER) { > + print_dma_info(io); > + } > + io->len = 0; > + io->dma_end(io); > +} > + > + > +/* Adds sample data to the buffer */ > +static void add_to_speaker_buffer(DBDMA_io *io) > +{ > + ScreamerState *s = (ScreamerState *) io->opaque; > + > + if (s->spk_buffer_position + io->len > MAX_BUFFER_SIZE) { > + /* postpone calling these samples until the buffer has been > emptied */ > + memcpy(&s->dma_io, io, sizeof(DBDMA_io)); > + return; > + } > + dma_memory_read(&address_space_memory, io->addr, > + &s->spk_buffer[s->spk_buffer_position], io->len); > + s->spk_buffer_position += io->len; > + DPRINTF("%s() called - len: %d pos: %d/%d\n", __func__, io->len, > + s->spk_buffer_position, MAX_BUFFER_SIZE); > + > + dma_request(io); > +} > + > +/* > + * Called by the DMA chip to transfer samples from memory to the > + * Screamer chip. > + * Used for sound output. > + */ > +static void dma_to_screamer(DBDMA_io *io) > +{ > + add_to_speaker_buffer(io); > +} > + > + > +/* > + * This will flush the audio buffer of previous audio - eliminating > previous > + * audio playback. > + */ > +static void send_silence_to_speaker(ScreamerState *s) > +{ > + DPRINTF("Silencing audio buffer...\n"); > + int length = MAX_BUFFER_SIZE; > + s->spk_buffer_position = length; > + s->spk_play_position = 0; > + memset(s->spk_buffer, 0, length); > + s->dma_io.len = 0; /* stop any postponed samples from playing */ > +} > + > + > +/* This is called after audio stops playing */ > +static void dma_send_flush(DBDMA_io *io) > +{ > + DPRINTF("dma_send_flush() called\n"); > + if (DEBUG_SCREAMER) { > + print_dma_info(io); > + } > + ScreamerState *s = (ScreamerState *)io->opaque; > + reset_markers(s); > + send_silence_to_speaker(s); > + if (io->len > 0) { > + dma_request(io); > + } > +} > + > + > +static void dma_receive_flush(DBDMA_io *io) > +{ > + DPRINTF("dma_receive_flush() called\n"); > +} > + > + > +/* Set the functions the DMA system will call */ > +void screamer_register_dma_functions(ScreamerState *s, void *dbdma, > + int send_channel, int receive_channel) > +{ > + DPRINTF("%s() called\n", __func__); > + DPRINTF("send channel: %d\treceive channel: %d\n", send_channel, > + receive_channel); > + s->dbdma = dbdma; > + > + /* Setup the DMA send system */ > + DBDMA_register_channel(s->dbdma, send_channel, s->dma_send_irq, > + dma_to_screamer, dma_send_flush, s); > + > + /* Setup the DMA receive system */ > + DBDMA_register_channel(s->dbdma, receive_channel, s->dma_receive_irq, > + screamer_to_dma, dma_receive_flush, s); > +} > + > +/************************* End of DMA functions > **************************/ > + > +/* Resets this sound chip */ > +static void screamer_reset(DeviceState *d) > +{ > + DPRINTF("screamer_reset() called\n"); > + ScreamerState *s = SCREAMER(d); > + set_sound_control_reg(s, 0); > + set_codec_control_reg(s, 0); > + set_codec_status_reg(s, 0); > + set_clip_count_reg(s, 0); > + set_byte_swap_reg(s, 0); > + set_frame_count_reg(s, 0); > + int i, num_awacs_regs = 8; > + for (i = 0; i < num_awacs_regs; i++) { > + s->awacs[i] = 0; > + } > + set_QEMU_audio_settings(s); > + reset_markers(s); > + s->dma_io.len = 0; > +} > + > +/* Called when the CPU reads the memory addresses assigned to Screamer */ > +static uint64_t screamer_mmio_read(void *opaque, hwaddr addr, unsigned > size) > +{ > + ScreamerState *state = opaque; > + uint32_t return_value; > + > + addr = addr >> 4; > + switch (addr) { > + case SOUND_CONTROL_REG: > + return_value = get_sound_control_reg(state); > + break; > + case CODEC_CONTROL_REG: > + return_value = get_codec_control_reg(state); > + break; > + case CODEC_STATUS_REG: > + return_value = get_codec_status_reg(state); > + break; > + case CLIP_COUNT_REG: > + return_value = get_clip_count_reg(state); > + break; > + case BYTE_SWAP_REG: > + return_value = get_byte_swap_reg(state); > + break; > + case FRAME_COUNT_REG: > + return_value = get_frame_count_reg(state); > + break; > + default: > + DPRINTF("Unknown register read - addr:%llu\tsize:%d\n", addr, > size); > + return_value = 12021981; /* Value used for debugging purposes */ > + } > + DPRINTF("screamer_mmio_read() called addr: %llu size: %d", addr >> 4, > + size); > + DPRINTF(" returning 0x%x\n", return_value); > + return return_value; > +} > + > + > +/* Called when the CPU writes to the memory addresses assigned to > Screamer */ > +static void screamer_mmio_write(void *opaque, hwaddr addr, uint64_t > raw_value, > + unsigned size) > +{ > + DPRINTF("screamer_mmio_write() called - size: %d\n", size); > + ScreamerState *state = opaque; > + uint32_t value = raw_value & 0xffffffff; > + addr = addr >> 4; > + > + switch (addr) { > + case SOUND_CONTROL_REG: > + set_sound_control_reg(state, value); > + break; > + case CODEC_CONTROL_REG: > + set_codec_control_reg(state, value); > + break; > + case CODEC_STATUS_REG: > + set_codec_status_reg(state, value); > + break; > + case CLIP_COUNT_REG: > + set_clip_count_reg(state, value); > + break; > + case BYTE_SWAP_REG: > + set_byte_swap_reg(state, value); > + break; > + case FRAME_COUNT_REG: > + set_frame_count_reg(state, value); > + break; > + default: > + DPRINTF("Unknown register write - addr:%llu\tvalue:%d\n", addr, > value); > + } > +} > + > +/* Used for memory_region_init_io() for memory mapped I/O */ > +static const MemoryRegionOps screamer_ops = { > + .read = screamer_mmio_read, > + .write = screamer_mmio_write, > + .endianness = DEVICE_LITTLE_ENDIAN, > + .valid = { > + .min_access_size = 4, > + .max_access_size = 4 > + } > +}; > + > +/* Called when the device has become active */ > +static void screamer_realize(DeviceState *dev, Error **errp) > +{ > + DPRINTF("screamer_realize() called\n"); > + screamer_reset(dev); > +} > + > + > +/* > + * Called when an instance of the Screamer device is created. > + * Also called when this HMP command is called: device_add screamer > + */ > +static void screamer_init(Object *obj) > +{ > + DPRINTF("screamer_init() called\n"); > + > + ScreamerState *s = (ScreamerState *)obj; > + SysBusDevice *d = SYS_BUS_DEVICE(obj); > + const int region_size = 5 * 32; > + > + /* Makes the read and write ops work */ > + memory_region_init_io(&s->io_memory_region, OBJECT(s), > + &screamer_ops, s, SOUND_CHIP_NAME, region_size); > + > + /* Sets the SysBusDevice's memory property */ > + sysbus_init_mmio(d, &s->io_memory_region); > + > + /* Setup all the interrupt requests */ > + sysbus_init_irq(d, &s->irq); > + sysbus_init_irq(d, &s->dma_send_irq); > + sysbus_init_irq(d, &s->dma_receive_irq); > + > + /* Registers Screamer with QEMU's audio system */ > + AUD_register_card(SOUND_CHIP_NAME, &s->card); > +} > + > + > +/* > + * When saving and restoring the state of the VM, this is used to save and > + * restore the registers. > + */ > +static const VMStateDescription vmstate_screamer = { > + .name = "Screamer", > + .version_id = 1, > + .minimum_version_id = 1, > + .fields = (VMStateField[]) { > + VMSTATE_UINT16_ARRAY(awacs, ScreamerState, 8), /* 8 AWACS > registers */ > + VMSTATE_UINT32(sound_control, ScreamerState), > + VMSTATE_UINT32(codec_control, ScreamerState), > + VMSTATE_UINT32(codec_status, ScreamerState), > + VMSTATE_UINT32(clip_count, ScreamerState), > + VMSTATE_UINT32(byte_swap, ScreamerState), > + VMSTATE_UINT32(frame_count, ScreamerState), > + VMSTATE_END_OF_LIST() > + } > +}; > + > + > +/* > + * Sets the class data. It is like polymorphism and inheritance in object > + * oriented languages. > + */ > +static void screamer_class_init(ObjectClass *class, void *data) > +{ > + DPRINTF("screamer_class_init() called\n"); > + DeviceClass *dc = DEVICE_CLASS(class); > + dc->realize = screamer_realize; > + dc->reset = screamer_reset; > + dc->desc = "Apple Screamer"; > + dc->vmsd = &vmstate_screamer; > + dc->hotpluggable = false; > +} > + > +/* Used for QOM function registration */ > +static const TypeInfo screamer_info = { > + .name = "screamer", > + .parent = TYPE_SYS_BUS_DEVICE, > + .instance_size = sizeof(ScreamerState), > + .instance_init = screamer_init, > + .class_init = screamer_class_init, > +}; > + > +/* QOM registration of above functions for calling */ > +static void screamer_register_types(void) > +{ > + DPRINTF("screamer_register_types() called\n"); > + type_register_static(&screamer_info); > +} > + > +/* QEMU Object Model (QOM) stuff */ > +type_init(screamer_register_types) > diff --git a/hw/misc/macio/macio.c b/hw/misc/macio/macio.c > index 79222192e8..3307fa3818 100644 > --- a/hw/misc/macio/macio.c > +++ b/hw/misc/macio/macio.c > @@ -37,6 +37,7 @@ > #include "hw/intc/heathrow_pic.h" > #include "sysemu/sysemu.h" > #include "trace.h" > +#include "include/hw/audio/screamer.h" > > /* Note: this code is strongly inspirated from the corresponding code > * in PearPC */ > @@ -109,7 +110,10 @@ static void macio_common_realize(PCIDevice *d, Error > **errp) > SysBusDevice *sysbus_dev; > Error *err = NULL; > > - object_property_set_bool(OBJECT(&s->dbdma), true, "realized", &err); > + const char *realized_property = "realized"; > + bool new_value = true; > + object_property_set_bool(OBJECT(&s->dbdma), new_value, > realized_property, > + &err); > if (err) { > error_propagate(errp, err); > return; > @@ -117,6 +121,19 @@ static void macio_common_realize(PCIDevice *d, Error > **errp) > sysbus_dev = SYS_BUS_DEVICE(&s->dbdma); > memory_region_add_subregion(&s->bar, 0x08000, > sysbus_mmio_get_region(sysbus_dev, 0)); > + object_property_set_bool(OBJECT(&s->screamer), true, "realized", > &err); > + if (err) { > + error_propagate(errp, err); > + return; > + } > + > + /* Add the screamer sound chip */ > + sysbus_dev = SYS_BUS_DEVICE(&s->screamer); > + const int offset = 0x14000; /* Offset from base address register > (bar) */ > + const int region_number = 0; /* which memory region to use */ > + memory_region_add_subregion(&s->bar, offset, > + sysbus_mmio_get_region(sysbus_dev, > + region_number)); > > qdev_prop_set_uint32(DEVICE(&s->escc), "disabled", 0); > qdev_prop_set_uint32(DEVICE(&s->escc), "frequency", ESCC_CLOCK); > @@ -386,6 +403,19 @@ static void macio_newworld_realize(PCIDevice *d, > Error **errp) > memory_region_add_subregion(&s->bar, 0x16000, > sysbus_mmio_get_region(sysbus_dev, > 0)); > } > + > + /* Screamer Sound Chip */ > + const int gpio_0 = 0; > + const int gpio_1 = 1; > + const int transmit_channel = 0x10; > + const int receive_channel = 0x12; > + sysbus_dev = SYS_BUS_DEVICE(&s->screamer); > + sysbus_connect_irq(sysbus_dev, gpio_0, qdev_get_gpio_in(pic_dev, > + NEWWORLD_SCREAMER_IRQ)); > + sysbus_connect_irq(sysbus_dev, gpio_1, qdev_get_gpio_in(pic_dev, > + NEWWORLD_SCREAMER_DMA_IRQ)); > + screamer_register_dma_functions(SCREAMER(sysbus_dev), &s->dbdma, > + transmit_channel, receive_channel); > } > > static void macio_newworld_init(Object *obj) > @@ -420,6 +450,9 @@ static void macio_instance_init(Object *obj) > TYPE_MAC_DBDMA); > > macio_init_child_obj(s, "escc", &s->escc, sizeof(s->escc), TYPE_ESCC); > + > + macio_init_child_obj(s, SOUND_CHIP_NAME, &s->screamer, > sizeof(s->screamer), > + TYPE_SCREAMER); > } > > static const VMStateDescription vmstate_macio_oldworld = { > diff --git a/hw/ppc/Kconfig b/hw/ppc/Kconfig > index 354828bf13..4ffc3a1c16 100644 > --- a/hw/ppc/Kconfig > +++ b/hw/ppc/Kconfig > @@ -104,6 +104,7 @@ config MAC_NEWWORLD > select MAC_PMU > select UNIN_PCI > select FW_CFG_PPC > + select SCREAMER > > config E500 > bool > diff --git a/hw/ppc/mac.h b/hw/ppc/mac.h > index 6af87d1fa0..c65f2fd15f 100644 > --- a/hw/ppc/mac.h > +++ b/hw/ppc/mac.h > @@ -34,6 +34,8 @@ > #include "hw/misc/mos6522.h" > #include "hw/pci/pci_host.h" > #include "hw/pci-host/uninorth.h" > +#include "hw/ppc/mac_dbdma.h" > +#include "audio/audio.h" > > /* SMP is not enabled, for now */ > #define MAX_CPUS 1 > @@ -68,6 +70,9 @@ > #define NEWWORLD_IDE1_DMA_IRQ 0x3 > #define NEWWORLD_EXTING_GPIO1 0x2f > #define NEWWORLD_EXTING_GPIO9 0x37 > +#define NEWWORLD_SCREAMER_IRQ 0x18 > +#define NEWWORLD_SCREAMER_DMA_IRQ 0x9 > +#define NEWWORLD_SCREAMER_RX_IRQ 0xa > > /* Core99 machine */ > #define TYPE_CORE99_MACHINE MACHINE_TYPE_NAME("mac99") > diff --git a/include/hw/audio/screamer.h b/include/hw/audio/screamer.h > new file mode 100644 > index 0000000000..7155541688 > --- /dev/null > +++ b/include/hw/audio/screamer.h > @@ -0,0 +1,42 @@ > +/* > + * File: screamer.h > + * Description: header file to the hw/audio/screamer.c file > + */ > + > +#ifndef screamer_h > +#define screamer_h > + > +#include > +#include "audio/audio.h" > +#include "hw/ppc/mac_dbdma.h" > + > +#define TYPE_SCREAMER "screamer" > +#define SCREAMER(obj) OBJECT_CHECK(ScreamerState, (obj), TYPE_SCREAMER) > +#define SOUND_CHIP_NAME "Screamer Sound Chip" > +#define MAX_BUFFER_SIZE (128 * 64) > + > +typedef struct ScreamerState { > + SysBusDevice parent_obj; > + uint16_t awacs[8]; /* Shadow/awacs registers */ > + uint32_t sound_control; > + uint32_t codec_control; > + uint32_t codec_status; > + uint32_t clip_count; > + uint32_t byte_swap; > + uint32_t frame_count; > + SWVoiceOut *speaker_voice; > + DBDMAState *dbdma; > + qemu_irq dma_send_irq; > + qemu_irq dma_receive_irq; > + qemu_irq irq; > + QEMUSoundCard card; > + MemoryRegion io_memory_region; > + uint8_t spk_buffer[MAX_BUFFER_SIZE]; > + uint16_t spk_buffer_position, spk_play_position; > + DBDMA_io dma_io; > +} ScreamerState; > + > +void screamer_register_dma_functions(ScreamerState *s, void *dbdma, > + int send_channel, int > receive_channel); > + > +#endif /* screamer_h */ > diff --git a/include/hw/misc/macio/macio.h b/include/hw/misc/macio/macio.h > index 070a694eb5..81ad552d61 100644 > --- a/include/hw/misc/macio/macio.h > +++ b/include/hw/misc/macio/macio.h > @@ -35,6 +35,7 @@ > #include "hw/ppc/mac.h" > #include "hw/ppc/mac_dbdma.h" > #include "hw/ppc/openpic.h" > +#include "hw/audio/screamer.h" > > /* MacIO virtual bus */ > #define TYPE_MACIO_BUS "macio-bus" > @@ -86,6 +87,7 @@ typedef struct MacIOState { > PMUState pmu; > DBDMAState dbdma; > ESCCState escc; > + ScreamerState screamer; > uint64_t frequency; > } MacIOState; > > -- > 2.14.3 (Apple Git-98) > > Hi, This patch will not compile without errors. Host is Fedora 31. The compiler suggests changing lines 839, 842 and 878 in screamer.c so the DPRINTF arguments use %lu instead of %llu. With that fixed, compiling completes succesfully. Best, Howard