// SPDX-License-Identifier: GPL-2.0+ /* * comedi/drivers/rti800.c * Hardware driver for Analog Devices RTI-800/815 board * * COMEDI - Linux Control and Measurement Device Interface * Copyright (C) 1998 David A. Schleef */ /* * Driver: rti800 * Description: Analog Devices RTI-800/815 * Devices: [Analog Devices] RTI-800 (rti800), RTI-815 (rti815) * Author: David A. Schleef * Status: unknown * Updated: Fri, 05 Sep 2008 14:50:44 +0100 * * Configuration options: * [0] - I/O port base address * [1] - IRQ (not supported / unused) * [2] - A/D mux/reference (number of channels) * 0 = differential * 1 = pseudodifferential (common) * 2 = single-ended * [3] - A/D range * 0 = [-10,10] * 1 = [-5,5] * 2 = [0,10] * [4] - A/D encoding * 0 = two's complement * 1 = straight binary * [5] - DAC 0 range * 0 = [-10,10] * 1 = [0,10] * [6] - DAC 0 encoding * 0 = two's complement * 1 = straight binary * [7] - DAC 1 range (same as DAC 0) * [8] - DAC 1 encoding (same as DAC 0) */ #include #include #include #include "../comedidev.h" /* * Register map */ #define RTI800_CSR 0x00 #define RTI800_CSR_BUSY BIT(7) #define RTI800_CSR_DONE BIT(6) #define RTI800_CSR_OVERRUN BIT(5) #define RTI800_CSR_TCR BIT(4) #define RTI800_CSR_DMA_ENAB BIT(3) #define RTI800_CSR_INTR_TC BIT(2) #define RTI800_CSR_INTR_EC BIT(1) #define RTI800_CSR_INTR_OVRN BIT(0) #define RTI800_MUXGAIN 0x01 #define RTI800_CONVERT 0x02 #define RTI800_ADCLO 0x03 #define RTI800_ADCHI 0x04 #define RTI800_DAC0LO 0x05 #define RTI800_DAC0HI 0x06 #define RTI800_DAC1LO 0x07 #define RTI800_DAC1HI 0x08 #define RTI800_CLRFLAGS 0x09 #define RTI800_DI 0x0a #define RTI800_DO 0x0b #define RTI800_9513A_DATA 0x0c #define RTI800_9513A_CNTRL 0x0d #define RTI800_9513A_STATUS 0x0d static const struct comedi_lrange range_rti800_ai_10_bipolar = { 4, { BIP_RANGE(10), BIP_RANGE(1), BIP_RANGE(0.1), BIP_RANGE(0.02) } }; static const struct comedi_lrange range_rti800_ai_5_bipolar = { 4, { BIP_RANGE(5), BIP_RANGE(0.5), BIP_RANGE(0.05), BIP_RANGE(0.01) } }; static const struct comedi_lrange range_rti800_ai_unipolar = { 4, { UNI_RANGE(10), UNI_RANGE(1), UNI_RANGE(0.1), UNI_RANGE(0.02) } }; static const struct comedi_lrange *const rti800_ai_ranges[] = { &range_rti800_ai_10_bipolar, &range_rti800_ai_5_bipolar, &range_rti800_ai_unipolar, }; static const struct comedi_lrange *const rti800_ao_ranges[] = { &range_bipolar10, &range_unipolar10, }; struct rti800_board { const char *name; int has_ao; }; static const struct rti800_board rti800_boardtypes[] = { { .name = "rti800", }, { .name = "rti815", .has_ao = 1, }, }; struct rti800_private { bool adc_2comp; bool dac_2comp[2]; const struct comedi_lrange *ao_range_type_list[2]; unsigned char muxgain_bits; }; static int rti800_ai_eoc(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned long context) { unsigned char status; status = inb(dev->iobase + RTI800_CSR); if (status & RTI800_CSR_OVERRUN) { outb(0, dev->iobase + RTI800_CLRFLAGS); return -EOVERFLOW; } if (status & RTI800_CSR_DONE) return 0; return -EBUSY; } static int rti800_ai_insn_read(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { struct rti800_private *devpriv = dev->private; unsigned int chan = CR_CHAN(insn->chanspec); unsigned int gain = CR_RANGE(insn->chanspec); unsigned char muxgain_bits; int ret; int i; inb(dev->iobase + RTI800_ADCHI); outb(0, dev->iobase + RTI800_CLRFLAGS); muxgain_bits = chan | (gain << 5); if (muxgain_bits != devpriv->muxgain_bits) { devpriv->muxgain_bits = muxgain_bits; outb(devpriv->muxgain_bits, dev->iobase + RTI800_MUXGAIN); /* * Without a delay here, the RTI_CSR_OVERRUN bit * gets set, and you will have an error. */ if (insn->n > 0) { int delay = (gain == 0) ? 10 : (gain == 1) ? 20 : (gain == 2) ? 40 : 80; udelay(delay); } } for (i = 0; i < insn->n; i++) { unsigned int val; outb(0, dev->iobase + RTI800_CONVERT); ret = comedi_timeout(dev, s, insn, rti800_ai_eoc, 0); if (ret) return ret; val = inb(dev->iobase + RTI800_ADCLO); val |= (inb(dev->iobase + RTI800_ADCHI) & 0xf) << 8; if (devpriv->adc_2comp) val = comedi_offset_munge(s, val); data[i] = val; } return insn->n; } static int rti800_ao_insn_write(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { struct rti800_private *devpriv = dev->private; unsigned int chan = CR_CHAN(insn->chanspec); int reg_lo = chan ? RTI800_DAC1LO : RTI800_DAC0LO; int reg_hi = chan ? RTI800_DAC1HI : RTI800_DAC0HI; int i; for (i = 0; i < insn->n; i++) { unsigned int val = data[i]; s->readback[chan] = val; if (devpriv->dac_2comp[chan]) val = comedi_offset_munge(s, val); outb(val & 0xff, dev->iobase + reg_lo); outb((val >> 8) & 0xff, dev->iobase + reg_hi); } return insn->n; } static int rti800_di_insn_bits(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { data[1] = inb(dev->iobase + RTI800_DI); return insn->n; } static int rti800_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)) { /* Outputs are inverted... */ outb(s->state ^ 0xff, dev->iobase + RTI800_DO); } data[1] = s->state; return insn->n; } static int rti800_attach(struct comedi_device *dev, struct comedi_devconfig *it) { const struct rti800_board *board = dev->board_ptr; struct rti800_private *devpriv; struct comedi_subdevice *s; int ret; ret = comedi_request_region(dev, it->options[0], 0x10); if (ret) return ret; outb(0, dev->iobase + RTI800_CSR); inb(dev->iobase + RTI800_ADCHI); outb(0, dev->iobase + RTI800_CLRFLAGS); devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); if (!devpriv) return -ENOMEM; devpriv->adc_2comp = (it->options[4] == 0); devpriv->dac_2comp[0] = (it->options[6] == 0); devpriv->dac_2comp[1] = (it->options[8] == 0); /* invalid, forces the MUXGAIN register to be set when first used */ devpriv->muxgain_bits = 0xff; ret = comedi_alloc_subdevices(dev, 4); if (ret) return ret; s = &dev->subdevices[0]; /* ai subdevice */ s->type = COMEDI_SUBD_AI; s->subdev_flags = SDF_READABLE | SDF_GROUND; s->n_chan = (it->options[2] ? 16 : 8); s->insn_read = rti800_ai_insn_read; s->maxdata = 0x0fff; s->range_table = (it->options[3] < ARRAY_SIZE(rti800_ai_ranges)) ? rti800_ai_ranges[it->options[3]] : &range_unknown; s = &dev->subdevices[1]; if (board->has_ao) { /* ao subdevice (only on rti815) */ s->type = COMEDI_SUBD_AO; s->subdev_flags = SDF_WRITABLE; s->n_chan = 2; s->maxdata = 0x0fff; s->range_table_list = devpriv->ao_range_type_list; devpriv->ao_range_type_list[0] = (it->options[5] < ARRAY_SIZE(rti800_ao_ranges)) ? rti800_ao_ranges[it->options[5]] : &range_unknown; devpriv->ao_range_type_list[1] = (it->options[7] < ARRAY_SIZE(rti800_ao_ranges)) ? rti800_ao_ranges[it->options[7]] : &range_unknown; s->insn_write = rti800_ao_insn_write; ret = comedi_alloc_subdev_readback(s); if (ret) return ret; } else { s->type = COMEDI_SUBD_UNUSED; } s = &dev->subdevices[2]; /* di */ s->type = COMEDI_SUBD_DI; s->subdev_flags = SDF_READABLE; s->n_chan = 8; s->insn_bits = rti800_di_insn_bits; s->maxdata = 1; s->range_table = &range_digital; s = &dev->subdevices[3]; /* do */ s->type = COMEDI_SUBD_DO; s->subdev_flags = SDF_WRITABLE; s->n_chan = 8; s->insn_bits = rti800_do_insn_bits; s->maxdata = 1; s->range_table = &range_digital; /* * There is also an Am9513 timer on these boards. This subdevice * is not currently supported. */ return 0; } static struct comedi_driver rti800_driver = { .driver_name = "rti800", .module = THIS_MODULE, .attach = rti800_attach, .detach = comedi_legacy_detach, .num_names = ARRAY_SIZE(rti800_boardtypes), .board_name = &rti800_boardtypes[0].name, .offset = sizeof(struct rti800_board), }; module_comedi_driver(rti800_driver); MODULE_DESCRIPTION("Comedi: RTI-800 Multifunction Analog/Digital board"); MODULE_AUTHOR("Comedi https://www.comedi.org"); MODULE_LICENSE("GPL");