mc68340: Timer improvements

- Fix prescaler calculation
- Handle various reset behaviors
- TOUT changes recorded in status register
- Better handling of COM flag and compare register being zero
- Reload not immediately after reaching zero but on next cycle
This commit is contained in:
AJR 2019-05-28 19:56:04 -04:00
parent 78d3026f86
commit e72d1130fe
2 changed files with 129 additions and 90 deletions

View File

@ -19,6 +19,7 @@
#define LOG_READ (1U << 2)
#define LOG_TIMER (1U << 3)
#define LOG_INT (1U << 4)
#define LOG_COUNT (1U << 5)
//#define VERBOSE (LOG_SETUP|LOG_INT|LOG_TIMER)
@ -29,6 +30,7 @@
#define LOGR(...) LOGMASKED(LOG_READ, __VA_ARGS__)
#define LOGTIMER(...) LOGMASKED(LOG_TIMER, __VA_ARGS__)
#define LOGINT(...) LOGMASKED(LOG_INT, __VA_ARGS__)
#define LOGCOUNT(...) LOGMASKED(LOG_COUNT, __VA_ARGS__)
#ifdef _MSC_VER
#define FUNCNAME __func__
@ -144,8 +146,7 @@ WRITE16_MEMBER( mc68340_timer_module_device::write )
LOGTIMER("- PCLK: Counter uses %s\n", (data & REG_CR_PCLK) ? "prescaler" : "clock");
LOGTIMER("- CPE: Counter is %s\n", (data & REG_CR_CPE) ? "enabled" : "disabled");
LOGTIMER("- CLK: Clock is %s\n", (data & REG_CR_CLK) ? "TIN (external)" : "system clock / 2");
LOGTIMER("- Prescaler: Divide by %d\n", (data & REG_CR_POT_MASK) ? ( 1 << ((data & REG_CR_POT_MASK) >> 5)) : 256);
LOGTIMER("- Prescaler: Divide by %d\n", (0x101 << ((data & REG_CR_POT_MASK) >> 5) & 0x1fe));
LOGTIMER("- Prescaler: Divide by %d\n", (0x101 << ((data & REG_CR_POT_MASK) >> 5) & 0x1ff) * 2);
LOGTIMER("- MODE: %s\n", std::array<char const *, 8>
{{ "Input Capture/Output Compare",
"Square-Wave Generator - not implemented",
@ -162,40 +163,51 @@ WRITE16_MEMBER( mc68340_timer_module_device::write )
/* The timer is enabled when the counter prescaler enable (CPE) and SWRx bits in the CR
are set. Once enabled, the counter enable (ON) bit in the SR is set, and the next falling
edge of the counter clock causes the counter to be loaded with the value in the preload 1
register (PREL1). TODO: make sure of the intial load of PREL1 on first falling flank */
if (m_cr & REG_CR_SWR)
register (PREL1). */
if ((m_cr & (REG_CR_SWR | REG_CR_CPE)) == (REG_CR_SWR | REG_CR_CPE))
{
m_sr &= ~REG_SR_COM;
if (m_cr & REG_CR_CPE)
if ((m_sr & REG_SR_ON) == 0)
{
m_sr |= REG_SR_ON; // Starts the counter
LOGTIMER("Starts counter %d\n", m_cpu->get_timer_index(this));
if ((m_cr & REG_CR_CLK) == 0)
{
LOGTIMER("- Using system clock/2\n");
m_timer->adjust(m_cpu->cycles_to_attotime( (m_cpu->clock() / 2) / (0x101 << ((m_cr & REG_CR_POT_MASK) >> 5) & 0x1fe) * 2) );
attotime period = m_cpu->cycles_to_attotime((0x101 << ((m_cr & REG_CR_POT_MASK) >> 5) & 0x1ff) * 2);
LOGTIMER("- Using system clock/2: %f Hz\n", period.as_hz());
m_timer->adjust(period / 2);
}
else
{
LOGTIMER("- Using TIN%d\n", m_cpu->get_timer_index(this));
}
}
}
else
{
if ((m_sr & REG_SR_ON) != 0)
{
m_sr &= ~REG_SR_ON; // Stops the counter
LOGTIMER("Stops counter %d\n", m_cpu->get_timer_index(this));
m_timer->adjust(attotime::never);
}
}
else
{ // TODO: Detect Disable mode setting line to three state
if ((m_cr & REG_CR_SWR) == 0)
{
m_sr = (m_sr & ~REG_SR_COM) | 0x00ff;
m_cntr = m_cntr_reg = 0x0000;
if ((m_cr & REG_CR_OC_MASK) == REG_CR_OC_ONE)
{
m_tout_out_cb(ASSERT_LINE);
tout_set();
}
else if ((m_cr & REG_CR_OC_MASK) != REG_CR_OC_DISABLED)
{
tout_clear();
}
else
{
m_tout_out_cb(CLEAR_LINE);
// TODO: Disable mode setting line to three state
}
}
}
break;
@ -289,8 +301,8 @@ TIMER_CALLBACK_MEMBER(mc68340_timer_module_device::timer_callback)
do_timer_tick();
if ((m_sr & REG_SR_ON) != 0)
{
LOGTIMER("Re-arming timer %d using system clock/2 as base: %d Hz\n", m_cpu->get_timer_index(this) + 1, (m_cpu->clock() / 2) / (0x101 << ((m_cr & REG_CR_POT_MASK) >> 5) & 0x1fe));
m_timer->adjust(m_cpu->cycles_to_attotime( (m_cpu->clock() / 2) / (0x101 << ((m_cr & REG_CR_POT_MASK) >> 5) & 0x1fe) * 2));
//LOGTIMER("Re-arming timer %d using system clock/2 as base\n", m_cpu->get_timer_index(this) + 1);
m_timer->adjust(m_cpu->cycles_to_attotime((0x101 << ((m_cr & REG_CR_POT_MASK) >> 5) & 0x1ff) * 2) / 2);
}
}
@ -310,12 +322,20 @@ void mc68340_timer_module_device::device_start()
void mc68340_timer_module_device::device_reset()
{
m_mcr = REG_MCR_SUPV;
m_ir = 0x000f;
module_reset();
}
void mc68340_timer_module_device::module_reset()
{
// TODO
m_cr = 0x0000;
m_sr = (m_sr & REG_SR_TG) | 0x00ff;
m_cntr = m_cntr_reg = 0x0000;
m_prel1 = m_prel2 = 0xffff;
m_com = 0x0000;
m_timer->adjust(attotime::never);
m_cpu->update_ipl();
}
void mc68340_timer_module_device::do_timer_irq()
@ -341,10 +361,20 @@ void mc68340_timer_module_device::do_timer_tick()
if (!((m_cr & REG_CR_MODE_MASK) == REG_CR_MODE_ICOC &&
(m_sr & REG_SR_TG) != 0))
m_cntr_reg = m_cntr;
return;
}
else // Falling flank
// Counter decrements on falling flank
if (m_cntr == 0)
{
m_cntr = m_prel1; // TODO: Support prel2 for certain modes
LOGCOUNT("Counter reloaded with %04X\n", m_cntr);
}
else
{
m_cntr--;
LOGCOUNT("Counter decremented to %04X\n", m_cntr);
}
/* TC - Timer Compare Interrupt
1 = This bit is set when the counter transitions (off a clock/event falling edge) to the
@ -354,13 +384,17 @@ void mc68340_timer_module_device::do_timer_tick()
IMB, regardless of the mode of operation. This bit may also be cleared by writing
a one to it. Writing a zero to this bit does not alter its contents. This bit is not
affected by disabling the timer (SWR = 0).*/
if (m_cntr == m_com) // Check COM register
if (m_cntr == 0)
{
LOGINT("Timeout reached\n");
m_sr &= ~REG_SR_COM;
}
else if (m_cntr == m_com) // Check COM register
{
LOGINT("COM value reached\n");
m_sr |= REG_SR_COM;
}
}
LOGINT("%s reached\n", m_cntr_reg == 0 ? "Timeout" : "COM value");
/* OC1/OC0 - Output Control
These bits select the conditions under which TOUTx changes These
bits may have a different effect when in the input capture/output compare mode.*/
@ -380,16 +414,22 @@ void mc68340_timer_module_device::do_timer_tick()
when the counter reaches the value stored in the COM.)*/
if ((m_cr & REG_CR_MODE_MASK) == REG_CR_MODE_ICOC) // Detect Input Capture/Output Compare mode
{
if ((m_sr & REG_SR_COM) != 0) // timer reached compare value?
if (m_cntr == m_com) // timer reached compare value?
{
m_tout_out_cb((m_tout++ & 1) != 0 ? ASSERT_LINE : CLEAR_LINE);
if ((m_sr & REG_SR_OUT) != 0)
tout_clear();
else
tout_set();
}
}
else // Any oher mode
{
if (m_cntr_reg == 0) // Counter reached timeout?
if (m_cntr == 0) // Counter reached timeout?
{
m_tout_out_cb((m_tout++ & 1) != 0 ? ASSERT_LINE : CLEAR_LINE);
if ((m_sr & REG_SR_OUT) != 0)
tout_clear();
else
tout_set();
}
}
break;
@ -402,23 +442,13 @@ void mc68340_timer_module_device::do_timer_tick()
immediately set to zero if the timer is disabled (SWR = 0). If the timer is enabled (SWR
= 1), TOUTx will be set to zero at timeouts and set to one at timer compare events. If
the COM is $0000, TOUTx will be set to zero at the timeout/timer compare event.*/
if ((m_cr & REG_CR_MODE_MASK) == REG_CR_MODE_ICOC) // Detect Input Capture/Output Compare mode
if (m_cntr == 0) // timer reached timeout value?
{
if ((m_sr & REG_SR_COM) != 0) // timer reached compare value?
{
m_tout_out_cb(ASSERT_LINE);
tout_clear();
}
if (m_cntr_reg == 0) // timer reached timeout value?
else if ((m_cr & REG_CR_MODE_MASK) == REG_CR_MODE_ICOC && m_cntr == m_com) // timer reached compare value?
{
m_tout_out_cb(CLEAR_LINE);
}
}
else
{
if (m_cntr_reg == 0) // timer reached timeout value?
{
m_tout_out_cb(CLEAR_LINE);
}
tout_set();
}
break;
case REG_CR_OC_ONE:
@ -430,41 +460,20 @@ void mc68340_timer_module_device::do_timer_tick()
immediately set to one if the timer is disabled (SWR = 0). If the timer is enabled (SWR =
1), TOUTx will be set to one at timeouts and set to zero at timer compare events. If the
COM is $0000, TOUTx will be set to one at the timeout/timer compare event.*/
if ((m_cr & REG_CR_MODE_MASK) == REG_CR_MODE_ICOC) // Detect Input Capture/Output Compare mode
if (m_cntr == 0) // timer reached timeout value?
{
if ((m_sr & REG_SR_COM) != 0) // timer reached compare value?
{
m_tout_out_cb(CLEAR_LINE);
tout_set();
}
if (m_cntr_reg == 0) // timer reached timeout value?
else if ((m_cr & REG_CR_MODE_MASK) == REG_CR_MODE_ICOC && m_cntr == m_com) // timer reached compare value?
{
m_tout_out_cb(ASSERT_LINE);
}
}
else
{
if (m_cntr_reg == 0) // timer reached timeout value?
{
m_tout_out_cb(ASSERT_LINE);
}
tout_clear();
}
break;
default:
LOGTIMER("Wrong TOUT mode, fix the code!\n");
}
if (m_cntr_reg == 0) // timer reached timeout value?
{
m_sr &= ~REG_SR_COM;
m_cntr = m_prel1; // TODO: Support prel2 for certain modes
m_sr |= REG_SR_TO;
if (m_cr & REG_CR_IE2)
{
LOGTIMER(" - TO interrupt\n");
do_timer_irq();
}
}
if ((m_sr & REG_SR_COM) != 0) // timer reached compare value? )
if (m_cntr == m_com) // timer reached compare value? )
{
m_sr |= REG_SR_TC;
if (m_cr & REG_CR_IE0)
@ -473,6 +482,35 @@ void mc68340_timer_module_device::do_timer_tick()
do_timer_irq();
}
}
if (m_cntr == 0) // timer reached timeout value?
{
m_sr |= REG_SR_TO;
if (m_cr & REG_CR_IE2)
{
LOGTIMER(" - TO interrupt\n");
do_timer_irq();
}
}
}
}
void mc68340_timer_module_device::tout_set()
{
if ((m_sr & REG_SR_OUT) == 0)
{
m_sr |= REG_SR_OUT;
LOGTIMER(" - TOUT set\n");
m_tout_out_cb(1);
}
}
void mc68340_timer_module_device::tout_clear()
{
if ((m_sr & REG_SR_OUT) != 0)
{
m_sr &= ~REG_SR_OUT;
LOGTIMER(" - TOUT cleared\n");
m_tout_out_cb(0);
}
}

View File

@ -44,7 +44,6 @@ protected:
uint16_t m_timer_counter;
uint32_t m_tin;
uint32_t m_tgate;
uint32_t m_tout;
emu_timer *m_timer;
devcb_write_line m_tout_out_cb;
@ -52,6 +51,8 @@ protected:
devcb_write_line m_tgate_in_cb;
void do_timer_irq();
void do_timer_tick();
void tout_set();
void tout_clear();
TIMER_CALLBACK_MEMBER(timer_callback);