Switch to a simple X11-style license.
[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 // Permission is hereby granted, free of charge, to any person obtaining a copy of
6 // this software and associated documentation files (the "Software"), to deal with
7 // the Software without restriction, including without limitation the rights to
8 // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9 // of the Software, and to permit persons to whom the Software is furnished to do
10 // so, subject to the following condition:
11 // 
12 // The above copyright notice and this permission notice shall be
13 // included in all copies or substantial portions of the Software.
14 // 
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
18 // CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
21 // SOFTWARE.
22
23 #include <kern/time.h>
24 #include <kern/io.h>
25 #include <lowlevel/clock.h>
26
27 namespace IO {
28 namespace Timer {
29         using Time::HWTimer;
30         using Time::monotonic_clock;
31         using Time::hw_timer;
32         using Time::Time;
33
34         class I8254 : public HWTimer {
35                 Lock::SpinLock lock;
36                 bool armed;
37         
38                 u16 read_time()
39                 {
40                         ll_out_8(0x43, 0);
41                         u16 ret = ll_in_8(0x40);
42                         ret |= (u16)ll_in_8(0x40) << 8;
43                         return ret;
44                 }
45                 
46                 void wait_for_zero()
47                 {
48                         u16 val;
49                 
50                         do {
51                                 val = read_time();
52                         } while (val < 0x8000);
53
54                         do {
55                                 val = read_time();
56                         } while (val > 0x8000);
57                 }
58                 
59                 enum {
60                         clock_freq = 1193182
61                 };
62                 
63         public:
64                 I8254()
65                 {
66                         hw_timer = this;
67                         armed = false;
68                 }
69         
70                 void calibrate()
71                 {
72                         wait_for_zero();
73                         s64 start_tick = ll_getclock();
74                         wait_for_zero();
75                         wait_for_zero();
76                         s64 end_tick = ll_getclock();
77
78                         s64 ticks_per_second = (end_tick - start_tick) * 
79                                                (u64)clock_freq / 0x20000;
80                         
81                         monotonic_clock.calibrate(ticks_per_second);
82                 }
83         
84                 void arm(Time new_expiry)
85                 {
86                         // Interrupts should always be disabled when
87                         // this is called.
88                         
89                         Lock::AutoSpinLock autolock(lock);
90
91                         // The 8254 is often slow to program, so don't re-program if the
92                         // expiry is the same.  To accomodate this, the tick timer must
93                         // be set to go off often enough that the expiry is never more
94                         // than 0xffff 8254 ticks (about 1/18.2 sec) in the future.
95
96                         if (armed && expiry != new_expiry) {
97                                 Time now;
98                                 monotonic_clock.get_time(&now);
99                                 Time rel_expiry = new_expiry - now;
100                                 expiry = new_expiry;
101                                 
102                                 u32 ticks;
103                                 
104                                 assert(rel_expiry.seconds <= 0);
105         
106                                 if (rel_expiry.seconds < 0)
107                                         ticks = 1;
108                                 else {
109                                         // Add one extra tick to make sure we round up rather than
110                                         // down; otherwise, we'll just end up programming the timer
111                                         // for one tick and trying again.
112                                         
113                                         ticks = rel_expiry.nanos * (u64)clock_freq /
114                                                 1000000000ULL + 1;
115                                         
116                                         assert(ticks <= 0xffff);
117         
118                                         if (ticks == 0)
119                                                 ticks = 1;
120                                 }
121                                 
122                                 if (!armed)
123                                         ll_out_8(0x43, 0x30);
124                                         
125                                 ll_out_8(0x40, ticks & 0xff);
126                                 ll_out_8(0x40, ticks >> 8);
127                                 armed = true;
128                         }
129                 }
130                 
131                 void disarm()
132                 {
133                         armed = false;
134                 }
135         };
136         
137         I8254 i8254;
138 }
139 }