| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * comedi/drivers/amplc_dio200_common.c |
| * |
| * Common support code for "amplc_dio200" and "amplc_dio200_pci". |
| * |
| * Copyright (C) 2005-2013 MEV Ltd. <https://www.mev.co.uk/> |
| * |
| * COMEDI - Linux Control and Measurement Device Interface |
| * Copyright (C) 1998,2000 David A. Schleef <ds@schleef.org> |
| */ |
| |
| #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); |
| } |
| |
| /* |
| * 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; /* protects the 'active' flag */ |
| unsigned int ofs; |
| unsigned int valid_isns; |
| unsigned int enabled_isns; |
| unsigned int 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 int 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 int triggered; |
| unsigned int intstat; |
| unsigned int 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 int 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), |
| clk_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_gat_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. Mark the subdevice as having private data |
| * to be automatically freed when the device is detached. |
| */ |
| comedi_set_spriv_auto_free(s); |
| |
| /* 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 https://www.comedi.org"); |
| MODULE_DESCRIPTION("Comedi helper for amplc_dio200 and amplc_dio200_pci"); |
| MODULE_LICENSE("GPL"); |