Commit | Line | Data |
---|---|---|
703afc38 DS |
1 | /* |
2 | comedi/drivers/8253.h | |
3 | Header file for 8253 | |
4 | ||
5 | COMEDI - Linux Control and Measurement Device Interface | |
6 | Copyright (C) 2000 David A. Schleef <ds@schleef.org> | |
7 | ||
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. | |
12 | ||
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. | |
17 | ||
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. | |
21 | ||
22 | */ | |
23 | ||
24 | #ifndef _8253_H | |
25 | #define _8253_H | |
26 | ||
703afc38 | 27 | #include "../comedi.h" |
703afc38 DS |
28 | |
29 | #define i8253_cascade_ns_to_timer i8253_cascade_ns_to_timer_2div | |
30 | ||
31 | static inline void i8253_cascade_ns_to_timer_2div_old(int i8253_osc_base, | |
0a85b6f0 MT |
32 | unsigned int *d1, |
33 | unsigned int *d2, | |
34 | unsigned int *nanosec, | |
35 | int round_mode) | |
703afc38 DS |
36 | { |
37 | int divider; | |
38 | int div1, div2; | |
39 | int div1_glb, div2_glb, ns_glb; | |
40 | int div1_lub, div2_lub, ns_lub; | |
41 | int ns; | |
42 | ||
43 | divider = (*nanosec + i8253_osc_base / 2) / i8253_osc_base; | |
44 | ||
45 | /* find 2 integers 1<={x,y}<=65536 such that x*y is | |
46 | close to divider */ | |
47 | ||
48 | div1_lub = div2_lub = 0; | |
49 | div1_glb = div2_glb = 0; | |
50 | ||
51 | ns_glb = 0; | |
52 | ns_lub = 0xffffffff; | |
53 | ||
54 | div2 = 0x10000; | |
55 | for (div1 = divider / 65536 + 1; div1 < div2; div1++) { | |
56 | div2 = divider / div1; | |
57 | ||
58 | ns = i8253_osc_base * div1 * div2; | |
59 | if (ns <= *nanosec && ns > ns_glb) { | |
60 | ns_glb = ns; | |
61 | div1_glb = div1; | |
62 | div2_glb = div2; | |
63 | } | |
64 | ||
65 | div2++; | |
66 | if (div2 <= 65536) { | |
67 | ns = i8253_osc_base * div1 * div2; | |
68 | if (ns > *nanosec && ns < ns_lub) { | |
69 | ns_lub = ns; | |
70 | div1_lub = div1; | |
71 | div2_lub = div2; | |
72 | } | |
73 | } | |
74 | } | |
75 | ||
76 | *nanosec = div1_lub * div2_lub * i8253_osc_base; | |
77 | *d1 = div1_lub & 0xffff; | |
78 | *d2 = div2_lub & 0xffff; | |
79 | return; | |
80 | } | |
81 | ||
82 | static inline void i8253_cascade_ns_to_timer_power(int i8253_osc_base, | |
0a85b6f0 MT |
83 | unsigned int *d1, |
84 | unsigned int *d2, | |
85 | unsigned int *nanosec, | |
86 | int round_mode) | |
703afc38 DS |
87 | { |
88 | int div1, div2; | |
89 | int base; | |
90 | ||
91 | for (div1 = 2; div1 <= (1 << 16); div1 <<= 1) { | |
92 | base = i8253_osc_base * div1; | |
93 | round_mode &= TRIG_ROUND_MASK; | |
94 | switch (round_mode) { | |
95 | case TRIG_ROUND_NEAREST: | |
96 | default: | |
97 | div2 = (*nanosec + base / 2) / base; | |
98 | break; | |
99 | case TRIG_ROUND_DOWN: | |
100 | div2 = (*nanosec) / base; | |
101 | break; | |
102 | case TRIG_ROUND_UP: | |
103 | div2 = (*nanosec + base - 1) / base; | |
104 | break; | |
105 | } | |
106 | if (div2 < 2) | |
107 | div2 = 2; | |
108 | if (div2 <= 65536) { | |
109 | *nanosec = div2 * base; | |
110 | *d1 = div1 & 0xffff; | |
111 | *d2 = div2 & 0xffff; | |
112 | return; | |
113 | } | |
114 | } | |
115 | ||
116 | /* shouldn't get here */ | |
117 | div1 = 0x10000; | |
118 | div2 = 0x10000; | |
119 | *nanosec = div1 * div2 * i8253_osc_base; | |
120 | *d1 = div1 & 0xffff; | |
121 | *d2 = div2 & 0xffff; | |
122 | } | |
123 | ||
124 | static inline void i8253_cascade_ns_to_timer_2div(int i8253_osc_base, | |
0a85b6f0 MT |
125 | unsigned int *d1, |
126 | unsigned int *d2, | |
127 | unsigned int *nanosec, | |
128 | int round_mode) | |
703afc38 DS |
129 | { |
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; | |
134 | unsigned int ns; | |
135 | unsigned int start; | |
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 | |
140 | * and execution) */ | |
141 | div1 = *d1 ? *d1 : max_count; | |
142 | div2 = *d2 ? *d2 : max_count; | |
143 | divider = div1 * div2; | |
144 | if (div1 * div2 * i8253_osc_base == *nanosec && | |
0a85b6f0 MT |
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) { | |
703afc38 DS |
150 | return; |
151 | } | |
152 | ||
153 | divider = *nanosec / i8253_osc_base; | |
154 | ||
155 | div1_lub = div2_lub = 0; | |
156 | div1_glb = div2_glb = 0; | |
157 | ||
158 | ns_glb = 0; | |
159 | ns_lub = 0xffffffff; | |
160 | ||
161 | div2 = max_count; | |
162 | start = divider / div2; | |
163 | if (start < 2) | |
164 | start = 2; | |
165 | for (div1 = start; div1 <= divider / div1 + 1 && div1 <= max_count; | |
0a85b6f0 | 166 | div1++) { |
703afc38 | 167 | for (div2 = divider / div1; |
0a85b6f0 MT |
168 | div1 * div2 <= divider + div1 + 1 && div2 <= max_count; |
169 | div2++) { | |
703afc38 DS |
170 | ns = i8253_osc_base * div1 * div2; |
171 | if (ns <= *nanosec && ns > ns_glb) { | |
172 | ns_glb = ns; | |
173 | div1_glb = div1; | |
174 | div2_glb = div2; | |
175 | } | |
176 | if (ns >= *nanosec && ns < ns_lub) { | |
177 | ns_lub = ns; | |
178 | div1_lub = div1; | |
179 | div2_lub = div2; | |
180 | } | |
181 | } | |
182 | } | |
183 | ||
184 | round_mode &= TRIG_ROUND_MASK; | |
185 | switch (round_mode) { | |
186 | case TRIG_ROUND_NEAREST: | |
187 | default: | |
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) { | |
191 | div1 = div1_lub; | |
192 | div2 = div2_lub; | |
193 | } else { | |
194 | div1 = div1_glb; | |
195 | div2 = div2_glb; | |
196 | } | |
197 | break; | |
198 | case TRIG_ROUND_UP: | |
199 | div1 = div1_lub; | |
200 | div2 = div2_lub; | |
201 | break; | |
202 | case TRIG_ROUND_DOWN: | |
203 | div1 = div1_glb; | |
204 | div2 = div2_glb; | |
205 | break; | |
206 | } | |
207 | ||
208 | *nanosec = div1 * div2 * i8253_osc_base; | |
155b44aa GKH |
209 | /* masking is done since counter maps zero to 0x10000 */ |
210 | *d1 = div1 & 0xffff; | |
703afc38 DS |
211 | *d2 = div2 & 0xffff; |
212 | return; | |
213 | } | |
214 | ||
215 | #ifndef CMDTEST | |
216 | /* i8254_load programs 8254 counter chip. It should also work for the 8253. | |
a88e29cf GH |
217 | * base_address is the lowest io address |
218 | * for the chip (the address of counter 0). | |
703afc38 DS |
219 | * counter_number is the counter you want to load (0,1 or 2) |
220 | * count is the number to load into the counter. | |
221 | * | |
222 | * You probably want to use mode 2. | |
223 | * | |
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(). | |
226 | * | |
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 | |
231 | * with a spinlock. | |
232 | * | |
233 | * FMH | |
234 | */ | |
235 | ||
236 | #define i8254_control_reg 3 | |
237 | ||
238 | static inline int i8254_load(unsigned long base_address, unsigned int regshift, | |
0a85b6f0 MT |
239 | unsigned int counter_number, unsigned int count, |
240 | unsigned int mode) | |
703afc38 DS |
241 | { |
242 | unsigned int byte; | |
243 | ||
244 | if (counter_number > 2) | |
245 | return -1; | |
246 | if (count > 0xffff) | |
247 | return -1; | |
248 | if (mode > 5) | |
249 | return -1; | |
250 | if ((mode == 2 || mode == 3) && count == 1) | |
251 | return -1; | |
252 | ||
253 | byte = counter_number << 6; | |
67d83b4f BP |
254 | byte |= 0x30; /* load low then high byte */ |
255 | byte |= (mode << 1); /* set counter mode */ | |
703afc38 | 256 | outb(byte, base_address + (i8254_control_reg << regshift)); |
67d83b4f | 257 | byte = count & 0xff; /* lsb of counter value */ |
703afc38 | 258 | outb(byte, base_address + (counter_number << regshift)); |
67d83b4f | 259 | byte = (count >> 8) & 0xff; /* msb of counter value */ |
703afc38 DS |
260 | outb(byte, base_address + (counter_number << regshift)); |
261 | ||
262 | return 0; | |
263 | } | |
264 | ||
265 | static inline int i8254_mm_load(void *base_address, unsigned int regshift, | |
0a85b6f0 MT |
266 | unsigned int counter_number, unsigned int count, |
267 | unsigned int mode) | |
703afc38 DS |
268 | { |
269 | unsigned int byte; | |
270 | ||
271 | if (counter_number > 2) | |
272 | return -1; | |
273 | if (count > 0xffff) | |
274 | return -1; | |
275 | if (mode > 5) | |
276 | return -1; | |
277 | if ((mode == 2 || mode == 3) && count == 1) | |
278 | return -1; | |
279 | ||
280 | byte = counter_number << 6; | |
67d83b4f BP |
281 | byte |= 0x30; /* load low then high byte */ |
282 | byte |= (mode << 1); /* set counter mode */ | |
703afc38 | 283 | writeb(byte, base_address + (i8254_control_reg << regshift)); |
67d83b4f | 284 | byte = count & 0xff; /* lsb of counter value */ |
703afc38 | 285 | writeb(byte, base_address + (counter_number << regshift)); |
67d83b4f | 286 | byte = (count >> 8) & 0xff; /* msb of counter value */ |
703afc38 DS |
287 | writeb(byte, base_address + (counter_number << regshift)); |
288 | ||
289 | return 0; | |
290 | } | |
291 | ||
292 | /* Returns 16 bit counter value, should work for 8253 also.*/ | |
293 | static inline int i8254_read(unsigned long base_address, unsigned int regshift, | |
0a85b6f0 | 294 | unsigned int counter_number) |
703afc38 DS |
295 | { |
296 | unsigned int byte; | |
297 | int ret; | |
298 | ||
299 | if (counter_number > 2) | |
300 | return -1; | |
301 | ||
67d83b4f | 302 | /* latch counter */ |
703afc38 DS |
303 | byte = counter_number << 6; |
304 | outb(byte, base_address + (i8254_control_reg << regshift)); | |
305 | ||
67d83b4f | 306 | /* read lsb */ |
703afc38 | 307 | ret = inb(base_address + (counter_number << regshift)); |
67d83b4f | 308 | /* read msb */ |
703afc38 DS |
309 | ret += inb(base_address + (counter_number << regshift)) << 8; |
310 | ||
311 | return ret; | |
312 | } | |
313 | ||
314 | static inline int i8254_mm_read(void *base_address, unsigned int regshift, | |
0a85b6f0 | 315 | unsigned int counter_number) |
703afc38 DS |
316 | { |
317 | unsigned int byte; | |
318 | int ret; | |
319 | ||
320 | if (counter_number > 2) | |
321 | return -1; | |
322 | ||
67d83b4f | 323 | /* latch counter */ |
703afc38 DS |
324 | byte = counter_number << 6; |
325 | writeb(byte, base_address + (i8254_control_reg << regshift)); | |
326 | ||
67d83b4f | 327 | /* read lsb */ |
703afc38 | 328 | ret = readb(base_address + (counter_number << regshift)); |
67d83b4f | 329 | /* read msb */ |
703afc38 DS |
330 | ret += readb(base_address + (counter_number << regshift)) << 8; |
331 | ||
332 | return ret; | |
333 | } | |
334 | ||
335 | /* Loads 16 bit initial counter value, should work for 8253 also. */ | |
336 | static inline void i8254_write(unsigned long base_address, | |
0a85b6f0 MT |
337 | unsigned int regshift, |
338 | unsigned int counter_number, unsigned int count) | |
703afc38 DS |
339 | { |
340 | unsigned int byte; | |
341 | ||
342 | if (counter_number > 2) | |
343 | return; | |
344 | ||
67d83b4f | 345 | byte = count & 0xff; /* lsb of counter value */ |
703afc38 | 346 | outb(byte, base_address + (counter_number << regshift)); |
67d83b4f | 347 | byte = (count >> 8) & 0xff; /* msb of counter value */ |
703afc38 DS |
348 | outb(byte, base_address + (counter_number << regshift)); |
349 | } | |
350 | ||
351 | static inline void i8254_mm_write(void *base_address, | |
0a85b6f0 MT |
352 | unsigned int regshift, |
353 | unsigned int counter_number, | |
354 | unsigned int count) | |
703afc38 DS |
355 | { |
356 | unsigned int byte; | |
357 | ||
358 | if (counter_number > 2) | |
359 | return; | |
360 | ||
67d83b4f | 361 | byte = count & 0xff; /* lsb of counter value */ |
703afc38 | 362 | writeb(byte, base_address + (counter_number << regshift)); |
67d83b4f | 363 | byte = (count >> 8) & 0xff; /* msb of counter value */ |
703afc38 DS |
364 | writeb(byte, base_address + (counter_number << regshift)); |
365 | } | |
366 | ||
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 | |
371 | * OR'ed with: | |
372 | * I8254_BCD, I8254_BINARY | |
373 | */ | |
374 | static inline int i8254_set_mode(unsigned long base_address, | |
0a85b6f0 MT |
375 | unsigned int regshift, |
376 | unsigned int counter_number, unsigned int mode) | |
703afc38 DS |
377 | { |
378 | unsigned int byte; | |
379 | ||
380 | if (counter_number > 2) | |
381 | return -1; | |
382 | if (mode > (I8254_MODE5 | I8254_BINARY)) | |
383 | return -1; | |
384 | ||
385 | byte = counter_number << 6; | |
67d83b4f BP |
386 | byte |= 0x30; /* load low then high byte */ |
387 | byte |= mode; /* set counter mode and BCD|binary */ | |
703afc38 DS |
388 | outb(byte, base_address + (i8254_control_reg << regshift)); |
389 | ||
390 | return 0; | |
391 | } | |
392 | ||
393 | static inline int i8254_mm_set_mode(void *base_address, | |
0a85b6f0 MT |
394 | unsigned int regshift, |
395 | unsigned int counter_number, | |
396 | unsigned int mode) | |
703afc38 DS |
397 | { |
398 | unsigned int byte; | |
399 | ||
400 | if (counter_number > 2) | |
401 | return -1; | |
402 | if (mode > (I8254_MODE5 | I8254_BINARY)) | |
403 | return -1; | |
404 | ||
405 | byte = counter_number << 6; | |
67d83b4f BP |
406 | byte |= 0x30; /* load low then high byte */ |
407 | byte |= mode; /* set counter mode and BCD|binary */ | |
703afc38 DS |
408 | writeb(byte, base_address + (i8254_control_reg << regshift)); |
409 | ||
410 | return 0; | |
411 | } | |
412 | ||
413 | static inline int i8254_status(unsigned long base_address, | |
0a85b6f0 MT |
414 | unsigned int regshift, |
415 | unsigned int counter_number) | |
703afc38 DS |
416 | { |
417 | outb(0xE0 | (2 << counter_number), | |
0a85b6f0 | 418 | base_address + (i8254_control_reg << regshift)); |
703afc38 DS |
419 | return inb(base_address + (counter_number << regshift)); |
420 | } | |
421 | ||
422 | static inline int i8254_mm_status(void *base_address, | |
0a85b6f0 MT |
423 | unsigned int regshift, |
424 | unsigned int counter_number) | |
703afc38 DS |
425 | { |
426 | writeb(0xE0 | (2 << counter_number), | |
0a85b6f0 | 427 | base_address + (i8254_control_reg << regshift)); |
703afc38 DS |
428 | return readb(base_address + (counter_number << regshift)); |
429 | } | |
430 | ||
431 | #endif | |
432 | ||
433 | #endif |