8253.h revision 155b44aae0ed9a0f9aecc8c528ba5bc3b26f8377
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
31static 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
82static 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
124static 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 for the chip (the address of counter 0).
218 * counter_number is the counter you want to load (0,1 or 2)
219 * count is the number to load into the counter.
220 *
221 * You probably want to use mode 2.
222 *
223 * Use i8254_mm_load() if you board uses memory-mapped io, it is
224 * the same as i8254_load() except it uses writeb() instead of outb().
225 *
226 * Neither i8254_load() or i8254_read() do their loading/reading
227 * atomically.  The 16 bit read/writes are performed with two successive
228 * 8 bit read/writes.  So if two parts of your driver do a load/read on
229 * the same counter, it may be necessary to protect these functions
230 * with a spinlock.
231 *
232 * FMH
233 */
234
235#define i8254_control_reg	3
236
237static inline int i8254_load(unsigned long base_address, unsigned int regshift,
238			     unsigned int counter_number, unsigned int count,
239			     unsigned int mode)
240{
241	unsigned int byte;
242
243	if (counter_number > 2)
244		return -1;
245	if (count > 0xffff)
246		return -1;
247	if (mode > 5)
248		return -1;
249	if ((mode == 2 || mode == 3) && count == 1)
250		return -1;
251
252	byte = counter_number << 6;
253	byte |= 0x30;		/*  load low then high byte */
254	byte |= (mode << 1);	/*  set counter mode */
255	outb(byte, base_address + (i8254_control_reg << regshift));
256	byte = count & 0xff;	/*  lsb of counter value */
257	outb(byte, base_address + (counter_number << regshift));
258	byte = (count >> 8) & 0xff;	/*  msb of counter value */
259	outb(byte, base_address + (counter_number << regshift));
260
261	return 0;
262}
263
264static inline int i8254_mm_load(void *base_address, unsigned int regshift,
265				unsigned int counter_number, unsigned int count,
266				unsigned int mode)
267{
268	unsigned int byte;
269
270	if (counter_number > 2)
271		return -1;
272	if (count > 0xffff)
273		return -1;
274	if (mode > 5)
275		return -1;
276	if ((mode == 2 || mode == 3) && count == 1)
277		return -1;
278
279	byte = counter_number << 6;
280	byte |= 0x30;		/*  load low then high byte */
281	byte |= (mode << 1);	/*  set counter mode */
282	writeb(byte, base_address + (i8254_control_reg << regshift));
283	byte = count & 0xff;	/*  lsb of counter value */
284	writeb(byte, base_address + (counter_number << regshift));
285	byte = (count >> 8) & 0xff;	/*  msb of counter value */
286	writeb(byte, base_address + (counter_number << regshift));
287
288	return 0;
289}
290
291/* Returns 16 bit counter value, should work for 8253 also.*/
292static inline int i8254_read(unsigned long base_address, unsigned int regshift,
293			     unsigned int counter_number)
294{
295	unsigned int byte;
296	int ret;
297
298	if (counter_number > 2)
299		return -1;
300
301	/*  latch counter */
302	byte = counter_number << 6;
303	outb(byte, base_address + (i8254_control_reg << regshift));
304
305	/*  read lsb */
306	ret = inb(base_address + (counter_number << regshift));
307	/*  read msb */
308	ret += inb(base_address + (counter_number << regshift)) << 8;
309
310	return ret;
311}
312
313static inline int i8254_mm_read(void *base_address, unsigned int regshift,
314				unsigned int counter_number)
315{
316	unsigned int byte;
317	int ret;
318
319	if (counter_number > 2)
320		return -1;
321
322	/*  latch counter */
323	byte = counter_number << 6;
324	writeb(byte, base_address + (i8254_control_reg << regshift));
325
326	/*  read lsb */
327	ret = readb(base_address + (counter_number << regshift));
328	/*  read msb */
329	ret += readb(base_address + (counter_number << regshift)) << 8;
330
331	return ret;
332}
333
334/* Loads 16 bit initial counter value, should work for 8253 also. */
335static inline void i8254_write(unsigned long base_address,
336			       unsigned int regshift,
337			       unsigned int counter_number, unsigned int count)
338{
339	unsigned int byte;
340
341	if (counter_number > 2)
342		return;
343
344	byte = count & 0xff;	/*  lsb of counter value */
345	outb(byte, base_address + (counter_number << regshift));
346	byte = (count >> 8) & 0xff;	/*  msb of counter value */
347	outb(byte, base_address + (counter_number << regshift));
348}
349
350static inline void i8254_mm_write(void *base_address,
351				  unsigned int regshift,
352				  unsigned int counter_number,
353				  unsigned int count)
354{
355	unsigned int byte;
356
357	if (counter_number > 2)
358		return;
359
360	byte = count & 0xff;	/*  lsb of counter value */
361	writeb(byte, base_address + (counter_number << regshift));
362	byte = (count >> 8) & 0xff;	/*  msb of counter value */
363	writeb(byte, base_address + (counter_number << regshift));
364}
365
366/* Set counter mode, should work for 8253 also.
367 * Note: the 'mode' value is different to that for i8254_load() and comes
368 * from the INSN_CONFIG_8254_SET_MODE command:
369 *   I8254_MODE0, I8254_MODE1, ..., I8254_MODE5
370 * OR'ed with:
371 *   I8254_BCD, I8254_BINARY
372 */
373static inline int i8254_set_mode(unsigned long base_address,
374				 unsigned int regshift,
375				 unsigned int counter_number, unsigned int mode)
376{
377	unsigned int byte;
378
379	if (counter_number > 2)
380		return -1;
381	if (mode > (I8254_MODE5 | I8254_BINARY))
382		return -1;
383
384	byte = counter_number << 6;
385	byte |= 0x30;		/*  load low then high byte */
386	byte |= mode;		/*  set counter mode and BCD|binary */
387	outb(byte, base_address + (i8254_control_reg << regshift));
388
389	return 0;
390}
391
392static inline int i8254_mm_set_mode(void *base_address,
393				    unsigned int regshift,
394				    unsigned int counter_number,
395				    unsigned int mode)
396{
397	unsigned int byte;
398
399	if (counter_number > 2)
400		return -1;
401	if (mode > (I8254_MODE5 | I8254_BINARY))
402		return -1;
403
404	byte = counter_number << 6;
405	byte |= 0x30;		/*  load low then high byte */
406	byte |= mode;		/*  set counter mode and BCD|binary */
407	writeb(byte, base_address + (i8254_control_reg << regshift));
408
409	return 0;
410}
411
412static inline int i8254_status(unsigned long base_address,
413			       unsigned int regshift,
414			       unsigned int counter_number)
415{
416	outb(0xE0 | (2 << counter_number),
417	     base_address + (i8254_control_reg << regshift));
418	return inb(base_address + (counter_number << regshift));
419}
420
421static inline int i8254_mm_status(void *base_address,
422				  unsigned int regshift,
423				  unsigned int counter_number)
424{
425	writeb(0xE0 | (2 << counter_number),
426	       base_address + (i8254_control_reg << regshift));
427	return readb(base_address + (counter_number << regshift));
428}
429
430#endif
431
432#endif
433