text: /*
     comedi/drivers/dt2811.c
     Hardware driver for Data Translation DT2811
  
     COMEDI - Linux Control and Measurement Device Interface
     History:
     Base Version  - David A. Schleef <ds@schleef.org>
     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.
   */
  /*
  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 <linux/module.h>
  #include "../comedidev.h"
  
  static const struct comedi_lrange range_dt2811_pgh_ai_5_unipolar = {
  	4, {
  		UNI_RANGE(5),
  		UNI_RANGE(2.5),
  		UNI_RANGE(1.25),
  		UNI_RANGE(0.625)
  	}
  };
  
  static const struct comedi_lrange range_dt2811_pgh_ai_2_5_bipolar = {
  	4, {
  		BIP_RANGE(2.5),
  		BIP_RANGE(1.25),
  		BIP_RANGE(0.625),
  		BIP_RANGE(0.3125)
  	}
  };
  
  static const struct comedi_lrange range_dt2811_pgh_ai_5_bipolar = {
  	4, {
  		BIP_RANGE(5),
  		BIP_RANGE(2.5),
  		BIP_RANGE(1.25),
  		BIP_RANGE(0.625)
  	}
  };
  
  static const struct comedi_lrange range_dt2811_pgl_ai_5_unipolar = {
  	4, {
  		UNI_RANGE(5),
  		UNI_RANGE(0.5),
  		UNI_RANGE(0.05),
  		UNI_RANGE(0.01)
  	}
  };
  
  static const struct comedi_lrange range_dt2811_pgl_ai_2_5_bipolar = {
  	4, {
  		BIP_RANGE(2.5),
  		BIP_RANGE(0.25),
  		BIP_RANGE(0.025),
  		BIP_RANGE(0.005)
  	}
  };
  
  static const struct comedi_lrange range_dt2811_pgl_ai_5_bipolar = {
  	4, {
  		BIP_RANGE(5),
  		BIP_RANGE(0.5),
  		BIP_RANGE(0.05),
  		BIP_RANGE(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_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
  
  struct dt2811_board {
  	const char *name;
  	const struct comedi_lrange *bip_5;
  	const struct comedi_lrange *bip_2_5;
  	const struct comedi_lrange *unip_5;
  };
  
  enum { card_2811_pgh, card_2811_pgl };
  
  struct dt2811_private {
  	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 struct comedi_lrange *range_type_list[2];
  };
  
  static const struct comedi_lrange *dac_range_types[] = {
  	&range_bipolar5,
  	&range_bipolar2_5,
  	&range_unipolar5
  };
  
  static int dt2811_ai_eoc(struct comedi_device *dev,
  			 struct comedi_subdevice *s,
  			 struct comedi_insn *insn,
  			 unsigned long context)
  {
  	unsigned int status;
  
  	status = inb(dev->iobase + DT2811_ADCSR);
  	if ((status & DT2811_ADBUSY) == 0)
  		return 0;
  	return -EBUSY;
  }
  
  static int dt2811_ai_insn(struct comedi_device *dev, struct comedi_subdevice *s,
  			  struct comedi_insn *insn, unsigned int *data)
  {
  	int chan = CR_CHAN(insn->chanspec);
  	int ret;
  	int i;
  
  	for (i = 0; i < insn->n; i++) {
  		outb(chan, dev->iobase + DT2811_ADGCR);
  
  		ret = comedi_timeout(dev, s, insn, dt2811_ai_eoc, 0);
  		if (ret)
  			return ret;
  
  		data[i] = inb(dev->iobase + DT2811_ADDATLO);
  		data[i] |= inb(dev->iobase + DT2811_ADDATHI) << 8;
  		data[i] &= 0xfff;
  	}
  
  	return i;
  }
  
  static int dt2811_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];
  		outb(val & 0xff, dev->iobase + DT2811_DADAT0LO + 2 * chan);
  		outb((val >> 8) & 0xff,
  		     dev->iobase + DT2811_DADAT0HI + 2 * chan);
  	}
  	s->readback[chan] = val;
  
  	return insn->n;
  }
  
  static int dt2811_di_insn_bits(struct comedi_device *dev,
  			       struct comedi_subdevice *s,
  			       struct comedi_insn *insn, unsigned int *data)
  {
  	data[1] = inb(dev->iobase + DT2811_DIO);
  
  	return insn->n;
  }
  
  static int dt2811_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))
  		outb(s->state, dev->iobase + DT2811_DIO);
  
  	data[1] = s->state;
  
  	return insn->n;
  }
  
  /*
    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(struct comedi_device *dev, struct comedi_devconfig *it)
  {
  	/* int i; */
  	const struct dt2811_board *board = dev->board_ptr;
  	struct dt2811_private *devpriv;
  	int ret;
  	struct comedi_subdevice *s;
  
  	ret = comedi_request_region(dev, it->options[0], 0x8);
  	if (ret)
  		return ret;
  
  #if 0
  	outb(0, dev->iobase + DT2811_ADCSR);
  	udelay(100);
  	i = inb(dev->iobase + DT2811_ADDATLO);
  	i = inb(dev->iobase + DT2811_ADDATHI);
  #endif
  
  	ret = comedi_alloc_subdevices(dev, 4);
  	if (ret)
  		return ret;
  
  	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
  	if (!devpriv)
  		return -ENOMEM;
  
  	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 = board->bip_5;
  		break;
  	case 1:
  		s->range_table = board->bip_2_5;
  		break;
  	case 2:
  		s->range_table = 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->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->insn_write = dt2811_ao_insn_write;
  
  	ret = comedi_alloc_subdev_readback(s);
  	if (ret)
  		return ret;
  
  	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 const struct dt2811_board boardtypes[] = {
  	{
  		.name		= "dt2811-pgh",
  		.bip_5		= &range_dt2811_pgh_ai_5_bipolar,
  		.bip_2_5	= &range_dt2811_pgh_ai_2_5_bipolar,
  		.unip_5		= &range_dt2811_pgh_ai_5_unipolar,
  	}, {
  		.name		= "dt2811-pgl",
  		.bip_5		= &range_dt2811_pgl_ai_5_bipolar,
  		.bip_2_5	= &range_dt2811_pgl_ai_2_5_bipolar,
  		.unip_5		= &range_dt2811_pgl_ai_5_unipolar,
  	},
  };
  
  static struct comedi_driver dt2811_driver = {
  	.driver_name	= "dt2811",
  	.module		= THIS_MODULE,
  	.attach		= dt2811_attach,
  	.detach		= comedi_legacy_detach,
  	.board_name	= &boardtypes[0].name,
  	.num_names	= ARRAY_SIZE(boardtypes),
  	.offset		= sizeof(struct dt2811_board),
  };
  module_comedi_driver(dt2811_driver);
  
  MODULE_AUTHOR("Comedi http://www.comedi.org");
  MODULE_DESCRIPTION("Comedi low-level driver");
  MODULE_LICENSE("GPL");
  