]> 4ch.mooo.com Git - 16.git/blob - src/lib/doslib/hw/8254/8254.h
added a bunch of things~ and midi stuff~
[16.git] / src / lib / doslib / hw / 8254 / 8254.h
1 /* 8254.h
2  *
3  * 8254 programmable interrupt timer control library.
4  * (C) 2008-2012 Jonathan Campbell.
5  * Hackipedia DOS library.
6  *
7  * This code is licensed under the LGPL.
8  * <insert LGPL legal text here>
9  *
10  * Compiles for intended target environments:
11  *   - MS-DOS [pure DOS mode, or Windows or OS/2 DOS Box] */
12  
13 #ifndef __HW_8254_8254_H
14 #define __HW_8254_8254_H
15
16 #include <hw/cpu/cpu.h>
17 #include <stdint.h>
18
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.
25  *
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) */
29
30 #define PC_SPEAKER_GATE                                         0x61
31
32 /* 1.19318MHz from which the counter values divide down from */
33 #define T8254_REF_CLOCK_HZ                                      1193180
34
35 #define T8254_IRQ                                               0
36
37 #define T8254_PORT(x)                                           ((x) + 0x40)
38 #define T8254_TIMER_PORT(x)                                     T8254_PORT(x)
39 #define T8254_CONTROL_PORT                                      T8254_PORT(3)
40
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
47
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
54
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;
61
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 {
68         unsigned char           status;
69         t8254_time_t            count;
70 };
71
72 struct t8254_readback_t {
73         struct t8254_readback_entry_t   timer[3];
74 };
75
76 extern uint32_t t8254_counter[3];
77 extern int8_t probed_8254_result;
78
79 int probe_8254();
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);
84
85 static inline t8254_time_t read_8254_ncli(unsigned char timer) {
86         t8254_time_t x;
87
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;
92         return x;
93 }
94
95 static inline t8254_time_t read_8254(unsigned char timer) {
96         unsigned int flags;
97         t8254_time_t x;
98
99         flags = get_cpu_flags();
100         x = read_8254_ncli(timer);
101         _sti_if_flags(flags);
102         return x;
103 }
104
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);
115 }
116
117 static inline void write_8254(unsigned char timer,t8254_time_t count,unsigned char mode) {
118         unsigned int flags;
119
120         flags = get_cpu_flags();
121         write_8254_ncli(timer,count,mode);
122         _sti_if_flags(flags);
123 }
124
125 static inline unsigned char t8254_pc_speaker_read_gate() {
126         return inp(PC_SPEAKER_GATE) & 3;
127 }
128
129 static inline void t8254_pc_speaker_set_gate(unsigned char m) {
130         unsigned char x;
131
132         x = inp(PC_SPEAKER_GATE);
133         x = (x & ~0x3) | (m & 3);
134         outp(PC_SPEAKER_GATE,x);
135 }
136
137 static inline void write_8254_system_timer(t8254_time_t max) {
138         write_8254(0,max,T8254_MODE_2_RATE_GENERATOR);
139 }
140
141 static inline void write_8254_pc_speaker(t8254_time_t max) {
142         write_8254(2,max,T8254_MODE_3_SQUARE_WAVE_MODE);
143 }
144
145 #endif /* __HW_8254_8254_H */
146