velxio/test/test_intel/test_buses/8253-pit.c

277 lines
9.7 KiB
C

/*
* Intel 8253 Programmable Interval Timer — Modes 0, 2, 3 subset.
*
* The 8253 (and pin-compatible 8254) is a 24-pin DIP that gives a CPU
* three independent 16-bit countdown counters. Each counter has its
* own external CLK input and GATE input plus an OUT output. Six
* counting modes; we implement the three most common:
*
* Mode 0 (interrupt on terminal count): OUT low after writing the
* control word; once a count is loaded and GATE is high, OUT stays
* low until count counts down to zero, then goes high (and stays
* high until a new count is written).
*
* Mode 2 (rate generator): OUT goes low for one CLK then back high;
* counter automatically reloads. Used for periodic system-tick.
*
* Mode 3 (square wave): OUT toggles every (count/2) CLKs (for even
* counts); used for PC-speaker tone generation.
*
* Source: Intel 8253/8254 Datasheet (public mirror, bitsavers.org).
*
* Pin contract (24-pin DIP):
* D0..D7 bidirectional 8-bit data bus
* A0, A1 inputs — register select (00/01/10 = counters; 11 = ctrl)
* CS̅, RD̅, WR̅
* CLK0..2 inputs — counter clocks (rising edge counts)
* GATE0..2 inputs — counter enable (high = enable)
* OUT0..2 outputs — counter outputs (mode-specific behaviour)
* VCC, GND
*
* Control word format:
* bits 7..6: counter select (00=ch0, 01=ch1, 10=ch2, 11=read-back NI)
* bits 5..4: read/write mode
* 00 = latch counter for read
* 01 = read/write LSB only
* 10 = read/write MSB only
* 11 = read/write LSB then MSB
* bits 3..1: counting mode (we implement 0, 2, 3)
* bit 0: 0 = binary, 1 = BCD (BCD not supported)
*
* Modes 1, 4, 5 are not implemented; control writes selecting them
* load as Mode 0 with a warning (silently).
*/
#include "velxio-chip.h"
#include <stdint.h>
#include <stdbool.h>
typedef struct {
/* Per-counter state */
uint8_t mode; /* 0, 2, or 3 (others coerced to 0) */
uint8_t rw_mode; /* 0 = latch, 1 = LSB, 2 = MSB, 3 = LSB-then-MSB */
bool write_high_next; /* for rw_mode == 3 */
bool read_high_next;
uint16_t reload; /* loaded count value */
uint16_t count; /* current count */
uint16_t latched; /* snapshot for read-back */
bool have_latched;
bool out_state;
bool armed; /* count is loaded and ready */
} channel_t;
typedef struct {
vx_pin d[8];
vx_pin a0, a1;
vx_pin cs, rd, wr;
vx_pin clk[3];
vx_pin gate[3];
vx_pin out[3];
vx_pin vcc, gnd;
channel_t ch[3];
bool driving_d;
int wr_last;
} chip_t;
static chip_t G;
/* ─── D-bus helpers ─────────────────────────────────────────────────────── */
static uint8_t read_d_byte(void) {
uint8_t v = 0;
for (int i = 0; i < 8; i++) if (vx_pin_read(G.d[i])) v |= (1u << i);
return v;
}
static void drive_d(uint8_t v) {
for (int i = 0; i < 8; i++) {
vx_pin_set_mode(G.d[i], VX_OUTPUT);
vx_pin_write(G.d[i], (v >> i) & 1);
}
G.driving_d = true;
}
static void release_d(void) {
if (!G.driving_d) return;
for (int i = 0; i < 8; i++) vx_pin_set_mode(G.d[i], VX_INPUT);
G.driving_d = false;
}
static void drive_out(int idx, bool high) {
G.ch[idx].out_state = high;
vx_pin_write(G.out[idx], high ? 1 : 0);
}
/* ─── Control word parsing ──────────────────────────────────────────────── */
static void apply_control(uint8_t cw) {
int sel = (cw >> 6) & 3;
int rw = (cw >> 4) & 3;
int mode = (cw >> 1) & 7;
if (sel == 3) return; /* read-back command — not supported */
channel_t* c = &G.ch[sel];
c->rw_mode = rw;
if (rw == 0) {
/* Latch: snapshot current count for next read. */
c->latched = c->count;
c->have_latched = true;
return;
}
/* Coerce unsupported modes to 0. */
if (mode != 0 && mode != 2 && mode != 3) mode = 0;
c->mode = (uint8_t)mode;
c->armed = false;
c->write_high_next = false;
c->read_high_next = false;
/* OUT goes low after a control word for Mode 0; high for 2 and 3. */
drive_out(sel, mode != 0);
}
/* Counter byte write. */
static void counter_write(int idx, uint8_t byte) {
channel_t* c = &G.ch[idx];
switch (c->rw_mode) {
case 1: /* LSB */
c->reload = (c->reload & 0xFF00) | byte;
c->count = c->reload;
c->armed = true;
break;
case 2: /* MSB */
c->reload = (uint16_t)((c->reload & 0x00FF) | ((uint16_t)byte << 8));
c->count = c->reload;
c->armed = true;
break;
case 3: /* LSB then MSB */
if (!c->write_high_next) {
c->reload = (c->reload & 0xFF00) | byte;
c->write_high_next = true;
/* Counter is "disarmed" between LSB and MSB writes. */
c->armed = false;
} else {
c->reload = (uint16_t)((c->reload & 0x00FF) | ((uint16_t)byte << 8));
c->count = c->reload;
c->armed = true;
c->write_high_next = false;
}
break;
}
}
static uint8_t counter_read(int idx) {
channel_t* c = &G.ch[idx];
uint16_t value = c->have_latched ? c->latched : c->count;
switch (c->rw_mode) {
case 1: /* LSB */
c->have_latched = false;
return (uint8_t)(value & 0xFF);
case 2: /* MSB */
c->have_latched = false;
return (uint8_t)(value >> 8);
case 3: /* LSB then MSB */
if (!c->read_high_next) {
c->read_high_next = true;
return (uint8_t)(value & 0xFF);
} else {
c->read_high_next = false;
c->have_latched = false;
return (uint8_t)(value >> 8);
}
default:
c->have_latched = false;
return (uint8_t)(value & 0xFF);
}
}
/* ─── CLK rising-edge per channel: count down. ──────────────────────────── */
static void on_clk(void* user_data, vx_pin pin, int value) {
int idx = (int)(intptr_t)user_data;
(void)pin;
if (value != 1) return;
if (vx_pin_read(G.gate[idx]) == 0) return;
channel_t* c = &G.ch[idx];
if (!c->armed) return;
switch (c->mode) {
case 0: /* interrupt on terminal count */
if (c->count > 0) c->count--;
if (c->count == 0) {
drive_out(idx, true);
/* Stay at 0 until new count is loaded (count is 0xFFFF
on next CLK; we just leave at 0). */
c->armed = false;
}
break;
case 2: /* rate generator */
if (c->count > 0) c->count--;
if (c->count == 1) {
drive_out(idx, false);
} else if (c->count == 0) {
drive_out(idx, true);
c->count = c->reload;
}
break;
case 3: { /* square wave — decrement by 2 each CLK */
if (c->count >= 2) c->count -= 2;
else c->count = 0;
if (c->count == 0) {
drive_out(idx, !c->out_state);
c->count = c->reload;
}
break;
}
}
}
/* ─── RD / WR strobes ───────────────────────────────────────────────────── */
static void on_rd(void* user_data, vx_pin pin, int value) {
(void)user_data; (void)pin;
if (vx_pin_read(G.cs) != 0) { release_d(); return; }
if (value != 0) { release_d(); return; }
int sel = (vx_pin_read(G.a1) ? 2 : 0) | (vx_pin_read(G.a0) ? 1 : 0);
if (sel == 3) { drive_d(0); return; } /* control reg reads back undefined */
drive_d(counter_read(sel));
}
static void on_wr(void* user_data, vx_pin pin, int value) {
(void)user_data; (void)pin;
if (vx_pin_read(G.cs) != 0) { G.wr_last = value; return; }
if (G.wr_last == 0 && value == 1) {
int sel = (vx_pin_read(G.a1) ? 2 : 0) | (vx_pin_read(G.a0) ? 1 : 0);
uint8_t byte = read_d_byte();
if (sel == 3) apply_control(byte);
else counter_write(sel, byte);
}
G.wr_last = value;
}
void chip_setup(void) {
char name[6];
for (int i = 0; i < 8; i++) {
name[0]='D'; name[1]='0'+i; name[2]=0;
G.d[i] = vx_pin_register(name, VX_INPUT);
}
G.a0 = vx_pin_register("A0", VX_INPUT);
G.a1 = vx_pin_register("A1", VX_INPUT);
G.cs = vx_pin_register("CS", VX_INPUT);
G.rd = vx_pin_register("RD", VX_INPUT);
G.wr = vx_pin_register("WR", VX_INPUT);
for (int i = 0; i < 3; i++) {
name[0]='C'; name[1]='L'; name[2]='K'; name[3]='0'+i; name[4]=0;
G.clk[i] = vx_pin_register(name, VX_INPUT);
name[0]='G'; name[1]='A'; name[2]='T'; name[3]='E'; name[4]='0'+i; name[5]=0;
G.gate[i] = vx_pin_register(name, VX_INPUT);
name[0]='O'; name[1]='U'; name[2]='T'; name[3]='0'+i; name[4]=0;
G.out[i] = vx_pin_register(name, VX_OUTPUT_LOW);
}
G.vcc = vx_pin_register("VCC", VX_INPUT);
G.gnd = vx_pin_register("GND", VX_INPUT);
for (int i = 0; i < 3; i++) {
G.ch[i] = (channel_t){0};
drive_out(i, false);
}
G.driving_d = false;
G.wr_last = 1;
vx_pin_watch(G.rd, VX_EDGE_BOTH, on_rd, 0);
vx_pin_watch(G.wr, VX_EDGE_BOTH, on_wr, 0);
for (int i = 0; i < 3; i++) {
vx_pin_watch(G.clk[i], VX_EDGE_RISING, on_clk, (void*)(intptr_t)i);
}
}