diff options
Diffstat (limited to 'drivers/staging/comedi/drivers/dt2801.c')
| -rw-r--r-- | drivers/staging/comedi/drivers/dt2801.c | 700 |
1 files changed, 700 insertions, 0 deletions
diff --git a/drivers/staging/comedi/drivers/dt2801.c b/drivers/staging/comedi/drivers/dt2801.c new file mode 100644 index 00000000..8f5006d7 --- /dev/null +++ b/drivers/staging/comedi/drivers/dt2801.c @@ -0,0 +1,700 @@ +/* + * comedi/drivers/dt2801.c + * Device Driver for DataTranslation DT2801 + * + */ +/* +Driver: dt2801 +Description: Data Translation DT2801 series and DT01-EZ +Author: ds +Status: works +Devices: [Data Translation] DT2801 (dt2801), DT2801-A, DT2801/5716A, + DT2805, DT2805/5716A, DT2808, DT2818, DT2809, DT01-EZ + +This driver can autoprobe the type of board. + +Configuration options: + [0] - I/O port base address + [1] - unused + [2] - A/D reference 0=differential, 1=single-ended + [3] - A/D range + 0 = [-10, 10] + 1 = [0,10] + [4] - D/A 0 range + 0 = [-10, 10] + 1 = [-5,5] + 2 = [-2.5,2.5] + 3 = [0,10] + 4 = [0,5] + [5] - D/A 1 range (same choices) +*/ + +#include "../comedidev.h" +#include <linux/delay.h> +#include <linux/ioport.h> + +#define DT2801_TIMEOUT 1000 + +/* Hardware Configuration */ +/* ====================== */ + +#define DT2801_MAX_DMA_SIZE (64 * 1024) + +/* Ports */ +#define DT2801_IOSIZE 2 + +/* define's */ +/* ====================== */ + +/* Commands */ +#define DT_C_RESET 0x0 +#define DT_C_CLEAR_ERR 0x1 +#define DT_C_READ_ERRREG 0x2 +#define DT_C_SET_CLOCK 0x3 + +#define DT_C_TEST 0xb +#define DT_C_STOP 0xf + +#define DT_C_SET_DIGIN 0x4 +#define DT_C_SET_DIGOUT 0x5 +#define DT_C_READ_DIG 0x6 +#define DT_C_WRITE_DIG 0x7 + +#define DT_C_WRITE_DAIM 0x8 +#define DT_C_SET_DA 0x9 +#define DT_C_WRITE_DA 0xa + +#define DT_C_READ_ADIM 0xc +#define DT_C_SET_AD 0xd +#define DT_C_READ_AD 0xe + +/* Command modifiers (only used with read/write), EXTTRIG can be + used with some other commands. +*/ +#define DT_MOD_DMA (1<<4) +#define DT_MOD_CONT (1<<5) +#define DT_MOD_EXTCLK (1<<6) +#define DT_MOD_EXTTRIG (1<<7) + +/* Bits in status register */ +#define DT_S_DATA_OUT_READY (1<<0) +#define DT_S_DATA_IN_FULL (1<<1) +#define DT_S_READY (1<<2) +#define DT_S_COMMAND (1<<3) +#define DT_S_COMPOSITE_ERROR (1<<7) + +/* registers */ +#define DT2801_DATA 0 +#define DT2801_STATUS 1 +#define DT2801_CMD 1 + +#if 0 +/* ignore 'defined but not used' warning */ +static const struct comedi_lrange range_dt2801_ai_pgh_bipolar = { 4, { + RANGE(-10, + 10), + RANGE(-5, + 5), + RANGE + (-2.5, + 2.5), + RANGE + (-1.25, + 1.25), + } +}; +#endif +static const struct comedi_lrange range_dt2801_ai_pgl_bipolar = { 4, { + RANGE(-10, + 10), + RANGE(-1, + 1), + RANGE + (-0.1, + 0.1), + RANGE + (-0.02, + 0.02), + } +}; + +#if 0 +/* ignore 'defined but not used' warning */ +static const struct comedi_lrange range_dt2801_ai_pgh_unipolar = { 4, { + RANGE(0, + 10), + RANGE(0, + 5), + RANGE(0, + 2.5), + RANGE(0, + 1.25), + } +}; +#endif +static const struct comedi_lrange range_dt2801_ai_pgl_unipolar = { 4, { + RANGE(0, + 10), + RANGE(0, + 1), + RANGE(0, + 0.1), + RANGE(0, + 0.02), + } +}; + +struct dt2801_board { + + const char *name; + int boardcode; + int ad_diff; + int ad_chan; + int adbits; + int adrangetype; + int dabits; +}; + +/* Typeid's for the different boards of the DT2801-series + (taken from the test-software, that comes with the board) + */ +static const struct dt2801_board boardtypes[] = { + { + .name = "dt2801", + .boardcode = 0x09, + .ad_diff = 2, + .ad_chan = 16, + .adbits = 12, + .adrangetype = 0, + .dabits = 12}, + { + .name = "dt2801-a", + .boardcode = 0x52, + .ad_diff = 2, + .ad_chan = 16, + .adbits = 12, + .adrangetype = 0, + .dabits = 12}, + { + .name = "dt2801/5716a", + .boardcode = 0x82, + .ad_diff = 1, + .ad_chan = 16, + .adbits = 16, + .adrangetype = 1, + .dabits = 12}, + { + .name = "dt2805", + .boardcode = 0x12, + .ad_diff = 1, + .ad_chan = 16, + .adbits = 12, + .adrangetype = 0, + .dabits = 12}, + { + .name = "dt2805/5716a", + .boardcode = 0x92, + .ad_diff = 1, + .ad_chan = 16, + .adbits = 16, + .adrangetype = 1, + .dabits = 12}, + { + .name = "dt2808", + .boardcode = 0x20, + .ad_diff = 0, + .ad_chan = 16, + .adbits = 12, + .adrangetype = 2, + .dabits = 8}, + { + .name = "dt2818", + .boardcode = 0xa2, + .ad_diff = 0, + .ad_chan = 4, + .adbits = 12, + .adrangetype = 0, + .dabits = 12}, + { + .name = "dt2809", + .boardcode = 0xb0, + .ad_diff = 0, + .ad_chan = 8, + .adbits = 12, + .adrangetype = 1, + .dabits = 12}, +}; + +struct dt2801_private { + + const struct comedi_lrange *dac_range_types[2]; + unsigned int ao_readback[2]; +}; + +/* These are the low-level routines: + writecommand: write a command to the board + writedata: write data byte + readdata: read data byte + */ + +/* Only checks DataOutReady-flag, not the Ready-flag as it is done + in the examples of the manual. I don't see why this should be + necessary. */ +static int dt2801_readdata(struct comedi_device *dev, int *data) +{ + int stat = 0; + int timeout = DT2801_TIMEOUT; + + do { + stat = inb_p(dev->iobase + DT2801_STATUS); + if (stat & (DT_S_COMPOSITE_ERROR | DT_S_READY)) + return stat; + if (stat & DT_S_DATA_OUT_READY) { + *data = inb_p(dev->iobase + DT2801_DATA); + return 0; + } + } while (--timeout > 0); + + return -ETIME; +} + +static int dt2801_readdata2(struct comedi_device *dev, int *data) +{ + int lb, hb; + int ret; + + ret = dt2801_readdata(dev, &lb); + if (ret) + return ret; + ret = dt2801_readdata(dev, &hb); + if (ret) + return ret; + + *data = (hb << 8) + lb; + return 0; +} + +static int dt2801_writedata(struct comedi_device *dev, unsigned int data) +{ + int stat = 0; + int timeout = DT2801_TIMEOUT; + + do { + stat = inb_p(dev->iobase + DT2801_STATUS); + + if (stat & DT_S_COMPOSITE_ERROR) + return stat; + if (!(stat & DT_S_DATA_IN_FULL)) { + outb_p(data & 0xff, dev->iobase + DT2801_DATA); + return 0; + } +#if 0 + if (stat & DT_S_READY) { + printk + ("dt2801: ready flag set (bad!) in dt2801_writedata()\n"); + return -EIO; + } +#endif + } while (--timeout > 0); + + return -ETIME; +} + +static int dt2801_writedata2(struct comedi_device *dev, unsigned int data) +{ + int ret; + + ret = dt2801_writedata(dev, data & 0xff); + if (ret < 0) + return ret; + ret = dt2801_writedata(dev, (data >> 8)); + if (ret < 0) + return ret; + + return 0; +} + +static int dt2801_wait_for_ready(struct comedi_device *dev) +{ + int timeout = DT2801_TIMEOUT; + int stat; + + stat = inb_p(dev->iobase + DT2801_STATUS); + if (stat & DT_S_READY) + return 0; + do { + stat = inb_p(dev->iobase + DT2801_STATUS); + + if (stat & DT_S_COMPOSITE_ERROR) + return stat; + if (stat & DT_S_READY) + return 0; + } while (--timeout > 0); + + return -ETIME; +} + +static int dt2801_writecmd(struct comedi_device *dev, int command) +{ + int stat; + + dt2801_wait_for_ready(dev); + + stat = inb_p(dev->iobase + DT2801_STATUS); + if (stat & DT_S_COMPOSITE_ERROR) { + printk + ("dt2801: composite-error in dt2801_writecmd(), ignoring\n"); + } + if (!(stat & DT_S_READY)) + printk("dt2801: !ready in dt2801_writecmd(), ignoring\n"); + outb_p(command, dev->iobase + DT2801_CMD); + + return 0; +} + +static int dt2801_reset(struct comedi_device *dev) +{ + int board_code = 0; + unsigned int stat; + int timeout; + + DPRINTK("dt2801: resetting board...\n"); + DPRINTK("fingerprint: 0x%02x 0x%02x\n", inb_p(dev->iobase), + inb_p(dev->iobase + 1)); + + /* pull random data from data port */ + inb_p(dev->iobase + DT2801_DATA); + inb_p(dev->iobase + DT2801_DATA); + inb_p(dev->iobase + DT2801_DATA); + inb_p(dev->iobase + DT2801_DATA); + + DPRINTK("dt2801: stop\n"); + /* dt2801_writecmd(dev,DT_C_STOP); */ + outb_p(DT_C_STOP, dev->iobase + DT2801_CMD); + + /* dt2801_wait_for_ready(dev); */ + udelay(100); + timeout = 10000; + do { + stat = inb_p(dev->iobase + DT2801_STATUS); + if (stat & DT_S_READY) + break; + } while (timeout--); + if (!timeout) + printk("dt2801: timeout 1 status=0x%02x\n", stat); + + /* printk("dt2801: reading dummy\n"); */ + /* dt2801_readdata(dev,&board_code); */ + + DPRINTK("dt2801: reset\n"); + outb_p(DT_C_RESET, dev->iobase + DT2801_CMD); + /* dt2801_writecmd(dev,DT_C_RESET); */ + + udelay(100); + timeout = 10000; + do { + stat = inb_p(dev->iobase + DT2801_STATUS); + if (stat & DT_S_READY) + break; + } while (timeout--); + if (!timeout) + printk("dt2801: timeout 2 status=0x%02x\n", stat); + + DPRINTK("dt2801: reading code\n"); + dt2801_readdata(dev, &board_code); + + DPRINTK("dt2801: ok. code=0x%02x\n", board_code); + + return board_code; +} + +static int probe_number_of_ai_chans(struct comedi_device *dev) +{ + int n_chans; + int stat; + int data; + + for (n_chans = 0; n_chans < 16; n_chans++) { + stat = dt2801_writecmd(dev, DT_C_READ_ADIM); + dt2801_writedata(dev, 0); + dt2801_writedata(dev, n_chans); + stat = dt2801_readdata2(dev, &data); + + if (stat) + break; + } + + dt2801_reset(dev); + dt2801_reset(dev); + + return n_chans; +} + +static const struct comedi_lrange *dac_range_table[] = { + &range_bipolar10, + &range_bipolar5, + &range_bipolar2_5, + &range_unipolar10, + &range_unipolar5 +}; + +static const struct comedi_lrange *dac_range_lkup(int opt) +{ + if (opt < 0 || opt >= 5) + return &range_unknown; + return dac_range_table[opt]; +} + +static const struct comedi_lrange *ai_range_lkup(int type, int opt) +{ + switch (type) { + case 0: + return (opt) ? + &range_dt2801_ai_pgl_unipolar : + &range_dt2801_ai_pgl_bipolar; + case 1: + return (opt) ? &range_unipolar10 : &range_bipolar10; + case 2: + return &range_unipolar5; + } + return &range_unknown; +} + +static int dt2801_error(struct comedi_device *dev, int stat) +{ + if (stat < 0) { + if (stat == -ETIME) + printk("dt2801: timeout\n"); + else + printk("dt2801: error %d\n", stat); + return stat; + } + printk("dt2801: error status 0x%02x, resetting...\n", stat); + + dt2801_reset(dev); + dt2801_reset(dev); + + return -EIO; +} + +static int dt2801_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int d; + int stat; + int i; + + for (i = 0; i < insn->n; i++) { + stat = dt2801_writecmd(dev, DT_C_READ_ADIM); + dt2801_writedata(dev, CR_RANGE(insn->chanspec)); + dt2801_writedata(dev, CR_CHAN(insn->chanspec)); + stat = dt2801_readdata2(dev, &d); + + if (stat != 0) + return dt2801_error(dev, stat); + + data[i] = d; + } + + return i; +} + +static int dt2801_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct dt2801_private *devpriv = dev->private; + + data[0] = devpriv->ao_readback[CR_CHAN(insn->chanspec)]; + + return 1; +} + +static int dt2801_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct dt2801_private *devpriv = dev->private; + + dt2801_writecmd(dev, DT_C_WRITE_DAIM); + dt2801_writedata(dev, CR_CHAN(insn->chanspec)); + dt2801_writedata2(dev, data[0]); + + devpriv->ao_readback[CR_CHAN(insn->chanspec)] = data[0]; + + return 1; +} + +static int dt2801_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int which = 0; + + if (s == &dev->subdevices[3]) + which = 1; + + if (data[0]) { + s->state &= ~data[0]; + s->state |= (data[0] & data[1]); + dt2801_writecmd(dev, DT_C_WRITE_DIG); + dt2801_writedata(dev, which); + dt2801_writedata(dev, s->state); + } + dt2801_writecmd(dev, DT_C_READ_DIG); + dt2801_writedata(dev, which); + dt2801_readdata(dev, data + 1); + + return insn->n; +} + +static int dt2801_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int which = 0; + + if (s == &dev->subdevices[3]) + which = 1; + + /* configure */ + switch (data[0]) { + case INSN_CONFIG_DIO_OUTPUT: + s->io_bits = 0xff; + dt2801_writecmd(dev, DT_C_SET_DIGOUT); + break; + case INSN_CONFIG_DIO_INPUT: + s->io_bits = 0; + dt2801_writecmd(dev, DT_C_SET_DIGIN); + break; + case INSN_CONFIG_DIO_QUERY: + data[1] = s->io_bits ? COMEDI_OUTPUT : COMEDI_INPUT; + return insn->n; + default: + return -EINVAL; + } + dt2801_writedata(dev, which); + + return 1; +} + +/* + options: + [0] - i/o base + [1] - unused + [2] - a/d 0=differential, 1=single-ended + [3] - a/d range 0=[-10,10], 1=[0,10] + [4] - dac0 range 0=[-10,10], 1=[-5,5], 2=[-2.5,2.5] 3=[0,10], 4=[0,5] + [5] - dac1 range 0=[-10,10], 1=[-5,5], 2=[-2.5,2.5] 3=[0,10], 4=[0,5] +*/ +static int dt2801_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + const struct dt2801_board *board = comedi_board(dev); + struct dt2801_private *devpriv; + struct comedi_subdevice *s; + int board_code, type; + int ret = 0; + int n_ai_chans; + + ret = comedi_request_region(dev, it->options[0], DT2801_IOSIZE); + if (ret) + return ret; + + /* do some checking */ + + board_code = dt2801_reset(dev); + + /* heh. if it didn't work, try it again. */ + if (!board_code) + board_code = dt2801_reset(dev); + + for (type = 0; type < ARRAY_SIZE(boardtypes); type++) { + if (boardtypes[type].boardcode == board_code) + goto havetype; + } + printk("dt2801: unrecognized board code=0x%02x, contact author\n", + board_code); + type = 0; + +havetype: + dev->board_ptr = boardtypes + type; + board = comedi_board(dev); + + n_ai_chans = probe_number_of_ai_chans(dev); + + ret = comedi_alloc_subdevices(dev, 4); + if (ret) + goto out; + + devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL); + if (!devpriv) + return -ENOMEM; + dev->private = devpriv; + + dev->board_name = board->name; + + s = &dev->subdevices[0]; + /* ai subdevice */ + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND; +#if 1 + s->n_chan = n_ai_chans; +#else + if (it->options[2]) + s->n_chan = board->ad_chan; + else + s->n_chan = board->ad_chan / 2; +#endif + s->maxdata = (1 << board->adbits) - 1; + s->range_table = ai_range_lkup(board->adrangetype, it->options[3]); + s->insn_read = dt2801_ai_insn_read; + + s = &dev->subdevices[1]; + /* ao subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 2; + s->maxdata = (1 << board->dabits) - 1; + s->range_table_list = devpriv->dac_range_types; + devpriv->dac_range_types[0] = dac_range_lkup(it->options[4]); + devpriv->dac_range_types[1] = dac_range_lkup(it->options[5]); + s->insn_read = dt2801_ao_insn_read; + s->insn_write = dt2801_ao_insn_write; + + s = &dev->subdevices[2]; + /* 1st digital subdevice */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = dt2801_dio_insn_bits; + s->insn_config = dt2801_dio_insn_config; + + s = &dev->subdevices[3]; + /* 2nd digital subdevice */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = dt2801_dio_insn_bits; + s->insn_config = dt2801_dio_insn_config; + + ret = 0; +out: + return ret; +} + +static struct comedi_driver dt2801_driver = { + .driver_name = "dt2801", + .module = THIS_MODULE, + .attach = dt2801_attach, + .detach = comedi_legacy_detach, +}; +module_comedi_driver(dt2801_driver); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); |
