From 41cc83e4d3c810d9a61875c9af412122637410de Mon Sep 17 00:00:00 2001 From: David Schleef Date: Thu, 19 Feb 2009 10:08:26 -0800 Subject: Staging: comedi: add dt2811 driver From: David Schleef Driver for DataTranslation DT2811 From: David Schleef Cc: Ian Abbott Cc: Frank Mori Hess Signed-off-by: Greg Kroah-Hartman --- drivers/staging/comedi/drivers/dt2811.c | 601 ++++++++++++++++++++++++++++++++ 1 file changed, 601 insertions(+) --- /dev/null +++ b/drivers/staging/comedi/drivers/dt2811.c @@ -0,0 +1,601 @@ +/* + comedi/drivers/dt2811.c + Hardware driver for Data Translation DT2811 + + COMEDI - Linux Control and Measurement Device Interface + History: + Base Version - David A. Schleef + December 1998 - Updated to work. David does not have a DT2811 + board any longer so this was suffering from bitrot. + Updated performed by ... + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +/* +Driver: dt2811 +Description: Data Translation DT2811 +Author: ds +Devices: [Data Translation] DT2811-PGL (dt2811-pgl), DT2811-PGH (dt2811-pgh) +Status: works + +Configuration options: + [0] - I/O port base address + [1] - IRQ, although this is currently unused + [2] - A/D reference + 0 = signle-ended + 1 = differential + 2 = pseudo-differential (common reference) + [3] - A/D range + 0 = [-5,5] + 1 = [-2.5,2.5] + 2 = [0,5] + [4] - D/A 0 range (same choices) + [4] - D/A 1 range (same choices) +*/ + +#include "../comedidev.h" + +#include + +static const char *driver_name = "dt2811"; + +static const comedi_lrange range_dt2811_pgh_ai_5_unipolar = { 4, { + RANGE(0, 5), + RANGE(0, 2.5), + RANGE(0, 1.25), + RANGE(0, 0.625) + } +}; +static const comedi_lrange range_dt2811_pgh_ai_2_5_bipolar = { 4, { + RANGE(-2.5, 2.5), + RANGE(-1.25, 1.25), + RANGE(-0.625, 0.625), + RANGE(-0.3125, 0.3125) + } +}; +static const comedi_lrange range_dt2811_pgh_ai_5_bipolar = { 4, { + RANGE(-5, 5), + RANGE(-2.5, 2.5), + RANGE(-1.25, 1.25), + RANGE(-0.625, 0.625) + } +}; +static const comedi_lrange range_dt2811_pgl_ai_5_unipolar = { 4, { + RANGE(0, 5), + RANGE(0, 0.5), + RANGE(0, 0.05), + RANGE(0, 0.01) + } +}; +static const comedi_lrange range_dt2811_pgl_ai_2_5_bipolar = { 4, { + RANGE(-2.5, 2.5), + RANGE(-0.25, 0.25), + RANGE(-0.025, 0.025), + RANGE(-0.005, 0.005) + } +}; +static const comedi_lrange range_dt2811_pgl_ai_5_bipolar = { 4, { + RANGE(-5, 5), + RANGE(-0.5, 0.5), + RANGE(-0.05, 0.05), + RANGE(-0.01, 0.01) + } +}; + +/* + + 0x00 ADCSR R/W A/D Control/Status Register + bit 7 - (R) 1 indicates A/D conversion done + reading ADDAT clears bit + (W) ignored + bit 6 - (R) 1 indicates A/D error + (W) ignored + bit 5 - (R) 1 indicates A/D busy, cleared at end + of conversion + (W) ignored + bit 4 - (R) 0 + (W) + bit 3 - (R) 0 + bit 2 - (R/W) 1 indicates interrupts enabled + bits 1,0 - (R/W) mode bits + 00 single conversion on ADGCR load + 01 continuous conversion, internal clock, + (clock enabled on ADGCR load) + 10 continuous conversion, internal clock, + external trigger + 11 continuous conversion, external clock, + external trigger + + 0x01 ADGCR R/W A/D Gain/Channel Register + bit 6,7 - (R/W) gain select + 00 gain=1, both PGH, PGL models + 01 gain=2 PGH, 10 PGL + 10 gain=4 PGH, 100 PGL + 11 gain=8 PGH, 500 PGL + bit 4,5 - reserved + bit 3-0 - (R/W) channel select + channel number from 0-15 + + 0x02,0x03 (R) ADDAT A/D Data Register + (W) DADAT0 D/A Data Register 0 + 0x02 low byte + 0x03 high byte + + 0x04,0x05 (W) DADAT0 D/A Data Register 1 + + 0x06 (R) DIO0 Digital Input Port 0 + (W) DIO1 Digital Output Port 1 + + 0x07 TMRCTR (R/W) Timer/Counter Register + bits 6,7 - reserved + bits 5-3 - Timer frequency control (mantissa) + 543 divisor freqency (kHz) + 000 1 600 + 001 10 60 + 010 2 300 + 011 3 200 + 100 4 150 + 101 5 120 + 110 6 100 + 111 12 50 + bits 2-0 - Timer frequency control (exponent) + 210 multiply divisor/divide frequency by + 000 1 + 001 10 + 010 100 + 011 1000 + 100 10000 + 101 100000 + 110 1000000 + 111 10000000 + + */ + +#define TIMEOUT 10000 + +#define DT2811_SIZE 8 + +#define DT2811_ADCSR 0 +#define DT2811_ADGCR 1 +#define DT2811_ADDATLO 2 +#define DT2811_ADDATHI 3 +#define DT2811_DADAT0LO 2 +#define DT2811_DADAT0HI 3 +#define DT2811_DADAT1LO 4 +#define DT2811_DADAT1HI 5 +#define DT2811_DIO 6 +#define DT2811_TMRCTR 7 + +/* + * flags + */ + +/* ADCSR */ + +#define DT2811_ADDONE 0x80 +#define DT2811_ADERROR 0x40 +#define DT2811_ADBUSY 0x20 +#define DT2811_CLRERROR 0x10 +#define DT2811_INTENB 0x04 +#define DT2811_ADMODE 0x03 + +typedef struct { + const char *name; + const comedi_lrange *bip_5; + const comedi_lrange *bip_2_5; + const comedi_lrange *unip_5; +} boardtype; +static const boardtype boardtypes[] = { + {"dt2811-pgh", + &range_dt2811_pgh_ai_5_bipolar, + &range_dt2811_pgh_ai_2_5_bipolar, + &range_dt2811_pgh_ai_5_unipolar, + }, + {"dt2811-pgl", + &range_dt2811_pgl_ai_5_bipolar, + &range_dt2811_pgl_ai_2_5_bipolar, + &range_dt2811_pgl_ai_5_unipolar, + }, +}; + +#define this_board ((const boardtype *)dev->board_ptr) + +static int dt2811_attach(comedi_device * dev, comedi_devconfig * it); +static int dt2811_detach(comedi_device * dev); +static comedi_driver driver_dt2811 = { + driver_name:"dt2811", + module:THIS_MODULE, + attach:dt2811_attach, + detach:dt2811_detach, + board_name:&boardtypes[0].name, + num_names:sizeof(boardtypes) / sizeof(boardtype), + offset:sizeof(boardtype), +}; + +COMEDI_INITCLEANUP(driver_dt2811); + +static int dt2811_ai_insn(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data); +static int dt2811_ao_insn(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data); +static int dt2811_ao_insn_read(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data); +static int dt2811_di_insn_bits(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data); +static int dt2811_do_insn_bits(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data); + +enum { card_2811_pgh, card_2811_pgl }; +typedef struct { + int ntrig; + int curadchan; + enum { + adc_singleended, adc_diff, adc_pseudo_diff + } adc_mux; + enum { + dac_bipolar_5, dac_bipolar_2_5, dac_unipolar_5 + } dac_range[2]; + const comedi_lrange *range_type_list[2]; + lsampl_t ao_readback[2]; +} dt2811_private; + +#define devpriv ((dt2811_private *)dev->private) + +static const comedi_lrange *dac_range_types[] = { + &range_bipolar5, + &range_bipolar2_5, + &range_unipolar5 +}; + +#define DT2811_TIMEOUT 5 + +#if 0 +static irqreturn_t dt2811_interrupt(int irq, void *d PT_REGS_ARG) +{ + int lo, hi; + int data; + comedi_device *dev = d; + + if (!dev->attached) { + comedi_error(dev, "spurious interrupt"); + return IRQ_HANDLED; + } + + lo = inb(dev->iobase + DT2811_ADDATLO); + hi = inb(dev->iobase + DT2811_ADDATHI); + + data = lo + (hi << 8); + + if (!(--devpriv->ntrig)) { + /* how to turn off acquisition */ + s->async->events |= COMEDI_SB_EOA; + } + comedi_event(dev, s); + return IRQ_HANDLED; +} +#endif + +/* + options[0] Board base address + options[1] IRQ + options[2] Input configuration + 0 == single-ended + 1 == differential + 2 == pseudo-differential + options[3] Analog input range configuration + 0 == bipolar 5 (-5V -- +5V) + 1 == bipolar 2.5V (-2.5V -- +2.5V) + 2 == unipolar 5V (0V -- +5V) + options[4] Analog output 0 range configuration + 0 == bipolar 5 (-5V -- +5V) + 1 == bipolar 2.5V (-2.5V -- +2.5V) + 2 == unipolar 5V (0V -- +5V) + options[5] Analog output 1 range configuration + 0 == bipolar 5 (-5V -- +5V) + 1 == bipolar 2.5V (-2.5V -- +2.5V) + 2 == unipolar 5V (0V -- +5V) +*/ + +static int dt2811_attach(comedi_device * dev, comedi_devconfig * it) +{ + //int i, irq; + //unsigned long irqs; + //long flags; + int ret; + comedi_subdevice *s; + unsigned long iobase; + + iobase = it->options[0]; + + printk("comedi%d: dt2811: base=0x%04lx\n", dev->minor, iobase); + + if (!request_region(iobase, DT2811_SIZE, driver_name)) { + printk("I/O port conflict\n"); + return -EIO; + } + + dev->iobase = iobase; + dev->board_name = this_board->name; + +#if 0 + outb(0, dev->iobase + DT2811_ADCSR); + comedi_udelay(100); + i = inb(dev->iobase + DT2811_ADDATLO); + i = inb(dev->iobase + DT2811_ADDATHI); +#endif + +#if 0 + irq = it->options[1]; + if (irq < 0) { + save_flags(flags); + sti(); + irqs = probe_irq_on(); + + outb(DT2811_CLRERROR | DT2811_INTENB, + dev->iobase + DT2811_ADCSR); + outb(0, dev->iobase + DT2811_ADGCR); + + comedi_udelay(100); + + irq = probe_irq_off(irqs); + restore_flags(flags); + + /*outb(DT2811_CLRERROR|DT2811_INTENB,dev->iobase+DT2811_ADCSR); */ + + if (inb(dev->iobase + DT2811_ADCSR) & DT2811_ADERROR) { + printk("error probing irq (bad) \n"); + } + dev->irq = 0; + if (irq > 0) { + i = inb(dev->iobase + DT2811_ADDATLO); + i = inb(dev->iobase + DT2811_ADDATHI); + printk("(irq = %d)\n", irq); + ret = comedi_request_irq(irq, dt2811_interrupt, 0, + driver_name, dev); + if (ret < 0) + return -EIO; + dev->irq = irq; + } else if (irq == 0) { + printk("(no irq)\n"); + } else { + printk("( multiple irq's -- this is bad! )\n"); + } + } +#endif + + if ((ret = alloc_subdevices(dev, 4)) < 0) + return ret; + if ((ret = alloc_private(dev, sizeof(dt2811_private))) < 0) + return ret; + switch (it->options[2]) { + case 0: + devpriv->adc_mux = adc_singleended; + break; + case 1: + devpriv->adc_mux = adc_diff; + break; + case 2: + devpriv->adc_mux = adc_pseudo_diff; + break; + default: + devpriv->adc_mux = adc_singleended; + break; + } + switch (it->options[4]) { + case 0: + devpriv->dac_range[0] = dac_bipolar_5; + break; + case 1: + devpriv->dac_range[0] = dac_bipolar_2_5; + break; + case 2: + devpriv->dac_range[0] = dac_unipolar_5; + break; + default: + devpriv->dac_range[0] = dac_bipolar_5; + break; + } + switch (it->options[5]) { + case 0: + devpriv->dac_range[1] = dac_bipolar_5; + break; + case 1: + devpriv->dac_range[1] = dac_bipolar_2_5; + break; + case 2: + devpriv->dac_range[1] = dac_unipolar_5; + break; + default: + devpriv->dac_range[1] = dac_bipolar_5; + break; + } + + s = dev->subdevices + 0; + /* initialize the ADC subdevice */ + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND; + s->n_chan = devpriv->adc_mux == adc_diff ? 8 : 16; + s->insn_read = dt2811_ai_insn; + s->maxdata = 0xfff; + switch (it->options[3]) { + case 0: + default: + s->range_table = this_board->bip_5; + break; + case 1: + s->range_table = this_board->bip_2_5; + break; + case 2: + s->range_table = this_board->unip_5; + break; + } + + s = dev->subdevices + 1; + /* ao subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 2; + s->insn_write = dt2811_ao_insn; + s->insn_read = dt2811_ao_insn_read; + s->maxdata = 0xfff; + s->range_table_list = devpriv->range_type_list; + devpriv->range_type_list[0] = dac_range_types[devpriv->dac_range[0]]; + devpriv->range_type_list[1] = dac_range_types[devpriv->dac_range[1]]; + + s = dev->subdevices + 2; + /* di subdevice */ + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 8; + s->insn_bits = dt2811_di_insn_bits; + s->maxdata = 1; + s->range_table = &range_digital; + + s = dev->subdevices + 3; + /* do subdevice */ + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 8; + s->insn_bits = dt2811_do_insn_bits; + s->maxdata = 1; + s->state = 0; + s->range_table = &range_digital; + + return 0; +} + +static int dt2811_detach(comedi_device * dev) +{ + printk("comedi%d: dt2811: remove\n", dev->minor); + + if (dev->irq) { + comedi_free_irq(dev->irq, dev); + } + if (dev->iobase) { + release_region(dev->iobase, DT2811_SIZE); + } + + return 0; +} + +static int dt2811_ai_insn(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + int chan = CR_CHAN(insn->chanspec); + int timeout = DT2811_TIMEOUT; + int i; + + for (i = 0; i < insn->n; i++) { + outb(chan, dev->iobase + DT2811_ADGCR); + + while (timeout + && inb(dev->iobase + DT2811_ADCSR) & DT2811_ADBUSY) + timeout--; + if (!timeout) + return -ETIME; + + data[i] = inb(dev->iobase + DT2811_ADDATLO); + data[i] |= inb(dev->iobase + DT2811_ADDATHI) << 8; + data[i] &= 0xfff; + } + + return i; +} + +#if 0 +/* Wow. This is code from the Comedi stone age. But it hasn't been + * replaced, so I'll let it stay. */ +int dt2811_adtrig(kdev_t minor, comedi_adtrig * adtrig) +{ + comedi_device *dev = comedi_devices + minor; + + if (adtrig->n < 1) + return 0; + dev->curadchan = adtrig->chan; + switch (dev->i_admode) { + case COMEDI_MDEMAND: + dev->ntrig = adtrig->n - 1; + /*printk("dt2811: AD soft trigger\n"); */ + /*outb(DT2811_CLRERROR|DT2811_INTENB,dev->iobase+DT2811_ADCSR); *//* not neccessary */ + outb(dev->curadchan, dev->iobase + DT2811_ADGCR); + do_gettimeofday(&trigtime); + break; + case COMEDI_MCONTS: + dev->ntrig = adtrig->n; + break; + } + + return 0; +} +#endif + +static int dt2811_ao_insn(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + int i; + int chan; + + chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) { + outb(data[i] & 0xff, dev->iobase + DT2811_DADAT0LO + 2 * chan); + outb((data[i] >> 8) & 0xff, + dev->iobase + DT2811_DADAT0HI + 2 * chan); + devpriv->ao_readback[chan] = data[i]; + } + + return i; +} + +static int dt2811_ao_insn_read(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + int i; + int chan; + + chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) { + data[i] = devpriv->ao_readback[chan]; + } + + return i; +} + +static int dt2811_di_insn_bits(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + if (insn->n != 2) + return -EINVAL; + + data[1] = inb(dev->iobase + DT2811_DIO); + + return 2; +} + +static int dt2811_do_insn_bits(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + if (insn->n != 2) + return -EINVAL; + + s->state &= ~data[0]; + s->state |= data[0] & data[1]; + outb(s->state, dev->iobase + DT2811_DIO); + + data[1] = s->state; + + return 2; +}