5 COMEDI - Linux Control and Measurement Device Interface
6 Copyright (C) 2000 David A. Schleef <ds@schleef.org>
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
27 #include "../comedi.h"
29 #define i8253_cascade_ns_to_timer i8253_cascade_ns_to_timer_2div
31 static inline void i8253_cascade_ns_to_timer_2div_old(int i8253_osc_base
,
34 unsigned int *nanosec
,
39 int div1_glb
, div2_glb
, ns_glb
;
40 int div1_lub
, div2_lub
, ns_lub
;
43 divider
= (*nanosec
+ i8253_osc_base
/ 2) / i8253_osc_base
;
45 /* find 2 integers 1<={x,y}<=65536 such that x*y is
48 div1_lub
= div2_lub
= 0;
49 div1_glb
= div2_glb
= 0;
55 for (div1
= divider
/ 65536 + 1; div1
< div2
; div1
++) {
56 div2
= divider
/ div1
;
58 ns
= i8253_osc_base
* div1
* div2
;
59 if (ns
<= *nanosec
&& ns
> ns_glb
) {
67 ns
= i8253_osc_base
* div1
* div2
;
68 if (ns
> *nanosec
&& ns
< ns_lub
) {
76 *nanosec
= div1_lub
* div2_lub
* i8253_osc_base
;
77 *d1
= div1_lub
& 0xffff;
78 *d2
= div2_lub
& 0xffff;
82 static inline void i8253_cascade_ns_to_timer_power(int i8253_osc_base
,
85 unsigned int *nanosec
,
91 for (div1
= 2; div1
<= (1 << 16); div1
<<= 1) {
92 base
= i8253_osc_base
* div1
;
93 round_mode
&= TRIG_ROUND_MASK
;
95 case TRIG_ROUND_NEAREST
:
97 div2
= (*nanosec
+ base
/ 2) / base
;
100 div2
= (*nanosec
) / base
;
103 div2
= (*nanosec
+ base
- 1) / base
;
109 *nanosec
= div2
* base
;
116 /* shouldn't get here */
119 *nanosec
= div1
* div2
* i8253_osc_base
;
124 static inline void i8253_cascade_ns_to_timer_2div(int i8253_osc_base
,
127 unsigned int *nanosec
,
130 unsigned int divider
;
131 unsigned int div1
, div2
;
132 unsigned int div1_glb
, div2_glb
, ns_glb
;
133 unsigned int div1_lub
, div2_lub
, ns_lub
;
136 unsigned int ns_low
, ns_high
;
137 static const unsigned int max_count
= 0x10000;
138 /* exit early if everything is already correct (this can save time
139 * since this function may be called repeatedly during command tests
141 div1
= *d1
? *d1
: max_count
;
142 div2
= *d2
? *d2
: max_count
;
143 divider
= div1
* div2
;
144 if (div1
* div2
* i8253_osc_base
== *nanosec
&&
145 div1
> 1 && div1
<= max_count
&& div2
> 1 && div2
<= max_count
&&
146 /* check for overflow */
147 divider
> div1
&& divider
> div2
&&
148 divider
* i8253_osc_base
> divider
&&
149 divider
* i8253_osc_base
> i8253_osc_base
) {
153 divider
= *nanosec
/ i8253_osc_base
;
155 div1_lub
= div2_lub
= 0;
156 div1_glb
= div2_glb
= 0;
162 start
= divider
/ div2
;
165 for (div1
= start
; div1
<= divider
/ div1
+ 1 && div1
<= max_count
;
167 for (div2
= divider
/ div1
;
168 div1
* div2
<= divider
+ div1
+ 1 && div2
<= max_count
;
170 ns
= i8253_osc_base
* div1
* div2
;
171 if (ns
<= *nanosec
&& ns
> ns_glb
) {
176 if (ns
>= *nanosec
&& ns
< ns_lub
) {
184 round_mode
&= TRIG_ROUND_MASK
;
185 switch (round_mode
) {
186 case TRIG_ROUND_NEAREST
:
188 ns_high
= div1_lub
* div2_lub
* i8253_osc_base
;
189 ns_low
= div1_glb
* div2_glb
* i8253_osc_base
;
190 if (ns_high
- *nanosec
< *nanosec
- ns_low
) {
202 case TRIG_ROUND_DOWN
:
208 *nanosec
= div1
* div2
* i8253_osc_base
;
209 /* masking is done since counter maps zero to 0x10000 */
216 /* i8254_load programs 8254 counter chip. It should also work for the 8253.
217 * base_address is the lowest io address
218 * for the chip (the address of counter 0).
219 * counter_number is the counter you want to load (0,1 or 2)
220 * count is the number to load into the counter.
222 * You probably want to use mode 2.
224 * Use i8254_mm_load() if you board uses memory-mapped io, it is
225 * the same as i8254_load() except it uses writeb() instead of outb().
227 * Neither i8254_load() or i8254_read() do their loading/reading
228 * atomically. The 16 bit read/writes are performed with two successive
229 * 8 bit read/writes. So if two parts of your driver do a load/read on
230 * the same counter, it may be necessary to protect these functions
236 #define i8254_control_reg 3
238 static inline int i8254_load(unsigned long base_address
, unsigned int regshift
,
239 unsigned int counter_number
, unsigned int count
,
244 if (counter_number
> 2)
250 if ((mode
== 2 || mode
== 3) && count
== 1)
253 byte
= counter_number
<< 6;
254 byte
|= 0x30; /* load low then high byte */
255 byte
|= (mode
<< 1); /* set counter mode */
256 outb(byte
, base_address
+ (i8254_control_reg
<< regshift
));
257 byte
= count
& 0xff; /* lsb of counter value */
258 outb(byte
, base_address
+ (counter_number
<< regshift
));
259 byte
= (count
>> 8) & 0xff; /* msb of counter value */
260 outb(byte
, base_address
+ (counter_number
<< regshift
));
265 static inline int i8254_mm_load(void *base_address
, unsigned int regshift
,
266 unsigned int counter_number
, unsigned int count
,
271 if (counter_number
> 2)
277 if ((mode
== 2 || mode
== 3) && count
== 1)
280 byte
= counter_number
<< 6;
281 byte
|= 0x30; /* load low then high byte */
282 byte
|= (mode
<< 1); /* set counter mode */
283 writeb(byte
, base_address
+ (i8254_control_reg
<< regshift
));
284 byte
= count
& 0xff; /* lsb of counter value */
285 writeb(byte
, base_address
+ (counter_number
<< regshift
));
286 byte
= (count
>> 8) & 0xff; /* msb of counter value */
287 writeb(byte
, base_address
+ (counter_number
<< regshift
));
292 /* Returns 16 bit counter value, should work for 8253 also.*/
293 static inline int i8254_read(unsigned long base_address
, unsigned int regshift
,
294 unsigned int counter_number
)
299 if (counter_number
> 2)
303 byte
= counter_number
<< 6;
304 outb(byte
, base_address
+ (i8254_control_reg
<< regshift
));
307 ret
= inb(base_address
+ (counter_number
<< regshift
));
309 ret
+= inb(base_address
+ (counter_number
<< regshift
)) << 8;
314 static inline int i8254_mm_read(void *base_address
, unsigned int regshift
,
315 unsigned int counter_number
)
320 if (counter_number
> 2)
324 byte
= counter_number
<< 6;
325 writeb(byte
, base_address
+ (i8254_control_reg
<< regshift
));
328 ret
= readb(base_address
+ (counter_number
<< regshift
));
330 ret
+= readb(base_address
+ (counter_number
<< regshift
)) << 8;
335 /* Loads 16 bit initial counter value, should work for 8253 also. */
336 static inline void i8254_write(unsigned long base_address
,
337 unsigned int regshift
,
338 unsigned int counter_number
, unsigned int count
)
342 if (counter_number
> 2)
345 byte
= count
& 0xff; /* lsb of counter value */
346 outb(byte
, base_address
+ (counter_number
<< regshift
));
347 byte
= (count
>> 8) & 0xff; /* msb of counter value */
348 outb(byte
, base_address
+ (counter_number
<< regshift
));
351 static inline void i8254_mm_write(void *base_address
,
352 unsigned int regshift
,
353 unsigned int counter_number
,
358 if (counter_number
> 2)
361 byte
= count
& 0xff; /* lsb of counter value */
362 writeb(byte
, base_address
+ (counter_number
<< regshift
));
363 byte
= (count
>> 8) & 0xff; /* msb of counter value */
364 writeb(byte
, base_address
+ (counter_number
<< regshift
));
367 /* Set counter mode, should work for 8253 also.
368 * Note: the 'mode' value is different to that for i8254_load() and comes
369 * from the INSN_CONFIG_8254_SET_MODE command:
370 * I8254_MODE0, I8254_MODE1, ..., I8254_MODE5
372 * I8254_BCD, I8254_BINARY
374 static inline int i8254_set_mode(unsigned long base_address
,
375 unsigned int regshift
,
376 unsigned int counter_number
, unsigned int mode
)
380 if (counter_number
> 2)
382 if (mode
> (I8254_MODE5
| I8254_BINARY
))
385 byte
= counter_number
<< 6;
386 byte
|= 0x30; /* load low then high byte */
387 byte
|= mode
; /* set counter mode and BCD|binary */
388 outb(byte
, base_address
+ (i8254_control_reg
<< regshift
));
393 static inline int i8254_mm_set_mode(void *base_address
,
394 unsigned int regshift
,
395 unsigned int counter_number
,
400 if (counter_number
> 2)
402 if (mode
> (I8254_MODE5
| I8254_BINARY
))
405 byte
= counter_number
<< 6;
406 byte
|= 0x30; /* load low then high byte */
407 byte
|= mode
; /* set counter mode and BCD|binary */
408 writeb(byte
, base_address
+ (i8254_control_reg
<< regshift
));
413 static inline int i8254_status(unsigned long base_address
,
414 unsigned int regshift
,
415 unsigned int counter_number
)
417 outb(0xE0 | (2 << counter_number
),
418 base_address
+ (i8254_control_reg
<< regshift
));
419 return inb(base_address
+ (counter_number
<< regshift
));
422 static inline int i8254_mm_status(void *base_address
,
423 unsigned int regshift
,
424 unsigned int counter_number
)
426 writeb(0xE0 | (2 << counter_number
),
427 base_address
+ (i8254_control_reg
<< regshift
));
428 return readb(base_address
+ (counter_number
<< regshift
));