mirror of
https://github.com/holub/mame
synced 2025-10-06 00:54:22 +03:00
1131 lines
28 KiB
C++
1131 lines
28 KiB
C++
// license:BSD-3-Clause
|
|
// copyright-holders:Wilbert Pol, Nathan Woods
|
|
/*****************************************************************************
|
|
*
|
|
* Programmable Interval Timer 8253/8254
|
|
*
|
|
* Three Independent Timers
|
|
* (gate, clock, out pins)
|
|
*
|
|
* 8254 has an additional readback feature
|
|
*
|
|
* Revision History
|
|
* 1-Apr-2008 - WFP: Changed the implementation into a device.
|
|
* 8-Jul-2004 - AJ: Fixed some bugs. Styx now runs correctly.
|
|
* Implemented 8254 features.
|
|
* 1-Mar-2004 - NPW: Did an almost total rewrite and cleaned out much
|
|
* of the ugliness in the previous design. Bug #430
|
|
* seems to be fixed
|
|
* 1-Jul-2000 - PeT: Split off from PC driver and componentized
|
|
*
|
|
*****************************************************************************/
|
|
|
|
#include "emu.h"
|
|
#include "machine/pit8253.h"
|
|
|
|
/* device types */
|
|
enum
|
|
{
|
|
TYPE_PIT8253 = 0,
|
|
TYPE_PIT8254
|
|
};
|
|
|
|
|
|
/***************************************************************************
|
|
|
|
Structures & macros
|
|
|
|
***************************************************************************/
|
|
|
|
#define VERBOSE 0
|
|
|
|
#define LOG1(msg) do { if (VERBOSE >= 1) logerror msg; } while (0)
|
|
#define LOG2(msg) do { if (VERBOSE >= 2) logerror msg; } while (0)
|
|
|
|
|
|
const device_type PIT8253 = &device_creator<pit8253_device>;
|
|
|
|
|
|
pit8253_device::pit8253_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
|
|
device_t(mconfig, PIT8253, "8253 PIT", tag, owner, clock, "pit8253", __FILE__),
|
|
m_clk0(0),
|
|
m_clk1(0),
|
|
m_clk2(0),
|
|
m_out0_handler(*this),
|
|
m_out1_handler(*this),
|
|
m_out2_handler(*this)
|
|
{
|
|
}
|
|
|
|
pit8253_device::pit8253_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, device_t *owner, uint32_t clock, const char *shortname, const char *source) :
|
|
device_t(mconfig, type, name, tag, owner, clock, shortname, source),
|
|
m_clk0(0),
|
|
m_clk1(0),
|
|
m_clk2(0),
|
|
m_out0_handler(*this),
|
|
m_out1_handler(*this),
|
|
m_out2_handler(*this)
|
|
{
|
|
}
|
|
|
|
|
|
const device_type PIT8254 = &device_creator<pit8254_device>;
|
|
|
|
pit8254_device::pit8254_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
|
|
: pit8253_device(mconfig, PIT8254, "8254 PIT", tag, owner, clock, "pit8254", __FILE__)
|
|
{
|
|
}
|
|
|
|
|
|
pit8253_device::pit8253_timer *pit8253_device::get_timer(int which)
|
|
{
|
|
which &= 3;
|
|
if (which < PIT8253_MAX_TIMER)
|
|
return &m_timers[which];
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// device_start - device-specific startup
|
|
//-------------------------------------------------
|
|
|
|
void pit8253_device::device_start()
|
|
{
|
|
m_timers[0].clockin = m_clk0;
|
|
m_timers[1].clockin = m_clk1;
|
|
m_timers[2].clockin = m_clk2;
|
|
|
|
m_out0_handler.resolve_safe();
|
|
m_out1_handler.resolve_safe();
|
|
m_out2_handler.resolve_safe();
|
|
|
|
for (int timerno = 0; timerno < PIT8253_MAX_TIMER; timerno++)
|
|
{
|
|
pit8253_timer *timer = get_timer(timerno);
|
|
|
|
/* initialize timer */
|
|
timer->updatetimer = timer_alloc(timerno);
|
|
timer->updatetimer->adjust(attotime::never, timerno);
|
|
|
|
/* set up state save values */
|
|
save_item(NAME(timer->clockin), timerno);
|
|
save_item(NAME(timer->control), timerno);
|
|
save_item(NAME(timer->status), timerno);
|
|
save_item(NAME(timer->lowcount), timerno);
|
|
save_item(NAME(timer->latch), timerno);
|
|
save_item(NAME(timer->count), timerno);
|
|
save_item(NAME(timer->value), timerno);
|
|
save_item(NAME(timer->wmsb), timerno);
|
|
save_item(NAME(timer->rmsb), timerno);
|
|
save_item(NAME(timer->output), timerno);
|
|
save_item(NAME(timer->gate), timerno);
|
|
save_item(NAME(timer->latched_count), timerno);
|
|
save_item(NAME(timer->latched_status), timerno);
|
|
save_item(NAME(timer->null_count), timerno);
|
|
save_item(NAME(timer->phase), timerno);
|
|
save_item(NAME(timer->last_updated), timerno);
|
|
save_item(NAME(timer->clock), timerno);
|
|
|
|
/* zerofill */
|
|
timer->gate = 1;
|
|
timer->phase = 0;
|
|
timer->clock = 0;
|
|
|
|
timer->index = timerno;
|
|
timer->control = timer->status = 0x30;
|
|
timer->rmsb = timer->wmsb = 0;
|
|
timer->count = timer->value = timer->latch = 0;
|
|
timer->lowcount = 0;
|
|
|
|
timer->output = 0;
|
|
timer->latched_count = 0;
|
|
timer->latched_status = 0;
|
|
timer->null_count = 1;
|
|
|
|
timer->last_updated = machine().time();
|
|
}
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// device_reset - device-specific reset
|
|
//-------------------------------------------------
|
|
|
|
void pit8253_device::device_reset()
|
|
{
|
|
for (int i = 0; i < PIT8253_MAX_TIMER; i++)
|
|
{
|
|
pit8253_timer *timer = get_timer(i);
|
|
|
|
/* According to Intel's 8254 docs, the state of a timer is undefined
|
|
until the first mode control word is written. Here we define this
|
|
undefined behaviour */
|
|
timer->control = timer->status = 0x30;
|
|
timer->rmsb = timer->wmsb = 0;
|
|
timer->count = timer->value = timer->latch = 0;
|
|
timer->lowcount = 0;
|
|
|
|
timer->output = 2; /* output is undetermined */
|
|
timer->latched_count = 0;
|
|
timer->latched_status = 0;
|
|
timer->null_count = 1;
|
|
|
|
timer->last_updated = machine().time();
|
|
|
|
update(timer);
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
|
|
Functions
|
|
|
|
***************************************************************************/
|
|
|
|
#define CTRL_ACCESS(control) (((control) >> 4) & 0x03)
|
|
#define CTRL_MODE(control) (((control) >> 1) & (((control) & 0x04) ? 0x03 : 0x07))
|
|
#define CTRL_BCD(control) (((control) >> 0) & 0x01)
|
|
|
|
|
|
inline uint32_t pit8253_device::adjusted_count(int bcd, uint16_t val)
|
|
{
|
|
if (!bcd)
|
|
return (val == 0) ? 0x10000 : val;
|
|
else if (val == 0)
|
|
return 10000;
|
|
|
|
/* In BCD mode, a nybble loaded with value A-F counts down the same as in
|
|
binary mode, but wraps around to 9 instead of F after 0, so loading the
|
|
count register with 0xFFFF gives a period of
|
|
0xF - for the units to count down to 0
|
|
+ 10*0xF - for the tens to count down to 0
|
|
+ 100*0xF - for the hundreds to count down to 0
|
|
+ 1000*0xF - for the thousands to count down to 0
|
|
= 16665 cycles
|
|
*/
|
|
return
|
|
((val>>12) & 0xF) * 1000 +
|
|
((val>> 8) & 0xF) * 100 +
|
|
((val>> 4) & 0xF) * 10 +
|
|
( val & 0xF);
|
|
}
|
|
|
|
|
|
/* This function subtracts 1 from timer->value "cycles" times, taking into
|
|
account binary or BCD operation, and wrapping around from 0 to 0xFFFF or
|
|
0x9999 as necessary. */
|
|
void pit8253_device::decrease_counter_value(pit8253_timer *timer, int64_t cycles)
|
|
{
|
|
uint16_t value;
|
|
uint8_t units, tens, hundreds, thousands;
|
|
|
|
if (CTRL_BCD(timer->control) == 0)
|
|
{
|
|
timer->value -= (cycles & 0xffff);
|
|
return;
|
|
}
|
|
|
|
value = timer->value;
|
|
units = value & 0xf;
|
|
tens = (value >> 4) & 0xf;
|
|
hundreds = (value >> 8) & 0xf;
|
|
thousands = (value >> 12) & 0xf;
|
|
|
|
if (cycles <= units)
|
|
{
|
|
units -= cycles;
|
|
}
|
|
else
|
|
{
|
|
cycles -= units;
|
|
units = (10 - cycles % 10) % 10;
|
|
|
|
cycles = (cycles + 9) / 10; /* the +9 is so we get a carry if cycles%10 wasn't 0 */
|
|
if (cycles <= tens)
|
|
{
|
|
tens -= cycles;
|
|
}
|
|
else
|
|
{
|
|
cycles -= tens;
|
|
tens = (10 - cycles % 10) % 10;
|
|
|
|
cycles = (cycles + 9) / 10;
|
|
if (cycles <= hundreds)
|
|
{
|
|
hundreds -= cycles;
|
|
}
|
|
else
|
|
{
|
|
cycles -= hundreds;
|
|
hundreds = (10 - cycles % 10) % 10;
|
|
cycles = (cycles + 9) / 10;
|
|
thousands = (10 + thousands - cycles % 10) % 10;
|
|
}
|
|
}
|
|
}
|
|
|
|
timer->value = (thousands << 12) | (hundreds << 8) | (tens << 4) | units;
|
|
}
|
|
|
|
|
|
/* Counter loading: transfer of a count from the CR to the CE */
|
|
void pit8253_device::load_counter_value(pit8253_timer *timer)
|
|
{
|
|
timer->value = timer->count;
|
|
timer->null_count = 0;
|
|
|
|
if (CTRL_MODE(timer->control) == 3 && timer->output == 0)
|
|
timer->value &= 0xfffe;
|
|
}
|
|
|
|
|
|
void pit8253_device::set_output(pit8253_timer *timer, int output)
|
|
{
|
|
if (output != timer->output)
|
|
{
|
|
timer->output = output;
|
|
LOG2(("set_output(): timer %d, %s\n", timer->index, output ? "low to high" : "high to low"));
|
|
|
|
switch (timer->index)
|
|
{
|
|
case 0:
|
|
m_out0_handler(output);
|
|
break;
|
|
|
|
case 1:
|
|
m_out1_handler(output);
|
|
break;
|
|
|
|
case 2:
|
|
m_out2_handler(output);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* This emulates timer "timer" for "elapsed_cycles" cycles and assumes no
|
|
callbacks occur during that time. */
|
|
void pit8253_device::simulate2(pit8253_timer *timer, int64_t elapsed_cycles)
|
|
{
|
|
uint32_t adjusted_value;
|
|
int bcd = CTRL_BCD(timer->control);
|
|
int mode = CTRL_MODE(timer->control);
|
|
static const uint32_t CYCLES_NEVER = (0xffffffff);
|
|
uint32_t cycles_to_output = 0;
|
|
|
|
LOG2(("simulate2(): simulating %d cycles for %d in mode %d, bcd = %d, phase = %d, gate = %d, output %d, value = 0x%04x\n",
|
|
(int)elapsed_cycles, timer->index, mode, bcd, timer->phase, timer->gate, timer->output, timer->value));
|
|
|
|
switch (mode)
|
|
{
|
|
case 0:
|
|
/* Mode 0: (Interrupt on Terminal Count)
|
|
|
|
+------------------
|
|
|
|
|
----------+
|
|
<- n+1 ->
|
|
|
|
^
|
|
+- counter load
|
|
|
|
phase|output|length |value|next|comment
|
|
-----+------+--------+-----+----+----------------------------------
|
|
0|low |infinity| |1 |waiting for count
|
|
1|low |1 | |2 |internal delay when counter loaded
|
|
2|low |n |n..1 |3 |counting down
|
|
3|high |infinity|0..1 |3 |counting down
|
|
|
|
Gate level sensitive only. Low disables counting, high enables it. */
|
|
|
|
if (timer->phase == 0)
|
|
{
|
|
cycles_to_output = CYCLES_NEVER;
|
|
}
|
|
else
|
|
{
|
|
if (elapsed_cycles >= 0 && timer->phase == 1)
|
|
{
|
|
/* Counter load cycle */
|
|
if (elapsed_cycles > 0)
|
|
{
|
|
--elapsed_cycles;
|
|
timer->phase = 2;
|
|
}
|
|
load_counter_value(timer);
|
|
}
|
|
|
|
if (timer->gate == 0)
|
|
{
|
|
cycles_to_output = CYCLES_NEVER;
|
|
}
|
|
else
|
|
{
|
|
if (timer->phase == 2)
|
|
{
|
|
adjusted_value = adjusted_count(bcd, timer->value);
|
|
if (elapsed_cycles >= adjusted_value)
|
|
{
|
|
/* Counter wrapped, output goes high */
|
|
elapsed_cycles -= adjusted_value;
|
|
timer->phase = 3;
|
|
timer->value = 0;
|
|
set_output(timer, 1);
|
|
}
|
|
}
|
|
|
|
decrease_counter_value(timer, elapsed_cycles);
|
|
|
|
switch (timer->phase)
|
|
{
|
|
case 1: cycles_to_output = 1; break;
|
|
case 2: cycles_to_output = adjusted_count(bcd, timer->value); break;
|
|
case 3: cycles_to_output = adjusted_count(bcd, timer->value); break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
|
|
case 1:
|
|
/* Mode 1: (Hardware Retriggerable One-Shot a.k.a. Programmable One-Shot)
|
|
|
|
-----+ +------------------
|
|
| |
|
|
+-------+
|
|
<- n ->
|
|
|
|
^
|
|
+- trigger
|
|
|
|
phase|output|length |value|next|comment
|
|
-----+------+--------+-----+----+----------------------------------
|
|
0|high |infinity| |1 |counting down
|
|
1|high |1 | |2 |internal delay to load counter
|
|
2|low |n |n..1 |3 |counting down
|
|
3|high |infinity|0..1 |3 |counting down
|
|
|
|
Gate rising-edge sensitive only.
|
|
Rising edge initiates counting and resets output after next clock. */
|
|
|
|
if (elapsed_cycles >= 0 && timer->phase == 1)
|
|
{
|
|
/* Counter load cycle, output goes low */
|
|
if (elapsed_cycles > 0)
|
|
{
|
|
--elapsed_cycles;
|
|
timer->phase = 2;
|
|
}
|
|
load_counter_value(timer);
|
|
set_output(timer, 0);
|
|
}
|
|
|
|
if (timer->phase == 2)
|
|
{
|
|
adjusted_value = adjusted_count(bcd, timer->value);
|
|
if (elapsed_cycles >= adjusted_value)
|
|
{
|
|
/* Counter wrapped, output goes high */
|
|
timer->phase = 3;
|
|
set_output(timer, 1);
|
|
}
|
|
}
|
|
|
|
decrease_counter_value(timer, elapsed_cycles);
|
|
|
|
switch (timer->phase)
|
|
{
|
|
case 1: cycles_to_output = 1; break;
|
|
case 2: cycles_to_output = adjusted_count(bcd, timer->value); break;
|
|
default: cycles_to_output = CYCLES_NEVER; break;
|
|
}
|
|
break;
|
|
|
|
|
|
case 2:
|
|
/* Mode 2: (Rate Generator)
|
|
|
|
--------------+ +---------+ +----
|
|
| | | |
|
|
+-+ +-+
|
|
<- n -X- n ->
|
|
<1>
|
|
^
|
|
+- counter load or trigger
|
|
|
|
phase|output|length |value|next|comment
|
|
-----+------+--------+-----+----+----------------------------------
|
|
0|high |infinity| |1 |waiting for count
|
|
1|high |1 | |2 |internal delay to load counter
|
|
2|high |n |n..2 |3 |counting down
|
|
3|low |1 |1 |2 |reload counter
|
|
|
|
Counter rewrite has no effect until repeated
|
|
|
|
Gate rising-edge and level sensitive.
|
|
Gate low disables counting and sets output immediately high.
|
|
Rising-edge reloads count and initiates counting
|
|
Gate high enables counting. */
|
|
|
|
if (timer->gate == 0 || timer->phase == 0)
|
|
{
|
|
/* Gate low or mode control write forces output high */
|
|
set_output(timer, 1);
|
|
cycles_to_output = CYCLES_NEVER;
|
|
}
|
|
else
|
|
{
|
|
if (elapsed_cycles >= 0 && timer->phase == 1)
|
|
{
|
|
if (elapsed_cycles > 0)
|
|
{
|
|
--elapsed_cycles;
|
|
timer->phase = 2;
|
|
}
|
|
load_counter_value(timer);
|
|
}
|
|
|
|
adjusted_value = adjusted_count(bcd, timer->value);
|
|
|
|
do
|
|
{
|
|
if (timer->phase == 2)
|
|
{
|
|
if (elapsed_cycles + 1 >= adjusted_value)
|
|
{
|
|
/* Counter hits 1, output goes low */
|
|
timer->phase = 3;
|
|
set_output(timer, 0);
|
|
}
|
|
}
|
|
|
|
if (elapsed_cycles > 0 && timer->phase == 3)
|
|
{
|
|
/* Reload counter, output goes high */
|
|
elapsed_cycles -= adjusted_value;
|
|
timer->phase = 2;
|
|
load_counter_value(timer);
|
|
adjusted_value = adjusted_count(bcd, timer->value);
|
|
set_output(timer, 1);
|
|
}
|
|
}
|
|
while (elapsed_cycles >= adjusted_value);
|
|
|
|
/* Calculate counter value */
|
|
decrease_counter_value(timer, elapsed_cycles);
|
|
|
|
switch (timer->phase)
|
|
{
|
|
case 1: cycles_to_output = 1; break;
|
|
default: cycles_to_output = (timer->value == 1) ? 1 : (adjusted_count(bcd, timer->value) - 1); break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
|
|
case 3:
|
|
/* Mode 3: (Square Wave Generator)
|
|
|
|
----------------+ +-----------+ +----
|
|
| | | |
|
|
+-----------+ +-----------+
|
|
<- (n+1)/2 -X- n/2 ->
|
|
^
|
|
+- counter load or trigger
|
|
|
|
phase|output|length |value|next|comment
|
|
-----+------+--------+-----+----+----------------------------------
|
|
0|high |infinity| |1 |waiting for count
|
|
1|high |1 | |2 |internal delay to load counter
|
|
2|high |n/2(+1) |n..0 |3 |counting down double speed, reload counter
|
|
3|low |n/2 |n..0 |2 |counting down double speed, reload counter
|
|
|
|
Counter rewrite has no effect until repeated (output falling or rising)
|
|
|
|
Gate rising-edge and level sensitive.
|
|
Gate low disables counting and sets output immediately high.
|
|
Rising-edge reloads count and initiates counting
|
|
Gate high enables counting. */
|
|
|
|
if (timer->gate == 0 || timer->phase == 0)
|
|
{
|
|
/* Gate low or mode control write forces output high */
|
|
set_output(timer, 1);
|
|
cycles_to_output = CYCLES_NEVER;
|
|
}
|
|
else
|
|
{
|
|
if (elapsed_cycles >= 0 && timer->phase == 1)
|
|
{
|
|
if (elapsed_cycles > 0)
|
|
{
|
|
--elapsed_cycles;
|
|
timer->phase = 2;
|
|
}
|
|
load_counter_value(timer);
|
|
}
|
|
|
|
if (elapsed_cycles > 0)
|
|
{
|
|
adjusted_value = adjusted_count(bcd, timer->value);
|
|
|
|
do
|
|
{
|
|
if (timer->phase == 2 && elapsed_cycles >= ((adjusted_value + 1) >> 1))
|
|
{
|
|
/* High phase expired, output goes low */
|
|
elapsed_cycles -= ((adjusted_value + 1) >> 1);
|
|
timer->phase = 3;
|
|
load_counter_value(timer);
|
|
adjusted_value = adjusted_count(bcd, timer->value);
|
|
set_output(timer, 0);
|
|
}
|
|
|
|
if (timer->phase == 3 && elapsed_cycles >= (adjusted_value >> 1))
|
|
{
|
|
/* Low phase expired, output goes high */
|
|
elapsed_cycles -= (adjusted_value >> 1);
|
|
timer->phase = 2;
|
|
load_counter_value(timer);
|
|
adjusted_value = adjusted_count(bcd, timer->value);
|
|
set_output(timer, 1);
|
|
}
|
|
}
|
|
while ((timer->phase == 2 && elapsed_cycles >= ((adjusted_value + 1) >> 1)) ||
|
|
(timer->phase == 3 && elapsed_cycles >= (adjusted_value >> 1)));
|
|
|
|
decrease_counter_value(timer, elapsed_cycles * 2);
|
|
|
|
switch (timer->phase)
|
|
{
|
|
case 1: cycles_to_output = 1; break;
|
|
case 2: cycles_to_output = (adjusted_count(bcd, timer->value) + 1) >> 1; break;
|
|
case 3: cycles_to_output = adjusted_count(bcd, timer->value) >> 1; break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
|
|
case 4:
|
|
case 5:
|
|
/* Mode 4: (Software Trigger Strobe)
|
|
Mode 5: (Hardware Trigger Strobe)
|
|
|
|
--------------+ +--------------------
|
|
| |
|
|
+-+
|
|
<- n+1 ->
|
|
^ <1>
|
|
+- counter load (mode 4) or trigger (mode 5)
|
|
|
|
phase|output|length |value|next|comment
|
|
-----+------+--------+-----+----+----------------------------------
|
|
0|high |infinity|0..1 |0 |waiting for count/counting down
|
|
1|high |1 | |2 |internal delay when counter loaded
|
|
2|high |n |n..1 |3 |counting down
|
|
3|low |1 |0 |0 |strobe
|
|
|
|
Mode 4 only: counter rewrite loads new counter
|
|
Mode 5 only: count not reloaded immediately.
|
|
Mode control write doesn't stop count but sets output high
|
|
|
|
Mode 4 only: Gate level sensitive only. Low disables counting, high enables it.
|
|
Mode 5 only: Gate rising-edge sensitive only. Rising edge initiates counting */
|
|
|
|
if (timer->gate == 0 && mode == 4)
|
|
{
|
|
cycles_to_output = CYCLES_NEVER;
|
|
}
|
|
else
|
|
{
|
|
if (elapsed_cycles >= 0 && timer->phase == 1)
|
|
{
|
|
if (elapsed_cycles > 0)
|
|
{
|
|
--elapsed_cycles;
|
|
timer->phase = 2;
|
|
}
|
|
load_counter_value(timer);
|
|
}
|
|
|
|
if (timer->value == 0 && timer->phase == 2)
|
|
adjusted_value = 0;
|
|
else
|
|
adjusted_value = adjusted_count(bcd, timer->value);
|
|
|
|
if (timer->phase == 2 && elapsed_cycles >= adjusted_value)
|
|
{
|
|
/* Counter has hit zero, set output to low */
|
|
elapsed_cycles -= adjusted_value;
|
|
timer->phase = 3;
|
|
timer->value = 0;
|
|
set_output(timer, 0);
|
|
}
|
|
|
|
if (elapsed_cycles > 0 && timer->phase == 3)
|
|
{
|
|
--elapsed_cycles;
|
|
timer->phase = 0;
|
|
decrease_counter_value(timer, 1);
|
|
set_output(timer, 1);
|
|
}
|
|
|
|
decrease_counter_value(timer, elapsed_cycles);
|
|
|
|
switch (timer->phase)
|
|
{
|
|
case 1: cycles_to_output = 1; break;
|
|
case 2: cycles_to_output = adjusted_count(bcd, timer->value); break;
|
|
case 3: cycles_to_output = 1; break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (cycles_to_output == CYCLES_NEVER || timer->clockin == 0)
|
|
{
|
|
timer->updatetimer->adjust(attotime::never, timer->index);
|
|
}
|
|
else
|
|
{
|
|
attotime next_fire_time = timer->last_updated + cycles_to_output * attotime::from_hz(timer->clockin);
|
|
|
|
timer->updatetimer->adjust(next_fire_time - machine().time(), timer->index);
|
|
}
|
|
|
|
LOG2(("simulate2(): simulating %d cycles for %d in mode %d, bcd = %d, phase = %d, gate = %d, output %d, value = 0x%04x, cycles_to_output = %04x\n",
|
|
(int)elapsed_cycles, timer->index, mode, bcd, timer->phase, timer->gate, timer->output, timer->value, cycles_to_output));
|
|
}
|
|
|
|
|
|
/* This emulates timer "timer" for "elapsed_cycles" cycles, broken down into
|
|
sections punctuated by callbacks. */
|
|
void pit8253_device::simulate(pit8253_timer *timer, int64_t elapsed_cycles)
|
|
{
|
|
if (elapsed_cycles > 0)
|
|
simulate2(timer, elapsed_cycles);
|
|
else if (timer->clockin)
|
|
timer->updatetimer->adjust(attotime::from_hz(timer->clockin), timer->index);
|
|
}
|
|
|
|
|
|
/* This brings timer "timer" up to date */
|
|
void pit8253_device::update(pit8253_timer *timer)
|
|
{
|
|
/* With the 82C54's maximum clockin of 10MHz, 64 bits is nearly 60,000
|
|
years of time. Should be enough for now. */
|
|
attotime now = machine().time();
|
|
attotime elapsed_time = now - timer->last_updated;
|
|
int64_t elapsed_cycles = elapsed_time.as_double() * timer->clockin;
|
|
|
|
LOG1(("update(): timer %d, %d elapsed_cycles\n", timer->index, elapsed_cycles));
|
|
|
|
if (timer->clockin)
|
|
timer->last_updated += elapsed_cycles * attotime::from_hz(timer->clockin);
|
|
else
|
|
timer->last_updated = now;
|
|
|
|
simulate(timer, elapsed_cycles);
|
|
}
|
|
|
|
|
|
/* We recycle bit 0 of timer->value to hold the phase in mode 3 when count is
|
|
odd. Since read commands in mode 3 always return even numbers, we need to
|
|
mask this bit off. */
|
|
uint16_t pit8253_device::masked_value(pit8253_timer *timer)
|
|
{
|
|
LOG2(("masked_value\n"));
|
|
if (CTRL_MODE(timer->control) == 3)
|
|
return timer->value & 0xfffe;
|
|
return timer->value;
|
|
}
|
|
|
|
/* Reads only affect the following bits of the counter state:
|
|
latched_status
|
|
latched_count
|
|
rmsb
|
|
so they don't affect any timer operations except other reads. */
|
|
READ8_MEMBER( pit8253_device::read )
|
|
{
|
|
pit8253_timer *timer = get_timer(offset);
|
|
uint8_t data;
|
|
uint16_t value;
|
|
|
|
LOG2(("read(): offset %d\n", offset));
|
|
|
|
if (timer == nullptr)
|
|
{
|
|
/* Reading mode control register is illegal according to docs */
|
|
/* Experimentally determined: reading it returns 0 */
|
|
data = 0;
|
|
}
|
|
else
|
|
{
|
|
update(timer);
|
|
|
|
if (timer->latched_status)
|
|
{
|
|
/* Read status register (8254 only) */
|
|
data = timer->status;
|
|
timer->latched_status = 0;
|
|
}
|
|
else
|
|
{
|
|
if (timer->latched_count != 0)
|
|
{
|
|
/* Read back latched count */
|
|
data = (timer->latch >> (timer->rmsb ? 8 : 0)) & 0xff;
|
|
timer->rmsb = 1 - timer->rmsb;
|
|
--timer->latched_count;
|
|
}
|
|
else
|
|
{
|
|
value = masked_value(timer);
|
|
|
|
/* Read back current count */
|
|
switch (CTRL_ACCESS(timer->control))
|
|
{
|
|
case 0:
|
|
default:
|
|
/* This should never happen */
|
|
data = 0; /* Appease compiler */
|
|
break;
|
|
|
|
case 1:
|
|
/* read counter bits 0-7 only */
|
|
data = (value >> 0) & 0xff;
|
|
break;
|
|
|
|
case 2:
|
|
/* read counter bits 8-15 only */
|
|
data = (value >> 8) & 0xff;
|
|
break;
|
|
|
|
case 3:
|
|
/* read bits 0-7 first, then 8-15 */
|
|
data = (value >> (timer->rmsb ? 8 : 0)) & 0xff;
|
|
timer->rmsb = 1 - timer->rmsb;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
LOG2(("read(): offset=%d data=0x%02x\n", offset, data));
|
|
return data;
|
|
}
|
|
|
|
|
|
/* Loads a new value from the bus to the count register (CR) */
|
|
void pit8253_device::load_count(pit8253_timer *timer, uint16_t newcount)
|
|
{
|
|
int mode = CTRL_MODE(timer->control);
|
|
LOG1(("load_count(): %04x\n", newcount));
|
|
|
|
if (newcount == 1)
|
|
{
|
|
/* Count of 1 is illegal in modes 2 and 3. What happens here was
|
|
determined experimentally. */
|
|
if (mode == 2)
|
|
newcount = 2;
|
|
if (mode == 3)
|
|
newcount = 0;
|
|
}
|
|
|
|
timer->count = newcount;
|
|
|
|
if (mode == 2 || mode == 3)
|
|
{
|
|
if (timer->phase == 0)
|
|
timer->phase = 1;
|
|
}
|
|
else
|
|
{
|
|
if (mode == 0 || mode == 4)
|
|
timer->phase = 1;
|
|
}
|
|
}
|
|
|
|
|
|
void pit8253_device::readback(pit8253_timer *timer, int command)
|
|
{
|
|
uint16_t value;
|
|
update(timer);
|
|
|
|
if ((command & 1) == 0)
|
|
{
|
|
/* readback status command */
|
|
if (!timer->latched_status)
|
|
{
|
|
timer->status = (timer->control & 0x3f) | ((timer->output != 0) ? 0x80 : 0) | (timer->null_count ? 0x40 : 0);
|
|
timer->latched_status = 1;
|
|
}
|
|
}
|
|
|
|
/* Experimentally determined: the read latch command seems to have no
|
|
effect if we're halfway through a 16-bit read */
|
|
if ((command & 2) == 0 && !timer->rmsb)
|
|
{
|
|
/* readback count command */
|
|
if (timer->latched_count == 0)
|
|
{
|
|
value = masked_value(timer);
|
|
switch (CTRL_ACCESS(timer->control))
|
|
{
|
|
case 0:
|
|
/* This should never happen */
|
|
break;
|
|
|
|
case 1:
|
|
/* latch bits 0-7 only */
|
|
timer->latch = ((value << 8) & 0xff00) | (value & 0xff);
|
|
timer->latched_count = 1;
|
|
break;
|
|
|
|
case 2:
|
|
/* read bits 8-15 only */
|
|
timer->latch = (value & 0xff00) | ((value >> 8) & 0xff);
|
|
timer->latched_count = 1;
|
|
break;
|
|
|
|
case 3:
|
|
/* latch all 16 bits */
|
|
timer->latch = value;
|
|
timer->latched_count = 2;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void pit8253_device::readback_command(uint8_t data)
|
|
{
|
|
/* Readback command. Illegal on 8253 */
|
|
/* Todo: find out what (if anything) the 8253 hardware actually does here. */
|
|
}
|
|
|
|
void pit8254_device::readback_command(uint8_t data)
|
|
{
|
|
LOG1(("write(): readback %02x\n", data & 0x3f));
|
|
|
|
/* Bit 0 of data must be 0. Todo: find out what the hardware does if it isn't. */
|
|
int read_command = (data >> 4) & 3;
|
|
if ((data & 2) != 0)
|
|
readback(get_timer(0), read_command);
|
|
if ((data & 4) != 0)
|
|
readback(get_timer(1), read_command);
|
|
if ((data & 8) != 0)
|
|
readback(get_timer(2), read_command);
|
|
}
|
|
|
|
void pit8253_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
|
|
{
|
|
update(get_timer(id));
|
|
}
|
|
|
|
WRITE8_MEMBER( pit8253_device::write )
|
|
{
|
|
pit8253_timer *timer = get_timer(offset);
|
|
|
|
LOG2(("write(): offset=%d data=0x%02x\n", offset, data));
|
|
|
|
if (timer == nullptr)
|
|
{
|
|
/* Write to mode control register */
|
|
timer = get_timer((data >> 6) & 3);
|
|
if (timer == nullptr)
|
|
{
|
|
readback_command(data);
|
|
return;
|
|
}
|
|
|
|
update(timer);
|
|
|
|
if (CTRL_ACCESS(data) == 0)
|
|
{
|
|
LOG1(("write(): timer=%d readback\n", (data >> 6) & 3));
|
|
|
|
/* Latch current timer value */
|
|
/* Experimentally verified: this command does not affect the mode control register */
|
|
readback(timer, 1);
|
|
}
|
|
else
|
|
{
|
|
LOG1(("write(): timer=%d bytes=%d mode=%d bcd=%d\n", (data >> 6) & 3, (data >> 4) & 3, (data >> 1) & 7, data & 1));
|
|
|
|
timer->control = (data & 0x3f);
|
|
timer->null_count = 1;
|
|
timer->wmsb = timer->rmsb = 0;
|
|
/* Phase 0 is always the phase after a mode control write */
|
|
timer->phase = 0;
|
|
set_output(timer, CTRL_MODE(timer->control) ? 1 : 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int middle_of_a_cycle = 0;
|
|
|
|
update(timer);
|
|
|
|
if (machine().time() > timer->last_updated && timer->clockin != 0)
|
|
middle_of_a_cycle = 1;
|
|
|
|
switch (CTRL_ACCESS(timer->control))
|
|
{
|
|
case 0:
|
|
/* This should never happen */
|
|
break;
|
|
|
|
case 1:
|
|
/* read/write counter bits 0-7 only */
|
|
|
|
/* check if we should compensate for not being on a cycle boundary */
|
|
if (middle_of_a_cycle)
|
|
timer->last_updated += attotime::from_hz(timer->clockin);
|
|
|
|
load_count(timer, data);
|
|
simulate2(timer, 0);
|
|
|
|
if (CTRL_MODE(timer->control) == 0)
|
|
{
|
|
set_output(timer, 0);
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
/* read/write counter bits 8-15 only */
|
|
|
|
/* check if we should compensate for not being on a cycle boundary */
|
|
if (middle_of_a_cycle)
|
|
timer->last_updated += attotime::from_hz(timer->clockin);
|
|
|
|
load_count(timer, data << 8);
|
|
simulate2(timer, 0);
|
|
|
|
if (CTRL_MODE(timer->control) == 0)
|
|
{
|
|
set_output(timer, 0);
|
|
}
|
|
break;
|
|
|
|
case 3:
|
|
/* read/write bits 0-7 first, then 8-15 */
|
|
if (timer->wmsb)
|
|
{
|
|
/* check if we should compensate for not being on a cycle boundary */
|
|
if (middle_of_a_cycle)
|
|
timer->last_updated += attotime::from_hz(timer->clockin);
|
|
|
|
load_count(timer, timer->lowcount | (data << 8));
|
|
simulate2(timer, 0);
|
|
}
|
|
else
|
|
{
|
|
timer->lowcount = data;
|
|
if (CTRL_MODE(timer->control) == 0)
|
|
{
|
|
/* The Intel docs say that writing the MSB in mode 0, phase
|
|
2 won't stop the count, but this was experimentally
|
|
determined to be false. */
|
|
timer->phase = 0;
|
|
set_output(timer, 0);
|
|
}
|
|
}
|
|
timer->wmsb = 1 - timer->wmsb;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void pit8253_device::gate_w(int gate, int state)
|
|
{
|
|
pit8253_timer *timer = get_timer(gate);
|
|
|
|
if (timer == nullptr)
|
|
return;
|
|
|
|
LOG2(("gate_w(): gate=%d state=%d\n", gate, state));
|
|
|
|
if (state != timer->gate)
|
|
{
|
|
int mode = CTRL_MODE(timer->control);
|
|
|
|
update(timer);
|
|
timer->gate = state;
|
|
if (state != 0 && ( mode == 1 || mode == 2 || mode == 5 ))
|
|
{
|
|
timer->phase = 1;
|
|
}
|
|
update(timer);
|
|
}
|
|
}
|
|
|
|
WRITE_LINE_MEMBER( pit8253_device::write_gate0 )
|
|
{
|
|
gate_w(0, state);
|
|
}
|
|
|
|
WRITE_LINE_MEMBER( pit8253_device::write_gate1 )
|
|
{
|
|
gate_w(1, state);
|
|
}
|
|
|
|
WRITE_LINE_MEMBER( pit8253_device::write_gate2 )
|
|
{
|
|
gate_w(2, state);
|
|
}
|
|
|
|
|
|
/* ----------------------------------------------------------------------- */
|
|
|
|
void pit8253_device::set_clockin(int timerno, double new_clockin)
|
|
{
|
|
pit8253_timer *timer = get_timer(timerno);
|
|
assert(timer != nullptr);
|
|
|
|
LOG2(("set_clockin(): PIT timer=%d, clockin = %f\n", timerno, new_clockin));
|
|
|
|
update(timer);
|
|
timer->clockin = new_clockin;
|
|
update(timer);
|
|
}
|
|
|
|
|
|
void pit8253_device::set_clock_signal(int timerno, int state)
|
|
{
|
|
pit8253_timer *timer = get_timer(timerno);
|
|
assert(timer != nullptr);
|
|
|
|
LOG2(("set_clock_signal(): PIT timer=%d, state = %d\n", timerno, state));
|
|
|
|
/* Trigger on low to high transition */
|
|
if (!timer->clock && state)
|
|
{
|
|
/* Advance a cycle */
|
|
simulate2(timer, 1);
|
|
}
|
|
timer->clock = state;
|
|
}
|
|
|
|
WRITE_LINE_MEMBER( pit8253_device::write_clk0 )
|
|
{
|
|
set_clock_signal(0, state);
|
|
}
|
|
|
|
WRITE_LINE_MEMBER( pit8253_device::write_clk1 )
|
|
{
|
|
set_clock_signal(1, state);
|
|
}
|
|
|
|
WRITE_LINE_MEMBER( pit8253_device::write_clk2 )
|
|
{
|
|
set_clock_signal(2, state);
|
|
}
|