2 Copyright © 1995-2017, The AROS Development Team. All rights reserved.
5 Desc: Hardware management routines for IBM PC-AT timer
10 #include <proto/exec.h>
11 #include <proto/execlock.h>
14 #include "timer_macros.h"
17 * This code uses two channels of the PIT for simplicity:
18 * Channel 0 - sends IRQ 0 on terminal count. We use it as alarm clock.
19 * Channel 2 is used as EClock counter. It counts all the time and is never reloaded.
20 * We initialize it in timer_init.c.
24 * The math magic behind this is:
26 * 1. Theoretical value of microseconds is: tick * 1000000 / tb_eclock_rate.
27 * 2. tb_eclock_rate is constant and equal to 1193180Hz (frequency of PIT's master quartz).
28 * 3. tick2usec() is called much more frequently than usec2tick(). And multiplication is faster than division.
30 * So let's get rid of division:
32 * a) Multiply both divident and divisor of the initial equation by 0x100000000 (4294967296 in decimal). In asm this
33 * can be accomplished simply by using 64-bit math ops and using upper half instead of lower:
34 * usec = (tick * 1000000 * 0x100000000) / (1193180 * 0x100000000) = tick * (1000000 * 0x100000000 / 1193180) / 0x100000000.
35 * b) Calculate the constant part in brackets: 1000000 * 4294967296 / 1193180 = 3599597124.
36 * c) So: usec = tick * 3599597124 / 0x100000000 = (tick * 3599597124) >> 32.
40 static const ULONG TIMER_TUCONV
= (ULONG
)(1000000 * 0x100000000ULL
/ 1193180);
42 static inline const ULONG
tick2usec(const ULONG tick
)
44 return (ULONG
)(((UQUAD
)tick
* TIMER_TUCONV
) >> 32);
48 * So let's get rid of division once again:
50 * Theoretical value of ticks is: usec * tb_eclock_rate / 1000000
51 * a) Multiply both divident and divisor of the initial equation by 0x100000000 (4294967296 in decimal). In asm this
52 * can be accomplished simply by using 64-bit math ops and using upper half instead of lower:
53 * tick * 0x100000000 = usec * (1193180 * 0x100000000 / 1000000)
54 * b) Calculate the constant part in brackets:
55 * tick * 0x100000000 = usec * 5124669078
56 * c) Shift off the low bits
57 * tick = (usec * 5124669078) >> 32;
58 * d) BUT! 5124669078 > 1<<32 , so by the communitive property we get:
59 * tick = (usec * (2^32 + (5124669078 - 2^32))) / 2^32
61 * tick = ((usec * 2^32)) + (usec * (5124669078 - 2^32))) / 2^32
63 * tick = ((usec * 2^32) + (usec * 829701782)) / 2^32
65 * tick = ((usec * 2^32 / 2^32) + (usec * 829701782) / 2^32
67 * tick = usec + ((usec * 829701782) >> 32);
69 static const ULONG TIMER_UTCONV
= (1193180 * 0x100000000ULL
/ 1000000) - 0x100000000;
71 static inline const ULONG
usec2tick(const ULONG usec
)
73 return usec
+ (ULONG
)(((UQUAD
)usec
* TIMER_UTCONV
) >> 32);
77 * Calculate difference and add it to our current EClock value.
78 * PIT counters actually count backwards. This is why everything here looks reversed.
80 static void EClockAdd(ULONG time
, struct TimerBase
*TimerBase
)
82 ULONG diff
= (TimerBase
->tb_prev_tick
- time
);
84 if (time
> TimerBase
->tb_prev_tick
)
86 /* Handle PIT rollover through 0xFFFF */
90 /* Increment our time counters */
91 TimerBase
->tb_ticks_total
+= diff
;
92 INCTIME(TimerBase
->tb_CurrentTime
, TimerBase
->tb_ticks_sec
, diff
);
93 INCTIME(TimerBase
->tb_Elapsed
, TimerBase
->tb_ticks_elapsed
, diff
);
96 void EClockUpdate(struct TimerBase
*TimerBase
)
100 outb(CH0
|ACCESS_LATCH
, PIT_CONTROL
); /* Latch the current time value */
101 time
= ch_read(PIT_CH0
); /* Read out current 16-bit time */
103 EClockAdd(time
, TimerBase
); /* Increment our time counters */
104 TimerBase
->tb_prev_tick
= time
; /* Remember last counter value as start of new interval */
107 void EClockSet(struct TimerBase
*TimerBase
)
109 TimerBase
->tb_ticks_sec
= usec2tick(TimerBase
->tb_CurrentTime
.tv_micro
);
110 TimerBase
->tb_ticks_total
= TimerBase
->tb_ticks_sec
+ (UQUAD
)TimerBase
->tb_CurrentTime
.tv_secs
* TimerBase
->tb_eclock_rate
;
112 outb(CH0
|ACCESS_LATCH
, PIT_CONTROL
); /* Latch the current time value */
113 TimerBase
->tb_prev_tick
= ch_read(PIT_CH0
); /* Read out current 16-bit time */
116 void Timer0Setup(struct TimerBase
*TimerBase
)
118 #if defined(__AROSEXEC_SMP__)
119 struct ExecLockBase
*ExecLockBase
= TimerBase
->tb_ExecLockBase
;
124 struct timerequest
*tr
;
126 #if defined(__AROSEXEC_SMP__)
127 if (ExecLockBase
) ObtainLock(TimerBase
->tb_ListLock
, SPINLOCK_MODE_READ
, 0);
129 if ((tr
= (struct timerequest
*)GetHead(&TimerBase
->tb_Lists
[TL_MICROHZ
])) != NULL
)
131 time
.tv_micro
= tr
->tr_time
.tv_micro
;
132 time
.tv_secs
= tr
->tr_time
.tv_secs
;
134 EClockUpdate(TimerBase
);
135 SUBTIME(&time
, &TimerBase
->tb_Elapsed
);
137 if ((LONG
)time
.tv_secs
< 0)
141 else if (time
.tv_secs
== 0)
143 if (time
.tv_micro
< 20000)
145 delay
= usec2tick(time
.tv_micro
);
149 #if defined(__AROSEXEC_SMP__)
150 if (ExecLockBase
) ReleaseLock(TimerBase
->tb_ListLock
, 0);
152 if (delay
< 2) delay
= 2;
155 * We are going to reload the counter. By this moment, some time has passed after the last EClockUpdate()pdate.
156 * In order to keep up with the precision, we pick up this time here.
158 outb(CH0
|ACCESS_LATCH
, PIT_CONTROL
); /* Latch the current time value */
159 old_tick
= ch_read(PIT_CH0
); /* Read out current 16-bit time */
161 outb(CH0
|ACCESS_FULL
|MODE_SW_STROBE
, PIT_CONTROL
); /* Software strobe mode, 16-bit access */
162 ch_write(delay
, PIT_CH0
); /* Activate the new delay */
165 * Now, when our new delay is already in progress, we can spend some time
166 * on adding previous value to our time counters.
168 EClockAdd(old_tick
, TimerBase
);
169 /* tb_prev_tick is used by EClockAdd(), so update it only now */
170 TimerBase
->tb_prev_tick
= delay
;