Commit | Line | Data |
---|---|---|
703afc38 | 1 | /* |
797f8894 IA |
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 | */ | |
703afc38 DS |
18 | |
19 | #ifndef _8253_H | |
20 | #define _8253_H | |
21 | ||
703afc38 | 22 | #include "../comedi.h" |
703afc38 | 23 | |
cb9cfd7e HS |
24 | /* |
25 | * Common oscillator base values in nanoseconds | |
26 | */ | |
27 | #define I8254_OSC_BASE_10MHZ 100 | |
28 | #define I8254_OSC_BASE_5MHZ 200 | |
29 | #define I8254_OSC_BASE_4MHZ 250 | |
30 | #define I8254_OSC_BASE_2MHZ 500 | |
31 | #define I8254_OSC_BASE_1MHZ 1000 | |
32 | ||
d33f8858 HS |
33 | static inline void i8253_cascade_ns_to_timer(int i8253_osc_base, |
34 | unsigned int *d1, | |
35 | unsigned int *d2, | |
36 | unsigned int *nanosec, | |
a207c12f | 37 | unsigned int flags) |
703afc38 DS |
38 | { |
39 | unsigned int divider; | |
40 | unsigned int div1, div2; | |
41 | unsigned int div1_glb, div2_glb, ns_glb; | |
42 | unsigned int div1_lub, div2_lub, ns_lub; | |
43 | unsigned int ns; | |
44 | unsigned int start; | |
45 | unsigned int ns_low, ns_high; | |
46 | static const unsigned int max_count = 0x10000; | |
6400fb71 IA |
47 | /* |
48 | * exit early if everything is already correct (this can save time | |
703afc38 | 49 | * since this function may be called repeatedly during command tests |
6400fb71 IA |
50 | * and execution) |
51 | */ | |
703afc38 DS |
52 | div1 = *d1 ? *d1 : max_count; |
53 | div2 = *d2 ? *d2 : max_count; | |
54 | divider = div1 * div2; | |
55 | if (div1 * div2 * i8253_osc_base == *nanosec && | |
0a85b6f0 MT |
56 | div1 > 1 && div1 <= max_count && div2 > 1 && div2 <= max_count && |
57 | /* check for overflow */ | |
58 | divider > div1 && divider > div2 && | |
59 | divider * i8253_osc_base > divider && | |
60 | divider * i8253_osc_base > i8253_osc_base) { | |
703afc38 DS |
61 | return; |
62 | } | |
63 | ||
64 | divider = *nanosec / i8253_osc_base; | |
65 | ||
66 | div1_lub = div2_lub = 0; | |
67 | div1_glb = div2_glb = 0; | |
68 | ||
69 | ns_glb = 0; | |
70 | ns_lub = 0xffffffff; | |
71 | ||
72 | div2 = max_count; | |
73 | start = divider / div2; | |
74 | if (start < 2) | |
75 | start = 2; | |
76 | for (div1 = start; div1 <= divider / div1 + 1 && div1 <= max_count; | |
0a85b6f0 | 77 | div1++) { |
703afc38 | 78 | for (div2 = divider / div1; |
0a85b6f0 MT |
79 | div1 * div2 <= divider + div1 + 1 && div2 <= max_count; |
80 | div2++) { | |
703afc38 DS |
81 | ns = i8253_osc_base * div1 * div2; |
82 | if (ns <= *nanosec && ns > ns_glb) { | |
83 | ns_glb = ns; | |
84 | div1_glb = div1; | |
85 | div2_glb = div2; | |
86 | } | |
87 | if (ns >= *nanosec && ns < ns_lub) { | |
88 | ns_lub = ns; | |
89 | div1_lub = div1; | |
90 | div2_lub = div2; | |
91 | } | |
92 | } | |
93 | } | |
94 | ||
8a512e41 IA |
95 | switch (flags & CMDF_ROUND_MASK) { |
96 | case CMDF_ROUND_NEAREST: | |
703afc38 DS |
97 | default: |
98 | ns_high = div1_lub * div2_lub * i8253_osc_base; | |
99 | ns_low = div1_glb * div2_glb * i8253_osc_base; | |
100 | if (ns_high - *nanosec < *nanosec - ns_low) { | |
101 | div1 = div1_lub; | |
102 | div2 = div2_lub; | |
103 | } else { | |
104 | div1 = div1_glb; | |
105 | div2 = div2_glb; | |
106 | } | |
107 | break; | |
8a512e41 | 108 | case CMDF_ROUND_UP: |
703afc38 DS |
109 | div1 = div1_lub; |
110 | div2 = div2_lub; | |
111 | break; | |
8a512e41 | 112 | case CMDF_ROUND_DOWN: |
703afc38 DS |
113 | div1 = div1_glb; |
114 | div2 = div2_glb; | |
115 | break; | |
116 | } | |
117 | ||
118 | *nanosec = div1 * div2 * i8253_osc_base; | |
5cc1e33c | 119 | /* masking is done since counter maps zero to 0x10000 */ |
155b44aa | 120 | *d1 = div1 & 0xffff; |
703afc38 | 121 | *d2 = div2 & 0xffff; |
703afc38 DS |
122 | } |
123 | ||
124 | #ifndef CMDTEST | |
6400fb71 IA |
125 | /* |
126 | * i8254_load programs 8254 counter chip. It should also work for the 8253. | |
a88e29cf GH |
127 | * base_address is the lowest io address |
128 | * for the chip (the address of counter 0). | |
703afc38 DS |
129 | * counter_number is the counter you want to load (0,1 or 2) |
130 | * count is the number to load into the counter. | |
131 | * | |
132 | * You probably want to use mode 2. | |
133 | * | |
134 | * Use i8254_mm_load() if you board uses memory-mapped io, it is | |
135 | * the same as i8254_load() except it uses writeb() instead of outb(). | |
136 | * | |
137 | * Neither i8254_load() or i8254_read() do their loading/reading | |
138 | * atomically. The 16 bit read/writes are performed with two successive | |
139 | * 8 bit read/writes. So if two parts of your driver do a load/read on | |
140 | * the same counter, it may be necessary to protect these functions | |
141 | * with a spinlock. | |
142 | * | |
143 | * FMH | |
144 | */ | |
145 | ||
146 | #define i8254_control_reg 3 | |
147 | ||
148 | static inline int i8254_load(unsigned long base_address, unsigned int regshift, | |
0a85b6f0 MT |
149 | unsigned int counter_number, unsigned int count, |
150 | unsigned int mode) | |
703afc38 DS |
151 | { |
152 | unsigned int byte; | |
153 | ||
154 | if (counter_number > 2) | |
155 | return -1; | |
156 | if (count > 0xffff) | |
157 | return -1; | |
158 | if (mode > 5) | |
159 | return -1; | |
160 | if ((mode == 2 || mode == 3) && count == 1) | |
161 | return -1; | |
162 | ||
163 | byte = counter_number << 6; | |
5cc1e33c IA |
164 | byte |= 0x30; /* load low then high byte */ |
165 | byte |= (mode << 1); /* set counter mode */ | |
703afc38 | 166 | outb(byte, base_address + (i8254_control_reg << regshift)); |
5cc1e33c | 167 | byte = count & 0xff; /* lsb of counter value */ |
703afc38 | 168 | outb(byte, base_address + (counter_number << regshift)); |
5cc1e33c | 169 | byte = (count >> 8) & 0xff; /* msb of counter value */ |
703afc38 DS |
170 | outb(byte, base_address + (counter_number << regshift)); |
171 | ||
172 | return 0; | |
173 | } | |
174 | ||
5743aaac HS |
175 | static inline int i8254_mm_load(void __iomem *base_address, |
176 | unsigned int regshift, | |
177 | unsigned int counter_number, | |
178 | unsigned int count, | |
0a85b6f0 | 179 | unsigned int mode) |
703afc38 DS |
180 | { |
181 | unsigned int byte; | |
182 | ||
183 | if (counter_number > 2) | |
184 | return -1; | |
185 | if (count > 0xffff) | |
186 | return -1; | |
187 | if (mode > 5) | |
188 | return -1; | |
189 | if ((mode == 2 || mode == 3) && count == 1) | |
190 | return -1; | |
191 | ||
192 | byte = counter_number << 6; | |
5cc1e33c IA |
193 | byte |= 0x30; /* load low then high byte */ |
194 | byte |= (mode << 1); /* set counter mode */ | |
703afc38 | 195 | writeb(byte, base_address + (i8254_control_reg << regshift)); |
5cc1e33c | 196 | byte = count & 0xff; /* lsb of counter value */ |
703afc38 | 197 | writeb(byte, base_address + (counter_number << regshift)); |
5cc1e33c | 198 | byte = (count >> 8) & 0xff; /* msb of counter value */ |
703afc38 DS |
199 | writeb(byte, base_address + (counter_number << regshift)); |
200 | ||
201 | return 0; | |
202 | } | |
203 | ||
5cc1e33c | 204 | /* Returns 16 bit counter value, should work for 8253 also. */ |
703afc38 | 205 | static inline int i8254_read(unsigned long base_address, unsigned int regshift, |
0a85b6f0 | 206 | unsigned int counter_number) |
703afc38 DS |
207 | { |
208 | unsigned int byte; | |
209 | int ret; | |
210 | ||
211 | if (counter_number > 2) | |
212 | return -1; | |
213 | ||
5cc1e33c | 214 | /* latch counter */ |
703afc38 DS |
215 | byte = counter_number << 6; |
216 | outb(byte, base_address + (i8254_control_reg << regshift)); | |
217 | ||
5cc1e33c | 218 | /* read lsb */ |
703afc38 | 219 | ret = inb(base_address + (counter_number << regshift)); |
5cc1e33c | 220 | /* read msb */ |
703afc38 DS |
221 | ret += inb(base_address + (counter_number << regshift)) << 8; |
222 | ||
223 | return ret; | |
224 | } | |
225 | ||
5743aaac HS |
226 | static inline int i8254_mm_read(void __iomem *base_address, |
227 | unsigned int regshift, | |
0a85b6f0 | 228 | unsigned int counter_number) |
703afc38 DS |
229 | { |
230 | unsigned int byte; | |
231 | int ret; | |
232 | ||
233 | if (counter_number > 2) | |
234 | return -1; | |
235 | ||
5cc1e33c | 236 | /* latch counter */ |
703afc38 DS |
237 | byte = counter_number << 6; |
238 | writeb(byte, base_address + (i8254_control_reg << regshift)); | |
239 | ||
5cc1e33c | 240 | /* read lsb */ |
703afc38 | 241 | ret = readb(base_address + (counter_number << regshift)); |
5cc1e33c | 242 | /* read msb */ |
703afc38 DS |
243 | ret += readb(base_address + (counter_number << regshift)) << 8; |
244 | ||
245 | return ret; | |
246 | } | |
247 | ||
248 | /* Loads 16 bit initial counter value, should work for 8253 also. */ | |
249 | static inline void i8254_write(unsigned long base_address, | |
0a85b6f0 MT |
250 | unsigned int regshift, |
251 | unsigned int counter_number, unsigned int count) | |
703afc38 DS |
252 | { |
253 | unsigned int byte; | |
254 | ||
255 | if (counter_number > 2) | |
256 | return; | |
257 | ||
5cc1e33c | 258 | byte = count & 0xff; /* lsb of counter value */ |
703afc38 | 259 | outb(byte, base_address + (counter_number << regshift)); |
5cc1e33c | 260 | byte = (count >> 8) & 0xff; /* msb of counter value */ |
703afc38 DS |
261 | outb(byte, base_address + (counter_number << regshift)); |
262 | } | |
263 | ||
5743aaac | 264 | static inline void i8254_mm_write(void __iomem *base_address, |
0a85b6f0 MT |
265 | unsigned int regshift, |
266 | unsigned int counter_number, | |
267 | unsigned int count) | |
703afc38 DS |
268 | { |
269 | unsigned int byte; | |
270 | ||
271 | if (counter_number > 2) | |
272 | return; | |
273 | ||
5cc1e33c | 274 | byte = count & 0xff; /* lsb of counter value */ |
703afc38 | 275 | writeb(byte, base_address + (counter_number << regshift)); |
5cc1e33c | 276 | byte = (count >> 8) & 0xff; /* msb of counter value */ |
703afc38 DS |
277 | writeb(byte, base_address + (counter_number << regshift)); |
278 | } | |
279 | ||
6400fb71 IA |
280 | /* |
281 | * Set counter mode, should work for 8253 also. | |
703afc38 DS |
282 | * Note: the 'mode' value is different to that for i8254_load() and comes |
283 | * from the INSN_CONFIG_8254_SET_MODE command: | |
284 | * I8254_MODE0, I8254_MODE1, ..., I8254_MODE5 | |
285 | * OR'ed with: | |
286 | * I8254_BCD, I8254_BINARY | |
287 | */ | |
288 | static inline int i8254_set_mode(unsigned long base_address, | |
0a85b6f0 MT |
289 | unsigned int regshift, |
290 | unsigned int counter_number, unsigned int mode) | |
703afc38 DS |
291 | { |
292 | unsigned int byte; | |
293 | ||
294 | if (counter_number > 2) | |
295 | return -1; | |
7d52477e | 296 | if (mode > (I8254_MODE5 | I8254_BCD)) |
703afc38 DS |
297 | return -1; |
298 | ||
299 | byte = counter_number << 6; | |
5cc1e33c IA |
300 | byte |= 0x30; /* load low then high byte */ |
301 | byte |= mode; /* set counter mode and BCD|binary */ | |
703afc38 DS |
302 | outb(byte, base_address + (i8254_control_reg << regshift)); |
303 | ||
304 | return 0; | |
305 | } | |
306 | ||
5743aaac | 307 | static inline int i8254_mm_set_mode(void __iomem *base_address, |
0a85b6f0 MT |
308 | unsigned int regshift, |
309 | unsigned int counter_number, | |
310 | unsigned int mode) | |
703afc38 DS |
311 | { |
312 | unsigned int byte; | |
313 | ||
314 | if (counter_number > 2) | |
315 | return -1; | |
7d52477e | 316 | if (mode > (I8254_MODE5 | I8254_BCD)) |
703afc38 DS |
317 | return -1; |
318 | ||
319 | byte = counter_number << 6; | |
5cc1e33c IA |
320 | byte |= 0x30; /* load low then high byte */ |
321 | byte |= mode; /* set counter mode and BCD|binary */ | |
703afc38 DS |
322 | writeb(byte, base_address + (i8254_control_reg << regshift)); |
323 | ||
324 | return 0; | |
325 | } | |
326 | ||
327 | static inline int i8254_status(unsigned long base_address, | |
0a85b6f0 MT |
328 | unsigned int regshift, |
329 | unsigned int counter_number) | |
703afc38 DS |
330 | { |
331 | outb(0xE0 | (2 << counter_number), | |
0a85b6f0 | 332 | base_address + (i8254_control_reg << regshift)); |
703afc38 DS |
333 | return inb(base_address + (counter_number << regshift)); |
334 | } | |
335 | ||
5743aaac | 336 | static inline int i8254_mm_status(void __iomem *base_address, |
0a85b6f0 MT |
337 | unsigned int regshift, |
338 | unsigned int counter_number) | |
703afc38 DS |
339 | { |
340 | writeb(0xE0 | (2 << counter_number), | |
0a85b6f0 | 341 | base_address + (i8254_control_reg << regshift)); |
703afc38 DS |
342 | return readb(base_address + (counter_number << regshift)); |
343 | } | |
344 | ||
345 | #endif | |
346 | ||
347 | #endif |