// license:BSD-3-Clause
// copyright-holders:Aaron Giles
/***************************************************************************
Atari CAGE Audio Board
****************************************************************************/
#include "emu.h"
#include "cage.h"
#include "speaker.h"
#define LOG_COMM (0)
#define LOG_32031_IOPORTS (0)
/*************************************
*
* Constants/macros
*
*************************************/
#define DAC_BUFFER_CHANNELS 4
#define STACK_SOUND_BUFSIZE (1024)
/*************************************
*
* I/O port definitions
*
*************************************/
#define DMA_GLOBAL_CTL 0x00
#define DMA_SOURCE_ADDR 0x04
#define DMA_DEST_ADDR 0x06
#define DMA_TRANSFER_COUNT 0x08
#define TIMER0_GLOBAL_CTL 0x20
#define TIMER0_COUNTER 0x24
#define TIMER0_PERIOD 0x28
#define TIMER1_GLOBAL_CTL 0x30
#define TIMER1_COUNTER 0x34
#define TIMER1_PERIOD 0x38
#define SPORT_GLOBAL_CTL 0x40
#define SPORT_TX_CTL 0x42
#define SPORT_RX_CTL 0x43
#define SPORT_TIMER_CTL 0x44
#define SPORT_TIMER_COUNTER 0x45
#define SPORT_TIMER_PERIOD 0x46
#define SPORT_DATA_TX 0x48
#define SPORT_DATA_RX 0x4c
static const char *const register_names[] =
{
"TMS32031-DMA global control", nullptr, nullptr, nullptr,
"TMS32031-DMA source address", nullptr, "TMS32031-DMA destination address", nullptr,
"TMS32031-DMA transfer counter", nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
"TMS32031-Timer 0 global control", nullptr, nullptr, nullptr,
"TMS32031-Timer 0 counter", nullptr, nullptr, nullptr,
"TMS32031-Timer 0 period", nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
"TMS32031-Timer 1 global control", nullptr, nullptr, nullptr,
"TMS32031-Timer 1 counter", nullptr, nullptr, nullptr,
"TMS32031-Timer 1 period", nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
"TMS32031-Serial port global control", nullptr, "TMS32031-Serial port TX control", "TMS32031-Serial port RX control",
"TMS32031-Serial port timer control", "TMS32031-Serial port timer counter", "TMS32031-Serial port timer period", nullptr,
"TMS32031-Serial port data TX", nullptr, nullptr, nullptr,
"TMS32031-Serial port data RX", nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
"TMS32031-Primary bus control", nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr
};
/*************************************
*
* Initialization
*
*************************************/
DEFINE_DEVICE_TYPE(ATARI_CAGE, atari_cage_device, "atari_cage", "Atari CAGE")
//-------------------------------------------------
// atari_cage_device - constructor
//-------------------------------------------------
atari_cage_device::atari_cage_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
atari_cage_device(mconfig, ATARI_CAGE, tag, owner, clock)
{
}
atari_cage_device::atari_cage_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) :
device_t(mconfig, type, tag, owner, clock),
m_cpu(*this, "cpu"),
m_cageram(*this, "cageram"),
m_soundlatch(*this, "soundlatch"),
m_dma_timer(*this, "cage_dma_timer"),
m_timer(*this, "cage_timer%u", 0U),
m_dmadac(*this, "dac%u", 1U),
m_bootbank(*this, "bootbank"),
m_mainbank(*this, "mainbank"),
m_bootrom(*this, "boot"),
m_mainrom(*this, DEVICE_SELF),
m_irqhandler(*this)
{
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void atari_cage_device::device_start()
{
attotime cage_cpu_clock_period;
// resolve callbacks
m_irqhandler.resolve_safe();
m_bootbank->set_base(m_bootrom->base());
m_mainbank->set_base(m_mainrom->base());
cage_cpu_clock_period = attotime::from_hz(m_cpu->clock());
m_cpu_h1_clock_period = cage_cpu_clock_period * 2;
if (m_speedup) {
m_cpu->space(AS_PROGRAM).install_write_handler(m_speedup, m_speedup, write32s_delegate(*this, FUNC(atari_cage_device::speedup_w)));
m_speedup_ram = m_cageram + m_speedup;
}
save_item(NAME(m_cpu_to_cage_ready));
save_item(NAME(m_cage_to_cpu_ready));
save_item(NAME(m_serial_period_per_word));
save_item(NAME(m_dma_enabled));
save_item(NAME(m_dma_timer_enabled));
save_item(NAME(m_timer_enabled));
save_item(NAME(m_from_main));
save_item(NAME(m_control));
save_item(NAME(m_tms32031_io_regs));
}
void atari_cage_device::reset_w(int state)
{
if (state)
control_w(0);
m_cpu->set_input_line(INPUT_LINE_RESET, state ? ASSERT_LINE : CLEAR_LINE);
}
/*************************************
*
* DMA timers
*
*************************************/
TIMER_DEVICE_CALLBACK_MEMBER( atari_cage_device::dma_timer_callback )
{
/* if we weren't enabled, don't do anything, just shut ourself off */
if (!m_dma_enabled)
{
if (m_dma_timer_enabled)
{
timer.adjust(attotime::never);
m_dma_timer_enabled = 0;
}
return;
}
/* set the final count to 0 and the source address to the final address */
m_tms32031_io_regs[DMA_TRANSFER_COUNT] = 0;
m_tms32031_io_regs[DMA_SOURCE_ADDR] = param;
/* set the interrupt */
m_cpu->set_input_line(TMS3203X_DINT, ASSERT_LINE);
m_dma_enabled = 0;
}
void atari_cage_device::update_dma_state()
{
/* determine the new enabled state */
int enabled = ((m_tms32031_io_regs[DMA_GLOBAL_CTL] & 3) == 3) && (m_tms32031_io_regs[DMA_TRANSFER_COUNT] != 0);
/* see if we turned on */
if (enabled && !m_dma_enabled)
{
int16_t sound_data[STACK_SOUND_BUFSIZE];
uint32_t addr, inc;
int i;
/* make sure our assumptions are correct */
if (m_tms32031_io_regs[DMA_DEST_ADDR] != 0x808048)
logerror("CAGE DMA: unexpected dest address %08X!\n", m_tms32031_io_regs[DMA_DEST_ADDR]);
if ((m_tms32031_io_regs[DMA_GLOBAL_CTL] & 0xfef) != 0xe03)
logerror("CAGE DMA: unexpected transfer params %08X!\n", m_tms32031_io_regs[DMA_GLOBAL_CTL]);
/* do the DMA up front */
addr = m_tms32031_io_regs[DMA_SOURCE_ADDR];
inc = (m_tms32031_io_regs[DMA_GLOBAL_CTL] >> 4) & 1;
for (i = 0; i < m_tms32031_io_regs[DMA_TRANSFER_COUNT]; i++)
{
sound_data[i % STACK_SOUND_BUFSIZE] = m_cpu->space(AS_PROGRAM).read_dword(addr);
addr += inc;
if (i % STACK_SOUND_BUFSIZE == STACK_SOUND_BUFSIZE - 1)
for (int j = 0; j < DAC_BUFFER_CHANNELS; j++)
m_dmadac[j]->transfer(j, 1, DAC_BUFFER_CHANNELS, STACK_SOUND_BUFSIZE / DAC_BUFFER_CHANNELS, sound_data);
}
if (m_tms32031_io_regs[DMA_TRANSFER_COUNT] % STACK_SOUND_BUFSIZE != 0)
for (int j = 0; j < DAC_BUFFER_CHANNELS; j++)
m_dmadac[j]->transfer(j, 1, DAC_BUFFER_CHANNELS, (m_tms32031_io_regs[DMA_TRANSFER_COUNT] % STACK_SOUND_BUFSIZE) / DAC_BUFFER_CHANNELS, sound_data);
/* compute the time of the interrupt and set the timer */
if (!m_dma_timer_enabled)
{
attotime period = m_serial_period_per_word * m_tms32031_io_regs[DMA_TRANSFER_COUNT];
m_dma_timer->adjust(period, addr, period);
m_dma_timer_enabled = 1;
}
}
/* see if we turned off */
else if (!enabled && m_dma_enabled)
{
m_dma_timer->reset();
m_dma_timer_enabled = 0;
}
/* set the new state */
m_dma_enabled = enabled;
}
/*************************************
*
* Internal timers
*
*************************************/
TIMER_DEVICE_CALLBACK_MEMBER( atari_cage_device::cage_timer_callback )
{
int which = param;
/* set the interrupt */
m_cpu->set_input_line(TMS3203X_TINT0 + which, ASSERT_LINE);
m_timer_enabled[which] = 0;
update_timer(which);
}
void atari_cage_device::update_timer(int which)
{
/* determine the new enabled state */
int base = 0x10 * which;
int enabled = ((m_tms32031_io_regs[base + TIMER0_GLOBAL_CTL] & 0xc0) == 0xc0);
/* see if we turned on */
if (enabled && !m_timer_enabled[which])
{
attotime period = m_cpu_h1_clock_period * (2 * m_tms32031_io_regs[base + TIMER0_PERIOD]);
/* make sure our assumptions are correct */
if (m_tms32031_io_regs[base + TIMER0_GLOBAL_CTL] != 0x2c1)
logerror("CAGE TIMER%d: unexpected timer config %08X!\n", which, m_tms32031_io_regs[base + TIMER0_GLOBAL_CTL]);
m_timer[which]->adjust(period, which);
}
/* see if we turned off */
else if (!enabled && m_timer_enabled[which])
{
m_timer[which]->adjust(attotime::never, which);
}
/* set the new state */
m_timer_enabled[which] = enabled;
}
/*************************************
*
* Serial port I/O
*
*************************************/
void atari_cage_device::update_serial()
{
attotime serial_clock_period, bit_clock_period;
uint32_t freq;
/* we start out at half the H1 frequency (or 2x the H1 period) */
serial_clock_period = m_cpu_h1_clock_period * 2;
/* if we're in clock mode, muliply by another factor of 2 */
if (m_tms32031_io_regs[SPORT_GLOBAL_CTL] & 4)
serial_clock_period *= 2;
/* now multiply by the timer period */
if ((m_tms32031_io_regs[SPORT_TIMER_PERIOD] & 0xffff) == 0)
bit_clock_period = (m_tms32031_io_regs[SPORT_GLOBAL_CTL] & 4) ? serial_clock_period : attotime::never;
else
bit_clock_period = serial_clock_period * (m_tms32031_io_regs[SPORT_TIMER_PERIOD] & 0xffff);
/* and times the number of bits per sample */
m_serial_period_per_word = bit_clock_period * (8 * (((m_tms32031_io_regs[SPORT_GLOBAL_CTL] >> 18) & 3) + 1));
/* compute the step value to stretch this to the sample_rate */
freq = m_serial_period_per_word.as_hz() / DAC_BUFFER_CHANNELS;
if (freq > 0 && freq < 100000)
{
for (int i = 0; i < 4; i++)
{
m_dmadac[i]->set_frequency(freq);
m_dmadac[i]->enable(1);
}
}
}
/*************************************
*
* Master read/write
*
*************************************/
uint32_t atari_cage_device::tms32031_io_r(offs_t offset)
{
uint16_t result = m_tms32031_io_regs[offset];
switch (offset)
{
case DMA_GLOBAL_CTL:
result = (result & ~0xc) | (m_dma_enabled ? 0xc : 0x0);
break;
}
if (LOG_32031_IOPORTS)
logerror("%s CAGE:%s read -> %08X\n", machine().describe_context(), register_names[offset & 0x7f], result);
return result;
}
void atari_cage_device::tms32031_io_w(offs_t offset, uint32_t data, uint32_t mem_mask)
{
COMBINE_DATA(&m_tms32031_io_regs[offset]);
if (LOG_32031_IOPORTS)
logerror("%s CAGE:%s write = %08X\n", machine().describe_context(), register_names[offset & 0x7f], m_tms32031_io_regs[offset]);
switch (offset)
{
case DMA_GLOBAL_CTL:
case DMA_SOURCE_ADDR:
case DMA_DEST_ADDR:
case DMA_TRANSFER_COUNT:
update_dma_state();
break;
case TIMER0_GLOBAL_CTL:
case TIMER0_COUNTER:
case TIMER0_PERIOD:
update_timer(0);
break;
case TIMER1_GLOBAL_CTL:
case TIMER1_COUNTER:
case TIMER1_PERIOD:
update_timer(1);
break;
case SPORT_TX_CTL:
case SPORT_RX_CTL:
case SPORT_TIMER_COUNTER:
case SPORT_DATA_RX:
break;
case SPORT_DATA_TX:
#if (DAC_BUFFER_CHANNELS == 4)
if (int(m_serial_period_per_word.as_hz()) == 22050*4 && (m_tms32031_io_regs[SPORT_RX_CTL] & 0xff) == 0x62)
m_tms32031_io_regs[SPORT_RX_CTL] ^= 0x800;
#endif
break;
case SPORT_GLOBAL_CTL:
case SPORT_TIMER_CTL:
case SPORT_TIMER_PERIOD:
update_serial();
break;
}
}
/*************************************
*
* External interfaces
*
*************************************/
void atari_cage_device::update_control_lines()
{
int val;
/* set the IRQ to the main CPU */
int reason = 0;
if ((m_control & 3) == 3 && !m_cpu_to_cage_ready)
reason |= CAGE_IRQ_REASON_BUFFER_EMPTY;
if ((m_control & 2) && m_cage_to_cpu_ready)
reason |= CAGE_IRQ_REASON_DATA_READY;
m_irqhandler(0, reason);
/* set the IOF input lines */
val = m_cpu->state_int(TMS3203X_IOF);
val &= ~0x88;
if (m_cpu_to_cage_ready) val |= 0x08;
if (m_cage_to_cpu_ready) val |= 0x80;
m_cpu->set_state_int(TMS3203X_IOF, val);
}
uint32_t atari_cage_device::cage_from_main_r()
{
if (LOG_COMM)
logerror("%s CAGE read command = %04X\n", machine().describe_context(), m_from_main);
m_cpu_to_cage_ready = 0;
update_control_lines();
m_cpu->set_input_line(TMS3203X_IRQ0, CLEAR_LINE);
return m_from_main;
}
void atari_cage_device::cage_from_main_ack_w(uint32_t data)
{
if (LOG_COMM)
{
logerror("%s CAGE ack command = %04X\n", machine().describe_context(), m_from_main);
}
}
void atari_cage_device::cage_to_main_w(uint32_t data)
{
if (LOG_COMM)
logerror("%s Data from CAGE = %04X\n", machine().describe_context(), data);
m_soundlatch->write(data);
m_cage_to_cpu_ready = 1;
update_control_lines();
}
uint32_t atari_cage_device::cage_io_status_r()
{
int result = 0;
if (m_cpu_to_cage_ready)
result |= 0x80;
if (!m_cage_to_cpu_ready)
result |= 0x40;
return result;
}
uint16_t atari_cage_device::main_r()
{
if (LOG_COMM)
logerror("%s:main read data = %04X\n", machine().describe_context(), m_soundlatch->read());
m_cage_to_cpu_ready = 0;
update_control_lines();
return m_soundlatch->read();
}
TIMER_CALLBACK_MEMBER( atari_cage_device::cage_deferred_w )
{
m_from_main = param;
m_cpu_to_cage_ready = 1;
update_control_lines();
m_cpu->set_input_line(TMS3203X_IRQ0, ASSERT_LINE);
}
void atari_cage_device::main_w(uint16_t data)
{
if (LOG_COMM)
logerror("%s:Command to CAGE = %04X\n", machine().describe_context(), data);
machine().scheduler().synchronize(timer_expired_delegate(FUNC(atari_cage_device::cage_deferred_w),this), data);
}
uint16_t atari_cage_device::control_r()
{
uint16_t result = 0;
if (m_cpu_to_cage_ready)
result |= 2;
if (m_cage_to_cpu_ready)
result |= 1;
return result;
}
void atari_cage_device::control_w(uint16_t data)
{
m_control = data;
/* CPU is reset if both control lines are 0 */
if (!(m_control & 3))
{
m_cpu->set_input_line(INPUT_LINE_RESET, ASSERT_LINE);
m_cpu->set_input_line(TMS3203X_IRQ1, ASSERT_LINE);
m_dma_enabled = 0;
m_dma_timer_enabled = 0;
m_dma_timer->reset();
m_timer_enabled[0] = 0;
m_timer_enabled[1] = 0;
m_timer[0]->reset();
m_timer[1]->reset();
memset(m_tms32031_io_regs, 0, 0x60 * 4);
m_cpu_to_cage_ready = 0;
m_cage_to_cpu_ready = 0;
}
else
{
m_cpu->set_input_line(INPUT_LINE_RESET, CLEAR_LINE);
m_cpu->set_input_line(TMS3203X_IRQ1, CLEAR_LINE);
}
/* update the control state */
update_control_lines();
}
/*************************************
*
* Speedups
*
*************************************/
void atari_cage_device::speedup_w(offs_t offset, uint32_t data, uint32_t mem_mask)
{
m_cpu->eat_cycles(100);
COMBINE_DATA(&m_speedup_ram[offset]);
}
/*************************************
*
* CPU memory map & config
*
*************************************/
void atari_cage_device::cage_map(address_map &map)
{
map(0x000000, 0x00ffff).ram().share("cageram");
map(0x200000, 0x200000).nopw();
map(0x400000, 0x47ffff).bankr("bootbank");
map(0x808000, 0x8080ff).rw(FUNC(atari_cage_device::tms32031_io_r), FUNC(atari_cage_device::tms32031_io_w));
map(0xa00000, 0xa00000).rw(FUNC(atari_cage_device::cage_from_main_r), FUNC(atari_cage_device::cage_to_main_w));
map(0xc00000, 0xffffff).bankr("mainbank");
}
void atari_cage_seattle_device::cage_map_seattle(address_map &map)
{
map(0x000000, 0x00ffff).ram().share("cageram");
map(0x200000, 0x200000).nopw();
map(0x400000, 0x47ffff).bankr("bootbank");
map(0x808000, 0x8080ff).rw(FUNC(atari_cage_seattle_device::tms32031_io_r), FUNC(atari_cage_seattle_device::tms32031_io_w));
map(0xa00000, 0xa00000).rw(FUNC(atari_cage_seattle_device::cage_from_main_r), FUNC(atari_cage_seattle_device::cage_from_main_ack_w));
map(0xa00001, 0xa00001).w(FUNC(atari_cage_seattle_device::cage_to_main_w));
map(0xa00003, 0xa00003).r(FUNC(atari_cage_seattle_device::cage_io_status_r));
map(0xc00000, 0xffffff).bankr("mainbank");
}
//-------------------------------------------------
// machine_add_config - add device configuration
//-------------------------------------------------
void atari_cage_device::device_add_mconfig(machine_config &config)
{
/* basic machine hardware */
TMS32031(config, m_cpu, 33868800);
m_cpu->set_addrmap(AS_PROGRAM, &atari_cage_device::cage_map);
m_cpu->set_mcbl_mode(true);
TIMER(config, m_dma_timer).configure_generic(DEVICE_SELF, FUNC(atari_cage_device::dma_timer_callback));
TIMER(config, m_timer[0]).configure_generic(DEVICE_SELF, FUNC(atari_cage_device::cage_timer_callback));
TIMER(config, m_timer[1]).configure_generic(DEVICE_SELF, FUNC(atari_cage_device::cage_timer_callback));
/* sound hardware */
SPEAKER(config, "lspeaker").front_left();
SPEAKER(config, "rspeaker").front_right();
GENERIC_LATCH_16(config, m_soundlatch);
#if (DAC_BUFFER_CHANNELS == 4)
DMADAC(config, m_dmadac[0]).add_route(ALL_OUTPUTS, "rspeaker", 0.50);
DMADAC(config, m_dmadac[1]).add_route(ALL_OUTPUTS, "lspeaker", 0.50);
DMADAC(config, m_dmadac[2]).add_route(ALL_OUTPUTS, "lspeaker", 0.50);
DMADAC(config, m_dmadac[3]).add_route(ALL_OUTPUTS, "rspeaker", 0.50);
#else
DMADAC(config, m_dmadac[0]).add_route(ALL_OUTPUTS, "lspeaker", 1.0);
DMADAC(config, m_dmadac[1]).add_route(ALL_OUTPUTS, "rspeaker", 1.0);
#endif
}
DEFINE_DEVICE_TYPE(ATARI_CAGE_SEATTLE, atari_cage_seattle_device, "atari_cage_seattle", "Atari CAGE Seattle")
//-------------------------------------------------
// atari_cage_seattle_device - constructor
//-------------------------------------------------
atari_cage_seattle_device::atari_cage_seattle_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
atari_cage_device(mconfig, ATARI_CAGE_SEATTLE, tag, owner, clock)
{
}
//-------------------------------------------------
// machine_add_config - add device configuration
//-------------------------------------------------
void atari_cage_seattle_device::device_add_mconfig(machine_config &config)
{
atari_cage_device::device_add_mconfig(config);
m_cpu->set_addrmap(AS_PROGRAM, &atari_cage_seattle_device::cage_map_seattle);
}