From 1c7dd46a264365468707c069e29dbf8a5423dee8 Mon Sep 17 00:00:00 2001 From: David Schleef Date: Thu, 12 Feb 2009 15:23:59 -0800 Subject: Staging: comedi: add 8253.h header From: David Schleef This is needed by a bunch of different comedi drivers. From: David Schleef Cc: Frank Mori Hess Cc: Ian Abbott Signed-off-by: Greg Kroah-Hartman --- drivers/staging/comedi/drivers/8253.h | 420 ++++++++++++++++++++++++++++++++++ 1 file changed, 420 insertions(+) create mode 100644 drivers/staging/comedi/drivers/8253.h --- /dev/null +++ b/drivers/staging/comedi/drivers/8253.h @@ -0,0 +1,420 @@ +/* + comedi/drivers/8253.h + Header file for 8253 + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef _8253_H +#define _8253_H + +#ifndef CMDTEST +#include "../comedi.h" +#else +#include "../comedi.h" +#endif + +#define i8253_cascade_ns_to_timer i8253_cascade_ns_to_timer_2div + +static inline void i8253_cascade_ns_to_timer_2div_old(int i8253_osc_base, + unsigned int *d1, unsigned int *d2, unsigned int *nanosec, + int round_mode) +{ + int divider; + int div1, div2; + int div1_glb, div2_glb, ns_glb; + int div1_lub, div2_lub, ns_lub; + int ns; + + divider = (*nanosec + i8253_osc_base / 2) / i8253_osc_base; + + /* find 2 integers 1<={x,y}<=65536 such that x*y is + close to divider */ + + div1_lub = div2_lub = 0; + div1_glb = div2_glb = 0; + + ns_glb = 0; + ns_lub = 0xffffffff; + + div2 = 0x10000; + for (div1 = divider / 65536 + 1; div1 < div2; div1++) { + div2 = divider / div1; + + ns = i8253_osc_base * div1 * div2; + if (ns <= *nanosec && ns > ns_glb) { + ns_glb = ns; + div1_glb = div1; + div2_glb = div2; + } + + div2++; + if (div2 <= 65536) { + ns = i8253_osc_base * div1 * div2; + if (ns > *nanosec && ns < ns_lub) { + ns_lub = ns; + div1_lub = div1; + div2_lub = div2; + } + } + } + + *nanosec = div1_lub * div2_lub * i8253_osc_base; + *d1 = div1_lub & 0xffff; + *d2 = div2_lub & 0xffff; + return; +} + +static inline void i8253_cascade_ns_to_timer_power(int i8253_osc_base, + unsigned int *d1, unsigned int *d2, unsigned int *nanosec, + int round_mode) +{ + int div1, div2; + int base; + + for (div1 = 2; div1 <= (1 << 16); div1 <<= 1) { + base = i8253_osc_base * div1; + round_mode &= TRIG_ROUND_MASK; + switch (round_mode) { + case TRIG_ROUND_NEAREST: + default: + div2 = (*nanosec + base / 2) / base; + break; + case TRIG_ROUND_DOWN: + div2 = (*nanosec) / base; + break; + case TRIG_ROUND_UP: + div2 = (*nanosec + base - 1) / base; + break; + } + if (div2 < 2) + div2 = 2; + if (div2 <= 65536) { + *nanosec = div2 * base; + *d1 = div1 & 0xffff; + *d2 = div2 & 0xffff; + return; + } + } + + /* shouldn't get here */ + div1 = 0x10000; + div2 = 0x10000; + *nanosec = div1 * div2 * i8253_osc_base; + *d1 = div1 & 0xffff; + *d2 = div2 & 0xffff; +} + +static inline void i8253_cascade_ns_to_timer_2div(int i8253_osc_base, + unsigned int *d1, unsigned int *d2, unsigned int *nanosec, + int round_mode) +{ + unsigned int divider; + unsigned int div1, div2; + unsigned int div1_glb, div2_glb, ns_glb; + unsigned int div1_lub, div2_lub, ns_lub; + unsigned int ns; + unsigned int start; + unsigned int ns_low, ns_high; + static const unsigned int max_count = 0x10000; + /* exit early if everything is already correct (this can save time + * since this function may be called repeatedly during command tests + * and execution) */ + div1 = *d1 ? *d1 : max_count; + div2 = *d2 ? *d2 : max_count; + divider = div1 * div2; + if (div1 * div2 * i8253_osc_base == *nanosec && + div1 > 1 && div1 <= max_count && + div2 > 1 && div2 <= max_count && + /* check for overflow */ + divider > div1 && divider > div2 && + divider * i8253_osc_base > divider && + divider * i8253_osc_base > i8253_osc_base) { + return; + } + + divider = *nanosec / i8253_osc_base; + + div1_lub = div2_lub = 0; + div1_glb = div2_glb = 0; + + ns_glb = 0; + ns_lub = 0xffffffff; + + div2 = max_count; + start = divider / div2; + if (start < 2) + start = 2; + for (div1 = start; div1 <= divider / div1 + 1 && div1 <= max_count; + div1++) { + for (div2 = divider / div1; + div1 * div2 <= divider + div1 + 1 && div2 <= max_count; + div2++) { + ns = i8253_osc_base * div1 * div2; + if (ns <= *nanosec && ns > ns_glb) { + ns_glb = ns; + div1_glb = div1; + div2_glb = div2; + } + if (ns >= *nanosec && ns < ns_lub) { + ns_lub = ns; + div1_lub = div1; + div2_lub = div2; + } + } + } + + round_mode &= TRIG_ROUND_MASK; + switch (round_mode) { + case TRIG_ROUND_NEAREST: + default: + ns_high = div1_lub * div2_lub * i8253_osc_base; + ns_low = div1_glb * div2_glb * i8253_osc_base; + if (ns_high - *nanosec < *nanosec - ns_low) { + div1 = div1_lub; + div2 = div2_lub; + } else { + div1 = div1_glb; + div2 = div2_glb; + } + break; + case TRIG_ROUND_UP: + div1 = div1_lub; + div2 = div2_lub; + break; + case TRIG_ROUND_DOWN: + div1 = div1_glb; + div2 = div2_glb; + break; + } + + *nanosec = div1 * div2 * i8253_osc_base; + *d1 = div1 & 0xffff; // masking is done since counter maps zero to 0x10000 + *d2 = div2 & 0xffff; + return; +} + +#ifndef CMDTEST +/* i8254_load programs 8254 counter chip. It should also work for the 8253. + * base_address is the lowest io address for the chip (the address of counter 0). + * counter_number is the counter you want to load (0,1 or 2) + * count is the number to load into the counter. + * + * You probably want to use mode 2. + * + * Use i8254_mm_load() if you board uses memory-mapped io, it is + * the same as i8254_load() except it uses writeb() instead of outb(). + * + * Neither i8254_load() or i8254_read() do their loading/reading + * atomically. The 16 bit read/writes are performed with two successive + * 8 bit read/writes. So if two parts of your driver do a load/read on + * the same counter, it may be necessary to protect these functions + * with a spinlock. + * + * FMH + */ + +#define i8254_control_reg 3 + +static inline int i8254_load(unsigned long base_address, unsigned int regshift, + unsigned int counter_number, unsigned int count, unsigned int mode) +{ + unsigned int byte; + + if (counter_number > 2) + return -1; + if (count > 0xffff) + return -1; + if (mode > 5) + return -1; + if ((mode == 2 || mode == 3) && count == 1) + return -1; + + byte = counter_number << 6; + byte |= 0x30; // load low then high byte + byte |= (mode << 1); // set counter mode + outb(byte, base_address + (i8254_control_reg << regshift)); + byte = count & 0xff; // lsb of counter value + outb(byte, base_address + (counter_number << regshift)); + byte = (count >> 8) & 0xff; // msb of counter value + outb(byte, base_address + (counter_number << regshift)); + + return 0; +} + +static inline int i8254_mm_load(void *base_address, unsigned int regshift, + unsigned int counter_number, unsigned int count, unsigned int mode) +{ + unsigned int byte; + + if (counter_number > 2) + return -1; + if (count > 0xffff) + return -1; + if (mode > 5) + return -1; + if ((mode == 2 || mode == 3) && count == 1) + return -1; + + byte = counter_number << 6; + byte |= 0x30; // load low then high byte + byte |= (mode << 1); // set counter mode + writeb(byte, base_address + (i8254_control_reg << regshift)); + byte = count & 0xff; // lsb of counter value + writeb(byte, base_address + (counter_number << regshift)); + byte = (count >> 8) & 0xff; // msb of counter value + writeb(byte, base_address + (counter_number << regshift)); + + return 0; +} + +/* Returns 16 bit counter value, should work for 8253 also.*/ +static inline int i8254_read(unsigned long base_address, unsigned int regshift, + unsigned int counter_number) +{ + unsigned int byte; + int ret; + + if (counter_number > 2) + return -1; + + // latch counter + byte = counter_number << 6; + outb(byte, base_address + (i8254_control_reg << regshift)); + + // read lsb + ret = inb(base_address + (counter_number << regshift)); + // read msb + ret += inb(base_address + (counter_number << regshift)) << 8; + + return ret; +} + +static inline int i8254_mm_read(void *base_address, unsigned int regshift, + unsigned int counter_number) +{ + unsigned int byte; + int ret; + + if (counter_number > 2) + return -1; + + // latch counter + byte = counter_number << 6; + writeb(byte, base_address + (i8254_control_reg << regshift)); + + // read lsb + ret = readb(base_address + (counter_number << regshift)); + // read msb + ret += readb(base_address + (counter_number << regshift)) << 8; + + return ret; +} + +/* Loads 16 bit initial counter value, should work for 8253 also. */ +static inline void i8254_write(unsigned long base_address, + unsigned int regshift, unsigned int counter_number, unsigned int count) +{ + unsigned int byte; + + if (counter_number > 2) + return; + + byte = count & 0xff; // lsb of counter value + outb(byte, base_address + (counter_number << regshift)); + byte = (count >> 8) & 0xff; // msb of counter value + outb(byte, base_address + (counter_number << regshift)); +} + +static inline void i8254_mm_write(void *base_address, + unsigned int regshift, unsigned int counter_number, unsigned int count) +{ + unsigned int byte; + + if (counter_number > 2) + return; + + byte = count & 0xff; // lsb of counter value + writeb(byte, base_address + (counter_number << regshift)); + byte = (count >> 8) & 0xff; // msb of counter value + writeb(byte, base_address + (counter_number << regshift)); +} + +/* Set counter mode, should work for 8253 also. + * Note: the 'mode' value is different to that for i8254_load() and comes + * from the INSN_CONFIG_8254_SET_MODE command: + * I8254_MODE0, I8254_MODE1, ..., I8254_MODE5 + * OR'ed with: + * I8254_BCD, I8254_BINARY + */ +static inline int i8254_set_mode(unsigned long base_address, + unsigned int regshift, unsigned int counter_number, unsigned int mode) +{ + unsigned int byte; + + if (counter_number > 2) + return -1; + if (mode > (I8254_MODE5 | I8254_BINARY)) + return -1; + + byte = counter_number << 6; + byte |= 0x30; // load low then high byte + byte |= mode; // set counter mode and BCD|binary + outb(byte, base_address + (i8254_control_reg << regshift)); + + return 0; +} + +static inline int i8254_mm_set_mode(void *base_address, + unsigned int regshift, unsigned int counter_number, unsigned int mode) +{ + unsigned int byte; + + if (counter_number > 2) + return -1; + if (mode > (I8254_MODE5 | I8254_BINARY)) + return -1; + + byte = counter_number << 6; + byte |= 0x30; // load low then high byte + byte |= mode; // set counter mode and BCD|binary + writeb(byte, base_address + (i8254_control_reg << regshift)); + + return 0; +} + +static inline int i8254_status(unsigned long base_address, + unsigned int regshift, unsigned int counter_number) +{ + outb(0xE0 | (2 << counter_number), + base_address + (i8254_control_reg << regshift)); + return inb(base_address + (counter_number << regshift)); +} + +static inline int i8254_mm_status(void *base_address, + unsigned int regshift, unsigned int counter_number) +{ + writeb(0xE0 | (2 << counter_number), + base_address + (i8254_control_reg << regshift)); + return readb(base_address + (counter_number << regshift)); +} + +#endif + +#endif