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