// license:BSD-3-Clause
// copyright-holders:AJR
/**********************************************************************
Am9513A/Am9513 System Timing Controller (STC)
The Am9513 is a five-channel counter/timer circuit introduced by
AMD around 1980. (It was also sold as the AmZ8073, apparently due
to a licensing deal with Zilog to develop Z8000 peripherals. No
company is known to have second-sourced the device, however.)
Clock source, edge selection, gating and retriggering are
programmable for each channel. There is also a frequency divider
which can take any of the 15 normal counter inputs and divide it
by any number between 1 and 16.
All internal counters are 16 bits wide (except the internal 4-bit
counter for the FOUT divider, which is not externally accessible).
The device defaults to an 8-bit external interface after being
powered on or a programmed master reset, but can be configured to
work more efficiently with an 16-bit data bus.
There is no reset line, though the device does reset itself when
it powers on, and there is a "master reset" software command.
For a full description of each counter mode, see the datasheet.
**********************************************************************/
#include "emu.h"
#include "machine/am9513.h"
#define LOG_GENERAL (1U << 0)
#define LOG_MODE (1U << 1)
#define LOG_INPUT (1U << 2)
#define LOG_TC (1U << 3)
#define VERBOSE (LOG_GENERAL | LOG_MODE)
#include "logmacro.h"
//**************************************************************************
// TYPE DEFINITIONS
//**************************************************************************
DEFINE_DEVICE_TYPE(AM9513, am9513_device, "am9513", "Am9513 STC")
DEFINE_DEVICE_TYPE(AM9513A, am9513a_device, "am9513a", "Am9513A STC")
//**************************************************************************
// DEVICE CONSTRUCTION AND INITIALIZATION
//**************************************************************************
//-------------------------------------------------
// am9513_device - constructor
//-------------------------------------------------
am9513_device::am9513_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock, bool is_am9513a)
: device_t(mconfig, type, tag, owner, clock),
m_out_cb{{*this}, {*this}, {*this}, {*this}, {*this}},
m_fout_cb(*this),
m_is_am9513a(is_am9513a)
{
}
am9513_device::am9513_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
: am9513_device(mconfig, AM9513, tag, owner, clock, false)
{
}
am9513a_device::am9513a_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
: am9513_device(mconfig, AM9513A, tag, owner, clock, true)
{
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void am9513_device::device_start()
{
// Resolve callbacks
for (auto &cb : m_out_cb)
cb.resolve_safe();
m_fout_cb.resolve();
// Power-on reset
m_dpr = 0x1f;
m_mmr = 0;
m_write_prefetch = true;
m_status = 0;
m_f = 0;
m_fout = true;
m_fout_counter = 16;
std::fill(std::begin(m_count), std::end(m_count), 0);
std::fill(std::begin(m_counter_load), std::end(m_counter_load), 0);
std::fill(std::begin(m_counter_hold), std::end(m_counter_hold), 0);
std::fill(std::begin(m_counter_mode), std::end(m_counter_mode), 0x0b00);
std::fill(std::begin(m_alarm), std::end(m_alarm), 0);
std::fill(std::begin(m_counter_armed), std::end(m_counter_armed), false);
std::fill(std::begin(m_counter_running), std::end(m_counter_running), false);
std::fill(std::begin(m_alternate_count), std::end(m_alternate_count), false);
std::fill(std::begin(m_tc), std::end(m_tc), false);
std::fill(std::begin(m_toggle), std::end(m_toggle), false);
// Unused SRC and GATE inputs are typically grounded
std::fill(std::begin(m_src), std::end(m_src), false);
std::fill(std::begin(m_gate), std::end(m_gate), false);
std::fill(std::begin(m_gate_active), std::end(m_gate_active), false);
// Alternate gate inputs should be tied high if not used
std::fill(std::begin(m_gate_alt), std::end(m_gate_alt), true);
// Set up frequency timers
for (int f = 0; f < 5; f++)
{
m_freq_timer[f] = timer_alloc(TIMER_F1 + f);
m_freq_timer_selected[f] = (f == 0) ? (m_fout_cb.isnull() ? 0x3e : 0x3f) : 0;
m_freq_timer_cycle[f] = 0;
}
// Save device state
save_item(NAME(m_dpr));
save_item(NAME(m_mmr));
save_item(NAME(m_status));
save_item(NAME(m_write_prefetch));
save_item(NAME(m_count));
save_item(NAME(m_counter_load));
save_item(NAME(m_counter_hold));
save_item(NAME(m_counter_mode));
save_item(NAME(m_alarm));
save_item(NAME(m_counter_armed));
save_item(NAME(m_counter_running));
save_item(NAME(m_alternate_count));
save_item(NAME(m_src));
save_item(NAME(m_gate));
save_item(NAME(m_gate_alt));
save_item(NAME(m_gate_active));
save_item(NAME(m_tc));
save_item(NAME(m_toggle));
save_item(NAME(m_f));
save_item(NAME(m_freq_timer_selected));
save_item(NAME(m_freq_timer_cycle));
save_item(NAME(m_fout));
save_item(NAME(m_fout_counter));
// Synchronize clearing of OUT n
machine().scheduler().synchronize(timer_expired_delegate(FUNC(am9513_device::clear_outputs), this));
}
//-------------------------------------------------
// clear_outputs - output initial clear state
// (delayed until all devices have started)
//-------------------------------------------------
TIMER_CALLBACK_MEMBER(am9513_device::clear_outputs)
{
for (int c = 0; c < 5; c++)
m_out_cb[c](0);
}
//-------------------------------------------------
// master_reset - software-controlled reset
//-------------------------------------------------
void am9513_device::master_reset()
{
LOGMASKED(LOG_MODE, "Master reset\n");
// Clear master mode register
set_master_mode(0);
// Enable prefetch for write
m_write_prefetch = true;
// Clear TC state
std::fill(std::begin(m_tc), std::end(m_tc), false);
std::fill(std::begin(m_toggle), std::end(m_toggle), false);
// Initialize counter mode, load and hold registers
for (int c = 0; c < 5; c++)
{
set_counter_mode(c, 0x0b00);
m_counter_load[c] = 0;
m_counter_hold[c] = 0;
}
}
//**************************************************************************
// FREQUENCY SCALER
//**************************************************************************
//-------------------------------------------------
// init_freq_timer - set up one of the F1-F5
// frequency timers
//-------------------------------------------------
void am9513_device::init_freq_timer(int f)
{
u32 scale = 1;
for (int n = 0; n < f; n++)
scale *= BIT(m_mmr, 15) ? 10 : 16;
attotime freq = clocks_to_attotime(scale);
if (m_freq_timer_cycle[f] == 0)
m_freq_timer[f]->adjust(freq, 0, freq);
else
m_freq_timer[f]->adjust(freq / 2, 0, freq / 2);
m_freq_timer[f]->enable(m_freq_timer_selected[f] != 0);
LOGMASKED(LOG_GENERAL, "F%d = %f Hz (%s cycle emulation)\n", f + 1, double(clock()) / scale,
m_freq_timer_selected[f] == 0 ? "no" : m_freq_timer_cycle[f] == 0 ? "partial" : "full");
}
//-------------------------------------------------
// device_clock_changed - called when the
// device clock is altered in any way
//-------------------------------------------------
void am9513_device::device_clock_changed()
{
for (int f = 0; f < 5; f++)
init_freq_timer(f);
}
//-------------------------------------------------
// device_timer - called whenever a device timer
// fires
//-------------------------------------------------
void am9513_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
assert(id >= TIMER_F1 && id <= TIMER_F5);
int cycle = m_freq_timer_cycle[id - TIMER_F1] == 0 ? 2 : 1;
while (cycle-- > 0)
{
m_f ^= 1 << (id - TIMER_F1);
bool level = BIT(m_f, id - TIMER_F1);
int source = id - TIMER_F1 + 11;
for (int c = 0; c < 5; c++)
{
if ((m_counter_mode[c] & 0x0f00) >> 8 == source && BIT(m_counter_mode[c], 12) == !level)
count_edge(c);
}
// FOUT Source = Fn
if ((m_mmr & 0x00f0) >> 4 == source || ((m_mmr & 0x00f0) == 0 && id == TIMER_F1))
fout_tick();
}
}
//-------------------------------------------------
// select_freq_timer - keep track of which of the
// F1-F5 timers are actually in use
//-------------------------------------------------
void am9513_device::select_freq_timer(int f, int c, bool selected, bool cycle)
{
assert(f >= 0 && f < 5);
if (selected)
m_freq_timer_selected[f] |= 1 << c;
else
m_freq_timer_selected[f] &= ~(1 << c);
if (selected && cycle)
m_freq_timer_cycle[f] |= 1 << c;
else
m_freq_timer_cycle[f] &= ~(1 << c);
init_freq_timer(f);
}
//**************************************************************************
// MASTER MODE SELECTION
//**************************************************************************
//-------------------------------------------------
// set_master_mode - master mode control
//-------------------------------------------------
void am9513_device::set_master_mode(u16 data)
{
u16 old_mmr = m_mmr;
m_mmr = data;
// Time-of-Day options
if ((m_mmr & 0x0003) != (old_mmr & 0x0003))
{
switch (m_mmr & 0x0003)
{
case 0: LOGMASKED(LOG_MODE, "TOD Disabled\n"); break;
case 1: LOGMASKED(LOG_MODE, "TOD Enabled (/5 Input)\n"); break;
case 2: LOGMASKED(LOG_MODE, "TOD Enabled (/6 Input)\n"); break;
case 3: LOGMASKED(LOG_MODE, "TOD Enabled (/10 Input)\n"); break;
}
}
// Comparator enable
if (BIT(m_mmr, 2) != BIT(old_mmr, 2))
LOGMASKED(LOG_MODE, "Compare 1 %s\n", BIT(m_mmr, 2) ? "Enabled" : "Disabled");
if (BIT(m_mmr, 3) != BIT(old_mmr, 3))
LOGMASKED(LOG_MODE, "Compare 2 %s\n", BIT(m_mmr, 3) ? "Enabled" : "Disabled");
// FOUT Source/Divider selection
if ((m_mmr & 0x0ff0) != (old_mmr & 0x0ff0))
{
int source = (m_mmr >> 4) & 15;
int divider = (m_mmr >> 8) & 15;
if (source == 0)
source = 11;
if (divider == 0)
divider = 16;
int old_source = (old_mmr >> 4) & 15;
if (old_source == 0)
old_source = 11;
if (old_source >= 11 && old_source <= 15 && source != old_source)
select_freq_timer(old_source - 11, 0, false, false);
if (source >= 11 && source <= 15)
{
LOGMASKED(LOG_MODE, "FOUT = F%d / %d\n", source - 10, divider);
select_freq_timer(source - 11, 0, !m_fout_cb.isnull(), BIT(divider, 0));
}
else if (source >= 6 && source <= 10)
LOGMASKED(LOG_MODE, "FOUT = GATE %d / %d\n", source - 5, divider);
else
LOGMASKED(LOG_MODE, "FOUT = SRC %d / %d\n", source, divider);
}
// MM12: FOUT gate control
if (BIT(m_mmr, 12) != BIT(old_mmr, 12))
LOGMASKED(LOG_MODE, "FOUT: Gate %s\n", BIT(m_mmr, 12) ? "Off" : "On");
// MM13: Data bus width
if (BIT(m_mmr, 13) != BIT(old_mmr, 13))
LOGMASKED(LOG_MODE, "Data Bus Width = %d-Bit\n", BIT(m_mmr, 13) ? 16 : 8);
// MM14: Data pointer sequencing
if (BIT(m_mmr, 14) != BIT(old_mmr, 14))
LOGMASKED(LOG_MODE, "%s Data Pointer Increment\n", BIT(m_mmr, 14) ? "Disable" : "Enable");
// MM15: Scaler ratio control
if (BIT(m_mmr, 15) != BIT(old_mmr, 15))
{
LOGMASKED(LOG_MODE, "%s Frequency Division\n", BIT(m_mmr, 15) ? "BCD" : "Binary");
for (int f = 1; f < 5; f++)
init_freq_timer(f);
}
}
//**************************************************************************
// COUNTER CONTROL
//**************************************************************************
//-------------------------------------------------
// counter_is_mode_x - return true if the counter
// is configured for the special Mode X
//-------------------------------------------------
bool am9513_device::counter_is_mode_x(int c) const
{
// Am9513A only: CM7-CM5 = 1, CM15-CM13 = EDGE
return m_is_am9513a && (m_counter_mode[c] & 0xc0e0) == 0xc0e0;
}
//-------------------------------------------------
// compare_count - determine comparator output
// for Counter 1 or Counter 2
//-------------------------------------------------
bool am9513_device::compare_count(int c) const
{
assert(c == 0 || c == 1);
// TOD special case: Comparator 2 does 32-bit comparison when Comparator 1 is also activated
if (c == 1 && BIT(m_mmr, 2) && (m_mmr & 0x0003) != 0)
return m_count[0] == m_alarm[0] && m_count[1] == m_alarm[1];
else
return m_count[c] == m_alarm[c];
}
//-------------------------------------------------
// set_counter_mode - handle counter mode changes
//-------------------------------------------------
void am9513_device::set_counter_mode(int c, u16 data)
{
if ((data & 0xe0e0) != (m_counter_mode[c] & 0xe0e0))
{
// CM15-CM13, CM7-CM5: Mode selection and gating control
int mode = ((data >> 5) & 7) * 3;
switch (data & 0xe000)
{
case 0x0000:
mode += 'A';
LOGMASKED(LOG_MODE, "Counter %d: Mode %c selected (no gating)\n", c + 1, mode);
break;
case 0x2000:
mode += 'B';
LOGMASKED(LOG_MODE, "Counter %d: Mode %c selected (active high TC%d)\n", c + 1, mode, c);
break;
case 0x4000:
case 0x6000:
mode += 'B';
LOGMASKED(LOG_MODE, "Counter %d: Mode %c selected (active high GATE %d)\n", c + 1, mode, BIT(data, 13) ? c + 2 : c);
break;
case 0x8000:
case 0xa000:
mode += 'B';
LOGMASKED(LOG_MODE, "Counter %d: Mode %c selected (active %s GATE %d)\n", c + 1, mode, BIT(data, 13) ? "low" : "high", c + 1);
break;
case 0xc000:
case 0xe000:
mode += 'C';
LOGMASKED(LOG_MODE, "Counter %d: Mode %c selected (%s edge GATE %d)\n", c + 1, mode, BIT(data, 13) ? "falling" : "rising", c + 1);
break;
}
}
if ((data & 0x1f00) != (m_counter_mode[c] & 0x1f00))
{
// CM11-CM8: Source selection
// CM12: Source edge control
int source = (data >> 8) & 15;
int old_source = (m_counter_mode[c] >> 8) & 15;
if (old_source >= 11 && old_source <= 15 && source != old_source)
select_freq_timer(old_source - 11, c + 1, false, false);
if (source >= 11 && source <= 15)
{
select_freq_timer(source - 11, c + 1, true, BIT(data, 12));
LOGMASKED(LOG_MODE, "Counter %d: Count on %s edge of F%d\n", c + 1, BIT(data, 12) ? "falling" : "rising", source - 10);
}
else if (source >= 6 && source <= 10)
LOGMASKED(LOG_MODE, "Counter %d: Count on %s edge of GATE %d\n", c + 1, BIT(data, 12) ? "falling" : "rising", source - 5);
else if (source == 0)
LOGMASKED(LOG_MODE, "Counter %d: Count on %s edge of TC%d\n", c + 1, BIT(data, 12) ? "falling" : "rising", c - 1);
else
LOGMASKED(LOG_MODE, "Counter %d: Count on %s edge of SRC %d\n", c + 1, BIT(data, 12) ? "falling" : "rising", source);
}
if ((data & 0x0018) != (m_counter_mode[c] & 0x0018))
LOGMASKED(LOG_MODE, "Counter %d: %s %s count\n", c + 1, BIT(data, 4) ? "BCD" : "Binary", BIT(data, 3) ? "up" : "down");
if ((data & 0x0007) != (m_counter_mode[c] & 0x0007))
{
// CM2-CM0: Output form
switch (data & 0x0007)
{
case 0x0000:
LOGMASKED(LOG_MODE, "Counter %d: Output low (inactive)\n", c + 1);
set_output(c, false);
break;
case 0x0004:
LOGMASKED(LOG_MODE, "Counter %d: Output high impedance (inactive)\n", c + 1);
break;
case 0x0001:
if (c < 2 && BIT(m_mmr, c + 2))
set_output(c, compare_count(c));
else
{
LOGMASKED(LOG_MODE, "Counter %d: Output active high TC pulse\n", c + 1);
set_output(c, m_tc[c]);
}
break;
case 0x0005:
if (c < 2 && BIT(m_mmr, c + 2))
set_output(c, !compare_count(c));
else
{
LOGMASKED(LOG_MODE, "Counter %d: Output active low TC pulse\n", c + 1);
set_output(c, !m_tc[c]);
}
break;
case 0x0002:
case 0x0003: // SCP-300F sets this up; why?
if (c < 2 && BIT(m_mmr, c + 2))
set_output(c, compare_count(c));
else
{
LOGMASKED(LOG_MODE, "Counter %d: Output toggle on TC\n", c + 1);
set_output(c, m_toggle[c]);
}
break;
default:
LOGMASKED(LOG_MODE, "Counter %d: Output mode %d (illegal)\n", c + 1, data & 0x0007);
break;
}
}
m_counter_mode[c] = data;
}
//-------------------------------------------------
// arm_counter - arm a particular counter
//-------------------------------------------------
void am9513_device::arm_counter(int c)
{
if (!m_counter_armed[c])
{
LOGMASKED(LOG_GENERAL, "Counter %d: Arming counter\n", c + 1);
m_counter_armed[c] = true;
// Count starts upon first active gate edge after arming in Modes C, F, I, L, O, R, X
m_counter_running[c] = (m_counter_mode[c] & 0xc000) != 0xc000;
}
m_alternate_count[c] = false;
}
//-------------------------------------------------
// disarm_counter - disarm a particular counter
//-------------------------------------------------
void am9513_device::disarm_counter(int c)
{
if (m_counter_armed[c])
{
LOGMASKED(LOG_GENERAL, "Counter %d: Disarming counter\n", c + 1);
m_counter_armed[c] = false;
m_counter_running[c] = false;
}
}
//-------------------------------------------------
// save_counter - capture the current count
//-------------------------------------------------
void am9513_device::save_counter(int c)
{
m_counter_hold[c] = m_count[c];
LOGMASKED(LOG_GENERAL, "Counter %d: Count %u saved\n", c + 1, m_count[c]);
}
//**************************************************************************
// OUTPUT CONTROL
//**************************************************************************
//-------------------------------------------------
// set_output - set one of the 5 main outputs
//-------------------------------------------------
void am9513_device::set_output(int c, bool state)
{
// SR1-SR5 track the output state
if (BIT(m_status, c + 1) == state)
return;
if (state)
m_status |= 1 << (c + 1);
else
m_status &= ~(1 << (c + 1));
m_out_cb[c](state);
}
//-------------------------------------------------
// set_toggle - output level control for TC
// toggle mode
//-------------------------------------------------
void am9513_device::set_toggle(int c, bool state)
{
m_toggle[c] = state;
if ((m_counter_mode[c] & 0x0006) == 0x0002)
set_output(c, state);
}
//-------------------------------------------------
// set_tc - register terminal count status
//-------------------------------------------------
void am9513_device::set_tc(int c, bool state)
{
m_tc[c] = state;
// TC output is disabled when comparator is enabled
if (c >= 2 || !BIT(m_mmr, 2 + c))
{
switch (m_counter_mode[c] & 0x0007)
{
case 0x0001:
// Active high TC pulse
set_output(c, state);
break;
case 0x0002:
case 0x0003: // SCP-300F sets this up; why?
// TC toggled output
if (!state)
set_toggle(c, !m_toggle[c]);
break;
case 0x0005:
// Active low TC pulse
set_output(c, !state);
break;
}
}
// TCn-1 = TC5 for Counter 1
int d = (c + 1) % 5;
// TC cascading
if ((m_counter_mode[d] & 0x1f00) == (state ? 0x0000 : 0x1000))
count_edge(d);
// TC gating
if ((m_counter_mode[d] & 0xe000) == 0x2000)
gate_count(d, state && (bus_is_16_bit() || m_gate_alt[d]));
}
//**************************************************************************
// SOURCE INPUTS
//**************************************************************************
//-------------------------------------------------
// write_source - register state changes on SRC
// input lines
//-------------------------------------------------
void am9513_device::write_source(int s, bool level)
{
if (level == m_src[s])
return;
m_src[s] = level;
LOGMASKED(LOG_INPUT, "Source %d: %s edge\n", s + 1, level ? "Rising" : "Falling");
for (int c = 0; c < 5; c++)
{
if ((m_counter_mode[c] & 0x0f00) >> 8 == (s + 1) && BIT(m_counter_mode[c], 12) == !level)
count_edge(c);
}
// FOUT Source = SRC n
if ((m_mmr & 0x00f0) >> 4 == (s + 1))
fout_tick();
}
//**************************************************************************
// COUNTER OPERATION
//**************************************************************************
//-------------------------------------------------
// count_edge - gate and count active edges
//-------------------------------------------------
void am9513_device::count_edge(int c)
{
// Counting cannot be disabled or gated during TC
if (!m_tc[c])
{
if (!m_counter_running[c])
return;
if (!m_gate_active[c])
{
// Modes B, E, H, K, N & Q: Count only during active gate level
int gating = (m_counter_mode[c] >> 13) & 7;
if (gating >= 1 && gating <= 5)
return;
}
}
step_counter(c, false);
}
//-------------------------------------------------
// reload_from_hold - return true if the current
// reload source of the counter is the hold
// register rather than the load register
//-------------------------------------------------
bool am9513_device::reload_from_hold(int c) const
{
if (counter_is_mode_x(c))
return false;
// Modes S & V: Reload from hold register when gate is high
else if ((m_counter_mode[c] & 0x00c0) == 0x00c0)
return m_gate_active[c];
// Modes G, H, I, J, K & L: Alternating reload
else if ((m_counter_mode[c] & 0x00c0) == 0x0040)
return m_alternate_count[c];
else
return false;
}
//-------------------------------------------------
// step_counter - advance the counter by one step
// and/or reload it from the specified register
// N.B. The "load counter" command works by
// causing an internal step and so can affect TC
//-------------------------------------------------
void am9513_device::step_counter(int c, bool force_load)
{
if (BIT(m_counter_mode[c], 3))
{
// CM3 = 1: Count up
++m_count[c];
// CM4 = 1: BCD adjustment
if (BIT(m_counter_mode[c], 4))
{
if ((m_count[c] & 0x000f) >= 0x000a)
m_count[c] += 0x0006;
if (c == 0 && (m_mmr & 0x0003) == 0x0001)
{
// TOD: 50Hz
if ((m_count[c] & 0x00f0) >= 0x0050)
m_count[c] += 0x00b0;
}
else if (c == 0 && (m_mmr & 0x0003) == 0x0002)
{
// TOD: 60Hz
if ((m_count[c] & 0x00f0) >= 0x0060)
m_count[c] += 0x00a0;
}
else if (c == 1 && (m_mmr & 0x0003) != 0)
{
// TOD: minutes
if ((m_count[c] & 0x00f0) >= 0x0060)
m_count[c] += 0x00a0;
}
else
{
if ((m_count[c] & 0x00f0) >= 0x00a0)
m_count[c] += 0x0060;
}
if ((m_count[c] & 0x0f00) >= 0x0a00)
m_count[c] += 0x0600;
if (c == 0 && (m_mmr & 0x0003) != 0)
{
// TOD: seconds
if ((m_count[c] & 0xf000) >= 0x6000)
m_count[c] += 0xa000;
}
else if (c == 1 && (m_mmr & 0x0003) != 0)
{
// TOD: hours
if ((m_count[c] & 0xff00) >= 0x2400)
m_count[c] += 0xdc00;
}
else
{
if ((m_count[c] & 0xf000) >= 0xa000)
m_count[c] += 0x6000;
}
}
}
else
{
// CM3 = 0: Count down
--m_count[c];
// CM4 = 1: BCD adjustment
if (BIT(m_counter_mode[c], 4))
{
if ((m_count[c] & 0x000f) >= 0x000a)
m_count[c] -= 0x0006;
if (c == 0 && (m_mmr & 0x0003) == 0x0001)
{
// TOD: 50Hz
if ((m_count[c] & 0x00f0) >= 0x0050)
m_count[c] -= 0x00b0;
}
else if (c == 0 && (m_mmr & 0x0003) == 0x0002)
{
// TOD: 60Hz
if ((m_count[c] & 0x00f0) >= 0x0060)
m_count[c] -= 0x00a0;
}
else if (c == 1 && (m_mmr & 0x0003) != 0)
{
// TOD: minutes
if ((m_count[c] & 0x00f0) >= 0x0060)
m_count[c] -= 0x00a0;
}
else
{
if ((m_count[c] & 0x00f0) >= 0x00a0)
m_count[c] -= 0x0060;
}
if ((m_count[c] & 0x0f00) >= 0x0a00)
m_count[c] -= 0x0600;
if (c == 0 && (m_mmr & 0x0003) != 0)
{
// TOD: seconds
if ((m_count[c] & 0xf000) >= 0x6000)
m_count[c] -= 0xa000;
}
else if (c == 1 && (m_mmr & 0x0003) != 0)
{
// TOD: hours
if ((m_count[c] & 0xff00) >= 0x2400)
m_count[c] -= 0xdc00;
}
else
{
if ((m_count[c] & 0xf000) >= 0xa000)
m_count[c] -= 0x6000;
}
}
}
if (m_count[c] == 0)
{
set_tc(c, true);
m_count[c] = reload_from_hold(c) ? m_counter_hold[c] : m_counter_load[c];
m_alternate_count[c] = !m_alternate_count[c];
LOGMASKED(LOG_TC, "Counter %d: Terminal count (%u reloaded)\n", c + 1, m_count[c]);
// Modes A, B, C, N & O: disarm counter after counting to TC once
if ((m_counter_mode[c] & 0x0060) == 0x0000)
disarm_counter(c);
// Modes G, H, I & S: disarm counter after counting to TC twice
if ((m_counter_mode[c] & 0x0060) == 0x0040 && !m_alternate_count[c])
disarm_counter(c);
// Modes C, F, O, R & X: retriggering required after first TC
// Modes I & L: retriggering required after second TC
if ((m_counter_mode[c] & 0xc000) == 0xc000 && ((m_counter_mode[c] & 0x00c0) != 0x0040 || !m_alternate_count[c]))
m_counter_running[c] = false;
}
else
{
// Drive counter out of TC
if (m_tc[c])
set_tc(c, false);
// Load if requested
if (force_load)
{
m_count[c] = reload_from_hold(c) ? m_counter_hold[c] : m_counter_load[c];
LOGMASKED(LOG_GENERAL, "Counter %d: %u loaded\n", c + 1, m_count[c]);
}
}
// Active comparators
if (c < 2 && BIT(m_mmr, c + 2))
{
switch (m_counter_mode[c] & 0x0007)
{
case 0x0001:
case 0x0002:
// Active high comparator output
set_output(c, compare_count(c));
break;
case 0x0005:
// Active low comparator output
set_output(c, !compare_count(c));
break;
}
}
}
//**************************************************************************
// HARDWARE GATING
//**************************************************************************
//-------------------------------------------------
// gate_count - track active gate state for each
// counter
//-------------------------------------------------
void am9513_device::gate_count(int c, bool state)
{
// Active low gating when CM15 = CM13 = 1
if ((m_counter_mode[c] & 0xa000) == 0xa000)
state = !state;
if (m_gate_active[c] == state)
return;
m_gate_active[c] = state;
LOGMASKED(LOG_INPUT, "Counter %d: Gate %sactive\n", c + 1, state ? "" : "in");
// Active gate edge handling for armed counters
if (state && m_counter_armed[c])
{
// Mode X: transfer counter into Hold register but continue counting
if (counter_is_mode_x(c))
m_counter_hold[c] = m_count[c];
// Modes N, O, Q & R: hardware retriggering
if ((m_counter_mode[c] & 0x00c0) == 0x0080)
{
m_counter_hold[c] = m_count[c];
m_count[c] = m_counter_load[c];
}
// Modes C, F, I, L, O, R & X: start count on active gate edge
if ((m_counter_mode[c] & 0xc000) == 0xc000)
m_counter_running[c] = true;
}
}
//-------------------------------------------------
// write_gate - register state changes on the
// GATE n inputs
//-------------------------------------------------
void am9513_device::write_gate(int g, bool level)
{
if (level == m_gate[g])
return;
m_gate[g] = level;
// Check for selection of GATE n as source for any counter
for (int c = 0; c < 5; c++)
{
if ((m_counter_mode[c] & 0x0f00) >> 8 == (g + 6) && BIT(m_counter_mode[c], 12) == !level)
count_edge(c);
}
// Check for selection of GATE n as control for same-numbered counter
if (BIT(m_counter_mode[g], 15))
gate_count(g, level && (bus_is_16_bit() || m_gate_alt[g]));
// Check for selection of GATE n as control for previous counter
if ((m_counter_mode[(g + 4) % 5] & 0xe000) == 0x4000)
gate_count((g + 4) % 5, level && (bus_is_16_bit() || m_gate_alt[(g + 4) % 5]));
// Check for selection of GATE n as control for next counter
if ((m_counter_mode[(g + 1) % 5] & 0xe000) == 0x6000)
gate_count((g + 1) % 5, level && (bus_is_16_bit() || m_gate_alt[(g + 1) % 5]));
// Check for selection of GATE n as source for FOUT
if ((m_mmr & 0x00f0) >> 4 == (g + 6))
fout_tick();
}
//-------------------------------------------------
// write_gate_alt - register state on GATE nA
// inputs (only available in 8-bit mode)
//-------------------------------------------------
void am9513_device::write_gate_alt(int c, bool level)
{
if (bus_is_16_bit())
{
logerror("Gate %dA written when configured as DB%d\n", c + 1, c + 8);
return;
}
m_gate_alt[c] = level;
switch (m_counter_mode[c] & 0xe000)
{
case 0x2000:
gate_count(c, level && m_tc[(c + 4) % 5]);
break;
case 0x4000:
gate_count((c + 1) % 5, level && m_gate[(c + 1) % 5]);
break;
case 0x6000:
gate_count((c + 4) % 5, level && m_gate[(c + 4) % 5]);
break;
case 0x8000:
case 0xa000:
case 0xc000:
case 0xe000:
gate_count(c, level && m_gate[c]);
break;
}
}
//**************************************************************************
// DATA PORT MUX
//**************************************************************************
//-------------------------------------------------
// describe_register - diagnostic helper
//-------------------------------------------------
std::string am9513_device::describe_register() const
{
switch (m_dpr)
{
case 0x17:
return std::string("Master Mode");
case 0x1f:
return std::string("Status");
case 0x07:
case 0x0f:
return string_format("Counter %d Alarm", BIT(m_dpr, 3) ? 2 : 1);
case 0x01:
case 0x02:
case 0x03:
case 0x04:
case 0x05:
return string_format("Counter %d Mode", m_dpr & 7);
case 0x09:
case 0x0a:
case 0x0b:
case 0x0c:
case 0x0d:
return string_format("Counter %d Load", m_dpr & 7);
case 0x11: case 0x19:
case 0x12: case 0x1a:
case 0x13: case 0x1b:
case 0x14: case 0x1c:
case 0x15: case 0x1d:
return string_format("Counter %d Hold", m_dpr & 7);
default:
return string_format("Illegal %02X", m_dpr);
}
}
//-------------------------------------------------
// internal_read - read from register selected by
// the data pointer
//-------------------------------------------------
u16 am9513_device::internal_read() const
{
switch (m_dpr)
{
case 0x17: // Master mode register
return m_mmr;
case 0x1f: // Status register
return m_status | 0xff00;
case 0x07: // Alarm 1 register
case 0x0f: // Alarm 2 register
return m_alarm[BIT(m_dpr, 3)];
case 0x01: // Counter 1 mode register
case 0x02: // Counter 2 mode register
case 0x03: // Counter 3 mode register
case 0x04: // Counter 4 mode register
case 0x05: // Counter 5 mode register
return m_counter_mode[(m_dpr & 7) - 1];
case 0x09: // Counter 1 load register
case 0x0a: // Counter 2 load register
case 0x0b: // Counter 3 load register
case 0x0c: // Counter 4 load register
case 0x0d: // Counter 5 load register
return m_counter_load[(m_dpr & 7) - 1];
case 0x11: case 0x19: // Counter 1 hold register
case 0x12: case 0x1a: // Counter 2 hold register
case 0x13: case 0x1b: // Counter 3 hold register
case 0x14: case 0x1c: // Counter 4 hold register
case 0x15: case 0x1d: // Counter 5 hold register
return m_counter_hold[(m_dpr & 7) - 1];
default: // Invalid register
return 0xffff;
}
}
//-------------------------------------------------
// internal_read - write to register selected by
// the data pointer
//-------------------------------------------------
void am9513_device::internal_write(u16 data)
{
switch (m_dpr)
{
case 0x17: // Master mode register
set_master_mode(data);
break;
case 0x1f: // Status register (read only?)
logerror("Writing %04X to status register\n", data);
break;
case 0x07: // Alarm 1 register
case 0x0f: // Alarm 2 register
if (m_alarm[BIT(m_dpr, 3)] != data)
LOGMASKED(LOG_GENERAL, "Counter %d: Alarm = %u\n", BIT(m_dpr, 3) ? 2 : 1, data);
m_alarm[BIT(m_dpr, 3)] = data;
break;
case 0x01: // Counter 1 mode register
case 0x02: // Counter 2 mode register
case 0x03: // Counter 3 mode register
case 0x04: // Counter 4 mode register
case 0x05: // Counter 5 mode register
if (m_counter_mode[(m_dpr & 7) - 1] != data)
LOGMASKED(LOG_GENERAL, "Counter %d: Mode = %04X\n", m_dpr & 7, data);
set_counter_mode((m_dpr & 7) - 1, data);
break;
case 0x09: // Counter 1 load register
case 0x0a: // Counter 2 load register
case 0x0b: // Counter 3 load register
case 0x0c: // Counter 4 load register
case 0x0d: // Counter 5 load register
if (m_counter_load[(m_dpr & 7) - 1] != data)
LOGMASKED(LOG_GENERAL, "Counter %d: Load = %u\n", m_dpr & 7, data);
m_counter_load[(m_dpr & 7) - 1] = data;
break;
case 0x11: case 0x19: // Counter 1 hold register
case 0x12: case 0x1a: // Counter 2 hold register
case 0x13: case 0x1b: // Counter 3 hold register
case 0x14: case 0x1c: // Counter 4 hold register
case 0x15: case 0x1d: // Counter 5 hold register
if (m_counter_hold[(m_dpr & 7) - 1] != data)
LOGMASKED(LOG_GENERAL, "Counter %d: Hold = %u\n", m_dpr & 7, data);
m_counter_hold[(m_dpr & 7) - 1] = data;
break;
default: // Invalid register
logerror("Writing %04X to register %02X\n", data, m_dpr);
break;
}
}
//**************************************************************************
// PREFETCH LATCH
//**************************************************************************
//-------------------------------------------------
// advance_dpr - advance the data pointer through
// one of four cycles after a data read or write
//-------------------------------------------------
void am9513_device::advance_dpr()
{
if (machine().side_effects_disabled())
return;
if (bus_is_16_bit() || !BIT(m_status, 0))
{
// Least significant byte (or 16-bit word) transferred next
m_status |= 0x01;
if (BIT(m_mmr, 14))
return;
}
else
{
// Most significant byte transferred next
m_status &= 0xfe;
return;
}
// Cycle within group
switch (m_dpr & 0x18)
{
case 0x00: // Mode/Alarm 1 -> Load/Alarm 2
case 0x08: // Load/Alarm 2 -> Hold/Master Mode
m_dpr += 0x08;
return;
case 0x10: // Hold/Master Mode -> Mode (next counter)/Alarm 1
m_dpr -= 0x10;
break;
case 0x18:
break;
}
// Cycle through groups
switch (m_dpr & 0x07)
{
case 0x05: // Counter 5 -> Counter 1
m_dpr -= 0x04;
break;
case 0x07: // In control group/status cycle
break;
default:
m_dpr += 0x01;
break;
}
}
//**************************************************************************
// CONTROL PORT
//**************************************************************************
//-------------------------------------------------
// command_write - decode writes to control
// register
//-------------------------------------------------
void am9513_device::command_write(u8 data)
{
switch (data & 0xe0)
{
case 0x00:
if ((data & 0x07) == 0x00 || (data & 0x07) == 0x06)
{
logerror("Invalid register selected: %02X\n", data);
break;
}
// Load data pointer
m_dpr = data;
// Automatic prefetch
m_status |= 0x01;
break;
case 0x20: // Arm
case 0x40: // Load
case 0x60: // Load and Arm
for (int c = 0; c < 5; c++)
{
if (BIT(data, c))
{
if (BIT(data, 6))
step_counter(c, true);
if (BIT(data, 5))
arm_counter(c);
}
}
break;
case 0x80: // Disarm and Save
case 0xa0: // Save
case 0xc0: // Disarm
for (int c = 0; c < 5; c++)
{
if (BIT(data, c))
{
if (!BIT(data, 5))
disarm_counter(c);
if (!BIT(data, 6))
save_counter(c);
}
}
break;
default:
switch (data)
{
case 0xe0: case 0xe8: // Clear/set MM14 (Enable/Disable data pointer sequencing)
LOGMASKED(LOG_MODE, "%s Data Pointer Increment\n", BIT(data, 3) ? "Disable" : "Enable");
m_mmr = ((m_mmr & ~(1 << 14)) | BIT(data, 3) << 14);
break;
case 0xe1: case 0xe9: // Clear/set toggle out for counter 1
case 0xe2: case 0xea: // Clear/set toggle out for counter 2
case 0xe3: case 0xeb: // Clear/set toggle out for counter 3
case 0xe4: case 0xec: // Clear/set toggle out for counter 4
case 0xe5: case 0xed: // Clear/set toggle out for counter 5
set_toggle((data & 7) - 1, BIT(data, 3));
break;
case 0xe6: case 0xee: // Clear/set MM12 (FOUT gate on/FOUT gate off)
LOGMASKED(LOG_MODE, "FOUT: Gate %s\n", BIT(data, 3) ? "Off" : "On");
m_mmr = ((m_mmr & ~(1 << 12)) | BIT(data, 3) << 12);
break;
case 0xe7: case 0xef: // Clear/set MM13 (8-bit bus/16-bit bus)
m_mmr = ((m_mmr & ~(1 << 13)) | BIT(data, 3) << 13);
break;
case 0xf1: // Step counter 1
case 0xf2: // Step counter 2
case 0xf3: // Step counter 3
case 0xf4: // Step counter 4
case 0xf5: // Step counter 5
step_counter((data & 7) - 1, false);
break;
case 0xff: // Master reset
master_reset();
break;
case 0xf8: case 0xf9: // Enable/disable prefetch for write (Am9513A only)
if (m_is_am9513a)
{
LOGMASKED(LOG_MODE, "Prefetch %s for write operations\n", BIT(data, 0) ? "disabled" : "enabled");
m_write_prefetch = !BIT(data, 0);
break;
}
// else fall through
default:
logerror("Invalid command: %02X\n", data);
break;
}
break;
}
}
//-------------------------------------------------
// status_read - return the status register
//-------------------------------------------------
u8 am9513_device::status_read() const
{
return m_status;
}
//**************************************************************************
// DATA BUS MUX
//**************************************************************************
//-------------------------------------------------
// bus_is_16_bit - determine whether or not the
// device is configured for a 16-bit bus
//-------------------------------------------------
bool am9513_device::bus_is_16_bit() const
{
return BIT(m_mmr, 13);
}
//-------------------------------------------------
// data_read - generic data read access handler
//-------------------------------------------------
u16 am9513_device::data_read()
{
u16 result = internal_read();
if (!bus_is_16_bit() && !BIT(m_status, 0))
result = (result >> 8) | 0xff00;
// update the data pointer
advance_dpr();
return result;
}
//-------------------------------------------------
// data_write - generic data write access handler
//-------------------------------------------------
void am9513_device::data_write(u16 data)
{
if (!bus_is_16_bit())
{
data &= 0x00ff;
if (BIT(m_status, 0))
data |= internal_read() & 0xff00;
else
data = (data << 8) | (internal_read() & 0x00ff);
}
internal_write(data);
// update the data pointer
if (m_write_prefetch)
advance_dpr();
}
//-------------------------------------------------
// read8 - 8-bit read access
//-------------------------------------------------
READ8_MEMBER(am9513_device::read8)
{
if (BIT(offset, 0))
return status_read();
else
return data_read() & 0x00ff;
}
//-------------------------------------------------
// write8 - 8-bit write access
//-------------------------------------------------
WRITE8_MEMBER(am9513_device::write8)
{
if (BIT(offset, 0))
{
if (data == 0xef)
logerror("16-bit data bus selected with 8-bit write\n");
command_write(data);
}
else
data_write(data | 0xff00);
}
//-------------------------------------------------
// read16 - 16-bit read access
//-------------------------------------------------
READ16_MEMBER(am9513_device::read16)
{
if (BIT(offset, 0))
return status_read() | 0xff00;
else
{
if (!bus_is_16_bit())
logerror("16-bit data read in 8-bit bus mode\n");
return data_read();
}
}
//-------------------------------------------------
// write16 - 16-bit write access
//-------------------------------------------------
WRITE16_MEMBER(am9513_device::write16)
{
if ((!bus_is_16_bit() || BIT(offset, 0)) && (data & 0xff00) != 0xff00)
logerror("Errant write of %02X to upper byte of %s register in %d-bit bus mode\n",
(data & 0xff00) >> 8,
BIT(offset, 0) ? "control" : "data",
bus_is_16_bit() ? 16 : 8);
if (BIT(offset, 0))
{
if ((data & 0x00ff) == 0x00e7)
logerror("8-bit data bus selected with 16-bit write\n");
else if ((data & 0x00ff) == 0x00ef && !bus_is_16_bit())
logerror("16-bit data bus selected\n");
command_write(data & 0x00ff);
}
else
data_write(data);
}
//**************************************************************************
// DIVIDED FREQUENCY OUTPUT
//**************************************************************************
void am9513_device::fout_tick()
{
--m_fout_counter;
if (m_fout_counter > 0)
return;
// Toggle the output
m_fout = !m_fout;
// Check whether the FOUT gate is on
if (!BIT(m_mmr, 12) && !m_fout_cb.isnull())
m_fout_cb(m_fout);
// Reload the counter
m_fout_counter = (m_mmr >> 8) & 15;
if (m_fout_counter == 0)
m_fout_counter = 16;
}