// SPDX-License-Identifier: GPL-2.0+ /* * pcl711.c * Comedi driver for PC-LabCard PCL-711 and AdSys ACL-8112 and compatibles * Copyright (C) 1998 David A. Schleef * Janne Jalkanen * Eric Bunn * * COMEDI - Linux Control and Measurement Device Interface * Copyright (C) 1998 David A. Schleef */ /* * Driver: pcl711 * Description: Advantech PCL-711 and 711b, ADLink ACL-8112 * Devices: [Advantech] PCL-711 (pcl711), PCL-711B (pcl711b), * [ADLink] ACL-8112HG (acl8112hg), ACL-8112DG (acl8112dg) * Author: David A. Schleef * Janne Jalkanen * Eric Bunn * Updated: * Status: mostly complete * * Configuration Options: * [0] - I/O port base * [1] - IRQ, optional */ #include #include #include #include "../comedidev.h" #include "comedi_8254.h" /* * I/O port register map */ #define PCL711_TIMER_BASE 0x00 #define PCL711_AI_LSB_REG 0x04 #define PCL711_AI_MSB_REG 0x05 #define PCL711_AI_MSB_DRDY BIT(4) #define PCL711_AO_LSB_REG(x) (0x04 + ((x) * 2)) #define PCL711_AO_MSB_REG(x) (0x05 + ((x) * 2)) #define PCL711_DI_LSB_REG 0x06 #define PCL711_DI_MSB_REG 0x07 #define PCL711_INT_STAT_REG 0x08 #define PCL711_INT_STAT_CLR (0 << 0) /* any value will work */ #define PCL711_AI_GAIN_REG 0x09 #define PCL711_AI_GAIN(x) (((x) & 0xf) << 0) #define PCL711_MUX_REG 0x0a #define PCL711_MUX_CHAN(x) (((x) & 0xf) << 0) #define PCL711_MUX_CS0 BIT(4) #define PCL711_MUX_CS1 BIT(5) #define PCL711_MUX_DIFF (PCL711_MUX_CS0 | PCL711_MUX_CS1) #define PCL711_MODE_REG 0x0b #define PCL711_MODE(x) (((x) & 0x7) << 0) #define PCL711_MODE_DEFAULT PCL711_MODE(0) #define PCL711_MODE_SOFTTRIG PCL711_MODE(1) #define PCL711_MODE_EXT PCL711_MODE(2) #define PCL711_MODE_EXT_IRQ PCL711_MODE(3) #define PCL711_MODE_PACER PCL711_MODE(4) #define PCL711_MODE_PACER_IRQ PCL711_MODE(6) #define PCL711_MODE_IRQ(x) (((x) & 0x7) << 4) #define PCL711_SOFTTRIG_REG 0x0c #define PCL711_SOFTTRIG (0 << 0) /* any value will work */ #define PCL711_DO_LSB_REG 0x0d #define PCL711_DO_MSB_REG 0x0e static const struct comedi_lrange range_pcl711b_ai = { 5, { BIP_RANGE(5), BIP_RANGE(2.5), BIP_RANGE(1.25), BIP_RANGE(0.625), BIP_RANGE(0.3125) } }; static const struct comedi_lrange range_acl8112hg_ai = { 12, { BIP_RANGE(5), BIP_RANGE(0.5), BIP_RANGE(0.05), BIP_RANGE(0.005), UNI_RANGE(10), UNI_RANGE(1), UNI_RANGE(0.1), UNI_RANGE(0.01), BIP_RANGE(10), BIP_RANGE(1), BIP_RANGE(0.1), BIP_RANGE(0.01) } }; static const struct comedi_lrange range_acl8112dg_ai = { 9, { BIP_RANGE(5), BIP_RANGE(2.5), BIP_RANGE(1.25), BIP_RANGE(0.625), UNI_RANGE(10), UNI_RANGE(5), UNI_RANGE(2.5), UNI_RANGE(1.25), BIP_RANGE(10) } }; struct pcl711_board { const char *name; int n_aichan; int n_aochan; int maxirq; const struct comedi_lrange *ai_range_type; }; static const struct pcl711_board boardtypes[] = { { .name = "pcl711", .n_aichan = 8, .n_aochan = 1, .ai_range_type = &range_bipolar5, }, { .name = "pcl711b", .n_aichan = 8, .n_aochan = 1, .maxirq = 7, .ai_range_type = &range_pcl711b_ai, }, { .name = "acl8112hg", .n_aichan = 16, .n_aochan = 2, .maxirq = 15, .ai_range_type = &range_acl8112hg_ai, }, { .name = "acl8112dg", .n_aichan = 16, .n_aochan = 2, .maxirq = 15, .ai_range_type = &range_acl8112dg_ai, }, }; static void pcl711_ai_set_mode(struct comedi_device *dev, unsigned int mode) { /* * The pcl711b board uses bits in the mode register to select the * interrupt. The other boards supported by this driver all use * jumpers on the board. * * Enables the interrupt when needed on the pcl711b board. These * bits do nothing on the other boards. */ if (mode == PCL711_MODE_EXT_IRQ || mode == PCL711_MODE_PACER_IRQ) mode |= PCL711_MODE_IRQ(dev->irq); outb(mode, dev->iobase + PCL711_MODE_REG); } static unsigned int pcl711_ai_get_sample(struct comedi_device *dev, struct comedi_subdevice *s) { unsigned int val; val = inb(dev->iobase + PCL711_AI_MSB_REG) << 8; val |= inb(dev->iobase + PCL711_AI_LSB_REG); return val & s->maxdata; } static int pcl711_ai_cancel(struct comedi_device *dev, struct comedi_subdevice *s) { outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG); pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG); return 0; } static irqreturn_t pcl711_interrupt(int irq, void *d) { struct comedi_device *dev = d; struct comedi_subdevice *s = dev->read_subdev; struct comedi_cmd *cmd = &s->async->cmd; unsigned int data; if (!dev->attached) { dev_err(dev->class_dev, "spurious interrupt\n"); return IRQ_HANDLED; } data = pcl711_ai_get_sample(dev, s); outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG); comedi_buf_write_samples(s, &data, 1); if (cmd->stop_src == TRIG_COUNT && s->async->scans_done >= cmd->stop_arg) s->async->events |= COMEDI_CB_EOA; comedi_handle_events(dev, s); return IRQ_HANDLED; } static void pcl711_set_changain(struct comedi_device *dev, struct comedi_subdevice *s, unsigned int chanspec) { unsigned int chan = CR_CHAN(chanspec); unsigned int range = CR_RANGE(chanspec); unsigned int aref = CR_AREF(chanspec); unsigned int mux = 0; outb(PCL711_AI_GAIN(range), dev->iobase + PCL711_AI_GAIN_REG); if (s->n_chan > 8) { /* Select the correct MPC508A chip */ if (aref == AREF_DIFF) { chan &= 0x7; mux |= PCL711_MUX_DIFF; } else { if (chan < 8) mux |= PCL711_MUX_CS0; else mux |= PCL711_MUX_CS1; } } outb(mux | PCL711_MUX_CHAN(chan), dev->iobase + PCL711_MUX_REG); } static int pcl711_ai_eoc(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned long context) { unsigned int status; status = inb(dev->iobase + PCL711_AI_MSB_REG); if ((status & PCL711_AI_MSB_DRDY) == 0) return 0; return -EBUSY; } static int pcl711_ai_insn_read(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { int ret; int i; pcl711_set_changain(dev, s, insn->chanspec); pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG); for (i = 0; i < insn->n; i++) { outb(PCL711_SOFTTRIG, dev->iobase + PCL711_SOFTTRIG_REG); ret = comedi_timeout(dev, s, insn, pcl711_ai_eoc, 0); if (ret) return ret; data[i] = pcl711_ai_get_sample(dev, s); } return insn->n; } static int pcl711_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); err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER | 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->scan_begin_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); if (cmd->scan_begin_src == TRIG_EXT) { err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); } else { #define MAX_SPEED 1000 err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, MAX_SPEED); } 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 */ if (cmd->scan_begin_src == TRIG_TIMER) { unsigned int arg = cmd->scan_begin_arg; comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags); err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg); } if (err) return 4; return 0; } static int pcl711_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) { struct comedi_cmd *cmd = &s->async->cmd; pcl711_set_changain(dev, s, cmd->chanlist[0]); if (cmd->scan_begin_src == TRIG_TIMER) { comedi_8254_update_divisors(dev->pacer); comedi_8254_pacer_enable(dev->pacer, 1, 2, true); outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG); pcl711_ai_set_mode(dev, PCL711_MODE_PACER_IRQ); } else { pcl711_ai_set_mode(dev, PCL711_MODE_EXT_IRQ); } return 0; } static void pcl711_ao_write(struct comedi_device *dev, unsigned int chan, unsigned int val) { outb(val & 0xff, dev->iobase + PCL711_AO_LSB_REG(chan)); outb((val >> 8) & 0xff, dev->iobase + PCL711_AO_MSB_REG(chan)); } static int pcl711_ao_insn_write(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { unsigned int chan = CR_CHAN(insn->chanspec); unsigned int val = s->readback[chan]; int i; for (i = 0; i < insn->n; i++) { val = data[i]; pcl711_ao_write(dev, chan, val); } s->readback[chan] = val; return insn->n; } static int pcl711_di_insn_bits(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { unsigned int val; val = inb(dev->iobase + PCL711_DI_LSB_REG); val |= (inb(dev->iobase + PCL711_DI_MSB_REG) << 8); data[1] = val; return insn->n; } static int pcl711_do_insn_bits(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { unsigned int mask; mask = comedi_dio_update_state(s, data); if (mask) { if (mask & 0x00ff) outb(s->state & 0xff, dev->iobase + PCL711_DO_LSB_REG); if (mask & 0xff00) outb((s->state >> 8), dev->iobase + PCL711_DO_MSB_REG); } data[1] = s->state; return insn->n; } static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it) { const struct pcl711_board *board = dev->board_ptr; struct comedi_subdevice *s; int ret; ret = comedi_request_region(dev, it->options[0], 0x10); if (ret) return ret; if (it->options[1] && it->options[1] <= board->maxirq) { ret = request_irq(it->options[1], pcl711_interrupt, 0, dev->board_name, dev); if (ret == 0) dev->irq = it->options[1]; } dev->pacer = comedi_8254_init(dev->iobase + PCL711_TIMER_BASE, I8254_OSC_BASE_2MHZ, I8254_IO8, 0); if (!dev->pacer) return -ENOMEM; ret = comedi_alloc_subdevices(dev, 4); if (ret) return ret; /* Analog Input subdevice */ s = &dev->subdevices[0]; s->type = COMEDI_SUBD_AI; s->subdev_flags = SDF_READABLE | SDF_GROUND; if (board->n_aichan > 8) s->subdev_flags |= SDF_DIFF; s->n_chan = board->n_aichan; s->maxdata = 0xfff; s->range_table = board->ai_range_type; s->insn_read = pcl711_ai_insn_read; if (dev->irq) { dev->read_subdev = s; s->subdev_flags |= SDF_CMD_READ; s->len_chanlist = 1; s->do_cmdtest = pcl711_ai_cmdtest; s->do_cmd = pcl711_ai_cmd; s->cancel = pcl711_ai_cancel; } /* Analog Output subdevice */ s = &dev->subdevices[1]; s->type = COMEDI_SUBD_AO; s->subdev_flags = SDF_WRITABLE; s->n_chan = board->n_aochan; s->maxdata = 0xfff; s->range_table = &range_bipolar5; s->insn_write = pcl711_ao_insn_write; ret = comedi_alloc_subdev_readback(s); if (ret) return ret; /* Digital Input subdevice */ s = &dev->subdevices[2]; 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 = pcl711_di_insn_bits; /* Digital Output subdevice */ s = &dev->subdevices[3]; 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 = pcl711_do_insn_bits; /* clear DAC */ pcl711_ao_write(dev, 0, 0x0); pcl711_ao_write(dev, 1, 0x0); return 0; } static struct comedi_driver pcl711_driver = { .driver_name = "pcl711", .module = THIS_MODULE, .attach = pcl711_attach, .detach = comedi_legacy_detach, .board_name = &boardtypes[0].name, .num_names = ARRAY_SIZE(boardtypes), .offset = sizeof(struct pcl711_board), }; module_comedi_driver(pcl711_driver); MODULE_AUTHOR("Comedi http://www.comedi.org"); MODULE_DESCRIPTION("Comedi driver for PCL-711 compatible boards"); MODULE_LICENSE("GPL");