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