X-Git-Url: https://gerrit.opnfv.org/gerrit/gitweb?a=blobdiff_plain;f=kernel%2Fdrivers%2Fstaging%2Fcomedi%2Fdrivers%2Fadv_pci1710.c;fp=kernel%2Fdrivers%2Fstaging%2Fcomedi%2Fdrivers%2Fadv_pci1710.c;h=0c6aa964c884f274de3cd0e194ffe0f90edc630f;hb=9ca8dbcc65cfc63d6f5ef3312a33184e1d726e00;hp=0000000000000000000000000000000000000000;hpb=98260f3884f4a202f9ca5eabed40b1354c489b29;p=kvmfornfv.git diff --git a/kernel/drivers/staging/comedi/drivers/adv_pci1710.c b/kernel/drivers/staging/comedi/drivers/adv_pci1710.c new file mode 100644 index 000000000..0c6aa964c --- /dev/null +++ b/kernel/drivers/staging/comedi/drivers/adv_pci1710.c @@ -0,0 +1,1100 @@ +/* + * comedi/drivers/adv_pci1710.c + * + * Author: Michal Dobes + * + * Thanks to ZhenGang Shang + * for testing and information. + * + * hardware driver for Advantech cards: + * card: PCI-1710, PCI-1710HG, PCI-1711, PCI-1713, PCI-1720, PCI-1731 + * driver: pci1710, pci1710hg, pci1711, pci1713, pci1720, pci1731 + * + * Options: + * [0] - PCI bus number - if bus number and slot number are 0, + * then driver search for first unused card + * [1] - PCI slot number + * +*/ +/* +Driver: adv_pci1710 +Description: Advantech PCI-1710, PCI-1710HG, PCI-1711, PCI-1713, + Advantech PCI-1720, PCI-1731 +Author: Michal Dobes +Devices: [Advantech] PCI-1710 (adv_pci1710), PCI-1710HG (pci1710hg), + PCI-1711 (adv_pci1710), PCI-1713, PCI-1720, + PCI-1731 +Status: works + +This driver supports AI, AO, DI and DO subdevices. +AI subdevice supports cmd and insn interface, +other subdevices support only insn interface. + +The PCI-1710 and PCI-1710HG have the same PCI device ID, so the +driver cannot distinguish between them, as would be normal for a +PCI driver. + +Configuration options: + [0] - PCI bus of device (optional) + [1] - PCI slot of device (optional) + If bus/slot is not specified, the first available PCI + device will be used. +*/ + +#include +#include + +#include "../comedi_pci.h" + +#include "comedi_8254.h" +#include "amcc_s5933.h" + +#define PCI171x_AD_DATA 0 /* R: A/D data */ +#define PCI171x_SOFTTRG 0 /* W: soft trigger for A/D */ +#define PCI171x_RANGE 2 /* W: A/D gain/range register */ +#define PCI171x_MUX 4 /* W: A/D multiplexor control */ +#define PCI171x_STATUS 6 /* R: status register */ +#define PCI171x_CONTROL 6 /* W: control register */ +#define PCI171x_CLRINT 8 /* W: clear interrupts request */ +#define PCI171x_CLRFIFO 9 /* W: clear FIFO */ +#define PCI171x_DA1 10 /* W: D/A register */ +#define PCI171x_DA2 12 /* W: D/A register */ +#define PCI171x_DAREF 14 /* W: D/A reference control */ +#define PCI171x_DI 16 /* R: digi inputs */ +#define PCI171x_DO 16 /* R: digi inputs */ + +#define PCI171X_TIMER_BASE 0x18 + +/* upper bits from status register (PCI171x_STATUS) (lower is same with control + * reg) */ +#define Status_FE 0x0100 /* 1=FIFO is empty */ +#define Status_FH 0x0200 /* 1=FIFO is half full */ +#define Status_FF 0x0400 /* 1=FIFO is full, fatal error */ +#define Status_IRQ 0x0800 /* 1=IRQ occurred */ +/* bits from control register (PCI171x_CONTROL) */ +#define Control_CNT0 0x0040 /* 1=CNT0 have external source, + * 0=have internal 100kHz source */ +#define Control_ONEFH 0x0020 /* 1=IRQ on FIFO is half full, 0=every sample */ +#define Control_IRQEN 0x0010 /* 1=enable IRQ */ +#define Control_GATE 0x0008 /* 1=enable external trigger GATE (8254?) */ +#define Control_EXT 0x0004 /* 1=external trigger source */ +#define Control_PACER 0x0002 /* 1=enable internal 8254 trigger source */ +#define Control_SW 0x0001 /* 1=enable software trigger source */ + +#define PCI1720_DA0 0 /* W: D/A register 0 */ +#define PCI1720_DA1 2 /* W: D/A register 1 */ +#define PCI1720_DA2 4 /* W: D/A register 2 */ +#define PCI1720_DA3 6 /* W: D/A register 3 */ +#define PCI1720_RANGE 8 /* R/W: D/A range register */ +#define PCI1720_SYNCOUT 9 /* W: D/A synchronized output register */ +#define PCI1720_SYNCONT 15 /* R/W: D/A synchronized control */ + +/* D/A synchronized control (PCI1720_SYNCONT) */ +#define Syncont_SC0 1 /* set synchronous output mode */ + +static const struct comedi_lrange range_pci1710_3 = { + 9, { + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + BIP_RANGE(0.625), + BIP_RANGE(10), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25) + } +}; + +static const char range_codes_pci1710_3[] = { 0x00, 0x01, 0x02, 0x03, 0x04, + 0x10, 0x11, 0x12, 0x13 }; + +static const struct comedi_lrange range_pci1710hg = { + 12, { + BIP_RANGE(5), + BIP_RANGE(0.5), + BIP_RANGE(0.05), + BIP_RANGE(0.005), + BIP_RANGE(10), + BIP_RANGE(1), + BIP_RANGE(0.1), + BIP_RANGE(0.01), + UNI_RANGE(10), + UNI_RANGE(1), + UNI_RANGE(0.1), + UNI_RANGE(0.01) + } +}; + +static const char range_codes_pci1710hg[] = { 0x00, 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x10, 0x11, + 0x12, 0x13 }; + +static const struct comedi_lrange range_pci17x1 = { + 5, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + BIP_RANGE(0.625) + } +}; + +static const char range_codes_pci17x1[] = { 0x00, 0x01, 0x02, 0x03, 0x04 }; + +static const struct comedi_lrange pci1720_ao_range = { + 4, { + UNI_RANGE(5), + UNI_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(10) + } +}; + +static const struct comedi_lrange pci171x_ao_range = { + 2, { + UNI_RANGE(5), + UNI_RANGE(10) + } +}; + +enum pci1710_boardid { + BOARD_PCI1710, + BOARD_PCI1710HG, + BOARD_PCI1711, + BOARD_PCI1713, + BOARD_PCI1720, + BOARD_PCI1731, +}; + +struct boardtype { + const char *name; /* board name */ + int n_aichan; /* num of A/D chans */ + const struct comedi_lrange *rangelist_ai; /* rangelist for A/D */ + const char *rangecode_ai; /* range codes for programming */ + unsigned int is_pci1713:1; + unsigned int is_pci1720:1; + unsigned int has_irq:1; + unsigned int has_large_fifo:1; /* 4K or 1K FIFO */ + unsigned int has_diff_ai:1; + unsigned int has_ao:1; + unsigned int has_di_do:1; + unsigned int has_counter:1; +}; + +static const struct boardtype boardtypes[] = { + [BOARD_PCI1710] = { + .name = "pci1710", + .n_aichan = 16, + .rangelist_ai = &range_pci1710_3, + .rangecode_ai = range_codes_pci1710_3, + .has_irq = 1, + .has_large_fifo = 1, + .has_diff_ai = 1, + .has_ao = 1, + .has_di_do = 1, + .has_counter = 1, + }, + [BOARD_PCI1710HG] = { + .name = "pci1710hg", + .n_aichan = 16, + .rangelist_ai = &range_pci1710hg, + .rangecode_ai = range_codes_pci1710hg, + .has_irq = 1, + .has_large_fifo = 1, + .has_diff_ai = 1, + .has_ao = 1, + .has_di_do = 1, + .has_counter = 1, + }, + [BOARD_PCI1711] = { + .name = "pci1711", + .n_aichan = 16, + .rangelist_ai = &range_pci17x1, + .rangecode_ai = range_codes_pci17x1, + .has_irq = 1, + .has_ao = 1, + .has_di_do = 1, + .has_counter = 1, + }, + [BOARD_PCI1713] = { + .name = "pci1713", + .n_aichan = 32, + .rangelist_ai = &range_pci1710_3, + .rangecode_ai = range_codes_pci1710_3, + .is_pci1713 = 1, + .has_irq = 1, + .has_large_fifo = 1, + .has_diff_ai = 1, + }, + [BOARD_PCI1720] = { + .name = "pci1720", + .is_pci1720 = 1, + .has_ao = 1, + }, + [BOARD_PCI1731] = { + .name = "pci1731", + .n_aichan = 16, + .rangelist_ai = &range_pci17x1, + .rangecode_ai = range_codes_pci17x1, + .has_irq = 1, + .has_di_do = 1, + }, +}; + +struct pci1710_private { + unsigned int max_samples; + unsigned int CntrlReg; /* Control register */ + unsigned char ai_et; + unsigned int ai_et_CntrlReg; + unsigned int ai_et_MuxVal; + unsigned int act_chanlist[32]; /* list of scanned channel */ + unsigned char saved_seglen; /* len of the non-repeating chanlist */ + unsigned char da_ranges; /* copy of D/A outpit range register */ +}; + +static int pci171x_ai_check_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + struct pci1710_private *devpriv = dev->private; + unsigned int chan0 = CR_CHAN(cmd->chanlist[0]); + unsigned int last_aref = CR_AREF(cmd->chanlist[0]); + unsigned int next_chan = (chan0 + 1) % s->n_chan; + unsigned int chansegment[32]; + unsigned int seglen; + int i; + + if (cmd->chanlist_len == 1) { + devpriv->saved_seglen = cmd->chanlist_len; + return 0; + } + + /* first channel is always ok */ + chansegment[0] = cmd->chanlist[0]; + + for (i = 1; i < cmd->chanlist_len; i++) { + unsigned int chan = CR_CHAN(cmd->chanlist[i]); + unsigned int aref = CR_AREF(cmd->chanlist[i]); + + if (cmd->chanlist[0] == cmd->chanlist[i]) + break; /* we detected a loop, stop */ + + if (aref == AREF_DIFF && (chan & 1)) { + dev_err(dev->class_dev, + "Odd channel cannot be differential input!\n"); + return -EINVAL; + } + + if (last_aref == AREF_DIFF) + next_chan = (next_chan + 1) % s->n_chan; + if (chan != next_chan) { + dev_err(dev->class_dev, + "channel list must be continuous! chanlist[%i]=%d but must be %d or %d!\n", + i, chan, next_chan, chan0); + return -EINVAL; + } + + /* next correct channel in list */ + chansegment[i] = cmd->chanlist[i]; + last_aref = aref; + } + seglen = i; + + for (i = 0; i < cmd->chanlist_len; i++) { + if (cmd->chanlist[i] != chansegment[i % seglen]) { + dev_err(dev->class_dev, + "bad channel, reference or range number! chanlist[%i]=%d,%d,%d and not %d,%d,%d!\n", + i, CR_CHAN(chansegment[i]), + CR_RANGE(chansegment[i]), + CR_AREF(chansegment[i]), + CR_CHAN(cmd->chanlist[i % seglen]), + CR_RANGE(cmd->chanlist[i % seglen]), + CR_AREF(chansegment[i % seglen])); + return -EINVAL; + } + } + devpriv->saved_seglen = seglen; + + return 0; +} + +static void pci171x_ai_setup_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int *chanlist, + unsigned int n_chan, + unsigned int seglen) +{ + const struct boardtype *board = dev->board_ptr; + struct pci1710_private *devpriv = dev->private; + unsigned int first_chan = CR_CHAN(chanlist[0]); + unsigned int last_chan = CR_CHAN(chanlist[seglen - 1]); + unsigned int i; + + for (i = 0; i < seglen; i++) { /* store range list to card */ + unsigned int chan = CR_CHAN(chanlist[i]); + unsigned int range = CR_RANGE(chanlist[i]); + unsigned int aref = CR_AREF(chanlist[i]); + unsigned int rangeval; + + rangeval = board->rangecode_ai[range]; + if (aref == AREF_DIFF) + rangeval |= 0x0020; + + /* select channel and set range */ + outw(chan | (chan << 8), dev->iobase + PCI171x_MUX); + outw(rangeval, dev->iobase + PCI171x_RANGE); + + devpriv->act_chanlist[i] = chan; + } + for ( ; i < n_chan; i++) /* store remainder of channel list */ + devpriv->act_chanlist[i] = CR_CHAN(chanlist[i]); + + /* select channel interval to scan */ + devpriv->ai_et_MuxVal = first_chan | (last_chan << 8); + outw(devpriv->ai_et_MuxVal, dev->iobase + PCI171x_MUX); +} + +static int pci171x_ai_eoc(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) +{ + unsigned int status; + + status = inw(dev->iobase + PCI171x_STATUS); + if ((status & Status_FE) == 0) + return 0; + return -EBUSY; +} + +static int pci171x_ai_read_sample(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int cur_chan, + unsigned int *val) +{ + const struct boardtype *board = dev->board_ptr; + struct pci1710_private *devpriv = dev->private; + unsigned int sample; + unsigned int chan; + + sample = inw(dev->iobase + PCI171x_AD_DATA); + if (!board->is_pci1713) { + /* + * The upper 4 bits of the 16-bit sample are the channel number + * that the sample was acquired from. Verify that this channel + * number matches the expected channel number. + */ + chan = sample >> 12; + if (chan != devpriv->act_chanlist[cur_chan]) { + dev_err(dev->class_dev, + "A/D data droput: received from channel %d, expected %d\n", + chan, devpriv->act_chanlist[cur_chan]); + return -ENODATA; + } + } + *val = sample & s->maxdata; + return 0; +} + +static int pci171x_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct pci1710_private *devpriv = dev->private; + int ret = 0; + int i; + + devpriv->CntrlReg &= Control_CNT0; + devpriv->CntrlReg |= Control_SW; /* set software trigger */ + outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL); + outb(0, dev->iobase + PCI171x_CLRFIFO); + outb(0, dev->iobase + PCI171x_CLRINT); + + pci171x_ai_setup_chanlist(dev, s, &insn->chanspec, 1, 1); + + for (i = 0; i < insn->n; i++) { + unsigned int val; + + outw(0, dev->iobase + PCI171x_SOFTTRG); /* start conversion */ + + ret = comedi_timeout(dev, s, insn, pci171x_ai_eoc, 0); + if (ret) + break; + + ret = pci171x_ai_read_sample(dev, s, 0, &val); + if (ret) + break; + + data[i] = val; + } + + outb(0, dev->iobase + PCI171x_CLRFIFO); + outb(0, dev->iobase + PCI171x_CLRINT); + + return ret ? ret : insn->n; +} + +static int pci171x_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct pci1710_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int range = CR_RANGE(insn->chanspec); + unsigned int reg = chan ? PCI171x_DA2 : PCI171x_DA1; + unsigned int val = s->readback[chan]; + int i; + + devpriv->da_ranges &= ~(1 << (chan << 1)); + devpriv->da_ranges |= (range << (chan << 1)); + outw(devpriv->da_ranges, dev->iobase + PCI171x_DAREF); + + for (i = 0; i < insn->n; i++) { + val = data[i]; + outw(val, dev->iobase + reg); + } + + s->readback[chan] = val; + + return insn->n; +} + +static int pci171x_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = inw(dev->iobase + PCI171x_DI); + + return insn->n; +} + +static int pci171x_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (comedi_dio_update_state(s, data)) + outw(s->state, dev->iobase + PCI171x_DO); + + data[1] = s->state; + + return insn->n; +} + +static int pci1720_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct pci1710_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int range = CR_RANGE(insn->chanspec); + unsigned int val; + int i; + + val = devpriv->da_ranges & (~(0x03 << (chan << 1))); + val |= (range << (chan << 1)); + if (val != devpriv->da_ranges) { + outb(val, dev->iobase + PCI1720_RANGE); + devpriv->da_ranges = val; + } + + val = s->readback[chan]; + for (i = 0; i < insn->n; i++) { + val = data[i]; + outw(val, dev->iobase + PCI1720_DA0 + (chan << 1)); + outb(0, dev->iobase + PCI1720_SYNCOUT); /* update outputs */ + } + + s->readback[chan] = val; + + return insn->n; +} + +static int pci171x_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pci1710_private *devpriv = dev->private; + + devpriv->CntrlReg &= Control_CNT0; + devpriv->CntrlReg |= Control_SW; + /* reset any operations */ + outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL); + comedi_8254_pacer_enable(dev->pacer, 1, 2, false); + outb(0, dev->iobase + PCI171x_CLRFIFO); + outb(0, dev->iobase + PCI171x_CLRINT); + + return 0; +} + +static void pci1710_handle_every_sample(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int status; + unsigned int val; + int ret; + + status = inw(dev->iobase + PCI171x_STATUS); + if (status & Status_FE) { + dev_dbg(dev->class_dev, "A/D FIFO empty (%4x)\n", status); + s->async->events |= COMEDI_CB_ERROR; + return; + } + if (status & Status_FF) { + dev_dbg(dev->class_dev, + "A/D FIFO Full status (Fatal Error!) (%4x)\n", status); + s->async->events |= COMEDI_CB_ERROR; + return; + } + + outb(0, dev->iobase + PCI171x_CLRINT); /* clear our INT request */ + + for (; !(inw(dev->iobase + PCI171x_STATUS) & Status_FE);) { + ret = pci171x_ai_read_sample(dev, s, s->async->cur_chan, &val); + if (ret) { + s->async->events |= COMEDI_CB_ERROR; + break; + } + + 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; + break; + } + } + + outb(0, dev->iobase + PCI171x_CLRINT); /* clear our INT request */ +} + +static void pci1710_handle_fifo(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pci1710_private *devpriv = dev->private; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned int status; + int i; + + status = inw(dev->iobase + PCI171x_STATUS); + if (!(status & Status_FH)) { + dev_dbg(dev->class_dev, "A/D FIFO not half full!\n"); + async->events |= COMEDI_CB_ERROR; + return; + } + if (status & Status_FF) { + dev_dbg(dev->class_dev, + "A/D FIFO Full status (Fatal Error!)\n"); + async->events |= COMEDI_CB_ERROR; + return; + } + + for (i = 0; i < devpriv->max_samples; i++) { + unsigned int val; + int ret; + + ret = pci171x_ai_read_sample(dev, s, s->async->cur_chan, &val); + if (ret) { + s->async->events |= COMEDI_CB_ERROR; + break; + } + + if (!comedi_buf_write_samples(s, &val, 1)) + break; + + if (cmd->stop_src == TRIG_COUNT && + async->scans_done >= cmd->stop_arg) { + async->events |= COMEDI_CB_EOA; + break; + } + } + + outb(0, dev->iobase + PCI171x_CLRINT); /* clear our INT request */ +} + +static irqreturn_t interrupt_service_pci1710(int irq, void *d) +{ + struct comedi_device *dev = d; + struct pci1710_private *devpriv = dev->private; + struct comedi_subdevice *s; + struct comedi_cmd *cmd; + + if (!dev->attached) /* is device attached? */ + return IRQ_NONE; /* no, exit */ + + s = dev->read_subdev; + cmd = &s->async->cmd; + + /* is this interrupt from our board? */ + if (!(inw(dev->iobase + PCI171x_STATUS) & Status_IRQ)) + return IRQ_NONE; /* no, exit */ + + if (devpriv->ai_et) { /* Switch from initial TRIG_EXT to TRIG_xxx. */ + devpriv->ai_et = 0; + devpriv->CntrlReg &= Control_CNT0; + devpriv->CntrlReg |= Control_SW; /* set software trigger */ + outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL); + devpriv->CntrlReg = devpriv->ai_et_CntrlReg; + outb(0, dev->iobase + PCI171x_CLRFIFO); + outb(0, dev->iobase + PCI171x_CLRINT); + outw(devpriv->ai_et_MuxVal, dev->iobase + PCI171x_MUX); + outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL); + comedi_8254_pacer_enable(dev->pacer, 1, 2, true); + return IRQ_HANDLED; + } + + if (cmd->flags & CMDF_WAKE_EOS) + pci1710_handle_every_sample(dev, s); + else + pci1710_handle_fifo(dev, s); + + comedi_handle_events(dev, s); + + return IRQ_HANDLED; +} + +static int pci171x_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct pci1710_private *devpriv = dev->private; + struct comedi_cmd *cmd = &s->async->cmd; + + pci171x_ai_setup_chanlist(dev, s, cmd->chanlist, cmd->chanlist_len, + devpriv->saved_seglen); + + outb(0, dev->iobase + PCI171x_CLRFIFO); + outb(0, dev->iobase + PCI171x_CLRINT); + + devpriv->CntrlReg &= Control_CNT0; + if ((cmd->flags & CMDF_WAKE_EOS) == 0) + devpriv->CntrlReg |= Control_ONEFH; + + if (cmd->convert_src == TRIG_TIMER) { + comedi_8254_update_divisors(dev->pacer); + + devpriv->CntrlReg |= Control_PACER | Control_IRQEN; + if (cmd->start_src == TRIG_EXT) { + devpriv->ai_et_CntrlReg = devpriv->CntrlReg; + devpriv->CntrlReg &= + ~(Control_PACER | Control_ONEFH | Control_GATE); + devpriv->CntrlReg |= Control_EXT; + devpriv->ai_et = 1; + } else { /* TRIG_NOW */ + devpriv->ai_et = 0; + } + outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL); + + if (cmd->start_src == TRIG_NOW) + comedi_8254_pacer_enable(dev->pacer, 1, 2, true); + } else { /* TRIG_EXT */ + devpriv->CntrlReg |= Control_EXT | Control_IRQEN; + outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL); + } + + return 0; +} + +static int pci171x_ai_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_EXT); + err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW); + err |= comedi_check_trigger_src(&cmd->convert_src, + TRIG_TIMER | TRIG_EXT); + 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->convert_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); + + if (cmd->convert_src == TRIG_TIMER) + err |= comedi_check_trigger_arg_min(&cmd->convert_arg, 10000); + else /* TRIG_FOLLOW */ + 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 (cmd->convert_src == TRIG_TIMER) { + unsigned int arg = cmd->convert_arg; + + comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags); + err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg); + } + + if (err) + return 4; + + /* Step 5: check channel list */ + + err |= pci171x_ai_check_chanlist(dev, s, cmd); + + if (err) + return 5; + + return 0; +} + +static int pci171x_insn_counter_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct pci1710_private *devpriv = dev->private; + + switch (data[0]) { + case INSN_CONFIG_SET_CLOCK_SRC: + switch (data[1]) { + case 0: /* internal */ + devpriv->ai_et_CntrlReg &= ~Control_CNT0; + break; + case 1: /* external */ + devpriv->ai_et_CntrlReg |= Control_CNT0; + break; + default: + return -EINVAL; + } + outw(devpriv->ai_et_CntrlReg, dev->iobase + PCI171x_CONTROL); + break; + case INSN_CONFIG_GET_CLOCK_SRC: + if (devpriv->ai_et_CntrlReg & Control_CNT0) { + data[1] = 1; + data[2] = 0; + } else { + data[1] = 0; + data[2] = I8254_OSC_BASE_10MHZ; + } + break; + default: + return -EINVAL; + } + + return insn->n; +} + +static int pci171x_reset(struct comedi_device *dev) +{ + const struct boardtype *board = dev->board_ptr; + struct pci1710_private *devpriv = dev->private; + + /* Software trigger, CNT0=external */ + devpriv->CntrlReg = Control_SW | Control_CNT0; + /* reset any operations */ + outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL); + outb(0, dev->iobase + PCI171x_CLRFIFO); /* clear FIFO */ + outb(0, dev->iobase + PCI171x_CLRINT); /* clear INT request */ + devpriv->da_ranges = 0; + if (board->has_ao) { + /* set DACs to 0..5V */ + outb(devpriv->da_ranges, dev->iobase + PCI171x_DAREF); + outw(0, dev->iobase + PCI171x_DA1); /* set DA outputs to 0V */ + outw(0, dev->iobase + PCI171x_DA2); + } + outw(0, dev->iobase + PCI171x_DO); /* digital outputs to 0 */ + outb(0, dev->iobase + PCI171x_CLRFIFO); /* clear FIFO */ + outb(0, dev->iobase + PCI171x_CLRINT); /* clear INT request */ + + return 0; +} + +static int pci1720_reset(struct comedi_device *dev) +{ + struct pci1710_private *devpriv = dev->private; + /* set synchronous output mode */ + outb(Syncont_SC0, dev->iobase + PCI1720_SYNCONT); + devpriv->da_ranges = 0xAA; + /* set all ranges to +/-5V */ + outb(devpriv->da_ranges, dev->iobase + PCI1720_RANGE); + outw(0x0800, dev->iobase + PCI1720_DA0); /* set outputs to 0V */ + outw(0x0800, dev->iobase + PCI1720_DA1); + outw(0x0800, dev->iobase + PCI1720_DA2); + outw(0x0800, dev->iobase + PCI1720_DA3); + outb(0, dev->iobase + PCI1720_SYNCOUT); /* update outputs */ + + return 0; +} + +static int pci1710_reset(struct comedi_device *dev) +{ + const struct boardtype *board = dev->board_ptr; + + if (board->is_pci1720) + return pci1720_reset(dev); + + return pci171x_reset(dev); +} + +static int pci1710_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + const struct boardtype *board = NULL; + struct pci1710_private *devpriv; + struct comedi_subdevice *s; + int ret, subdev, n_subdevices; + + if (context < ARRAY_SIZE(boardtypes)) + board = &boardtypes[context]; + if (!board) + return -ENODEV; + dev->board_ptr = board; + dev->board_name = board->name; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + dev->iobase = pci_resource_start(pcidev, 2); + + dev->pacer = comedi_8254_init(dev->iobase + PCI171X_TIMER_BASE, + I8254_OSC_BASE_10MHZ, I8254_IO16, 0); + if (!dev->pacer) + return -ENOMEM; + + n_subdevices = 0; + if (board->n_aichan) + n_subdevices++; + if (board->has_ao) + n_subdevices++; + if (board->has_di_do) + n_subdevices += 2; + if (board->has_counter) + n_subdevices++; + + ret = comedi_alloc_subdevices(dev, n_subdevices); + if (ret) + return ret; + + pci1710_reset(dev); + + if (board->has_irq && pcidev->irq) { + ret = request_irq(pcidev->irq, interrupt_service_pci1710, + IRQF_SHARED, dev->board_name, dev); + if (ret == 0) + dev->irq = pcidev->irq; + } + + subdev = 0; + + if (board->n_aichan) { + s = &dev->subdevices[subdev]; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND; + if (board->has_diff_ai) + s->subdev_flags |= SDF_DIFF; + s->n_chan = board->n_aichan; + s->maxdata = 0x0fff; + s->range_table = board->rangelist_ai; + s->insn_read = pci171x_ai_insn_read; + if (dev->irq) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->len_chanlist = s->n_chan; + s->do_cmdtest = pci171x_ai_cmdtest; + s->do_cmd = pci171x_ai_cmd; + s->cancel = pci171x_ai_cancel; + } + subdev++; + } + + if (board->has_ao) { + s = &dev->subdevices[subdev]; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; + s->maxdata = 0x0fff; + if (board->is_pci1720) { + s->n_chan = 4; + s->range_table = &pci1720_ao_range; + s->insn_write = pci1720_ao_insn_write; + } else { + s->n_chan = 2; + s->range_table = &pci171x_ao_range; + s->insn_write = pci171x_ao_insn_write; + } + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; + + /* initialize the readback values to match the board reset */ + if (board->is_pci1720) { + int i; + + for (i = 0; i < s->n_chan; i++) + s->readback[i] = 0x0800; + } + + subdev++; + } + + if (board->has_di_do) { + s = &dev->subdevices[subdev]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = pci171x_di_insn_bits; + subdev++; + + s = &dev->subdevices[subdev]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = pci171x_do_insn_bits; + subdev++; + } + + /* Counter subdevice (8254) */ + if (board->has_counter) { + s = &dev->subdevices[subdev]; + comedi_8254_subdevice_init(s, dev->pacer); + + dev->pacer->insn_config = pci171x_insn_counter_config; + + /* counters 1 and 2 are used internally for the pacer */ + comedi_8254_set_busy(dev->pacer, 1, true); + comedi_8254_set_busy(dev->pacer, 2, true); + + subdev++; + } + + /* max_samples is half the FIFO size (2 bytes/sample) */ + devpriv->max_samples = (board->has_large_fifo) ? 2048 : 512; + + return 0; +} + +static void pci1710_detach(struct comedi_device *dev) +{ + if (dev->iobase) + pci1710_reset(dev); + comedi_pci_detach(dev); +} + +static struct comedi_driver adv_pci1710_driver = { + .driver_name = "adv_pci1710", + .module = THIS_MODULE, + .auto_attach = pci1710_auto_attach, + .detach = pci1710_detach, +}; + +static int adv_pci1710_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &adv_pci1710_driver, + id->driver_data); +} + +static const struct pci_device_id adv_pci1710_pci_table[] = { + { + PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, + PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9050), + .driver_data = BOARD_PCI1710, + }, { + PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, + PCI_VENDOR_ID_ADVANTECH, 0x0000), + .driver_data = BOARD_PCI1710, + }, { + PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, + PCI_VENDOR_ID_ADVANTECH, 0xb100), + .driver_data = BOARD_PCI1710, + }, { + PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, + PCI_VENDOR_ID_ADVANTECH, 0xb200), + .driver_data = BOARD_PCI1710, + }, { + PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, + PCI_VENDOR_ID_ADVANTECH, 0xc100), + .driver_data = BOARD_PCI1710, + }, { + PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, + PCI_VENDOR_ID_ADVANTECH, 0xc200), + .driver_data = BOARD_PCI1710, + }, { + PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, 0x1000, 0xd100), + .driver_data = BOARD_PCI1710, + }, { + PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, + PCI_VENDOR_ID_ADVANTECH, 0x0002), + .driver_data = BOARD_PCI1710HG, + }, { + PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, + PCI_VENDOR_ID_ADVANTECH, 0xb102), + .driver_data = BOARD_PCI1710HG, + }, { + PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, + PCI_VENDOR_ID_ADVANTECH, 0xb202), + .driver_data = BOARD_PCI1710HG, + }, { + PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, + PCI_VENDOR_ID_ADVANTECH, 0xc102), + .driver_data = BOARD_PCI1710HG, + }, { + PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, + PCI_VENDOR_ID_ADVANTECH, 0xc202), + .driver_data = BOARD_PCI1710HG, + }, { + PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, 0x1000, 0xd102), + .driver_data = BOARD_PCI1710HG, + }, + { PCI_VDEVICE(ADVANTECH, 0x1711), BOARD_PCI1711 }, + { PCI_VDEVICE(ADVANTECH, 0x1713), BOARD_PCI1713 }, + { PCI_VDEVICE(ADVANTECH, 0x1720), BOARD_PCI1720 }, + { PCI_VDEVICE(ADVANTECH, 0x1731), BOARD_PCI1731 }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, adv_pci1710_pci_table); + +static struct pci_driver adv_pci1710_pci_driver = { + .name = "adv_pci1710", + .id_table = adv_pci1710_pci_table, + .probe = adv_pci1710_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(adv_pci1710_driver, adv_pci1710_pci_driver); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi: Advantech PCI-1710 Series Multifunction DAS Cards"); +MODULE_LICENSE("GPL");