diff options
Diffstat (limited to 'drivers/staging/comedi/drivers/amplc_dio200_common.c')
-rw-r--r-- | drivers/staging/comedi/drivers/amplc_dio200_common.c | 874 |
1 files changed, 874 insertions, 0 deletions
diff --git a/drivers/staging/comedi/drivers/amplc_dio200_common.c b/drivers/staging/comedi/drivers/amplc_dio200_common.c new file mode 100644 index 000000000..d15a3dc12 --- /dev/null +++ b/drivers/staging/comedi/drivers/amplc_dio200_common.c @@ -0,0 +1,874 @@ +/* + comedi/drivers/amplc_dio200_common.c + + Common support code for "amplc_dio200" and "amplc_dio200_pci". + + Copyright (C) 2005-2013 MEV Ltd. <http://www.mev.co.uk/> + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1998,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. +*/ + +#include <linux/module.h> +#include <linux/interrupt.h> + +#include "../comedidev.h" + +#include "amplc_dio200.h" +#include "comedi_8254.h" +#include "8255.h" /* only for register defines */ + +/* 200 series registers */ +#define DIO200_IO_SIZE 0x20 +#define DIO200_PCIE_IO_SIZE 0x4000 +#define DIO200_CLK_SCE(x) (0x18 + (x)) /* Group X/Y/Z clock sel reg */ +#define DIO200_GAT_SCE(x) (0x1b + (x)) /* Group X/Y/Z gate sel reg */ +#define DIO200_INT_SCE 0x1e /* Interrupt enable/status register */ +/* Extra registers for new PCIe boards */ +#define DIO200_ENHANCE 0x20 /* 1 to enable enhanced features */ +#define DIO200_VERSION 0x24 /* Hardware version register */ +#define DIO200_TS_CONFIG 0x600 /* Timestamp timer config register */ +#define DIO200_TS_COUNT 0x602 /* Timestamp timer count register */ + +/* + * Functions for constructing value for DIO_200_?CLK_SCE and + * DIO_200_?GAT_SCE registers: + * + * 'which' is: 0 for CTR-X1, CTR-Y1, CTR-Z1; 1 for CTR-X2, CTR-Y2 or CTR-Z2. + * 'chan' is the channel: 0, 1 or 2. + * 'source' is the signal source: 0 to 7, or 0 to 31 for "enhanced" boards. + */ +static unsigned char clk_gat_sce(unsigned int which, unsigned int chan, + unsigned int source) +{ + return (which << 5) | (chan << 3) | + ((source & 030) << 3) | (source & 007); +} + +static unsigned char clk_sce(unsigned int which, unsigned int chan, + unsigned int source) +{ + return clk_gat_sce(which, chan, source); +} + +static unsigned char gat_sce(unsigned int which, unsigned int chan, + unsigned int source) +{ + return clk_gat_sce(which, chan, source); +} + +/* + * Periods of the internal clock sources in nanoseconds. + */ +static const unsigned int clock_period[32] = { + [1] = 100, /* 10 MHz */ + [2] = 1000, /* 1 MHz */ + [3] = 10000, /* 100 kHz */ + [4] = 100000, /* 10 kHz */ + [5] = 1000000, /* 1 kHz */ + [11] = 50, /* 20 MHz (enhanced boards) */ + /* clock sources 12 and later reserved for enhanced boards */ +}; + +/* + * Timestamp timer configuration register (for new PCIe boards). + */ +#define TS_CONFIG_RESET 0x100 /* Reset counter to zero. */ +#define TS_CONFIG_CLK_SRC_MASK 0x0FF /* Clock source. */ +#define TS_CONFIG_MAX_CLK_SRC 2 /* Maximum clock source value. */ + +/* + * Periods of the timestamp timer clock sources in nanoseconds. + */ +static const unsigned int ts_clock_period[TS_CONFIG_MAX_CLK_SRC + 1] = { + 1, /* 1 nanosecond (but with 20 ns granularity). */ + 1000, /* 1 microsecond. */ + 1000000, /* 1 millisecond. */ +}; + +struct dio200_subdev_8255 { + unsigned int ofs; /* DIO base offset */ +}; + +struct dio200_subdev_intr { + spinlock_t spinlock; + unsigned int ofs; + unsigned int valid_isns; + unsigned int enabled_isns; + bool active:1; +}; + +static unsigned char dio200_read8(struct comedi_device *dev, + unsigned int offset) +{ + const struct dio200_board *board = dev->board_ptr; + + if (board->is_pcie) + offset <<= 3; + + if (dev->mmio) + return readb(dev->mmio + offset); + return inb(dev->iobase + offset); +} + +static void dio200_write8(struct comedi_device *dev, + unsigned int offset, unsigned char val) +{ + const struct dio200_board *board = dev->board_ptr; + + if (board->is_pcie) + offset <<= 3; + + if (dev->mmio) + writeb(val, dev->mmio + offset); + else + outb(val, dev->iobase + offset); +} + +static unsigned int dio200_read32(struct comedi_device *dev, + unsigned int offset) +{ + const struct dio200_board *board = dev->board_ptr; + + if (board->is_pcie) + offset <<= 3; + + if (dev->mmio) + return readl(dev->mmio + offset); + return inl(dev->iobase + offset); +} + +static void dio200_write32(struct comedi_device *dev, + unsigned int offset, unsigned int val) +{ + const struct dio200_board *board = dev->board_ptr; + + if (board->is_pcie) + offset <<= 3; + + if (dev->mmio) + writel(val, dev->mmio + offset); + else + outl(val, dev->iobase + offset); +} + +static unsigned int dio200_subdev_8254_offset(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + const struct dio200_board *board = dev->board_ptr; + struct comedi_8254 *i8254 = s->private; + unsigned int offset; + + /* get the offset that was passed to comedi_8254_*_init() */ + if (dev->mmio) + offset = i8254->mmio - dev->mmio; + else + offset = i8254->iobase - dev->iobase; + + /* remove the shift that was added for PCIe boards */ + if (board->is_pcie) + offset >>= 3; + + /* this offset now works for the dio200_{read,write} helpers */ + return offset; +} + +static int dio200_subdev_intr_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + const struct dio200_board *board = dev->board_ptr; + struct dio200_subdev_intr *subpriv = s->private; + + if (board->has_int_sce) { + /* Just read the interrupt status register. */ + data[1] = dio200_read8(dev, subpriv->ofs) & subpriv->valid_isns; + } else { + /* No interrupt status register. */ + data[0] = 0; + } + + return insn->n; +} + +static void dio200_stop_intr(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + const struct dio200_board *board = dev->board_ptr; + struct dio200_subdev_intr *subpriv = s->private; + + subpriv->active = false; + subpriv->enabled_isns = 0; + if (board->has_int_sce) + dio200_write8(dev, subpriv->ofs, 0); +} + +static void dio200_start_intr(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + const struct dio200_board *board = dev->board_ptr; + struct dio200_subdev_intr *subpriv = s->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int n; + unsigned isn_bits; + + /* Determine interrupt sources to enable. */ + isn_bits = 0; + if (cmd->chanlist) { + for (n = 0; n < cmd->chanlist_len; n++) + isn_bits |= (1U << CR_CHAN(cmd->chanlist[n])); + } + isn_bits &= subpriv->valid_isns; + /* Enable interrupt sources. */ + subpriv->enabled_isns = isn_bits; + if (board->has_int_sce) + dio200_write8(dev, subpriv->ofs, isn_bits); +} + +static int dio200_inttrig_start_intr(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trig_num) +{ + struct dio200_subdev_intr *subpriv = s->private; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned long flags; + + if (trig_num != cmd->start_arg) + return -EINVAL; + + spin_lock_irqsave(&subpriv->spinlock, flags); + s->async->inttrig = NULL; + if (subpriv->active) + dio200_start_intr(dev, s); + + spin_unlock_irqrestore(&subpriv->spinlock, flags); + + return 1; +} + +static void dio200_read_scan_intr(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int triggered) +{ + struct comedi_cmd *cmd = &s->async->cmd; + unsigned short val; + unsigned int n, ch; + + val = 0; + for (n = 0; n < cmd->chanlist_len; n++) { + ch = CR_CHAN(cmd->chanlist[n]); + if (triggered & (1U << ch)) + val |= (1U << n); + } + + comedi_buf_write_samples(s, &val, 1); + + if (cmd->stop_src == TRIG_COUNT && + s->async->scans_done >= cmd->stop_arg) + s->async->events |= COMEDI_CB_EOA; +} + +static int dio200_handle_read_intr(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + const struct dio200_board *board = dev->board_ptr; + struct dio200_subdev_intr *subpriv = s->private; + unsigned triggered; + unsigned intstat; + unsigned cur_enabled; + unsigned long flags; + + triggered = 0; + + spin_lock_irqsave(&subpriv->spinlock, flags); + if (board->has_int_sce) { + /* + * Collect interrupt sources that have triggered and disable + * them temporarily. Loop around until no extra interrupt + * sources have triggered, at which point, the valid part of + * the interrupt status register will read zero, clearing the + * cause of the interrupt. + * + * Mask off interrupt sources already seen to avoid infinite + * loop in case of misconfiguration. + */ + cur_enabled = subpriv->enabled_isns; + while ((intstat = (dio200_read8(dev, subpriv->ofs) & + subpriv->valid_isns & ~triggered)) != 0) { + triggered |= intstat; + cur_enabled &= ~triggered; + dio200_write8(dev, subpriv->ofs, cur_enabled); + } + } else { + /* + * No interrupt status register. Assume the single interrupt + * source has triggered. + */ + triggered = subpriv->enabled_isns; + } + + if (triggered) { + /* + * Some interrupt sources have triggered and have been + * temporarily disabled to clear the cause of the interrupt. + * + * Reenable them NOW to minimize the time they are disabled. + */ + cur_enabled = subpriv->enabled_isns; + if (board->has_int_sce) + dio200_write8(dev, subpriv->ofs, cur_enabled); + + if (subpriv->active) { + /* + * The command is still active. + * + * Ignore interrupt sources that the command isn't + * interested in (just in case there's a race + * condition). + */ + if (triggered & subpriv->enabled_isns) + /* Collect scan data. */ + dio200_read_scan_intr(dev, s, triggered); + } + } + spin_unlock_irqrestore(&subpriv->spinlock, flags); + + comedi_handle_events(dev, s); + + return (triggered != 0); +} + +static int dio200_subdev_intr_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct dio200_subdev_intr *subpriv = s->private; + unsigned long flags; + + spin_lock_irqsave(&subpriv->spinlock, flags); + if (subpriv->active) + dio200_stop_intr(dev, s); + + spin_unlock_irqrestore(&subpriv->spinlock, flags); + + return 0; +} + +static int dio200_subdev_intr_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + + /* Step 1 : check if triggers are trivially valid */ + + err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); + err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW); + err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= comedi_check_trigger_is_unique(cmd->start_src); + err |= comedi_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + /* Step 3: check if arguments are trivially valid */ + + err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); + err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, + cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + /* if (err) return 4; */ + + return 0; +} + +static int dio200_subdev_intr_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + struct dio200_subdev_intr *subpriv = s->private; + unsigned long flags; + + spin_lock_irqsave(&subpriv->spinlock, flags); + + subpriv->active = true; + + if (cmd->start_src == TRIG_INT) + s->async->inttrig = dio200_inttrig_start_intr; + else /* TRIG_NOW */ + dio200_start_intr(dev, s); + + spin_unlock_irqrestore(&subpriv->spinlock, flags); + + return 0; +} + +static int dio200_subdev_intr_init(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int offset, + unsigned valid_isns) +{ + const struct dio200_board *board = dev->board_ptr; + struct dio200_subdev_intr *subpriv; + + subpriv = comedi_alloc_spriv(s, sizeof(*subpriv)); + if (!subpriv) + return -ENOMEM; + + subpriv->ofs = offset; + subpriv->valid_isns = valid_isns; + spin_lock_init(&subpriv->spinlock); + + if (board->has_int_sce) + /* Disable interrupt sources. */ + dio200_write8(dev, subpriv->ofs, 0); + + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE | SDF_CMD_READ | SDF_PACKED; + if (board->has_int_sce) { + s->n_chan = DIO200_MAX_ISNS; + s->len_chanlist = DIO200_MAX_ISNS; + } else { + /* No interrupt source register. Support single channel. */ + s->n_chan = 1; + s->len_chanlist = 1; + } + s->range_table = &range_digital; + s->maxdata = 1; + s->insn_bits = dio200_subdev_intr_insn_bits; + s->do_cmdtest = dio200_subdev_intr_cmdtest; + s->do_cmd = dio200_subdev_intr_cmd; + s->cancel = dio200_subdev_intr_cancel; + + return 0; +} + +static irqreturn_t dio200_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->read_subdev; + int handled; + + if (!dev->attached) + return IRQ_NONE; + + handled = dio200_handle_read_intr(dev, s); + + return IRQ_RETVAL(handled); +} + +static void dio200_subdev_8254_set_gate_src(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int chan, + unsigned int src) +{ + unsigned int offset = dio200_subdev_8254_offset(dev, s); + + dio200_write8(dev, DIO200_GAT_SCE(offset >> 3), + gat_sce((offset >> 2) & 1, chan, src)); +} + +static void dio200_subdev_8254_set_clock_src(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int chan, + unsigned int src) +{ + unsigned int offset = dio200_subdev_8254_offset(dev, s); + + dio200_write8(dev, DIO200_CLK_SCE(offset >> 3), + clk_sce((offset >> 2) & 1, chan, src)); +} + +static int dio200_subdev_8254_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + const struct dio200_board *board = dev->board_ptr; + struct comedi_8254 *i8254 = s->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int max_src = board->is_pcie ? 31 : 7; + unsigned int src; + + if (!board->has_clk_gat_sce) + return -EINVAL; + + switch (data[0]) { + case INSN_CONFIG_SET_GATE_SRC: + src = data[2]; + if (src > max_src) + return -EINVAL; + + dio200_subdev_8254_set_gate_src(dev, s, chan, src); + i8254->gate_src[chan] = src; + break; + case INSN_CONFIG_GET_GATE_SRC: + data[2] = i8254->gate_src[chan]; + break; + case INSN_CONFIG_SET_CLOCK_SRC: + src = data[1]; + if (src > max_src) + return -EINVAL; + + dio200_subdev_8254_set_clock_src(dev, s, chan, src); + i8254->clock_src[chan] = src; + break; + case INSN_CONFIG_GET_CLOCK_SRC: + data[1] = i8254->clock_src[chan]; + data[2] = clock_period[i8254->clock_src[chan]]; + break; + default: + return -EINVAL; + } + + return insn->n; +} + +static int dio200_subdev_8254_init(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int offset) +{ + const struct dio200_board *board = dev->board_ptr; + struct comedi_8254 *i8254; + unsigned int regshift; + int chan; + + /* + * PCIe boards need the offset shifted in order to get the + * correct base address of the timer. + */ + if (board->is_pcie) { + offset <<= 3; + regshift = 3; + } else { + regshift = 0; + } + + if (dev->mmio) + i8254 = comedi_8254_mm_init(dev->mmio + offset, + 0, I8254_IO8, regshift); + else + i8254 = comedi_8254_init(dev->iobase + offset, + 0, I8254_IO8, regshift); + if (!i8254) + return -ENOMEM; + + comedi_8254_subdevice_init(s, i8254); + + i8254->insn_config = dio200_subdev_8254_config; + + /* + * There could be multiple timers so this driver does not + * use dev->pacer to save the i8254 pointer. Instead, + * comedi_8254_subdevice_init() saved the i8254 pointer in + * s->private. Set the runflag bit so that the core will + * automatically free it when the driver is detached. + */ + s->runflags |= COMEDI_SRF_FREE_SPRIV; + + /* Initialize channels. */ + if (board->has_clk_gat_sce) { + for (chan = 0; chan < 3; chan++) { + /* Gate source 0 is VCC (logic 1). */ + dio200_subdev_8254_set_gate_src(dev, s, chan, 0); + /* Clock source 0 is the dedicated clock input. */ + dio200_subdev_8254_set_clock_src(dev, s, chan, 0); + } + } + + return 0; +} + +static void dio200_subdev_8255_set_dir(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct dio200_subdev_8255 *subpriv = s->private; + int config; + + config = I8255_CTRL_CW; + /* 1 in io_bits indicates output, 1 in config indicates input */ + if (!(s->io_bits & 0x0000ff)) + config |= I8255_CTRL_A_IO; + if (!(s->io_bits & 0x00ff00)) + config |= I8255_CTRL_B_IO; + if (!(s->io_bits & 0x0f0000)) + config |= I8255_CTRL_C_LO_IO; + if (!(s->io_bits & 0xf00000)) + config |= I8255_CTRL_C_HI_IO; + dio200_write8(dev, subpriv->ofs + I8255_CTRL_REG, config); +} + +static int dio200_subdev_8255_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct dio200_subdev_8255 *subpriv = s->private; + unsigned int mask; + unsigned int val; + + mask = comedi_dio_update_state(s, data); + if (mask) { + if (mask & 0xff) + dio200_write8(dev, subpriv->ofs + I8255_DATA_A_REG, + s->state & 0xff); + if (mask & 0xff00) + dio200_write8(dev, subpriv->ofs + I8255_DATA_B_REG, + (s->state >> 8) & 0xff); + if (mask & 0xff0000) + dio200_write8(dev, subpriv->ofs + I8255_DATA_C_REG, + (s->state >> 16) & 0xff); + } + + val = dio200_read8(dev, subpriv->ofs + I8255_DATA_A_REG); + val |= dio200_read8(dev, subpriv->ofs + I8255_DATA_B_REG) << 8; + val |= dio200_read8(dev, subpriv->ofs + I8255_DATA_C_REG) << 16; + + data[1] = val; + + return insn->n; +} + +static int dio200_subdev_8255_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int mask; + int ret; + + if (chan < 8) + mask = 0x0000ff; + else if (chan < 16) + mask = 0x00ff00; + else if (chan < 20) + mask = 0x0f0000; + else + mask = 0xf00000; + + ret = comedi_dio_insn_config(dev, s, insn, data, mask); + if (ret) + return ret; + + dio200_subdev_8255_set_dir(dev, s); + + return insn->n; +} + +static int dio200_subdev_8255_init(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int offset) +{ + struct dio200_subdev_8255 *subpriv; + + subpriv = comedi_alloc_spriv(s, sizeof(*subpriv)); + if (!subpriv) + return -ENOMEM; + + subpriv->ofs = offset; + + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 24; + s->range_table = &range_digital; + s->maxdata = 1; + s->insn_bits = dio200_subdev_8255_bits; + s->insn_config = dio200_subdev_8255_config; + dio200_subdev_8255_set_dir(dev, s); + return 0; +} + +static int dio200_subdev_timer_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int n; + + for (n = 0; n < insn->n; n++) + data[n] = dio200_read32(dev, DIO200_TS_COUNT); + return n; +} + +static void dio200_subdev_timer_reset(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned int clock; + + clock = dio200_read32(dev, DIO200_TS_CONFIG) & TS_CONFIG_CLK_SRC_MASK; + dio200_write32(dev, DIO200_TS_CONFIG, clock | TS_CONFIG_RESET); + dio200_write32(dev, DIO200_TS_CONFIG, clock); +} + +static void dio200_subdev_timer_get_clock_src(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int *src, + unsigned int *period) +{ + unsigned int clk; + + clk = dio200_read32(dev, DIO200_TS_CONFIG) & TS_CONFIG_CLK_SRC_MASK; + *src = clk; + *period = (clk < ARRAY_SIZE(ts_clock_period)) ? + ts_clock_period[clk] : 0; +} + +static int dio200_subdev_timer_set_clock_src(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int src) +{ + if (src > TS_CONFIG_MAX_CLK_SRC) + return -EINVAL; + dio200_write32(dev, DIO200_TS_CONFIG, src); + return 0; +} + +static int dio200_subdev_timer_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int ret = 0; + + switch (data[0]) { + case INSN_CONFIG_RESET: + dio200_subdev_timer_reset(dev, s); + break; + case INSN_CONFIG_SET_CLOCK_SRC: + ret = dio200_subdev_timer_set_clock_src(dev, s, data[1]); + if (ret < 0) + ret = -EINVAL; + break; + case INSN_CONFIG_GET_CLOCK_SRC: + dio200_subdev_timer_get_clock_src(dev, s, &data[1], &data[2]); + break; + default: + ret = -EINVAL; + break; + } + return ret < 0 ? ret : insn->n; +} + +void amplc_dio200_set_enhance(struct comedi_device *dev, unsigned char val) +{ + dio200_write8(dev, DIO200_ENHANCE, val); +} +EXPORT_SYMBOL_GPL(amplc_dio200_set_enhance); + +int amplc_dio200_common_attach(struct comedi_device *dev, unsigned int irq, + unsigned long req_irq_flags) +{ + const struct dio200_board *board = dev->board_ptr; + struct comedi_subdevice *s; + unsigned int n; + int ret; + + ret = comedi_alloc_subdevices(dev, board->n_subdevs); + if (ret) + return ret; + + for (n = 0; n < dev->n_subdevices; n++) { + s = &dev->subdevices[n]; + switch (board->sdtype[n]) { + case sd_8254: + /* counter subdevice (8254) */ + ret = dio200_subdev_8254_init(dev, s, + board->sdinfo[n]); + if (ret < 0) + return ret; + break; + case sd_8255: + /* digital i/o subdevice (8255) */ + ret = dio200_subdev_8255_init(dev, s, + board->sdinfo[n]); + if (ret < 0) + return ret; + break; + case sd_intr: + /* 'INTERRUPT' subdevice */ + if (irq && !dev->read_subdev) { + ret = dio200_subdev_intr_init(dev, s, + DIO200_INT_SCE, + board->sdinfo[n]); + if (ret < 0) + return ret; + dev->read_subdev = s; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + break; + case sd_timer: + s->type = COMEDI_SUBD_TIMER; + s->subdev_flags = SDF_READABLE | SDF_LSAMPL; + s->n_chan = 1; + s->maxdata = 0xffffffff; + s->insn_read = dio200_subdev_timer_read; + s->insn_config = dio200_subdev_timer_config; + break; + default: + s->type = COMEDI_SUBD_UNUSED; + break; + } + } + + if (irq && dev->read_subdev) { + if (request_irq(irq, dio200_interrupt, req_irq_flags, + dev->board_name, dev) >= 0) { + dev->irq = irq; + } else { + dev_warn(dev->class_dev, + "warning! irq %u unavailable!\n", irq); + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(amplc_dio200_common_attach); + +static int __init amplc_dio200_common_init(void) +{ + return 0; +} +module_init(amplc_dio200_common_init); + +static void __exit amplc_dio200_common_exit(void) +{ +} +module_exit(amplc_dio200_common_exit); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi helper for amplc_dio200 and amplc_dio200_pci"); +MODULE_LICENSE("GPL"); |