From 03e58dac356b82ca953ae7bdba850400d8881086 Mon Sep 17 00:00:00 2001 From: Maxim Poliakovski Date: Mon, 4 Oct 2021 23:46:19 +0200 Subject: [PATCH] Overhaul AWACs and implement PDM sound HW. --- devices/amic.cpp | 171 +++++++++++++++++++--- devices/amic.h | 91 ++++++++++-- devices/awacs.cpp | 313 ++++++++++++++-------------------------- devices/awacs.h | 103 +++++++------ devices/dbdma.cpp | 10 +- devices/dbdma.h | 7 +- devices/dmacore.h | 41 ++++++ devices/heathrow.cpp | 2 +- devices/macio.h | 2 +- devices/soundserver.cpp | 54 ++++++- devices/soundserver.h | 20 +-- machines/machinepdm.cpp | 4 + 12 files changed, 505 insertions(+), 313 deletions(-) create mode 100644 devices/dmacore.h diff --git a/devices/amic.cpp b/devices/amic.cpp index e1b290c..92fb543 100644 --- a/devices/amic.cpp +++ b/devices/amic.cpp @@ -25,11 +25,15 @@ along with this program. If not, see . */ #include "amic.h" +#include "cpu/ppc/ppcmmu.h" +#include "dmacore.h" #include "machines/machinebase.h" #include "memctrlbase.h" #include "viacuda.h" +#include #include #include +#include AMIC::AMIC() { @@ -44,7 +48,10 @@ AMIC::AMIC() } this->viacuda = std::unique_ptr (new ViaCuda()); - this->awacs = std::unique_ptr (new AwacDevicePdm()); + + this->snd_out_dma = std::unique_ptr (new AmicSndOutDma()); + this->awacs = std::unique_ptr (new AwacDevicePdm()); + this->awacs->set_dma_out(this->snd_out_dma.get()); } bool AMIC::supports_type(HWCompType type) { @@ -57,6 +64,8 @@ bool AMIC::supports_type(HWCompType type) { uint32_t AMIC::read(uint32_t reg_start, uint32_t offset, int size) { + uint32_t phase_val; + if (offset < 0x2000) { return this->viacuda->read(offset >> 9); } @@ -66,37 +75,80 @@ uint32_t AMIC::read(uint32_t reg_start, uint32_t offset, int size) case AMICReg::Snd_Stat_1: case AMICReg::Snd_Stat_2: return (this->awacs->read_stat() >> (offset & 3 * 8)) & 0xFF; + case AMICReg::Snd_Phase0: + case AMICReg::Snd_Phase1: + case AMICReg::Snd_Phase2: + // the sound phase register is organized as follows: + // 000000oo oooooooo oopppppp where 'o' is the 12-bit offset + // into the DMA buffer and 'p' is an undocumented prescale value + // HWInit doesn't care about. Let's hope it will be sufficient + // to return 0 for prescale. + phase_val = this->snd_out_dma->get_cur_buf_pos() << 6; + return (phase_val >> ((2 - (offset & 3)) * 8)) & 0xFF; + case AMICReg::Snd_Out_Ctrl: + return this->snd_out_ctrl; + case AMICReg::Snd_Out_DMA: + return this->snd_out_dma->read_stat(); + default: + LOG_F(WARNING, "Unknown AMIC register read, offset=%x", offset); } return 0; } void AMIC::write(uint32_t reg_start, uint32_t offset, uint32_t value, int size) { + uint32_t mask; + if (offset < 0x2000) { this->viacuda->write(offset >> 9, value); return; } switch(offset) { - case AMICReg::Snd_Cntl_0: - case AMICReg::Snd_Cntl_1: - case AMICReg::Snd_Cntl_2: + case AMICReg::Snd_Ctrl_0: + case AMICReg::Snd_Ctrl_1: + case AMICReg::Snd_Ctrl_2: // remember values of sound control registers this->imm_snd_regs[offset & 3] = value; // transfer control information to the sound codec when ready - if ((this->imm_snd_regs[0] & 0xC0) == PDM_SND_CNTL_VALID) { + if ((this->imm_snd_regs[0] & 0xC0) == PDM_SND_CTRL_VALID) { this->awacs->write_ctrl( (this->imm_snd_regs[1] >> 4) | (this->imm_snd_regs[0] & 0x3F), ((this->imm_snd_regs[1] & 0xF) << 8) | this->imm_snd_regs[2] ); } break; - case AMICReg::Snd_Out_Cntl: - LOG_F(INFO, "AMIC Sound Out Ctrl updated, val=%x", value); + case AMICReg::Snd_Buf_Size_Hi: + case AMICReg::Snd_Buf_Size_Lo: + mask = 0xFF00U >> (8 * (offset & 1)); + this->snd_buf_size = (this->snd_buf_size & ~mask) | + ((value & 0xFF) << (8 * ((offset & 1) ^1))); + this->snd_buf_size &= ~3; // sound buffer size is always a multiple of 4 + LOG_F(9, "AMIC: Sound buffer size set to 0x%X", this->snd_buf_size); break; - case AMICReg::Snd_In_Cntl: + case AMICReg::Snd_Out_Ctrl: + LOG_F(9, "AMIC Sound Out Ctrl updated, val=%x", value); + if ((value & 1) != (this->snd_out_ctrl & 1)) { + if (value & 1) { + LOG_F(9, "AMIC Sound Out DMA enabled!"); + this->snd_out_dma->init(this->dma_base & ~0x3FFFF, + this->snd_buf_size); + this->snd_out_dma->enable(); + this->awacs->set_sample_rate((this->snd_out_ctrl >> 1) & 3); + this->awacs->start_output_dma(); + } else { + LOG_F(9, "AMIC Sound Out DMA disabled!"); + this->snd_out_dma->disable(); + } + } + this->snd_out_ctrl = value; + break; + case AMICReg::Snd_In_Ctrl: LOG_F(INFO, "AMIC Sound In Ctrl updated, val=%x", value); break; + case AMICReg::Snd_Out_DMA: + this->snd_out_dma->write_dma_out_ctrl(value); + break; case AMICReg::VIA2_Slot_IER: LOG_F(INFO, "AMIC VIA2 Slot Interrupt Enable Register updated, val=%x", value); break; @@ -106,31 +158,40 @@ void AMIC::write(uint32_t reg_start, uint32_t offset, uint32_t value, int size) case AMICReg::Video_Mode_Reg: LOG_F(INFO, "AMIC Video Mode Register set to %x", value); break; - case AMICReg::Int_Cntl: + case AMICReg::Int_Ctrl: LOG_F(INFO, "AMIC Interrupt Control Register set to %X", value); break; - case AMICReg::Enet_DMA_Xmt_Cntl: + case AMICReg::DMA_Base_Addr_0: + case AMICReg::DMA_Base_Addr_1: + case AMICReg::DMA_Base_Addr_2: + case AMICReg::DMA_Base_Addr_3: + mask = 0xFF000000UL >> (8 * (offset & 3)); + this->dma_base = (this->dma_base & ~mask) | + ((value & 0xFF) << (8 * (3 - (offset & 3)))); + LOG_F(9, "AMIC: DMA base address set to 0x%X", this->dma_base); + break; + case AMICReg::Enet_DMA_Xmt_Ctrl: LOG_F(INFO, "AMIC Ethernet Transmit DMA Ctrl updated, val=%x", value); break; - case AMICReg::SCSI_DMA_Cntl: + case AMICReg::SCSI_DMA_Ctrl: LOG_F(INFO, "AMIC SCSI DMA Ctrl updated, val=%x", value); break; - case AMICReg::Enet_DMA_Rcv_Cntl: + case AMICReg::Enet_DMA_Rcv_Ctrl: LOG_F(INFO, "AMIC Ethernet Receive DMA Ctrl updated, val=%x", value); break; - case AMICReg::SWIM3_DMA_Cntl: + case AMICReg::SWIM3_DMA_Ctrl: LOG_F(INFO, "AMIC SWIM3 DMA Ctrl updated, val=%x", value); break; - case AMICReg::SCC_DMA_Xmt_A_Cntl: + case AMICReg::SCC_DMA_Xmt_A_Ctrl: LOG_F(INFO, "AMIC SCC Transmit Ch A DMA Ctrl updated, val=%x", value); break; - case AMICReg::SCC_DMA_Rcv_A_Cntl: + case AMICReg::SCC_DMA_Rcv_A_Ctrl: LOG_F(INFO, "AMIC SCC Receive Ch A DMA Ctrl updated, val=%x", value); break; - case AMICReg::SCC_DMA_Xmt_B_Cntl: + case AMICReg::SCC_DMA_Xmt_B_Ctrl: LOG_F(INFO, "AMIC SCC Transmit Ch B DMA Ctrl updated, val=%x", value); break; - case AMICReg::SCC_DMA_Rcv_B_Cntl: + case AMICReg::SCC_DMA_Rcv_B_Ctrl: LOG_F(INFO, "AMIC SCC Receive Ch B DMA Ctrl updated, val=%x", value); break; default: @@ -138,3 +199,79 @@ void AMIC::write(uint32_t reg_start, uint32_t offset, uint32_t value, int size) offset, value); } } + +// =========================== DMA related stuff ============================= +AmicSndOutDma::AmicSndOutDma() +{ + this->dma_out_ctrl = 0; + this->enabled = false; +} + +bool AmicSndOutDma::is_active() +{ + return true; +} + +void AmicSndOutDma::init(uint32_t buf_base, uint32_t buf_samples) +{ + this->out_buf0 = buf_base + AMIC_SND_BUF0_OFFS; + this->out_buf1 = buf_base + AMIC_SND_BUF1_OFFS; + + this->out_buf_len = buf_samples * 2 * 2; + + this->snd_buf_num = 0; + this->cur_buf_pos = 0; +} + +uint8_t AmicSndOutDma::read_stat() +{ + return this->dma_out_ctrl; +} + +void AmicSndOutDma::write_dma_out_ctrl(uint8_t value) +{ + // clear interrupt flags + value &= ~PDM_DMA_INTS_MASK; + this->dma_out_ctrl = value; + LOG_F(9, "AMIC: Sound out DMA control set to 0x%X", value); +} + +DmaPullResult AmicSndOutDma::pull_data(uint32_t req_len, uint32_t *avail_len, + uint8_t **p_data) +{ + *avail_len = 0; + + int rem_len = this->out_buf_len - this->cur_buf_pos; + if (rem_len <= 0) { + if (!this->snd_buf_num) { + // signal buffer 0 drained + this->dma_out_ctrl |= PDM_DMA_IF0; + // TODO: generate IE0 interrupt if enabled + } else { + // signal buffer 1 drained + this->dma_out_ctrl |= PDM_DMA_IF1; + // TODO: generate IE1 interrupt if enabled + } + + // check DMA enable flag after buffer 1 was processed + // if it's false stop delivering sound data + // this will effectively stop audio playback + if (this->snd_buf_num && !this->enabled) { + this->cur_buf_pos = 0; + return DmaPullResult::NoMoreData; + } + + this->cur_buf_pos = 0; // reset buffer position + this->snd_buf_num ^= 1; // toggle sound buffers + rem_len = this->out_buf_len; // buffer size = full buffer + } + + uint32_t len = std::min((uint32_t)rem_len, req_len); + + *p_data = mmu_get_dma_mem( + (this->snd_buf_num ? this->out_buf1 : this->out_buf0) + this->cur_buf_pos, + len); + this->cur_buf_pos += len; + *avail_len = len; + return DmaPullResult::MoreData; +} diff --git a/devices/amic.h b/devices/amic.h index 231ee7a..67e2e82 100644 --- a/devices/amic.h +++ b/devices/amic.h @@ -25,22 +25,70 @@ along with this program. If not, see . #define AMIC_H #include "awacs.h" +#include "dmacore.h" #include "mmiodevice.h" #include "viacuda.h" #include #include -/* AMIC DMA registers offsets from AMIC base (0x50F00000). */ +/** AMIC sound buffers are located at fixed offsets from DMA base. */ +#define AMIC_SND_BUF0_OFFS 0x10000 +#define AMIC_SND_BUF1_OFFS 0x12000 + +// PDM HWInit source defines two constants: kExpBit = 0x80 and kCmdBit = 0x40 +// I don't know what they means but it seems that their combination will +// cause sound control parameters to be transferred to the sound chip. +#define PDM_SND_CTRL_VALID 0xC0 + +#define PDM_DMA_IF1 0x80 // DMA interrupt flag => buffer 1 drained +#define PDM_DMA_IF0 0x40 // DMA interrupt flag => buffer 0 drained +#define PDM_DMA_INTS_MASK 0xF0 // mask for clearing all interrupt flags + +/** AMIC-specific sound output DMA implementation. */ +class AmicSndOutDma : public DmaOutChannel { +public: + AmicSndOutDma(); + ~AmicSndOutDma() = default; + + bool is_active(); + void init(uint32_t buf_base, uint32_t buf_samples); + void enable() { this->enabled = true; }; + void disable() { this->enabled = false; }; + uint8_t read_stat(); + void write_dma_out_ctrl(uint8_t value); + uint32_t get_cur_buf_pos() { return this->cur_buf_pos; }; + DmaPullResult pull_data(uint32_t req_len, uint32_t *avail_len, + uint8_t **p_data); + +private: + bool enabled; + uint8_t dma_out_ctrl; + + uint32_t out_buf0; + uint32_t out_buf1; + uint32_t out_buf_len; + uint32_t snd_buf_num; + uint32_t cur_buf_pos; +}; + +/* AMIC registers offsets from AMIC base (0x50F00000). */ enum AMICReg : uint32_t { - // Audio codec control registers - Snd_Cntl_0 = 0x14000, // audio codec control register 0 - Snd_Cntl_1 = 0x14001, // audio codec control register 1 - Snd_Cntl_2 = 0x14002, // audio codec control register 2 + // Sound control registers + Snd_Ctrl_0 = 0x14000, // audio codec control register 0 + Snd_Ctrl_1 = 0x14001, // audio codec control register 1 + Snd_Ctrl_2 = 0x14002, // audio codec control register 2 Snd_Stat_0 = 0x14004, // audio codec status register 0 Snd_Stat_1 = 0x14005, // audio codec status register 1 Snd_Stat_2 = 0x14006, // audio codec status register 2 - Snd_Out_Cntl = 0x14010, // audio codec output DMA control register - Snd_In_Cntl = 0x14011, // audio codec input DMA control register + Snd_Buf_Size_Hi = 0x14008, // sound buffer size, high-order byte + Snd_Buf_Size_Lo = 0x14009, // sound buffer size, low-order byte + Snd_Phase0 = 0x1400C, // high-order byte of the sound phase register + Snd_Phase1 = 0x1400D, // middle byte of the sound phase register + Snd_Phase2 = 0x1400E, // low-order byte of the sound phase register + Snd_Out_Ctrl = 0x14010, // audio codec output control register + Snd_In_Ctrl = 0x14011, // audio codec input control register + Snd_In_DMA = 0x14014, // sound input DMA status/control register + Snd_Out_DMA = 0x14018, // sound output DMA status/control register // VIA2 registers VIA2_Slot_IER = 0x26012, @@ -49,19 +97,24 @@ enum AMICReg : uint32_t { // Video control registers Video_Mode_Reg = 0x28000, - Int_Cntl = 0x2A000, + Int_Ctrl = 0x2A000, // DMA control registers - Enet_DMA_Xmt_Cntl = 0x31C20, - SCSI_DMA_Cntl = 0x32008, - Enet_DMA_Rcv_Cntl = 0x32028, - SWIM3_DMA_Cntl = 0x32068, - SCC_DMA_Xmt_A_Cntl = 0x32088, - SCC_DMA_Rcv_A_Cntl = 0x32098, - SCC_DMA_Xmt_B_Cntl = 0x320A8, - SCC_DMA_Rcv_B_Cntl = 0x320B8, + DMA_Base_Addr_0 = 0x31000, + DMA_Base_Addr_1 = 0x31001, + DMA_Base_Addr_2 = 0x31002, + DMA_Base_Addr_3 = 0x31003, + Enet_DMA_Xmt_Ctrl = 0x31C20, + SCSI_DMA_Ctrl = 0x32008, + Enet_DMA_Rcv_Ctrl = 0x32028, + SWIM3_DMA_Ctrl = 0x32068, + SCC_DMA_Xmt_A_Ctrl = 0x32088, + SCC_DMA_Rcv_A_Ctrl = 0x32098, + SCC_DMA_Xmt_B_Ctrl = 0x320A8, + SCC_DMA_Rcv_B_Ctrl = 0x320B8, }; +/** Apple Memory-mapped I/O controller device. */ class AMIC : public MMIODevice { public: AMIC(); @@ -79,8 +132,14 @@ protected: private: uint8_t imm_snd_regs[4]; // temporary storage for sound control registers + uint32_t dma_base = 0; // DMA physical base address + uint16_t snd_buf_size = 0; // sound buffer size in bytes + uint8_t snd_out_ctrl = 0; + std::unique_ptr viacuda; std::unique_ptr awacs; + + std::unique_ptr snd_out_dma; }; #endif // AMIC_H diff --git a/devices/awacs.cpp b/devices/awacs.cpp index 61e4838..74ade0b 100644 --- a/devices/awacs.cpp +++ b/devices/awacs.cpp @@ -1,6 +1,6 @@ /* DingusPPC - The Experimental PowerPC Macintosh emulator -Copyright (C) 2018-20 divingkatae and maximum +Copyright (C) 2018-21 divingkatae and maximum (theweirdo) spatium (Contact divingkatae#1017 or powermax#2286 on Discord for more info) @@ -21,7 +21,11 @@ along with this program. If not, see . /** AWAC sound device emulation. - Author: Max Poliakovski 2019-20 + Currently supported audio codecs: + - PDM AWACs in Nubus Power Macintosh models + - Screamer AWACs in Beige G3 + + Author: Max Poliakovski 2019-21 */ #include "awacs.h" @@ -31,37 +35,106 @@ along with this program. If not, see . #include "soundserver.h" #include #include +#include -static int awac_freqs[8] = {44100, 29400, 22050, 17640, 14700, 11025, 8820, 7350}; - - -AWACDevice::AWACDevice() +AwacsBase::AwacsBase() { - this->audio_proc = new AudioProcessor(); - - /* register audio processor chip with the I2C bus */ - I2CBus* i2c_bus = dynamic_cast(gMachineObj->get_comp_by_type(HWCompType::I2C_HOST)); - i2c_bus->register_device(0x45, this->audio_proc); - + // connect to SoundServer this->snd_server = dynamic_cast (gMachineObj->get_comp_by_name("SoundServer")); this->out_stream_ready = false; } -AWACDevice::~AWACDevice() +AwacsBase::~AwacsBase() { + // disconnect from SoundServer if (this->out_stream_ready) { snd_server->close_out_stream(); } - - delete this->audio_proc; } -void AWACDevice::set_dma_out(DMAChannel* dma_out_ch) { - this->dma_out_ch = dma_out_ch; +void AwacsBase::set_sample_rate(int sr_id) +{ + if (sr_id > this->max_sr_id) { + LOG_F(ERROR, "AWACs: invalid sample rate ID %d!", sr_id); + } else { + this->cur_sample_rate = this->sr_table[sr_id]; + } +}; + +void AwacsBase::start_output_dma() +{ + int err; + + if ((err = this->snd_server->open_out_stream(this->cur_sample_rate, + (void *)this->dma_out_ch))) { + LOG_F(ERROR, "AWACs: unable to open sound output stream: %d", err); + this->out_stream_ready = false; + return; + } + + this->out_stream_ready = true; + + if ((err = snd_server->start_out_stream())) { + LOG_F(ERROR, "AWACs: could not start sound output stream"); + } } -uint32_t AWACDevice::snd_ctrl_read(uint32_t offset, int size) { +void AwacsBase::stop_output_dma() +{ + if (this->out_stream_ready) { + snd_server->close_out_stream(); + this->out_stream_ready = false; + } +} + +//=========================== PDM-style AWACs ================================= +AwacDevicePdm::AwacDevicePdm() : AwacsBase() +{ + static int pdm_awac_freqs[3] = {22050, 29400, 44100}; + + // PDM-style AWACs only supports three sample rates + this->sr_table = pdm_awac_freqs; + this->max_sr_id = 2; +} + +uint32_t AwacDevicePdm::read_stat() +{ + LOG_F(INFO, "AWACs-PDM: status requested!"); + // TODO: return valid status including manufacturer & device IDs + return 0; +} + +void AwacDevicePdm::write_ctrl(uint32_t addr, uint16_t value) +{ + LOG_F(9, "AWACs-PDM: control updated, addr=0x%X, val=0x%X", addr, value); + + if (addr <= 4) { + this->ctrl_regs[addr] = value; + } else { + LOG_F(ERROR, "AWACs-PDM: invalid control register address %d!", addr); + } +} + +//============================= Screamer AWACs ================================ +AwacsScreamer::AwacsScreamer() : AwacsBase() +{ + this->audio_proc = std::unique_ptr (new AudioProcessor()); + + /* register audio processor chip with the I2C bus */ + I2CBus* i2c_bus = dynamic_cast(gMachineObj->get_comp_by_type(HWCompType::I2C_HOST)); + i2c_bus->register_device(0x45, this->audio_proc.get()); + + static int screamer_freqs[8] = { + 44100, 29400, 22050, 17640, 14700, 11025, 8820, 7350 + }; + + this->sr_table = screamer_freqs; + this->max_sr_id = 7; +} + +uint32_t AwacsScreamer::snd_ctrl_read(uint32_t offset, int size) +{ switch (offset) { case AWAC_SOUND_CTRL_REG: return this->snd_ctrl_reg; @@ -69,191 +142,43 @@ uint32_t AWACDevice::snd_ctrl_read(uint32_t offset, int size) { return this->is_busy; case AWAC_CODEC_STATUS_REG: return (AWAC_AVAILABLE << 8) | (AWAC_MAKER_CRYSTAL << 16) | (AWAC_REV_SCREAMER << 20); - break; default: - LOG_F(ERROR, "AWAC: unsupported register at offset 0x%X", offset); + LOG_F(ERROR, "Screamer: unsupported register at offset 0x%X", offset); } return 0; } -void AWACDevice::snd_ctrl_write(uint32_t offset, uint32_t value, int size) { +void AwacsScreamer::snd_ctrl_write(uint32_t offset, uint32_t value, int size) +{ int subframe, reg_num; uint16_t data; switch (offset) { case AWAC_SOUND_CTRL_REG: this->snd_ctrl_reg = BYTESWAP_32(value); - LOG_F(INFO, "New sound control value = 0x%X", this->snd_ctrl_reg); + LOG_F(9, "Screamer: new sound control value = 0x%X", this->snd_ctrl_reg); break; case AWAC_CODEC_CTRL_REG: subframe = (value >> 14) & 3; reg_num = (value >> 20) & 7; data = ((value >> 8) & 0xF00) | ((value >> 24) & 0xFF); - LOG_F(INFO, "AWAC subframe = %d, reg = %d, data = %08X\n", subframe, reg_num, data); + LOG_F(9, "Screamer subframe = %d, reg = %d, data = %08X\n", + subframe, reg_num, data); if (!subframe) this->control_regs[reg_num] = data; break; default: - LOG_F(ERROR, "AWAC: unsupported register at offset 0x%X", offset); + LOG_F(ERROR, "Screamer: unsupported register at offset 0x%X", offset); } } -#if 0 -static void convert_data(const uint8_t *in, SoundIoChannelArea *out_buf, uint32_t frame_count) -{ - uint16_t *p_in = (uint16_t *)in; - - for (int i = 0; i < frame_count; i += 2, p_in += 4) { - *(uint16_t *)(out_buf[0].ptr) = BYTESWAP_16(p_in[0]); - out_buf[0].ptr += out_buf[0].step; - *(uint16_t *)(out_buf[0].ptr) = BYTESWAP_16(p_in[1]); - out_buf[0].ptr += out_buf[0].step; - - *(uint16_t *)(out_buf[1].ptr) = BYTESWAP_16(p_in[2]); - out_buf[1].ptr += out_buf[1].step; - *(uint16_t *)(out_buf[1].ptr) = BYTESWAP_16(p_in[3]); - out_buf[1].ptr += out_buf[1].step; - } -} - -static void insert_silence(SoundIoChannelArea *out_buf, uint32_t frame_count) -{ - for (int i = 0; i < frame_count; i += 2) { - *(uint16_t *)(out_buf[0].ptr) = 0; - out_buf[0].ptr += out_buf[0].step; - *(uint16_t *)(out_buf[0].ptr) = 0; - out_buf[0].ptr += out_buf[0].step; - - *(uint16_t *)(out_buf[1].ptr) = 0; - out_buf[1].ptr += out_buf[1].step; - *(uint16_t *)(out_buf[1].ptr) = 0; - out_buf[1].ptr += out_buf[1].step; - } -} -#endif - -#if 0 -static void sound_out_callback(struct SoundIoOutStream *outstream, - int frame_count_min, int frame_count_max) -{ - int err, frame_count; - uint8_t *p_in; - uint32_t buf_len, rem_len, got_len; - struct SoundIoChannelArea *areas; - DMAChannel *dma_ch = (DMAChannel *)outstream->userdata; /* C API baby! */ - int n_channels = outstream->layout.channel_count; - bool stop = false; - - //if (!dma_ch->is_active()) { - // soundio_outstream_clear_buffer(outstream); - // soundio_outstream_pause(outstream, true); - // return; - //} - - if (frame_count_max > 512) { - frame_count = 512; - } - else { - frame_count = frame_count_max; - } - - buf_len = (frame_count * n_channels) << 1; - //frame_count = frame_count_max; - - //LOG_F(INFO, "frame_count_min=%d", frame_count_min); - //LOG_F(INFO, "frame_count_max=%d", frame_count_max); - //LOG_F(INFO, "channel count: %d", n_channels); - //LOG_F(INFO, "buf_len: %d", buf_len); - - if ((err = soundio_outstream_begin_write(outstream, &areas, &frame_count))) { - LOG_F(ERROR, "unrecoverable stream error: %s\n", soundio_strerror(err)); - return; - } - - for (rem_len = buf_len; rem_len > 0; rem_len -= got_len) { - if (!dma_ch->get_data(rem_len, &got_len, &p_in)) { - //LOG_F(INFO, "SndCallback: got_len = %d", got_len); - convert_data(p_in, areas, got_len >> 2); - //LOG_F(9, "Converted sound data, len = %d", got_len); - } else { /* no more data */ - //memset(buf, 0, rem_len); /* fill the buffer with silence */ - //LOG_F(9, "Inserted silence, len = %d", rem_len); - - /* fill the buffer with silence */ - //LOG_F(ERROR, "rem_len=%d", rem_len); - insert_silence(areas, rem_len >> 2); - stop = true; - break; - } - } - - if ((err = soundio_outstream_end_write(outstream))) { - LOG_F(ERROR, "unrecoverable stream error: %s\n", soundio_strerror(err)); - return; - } - - if (stop) { - LOG_F(INFO, "pausing result: %s", - soundio_strerror(soundio_outstream_pause(outstream, true))); - } -} -#endif - -long AWACDevice::sound_out_callback(cubeb_stream *stream, void *user_data, - void const *input_buffer, void *output_buffer, - long req_frames) -{ - uint8_t *p_in; - int16_t* in_buf, * out_buf; - uint32_t got_len; - long frames, out_frames; - AWACDevice *this_ptr = static_cast(user_data); /* C API baby! */ - - if (!this_ptr->dma_out_ch->is_active()) { - return 0; - } - - out_buf = (int16_t*)output_buffer; - - out_frames = 0; - - while (req_frames > 0) { - if (!this_ptr->dma_out_ch->get_data(req_frames << 2, &got_len, &p_in)) { - frames = got_len >> 2; - - in_buf = (int16_t*)p_in; - - for (int i = frames; i > 0; i--) { - out_buf[0] = BYTESWAP_16(in_buf[0]); - out_buf[1] = BYTESWAP_16(in_buf[1]); - in_buf += 2; - out_buf += 2; - } - - req_frames -= frames; - out_frames += frames; - } - else { - break; - } - } - - return out_frames; -} - -void status_callback(cubeb_stream *stream, void *user_data, cubeb_state state) -{ - LOG_F(9, "Cubeb status callback fired, status = %d", state); -} - -void AWACDevice::open_stream(int sample_rate) +void AwacsScreamer::open_stream(int sample_rate) { int err; - if ((err = this->snd_server->open_out_stream(sample_rate, sound_out_callback, - status_callback, (void *)this))) { - LOG_F(ERROR, "AWAC: unable to open sound output stream: %d", err); + if ((err = this->snd_server->open_out_stream(sample_rate, (void *)this->dma_out_ch))) { + LOG_F(ERROR, "Screamer: unable to open sound output stream: %d", err); this->out_stream_ready = false; } else { this->out_sample_rate = sample_rate; @@ -261,17 +186,17 @@ void AWACDevice::open_stream(int sample_rate) } } -void AWACDevice::dma_start() +void AwacsScreamer::dma_start() { int err; if (!this->out_stream_ready) { - this->open_stream(awac_freqs[(this->snd_ctrl_reg >> 8) & 7]); - } else if (this->out_sample_rate != awac_freqs[(this->snd_ctrl_reg >> 8) & 7]) { + this->open_stream(this->sr_table[(this->snd_ctrl_reg >> 8) & 7]); + } else if (this->out_sample_rate != this->sr_table[(this->snd_ctrl_reg >> 8) & 7]) { snd_server->close_out_stream(); - this->open_stream(awac_freqs[(this->snd_ctrl_reg >> 8) & 7]); + this->open_stream(this->sr_table[(this->snd_ctrl_reg >> 8) & 7]); } else { - LOG_F(ERROR, "AWAC: unpausing attempted!"); + LOG_F(ERROR, "Screamer: unpausing attempted!"); return; } @@ -280,32 +205,10 @@ void AWACDevice::dma_start() } if ((err = snd_server->start_out_stream())) { - LOG_F(ERROR, "Could not start sound output stream"); + LOG_F(ERROR, "Screamer: could not start sound output stream"); } } -void AWACDevice::dma_end() +void AwacsScreamer::dma_end() { } - -//=========================== PDM-style AWAC ================================= -AwacDevicePdm::AwacDevicePdm() -{ -} - -uint32_t AwacDevicePdm::read_stat() -{ - LOG_F(INFO, "AWAC-PDM status requested!"); - return 0; -} - -void AwacDevicePdm::write_ctrl(uint32_t addr, uint16_t value) -{ - LOG_F(INFO, "AWAC-PDM control updated, address=0x%X, val=0x%X", addr, value); - - if (addr <= 4) { - this->ctrl_regs[addr] = value; - } else { - LOG_F(ERROR, "AWAC-PDM: invalid control register address %d!", addr); - } -} diff --git a/devices/awacs.h b/devices/awacs.h index 6e388fe..e7b5822 100644 --- a/devices/awacs.h +++ b/devices/awacs.h @@ -21,26 +21,69 @@ along with this program. If not, see . /** AWAC sound devices family definitions. - Audio Waveform Aplifier and Converter (AWAC) is a family of custom audio + Audio Waveform Aplifier and Converters (AWACs) is a family of custom audio chips used in Power Macintosh. */ #ifndef AWAC_H #define AWAC_H +#include "dmacore.h" #include "dbdma.h" #include "i2c.h" #include "soundserver.h" #include +#include -/** AWAC registers offsets. */ +/** Base class for the AWACs codecs. */ +class AwacsBase { +public: + AwacsBase(); + ~AwacsBase(); + + void set_dma_out(DmaOutChannel *dma_out_ch) { + this->dma_out_ch = dma_out_ch; + }; + + void set_sample_rate(int sr_id); + void start_output_dma(); + void stop_output_dma(); + +protected: + SoundServer *snd_server; // SoundServer instance pointer + DmaOutChannel *dma_out_ch; // DMA output channel instance pointer + + int *sr_table; // pointer to the table of supported sample rates + int max_sr_id; // maximum value for sample rate ID + + bool out_stream_ready; + int cur_sample_rate; +}; + +/** AWACs PDM-style sound codec. */ +class AwacDevicePdm : public AwacsBase { +public: + AwacDevicePdm(); + ~AwacDevicePdm() = default; + + uint32_t read_stat(void); + void write_ctrl(uint32_t addr, uint16_t value); + + void dma_out_start(uint8_t sample_rate_id); + void dma_out_stop(); + +private: + uint16_t ctrl_regs[5]; // 12-bit wide control registers +}; + +/** AWACs Screamer registers offsets. */ enum { AWAC_SOUND_CTRL_REG = 0x00, AWAC_CODEC_CTRL_REG = 0x10, AWAC_CODEC_STATUS_REG = 0x20, }; -/** AWAC manufacturer and revision. */ +/** AWACs Screamer manufacturer and revision. */ #define AWAC_MAKER_CRYSTAL 1 #define AWAC_REV_SCREAMER 3 @@ -67,7 +110,8 @@ public: this->sub_addr = sub_addr & 0xF; this->auto_inc = !!(sub_addr & 0x10); - LOG_F(INFO, "TDA7433 subaddress = 0x%X, auto increment = %d", this->sub_addr, this->auto_inc); + LOG_F(9, "TDA7433 subaddress = 0x%X, auto increment = %d", + this->sub_addr, this->auto_inc); this->pos++; return true; }; @@ -76,7 +120,7 @@ public: if (!this->pos) { return send_subaddress(data); } else if (this->sub_addr <= 6) { - LOG_F(INFO, "TDA7433 byte 0x%X received", data); + LOG_F(9, "TDA7433 byte 0x%X received", data); this->regs[this->sub_addr] = data; if (this->auto_inc) { this->sub_addr++; @@ -89,7 +133,7 @@ public: bool receive_byte(uint8_t* p_data) { *p_data = this->regs[this->sub_addr]; - LOG_F(INFO, "TDA7433 byte 0x%X sent", *p_data); + LOG_F(9, "TDA7433 byte 0x%X sent", *p_data); return true; }; @@ -101,15 +145,13 @@ private: }; -class AWACDevice : public DMACallback { +class AwacsScreamer : public AwacsBase, public DMACallback { public: - AWACDevice(); - ~AWACDevice(); + AwacsScreamer(); + ~AwacsScreamer(); - void set_dma_out(DMAChannel* dma_out_ch); - - uint32_t snd_ctrl_read(uint32_t offset, int size); - void snd_ctrl_write(uint32_t offset, uint32_t value, int size); + uint32_t snd_ctrl_read(uint32_t offset, int size); + void snd_ctrl_write(uint32_t offset, uint32_t value, int size); /* DMACallback methods */ void dma_start(); @@ -119,42 +161,13 @@ protected: void open_stream(int sample_rate); private: - static long sound_out_callback(cubeb_stream* stream, void* user_data, - void const* input_buffer, void* output_buffer, - long req_frames); - - uint32_t snd_ctrl_reg = {0}; - uint16_t control_regs[8] = {0}; /* control registers, each 12-bits wide */ + uint32_t snd_ctrl_reg = { 0 }; + uint16_t control_regs[8] = { 0 }; /* control registers, each 12-bits wide */ uint8_t is_busy = 0; - AudioProcessor* audio_proc; - SoundServer *snd_server; - - bool out_stream_ready; int out_sample_rate; - DMAChannel *dma_out_ch; + std::unique_ptr audio_proc; }; -/** AWAC PDM-style definitions. */ - -// PDM HWInit source defines two constants: kExpBit = 0x80 and kCmdBit = 0x40 -// I don't know what they means but it seems that their combination will -// cause sound control parameters to be transferred to the sound chip. -#define PDM_SND_CNTL_VALID 0xC0 - -/** AWAC PDM-style sound codec. */ -class AwacDevicePdm { -public: - AwacDevicePdm(); - ~AwacDevicePdm() = default; - - uint32_t read_stat(void); - void write_ctrl(uint32_t addr, uint16_t value); - -private: - uint16_t ctrl_regs[5]; // 12-bit wide control registers -}; - - #endif /* AWAC_H */ diff --git a/devices/dbdma.cpp b/devices/dbdma.cpp index a654f26..184eb21 100644 --- a/devices/dbdma.cpp +++ b/devices/dbdma.cpp @@ -21,6 +21,7 @@ along with this program. If not, see . /** @file Descriptor-based direct memory access emulation. */ +#include "dmacore.h" #include "dbdma.h" #include "cpu/ppc/ppcmmu.h" #include "endianswap.h" @@ -184,13 +185,14 @@ void DMAChannel::reg_write(uint32_t offset, uint32_t value, int size) { } } -int DMAChannel::get_data(uint32_t req_len, uint32_t *avail_len, uint8_t **p_data) +DmaPullResult DMAChannel::pull_data(uint32_t req_len, uint32_t *avail_len, uint8_t **p_data) { *avail_len = 0; if (this->ch_stat & CH_STAT_DEAD || !(this->ch_stat & CH_STAT_ACTIVE)) { + /* dead or idle channel? -> no more data */ LOG_F(WARNING, "Dead/idle channel -> no more data"); - return -1; /* dead or idle channel? -> no more data */ + return DmaPullResult::NoMoreData; } /* interpret DBDMA program until we get data or become idle */ @@ -212,10 +214,10 @@ int DMAChannel::get_data(uint32_t req_len, uint32_t *avail_len, uint8_t **p_data *avail_len = this->queue_len; this->queue_len = 0; } - return 0; /* tell the caller there is more data */ + return DmaPullResult::MoreData; /* tell the caller there is more data */ } - return -1; /* tell the caller there is no more data */ + return DmaPullResult::NoMoreData; /* tell the caller there is no more data */ } bool DMAChannel::is_active() diff --git a/devices/dbdma.h b/devices/dbdma.h index 6712062..53e9a30 100644 --- a/devices/dbdma.h +++ b/devices/dbdma.h @@ -29,6 +29,7 @@ along with this program. If not, see . #ifndef DB_DMA_H #define DB_DMA_H +#include "dmacore.h" #include /** DBDMA Channel registers offsets */ @@ -67,7 +68,7 @@ public: //virtual void dma_pull(uint8_t *buf, int size) = 0; }; -class DMAChannel { +class DMAChannel : public DmaOutChannel { public: DMAChannel(DMACallback* cb) { this->dma_cb = cb; @@ -77,8 +78,8 @@ public: uint32_t reg_read(uint32_t offset, int size); void reg_write(uint32_t offset, uint32_t value, int size); - int get_data(uint32_t req_len, uint32_t *avail_len, uint8_t **p_data); - bool is_active(); + bool is_active(); + DmaPullResult pull_data(uint32_t req_len, uint32_t *avail_len, uint8_t **p_data); protected: void get_next_cmd(uint32_t cmd_addr, DMACmd* p_cmd); diff --git a/devices/dmacore.h b/devices/dmacore.h new file mode 100644 index 0000000..72bedb8 --- /dev/null +++ b/devices/dmacore.h @@ -0,0 +1,41 @@ +/* +DingusPPC - The Experimental PowerPC Macintosh emulator +Copyright (C) 2018-21 divingkatae and maximum + (theweirdo) spatium + +(Contact divingkatae#1017 or powermax#2286 on Discord for more info) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +/** @file Core definitions for direct memory access (DMA) emulation. */ + +#ifndef DMA_CORE_H +#define DMA_CORE_H + +#include + +enum DmaPullResult : int { + MoreData, // data source has more data to be pulled + NoMoreData, // data source has no more data to be pulled +}; + +class DmaOutChannel { +public: + virtual bool is_active() = 0; + virtual DmaPullResult pull_data(uint32_t req_len, uint32_t *avail_len, + uint8_t **p_data) = 0; +}; + +#endif // DMA_CORE_H diff --git a/devices/heathrow.cpp b/devices/heathrow.cpp index 97a4064..0958395 100644 --- a/devices/heathrow.cpp +++ b/devices/heathrow.cpp @@ -42,7 +42,7 @@ HeathrowIC::HeathrowIC() : PCIDevice("mac-io/heathrow") { this->viacuda = new ViaCuda(); gMachineObj->add_subdevice("ViaCuda", this->viacuda); - this->screamer = new AWACDevice(); + this->screamer = new AwacsScreamer(); this->snd_out_dma = new DMAChannel(this->screamer); this->screamer->set_dma_out(this->snd_out_dma); diff --git a/devices/macio.h b/devices/macio.h index daa2776..3810e2f 100644 --- a/devices/macio.h +++ b/devices/macio.h @@ -149,7 +149,7 @@ private: /* device cells */ ViaCuda* viacuda; // VIA cell with Cuda MCU attached to it NVram* nvram; // NVRAM cell - AWACDevice* screamer; // Screamer audio codec instance + AwacsScreamer *screamer; // Screamer audio codec instance MESHController *mesh; // MESH SCSI cell instance DMAChannel* snd_out_dma; diff --git a/devices/soundserver.cpp b/devices/soundserver.cpp index fb58c2c..72d8ea2 100644 --- a/devices/soundserver.cpp +++ b/devices/soundserver.cpp @@ -19,8 +19,9 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ +#include "dmacore.h" +#include "endianswap.h" #include "soundserver.h" -//#include #include #include #ifdef _WIN32 @@ -124,9 +125,54 @@ void SoundServer::shutdown() LOG_F(INFO, "Sound Server shut down."); } +long sound_out_callback(cubeb_stream *stream, void *user_data, + void const *input_buffer, void *output_buffer, + long req_frames) +{ + uint8_t *p_in; + int16_t* in_buf, * out_buf; + uint32_t got_len; + long frames, out_frames; + DmaOutChannel *dma_ch = static_cast(user_data); /* C API baby! */ -int SoundServer::open_out_stream(uint32_t sample_rate, cubeb_data_callback data_cb, - cubeb_state_callback status_cb, void *user_data) + if (!dma_ch->is_active()) { + return 0; + } + + out_buf = (int16_t*)output_buffer; + + out_frames = 0; + + while (req_frames > 0) { + if (!dma_ch->pull_data(req_frames << 2, &got_len, &p_in)) { + frames = got_len >> 2; + + in_buf = (int16_t*)p_in; + + for (int i = frames; i > 0; i--) { + out_buf[0] = BYTESWAP_16(in_buf[0]); + out_buf[1] = BYTESWAP_16(in_buf[1]); + in_buf += 2; + out_buf += 2; + } + + req_frames -= frames; + out_frames += frames; + } + else { + break; + } + } + + return out_frames; +} + +static void status_callback(cubeb_stream *stream, void *user_data, cubeb_state state) +{ + LOG_F(9, "Cubeb status callback fired, status = %d", state); +} + +int SoundServer::open_out_stream(uint32_t sample_rate, void *user_data) { int res; uint32_t latency_frames; @@ -148,7 +194,7 @@ int SoundServer::open_out_stream(uint32_t sample_rate, cubeb_data_callback data_ res = cubeb_stream_init(this->cubeb_ctx, &this->out_stream, "SndOut stream", NULL, NULL, NULL, ¶ms, latency_frames, - data_cb, status_cb, user_data); + sound_out_callback, status_callback, user_data); if (res != CUBEB_OK) { LOG_F(ERROR, "Could not open sound output stream, error: %d", res); return -1; diff --git a/devices/soundserver.h b/devices/soundserver.h index b0eccaa..6402096 100644 --- a/devices/soundserver.h +++ b/devices/soundserver.h @@ -35,7 +35,6 @@ along with this program. If not, see . #define SOUND_SERVER_H #include "hwcomponent.h" -//#include #include enum { @@ -52,32 +51,19 @@ public: int start(); void shutdown(); - int open_out_stream(uint32_t sample_rate, cubeb_data_callback data_cb, - cubeb_state_callback status_cb, void *user_data); + int open_out_stream(uint32_t sample_rate, void *user_data); int start_out_stream(); void close_out_stream(); - bool supports_type(HWCompType type) { return type == HWCompType::SND_SERVER; }; - -#if 0 - SoundIoDevice *get_out_device() { - if (this->status == SND_SERVER_UP) { - return this->out_device; - } else { - return NULL; - } + bool supports_type(HWCompType type) { + return type == HWCompType::SND_SERVER; }; -#endif private: int status; /* server status */ cubeb *cubeb_ctx; cubeb_stream *out_stream; - //SoundIo *soundio; - //int out_dev_index; /* current output device index */ - //SoundIoDevice *out_device; /* current output device instance */ - //SoundIoOutStream *outstream; }; #endif /* SOUND_SERVER_H */ diff --git a/machines/machinepdm.cpp b/machines/machinepdm.cpp index 967d442..c72299f 100644 --- a/machines/machinepdm.cpp +++ b/machines/machinepdm.cpp @@ -28,6 +28,7 @@ along with this program. If not, see . #include "devices/amic.h" #include "devices/hmc.h" #include "devices/machineid.h" +#include "devices/soundserver.h" #include "machinebase.h" #include "machineproperties.h" #include @@ -47,6 +48,9 @@ int create_pdm(std::string& id) { /* register HMC memory controller */ gMachineObj->add_component("HMC", new HMC); + /* start the sound server. */ + gMachineObj->add_component("SoundServer", new SoundServer()); + /* register AMIC I/O controller */ gMachineObj->add_component("AMIC", new AMIC);