| /* |
| * comedi/drivers/adv_pci1710.c |
| * |
| * Author: Michal Dobes <dobes@tesnet.cz> |
| * |
| * Thanks to ZhenGang Shang <ZhenGang.Shang@Advantech.com.cn> |
| * for testing and informations. |
| * |
| * 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 <dobes@tesnet.cz> |
| 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 <linux/module.h> |
| #include <linux/pci.h> |
| #include <linux/interrupt.h> |
| |
| #include "../comedidev.h" |
| |
| #include "comedi_fc.h" |
| #include "8253.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 |
| |
| #define PCI171x_CNT0 24 /* R/W: 8254 counter 0 */ |
| #define PCI171x_CNT1 26 /* R/W: 8254 counter 1 */ |
| #define PCI171x_CNT2 28 /* R/W: 8254 counter 2 */ |
| #define PCI171x_CNTCTRL 30 /* W: 8254 counter control */ |
| |
| /* 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 */ |
| /* bits from counter control register (PCI171x_CNTCTRL) */ |
| #define Counter_BCD 0x0001 /* 0 = binary counter, 1 = BCD counter */ |
| #define Counter_M0 0x0002 /* M0-M2 select modes 0-5 */ |
| #define Counter_M1 0x0004 /* 000 = mode 0, 010 = mode 2 ... */ |
| #define Counter_M2 0x0008 |
| #define Counter_RW0 0x0010 /* RW0/RW1 select read/write mode */ |
| #define Counter_RW1 0x0020 |
| #define Counter_SC0 0x0040 /* Select Counter. Only 00 or 11 may */ |
| #define Counter_SC1 0x0080 /* be used, 00 for CNT0, |
| * 11 for read-back command */ |
| |
| #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 next_divisor1; |
| unsigned int next_divisor2; |
| unsigned int divisor1; |
| unsigned int divisor2; |
| 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 */ |
| unsigned int cnt0_write_wait; /* after a write, wait for update of the |
| * internal state */ |
| }; |
| |
| 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; |
| unsigned int chan = CR_CHAN(insn->chanspec); |
| 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, chan, &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 void pci171x_start_pacer(struct comedi_device *dev, |
| bool load_counters) |
| { |
| struct pci1710_private *devpriv = dev->private; |
| unsigned long timer_base = dev->iobase + PCI171X_TIMER_BASE; |
| |
| i8254_set_mode(timer_base, 1, 2, I8254_MODE2 | I8254_BINARY); |
| i8254_set_mode(timer_base, 1, 1, I8254_MODE2 | I8254_BINARY); |
| |
| if (load_counters) { |
| i8254_write(timer_base, 1, 2, devpriv->divisor2); |
| i8254_write(timer_base, 1, 1, devpriv->divisor1); |
| } |
| } |
| |
| static int pci171x_counter_insn_read(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| unsigned int msb, lsb, ccntrl; |
| int i; |
| |
| ccntrl = 0xD2; /* count only */ |
| for (i = 0; i < insn->n; i++) { |
| outw(ccntrl, dev->iobase + PCI171x_CNTCTRL); |
| |
| lsb = inw(dev->iobase + PCI171x_CNT0) & 0xFF; |
| msb = inw(dev->iobase + PCI171x_CNT0) & 0xFF; |
| |
| data[0] = lsb | (msb << 8); |
| } |
| |
| return insn->n; |
| } |
| |
| static int pci171x_counter_insn_write(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| struct pci1710_private *devpriv = dev->private; |
| uint msb, lsb, ccntrl, status; |
| |
| lsb = data[0] & 0x00FF; |
| msb = (data[0] & 0xFF00) >> 8; |
| |
| /* write lsb, then msb */ |
| outw(lsb, dev->iobase + PCI171x_CNT0); |
| outw(msb, dev->iobase + PCI171x_CNT0); |
| |
| if (devpriv->cnt0_write_wait) { |
| /* wait for the new count to be loaded */ |
| ccntrl = 0xE2; |
| do { |
| outw(ccntrl, dev->iobase + PCI171x_CNTCTRL); |
| status = inw(dev->iobase + PCI171x_CNT0) & 0xFF; |
| } while (status & 0x40); |
| } |
| |
| return insn->n; |
| } |
| |
| static int pci171x_counter_insn_config(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| #ifdef unused |
| /* This doesn't work like a normal Comedi counter config */ |
| struct pci1710_private *devpriv = dev->private; |
| uint ccntrl = 0; |
| |
| devpriv->cnt0_write_wait = data[0] & 0x20; |
| |
| /* internal or external clock? */ |
| if (!(data[0] & 0x10)) { /* internal */ |
| devpriv->CntrlReg &= ~Control_CNT0; |
| } else { |
| devpriv->CntrlReg |= Control_CNT0; |
| } |
| outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL); |
| |
| if (data[0] & 0x01) |
| ccntrl |= Counter_M0; |
| if (data[0] & 0x02) |
| ccntrl |= Counter_M1; |
| if (data[0] & 0x04) |
| ccntrl |= Counter_M2; |
| if (data[0] & 0x08) |
| ccntrl |= Counter_BCD; |
| ccntrl |= Counter_RW0; /* set read/write mode */ |
| ccntrl |= Counter_RW1; |
| outw(ccntrl, dev->iobase + PCI171x_CNTCTRL); |
| #endif |
| |
| return 1; |
| } |
| |
| 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); |
| pci171x_start_pacer(dev, 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); |
| pci171x_start_pacer(dev, 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_start_pacer(dev, false); |
| |
| 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; |
| |
| devpriv->divisor1 = devpriv->next_divisor1; |
| devpriv->divisor2 = devpriv->next_divisor2; |
| |
| if (cmd->convert_src == TRIG_TIMER) { |
| 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) |
| pci171x_start_pacer(dev, 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) |
| { |
| struct pci1710_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 | TRIG_EXT); |
| 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->start_src); |
| 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_arg, 0); |
| |
| if (cmd->convert_src == TRIG_TIMER) |
| err |= cfc_check_trigger_arg_min(&cmd->convert_arg, 10000); |
| else /* TRIG_FOLLOW */ |
| 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; |
| i8253_cascade_ns_to_timer(I8254_OSC_BASE_10MHZ, |
| &devpriv->next_divisor1, |
| &devpriv->next_divisor2, |
| &arg, cmd->flags); |
| err |= cfc_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_reset(struct comedi_device *dev) |
| { |
| const struct boardtype *board = dev->board_ptr; |
| struct pci1710_private *devpriv = dev->private; |
| |
| outw(0x30, dev->iobase + PCI171x_CNTCTRL); |
| /* 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 */ |
| pci171x_start_pacer(dev, false); |
| 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); |
| |
| 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++; |
| } |
| |
| if (board->has_counter) { |
| s = &dev->subdevices[subdev]; |
| s->type = COMEDI_SUBD_COUNTER; |
| s->subdev_flags = SDF_READABLE | SDF_WRITABLE; |
| s->n_chan = 1; |
| s->maxdata = 0xffff; |
| s->range_table = &range_unknown; |
| s->insn_read = pci171x_counter_insn_read; |
| s->insn_write = pci171x_counter_insn_write; |
| s->insn_config = pci171x_counter_insn_config; |
| 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"); |