xref: /reactos/subsystems/mvdm/ntvdm/hardware/pit.c (revision c2c66aff)
1 /*
2  * COPYRIGHT:       GPL - See COPYING in the top level directory
3  * PROJECT:         ReactOS Virtual DOS Machine
4  * FILE:            subsystems/mvdm/ntvdm/hardware/pit.c
5  * PURPOSE:         Programmable Interval Timer emulation -
6  *                  i82C54/8254 compatible
7  * PROGRAMMERS:     Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
8  *                  Hermes Belusca-Maito (hermes.belusca@sfr.fr)
9  */
10 
11 /* INCLUDES *******************************************************************/
12 
13 #include "ntvdm.h"
14 
15 #define NDEBUG
16 #include <debug.h>
17 
18 #include "emulator.h"
19 #include "pit.h"
20 
21 #include "io.h"
22 #include "pic.h"
23 #include "clock.h"
24 
25 /* PRIVATE VARIABLES **********************************************************/
26 
27 static PIT_CHANNEL PitChannels[PIT_CHANNELS];
28 static PHARDWARE_TIMER MasterClock;
29 
30 /* PRIVATE FUNCTIONS **********************************************************/
31 
PitLatchChannelStatus(BYTE Channel)32 static VOID PitLatchChannelStatus(BYTE Channel)
33 {
34     if (Channel >= PIT_CHANNELS) return;
35 
36     /*
37      * A given counter can be latched only one time until it gets unlatched.
38      * If the counter is latched and then is latched again later before the
39      * value is read, then this last latch command is ignored and the value
40      * will be the value at the time the first command was issued.
41      */
42     if (PitChannels[Channel].LatchStatusSet == FALSE)
43     {
44         BYTE StatusLatch = 0;
45         /** HACK!! **/BYTE NullCount = 0;/** HACK!! **/
46 
47         StatusLatch  =  PitChannels[Channel].Out << 7 | NullCount  << 6;
48         StatusLatch |= (PitChannels[Channel].ReadWriteMode & 0x03) << 4;
49         StatusLatch |= (PitChannels[Channel].Mode & 0x07) << 1;
50         StatusLatch |= (PitChannels[Channel].Bcd  & 0x01);
51 
52         /* Latch the counter's status */
53         PitChannels[Channel].LatchStatusSet = TRUE;
54         PitChannels[Channel].StatusLatch    = StatusLatch;
55     }
56 }
57 
PitLatchChannelCount(BYTE Channel)58 static VOID PitLatchChannelCount(BYTE Channel)
59 {
60     if (Channel >= PIT_CHANNELS) return;
61 
62     /*
63      * A given counter can be latched only one time until it gets unlatched.
64      * If the counter is latched and then is latched again later before the
65      * value is read, then this last latch command is ignored and the value
66      * will be the value at the time the first command was issued.
67      */
68     if (PitChannels[Channel].ReadStatus == 0x00)
69     {
70         /* Latch the counter's value */
71         PitChannels[Channel].ReadStatus  = PitChannels[Channel].ReadWriteMode;
72 
73         /* Convert the current value to BCD if needed */
74         PitChannels[Channel].OutputLatch =
75             READ_PIT_VALUE(PitChannels[Channel], PitChannels[Channel].CurrentValue);
76     }
77 }
78 
PitSetOut(PPIT_CHANNEL Channel,BOOLEAN State)79 static VOID PitSetOut(PPIT_CHANNEL Channel, BOOLEAN State)
80 {
81     /** HACK!! **\ if (State == Channel->Out) return; \** HACK!! **/
82 
83     /* Set the new state of the OUT pin */
84     Channel->Out = State;
85 
86     /* Call the callback */
87     if (!Channel->Gate) return; // HACK: This is a HACK until gates are properly used (needed for the speaker to work properly).
88     if (Channel->OutFunction) Channel->OutFunction(Channel->OutParam, State);
89 }
90 
PitInitCounter(PPIT_CHANNEL Channel)91 static VOID PitInitCounter(PPIT_CHANNEL Channel)
92 {
93     switch (Channel->Mode)
94     {
95         case PIT_MODE_INT_ON_TERMINAL_COUNT:
96             PitSetOut(Channel, FALSE);
97             break;
98 
99         case PIT_MODE_HARDWARE_ONE_SHOT:
100         case PIT_MODE_RATE_GENERATOR:
101         case PIT_MODE_SQUARE_WAVE:
102         case PIT_MODE_SOFTWARE_STROBE:
103         case PIT_MODE_HARDWARE_STROBE:
104             PitSetOut(Channel, TRUE);
105             break;
106     }
107 }
108 
PitWriteCommand(BYTE Value)109 static VOID PitWriteCommand(BYTE Value)
110 {
111     BYTE Channel       = (Value >> 6) & 0x03;
112     BYTE ReadWriteMode = (Value >> 4) & 0x03;
113     BYTE Mode     = (Value >> 1) & 0x07;
114     BOOLEAN IsBcd = Value & 0x01;
115 
116     /*
117      * Check for valid PIT channel - Possible values: 0, 1, 2.
118      * A value of 3 is for Read-Back Command.
119      */
120     if (Channel > PIT_CHANNELS) return;
121 
122     /* Read-Back Command */
123     if (Channel == PIT_CHANNELS)
124     {
125         if ((Value & 0x20) == 0) // Bit 5 (Count) == 0: We latch multiple counters' counts
126         {
127             if (Value & 0x02) PitLatchChannelCount(0);
128             if (Value & 0x04) PitLatchChannelCount(1);
129             if (Value & 0x08) PitLatchChannelCount(2);
130         }
131         if ((Value & 0x10) == 0) // Bit 4 (Status) == 0: We latch multiple counters' statuses
132         {
133             if (Value & 0x02) PitLatchChannelStatus(0);
134             if (Value & 0x04) PitLatchChannelStatus(1);
135             if (Value & 0x08) PitLatchChannelStatus(2);
136         }
137         return;
138     }
139 
140     /* Check if this is a counter latch command... */
141     if (ReadWriteMode == 0)
142     {
143         PitLatchChannelCount(Channel);
144         return;
145     }
146 
147     /* ... otherwise, set the modes and reset flip-flops */
148     PitChannels[Channel].ReadWriteMode = ReadWriteMode;
149     PitChannels[Channel].ReadStatus    = 0x00;
150     PitChannels[Channel].WriteStatus   = 0x00;
151 
152     PitChannels[Channel].LatchStatusSet = FALSE;
153     PitChannels[Channel].StatusLatch    = 0x00;
154 
155     PitChannels[Channel].CountRegister = 0x00;
156     PitChannels[Channel].OutputLatch   = 0x00;
157 
158     /** HACK!! **/PitChannels[Channel].FlipFlop = FALSE;/** HACK!! **/
159 
160     /* Fix the current value if we switch to BCD counting */
161     PitChannels[Channel].Bcd = IsBcd;
162     if (IsBcd && PitChannels[Channel].CurrentValue > 9999)
163         PitChannels[Channel].CurrentValue = 9999;
164 
165     switch (Mode)
166     {
167         case 0:
168         case 1:
169         case 2:
170         case 3:
171         case 4:
172         case 5:
173         {
174             PitChannels[Channel].Mode = Mode;
175             break;
176         }
177 
178         case 6:
179         case 7:
180         {
181             /*
182              * Modes 6 and 7 become PIT_MODE_RATE_GENERATOR
183              * and PIT_MODE_SQUARE_WAVE respectively.
184              */
185             PitChannels[Channel].Mode = Mode - 4;
186             break;
187         }
188     }
189 
190     PitInitCounter(&PitChannels[Channel]);
191 }
192 
PitReadData(BYTE Channel)193 static BYTE PitReadData(BYTE Channel)
194 {
195     LPBYTE ReadWriteMode = NULL;
196     LPWORD CurrentValue  = NULL;
197 
198     /*
199      * If the status was latched, the first read operation will return the
200      * latched status, whichever value (count or status) was latched first.
201      */
202     if (PitChannels[Channel].LatchStatusSet)
203     {
204         PitChannels[Channel].LatchStatusSet = FALSE;
205         return PitChannels[Channel].StatusLatch;
206     }
207 
208     /* To be able to read the count asynchronously, latch it first if needed */
209     if (PitChannels[Channel].ReadStatus == 0) PitLatchChannelCount(Channel);
210 
211     /* The count is now latched */
212     ASSERT(PitChannels[Channel].ReadStatus != 0);
213 
214     ReadWriteMode = &PitChannels[Channel].ReadStatus ;
215     CurrentValue  = &PitChannels[Channel].OutputLatch;
216 
217     if (*ReadWriteMode & 1)
218     {
219         /* Read LSB */
220         *ReadWriteMode &= ~1;
221         return LOBYTE(*CurrentValue);
222     }
223 
224     if (*ReadWriteMode & 2)
225     {
226         /* Read MSB */
227         *ReadWriteMode &= ~2;
228         return HIBYTE(*CurrentValue);
229     }
230 
231     /* Shouldn't get here */
232     ASSERT(FALSE);
233     return 0;
234 }
235 
PitWriteData(BYTE Channel,BYTE Value)236 static VOID PitWriteData(BYTE Channel, BYTE Value)
237 {
238     LPBYTE ReadWriteMode = NULL;
239 
240     if (PitChannels[Channel].WriteStatus == 0x00)
241     {
242         PitChannels[Channel].WriteStatus = PitChannels[Channel].ReadWriteMode;
243     }
244 
245     ASSERT(PitChannels[Channel].WriteStatus != 0);
246 
247     ReadWriteMode = &PitChannels[Channel].WriteStatus;
248 
249     if (*ReadWriteMode & 1)
250     {
251         /* Write LSB */
252         *ReadWriteMode &= ~1;
253         PitChannels[Channel].CountRegister &= 0xFF00;
254         PitChannels[Channel].CountRegister |= Value;
255     }
256     else if (*ReadWriteMode & 2)
257     {
258         /* Write MSB */
259         *ReadWriteMode &= ~2;
260         PitChannels[Channel].CountRegister &= 0x00FF;
261         PitChannels[Channel].CountRegister |= Value << 8;
262     }
263 
264     /* ReadWriteMode went to zero: we are going to load the new count */
265     if (*ReadWriteMode == 0x00)
266     {
267         if (PitChannels[Channel].CountRegister == 0x0000)
268         {
269             /* Wrap around to the highest count */
270             if (PitChannels[Channel].Bcd)
271                 PitChannels[Channel].CountRegister = 9999;
272             else
273                 PitChannels[Channel].CountRegister = 0xFFFF; // 0x10000; // 65536
274         }
275 
276         /* Convert the current value from BCD if needed */
277         PitChannels[Channel].CountRegister =
278             WRITE_PIT_VALUE(PitChannels[Channel], PitChannels[Channel].CountRegister);
279         PitChannels[Channel].ReloadValue = PitChannels[Channel].CountRegister;
280 
281         /* Reload now the new count */
282         PitChannels[Channel].CurrentValue = PitChannels[Channel].ReloadValue;
283     }
284 }
285 
PitReadPort(USHORT Port)286 static BYTE WINAPI PitReadPort(USHORT Port)
287 {
288     switch (Port)
289     {
290         case PIT_DATA_PORT(0):
291         case PIT_DATA_PORT(1):
292         case PIT_DATA_PORT(2):
293         {
294             return PitReadData(Port - PIT_DATA_PORT(0));
295         }
296     }
297 
298     return 0;
299 }
300 
PitWritePort(USHORT Port,BYTE Data)301 static VOID WINAPI PitWritePort(USHORT Port, BYTE Data)
302 {
303     switch (Port)
304     {
305         case PIT_COMMAND_PORT:
306         {
307             PitWriteCommand(Data);
308             break;
309         }
310 
311         case PIT_DATA_PORT(0):
312         case PIT_DATA_PORT(1):
313         case PIT_DATA_PORT(2):
314         {
315             PitWriteData(Port - PIT_DATA_PORT(0), Data);
316             break;
317         }
318     }
319 }
320 
PitDecrementCount(PPIT_CHANNEL Channel,DWORD Count)321 static VOID PitDecrementCount(PPIT_CHANNEL Channel, DWORD Count)
322 {
323     if (Count == 0) return;
324 
325     switch (Channel->Mode)
326     {
327         case PIT_MODE_INT_ON_TERMINAL_COUNT:
328         {
329             /* Decrement the value */
330             if (Count > Channel->CurrentValue)
331             {
332                 /* The value does not reload in this case */
333                 Channel->CurrentValue = 0;
334             }
335             else Channel->CurrentValue -= Count;
336 
337             /* Did it fall to the terminal count? */
338             if (Channel->CurrentValue == 0 && !Channel->Out)
339             {
340                 /* Yes, raise the output line */
341                 PitSetOut(Channel, TRUE);
342             }
343             break;
344         }
345 
346         case PIT_MODE_RATE_GENERATOR:
347         {
348             BOOLEAN Reloaded = FALSE;
349 
350             while (Count)
351             {
352                 if ((Count > Channel->CurrentValue)
353                     && (Channel->CurrentValue != 0))
354                 {
355                     /* Decrement the count */
356                     Count -= Channel->CurrentValue;
357 
358                     /* Reload the value */
359                     Channel->CurrentValue = Channel->ReloadValue;
360 
361                     /* Set the flag */
362                     Reloaded = TRUE;
363                 }
364                 else
365                 {
366                     /* Decrement the value */
367                     Channel->CurrentValue -= Count;
368 
369                     /* Clear the count */
370                     Count = 0;
371 
372                     /* Did it fall to zero? */
373                     if (Channel->CurrentValue == 0)
374                     {
375                         Channel->CurrentValue = Channel->ReloadValue;
376                         Reloaded = TRUE;
377                     }
378                 }
379             }
380 
381             /* If there was a reload, raise the output line */
382             if (Reloaded) PitSetOut(Channel, TRUE);
383 
384             break;
385         }
386 
387         case PIT_MODE_SQUARE_WAVE:
388         {
389             INT ReloadCount = 0;
390             WORD ReloadValue = Channel->ReloadValue;
391 
392             /* The reload value must be even */
393             ReloadValue &= ~1;
394 
395             while (Count)
396             {
397                 if (((Count * 2) > Channel->CurrentValue)
398                     && (Channel->CurrentValue != 0))
399                 {
400                     /* Decrement the count */
401                     Count -= Channel->CurrentValue / 2;
402 
403                     /* Reload the value */
404                     Channel->CurrentValue = ReloadValue;
405 
406                     /* Increment the reload count */
407                     ReloadCount++;
408                 }
409                 else
410                 {
411                     /* Decrement the value */
412                     Channel->CurrentValue -= Count * 2;
413 
414                     /* Clear the count */
415                     Count = 0;
416 
417                     /* Did it fall to zero? */
418                     if (Channel->CurrentValue == 0)
419                     {
420                         /* Reload the value */
421                         Channel->CurrentValue = ReloadValue;
422 
423                         /* Increment the reload count */
424                         ReloadCount++;
425                     }
426                 }
427             }
428 
429             if (ReloadCount == 0) break;
430 
431             /* Toggle the flip-flop if the number of reloads was odd */
432             if (ReloadCount & 1)
433             {
434                 Channel->FlipFlop = !Channel->FlipFlop;
435                 PitSetOut(Channel, !Channel->Out);
436             }
437 
438             /* Was there any rising edge? */
439             if ((Channel->FlipFlop && (ReloadCount == 1)) || (ReloadCount > 1))
440             {
441                 /* Yes, raise the output line */
442                 PitSetOut(Channel, TRUE);
443             }
444 
445             break;
446         }
447 
448         case PIT_MODE_SOFTWARE_STROBE:
449         {
450             // TODO: NOT IMPLEMENTED
451             break;
452         }
453 
454         case PIT_MODE_HARDWARE_ONE_SHOT:
455         case PIT_MODE_HARDWARE_STROBE:
456         {
457             /* These modes do not work on x86 PCs */
458             break;
459         }
460     }
461 }
462 
PitClock(ULONGLONG Count)463 static VOID FASTCALL PitClock(ULONGLONG Count)
464 {
465     UCHAR i;
466 
467     for (i = 0; i < PIT_CHANNELS; i++)
468     {
469         // if (!PitChannels[i].Counting) continue;
470         PitDecrementCount(&PitChannels[i], Count);
471     }
472 }
473 
474 /* PUBLIC FUNCTIONS ***********************************************************/
475 
PitSetOutFunction(BYTE Channel,LPVOID Param,PIT_OUT_FUNCTION OutFunction)476 VOID PitSetOutFunction(BYTE Channel, LPVOID Param, PIT_OUT_FUNCTION OutFunction)
477 {
478     if (Channel >= PIT_CHANNELS) return;
479 
480     PitChannels[Channel].OutParam    = Param;
481     PitChannels[Channel].OutFunction = OutFunction;
482 }
483 
PitSetGate(BYTE Channel,BOOLEAN State)484 VOID PitSetGate(BYTE Channel, BOOLEAN State)
485 {
486     if (Channel >= PIT_CHANNELS) return;
487     if (State == PitChannels[Channel].Gate) return;
488 
489     /* UNIMPLEMENTED */
490     PitChannels[Channel].Gate = State;
491 }
492 
PitGetReloadValue(BYTE Channel)493 WORD PitGetReloadValue(BYTE Channel)
494 {
495     if (Channel >= PIT_CHANNELS) return 0xFFFF;
496 
497     if (PitChannels[Channel].ReloadValue == 0)
498         return 0xFFFF;
499     else
500         return PitChannels[Channel].ReloadValue;
501 }
502 
PitInitialize(VOID)503 VOID PitInitialize(VOID)
504 {
505     /* Set up the timers to their default value */
506     PitSetOutFunction(0, NULL, NULL);
507     PitSetGate(0, TRUE);
508     PitSetOutFunction(1, NULL, NULL);
509     PitSetGate(1, TRUE);
510     PitSetOutFunction(2, NULL, NULL);
511     PitSetGate(2, FALSE);
512 
513     /* Register the I/O Ports */
514     RegisterIoPort(PIT_COMMAND_PORT,        NULL, PitWritePort);
515     RegisterIoPort(PIT_DATA_PORT(0), PitReadPort, PitWritePort);
516     RegisterIoPort(PIT_DATA_PORT(1), PitReadPort, PitWritePort);
517     RegisterIoPort(PIT_DATA_PORT(2), PitReadPort, PitWritePort);
518 
519     /* Register the hardware timer */
520     MasterClock = CreateHardwareTimer(HARDWARE_TIMER_ENABLED | HARDWARE_TIMER_PRECISE,
521                                       HZ_TO_NS(PIT_BASE_FREQUENCY),
522                                       PitClock);
523 }
524 
525 /* EOF */
526