8253.h revision a88e29cfd8e59c82cf63440117bd1d61761f0f87
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
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
238static 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
265static 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.*/
293static 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
314static 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. */
336static 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
351static 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 */
374static 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
393static 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
413static 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
422static 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