paulbergmann_mpstubs/machine/pit.cc

217 lines
5.7 KiB
C++

#include "machine/pit.h"
#include "machine/ioport.h"
#include "machine/core.h"
namespace PIT {
// we only use PIT channel 2
const uint8_t CHANNEL = 2;
static IOPort data(0x40 + CHANNEL);
/*! \brief Access mode
*/
enum AccessMode {
LATCH_COUNT_VALUE = 0,
LOW_BYTE_ONLY = 1,
HIGH_BYTE_ONLY = 2,
LOW_AND_HIGH_BYTE = 3
};
/*! \brief Operating Mode
*
* \warning Channel 2 is not able to send interrupts, however, the status bit will be set
*/
enum OperatingMode {
INTERRUPT_ON_TERMINAL_COUNT = 0,
PROGRAMMABLE_ONE_SHOT = 1,
RATE_GENERATOR = 2,
SQUARE_WAVE_GENERATOR = 3, ///< useful for the PC speaker
SOFTWARE_TRIGGERED_STROBE = 4,
HARDWARE_TRIGGERED_STROBE = 5
};
/*! \brief data format
*/
enum Format {
BINARY = 0,
BCD = 1 ///< Binary Coded Decimals
};
// Mode register (only writable)
static IOPort mode_register(0x43);
union Mode {
struct {
Format format : 1;
OperatingMode operating : 3;
AccessMode access : 2;
uint8_t channel : 2;
};
uint8_t value;
/*! \brief Constructor for mode, takes the numeric value */
explicit Mode(uint8_t value) : value(value) {}
/*! \brief Constructor for counting mode
* \param access Access mode to the 16-bit counter value
* \param operating Operating mode for the counter
* \param format Number format for the 16-bit counter values (binary or BCD)
*/
Mode(AccessMode access, OperatingMode operating, Format format) :
format(format), operating(operating), access(access), channel(PIT::CHANNEL) {}
/*! \brief (Default) constructor for reading the counter value
*/
Mode() : value(0) {
this->channel = PIT::CHANNEL;
}
/*! \brief Write the value to the mode register
*/
void write() const {
mode_register.outb(value);
}
};
// The NMI Status and Control Register contains details about PIT counter 2
static IOPort controlRegister(0x61);
union Control {
/*! \brief I/O-port bitmap for the NMI Status and Control Register
* \note Over time, the meaning of the bits stored at I/O port 0x61 changed; don't get the structure confused
* with old documentation on the IBM PC XT platform.
* \see [Intel® I/O Controller Hub 7 (ICH7) Family](i-o-controller-hub-7-datasheet.pdf#page=415), page 415
*/
struct {
//! If enabled, the interrupt state will be visible at status_timer_counter2
uint8_t enable_timer_counter2 : 1;
uint8_t enable_speaker_data : 1; ///< If set, speaker output is equal to status_timer_counter2
uint8_t enable_pci_serr : 1; ///< not important, do not modify
uint8_t enable_nmi_iochk : 1; ///< not important, do not modify
const uint8_t refresh_cycle_toggle : 1; ///< not important, must be 0 on write
const uint8_t status_timer_counter2 : 1; ///< will be set on timer expiration; must be 0 on write
const uint8_t status_iochk_nmi_source : 1; ///< not important, must be 0 on write
const uint8_t status_serr_nmi_source : 1; ///< not important, must be 0 on write
};
uint8_t value;
/*! \brief Constructor
* \param value Numeric value for the control register
*/
explicit Control(uint8_t value) : value(value) {}
/*! \brief Default constructor
* Automatically reads the current contents from the control register.
*/
Control() : value(controlRegister.inb()) {}
/*! \brief Write the current state to the control register.
*/
void write() const {
controlRegister.outb(value);
}
};
// The base frequency is, due to historic reasons, 1.193182 MHz.
const uint64_t BASE_FREQUENCY = 1193182ULL;
bool set(uint16_t us) {
// Counter ticks for us
uint64_t counter = BASE_FREQUENCY * us / 1000000ULL;
// As the hardware counter has a size of 16 bit, we want to check whether the
// calculated counter value is too large ( > 54.9ms )
if (counter > 0xffff) {
return false;
}
// Interrupt state should be readable in status register, but PC speaker should remain off
Control c;
c.enable_speaker_data = 0;
c.enable_timer_counter2 = 1;
c.write();
// Channel 2, 16-bit divisor, with mode 0 (interrupt) in binary format
Mode m(AccessMode::LOW_AND_HIGH_BYTE, OperatingMode::INTERRUPT_ON_TERMINAL_COUNT, Format::BINARY);
m.write();
// Set the counter's start value
data.outb(counter & 0xff); // low
data.outb((counter >> 8) & 0xff); // high
return true;
}
uint16_t get(void) {
// Set mode to reading
Mode m;
m.write();
// Read low and high
uint16_t value = data.inb();
value |= data.inb() << 8;
return value;
}
bool isActive(void) {
Control c; // reads the current value from the control register
return c.enable_timer_counter2 == 1 && c.status_timer_counter2 == 0;
}
bool waitForTimeout(void) {
while(true) {
Control c; // reads the current value from the control register
if (c.enable_timer_counter2 == 0) {
return false;
} else if (c.status_timer_counter2 == 1) {
return true;
} else {
Core::pause();
}
}
}
bool delay(uint16_t us) {
return set(us) && waitForTimeout();
}
void pcspeaker(uint32_t freq) {
Control c;
if (freq == 0) {
disable();
} else {
// calculate frequency divider
uint64_t div = BASE_FREQUENCY / freq;
if (div > 0xffff) {
div = 0xffff;
}
// check if already configured
if (c.enable_speaker_data == 0) {
// if not, set mode
Mode m(AccessMode::LOW_AND_HIGH_BYTE, OperatingMode::SQUARE_WAVE_GENERATOR, Format::BINARY);
m.write();
}
// write frequency divider
data.outb(div & 0xff);
data.outb((div >> 8) & 0xff);
// already configured? (second part to prevent playing a wrong sound)
if (c.enable_speaker_data == 0) {
// activate PC speaker
c.enable_speaker_data = 1;
c.enable_timer_counter2 = 1;
c.write();
}
}
}
void disable(void) {
Control c;
c.enable_speaker_data = 0;
c.enable_timer_counter2 = 0;
c.write();
}
} // namespace PIT