8253.h revision 9096a4eaf4a608d9465f993f48ad9d6c38144b8e
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, unsigned int *d2, unsigned int *nanosec, 33 int round_mode) 34{ 35 int divider; 36 int div1, div2; 37 int div1_glb, div2_glb, ns_glb; 38 int div1_lub, div2_lub, ns_lub; 39 int ns; 40 41 divider = (*nanosec + i8253_osc_base / 2) / i8253_osc_base; 42 43 /* find 2 integers 1<={x,y}<=65536 such that x*y is 44 close to divider */ 45 46 div1_lub = div2_lub = 0; 47 div1_glb = div2_glb = 0; 48 49 ns_glb = 0; 50 ns_lub = 0xffffffff; 51 52 div2 = 0x10000; 53 for (div1 = divider / 65536 + 1; div1 < div2; div1++) { 54 div2 = divider / div1; 55 56 ns = i8253_osc_base * div1 * div2; 57 if (ns <= *nanosec && ns > ns_glb) { 58 ns_glb = ns; 59 div1_glb = div1; 60 div2_glb = div2; 61 } 62 63 div2++; 64 if (div2 <= 65536) { 65 ns = i8253_osc_base * div1 * div2; 66 if (ns > *nanosec && ns < ns_lub) { 67 ns_lub = ns; 68 div1_lub = div1; 69 div2_lub = div2; 70 } 71 } 72 } 73 74 *nanosec = div1_lub * div2_lub * i8253_osc_base; 75 *d1 = div1_lub & 0xffff; 76 *d2 = div2_lub & 0xffff; 77 return; 78} 79 80static inline void i8253_cascade_ns_to_timer_power(int i8253_osc_base, 81 unsigned int *d1, unsigned int *d2, unsigned int *nanosec, 82 int round_mode) 83{ 84 int div1, div2; 85 int base; 86 87 for (div1 = 2; div1 <= (1 << 16); div1 <<= 1) { 88 base = i8253_osc_base * div1; 89 round_mode &= TRIG_ROUND_MASK; 90 switch (round_mode) { 91 case TRIG_ROUND_NEAREST: 92 default: 93 div2 = (*nanosec + base / 2) / base; 94 break; 95 case TRIG_ROUND_DOWN: 96 div2 = (*nanosec) / base; 97 break; 98 case TRIG_ROUND_UP: 99 div2 = (*nanosec + base - 1) / base; 100 break; 101 } 102 if (div2 < 2) 103 div2 = 2; 104 if (div2 <= 65536) { 105 *nanosec = div2 * base; 106 *d1 = div1 & 0xffff; 107 *d2 = div2 & 0xffff; 108 return; 109 } 110 } 111 112 /* shouldn't get here */ 113 div1 = 0x10000; 114 div2 = 0x10000; 115 *nanosec = div1 * div2 * i8253_osc_base; 116 *d1 = div1 & 0xffff; 117 *d2 = div2 & 0xffff; 118} 119 120static inline void i8253_cascade_ns_to_timer_2div(int i8253_osc_base, 121 unsigned int *d1, unsigned int *d2, unsigned int *nanosec, 122 int round_mode) 123{ 124 unsigned int divider; 125 unsigned int div1, div2; 126 unsigned int div1_glb, div2_glb, ns_glb; 127 unsigned int div1_lub, div2_lub, ns_lub; 128 unsigned int ns; 129 unsigned int start; 130 unsigned int ns_low, ns_high; 131 static const unsigned int max_count = 0x10000; 132 /* exit early if everything is already correct (this can save time 133 * since this function may be called repeatedly during command tests 134 * and execution) */ 135 div1 = *d1 ? *d1 : max_count; 136 div2 = *d2 ? *d2 : max_count; 137 divider = div1 * div2; 138 if (div1 * div2 * i8253_osc_base == *nanosec && 139 div1 > 1 && div1 <= max_count && 140 div2 > 1 && div2 <= max_count && 141 /* check for overflow */ 142 divider > div1 && divider > div2 && 143 divider * i8253_osc_base > divider && 144 divider * i8253_osc_base > i8253_osc_base) { 145 return; 146 } 147 148 divider = *nanosec / i8253_osc_base; 149 150 div1_lub = div2_lub = 0; 151 div1_glb = div2_glb = 0; 152 153 ns_glb = 0; 154 ns_lub = 0xffffffff; 155 156 div2 = max_count; 157 start = divider / div2; 158 if (start < 2) 159 start = 2; 160 for (div1 = start; div1 <= divider / div1 + 1 && div1 <= max_count; 161 div1++) { 162 for (div2 = divider / div1; 163 div1 * div2 <= divider + div1 + 1 && div2 <= max_count; 164 div2++) { 165 ns = i8253_osc_base * div1 * div2; 166 if (ns <= *nanosec && ns > ns_glb) { 167 ns_glb = ns; 168 div1_glb = div1; 169 div2_glb = div2; 170 } 171 if (ns >= *nanosec && ns < ns_lub) { 172 ns_lub = ns; 173 div1_lub = div1; 174 div2_lub = div2; 175 } 176 } 177 } 178 179 round_mode &= TRIG_ROUND_MASK; 180 switch (round_mode) { 181 case TRIG_ROUND_NEAREST: 182 default: 183 ns_high = div1_lub * div2_lub * i8253_osc_base; 184 ns_low = div1_glb * div2_glb * i8253_osc_base; 185 if (ns_high - *nanosec < *nanosec - ns_low) { 186 div1 = div1_lub; 187 div2 = div2_lub; 188 } else { 189 div1 = div1_glb; 190 div2 = div2_glb; 191 } 192 break; 193 case TRIG_ROUND_UP: 194 div1 = div1_lub; 195 div2 = div2_lub; 196 break; 197 case TRIG_ROUND_DOWN: 198 div1 = div1_glb; 199 div2 = div2_glb; 200 break; 201 } 202 203 *nanosec = div1 * div2 * i8253_osc_base; 204 *d1 = div1 & 0xffff; /* masking is done since counter maps zero to 0x10000 */ 205 *d2 = div2 & 0xffff; 206 return; 207} 208 209#ifndef CMDTEST 210/* i8254_load programs 8254 counter chip. It should also work for the 8253. 211 * base_address is the lowest io address for the chip (the address of counter 0). 212 * counter_number is the counter you want to load (0,1 or 2) 213 * count is the number to load into the counter. 214 * 215 * You probably want to use mode 2. 216 * 217 * Use i8254_mm_load() if you board uses memory-mapped io, it is 218 * the same as i8254_load() except it uses writeb() instead of outb(). 219 * 220 * Neither i8254_load() or i8254_read() do their loading/reading 221 * atomically. The 16 bit read/writes are performed with two successive 222 * 8 bit read/writes. So if two parts of your driver do a load/read on 223 * the same counter, it may be necessary to protect these functions 224 * with a spinlock. 225 * 226 * FMH 227 */ 228 229#define i8254_control_reg 3 230 231static inline int i8254_load(unsigned long base_address, unsigned int regshift, 232 unsigned int counter_number, unsigned int count, unsigned int mode) 233{ 234 unsigned int byte; 235 236 if (counter_number > 2) 237 return -1; 238 if (count > 0xffff) 239 return -1; 240 if (mode > 5) 241 return -1; 242 if ((mode == 2 || mode == 3) && count == 1) 243 return -1; 244 245 byte = counter_number << 6; 246 byte |= 0x30; /* load low then high byte */ 247 byte |= (mode << 1); /* set counter mode */ 248 outb(byte, base_address + (i8254_control_reg << regshift)); 249 byte = count & 0xff; /* lsb of counter value */ 250 outb(byte, base_address + (counter_number << regshift)); 251 byte = (count >> 8) & 0xff; /* msb of counter value */ 252 outb(byte, base_address + (counter_number << regshift)); 253 254 return 0; 255} 256 257static inline int i8254_mm_load(void *base_address, unsigned int regshift, 258 unsigned int counter_number, unsigned int count, unsigned int mode) 259{ 260 unsigned int byte; 261 262 if (counter_number > 2) 263 return -1; 264 if (count > 0xffff) 265 return -1; 266 if (mode > 5) 267 return -1; 268 if ((mode == 2 || mode == 3) && count == 1) 269 return -1; 270 271 byte = counter_number << 6; 272 byte |= 0x30; /* load low then high byte */ 273 byte |= (mode << 1); /* set counter mode */ 274 writeb(byte, base_address + (i8254_control_reg << regshift)); 275 byte = count & 0xff; /* lsb of counter value */ 276 writeb(byte, base_address + (counter_number << regshift)); 277 byte = (count >> 8) & 0xff; /* msb of counter value */ 278 writeb(byte, base_address + (counter_number << regshift)); 279 280 return 0; 281} 282 283/* Returns 16 bit counter value, should work for 8253 also.*/ 284static inline int i8254_read(unsigned long base_address, unsigned int regshift, 285 unsigned int counter_number) 286{ 287 unsigned int byte; 288 int ret; 289 290 if (counter_number > 2) 291 return -1; 292 293 /* latch counter */ 294 byte = counter_number << 6; 295 outb(byte, base_address + (i8254_control_reg << regshift)); 296 297 /* read lsb */ 298 ret = inb(base_address + (counter_number << regshift)); 299 /* read msb */ 300 ret += inb(base_address + (counter_number << regshift)) << 8; 301 302 return ret; 303} 304 305static inline int i8254_mm_read(void *base_address, unsigned int regshift, 306 unsigned int counter_number) 307{ 308 unsigned int byte; 309 int ret; 310 311 if (counter_number > 2) 312 return -1; 313 314 /* latch counter */ 315 byte = counter_number << 6; 316 writeb(byte, base_address + (i8254_control_reg << regshift)); 317 318 /* read lsb */ 319 ret = readb(base_address + (counter_number << regshift)); 320 /* read msb */ 321 ret += readb(base_address + (counter_number << regshift)) << 8; 322 323 return ret; 324} 325 326/* Loads 16 bit initial counter value, should work for 8253 also. */ 327static inline void i8254_write(unsigned long base_address, 328 unsigned int regshift, unsigned int counter_number, unsigned int count) 329{ 330 unsigned int byte; 331 332 if (counter_number > 2) 333 return; 334 335 byte = count & 0xff; /* lsb of counter value */ 336 outb(byte, base_address + (counter_number << regshift)); 337 byte = (count >> 8) & 0xff; /* msb of counter value */ 338 outb(byte, base_address + (counter_number << regshift)); 339} 340 341static inline void i8254_mm_write(void *base_address, 342 unsigned int regshift, unsigned int counter_number, unsigned int count) 343{ 344 unsigned int byte; 345 346 if (counter_number > 2) 347 return; 348 349 byte = count & 0xff; /* lsb of counter value */ 350 writeb(byte, base_address + (counter_number << regshift)); 351 byte = (count >> 8) & 0xff; /* msb of counter value */ 352 writeb(byte, base_address + (counter_number << regshift)); 353} 354 355/* Set counter mode, should work for 8253 also. 356 * Note: the 'mode' value is different to that for i8254_load() and comes 357 * from the INSN_CONFIG_8254_SET_MODE command: 358 * I8254_MODE0, I8254_MODE1, ..., I8254_MODE5 359 * OR'ed with: 360 * I8254_BCD, I8254_BINARY 361 */ 362static inline int i8254_set_mode(unsigned long base_address, 363 unsigned int regshift, unsigned int counter_number, unsigned int mode) 364{ 365 unsigned int byte; 366 367 if (counter_number > 2) 368 return -1; 369 if (mode > (I8254_MODE5 | I8254_BINARY)) 370 return -1; 371 372 byte = counter_number << 6; 373 byte |= 0x30; /* load low then high byte */ 374 byte |= mode; /* set counter mode and BCD|binary */ 375 outb(byte, base_address + (i8254_control_reg << regshift)); 376 377 return 0; 378} 379 380static inline int i8254_mm_set_mode(void *base_address, 381 unsigned int regshift, unsigned int counter_number, unsigned int mode) 382{ 383 unsigned int byte; 384 385 if (counter_number > 2) 386 return -1; 387 if (mode > (I8254_MODE5 | I8254_BINARY)) 388 return -1; 389 390 byte = counter_number << 6; 391 byte |= 0x30; /* load low then high byte */ 392 byte |= mode; /* set counter mode and BCD|binary */ 393 writeb(byte, base_address + (i8254_control_reg << regshift)); 394 395 return 0; 396} 397 398static inline int i8254_status(unsigned long base_address, 399 unsigned int regshift, unsigned int counter_number) 400{ 401 outb(0xE0 | (2 << counter_number), 402 base_address + (i8254_control_reg << regshift)); 403 return inb(base_address + (counter_number << regshift)); 404} 405 406static inline int i8254_mm_status(void *base_address, 407 unsigned int regshift, unsigned int counter_number) 408{ 409 writeb(0xE0 | (2 << counter_number), 410 base_address + (i8254_control_reg << regshift)); 411 return readb(base_address + (counter_number << regshift)); 412} 413 414#endif 415 416#endif 417