diff options
Diffstat (limited to 'drivers/staging/comedi/drivers/comedi_8254.c')
-rw-r--r-- | drivers/staging/comedi/drivers/comedi_8254.c | 655 |
1 files changed, 0 insertions, 655 deletions
diff --git a/drivers/staging/comedi/drivers/comedi_8254.c b/drivers/staging/comedi/drivers/comedi_8254.c deleted file mode 100644 index d1d509e9add9..000000000000 --- a/drivers/staging/comedi/drivers/comedi_8254.c +++ /dev/null @@ -1,655 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * comedi_8254.c - * Generic 8254 timer/counter support - * Copyright (C) 2014 H Hartley Sweeten <hsweeten@visionengravers.com> - * - * Based on 8253.h and various subdevice implementations in comedi drivers. - * - * COMEDI - Linux Control and Measurement Device Interface - * Copyright (C) 2000 David A. Schleef <ds@schleef.org> - */ - -/* - * Module: comedi_8254 - * Description: Generic 8254 timer/counter support - * Author: H Hartley Sweeten <hsweeten@visionengravers.com> - * Updated: Thu Jan 8 16:45:45 MST 2015 - * Status: works - * - * This module is not used directly by end-users. Rather, it is used by other - * drivers to provide support for an 8254 Programmable Interval Timer. These - * counters are typically used to generate the pacer clock used for data - * acquisition. Some drivers also expose the counters for general purpose use. - * - * This module provides the following basic functions: - * - * comedi_8254_init() / comedi_8254_mm_init() - * Initializes this module to access the 8254 registers. The _mm version - * sets up the module for MMIO register access the other for PIO access. - * The pointer returned from these functions is normally stored in the - * comedi_device dev->pacer and will be freed by the comedi core during - * the driver (*detach). If a driver has multiple 8254 devices, they need - * to be stored in the drivers private data and freed when the driver is - * detached. - * - * NOTE: The counters are reset by setting them to I8254_MODE0 as part of - * this initialization. - * - * comedi_8254_set_mode() - * Sets a counters operation mode: - * I8254_MODE0 Interrupt on terminal count - * I8254_MODE1 Hardware retriggerable one-shot - * I8254_MODE2 Rate generator - * I8254_MODE3 Square wave mode - * I8254_MODE4 Software triggered strobe - * I8254_MODE5 Hardware triggered strobe (retriggerable) - * - * In addition I8254_BCD and I8254_BINARY specify the counting mode: - * I8254_BCD BCD counting - * I8254_BINARY Binary counting - * - * comedi_8254_write() - * Writes an initial value to a counter. - * - * The largest possible initial count is 0; this is equivalent to 2^16 - * for binary counting and 10^4 for BCD counting. - * - * NOTE: The counter does not stop when it reaches zero. In Mode 0, 1, 4, - * and 5 the counter "wraps around" to the highest count, either 0xffff - * for binary counting or 9999 for BCD counting, and continues counting. - * Modes 2 and 3 are periodic; the counter reloads itself with the initial - * count and continues counting from there. - * - * comedi_8254_read() - * Reads the current value from a counter. - * - * comedi_8254_status() - * Reads the status of a counter. - * - * comedi_8254_load() - * Sets a counters operation mode and writes the initial value. - * - * Typically the pacer clock is created by cascading two of the 16-bit counters - * to create a 32-bit rate generator (I8254_MODE2). These functions are - * provided to handle the cascaded counters: - * - * comedi_8254_ns_to_timer() - * Calculates the divisor value needed for a single counter to generate - * ns timing. - * - * comedi_8254_cascade_ns_to_timer() - * Calculates the two divisor values needed to the generate the pacer - * clock (in ns). - * - * comedi_8254_update_divisors() - * Transfers the intermediate divisor values to the current divisors. - * - * comedi_8254_pacer_enable() - * Programs the mode of the cascaded counters and writes the current - * divisor values. - * - * To expose the counters as a subdevice for general purpose use the following - * functions a provided: - * - * comedi_8254_subdevice_init() - * Initializes a comedi_subdevice to use the 8254 timer. - * - * comedi_8254_set_busy() - * Internally flags a counter as "busy". This is done to protect the - * counters that are used for the cascaded 32-bit pacer. - * - * The subdevice provides (*insn_read) and (*insn_write) operations to read - * the current value and write an initial value to a counter. A (*insn_config) - * operation is also provided to handle the following comedi instructions: - * - * INSN_CONFIG_SET_COUNTER_MODE calls comedi_8254_set_mode() - * INSN_CONFIG_8254_READ_STATUS calls comedi_8254_status() - * - * The (*insn_config) member of comedi_8254 can be initialized by the external - * driver to handle any additional instructions. - * - * NOTE: Gate control, clock routing, and any interrupt handling for the - * counters is not handled by this module. These features are driver dependent. - */ - -#include <linux/module.h> -#include <linux/slab.h> -#include <linux/io.h> - -#include "../comedidev.h" - -#include "comedi_8254.h" - -static unsigned int __i8254_read(struct comedi_8254 *i8254, unsigned int reg) -{ - unsigned int reg_offset = (reg * i8254->iosize) << i8254->regshift; - unsigned int val; - - switch (i8254->iosize) { - default: - case I8254_IO8: - if (i8254->mmio) - val = readb(i8254->mmio + reg_offset); - else - val = inb(i8254->iobase + reg_offset); - break; - case I8254_IO16: - if (i8254->mmio) - val = readw(i8254->mmio + reg_offset); - else - val = inw(i8254->iobase + reg_offset); - break; - case I8254_IO32: - if (i8254->mmio) - val = readl(i8254->mmio + reg_offset); - else - val = inl(i8254->iobase + reg_offset); - break; - } - return val & 0xff; -} - -static void __i8254_write(struct comedi_8254 *i8254, - unsigned int val, unsigned int reg) -{ - unsigned int reg_offset = (reg * i8254->iosize) << i8254->regshift; - - switch (i8254->iosize) { - default: - case I8254_IO8: - if (i8254->mmio) - writeb(val, i8254->mmio + reg_offset); - else - outb(val, i8254->iobase + reg_offset); - break; - case I8254_IO16: - if (i8254->mmio) - writew(val, i8254->mmio + reg_offset); - else - outw(val, i8254->iobase + reg_offset); - break; - case I8254_IO32: - if (i8254->mmio) - writel(val, i8254->mmio + reg_offset); - else - outl(val, i8254->iobase + reg_offset); - break; - } -} - -/** - * comedi_8254_status - return the status of a counter - * @i8254: comedi_8254 struct for the timer - * @counter: the counter number - */ -unsigned int comedi_8254_status(struct comedi_8254 *i8254, unsigned int counter) -{ - unsigned int cmd; - - if (counter > 2) - return 0; - - cmd = I8254_CTRL_READBACK_STATUS | I8254_CTRL_READBACK_SEL_CTR(counter); - __i8254_write(i8254, cmd, I8254_CTRL_REG); - - return __i8254_read(i8254, counter); -} -EXPORT_SYMBOL_GPL(comedi_8254_status); - -/** - * comedi_8254_read - read the current counter value - * @i8254: comedi_8254 struct for the timer - * @counter: the counter number - */ -unsigned int comedi_8254_read(struct comedi_8254 *i8254, unsigned int counter) -{ - unsigned int val; - - if (counter > 2) - return 0; - - /* latch counter */ - __i8254_write(i8254, I8254_CTRL_SEL_CTR(counter) | I8254_CTRL_LATCH, - I8254_CTRL_REG); - - /* read LSB then MSB */ - val = __i8254_read(i8254, counter); - val |= (__i8254_read(i8254, counter) << 8); - - return val; -} -EXPORT_SYMBOL_GPL(comedi_8254_read); - -/** - * comedi_8254_write - load a 16-bit initial counter value - * @i8254: comedi_8254 struct for the timer - * @counter: the counter number - * @val: the initial value - */ -void comedi_8254_write(struct comedi_8254 *i8254, - unsigned int counter, unsigned int val) -{ - unsigned int byte; - - if (counter > 2) - return; - if (val > 0xffff) - return; - - /* load LSB then MSB */ - byte = val & 0xff; - __i8254_write(i8254, byte, counter); - byte = (val >> 8) & 0xff; - __i8254_write(i8254, byte, counter); -} -EXPORT_SYMBOL_GPL(comedi_8254_write); - -/** - * comedi_8254_set_mode - set the mode of a counter - * @i8254: comedi_8254 struct for the timer - * @counter: the counter number - * @mode: the I8254_MODEx and I8254_BCD|I8254_BINARY - */ -int comedi_8254_set_mode(struct comedi_8254 *i8254, unsigned int counter, - unsigned int mode) -{ - unsigned int byte; - - if (counter > 2) - return -EINVAL; - if (mode > (I8254_MODE5 | I8254_BCD)) - return -EINVAL; - - byte = I8254_CTRL_SEL_CTR(counter) | /* select counter */ - I8254_CTRL_LSB_MSB | /* load LSB then MSB */ - mode; /* mode and BCD|binary */ - __i8254_write(i8254, byte, I8254_CTRL_REG); - - return 0; -} -EXPORT_SYMBOL_GPL(comedi_8254_set_mode); - -/** - * comedi_8254_load - program the mode and initial count of a counter - * @i8254: comedi_8254 struct for the timer - * @counter: the counter number - * @mode: the I8254_MODEx and I8254_BCD|I8254_BINARY - * @val: the initial value - */ -int comedi_8254_load(struct comedi_8254 *i8254, unsigned int counter, - unsigned int val, unsigned int mode) -{ - if (counter > 2) - return -EINVAL; - if (val > 0xffff) - return -EINVAL; - if (mode > (I8254_MODE5 | I8254_BCD)) - return -EINVAL; - - comedi_8254_set_mode(i8254, counter, mode); - comedi_8254_write(i8254, counter, val); - - return 0; -} -EXPORT_SYMBOL_GPL(comedi_8254_load); - -/** - * comedi_8254_pacer_enable - set the mode and load the cascaded counters - * @i8254: comedi_8254 struct for the timer - * @counter1: the counter number for the first divisor - * @counter2: the counter number for the second divisor - * @enable: flag to enable (load) the counters - */ -void comedi_8254_pacer_enable(struct comedi_8254 *i8254, - unsigned int counter1, - unsigned int counter2, - bool enable) -{ - unsigned int mode; - - if (counter1 > 2 || counter2 > 2 || counter1 == counter2) - return; - - if (enable) - mode = I8254_MODE2 | I8254_BINARY; - else - mode = I8254_MODE0 | I8254_BINARY; - - comedi_8254_set_mode(i8254, counter1, mode); - comedi_8254_set_mode(i8254, counter2, mode); - - if (enable) { - /* - * Divisors are loaded second counter then first counter to - * avoid possible issues with the first counter expiring - * before the second counter is loaded. - */ - comedi_8254_write(i8254, counter2, i8254->divisor2); - comedi_8254_write(i8254, counter1, i8254->divisor1); - } -} -EXPORT_SYMBOL_GPL(comedi_8254_pacer_enable); - -/** - * comedi_8254_update_divisors - update the divisors for the cascaded counters - * @i8254: comedi_8254 struct for the timer - */ -void comedi_8254_update_divisors(struct comedi_8254 *i8254) -{ - /* masking is done since counter maps zero to 0x10000 */ - i8254->divisor = i8254->next_div & 0xffff; - i8254->divisor1 = i8254->next_div1 & 0xffff; - i8254->divisor2 = i8254->next_div2 & 0xffff; -} -EXPORT_SYMBOL_GPL(comedi_8254_update_divisors); - -/** - * comedi_8254_cascade_ns_to_timer - calculate the cascaded divisor values - * @i8254: comedi_8254 struct for the timer - * @nanosec: the desired ns time - * @flags: comedi_cmd flags - */ -void comedi_8254_cascade_ns_to_timer(struct comedi_8254 *i8254, - unsigned int *nanosec, - unsigned int flags) -{ - unsigned int d1 = i8254->next_div1 ? i8254->next_div1 : I8254_MAX_COUNT; - unsigned int d2 = i8254->next_div2 ? i8254->next_div2 : I8254_MAX_COUNT; - unsigned int div = d1 * d2; - unsigned int ns_lub = 0xffffffff; - unsigned int ns_glb = 0; - unsigned int d1_lub = 0; - unsigned int d1_glb = 0; - unsigned int d2_lub = 0; - unsigned int d2_glb = 0; - unsigned int start; - unsigned int ns; - unsigned int ns_low; - unsigned int ns_high; - - /* exit early if everything is already correct */ - if (div * i8254->osc_base == *nanosec && - d1 > 1 && d1 <= I8254_MAX_COUNT && - d2 > 1 && d2 <= I8254_MAX_COUNT && - /* check for overflow */ - div > d1 && div > d2 && - div * i8254->osc_base > div && - div * i8254->osc_base > i8254->osc_base) - return; - - div = *nanosec / i8254->osc_base; - d2 = I8254_MAX_COUNT; - start = div / d2; - if (start < 2) - start = 2; - for (d1 = start; d1 <= div / d1 + 1 && d1 <= I8254_MAX_COUNT; d1++) { - for (d2 = div / d1; - d1 * d2 <= div + d1 + 1 && d2 <= I8254_MAX_COUNT; d2++) { - ns = i8254->osc_base * d1 * d2; - if (ns <= *nanosec && ns > ns_glb) { - ns_glb = ns; - d1_glb = d1; - d2_glb = d2; - } - if (ns >= *nanosec && ns < ns_lub) { - ns_lub = ns; - d1_lub = d1; - d2_lub = d2; - } - } - } - - switch (flags & CMDF_ROUND_MASK) { - case CMDF_ROUND_NEAREST: - default: - ns_high = d1_lub * d2_lub * i8254->osc_base; - ns_low = d1_glb * d2_glb * i8254->osc_base; - if (ns_high - *nanosec < *nanosec - ns_low) { - d1 = d1_lub; - d2 = d2_lub; - } else { - d1 = d1_glb; - d2 = d2_glb; - } - break; - case CMDF_ROUND_UP: - d1 = d1_lub; - d2 = d2_lub; - break; - case CMDF_ROUND_DOWN: - d1 = d1_glb; - d2 = d2_glb; - break; - } - - *nanosec = d1 * d2 * i8254->osc_base; - i8254->next_div1 = d1; - i8254->next_div2 = d2; -} -EXPORT_SYMBOL_GPL(comedi_8254_cascade_ns_to_timer); - -/** - * comedi_8254_ns_to_timer - calculate the divisor value for nanosec timing - * @i8254: comedi_8254 struct for the timer - * @nanosec: the desired ns time - * @flags: comedi_cmd flags - */ -void comedi_8254_ns_to_timer(struct comedi_8254 *i8254, - unsigned int *nanosec, unsigned int flags) -{ - unsigned int divisor; - - switch (flags & CMDF_ROUND_MASK) { - default: - case CMDF_ROUND_NEAREST: - divisor = DIV_ROUND_CLOSEST(*nanosec, i8254->osc_base); - break; - case CMDF_ROUND_UP: - divisor = DIV_ROUND_UP(*nanosec, i8254->osc_base); - break; - case CMDF_ROUND_DOWN: - divisor = *nanosec / i8254->osc_base; - break; - } - if (divisor < 2) - divisor = 2; - if (divisor > I8254_MAX_COUNT) - divisor = I8254_MAX_COUNT; - - *nanosec = divisor * i8254->osc_base; - i8254->next_div = divisor; -} -EXPORT_SYMBOL_GPL(comedi_8254_ns_to_timer); - -/** - * comedi_8254_set_busy - set/clear the "busy" flag for a given counter - * @i8254: comedi_8254 struct for the timer - * @counter: the counter number - * @busy: set/clear flag - */ -void comedi_8254_set_busy(struct comedi_8254 *i8254, - unsigned int counter, bool busy) -{ - if (counter < 3) - i8254->busy[counter] = busy; -} -EXPORT_SYMBOL_GPL(comedi_8254_set_busy); - -static int comedi_8254_insn_read(struct comedi_device *dev, - struct comedi_subdevice *s, - struct comedi_insn *insn, - unsigned int *data) -{ - struct comedi_8254 *i8254 = s->private; - unsigned int chan = CR_CHAN(insn->chanspec); - int i; - - if (i8254->busy[chan]) - return -EBUSY; - - for (i = 0; i < insn->n; i++) - data[i] = comedi_8254_read(i8254, chan); - - return insn->n; -} - -static int comedi_8254_insn_write(struct comedi_device *dev, - struct comedi_subdevice *s, - struct comedi_insn *insn, - unsigned int *data) -{ - struct comedi_8254 *i8254 = s->private; - unsigned int chan = CR_CHAN(insn->chanspec); - - if (i8254->busy[chan]) - return -EBUSY; - - if (insn->n) - comedi_8254_write(i8254, chan, data[insn->n - 1]); - - return insn->n; -} - -static int comedi_8254_insn_config(struct comedi_device *dev, - struct comedi_subdevice *s, - struct comedi_insn *insn, - unsigned int *data) -{ - struct comedi_8254 *i8254 = s->private; - unsigned int chan = CR_CHAN(insn->chanspec); - int ret; - - if (i8254->busy[chan]) - return -EBUSY; - - switch (data[0]) { - case INSN_CONFIG_RESET: - ret = comedi_8254_set_mode(i8254, chan, - I8254_MODE0 | I8254_BINARY); - if (ret) - return ret; - break; - case INSN_CONFIG_SET_COUNTER_MODE: - ret = comedi_8254_set_mode(i8254, chan, data[1]); - if (ret) - return ret; - break; - case INSN_CONFIG_8254_READ_STATUS: - data[1] = comedi_8254_status(i8254, chan); - break; - default: - /* - * If available, call the driver provided (*insn_config) - * to handle any driver implemented instructions. - */ - if (i8254->insn_config) - return i8254->insn_config(dev, s, insn, data); - - return -EINVAL; - } - - return insn->n; -} - -/** - * comedi_8254_subdevice_init - initialize a comedi_subdevice for the 8254 timer - * @s: comedi_subdevice struct - */ -void comedi_8254_subdevice_init(struct comedi_subdevice *s, - struct comedi_8254 *i8254) -{ - s->type = COMEDI_SUBD_COUNTER; - s->subdev_flags = SDF_READABLE | SDF_WRITABLE; - s->n_chan = 3; - s->maxdata = 0xffff; - s->range_table = &range_unknown; - s->insn_read = comedi_8254_insn_read; - s->insn_write = comedi_8254_insn_write; - s->insn_config = comedi_8254_insn_config; - - s->private = i8254; -} -EXPORT_SYMBOL_GPL(comedi_8254_subdevice_init); - -static struct comedi_8254 *__i8254_init(unsigned long iobase, - void __iomem *mmio, - unsigned int osc_base, - unsigned int iosize, - unsigned int regshift) -{ - struct comedi_8254 *i8254; - int i; - - /* sanity check that the iosize is valid */ - if (!(iosize == I8254_IO8 || iosize == I8254_IO16 || - iosize == I8254_IO32)) - return NULL; - - i8254 = kzalloc(sizeof(*i8254), GFP_KERNEL); - if (!i8254) - return NULL; - - i8254->iobase = iobase; - i8254->mmio = mmio; - i8254->iosize = iosize; - i8254->regshift = regshift; - - /* default osc_base to the max speed of a generic 8254 timer */ - i8254->osc_base = osc_base ? osc_base : I8254_OSC_BASE_10MHZ; - - /* reset all the counters by setting them to I8254_MODE0 */ - for (i = 0; i < 3; i++) - comedi_8254_set_mode(i8254, i, I8254_MODE0 | I8254_BINARY); - - return i8254; -} - -/** - * comedi_8254_init - allocate and initialize the 8254 device for pio access - * @mmio: port I/O base address - * @osc_base: base time of the counter in ns - * OPTIONAL - only used by comedi_8254_cascade_ns_to_timer() - * @iosize: I/O register size - * @regshift: register gap shift - */ -struct comedi_8254 *comedi_8254_init(unsigned long iobase, - unsigned int osc_base, - unsigned int iosize, - unsigned int regshift) -{ - return __i8254_init(iobase, NULL, osc_base, iosize, regshift); -} -EXPORT_SYMBOL_GPL(comedi_8254_init); - -/** - * comedi_8254_mm_init - allocate and initialize the 8254 device for mmio access - * @mmio: memory mapped I/O base address - * @osc_base: base time of the counter in ns - * OPTIONAL - only used by comedi_8254_cascade_ns_to_timer() - * @iosize: I/O register size - * @regshift: register gap shift - */ -struct comedi_8254 *comedi_8254_mm_init(void __iomem *mmio, - unsigned int osc_base, - unsigned int iosize, - unsigned int regshift) -{ - return __i8254_init(0, mmio, osc_base, iosize, regshift); -} -EXPORT_SYMBOL_GPL(comedi_8254_mm_init); - -static int __init comedi_8254_module_init(void) -{ - return 0; -} -module_init(comedi_8254_module_init); - -static void __exit comedi_8254_module_exit(void) -{ -} -module_exit(comedi_8254_module_exit); - -MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>"); -MODULE_DESCRIPTION("Comedi: Generic 8254 timer/counter support"); -MODULE_LICENSE("GPL"); |