Version 0.1.2
[seabios.git] / src / clock.c
1 // 16bit code to handle system clocks.
2 //
3 // Copyright (C) 2008  Kevin O'Connor <kevin@koconnor.net>
4 // Copyright (C) 2002  MandrakeSoft S.A.
5 //
6 // This file may be distributed under the terms of the GNU GPLv3 license.
7
8 #include "biosvar.h" // struct bregs
9 #include "util.h" // debug_enter
10 #include "disk.h" // floppy_tick
11 #include "cmos.h" // inb_cmos
12
13 static void
14 init_rtc()
15 {
16     outb_cmos(0x26, CMOS_STATUS_A);
17     outb_cmos(0x02, CMOS_STATUS_B);
18     inb_cmos(CMOS_STATUS_C);
19     inb_cmos(CMOS_STATUS_D);
20 }
21
22 static u8
23 rtc_updating()
24 {
25     // This function checks to see if the update-in-progress bit
26     // is set in CMOS Status Register A.  If not, it returns 0.
27     // If it is set, it tries to wait until there is a transition
28     // to 0, and will return 0 if such a transition occurs.  A 1
29     // is returned only after timing out.  The maximum period
30     // that this bit should be set is constrained to 244useconds.
31     // The count I use below guarantees coverage or more than
32     // this time, with any reasonable IPS setting.
33
34     u16 count = 25000;
35     while (--count != 0) {
36         if ( (inb_cmos(CMOS_STATUS_A) & 0x80) == 0 )
37             return 0;
38     }
39     return 1; // update-in-progress never transitioned to 0
40 }
41
42 // get current clock count
43 static void
44 handle_1a00(struct bregs *regs)
45 {
46     u32 ticks = GET_BDA(timer_counter);
47     regs->cx = ticks >> 16;
48     regs->dx = ticks;
49     regs->al = GET_BDA(timer_rollover);
50     SET_BDA(timer_rollover, 0); // reset flag
51     set_cf(regs, 0);
52 }
53
54 // Set Current Clock Count
55 static void
56 handle_1a01(struct bregs *regs)
57 {
58     u32 ticks = (regs->cx << 16) | regs->dx;
59     SET_BDA(timer_counter, ticks);
60     SET_BDA(timer_rollover, 0); // reset flag
61     regs->ah = 0;
62     set_cf(regs, 0);
63 }
64
65 // Read CMOS Time
66 static void
67 handle_1a02(struct bregs *regs)
68 {
69     if (rtc_updating()) {
70         set_cf(regs, 1);
71         return;
72     }
73
74     regs->dh = inb_cmos(CMOS_RTC_SECONDS);
75     regs->cl = inb_cmos(CMOS_RTC_MINUTES);
76     regs->ch = inb_cmos(CMOS_RTC_HOURS);
77     regs->dl = inb_cmos(CMOS_STATUS_B) & 0x01;
78     regs->ah = 0;
79     regs->al = regs->ch;
80     set_cf(regs, 0);
81 }
82
83 // Set CMOS Time
84 static void
85 handle_1a03(struct bregs *regs)
86 {
87     // Using a debugger, I notice the following masking/setting
88     // of bits in Status Register B, by setting Reg B to
89     // a few values and getting its value after INT 1A was called.
90     //
91     //        try#1       try#2       try#3
92     // before 1111 1101   0111 1101   0000 0000
93     // after  0110 0010   0110 0010   0000 0010
94     //
95     // Bit4 in try#1 flipped in hardware (forced low) due to bit7=1
96     // My assumption: RegB = ((RegB & 01100000b) | 00000010b)
97     if (rtc_updating()) {
98         init_rtc();
99         // fall through as if an update were not in progress
100     }
101     outb_cmos(regs->dh, CMOS_RTC_SECONDS);
102     outb_cmos(regs->cl, CMOS_RTC_MINUTES);
103     outb_cmos(regs->ch, CMOS_RTC_HOURS);
104     // Set Daylight Savings time enabled bit to requested value
105     u8 val8 = (inb_cmos(CMOS_STATUS_B) & 0x60) | 0x02 | (regs->dl & 0x01);
106     outb_cmos(val8, CMOS_STATUS_B);
107     regs->ah = 0;
108     regs->al = val8; // val last written to Reg B
109     set_cf(regs, 0);
110 }
111
112 // Read CMOS Date
113 static void
114 handle_1a04(struct bregs *regs)
115 {
116     regs->ah = 0;
117     if (rtc_updating()) {
118         set_cf(regs, 1);
119         return;
120     }
121     regs->cl = inb_cmos(CMOS_RTC_YEAR);
122     regs->dh = inb_cmos(CMOS_RTC_MONTH);
123     regs->dl = inb_cmos(CMOS_RTC_DAY_MONTH);
124     regs->ch = inb_cmos(CMOS_CENTURY);
125     regs->al = regs->ch;
126     set_cf(regs, 0);
127 }
128
129 // Set CMOS Date
130 static void
131 handle_1a05(struct bregs *regs)
132 {
133     // Using a debugger, I notice the following masking/setting
134     // of bits in Status Register B, by setting Reg B to
135     // a few values and getting its value after INT 1A was called.
136     //
137     //        try#1       try#2       try#3       try#4
138     // before 1111 1101   0111 1101   0000 0010   0000 0000
139     // after  0110 1101   0111 1101   0000 0010   0000 0000
140     //
141     // Bit4 in try#1 flipped in hardware (forced low) due to bit7=1
142     // My assumption: RegB = (RegB & 01111111b)
143     if (rtc_updating()) {
144         init_rtc();
145         set_cf(regs, 1);
146         return;
147     }
148     outb_cmos(regs->cl, CMOS_RTC_YEAR);
149     outb_cmos(regs->dh, CMOS_RTC_MONTH);
150     outb_cmos(regs->dl, CMOS_RTC_DAY_MONTH);
151     outb_cmos(regs->ch, CMOS_CENTURY);
152     u8 val8 = inb_cmos(CMOS_STATUS_B) & 0x7f; // clear halt-clock bit
153     outb_cmos(val8, CMOS_STATUS_B);
154     regs->ah = 0;
155     regs->al = val8; // AL = val last written to Reg B
156     set_cf(regs, 0);
157 }
158
159 // Set Alarm Time in CMOS
160 static void
161 handle_1a06(struct bregs *regs)
162 {
163     // Using a debugger, I notice the following masking/setting
164     // of bits in Status Register B, by setting Reg B to
165     // a few values and getting its value after INT 1A was called.
166     //
167     //        try#1       try#2       try#3
168     // before 1101 1111   0101 1111   0000 0000
169     // after  0110 1111   0111 1111   0010 0000
170     //
171     // Bit4 in try#1 flipped in hardware (forced low) due to bit7=1
172     // My assumption: RegB = ((RegB & 01111111b) | 00100000b)
173     u8 val8 = inb_cmos(CMOS_STATUS_B); // Get Status Reg B
174     regs->ax = 0;
175     if (val8 & 0x20) {
176         // Alarm interrupt enabled already
177         set_cf(regs, 1);
178         return;
179     }
180     if (rtc_updating()) {
181         init_rtc();
182         // fall through as if an update were not in progress
183     }
184     outb_cmos(regs->dh, CMOS_RTC_SECONDS_ALARM);
185     outb_cmos(regs->cl, CMOS_RTC_MINUTES_ALARM);
186     outb_cmos(regs->ch, CMOS_RTC_HOURS_ALARM);
187     outb(inb(PORT_PIC2_DATA) & ~PIC2_IRQ8, PORT_PIC2_DATA); // enable IRQ 8
188     // enable Status Reg B alarm bit, clear halt clock bit
189     outb_cmos((val8 & 0x7f) | 0x20, CMOS_STATUS_B);
190     set_cf(regs, 0);
191 }
192
193 // Turn off Alarm
194 static void
195 handle_1a07(struct bregs *regs)
196 {
197     // Using a debugger, I notice the following masking/setting
198     // of bits in Status Register B, by setting Reg B to
199     // a few values and getting its value after INT 1A was called.
200     //
201     //        try#1       try#2       try#3       try#4
202     // before 1111 1101   0111 1101   0010 0000   0010 0010
203     // after  0100 0101   0101 0101   0000 0000   0000 0010
204     //
205     // Bit4 in try#1 flipped in hardware (forced low) due to bit7=1
206     // My assumption: RegB = (RegB & 01010111b)
207     u8 val8 = inb_cmos(CMOS_STATUS_B); // Get Status Reg B
208     // clear clock-halt bit, disable alarm bit
209     outb_cmos(val8 & 0x57, CMOS_STATUS_B); // disable alarm bit
210     regs->ah = 0;
211     regs->al = val8; // val last written to Reg B
212     set_cf(regs, 0);
213 }
214
215 static void
216 handle_1ab1(struct bregs *regs)
217 {
218     // XXX - pcibios stuff
219     set_cf(regs, 1);
220 }
221
222 // Unsupported
223 static void
224 handle_1aXX(struct bregs *regs)
225 {
226     set_cf(regs, 1);
227 }
228
229 // INT 1Ah Time-of-day Service Entry Point
230 void VISIBLE
231 handle_1a(struct bregs *regs)
232 {
233     //debug_enter(regs);
234     switch (regs->ah) {
235     case 0x00: handle_1a00(regs); break;
236     case 0x01: handle_1a01(regs); break;
237     case 0x02: handle_1a02(regs); break;
238     case 0x03: handle_1a03(regs); break;
239     case 0x04: handle_1a04(regs); break;
240     case 0x05: handle_1a05(regs); break;
241     case 0x06: handle_1a06(regs); break;
242     case 0x07: handle_1a07(regs); break;
243     case 0xb1: handle_1ab1(regs); break;
244     default:   handle_1aXX(regs); break;
245     }
246     debug_exit(regs);
247 }
248
249 // User Timer Tick
250 void VISIBLE
251 handle_1c(struct bregs *regs)
252 {
253     //debug_enter(regs);
254 }
255
256 // INT 08h System Timer ISR Entry Point
257 void VISIBLE
258 handle_08(struct bregs *regs)
259 {
260 //    debug_enter(regs);
261
262     floppy_tick();
263
264     u32 counter = GET_BDA(timer_counter);
265     counter++;
266     // compare to one days worth of timer ticks at 18.2 hz
267     if (counter >= 0x001800B0) {
268         // there has been a midnight rollover at this point
269         counter = 0;
270         SET_BDA(timer_rollover, GET_BDA(timer_rollover) + 1);
271     }
272
273     SET_BDA(timer_counter, counter);
274
275     // chain to user timer tick INT #0x1c
276     struct bregs br;
277     memset(&br, 0, sizeof(br));
278     call16_int(0x1c, &br);
279
280     eoi_master_pic();
281 }
282
283 // int70h: IRQ8 - CMOS RTC
284 void VISIBLE
285 handle_70(struct bregs *regs)
286 {
287     debug_enter(regs);
288
289     // Check which modes are enabled and have occurred.
290     u8 registerB = inb_cmos(CMOS_STATUS_B);
291     u8 registerC = inb_cmos(CMOS_STATUS_C);
292
293     if (!(registerB & 0x60))
294         goto done;
295     if (registerC & 0x20) {
296         // Handle Alarm Interrupt.
297         struct bregs br;
298         memset(&br, 0, sizeof(br));
299         call16_int(0x4a, &br);
300     }
301     if (!(registerC & 0x40))
302         goto done;
303
304     // Handle Periodic Interrupt.
305
306     if (!GET_BDA(rtc_wait_flag))
307         goto done;
308
309     // Wait Interval (Int 15, AH=83) active.
310     u32 time = GET_BDA(user_wait_timeout);  // Time left in microseconds.
311     if (time < 0x3D1) {
312         // Done waiting.
313         u32 segoff = GET_BDA(ptr_user_wait_complete_flag);
314         u16 segment = segoff >> 16;
315         u16 offset = segoff & 0xffff;
316         // Turn off status byte.
317         SET_BDA(rtc_wait_flag, 0);
318         // Clear the Periodic Interrupt.
319         outb_cmos(registerB & 0x37, CMOS_STATUS_B);
320         // Write to specified flag byte.
321         u8 oldval = GET_FARVAR(segment, *(u8*)(offset+0));
322         SET_FARVAR(segment, *(u8*)(offset+0), oldval | 0x80);
323     } else {
324         // Continue waiting.
325         time -= 0x3D1;
326         SET_BDA(user_wait_timeout, time);
327     }
328
329 done:
330     eoi_both_pics();
331 }