2 Copyright © 1995-2011, The AROS Development Team. All rights reserved.
5 Desc: Hardware management routines for IBM PC-AT timer
10 #include <proto/exec.h>
13 #include "timer_macros.h"
16 * This code uses two channels of the PIT for simplicity:
17 * Channel 0 - sends IRQ 0 on terminal count. We use it as alarm clock.
18 * Channel 2 is used as EClock counter. It counts all the time and is never reloaded.
19 * We initialize it in timer_init.c.
23 * The math magic behing this is:
25 * 1. Theoretical value of microseconds is: tick * 1000000 / tb_eclock_rate.
26 * 2. tb_eclock_rate is constant and equal to 1193180Hz (frequency of PIT's master quartz).
27 * 3. tick2usec() is called much more frequently than usec2tick(). And multiplication is faster than division.
29 * So let's get rid of division:
31 * a) Multiply both divident and divisor of the initial equation by 0x100000000 (4294967296 in decimal). In asm this
32 * can be accomplished simply by using 64-bit math ops and using upper half instead of lower:
33 * usec = (tick * 1000000 * 0x100000000) / (1193180 * 0x100000000) = tick * (1000000 * 0x100000000 / 1193180) / 0x100000000.
34 * b) Calculate the constant part in brackets: 1000000 * 4294967296 / 1193180 = 3599597124.
35 * c) So: usec = tick * 3599597124 / 0x100000000 = (tick * 3599597124) >> 32.
39 static const ULONG TIMER_TUCONV
= (ULONG
)(1000000 * 0x100000000ULL
/ 1193180);
41 static inline const ULONG
tick2usec(const ULONG tick
)
43 return (ULONG
)(((UQUAD
)tick
* TIMER_TUCONV
) >> 32);
47 * So let's get rid of division once again:
49 * Theoretical value of ticks is: usec * tb_eclock_rate / 1000000
50 * a) Multiply both divident and divisor of the initial equation by 0x100000000 (4294967296 in decimal). In asm this
51 * can be accomplished simply by using 64-bit math ops and using upper half instead of lower:
52 * tick * 0x100000000 = usec * (1193180 * 0x100000000 / 1000000)
53 * b) Calculate the constant part in brackets:
54 * tick * 0x100000000 = usec * 5124669078
55 * c) Shift off the low bits
56 * tick = (usec * 5124669078) >> 32;
57 * d) BUT! 5124669078 > 1<<32 , so by the communitive property we get:
58 * tick = (usec * (2^32 + (5124669078 - 2^32))) / 2^32
60 * tick = ((usec * 2^32)) + (usec * (5124669078 - 2^32))) / 2^32
62 * tick = ((usec * 2^32) + (usec * 829701782)) / 2^32
64 * tick = ((usec * 2^32 / 2^32) + (usec * 829701782) / 2^32
66 * tick = usec + ((usec * 829701782) >> 32);
68 static const ULONG TIMER_UTCONV
= (1193180 * 0x100000000ULL
/ 1000000) - 0x100000000;
70 static inline const ULONG
usec2tick(const ULONG usec
)
72 return usec
+ (ULONG
)(((UQUAD
)usec
* TIMER_UTCONV
) >> 32);
76 * Calculate difference and add it to our current EClock value.
77 * PIT counters actually count backwards. This is why everything here looks reversed.
79 static void EClockAdd(ULONG time
, struct TimerBase
*TimerBase
)
81 ULONG diff
= (TimerBase
->tb_prev_tick
- time
);
83 if (time
> TimerBase
->tb_prev_tick
)
85 /* Handle PIT rollover through 0xFFFF */
89 /* Increment our time counters */
90 TimerBase
->tb_ticks_total
+= diff
;
91 INCTIME(TimerBase
->tb_CurrentTime
, TimerBase
->tb_ticks_sec
, diff
);
92 INCTIME(TimerBase
->tb_Elapsed
, TimerBase
->tb_ticks_elapsed
, diff
);
95 void EClockUpdate(struct TimerBase
*TimerBase
)
99 outb(CH0
|ACCESS_LATCH
, PIT_CONTROL
); /* Latch the current time value */
100 time
= ch_read(PIT_CH0
); /* Read out current 16-bit time */
102 EClockAdd(time
, TimerBase
); /* Increment our time counters */
103 TimerBase
->tb_prev_tick
= time
; /* Remember last counter value as start of new interval */
106 void EClockSet(struct TimerBase
*TimerBase
)
108 TimerBase
->tb_ticks_sec
= usec2tick(TimerBase
->tb_CurrentTime
.tv_micro
);
109 TimerBase
->tb_ticks_total
= TimerBase
->tb_ticks_sec
+ (UQUAD
)TimerBase
->tb_CurrentTime
.tv_secs
* TimerBase
->tb_eclock_rate
;
111 outb(CH0
|ACCESS_LATCH
, PIT_CONTROL
); /* Latch the current time value */
112 TimerBase
->tb_prev_tick
= ch_read(PIT_CH0
); /* Read out current 16-bit time */
115 void Timer0Setup(struct TimerBase
*TimerBase
)
120 struct timerequest
*tr
= (struct timerequest
*)GetHead(&TimerBase
->tb_Lists
[TL_MICROHZ
]);
124 time
.tv_micro
= tr
->tr_time
.tv_micro
;
125 time
.tv_secs
= tr
->tr_time
.tv_secs
;
127 EClockUpdate(TimerBase
);
128 SUBTIME(&time
, &TimerBase
->tb_Elapsed
);
130 if ((LONG
)time
.tv_secs
< 0)
134 else if (time
.tv_secs
== 0)
136 if (time
.tv_micro
< 20000)
138 delay
= usec2tick(time
.tv_micro
);
143 if (delay
< 2) delay
= 2;
146 * We are going to reload the counter. By this moment, some time has passed after the last EClockUpdate()pdate.
147 * In order to keep up with the precision, we pick up this time here.
149 outb(CH0
|ACCESS_LATCH
, PIT_CONTROL
); /* Latch the current time value */
150 old_tick
= ch_read(PIT_CH0
); /* Read out current 16-bit time */
152 outb(CH0
|ACCESS_FULL
|MODE_SW_STROBE
, PIT_CONTROL
); /* Software strobe mode, 16-bit access */
153 ch_write(delay
, PIT_CH0
); /* Activate the new delay */
156 * Now, when our new delay is already in progress, we can spend some time
157 * on adding previous value to our time counters.
159 EClockAdd(old_tick
, TimerBase
);
160 /* tb_prev_tick is used by EClockAdd(), so update it only now */
161 TimerBase
->tb_prev_tick
= delay
;