740 lines
20 KiB
C
740 lines
20 KiB
C
// 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_io_alloc() / comedi_8254_mm_alloc()
|
|
* Initializes this module to access the 8254 registers. The _mm version
|
|
* sets up the module for MMIO register access; the _io version sets it
|
|
* up for PIO access. These functions return a pointer to a struct
|
|
* comedi_8254 on success, or an ERR_PTR value on failure. 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. If the ERR_PTR value is stored, code should check the
|
|
* pointer value with !IS_ERR(pointer) before freeing.
|
|
*
|
|
* 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 <linux/comedi/comedidev.h>
|
|
#include <linux/comedi/comedi_8254.h>
|
|
|
|
#ifdef CONFIG_HAS_IOPORT
|
|
|
|
static unsigned int i8254_io8_cb(struct comedi_8254 *i8254, int dir,
|
|
unsigned int reg, unsigned int val)
|
|
{
|
|
unsigned long iobase = i8254->context;
|
|
unsigned int reg_offset = (reg * I8254_IO8) << i8254->regshift;
|
|
|
|
if (dir) {
|
|
outb(val, iobase + reg_offset);
|
|
return 0;
|
|
} else {
|
|
return inb(iobase + reg_offset);
|
|
}
|
|
}
|
|
|
|
static unsigned int i8254_io16_cb(struct comedi_8254 *i8254, int dir,
|
|
unsigned int reg, unsigned int val)
|
|
{
|
|
unsigned long iobase = i8254->context;
|
|
unsigned int reg_offset = (reg * I8254_IO16) << i8254->regshift;
|
|
|
|
if (dir) {
|
|
outw(val, iobase + reg_offset);
|
|
return 0;
|
|
} else {
|
|
return inw(iobase + reg_offset);
|
|
}
|
|
}
|
|
|
|
static unsigned int i8254_io32_cb(struct comedi_8254 *i8254, int dir,
|
|
unsigned int reg, unsigned int val)
|
|
{
|
|
unsigned long iobase = i8254->context;
|
|
unsigned int reg_offset = (reg * I8254_IO32) << i8254->regshift;
|
|
|
|
if (dir) {
|
|
outl(val, iobase + reg_offset);
|
|
return 0;
|
|
} else {
|
|
return inl(iobase + reg_offset);
|
|
}
|
|
}
|
|
|
|
#endif /* CONFIG_HAS_IOPORT */
|
|
|
|
static unsigned int i8254_mmio8_cb(struct comedi_8254 *i8254, int dir,
|
|
unsigned int reg, unsigned int val)
|
|
{
|
|
void __iomem *mmiobase = (void __iomem *)i8254->context;
|
|
unsigned int reg_offset = (reg * I8254_IO8) << i8254->regshift;
|
|
|
|
if (dir) {
|
|
writeb(val, mmiobase + reg_offset);
|
|
return 0;
|
|
} else {
|
|
return readb(mmiobase + reg_offset);
|
|
}
|
|
}
|
|
|
|
static unsigned int i8254_mmio16_cb(struct comedi_8254 *i8254, int dir,
|
|
unsigned int reg, unsigned int val)
|
|
{
|
|
void __iomem *mmiobase = (void __iomem *)i8254->context;
|
|
unsigned int reg_offset = (reg * I8254_IO16) << i8254->regshift;
|
|
|
|
if (dir) {
|
|
writew(val, mmiobase + reg_offset);
|
|
return 0;
|
|
} else {
|
|
return readw(mmiobase + reg_offset);
|
|
}
|
|
}
|
|
|
|
static unsigned int i8254_mmio32_cb(struct comedi_8254 *i8254, int dir,
|
|
unsigned int reg, unsigned int val)
|
|
{
|
|
void __iomem *mmiobase = (void __iomem *)i8254->context;
|
|
unsigned int reg_offset = (reg * I8254_IO32) << i8254->regshift;
|
|
|
|
if (dir) {
|
|
writel(val, mmiobase + reg_offset);
|
|
return 0;
|
|
} else {
|
|
return readl(mmiobase + reg_offset);
|
|
}
|
|
}
|
|
|
|
static unsigned int __i8254_read(struct comedi_8254 *i8254, unsigned int reg)
|
|
{
|
|
return 0xff & i8254->iocb(i8254, 0, reg, 0);
|
|
}
|
|
|
|
static void __i8254_write(struct comedi_8254 *i8254,
|
|
unsigned int val, unsigned int reg)
|
|
{
|
|
i8254->iocb(i8254, 1, reg, val);
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
* @i8254: comedi_8254 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(comedi_8254_iocb_fn *iocb,
|
|
unsigned long context,
|
|
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 ERR_PTR(-EINVAL);
|
|
|
|
if (!iocb)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
i8254 = kzalloc(sizeof(*i8254), GFP_KERNEL);
|
|
if (!i8254)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
i8254->iocb = iocb;
|
|
i8254->context = context;
|
|
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;
|
|
}
|
|
|
|
#ifdef CONFIG_HAS_IOPORT
|
|
|
|
/**
|
|
* comedi_8254_io_alloc - allocate and initialize the 8254 device for pio access
|
|
* @iobase: 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
|
|
*
|
|
* Return: A pointer to a struct comedi_8254 or an ERR_PTR value.
|
|
*/
|
|
struct comedi_8254 *comedi_8254_io_alloc(unsigned long iobase,
|
|
unsigned int osc_base,
|
|
unsigned int iosize,
|
|
unsigned int regshift)
|
|
{
|
|
comedi_8254_iocb_fn *iocb;
|
|
|
|
switch (iosize) {
|
|
case I8254_IO8:
|
|
iocb = i8254_io8_cb;
|
|
break;
|
|
case I8254_IO16:
|
|
iocb = i8254_io16_cb;
|
|
break;
|
|
case I8254_IO32:
|
|
iocb = i8254_io32_cb;
|
|
break;
|
|
default:
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
return __i8254_init(iocb, iobase, osc_base, iosize, regshift);
|
|
}
|
|
EXPORT_SYMBOL_GPL(comedi_8254_io_alloc);
|
|
|
|
#endif /* CONFIG_HAS_IOPORT */
|
|
|
|
/**
|
|
* comedi_8254_mm_alloc - 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
|
|
*
|
|
* Return: A pointer to a struct comedi_8254 or an ERR_PTR value.
|
|
*/
|
|
struct comedi_8254 *comedi_8254_mm_alloc(void __iomem *mmio,
|
|
unsigned int osc_base,
|
|
unsigned int iosize,
|
|
unsigned int regshift)
|
|
{
|
|
comedi_8254_iocb_fn *iocb;
|
|
|
|
switch (iosize) {
|
|
case I8254_IO8:
|
|
iocb = i8254_mmio8_cb;
|
|
break;
|
|
case I8254_IO16:
|
|
iocb = i8254_mmio16_cb;
|
|
break;
|
|
case I8254_IO32:
|
|
iocb = i8254_mmio32_cb;
|
|
break;
|
|
default:
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
return __i8254_init(iocb, (unsigned long)mmio, osc_base, iosize, regshift);
|
|
}
|
|
EXPORT_SYMBOL_GPL(comedi_8254_mm_alloc);
|
|
|
|
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");
|