Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / drivers / staging / comedi / drivers / comedi_8254.c
diff --git a/kernel/drivers/staging/comedi/drivers/comedi_8254.c b/kernel/drivers/staging/comedi/drivers/comedi_8254.c
new file mode 100644 (file)
index 0000000..0d5d56b
--- /dev/null
@@ -0,0 +1,664 @@
+/*
+ * 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>
+ *
+ * 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.
+ */
+
+/*
+ * 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");