[NTVDM]
[reactos.git] / subsystems / ntvdm / cmos.c
1 /*
2 * COPYRIGHT: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
4 * FILE: cmos.c
5 * PURPOSE: CMOS Real Time Clock emulation
6 * PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
7 */
8
9 /* INCLUDES *******************************************************************/
10
11 #define NDEBUG
12
13 #include "cmos.h"
14 #include "pic.h"
15
16 /* PRIVATE VARIABLES **********************************************************/
17
18 static BOOLEAN NmiEnabled = TRUE;
19 static BYTE StatusRegA = CMOS_DEFAULT_STA;
20 static BYTE StatusRegB = CMOS_DEFAULT_STB;
21 static BYTE StatusRegC = 0;
22 static BYTE AlarmHour, AlarmMinute, AlarmSecond;
23 static CMOS_REGISTERS SelectedRegister = CMOS_REG_STATUS_D;
24
25 /* PUBLIC FUNCTIONS ***********************************************************/
26
27 BOOLEAN IsNmiEnabled(VOID)
28 {
29 return NmiEnabled;
30 }
31
32 VOID CmosWriteAddress(BYTE Value)
33 {
34 /* Update the NMI enabled flag */
35 NmiEnabled = !(Value & CMOS_DISABLE_NMI);
36
37 /* Get the register number */
38 Value &= ~CMOS_DISABLE_NMI;
39
40 if (Value < CMOS_REG_MAX)
41 {
42 /* Select the new register */
43 SelectedRegister = Value;
44 }
45 else
46 {
47 /* Default to Status Register D */
48 SelectedRegister = CMOS_REG_STATUS_D;
49 }
50 }
51
52 BYTE CmosReadData(VOID)
53 {
54 SYSTEMTIME CurrentTime;
55
56 /* Get the current time */
57 GetLocalTime(&CurrentTime);
58
59 switch (SelectedRegister)
60 {
61 case CMOS_REG_SECONDS:
62 {
63 return (StatusRegB & CMOS_STB_BINARY)
64 ? CurrentTime.wSecond
65 : BINARY_TO_BCD(CurrentTime.wSecond);
66 }
67
68 case CMOS_REG_ALARM_SEC:
69 {
70 return (StatusRegB & CMOS_STB_BINARY)
71 ? AlarmSecond
72 : BINARY_TO_BCD(AlarmSecond);
73 }
74
75 case CMOS_REG_MINUTES:
76 {
77 return (StatusRegB & CMOS_STB_BINARY)
78 ? CurrentTime.wMinute
79 : BINARY_TO_BCD(CurrentTime.wMinute);
80 }
81
82 case CMOS_REG_ALARM_MIN:
83 {
84 return (StatusRegB & CMOS_STB_BINARY)
85 ? AlarmMinute
86 : BINARY_TO_BCD(AlarmMinute);
87 }
88
89 case CMOS_REG_HOURS:
90 {
91 BOOLEAN Afternoon = FALSE;
92 BYTE Value = CurrentTime.wHour;
93
94 if (!(StatusRegB & CMOS_STB_24HOUR) && (Value >= 12))
95 {
96 Value -= 12;
97 Afternoon = TRUE;
98 }
99
100 if (!(StatusRegB & CMOS_STB_BINARY)) Value = BINARY_TO_BCD(Value);
101
102 /* Convert to 12-hour */
103 if (Afternoon) Value |= 0x80;
104
105 return Value;
106 }
107
108 case CMOS_REG_ALARM_HRS:
109 {
110 BOOLEAN Afternoon = FALSE;
111 BYTE Value = AlarmHour;
112
113 if (!(StatusRegB & CMOS_STB_24HOUR) && (Value >= 12))
114 {
115 Value -= 12;
116 Afternoon = TRUE;
117 }
118
119 if (!(StatusRegB & CMOS_STB_BINARY)) Value = BINARY_TO_BCD(Value);
120
121 /* Convert to 12-hour */
122 if (Afternoon) Value |= 0x80;
123
124 return Value;
125 }
126
127 case CMOS_REG_DAY_OF_WEEK:
128 {
129 return (StatusRegB & CMOS_STB_BINARY)
130 ? CurrentTime.wDayOfWeek
131 : BINARY_TO_BCD(CurrentTime.wDayOfWeek);
132 }
133
134 case CMOS_REG_DAY:
135 {
136 return (StatusRegB & CMOS_STB_BINARY)
137 ? CurrentTime.wDay
138 :BINARY_TO_BCD(CurrentTime.wDay);
139 }
140
141 case CMOS_REG_MONTH:
142 {
143 return (StatusRegB & CMOS_STB_BINARY)
144 ? CurrentTime.wMonth
145 : BINARY_TO_BCD(CurrentTime.wMonth);
146 }
147
148 case CMOS_REG_YEAR:
149 {
150 return (StatusRegB & CMOS_STB_BINARY)
151 ? (CurrentTime.wYear % 100)
152 : BINARY_TO_BCD(CurrentTime.wYear % 100);
153 }
154
155 case CMOS_REG_STATUS_A:
156 {
157 return StatusRegA;
158 }
159
160 case CMOS_REG_STATUS_B:
161 {
162 return StatusRegB;
163 }
164
165 case CMOS_REG_STATUS_C:
166 {
167 BYTE Value = StatusRegC;
168
169 /* Clear status register C */
170 StatusRegC = 0;
171
172 /* Return the old value */
173 return Value;
174 }
175
176 case CMOS_REG_STATUS_D:
177 {
178 /* Our CMOS battery works perfectly forever */
179 return CMOS_BATTERY_OK;
180 }
181
182 case CMOS_REG_DIAGNOSTICS:
183 {
184 /* Diagnostics found no errors */
185 return 0;
186 }
187
188 default:
189 {
190 /* Read ignored */
191 return 0;
192 }
193 }
194
195 /* Return to Status Register D */
196 SelectedRegister = CMOS_REG_STATUS_D;
197 }
198
199 VOID CmosWriteData(BYTE Value)
200 {
201 BOOLEAN ChangeTime = FALSE;
202 SYSTEMTIME CurrentTime;
203
204 /* Get the current time */
205 GetLocalTime(&CurrentTime);
206
207 switch (SelectedRegister)
208 {
209 case CMOS_REG_SECONDS:
210 {
211 ChangeTime = TRUE;
212 CurrentTime.wSecond = (StatusRegB & CMOS_STB_BINARY)
213 ? Value
214 : BCD_TO_BINARY(Value);
215
216 break;
217 }
218
219 case CMOS_REG_ALARM_SEC:
220 {
221 AlarmSecond = (StatusRegB & CMOS_STB_BINARY)
222 ? Value
223 : BCD_TO_BINARY(Value);
224
225 break;
226 }
227
228 case CMOS_REG_MINUTES:
229 {
230 ChangeTime = TRUE;
231 CurrentTime.wMinute = (StatusRegB & CMOS_STB_BINARY)
232 ? Value
233 : BCD_TO_BINARY(Value);
234
235 break;
236 }
237
238 case CMOS_REG_ALARM_MIN:
239 {
240 AlarmMinute = (StatusRegB & CMOS_STB_BINARY)
241 ? Value
242 : BCD_TO_BINARY(Value);
243
244 break;
245 }
246
247 case CMOS_REG_HOURS:
248 {
249 BOOLEAN Afternoon = FALSE;
250
251 ChangeTime = TRUE;
252
253 if (!(StatusRegB & CMOS_STB_24HOUR) && (Value & 0x80))
254 {
255 Value &= ~0x80;
256 Afternoon = TRUE;
257 }
258
259 CurrentTime.wHour = (StatusRegB & CMOS_STB_BINARY)
260 ? Value
261 : BCD_TO_BINARY(Value);
262
263 /* Convert to 24-hour format */
264 if (Afternoon) CurrentTime.wHour += 12;
265
266 break;
267 }
268
269 case CMOS_REG_ALARM_HRS:
270 {
271 BOOLEAN Afternoon = FALSE;
272
273 if (!(StatusRegB & CMOS_STB_24HOUR) && (Value & 0x80))
274 {
275 Value &= ~0x80;
276 Afternoon = TRUE;
277 }
278
279 AlarmHour = (StatusRegB & CMOS_STB_BINARY)
280 ? Value
281 : BCD_TO_BINARY(Value);
282
283 /* Convert to 24-hour format */
284 if (Afternoon) AlarmHour += 12;
285
286 break;
287 }
288
289 case CMOS_REG_DAY_OF_WEEK:
290 {
291 ChangeTime = TRUE;
292 CurrentTime.wDayOfWeek = (StatusRegB & CMOS_STB_BINARY)
293 ? Value
294 : BCD_TO_BINARY(Value);
295
296 break;
297 }
298
299 case CMOS_REG_DAY:
300 {
301 ChangeTime = TRUE;
302 CurrentTime.wDay = (StatusRegB & CMOS_STB_BINARY)
303 ? Value
304 : BCD_TO_BINARY(Value);
305
306 break;
307 }
308
309 case CMOS_REG_MONTH:
310 {
311 ChangeTime = TRUE;
312 CurrentTime.wMonth = (StatusRegB & CMOS_STB_BINARY)
313 ? Value
314 : BCD_TO_BINARY(Value);
315
316 break;
317 }
318
319 case CMOS_REG_YEAR:
320 {
321 ChangeTime = TRUE;
322
323 /* Clear everything except the century */
324 CurrentTime.wYear = (CurrentTime.wYear / 100) * 100;
325
326 CurrentTime.wYear += (StatusRegB & CMOS_STB_BINARY)
327 ? Value
328 : BCD_TO_BINARY(Value);
329
330 break;
331 }
332
333 case CMOS_REG_STATUS_A:
334 {
335 StatusRegA = Value;
336 break;
337 }
338
339 case CMOS_REG_STATUS_B:
340 {
341 StatusRegB = Value;
342 break;
343 }
344
345 default:
346 {
347 /* Write ignored */
348 }
349 }
350
351 if (ChangeTime) SetLocalTime(&CurrentTime);
352
353 /* Return to Status Register D */
354 SelectedRegister = CMOS_REG_STATUS_D;
355 }
356
357 DWORD RtcGetTicksPerSecond(VOID)
358 {
359 BYTE RateSelect = StatusRegB & 0x0F;
360
361 if (RateSelect == 0)
362 {
363 /* No periodic interrupt */
364 return 0;
365 }
366
367 /* 1 and 2 act like 8 and 9 */
368 if (RateSelect <= 2) RateSelect += 7;
369
370 return 1 << (16 - RateSelect);
371 }
372
373 VOID RtcPeriodicTick(VOID)
374 {
375 /* Set PF */
376 StatusRegC |= CMOS_STC_PF;
377
378 /* Check if there should be an interrupt on a periodic timer tick */
379 if (StatusRegB & CMOS_STB_INT_PERIODIC)
380 {
381 StatusRegC |= CMOS_STC_IRQF;
382
383 /* Interrupt! */
384 PicInterruptRequest(RTC_IRQ_NUMBER);
385 }
386 }
387
388 /* Should be called every second */
389 VOID RtcTimeUpdate(VOID)
390 {
391 SYSTEMTIME CurrentTime;
392
393 /* Get the current time */
394 GetLocalTime(&CurrentTime);
395
396 /* Set UF */
397 StatusRegC |= CMOS_STC_UF;
398
399 /* Check if the time matches the alarm time */
400 if ((CurrentTime.wHour == AlarmHour)
401 && (CurrentTime.wMinute == AlarmMinute)
402 && (CurrentTime.wSecond == AlarmSecond))
403 {
404 /* Set the alarm flag */
405 StatusRegC |= CMOS_STC_AF;
406
407 /* Set IRQF if there should be an interrupt */
408 if (StatusRegB & CMOS_STB_INT_ON_ALARM) StatusRegC |= CMOS_STC_IRQF;
409 }
410
411 /* Check if there should be an interrupt on update */
412 if (StatusRegB & CMOS_STB_INT_ON_UPDATE) StatusRegC |= CMOS_STC_IRQF;
413
414 if (StatusRegC & CMOS_STC_IRQF)
415 {
416 /* Interrupt! */
417 PicInterruptRequest(RTC_IRQ_NUMBER);
418 }
419 }