3 * 8254 programmable interrupt timer control library.
4 * (C) 2008-2012 Jonathan Campbell.
5 * Hackipedia DOS library.
7 * This code is licensed under the LGPL.
8 * <insert LGPL legal text here>
10 * Compiles for intended target environments:
11 * - MS-DOS [pure DOS mode, or Windows or OS/2 DOS Box] */
13 #ifndef __HW_8254_8254_H
14 #define __HW_8254_8254_H
16 #include "src/lib/doslib/cpu.h"
19 /* WARNING: When calling these functions it is recommended that you disable interrupts
20 * during the programming procedure. In MS-DOS mode there is nothing to stop
21 * the BIOS from trying to use the 8254 chip at the same time you are. It is
22 * entirely possible that it might read values back during an interrupt. If
23 * you do not watch for that, you will get erroneous results from these
24 * routines. Use hw/cpu.h _cli() and _sti() functions.
26 * In contrast, it is extremely rare for interrupt handlers to mess with the
27 * PC speaker gate port (and even if they do, the worst that can happen is
28 * our code overrides what the IRQ is trying to do) */
30 #define PC_SPEAKER_GATE 0x61
32 /* 1.19318MHz from which the counter values divide down from */
33 #define T8254_REF_CLOCK_HZ 1193180
37 #define T8254_PORT(x) ((x) + 0x40)
38 #define T8254_TIMER_PORT(x) T8254_PORT(x)
39 #define T8254_CONTROL_PORT T8254_PORT(3)
41 #define T8254_MODE_0_INT_ON_TERMINAL_COUNT 0
42 #define T8254_MODE_1_HARDWARE_RETRIGGERABLE_ONE_SHOT 1
43 #define T8254_MODE_2_RATE_GENERATOR 2
44 #define T8254_MODE_3_SQUARE_WAVE_MODE 3
45 #define T8254_MODE_4_SOFTWARE_TRIGGERED_STROBE 4
46 #define T8254_MODE_5_HARDWARE_TRIGGERED_STROBE 5
48 #define T8254_READBACK_COUNT 0x20
49 #define T8254_READBACK_STATUS 0x10
50 #define T8254_READBACK_TIMER_2 0x08
51 #define T8254_READBACK_TIMER_1 0x04
52 #define T8254_READBACK_TIMER_0 0x02
53 #define T8254_READBACK_ALL 0x3E
55 /* this represents one counter value in the 8254/8253 chipset library, including the
56 * value the chip reloads on finishing countdown.
57 * NTS: In 8254 hardware, a value of 0 is treated as 65536 because the chip decrements
58 * THEN checks against zero. This allows the rate to drop as low as
59 * 1193180 / 65536 = 18.20648Hz on PC hardware */
60 typedef uint16_t t8254_time_t;
62 /* the 8254 (NOT 8253!) has a command that allows us to read the status and counter
63 * value of one or more latches (though contemporary hardware fails to emulate multi-
64 * counter latching from one command!). The status allows us to know what the counter
65 * was programmed as, the output of the counter at the time, and whether a new counter
66 * value was being loaded. The t8254_readback_t structure is used to read this info */
67 struct t8254_readback_entry_t {
72 struct t8254_readback_t {
73 struct t8254_readback_entry_t timer[3];
76 extern uint32_t t8254_counter[3];
77 extern int8_t probed_8254_result;
80 void readback_8254(unsigned char what,struct t8254_readback_t *t); /* WARNING: 8254 only, will not work on 8253 chips in original PC/XT hardware */
81 unsigned long t8254_us2ticks(unsigned long a);
82 unsigned long t8254_us2ticksr(unsigned long a,unsigned long *rem);
83 void t8254_wait(unsigned long ticks);
85 static inline t8254_time_t read_8254_ncli(unsigned char timer) {
88 if (timer > 2) return 0;
89 outp(T8254_CONTROL_PORT,(timer << 6) | (0 << 4) | 0); /* latch counter N, counter latch read */
90 x = (t8254_time_t)inp(T8254_TIMER_PORT(timer));
91 x |= (t8254_time_t)inp(T8254_TIMER_PORT(timer)) << 8U;
95 static inline t8254_time_t read_8254(unsigned char timer) {
99 flags = get_cpu_flags();
100 x = read_8254_ncli(timer);
101 _sti_if_flags(flags);
105 /* NTS: At the hardware level, count == 0 is equivalent to programming 0x10000 into it.
106 * t8254_time_t is a 16-bit integer, and we write 16 bits, so 0 and 0x10000 is
107 * the same thing to us anyway */
108 static inline void write_8254_ncli(unsigned char timer,t8254_time_t count,unsigned char mode) {
109 if (timer > 2) return;
110 outp(T8254_CONTROL_PORT,(timer << 6) | (3 << 4) | (mode << 1)); /* set new time */
111 outp(T8254_TIMER_PORT(timer),count);
112 outp(T8254_TIMER_PORT(timer),count >> 8);
113 /* for our own timing code, keep track of what that count was. we can't read it back from H/W anyway */
114 t8254_counter[timer] = (count == 0 ? 0x10000 : count);
117 static inline void write_8254(unsigned char timer,t8254_time_t count,unsigned char mode) {
120 flags = get_cpu_flags();
121 write_8254_ncli(timer,count,mode);
122 _sti_if_flags(flags);
125 static inline unsigned char t8254_pc_speaker_read_gate() {
126 return inp(PC_SPEAKER_GATE) & 3;
129 static inline void t8254_pc_speaker_set_gate(unsigned char m) {
132 x = inp(PC_SPEAKER_GATE);
133 x = (x & ~0x3) | (m & 3);
134 outp(PC_SPEAKER_GATE,x);
137 static inline void write_8254_system_timer(t8254_time_t max) {
138 write_8254(0,max,T8254_MODE_2_RATE_GENERATOR);
141 static inline void write_8254_pc_speaker(t8254_time_t max) {
142 write_8254(2,max,T8254_MODE_3_SQUARE_WAVE_MODE);
145 #endif /* __HW_8254_8254_H */