]> git.buserror.net Git - polintos/scott/priv.git/blob - kernel/io/timer/i8254.cc
minor doc updates
[polintos/scott/priv.git] / kernel / io / timer / i8254.cc
1 // io/timer/i8254.cc -- Intel 8254 and compatible timer driver
2 //
3 // This software is copyright (c) 2006 Scott Wood <scott@buserror.net>.
4 // 
5 // This software is provided 'as-is', without any express or implied warranty.
6 // In no event will the authors or contributors be held liable for any damages
7 // arising from the use of this software.
8 // 
9 // Permission is hereby granted to everyone, free of charge, to use, copy,
10 // modify, prepare derivative works of, publish, distribute, perform,
11 // sublicense, and/or sell copies of the Software, provided that the above
12 // copyright notice and disclaimer of warranty be included in all copies or
13 // substantial portions of this software.
14
15 #include <kern/time.h>
16 #include <kern/io.h>
17 #include <lowlevel/clock.h>
18
19 namespace IO {
20 namespace Timer {
21         using Time::HWTimer;
22         using Time::monotonic_clock;
23         using Time::hw_timer;
24         using Time::Time;
25
26         class I8254 : public HWTimer {
27                 Lock::SpinLock lock;
28                 bool armed;
29         
30                 u16 read_time()
31                 {
32                         ll_out_8(0x43, 0);
33                         u16 ret = ll_in_8(0x40);
34                         ret |= (u16)ll_in_8(0x40) << 8;
35                         return ret;
36                 }
37                 
38                 void wait_for_zero()
39                 {
40                         u16 val;
41                 
42                         do {
43                                 val = read_time();
44                         } while (val < 0x8000);
45
46                         do {
47                                 val = read_time();
48                         } while (val > 0x8000);
49                 }
50                 
51                 enum {
52                         clock_freq = 1193182
53                 };
54                 
55         public:
56                 I8254()
57                 {
58                         hw_timer = this;
59                         armed = false;
60                 }
61         
62                 void calibrate()
63                 {
64                         wait_for_zero();
65                         s64 start_tick = ll_getclock();
66                         wait_for_zero();
67                         wait_for_zero();
68                         s64 end_tick = ll_getclock();
69
70                         s64 ticks_per_second = (end_tick - start_tick) * 
71                                                (u64)clock_freq / 0x20000;
72                         
73                         monotonic_clock.calibrate(ticks_per_second);
74                 }
75         
76                 void arm(Time new_expiry)
77                 {
78                         // Interrupts should always be disabled when
79                         // this is called.
80                         
81                         Lock::AutoSpinLock autolock(lock);
82
83                         // The 8254 is often slow to program, so don't re-program if the
84                         // expiry is the same.  To accomodate this, the tick timer must
85                         // be set to go off often enough that the expiry is never more
86                         // than 0xffff 8254 ticks (about 1/18.2 sec) in the future.
87
88                         if (armed && expiry != new_expiry) {
89                                 Time now;
90                                 monotonic_clock.get_time(&now);
91                                 Time rel_expiry = new_expiry - now;
92                                 expiry = new_expiry;
93                                 
94                                 u32 ticks;
95                                 
96                                 assert(rel_expiry.seconds <= 0);
97         
98                                 if (rel_expiry.seconds < 0)
99                                         ticks = 1;
100                                 else {
101                                         // Add one extra tick to make sure we round up rather than
102                                         // down; otherwise, we'll just end up programming the timer
103                                         // for one tick and trying again.
104                                         
105                                         ticks = rel_expiry.nanos * (u64)clock_freq /
106                                                 1000000000ULL + 1;
107                                         
108                                         assert(ticks <= 0xffff);
109         
110                                         if (ticks == 0)
111                                                 ticks = 1;
112                                 }
113                                 
114                                 if (!armed)
115                                         ll_out_8(0x43, 0x30);
116                                         
117                                 ll_out_8(0x40, ticks & 0xff);
118                                 ll_out_8(0x40, ticks >> 8);
119                                 armed = true;
120                         }
121                 }
122                 
123                 void disarm()
124                 {
125                         armed = false;
126                 }
127         };
128         
129         I8254 i8254;
130 }
131 }