// license:BSD-3-Clause
// copyright-holders:Phil Bennett
/***************************************************************************
Acclaim RAX Sound Board
****************************************************************************/
#include "emu.h"
#include "rax.h"
#include "speaker.h"
/*************************************
*
* Constants
*
*************************************/
/* These are some of the control registers. We don't use them all */
enum
{
IDMA_CONTROL_REG = 0, /* 3fe0 */
BDMA_INT_ADDR_REG, /* 3fe1 */
BDMA_EXT_ADDR_REG, /* 3fe2 */
BDMA_CONTROL_REG, /* 3fe3 */
BDMA_WORD_COUNT_REG, /* 3fe4 */
PROG_FLAG_DATA_REG, /* 3fe5 */
PROG_FLAG_CONTROL_REG, /* 3fe6 */
S1_AUTOBUF_REG = 15, /* 3fef */
S1_RFSDIV_REG, /* 3ff0 */
S1_SCLKDIV_REG, /* 3ff1 */
S1_CONTROL_REG, /* 3ff2 */
S0_AUTOBUF_REG, /* 3ff3 */
S0_RFSDIV_REG, /* 3ff4 */
S0_SCLKDIV_REG, /* 3ff5 */
S0_CONTROL_REG, /* 3ff6 */
S0_MCTXLO_REG, /* 3ff7 */
S0_MCTXHI_REG, /* 3ff8 */
S0_MCRXLO_REG, /* 3ff9 */
S0_MCRXHI_REG, /* 3ffa */
TIMER_SCALE_REG, /* 3ffb */
TIMER_COUNT_REG, /* 3ffc */
TIMER_PERIOD_REG, /* 3ffd */
WAITSTATES_REG, /* 3ffe */
SYSCONTROL_REG /* 3fff */
};
/*************************************
*
* Interface
*
*************************************/
void acclaim_rax_device::data_w(uint16_t data)
{
m_data_in->write(data);
m_cpu->set_input_line(ADSP2181_IRQL0, ASSERT_LINE);
machine().scheduler().boost_interleave(attotime::zero, attotime::from_usec(5));
}
uint16_t acclaim_rax_device::data_r()
{
m_adsp_snd_pf0 = 1;
return m_data_out->read();
}
/*************************************
*
* Internal
*
*************************************/
uint16_t acclaim_rax_device::adsp_control_r(offs_t offset)
{
uint16_t res = 0;
switch (offset)
{
case PROG_FLAG_DATA_REG:
res = m_adsp_snd_pf0;
break;
default:
res = m_control_regs[offset];
}
return res;
}
void acclaim_rax_device::adsp_control_w(offs_t offset, uint16_t data)
{
m_control_regs[offset] = data;
switch (offset)
{
case 0x1:
m_control_regs[BDMA_INT_ADDR_REG] = data & 0x3fff;
break;
case 0x2:
m_control_regs[BDMA_EXT_ADDR_REG] = data & 0x3fff;
break;
case 0x3:
m_control_regs[BDMA_CONTROL_REG] = data & 0xff0f;
break;
case 0x4:
{
m_control_regs[BDMA_WORD_COUNT_REG] = data & 0x3fff;
const uint8_t *adsp_rom = &m_rom[m_rom_bank * 0x400000];
uint32_t page = (m_control_regs[BDMA_CONTROL_REG] >> 8) & 0xff;
uint32_t dir = (m_control_regs[BDMA_CONTROL_REG] >> 2) & 1;
uint32_t type = m_control_regs[BDMA_CONTROL_REG] & 3;
uint32_t src_addr = (page << 14) | m_control_regs[BDMA_EXT_ADDR_REG];
uint32_t count = m_control_regs[BDMA_WORD_COUNT_REG];
address_space* addr_space = (type == 0 ? m_program : m_data);
if (dir == 0)
{
if (type == 0)
{
while (count)
{
uint32_t src_dword = (adsp_rom[src_addr + 0] << 16) | (adsp_rom[src_addr + 1] << 8) | adsp_rom[src_addr + 2];
addr_space->write_dword(m_control_regs[BDMA_INT_ADDR_REG], src_dword);
src_addr += 3;
++m_control_regs[BDMA_INT_ADDR_REG];
--count;
}
}
else if (type == 1)
{
while (count)
{
uint16_t src_word = (adsp_rom[src_addr + 0] << 8) | adsp_rom[src_addr + 1];
addr_space->write_word(m_control_regs[BDMA_INT_ADDR_REG], src_word);
src_addr += 2;
++m_control_regs[BDMA_INT_ADDR_REG];
--count;
}
}
else
{
int shift = type == 2 ? 8 : 0;
while (count)
{
uint16_t src_word = adsp_rom[src_addr] << shift;
addr_space->write_word(m_control_regs[BDMA_INT_ADDR_REG], src_word);
++src_addr;
++m_control_regs[BDMA_INT_ADDR_REG];
--count;
}
}
}
else
{
// TODO: last stage in Batman Forever!?
// page = 0, dir = 1, type = 1, src_addr = 0xfd
fatalerror("%s DMA to byte memory!",this->tag());
}
attotime word_period = attotime::from_hz(m_cpu->unscaled_clock());
attotime period = word_period * (data & 0x3fff) * 1;
m_dma_timer->adjust(period, src_addr, period);
break;
}
case S1_AUTOBUF_REG:
/* autobuffer off: nuke the timer, and disable the DAC */
if ((data & 0x0002) == 0)
{
m_dmadac[1]->enable(0);
}
break;
case S0_AUTOBUF_REG:
/* autobuffer off: nuke the timer, and disable the DAC */
if ((data & 0x0002) == 0)
{
m_dmadac[0]->enable(0);
m_reg_timer->reset();
}
break;
case S1_CONTROL_REG:
if (((data >> 4) & 3) == 2)
fatalerror("DCS: Oh no!, the data is compressed with u-law encoding\n");
if (((data >> 4) & 3) == 3)
fatalerror("DCS: Oh no!, the data is compressed with A-law encoding\n");
break;
case PROG_FLAG_DATA_REG:
logerror("PFLAGS: %x\n", data);
break;
case PROG_FLAG_CONTROL_REG:
logerror("PFLAG CTRL: %x\n", data);
break;
default:
logerror("Unhandled register: %x %x\n", 0x3fe0 + offset, data);
}
}
TIMER_DEVICE_CALLBACK_MEMBER( acclaim_rax_device::dma_timer_callback )
{
/* Update external address count and page */
m_control_regs[BDMA_WORD_COUNT_REG] = 0;
m_control_regs[BDMA_EXT_ADDR_REG] = param & 0x3fff;
m_control_regs[BDMA_CONTROL_REG] &= ~0xff00;
m_control_regs[BDMA_CONTROL_REG] |= ((param >> 14) & 0xff) << 8;
if (m_control_regs[BDMA_CONTROL_REG] & 8)
m_cpu->pulse_input_line(INPUT_LINE_RESET, attotime::zero);
else
m_cpu->pulse_input_line(ADSP2181_BDMA, m_cpu->minimum_quantum_time());
timer.adjust(attotime::never);
}
void acclaim_rax_device::update_data_ram_bank()
{
if (m_dmovlay_val == 0)
membank("databank")->set_entry(0);
else
membank("databank")->set_entry(1 + m_data_bank);
}
void acclaim_rax_device::ram_bank_w(uint16_t data)
{
// Note: The PCB has two unstuffed RAM locations
m_data_bank = data & 3;
update_data_ram_bank();
}
void acclaim_rax_device::rom_bank_w(uint16_t data)
{
m_rom_bank = data;
}
uint16_t acclaim_rax_device::host_r()
{
m_cpu->set_input_line(ADSP2181_IRQL0, CLEAR_LINE);
return m_data_in->read();
}
void acclaim_rax_device::host_w(uint16_t data)
{
m_data_out->write(data);
m_adsp_snd_pf0 = 0;
}
/*************************************
*
* CPU memory map & config
*
*************************************/
void acclaim_rax_device::adsp_program_map(address_map &map)
{
map.unmap_value_high();
map(0x0000, 0x3fff).ram().share("adsp_pram");
}
void acclaim_rax_device::adsp_data_map(address_map &map)
{
map.unmap_value_high();
map(0x0000, 0x1fff).bankrw("databank");
map(0x2000, 0x3fdf).ram(); // Internal RAM
map(0x3fe0, 0x3fff).rw(FUNC(acclaim_rax_device::adsp_control_r), FUNC(acclaim_rax_device::adsp_control_w));
}
void acclaim_rax_device::adsp_io_map(address_map &map)
{
map.unmap_value_high();
map(0x0000, 0x0000).w(FUNC(acclaim_rax_device::ram_bank_w));
map(0x0001, 0x0001).w(FUNC(acclaim_rax_device::rom_bank_w));
map(0x0003, 0x0003).rw(FUNC(acclaim_rax_device::host_r), FUNC(acclaim_rax_device::host_w));
}
void acclaim_rax_device::device_start()
{
m_program = &m_cpu->space(AS_PROGRAM);
m_data = &m_cpu->space(AS_DATA);
// 1 bank for internal
m_banked_ram = make_unique_clear<uint16_t[]>(0x2000 * 5);
membank("databank")->configure_entries(0, 5, &m_banked_ram[0], 0x2000*sizeof(uint16_t));
}
void acclaim_rax_device::device_reset()
{
/* Load 32 program words (96 bytes) via BDMA */
for (int i = 0; i < 32; i ++)
{
uint32_t word;
word = m_rom[i*3 + 0] << 16;
word |= m_rom[i*3 + 1] << 8;
word |= m_rom[i*3 + 2];
m_adsp_pram[i] = word;
}
m_adsp_snd_pf0 = 1;
m_rom_bank = 0;
/* initialize our state structure and install the transmit callback */
m_size[0] = 0;
m_incs[0] = 0;
m_ireg[0] = 0;
/* initialize the ADSP control regs */
memset(m_control_regs, 0, sizeof(m_control_regs));
m_dmovlay_val = 0;
m_data_bank = 0;
update_data_ram_bank();
}
void acclaim_rax_device::adsp_irq(int which)
{
if (which != 0)
return;
/* get the index register */
int reg = m_cpu->state_int(ADSP2100_I0 + m_ireg[which]);
/* copy the current data into the buffer */
int count = m_size[which] / (4 * (m_incs[which] ? m_incs[which] : 1));
int16_t buffer[0x100];
for (uint32_t i = 0; i < count; i++)
{
buffer[i] = m_data->read_word(reg);
reg += m_incs[which];
}
for (int i = 0; i < 2; i++)
{
m_dmadac[i]->flush();
m_dmadac[i]->transfer(i, 1, 2, count/2, buffer);
}
/* check for wrapping */
if (reg >= m_ireg_base[which] + m_size[which])
{
/* reset the base pointer */
reg = m_ireg_base[which];
}
m_cpu->set_state_int(ADSP2100_I0 + m_ireg[which], reg);
}
TIMER_DEVICE_CALLBACK_MEMBER( acclaim_rax_device::adsp_irq0 )
{
adsp_irq(0);
}
void acclaim_rax_device::recompute_sample_rate(int which)
{
/* calculate how long until we generate an interrupt */
/* frequency the time per each bit sent */
attotime sample_period = attotime::from_hz(m_cpu->unscaled_clock()) * (1 * (m_control_regs[which ? S1_SCLKDIV_REG : S0_SCLKDIV_REG] + 1));
/* now put it down to samples, so we know what the channel frequency has to be */
sample_period = sample_period * (16 * 1);
for (auto &dmadac : m_dmadac)
{
dmadac->set_frequency(sample_period.as_hz());
dmadac->enable(1);
}
/* fire off a timer which will hit every half-buffer */
if (m_incs[which])
{
attotime period = (sample_period * m_size[which]) / (4 * 2 * m_incs[which]);
m_reg_timer->adjust(period, 0, period);
}
}
void acclaim_rax_device::adsp_sound_tx_callback(offs_t offset, uint32_t data)
{
int which = offset;
if (which != 0)
return;
int autobuf_reg = which ? S1_AUTOBUF_REG : S0_AUTOBUF_REG;
/* check if SPORT1 is enabled */
if (m_control_regs[SYSCONTROL_REG] & (which ? 0x0800 : 0x1000)) /* bit 11 */
{
/* we only support autobuffer here (which is what this thing uses), bail if not enabled */
if (m_control_regs[autobuf_reg] & 0x0002) /* bit 1 */
{
/* get the autobuffer registers */
int mreg, lreg;
uint16_t source;
m_ireg[which] = (m_control_regs[autobuf_reg] >> 9) & 7;
mreg = (m_control_regs[autobuf_reg] >> 7) & 3;
mreg |= m_ireg[which] & 0x04; /* msb comes from ireg */
lreg = m_ireg[which];
/* now get the register contents in a more legible format */
/* we depend on register indexes to be continuous (which is the case in our core) */
source = m_cpu->state_int(ADSP2100_I0 + m_ireg[which]);
m_incs[which] = m_cpu->state_int(ADSP2100_M0 + mreg);
m_size[which] = m_cpu->state_int(ADSP2100_L0 + lreg);
/* get the base value, since we need to keep it around for wrapping */
source -= m_incs[which];
/* make it go back one so we dont lose the first sample */
m_cpu->set_state_int(ADSP2100_I0 + m_ireg[which], source);
/* save it as it is now */
m_ireg_base[which] = source;
/* recompute the sample rate and timer */
recompute_sample_rate(which);
return;
}
else
logerror( "ADSP SPORT1: trying to transmit and autobuffer not enabled!\n" );
}
/* if we get there, something went wrong. Disable playing */
for (auto &dmadac : m_dmadac)
dmadac->enable(0);
/* remove timer */
m_reg_timer->reset();
}
void acclaim_rax_device::dmovlay_callback(uint32_t data)
{
if (data < 0 || data > 1)
{
fatalerror("dmovlay_callback: Error! dmovlay called with value = %X\n", data);
}
else
{
m_dmovlay_val = data;
update_data_ram_bank();
}
}
DEFINE_DEVICE_TYPE(ACCLAIM_RAX, acclaim_rax_device, "rax_audio", "Acclaim RAX")
//-------------------------------------------------
// acclaim_rax_device - constructor
//-------------------------------------------------
acclaim_rax_device::acclaim_rax_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, ACCLAIM_RAX, tag, owner, clock)
, m_cpu(*this, "adsp")
, m_dmadac(*this, { "dacl", "dacr" })
, m_reg_timer(*this, "adsp_reg_timer")
, m_dma_timer(*this, "adsp_dma_timer")
, m_adsp_pram(*this, "adsp_pram")
, m_adsp_data_bank(*this, "databank")
, m_rom(*this, DEVICE_SELF)
, m_data_in(*this, "data_in")
, m_data_out(*this, "data_out")
{
}
//-------------------------------------------------
// device_add_mconfig - add device configuration
//-------------------------------------------------
void acclaim_rax_device::device_add_mconfig(machine_config &config)
{
ADSP2181(config, m_cpu, XTAL(16'670'000));
m_cpu->sport_tx().set(FUNC(acclaim_rax_device::adsp_sound_tx_callback)); /* callback for serial transmit */
m_cpu->dmovlay().set(FUNC(acclaim_rax_device::dmovlay_callback)); /* callback for adsp 2181 dmovlay instruction */
m_cpu->set_addrmap(AS_PROGRAM, &acclaim_rax_device::adsp_program_map);
m_cpu->set_addrmap(AS_DATA, &acclaim_rax_device::adsp_data_map);
m_cpu->set_addrmap(AS_IO, &acclaim_rax_device::adsp_io_map);
TIMER(config, "adsp_reg_timer").configure_generic(FUNC(acclaim_rax_device::adsp_irq0));
TIMER(config, "adsp_dma_timer").configure_generic(FUNC(acclaim_rax_device::dma_timer_callback));
GENERIC_LATCH_16(config, m_data_in);
GENERIC_LATCH_16(config, m_data_out);
SPEAKER(config, "lspeaker").front_left();
SPEAKER(config, "rspeaker").front_right();
DMADAC(config, "dacl").add_route(ALL_OUTPUTS, "lspeaker", 1.0);
DMADAC(config, "dacr").add_route(ALL_OUTPUTS, "rspeaker", 1.0);
}