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 
27 #include "../comedi.h"
28 
29 #define i8253_cascade_ns_to_timer i8253_cascade_ns_to_timer_2div
30 
i8253_cascade_ns_to_timer_2div_old(int i8253_osc_base,unsigned int * d1,unsigned int * d2,unsigned int * nanosec,int round_mode)31 static inline void i8253_cascade_ns_to_timer_2div_old(int i8253_osc_base,
32 						      unsigned int *d1,
33 						      unsigned int *d2,
34 						      unsigned int *nanosec,
35 						      int round_mode)
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 
i8253_cascade_ns_to_timer_power(int i8253_osc_base,unsigned int * d1,unsigned int * d2,unsigned int * nanosec,int round_mode)82 static inline void i8253_cascade_ns_to_timer_power(int i8253_osc_base,
83 						   unsigned int *d1,
84 						   unsigned int *d2,
85 						   unsigned int *nanosec,
86 						   int round_mode)
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 
i8253_cascade_ns_to_timer_2div(int i8253_osc_base,unsigned int * d1,unsigned int * d2,unsigned int * nanosec,int round_mode)124 static inline void i8253_cascade_ns_to_timer_2div(int i8253_osc_base,
125 						  unsigned int *d1,
126 						  unsigned int *d2,
127 						  unsigned int *nanosec,
128 						  int round_mode)
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 &&
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) {
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;
166 	     div1++) {
167 		for (div2 = divider / div1;
168 		     div1 * div2 <= divider + div1 + 1 && div2 <= max_count;
169 		     div2++) {
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;
209 	/*  masking is done since counter maps zero to 0x10000 */
210 	*d1 = div1 & 0xffff;
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.
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.
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 
i8254_load(unsigned long base_address,unsigned int regshift,unsigned int counter_number,unsigned int count,unsigned int mode)238 static inline int i8254_load(unsigned long base_address, unsigned int regshift,
239 			     unsigned int counter_number, unsigned int count,
240 			     unsigned int mode)
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;
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));
261 
262 	return 0;
263 }
264 
i8254_mm_load(void * base_address,unsigned int regshift,unsigned int counter_number,unsigned int count,unsigned int mode)265 static inline int i8254_mm_load(void *base_address, unsigned int regshift,
266 				unsigned int counter_number, unsigned int count,
267 				unsigned int mode)
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;
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));
288 
289 	return 0;
290 }
291 
292 /* Returns 16 bit counter value, should work for 8253 also.*/
i8254_read(unsigned long base_address,unsigned int regshift,unsigned int counter_number)293 static inline int i8254_read(unsigned long base_address, unsigned int regshift,
294 			     unsigned int counter_number)
295 {
296 	unsigned int byte;
297 	int ret;
298 
299 	if (counter_number > 2)
300 		return -1;
301 
302 	/*  latch counter */
303 	byte = counter_number << 6;
304 	outb(byte, base_address + (i8254_control_reg << regshift));
305 
306 	/*  read lsb */
307 	ret = inb(base_address + (counter_number << regshift));
308 	/*  read msb */
309 	ret += inb(base_address + (counter_number << regshift)) << 8;
310 
311 	return ret;
312 }
313 
i8254_mm_read(void * base_address,unsigned int regshift,unsigned int counter_number)314 static inline int i8254_mm_read(void *base_address, unsigned int regshift,
315 				unsigned int counter_number)
316 {
317 	unsigned int byte;
318 	int ret;
319 
320 	if (counter_number > 2)
321 		return -1;
322 
323 	/*  latch counter */
324 	byte = counter_number << 6;
325 	writeb(byte, base_address + (i8254_control_reg << regshift));
326 
327 	/*  read lsb */
328 	ret = readb(base_address + (counter_number << regshift));
329 	/*  read msb */
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. */
i8254_write(unsigned long base_address,unsigned int regshift,unsigned int counter_number,unsigned int count)336 static inline void i8254_write(unsigned long base_address,
337 			       unsigned int regshift,
338 			       unsigned int counter_number, unsigned int count)
339 {
340 	unsigned int byte;
341 
342 	if (counter_number > 2)
343 		return;
344 
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));
349 }
350 
i8254_mm_write(void * base_address,unsigned int regshift,unsigned int counter_number,unsigned int count)351 static inline void i8254_mm_write(void *base_address,
352 				  unsigned int regshift,
353 				  unsigned int counter_number,
354 				  unsigned int count)
355 {
356 	unsigned int byte;
357 
358 	if (counter_number > 2)
359 		return;
360 
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));
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  */
i8254_set_mode(unsigned long base_address,unsigned int regshift,unsigned int counter_number,unsigned int mode)374 static inline int i8254_set_mode(unsigned long base_address,
375 				 unsigned int regshift,
376 				 unsigned int counter_number, unsigned int mode)
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;
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));
389 
390 	return 0;
391 }
392 
i8254_mm_set_mode(void * base_address,unsigned int regshift,unsigned int counter_number,unsigned int mode)393 static inline int i8254_mm_set_mode(void *base_address,
394 				    unsigned int regshift,
395 				    unsigned int counter_number,
396 				    unsigned int mode)
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;
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));
409 
410 	return 0;
411 }
412 
i8254_status(unsigned long base_address,unsigned int regshift,unsigned int counter_number)413 static inline int i8254_status(unsigned long base_address,
414 			       unsigned int regshift,
415 			       unsigned int counter_number)
416 {
417 	outb(0xE0 | (2 << counter_number),
418 	     base_address + (i8254_control_reg << regshift));
419 	return inb(base_address + (counter_number << regshift));
420 }
421 
i8254_mm_status(void * base_address,unsigned int regshift,unsigned int counter_number)422 static inline int i8254_mm_status(void *base_address,
423 				  unsigned int regshift,
424 				  unsigned int counter_number)
425 {
426 	writeb(0xE0 | (2 << counter_number),
427 	       base_address + (i8254_control_reg << regshift));
428 	return readb(base_address + (counter_number << regshift));
429 }
430 
431 #endif
432 
433 #endif
434