Add support for async commands with the Analog Input subdevice. Signed-off-by: H Hartley Sweeten <hsweeten@xxxxxxxxxxxxxxxxxxx> Cc: Ian Abbott <abbotti@xxxxxxxxx> Cc: Greg Kroah-Hartman <gregk@xxxxxxxxxxxxxxxxxxx> --- drivers/staging/comedi/Kconfig | 1 + drivers/staging/comedi/drivers/ni_daq_700.c | 401 ++++++++++++++++++++++++++-- 2 files changed, 385 insertions(+), 17 deletions(-) diff --git a/drivers/staging/comedi/Kconfig b/drivers/staging/comedi/Kconfig index 341fc07..5523d14 100644 --- a/drivers/staging/comedi/Kconfig +++ b/drivers/staging/comedi/Kconfig @@ -1144,6 +1144,7 @@ config COMEDI_DAS08_CS config COMEDI_NI_DAQ_700_CS tristate "NI DAQCard-700 PCMCIA support" + select COMEDI_FC ---help--- Enable support for the National Instruments PCMCIA DAQCard-700 DIO diff --git a/drivers/staging/comedi/drivers/ni_daq_700.c b/drivers/staging/comedi/drivers/ni_daq_700.c index 2a474b0..e1e7ba7 100644 --- a/drivers/staging/comedi/drivers/ni_daq_700.c +++ b/drivers/staging/comedi/drivers/ni_daq_700.c @@ -56,6 +56,7 @@ #include "../comedidev.h" +#include "comedi_fc.h" #include "8253.h" /* @@ -95,6 +96,8 @@ #define DAQ700_CMD2_DISABDAQ (1 << 1) #define DAQ700_TIMER_BASE 0x08 +#define DAQ700_FIFO_SIZE 512 /* samples */ + static const struct comedi_lrange range_daq700_ai = { 3, { @@ -104,6 +107,14 @@ static const struct comedi_lrange range_daq700_ai = { } }; +struct daq700_private { + unsigned int divisor; + unsigned int scans_done; + unsigned int samples_left; + unsigned char cmd1; + unsigned char cmd3; +}; + static int daq700_dio_insn_bits(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, @@ -151,8 +162,10 @@ static int daq700_dio_insn_config(struct comedi_device *dev, } static void daq700_ai_set_chanspec(struct comedi_device *dev, - unsigned int chanspec) + unsigned int chanspec, + bool scan) { + struct daq700_private *devpriv = dev->private; unsigned int chan = CR_CHAN(chanspec); unsigned int range = CR_RANGE(chanspec); unsigned int aref = CR_AREF(chanspec); @@ -164,13 +177,25 @@ static void daq700_ai_set_chanspec(struct comedi_device *dev, val = DAQ700_CMD3_ARNG(range); if (aref == AREF_DIFF) val |= DAQ700_CMD3_DIFF; - outb(val, dev->iobase + DAQ700_CMD3_REG); + if (val != devpriv->cmd3) { + outb(val, dev->iobase + DAQ700_CMD3_REG); + devpriv->cmd3 = val; + } /* set multiplexer for single-channel scan */ - outb(DAQ700_CMD1_SCANDISAB | DAQ700_CMD1_MA(chan), - dev->iobase + DAQ700_CMD1_REG); - /* mux needs 2us to really settle [Fred Brooks]. */ - udelay(2); + val = DAQ700_CMD1_SCANDISAB | DAQ700_CMD1_MA(chan); + if (val != devpriv->cmd1) { + outb(val, dev->iobase + DAQ700_CMD1_REG); + devpriv->cmd1 = val; + /* mux needs 2us to really settle [Fred Brooks] */ + udelay(2); + } + + /* enable multi-channel scanning */ + if (scan) { + devpriv->cmd1 &= ~DAQ700_CMD1_SCANDISAB; + outb(devpriv->cmd1, dev->iobase + DAQ700_CMD1_REG); + } } static void daq700_ai_start_conv(struct comedi_device *dev) @@ -185,6 +210,15 @@ static void daq700_ai_start_conv(struct comedi_device *dev) 0, I8254_MODE2 | I8254_BINARY); /* OUT0 high */ } +static void daq700_ai_stop_conv(struct comedi_device *dev) +{ + /* + * Stop A/D conversions by forcing OUT0 high. + */ + i8254_set_mode(dev->iobase + DAQ700_TIMER_BASE, 0, + 0, I8254_MODE2 | I8254_BINARY); /* OUT0 high */ +} + static void daq700_ai_flush_fifo(struct comedi_device *dev) { outb(DAQ700_AI_CLR_FIFO, dev->iobase + DAQ700_AI_CLR_REG); @@ -222,7 +256,7 @@ static int daq700_ai_insn_read(struct comedi_device *dev, int ret; int i; - daq700_ai_set_chanspec(dev, insn->chanspec); + daq700_ai_set_chanspec(dev, insn->chanspec, false); for (i = 0; i < insn->n; i++) { daq700_ai_start_conv(dev); @@ -241,6 +275,320 @@ static int daq700_ai_insn_read(struct comedi_device *dev, return insn->n; } +static bool daq700_interrupts_enabled(struct comedi_device *dev) +{ + struct daq700_private *devpriv = dev->private; + + if (devpriv->cmd1 & (DAQ700_CMD1_CNTINTEN | + DAQ700_CMD1_EXTINTEN | + DAQ700_CMD1_FIFOINTEN)) + return true; + else + return false; +} + +static void daq700_ai_read_fifo(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct daq700_private *devpriv = dev->private; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned int val; + unsigned char status; + + do { + status = inb(dev->iobase + DAQ700_STATUS1_REG); + + if (status & DAQ700_STATUS1_DATAERR) { + async->events |= COMEDI_CB_ERROR; + break; + } + + if (status & DAQ700_STATUS1_DAVAIL) { + val = inw(dev->iobase + DAQ700_AI_FIFO_REG); + cfc_write_to_buffer(s, comedi_offset_munge(s, val)); + + if (cmd->stop_src == TRIG_COUNT) { + devpriv->samples_left--; + if (devpriv->samples_left == 0) + break; + } + } + } while (status & DAQ700_STATUS1_DAVAIL); +} + +static irqreturn_t daq700_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct daq700_private *devpriv = dev->private; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned char status; + + if (!dev->attached || !daq700_interrupts_enabled(dev)) + return IRQ_NONE; + + status = inb(dev->iobase + DAQ700_STATUS1_REG); + if (status & DAQ700_STATUS1_CNTINT) { + /* + * Interrupt from counter 2 output + */ + outb(DAQ700_TIC_CLR_INT, dev->iobase + DAQ700_TIC_REG); + } + if ((status & DAQ700_STATUS1_EXTINT) == 0) { + /* + * Interrupt caused by the EXTINT* signal on the + * I/O connector. Not sure how to handle this... + */ + dev_dbg(dev->class_dev, "EXTINT detected\n"); + } + if (devpriv->cmd1 & DAQ700_CMD1_FIFOINTEN) { + daq700_ai_read_fifo(dev, s); + + if (async->events & COMEDI_CB_EOS) + devpriv->scans_done++; + + if (cmd->stop_src == TRIG_COUNT) { + if (devpriv->scans_done >= cmd->stop_arg) + async->events |= COMEDI_CB_EOA; + + if (devpriv->samples_left < (DAQ700_FIFO_SIZE / 2)) { + if (devpriv->cmd3 & DAQ700_CMD3_FIFOHFINT) { + devpriv->cmd3 &= ~DAQ700_CMD3_FIFOHFINT; + outb(devpriv->cmd3, + dev->iobase + DAQ700_CMD3_REG); + } + } + } + } + + cfc_handle_events(dev, s); + + return IRQ_HANDLED; +} + +static int daq700_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct daq700_private *devpriv = dev->private; + + outb(DAQ700_CMD2_DISABDAQ, dev->iobase + DAQ700_CMD2_REG); + + devpriv->cmd1 &= ~DAQ700_CMD1_FIFOINTEN; + outb(devpriv->cmd1, dev->iobase + DAQ700_CMD1_REG); + devpriv->cmd3 &= ~DAQ700_CMD3_FIFOHFINT; + outb(devpriv->cmd3, dev->iobase + DAQ700_CMD3_REG); + + daq700_ai_stop_conv(dev); + daq700_ai_flush_fifo(dev); + + outb(DAQ700_CMD2_ENADAQ, dev->iobase + DAQ700_CMD2_REG); + + return 0; +} + +static int daq700_ai_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct daq700_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + bool scan = false; + + devpriv->scans_done = 0; + if (cmd->stop_src == TRIG_COUNT) + devpriv->samples_left = cmd->scan_end_arg * cmd->stop_arg; + else /* TRIG_NONE */ + devpriv->samples_left = DAQ700_FIFO_SIZE; + + if (cmd->chanlist_len > 1) { + unsigned int chan0 = CR_CHAN(cmd->chanlist[0]); + unsigned int chan1 = CR_CHAN(cmd->chanlist[1]); + + if (chan0 != chan1) + scan = true; + } + daq700_ai_set_chanspec(dev, cmd->chanlist[0], scan); + + daq700_ai_flush_fifo(dev); + + if (cmd->convert_src == TRIG_TIMER) + i8254_load(dev->iobase + DAQ700_TIMER_BASE, 0, + 0, devpriv->divisor, I8254_MODE2 | I8254_BINARY); + + /* + * If TRIG_WAKE_EOS is not set we use the FIFO half-full interrupt + * to read as many samples as possible on each interrupt. + */ + if ((cmd->flags & TRIG_WAKE_EOS) == 0) { + if (devpriv->samples_left > (DAQ700_FIFO_SIZE / 2)) { + devpriv->cmd3 |= DAQ700_CMD3_FIFOHFINT; + outb(devpriv->cmd3, dev->iobase + DAQ700_CMD3_REG); + } + } + + /* enable interrupts */ + devpriv->cmd1 |= DAQ700_CMD1_FIFOINTEN; + outb(devpriv->cmd1, dev->iobase + DAQ700_CMD1_REG); + + return 0; +} + +static void daq700_ns_to_timer(unsigned int osc_base, + unsigned int *divisor, + unsigned int *ns, + unsigned int flags) +{ + unsigned int div; + + switch (flags & TRIG_ROUND_MASK) { + case TRIG_ROUND_NEAREST: + default: + div = (*ns + osc_base / 2) / osc_base; + break; + case TRIG_ROUND_UP: + div = *ns / osc_base; + break; + case TRIG_ROUND_DOWN: + div = (*ns + osc_base - 1) / osc_base; + break; + } + + if (div > 0xffff) + div = 0xffff; + *divisor = div; + *ns = div * osc_base; +} + +static int daq700_check_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + unsigned int chan0 = CR_CHAN(cmd->chanlist[0]); + unsigned int range0 = CR_RANGE(cmd->chanlist[0]); + unsigned int aref0 = CR_AREF(cmd->chanlist[0]); + int i; + + /* + * All channels in the scan list must have the same range and + * aref. The channels must also be sequential and count down + * to 0. + */ + for (i = 1; i < cmd->chanlist_len; i++) { + unsigned int chan = CR_CHAN(cmd->chanlist[i]); + unsigned int range = CR_RANGE(cmd->chanlist[i]); + unsigned int aref = CR_AREF(cmd->chanlist[i]); + + if (chan != (chan0 - i)) { + dev_dbg(dev->class_dev, + "chanlist must use consecutive channels (down to 0)\n"); + return -EINVAL; + } + if (range != range0) { + dev_dbg(dev->class_dev, + "chanlist must use the same range\n"); + return -EINVAL; + } + if (aref != aref0) { + dev_dbg(dev->class_dev, + "chanlist must use the same aref\n"); + return -EINVAL; + } + } + if (cmd->chanlist_len > 1) { + unsigned int last_chan; + + last_chan = CR_CHAN(cmd->chanlist[cmd->chanlist_len - 1]); + if (last_chan != 0) { + dev_dbg(dev->class_dev, + "last channel in chanlist must be 0\n"); + return -EINVAL; + } + } + return 0; +} + +static int daq700_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + struct daq700_private *devpriv = dev->private; + int err = 0; + unsigned int arg; + + /* Step 1 : check if triggers are trivially valid */ + + err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER | TRIG_EXT); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->convert_src); + err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_src, 0); + + if (cmd->convert_src == TRIG_TIMER) { + /* sample interval can be between 10 and 65535 ns */ + err |= cfc_check_trigger_arg_min(&cmd->convert_arg, + 10 * 1000); + err |= cfc_check_trigger_arg_max(&cmd->convert_arg, + 65535 * 1000); + } else { /* TRIG_EXT */ + /* + * A low-to-high transition of EXTCONV starts an + * A/D conversion. + */ + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); + } + + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0); + + if (err) + return 3; + + /* Step 4: fix up any arguments */ + + if (cmd->convert_src == TRIG_TIMER) { + arg = cmd->convert_arg; + daq700_ns_to_timer(I8254_OSC_BASE_1MHZ, + &devpriv->divisor, &arg, cmd->flags); + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, arg); + } + + if (err) + return 4; + + /* Step 5: check channel list if it exists */ + + if (cmd->chanlist && cmd->chanlist_len > 0) + err |= daq700_check_chanlist(dev, s, cmd); + + if (err) + return 5; + + return 0; +} + /* * Data acquisition is enabled. * The counter 0 output is high. @@ -254,15 +602,16 @@ static int daq700_ai_insn_read(struct comedi_device *dev, */ static void daq700_initialize(struct comedi_device *dev) { - unsigned long iobase = dev->iobase; + struct daq700_private *devpriv = dev->private; - outb(DAQ700_CMD1_SCANDISAB | DAQ700_CMD1_MA(0), - iobase + DAQ700_CMD1_REG); - outb(DAQ700_CMD2_ENADAQ, iobase + DAQ700_CMD2_REG); - outb(DAQ700_CMD3_ARNG(0), iobase + DAQ700_CMD3_REG); - i8254_set_mode(iobase + DAQ700_TIMER_BASE, 0, - 0, I8254_MODE2 | I8254_BINARY); /* OUT0 high */ - outb(DAQ700_TIC_CLR_INT, iobase + DAQ700_TIC_REG); + devpriv->cmd1 = DAQ700_CMD1_SCANDISAB | DAQ700_CMD1_MA(0); + devpriv->cmd3 = DAQ700_CMD3_ARNG(0); + + outb(devpriv->cmd1, dev->iobase + DAQ700_CMD1_REG); + outb(DAQ700_CMD2_ENADAQ, dev->iobase + DAQ700_CMD2_REG); + outb(devpriv->cmd3, dev->iobase + DAQ700_CMD3_REG); + daq700_ai_stop_conv(dev); + outb(DAQ700_TIC_CLR_INT, dev->iobase + DAQ700_TIC_REG); daq700_ai_flush_fifo(dev); } @@ -270,15 +619,27 @@ static int daq700_auto_attach(struct comedi_device *dev, unsigned long context) { struct pcmcia_device *link = comedi_to_pcmcia_dev(dev); + struct daq700_private *devpriv; struct comedi_subdevice *s; int ret; + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + link->config_flags |= CONF_AUTO_SET_IO; ret = comedi_pcmcia_enable(dev, NULL); if (ret) return ret; dev->iobase = link->resource[0]->start; + daq700_initialize(dev); + + link->priv = dev; + ret = pcmcia_request_irq(link, daq700_interrupt); + if (ret == 0) + dev->irq = link->irq; + ret = comedi_alloc_subdevices(dev, 2); if (ret) return ret; @@ -302,8 +663,14 @@ static int daq700_auto_attach(struct comedi_device *dev, s->maxdata = 0x0fff; s->range_table = &range_daq700_ai; s->insn_read = daq700_ai_insn_read; - - daq700_initialize(dev); + if (dev->irq) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->len_chanlist = s->n_chan; + s->do_cmdtest = daq700_ai_cmdtest; + s->do_cmd = daq700_ai_cmd; + s->cancel = daq700_ai_cancel; + } return 0; } -- 1.9.3 _______________________________________________ devel mailing list devel@xxxxxxxxxxxxxxxxxxxxxx http://driverdev.linuxdriverproject.org/mailman/listinfo/driverdev-devel