reactos/subsystems/mvdm/ntvdm/hardware/pit.c

526 lines
15 KiB
C

/*
* COPYRIGHT: GPL - See COPYING in the top level directory
* PROJECT: ReactOS Virtual DOS Machine
* FILE: subsystems/mvdm/ntvdm/hardware/pit.c
* PURPOSE: Programmable Interval Timer emulation -
* i82C54/8254 compatible
* PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
* Hermes Belusca-Maito (hermes.belusca@sfr.fr)
*/
/* INCLUDES *******************************************************************/
#include "ntvdm.h"
#define NDEBUG
#include <debug.h>
#include "emulator.h"
#include "pit.h"
#include "io.h"
#include "pic.h"
#include "clock.h"
/* PRIVATE VARIABLES **********************************************************/
static PIT_CHANNEL PitChannels[PIT_CHANNELS];
static PHARDWARE_TIMER MasterClock;
/* PRIVATE FUNCTIONS **********************************************************/
static VOID PitLatchChannelStatus(BYTE Channel)
{
if (Channel >= PIT_CHANNELS) return;
/*
* A given counter can be latched only one time until it gets unlatched.
* If the counter is latched and then is latched again later before the
* value is read, then this last latch command is ignored and the value
* will be the value at the time the first command was issued.
*/
if (PitChannels[Channel].LatchStatusSet == FALSE)
{
BYTE StatusLatch = 0;
/** HACK!! **/BYTE NullCount = 0;/** HACK!! **/
StatusLatch = PitChannels[Channel].Out << 7 | NullCount << 6;
StatusLatch |= (PitChannels[Channel].ReadWriteMode & 0x03) << 4;
StatusLatch |= (PitChannels[Channel].Mode & 0x07) << 1;
StatusLatch |= (PitChannels[Channel].Bcd & 0x01);
/* Latch the counter's status */
PitChannels[Channel].LatchStatusSet = TRUE;
PitChannels[Channel].StatusLatch = StatusLatch;
}
}
static VOID PitLatchChannelCount(BYTE Channel)
{
if (Channel >= PIT_CHANNELS) return;
/*
* A given counter can be latched only one time until it gets unlatched.
* If the counter is latched and then is latched again later before the
* value is read, then this last latch command is ignored and the value
* will be the value at the time the first command was issued.
*/
if (PitChannels[Channel].ReadStatus == 0x00)
{
/* Latch the counter's value */
PitChannels[Channel].ReadStatus = PitChannels[Channel].ReadWriteMode;
/* Convert the current value to BCD if needed */
PitChannels[Channel].OutputLatch =
READ_PIT_VALUE(PitChannels[Channel], PitChannels[Channel].CurrentValue);
}
}
static VOID PitSetOut(PPIT_CHANNEL Channel, BOOLEAN State)
{
/** HACK!! **\ if (State == Channel->Out) return; \** HACK!! **/
/* Set the new state of the OUT pin */
Channel->Out = State;
/* Call the callback */
if (!Channel->Gate) return; // HACK: This is a HACK until gates are properly used (needed for the speaker to work properly).
if (Channel->OutFunction) Channel->OutFunction(Channel->OutParam, State);
}
static VOID PitInitCounter(PPIT_CHANNEL Channel)
{
switch (Channel->Mode)
{
case PIT_MODE_INT_ON_TERMINAL_COUNT:
PitSetOut(Channel, FALSE);
break;
case PIT_MODE_HARDWARE_ONE_SHOT:
case PIT_MODE_RATE_GENERATOR:
case PIT_MODE_SQUARE_WAVE:
case PIT_MODE_SOFTWARE_STROBE:
case PIT_MODE_HARDWARE_STROBE:
PitSetOut(Channel, TRUE);
break;
}
}
static VOID PitWriteCommand(BYTE Value)
{
BYTE Channel = (Value >> 6) & 0x03;
BYTE ReadWriteMode = (Value >> 4) & 0x03;
BYTE Mode = (Value >> 1) & 0x07;
BOOLEAN IsBcd = Value & 0x01;
/*
* Check for valid PIT channel - Possible values: 0, 1, 2.
* A value of 3 is for Read-Back Command.
*/
if (Channel > PIT_CHANNELS) return;
/* Read-Back Command */
if (Channel == PIT_CHANNELS)
{
if ((Value & 0x20) == 0) // Bit 5 (Count) == 0: We latch multiple counters' counts
{
if (Value & 0x02) PitLatchChannelCount(0);
if (Value & 0x04) PitLatchChannelCount(1);
if (Value & 0x08) PitLatchChannelCount(2);
}
if ((Value & 0x10) == 0) // Bit 4 (Status) == 0: We latch multiple counters' statuses
{
if (Value & 0x02) PitLatchChannelStatus(0);
if (Value & 0x04) PitLatchChannelStatus(1);
if (Value & 0x08) PitLatchChannelStatus(2);
}
return;
}
/* Check if this is a counter latch command... */
if (ReadWriteMode == 0)
{
PitLatchChannelCount(Channel);
return;
}
/* ... otherwise, set the modes and reset flip-flops */
PitChannels[Channel].ReadWriteMode = ReadWriteMode;
PitChannels[Channel].ReadStatus = 0x00;
PitChannels[Channel].WriteStatus = 0x00;
PitChannels[Channel].LatchStatusSet = FALSE;
PitChannels[Channel].StatusLatch = 0x00;
PitChannels[Channel].CountRegister = 0x00;
PitChannels[Channel].OutputLatch = 0x00;
/** HACK!! **/PitChannels[Channel].FlipFlop = FALSE;/** HACK!! **/
/* Fix the current value if we switch to BCD counting */
PitChannels[Channel].Bcd = IsBcd;
if (IsBcd && PitChannels[Channel].CurrentValue > 9999)
PitChannels[Channel].CurrentValue = 9999;
switch (Mode)
{
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
{
PitChannels[Channel].Mode = Mode;
break;
}
case 6:
case 7:
{
/*
* Modes 6 and 7 become PIT_MODE_RATE_GENERATOR
* and PIT_MODE_SQUARE_WAVE respectively.
*/
PitChannels[Channel].Mode = Mode - 4;
break;
}
}
PitInitCounter(&PitChannels[Channel]);
}
static BYTE PitReadData(BYTE Channel)
{
LPBYTE ReadWriteMode = NULL;
LPWORD CurrentValue = NULL;
/*
* If the status was latched, the first read operation will return the
* latched status, whichever value (count or status) was latched first.
*/
if (PitChannels[Channel].LatchStatusSet)
{
PitChannels[Channel].LatchStatusSet = FALSE;
return PitChannels[Channel].StatusLatch;
}
/* To be able to read the count asynchronously, latch it first if needed */
if (PitChannels[Channel].ReadStatus == 0) PitLatchChannelCount(Channel);
/* The count is now latched */
ASSERT(PitChannels[Channel].ReadStatus != 0);
ReadWriteMode = &PitChannels[Channel].ReadStatus ;
CurrentValue = &PitChannels[Channel].OutputLatch;
if (*ReadWriteMode & 1)
{
/* Read LSB */
*ReadWriteMode &= ~1;
return LOBYTE(*CurrentValue);
}
if (*ReadWriteMode & 2)
{
/* Read MSB */
*ReadWriteMode &= ~2;
return HIBYTE(*CurrentValue);
}
/* Shouldn't get here */
ASSERT(FALSE);
return 0;
}
static VOID PitWriteData(BYTE Channel, BYTE Value)
{
LPBYTE ReadWriteMode = NULL;
if (PitChannels[Channel].WriteStatus == 0x00)
{
PitChannels[Channel].WriteStatus = PitChannels[Channel].ReadWriteMode;
}
ASSERT(PitChannels[Channel].WriteStatus != 0);
ReadWriteMode = &PitChannels[Channel].WriteStatus;
if (*ReadWriteMode & 1)
{
/* Write LSB */
*ReadWriteMode &= ~1;
PitChannels[Channel].CountRegister &= 0xFF00;
PitChannels[Channel].CountRegister |= Value;
}
else if (*ReadWriteMode & 2)
{
/* Write MSB */
*ReadWriteMode &= ~2;
PitChannels[Channel].CountRegister &= 0x00FF;
PitChannels[Channel].CountRegister |= Value << 8;
}
/* ReadWriteMode went to zero: we are going to load the new count */
if (*ReadWriteMode == 0x00)
{
if (PitChannels[Channel].CountRegister == 0x0000)
{
/* Wrap around to the highest count */
if (PitChannels[Channel].Bcd)
PitChannels[Channel].CountRegister = 9999;
else
PitChannels[Channel].CountRegister = 0xFFFF; // 0x10000; // 65536
}
/* Convert the current value from BCD if needed */
PitChannels[Channel].CountRegister =
WRITE_PIT_VALUE(PitChannels[Channel], PitChannels[Channel].CountRegister);
PitChannels[Channel].ReloadValue = PitChannels[Channel].CountRegister;
/* Reload now the new count */
PitChannels[Channel].CurrentValue = PitChannels[Channel].ReloadValue;
}
}
static BYTE WINAPI PitReadPort(USHORT Port)
{
switch (Port)
{
case PIT_DATA_PORT(0):
case PIT_DATA_PORT(1):
case PIT_DATA_PORT(2):
{
return PitReadData(Port - PIT_DATA_PORT(0));
}
}
return 0;
}
static VOID WINAPI PitWritePort(USHORT Port, BYTE Data)
{
switch (Port)
{
case PIT_COMMAND_PORT:
{
PitWriteCommand(Data);
break;
}
case PIT_DATA_PORT(0):
case PIT_DATA_PORT(1):
case PIT_DATA_PORT(2):
{
PitWriteData(Port - PIT_DATA_PORT(0), Data);
break;
}
}
}
static VOID PitDecrementCount(PPIT_CHANNEL Channel, DWORD Count)
{
if (Count == 0) return;
switch (Channel->Mode)
{
case PIT_MODE_INT_ON_TERMINAL_COUNT:
{
/* Decrement the value */
if (Count > Channel->CurrentValue)
{
/* The value does not reload in this case */
Channel->CurrentValue = 0;
}
else Channel->CurrentValue -= Count;
/* Did it fall to the terminal count? */
if (Channel->CurrentValue == 0 && !Channel->Out)
{
/* Yes, raise the output line */
PitSetOut(Channel, TRUE);
}
break;
}
case PIT_MODE_RATE_GENERATOR:
{
BOOLEAN Reloaded = FALSE;
while (Count)
{
if ((Count > Channel->CurrentValue)
&& (Channel->CurrentValue != 0))
{
/* Decrement the count */
Count -= Channel->CurrentValue;
/* Reload the value */
Channel->CurrentValue = Channel->ReloadValue;
/* Set the flag */
Reloaded = TRUE;
}
else
{
/* Decrement the value */
Channel->CurrentValue -= Count;
/* Clear the count */
Count = 0;
/* Did it fall to zero? */
if (Channel->CurrentValue == 0)
{
Channel->CurrentValue = Channel->ReloadValue;
Reloaded = TRUE;
}
}
}
/* If there was a reload, raise the output line */
if (Reloaded) PitSetOut(Channel, TRUE);
break;
}
case PIT_MODE_SQUARE_WAVE:
{
INT ReloadCount = 0;
WORD ReloadValue = Channel->ReloadValue;
/* The reload value must be even */
ReloadValue &= ~1;
while (Count)
{
if (((Count * 2) > Channel->CurrentValue)
&& (Channel->CurrentValue != 0))
{
/* Decrement the count */
Count -= Channel->CurrentValue / 2;
/* Reload the value */
Channel->CurrentValue = ReloadValue;
/* Increment the reload count */
ReloadCount++;
}
else
{
/* Decrement the value */
Channel->CurrentValue -= Count * 2;
/* Clear the count */
Count = 0;
/* Did it fall to zero? */
if (Channel->CurrentValue == 0)
{
/* Reload the value */
Channel->CurrentValue = ReloadValue;
/* Increment the reload count */
ReloadCount++;
}
}
}
if (ReloadCount == 0) break;
/* Toggle the flip-flop if the number of reloads was odd */
if (ReloadCount & 1)
{
Channel->FlipFlop = !Channel->FlipFlop;
PitSetOut(Channel, !Channel->Out);
}
/* Was there any rising edge? */
if ((Channel->FlipFlop && (ReloadCount == 1)) || (ReloadCount > 1))
{
/* Yes, raise the output line */
PitSetOut(Channel, TRUE);
}
break;
}
case PIT_MODE_SOFTWARE_STROBE:
{
// TODO: NOT IMPLEMENTED
break;
}
case PIT_MODE_HARDWARE_ONE_SHOT:
case PIT_MODE_HARDWARE_STROBE:
{
/* These modes do not work on x86 PCs */
break;
}
}
}
static VOID FASTCALL PitClock(ULONGLONG Count)
{
UCHAR i;
for (i = 0; i < PIT_CHANNELS; i++)
{
// if (!PitChannels[i].Counting) continue;
PitDecrementCount(&PitChannels[i], Count);
}
}
/* PUBLIC FUNCTIONS ***********************************************************/
VOID PitSetOutFunction(BYTE Channel, LPVOID Param, PIT_OUT_FUNCTION OutFunction)
{
if (Channel >= PIT_CHANNELS) return;
PitChannels[Channel].OutParam = Param;
PitChannels[Channel].OutFunction = OutFunction;
}
VOID PitSetGate(BYTE Channel, BOOLEAN State)
{
if (Channel >= PIT_CHANNELS) return;
if (State == PitChannels[Channel].Gate) return;
/* UNIMPLEMENTED */
PitChannels[Channel].Gate = State;
}
WORD PitGetReloadValue(BYTE Channel)
{
if (Channel >= PIT_CHANNELS) return 0xFFFF;
if (PitChannels[Channel].ReloadValue == 0)
return 0xFFFF;
else
return PitChannels[Channel].ReloadValue;
}
VOID PitInitialize(VOID)
{
/* Set up the timers to their default value */
PitSetOutFunction(0, NULL, NULL);
PitSetGate(0, TRUE);
PitSetOutFunction(1, NULL, NULL);
PitSetGate(1, TRUE);
PitSetOutFunction(2, NULL, NULL);
PitSetGate(2, FALSE);
/* Register the I/O Ports */
RegisterIoPort(PIT_COMMAND_PORT, NULL, PitWritePort);
RegisterIoPort(PIT_DATA_PORT(0), PitReadPort, PitWritePort);
RegisterIoPort(PIT_DATA_PORT(1), PitReadPort, PitWritePort);
RegisterIoPort(PIT_DATA_PORT(2), PitReadPort, PitWritePort);
/* Register the hardware timer */
MasterClock = CreateHardwareTimer(HARDWARE_TIMER_ENABLED | HARDWARE_TIMER_PRECISE,
HZ_TO_NS(PIT_BASE_FREQUENCY),
PitClock);
}
/* EOF */