| /* |
| * 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 |
| |
| static int dt2801_attach(struct comedi_device *dev, |
| struct comedi_devconfig *it); |
| static int dt2801_detach(struct comedi_device *dev); |
| static struct comedi_driver driver_dt2801 = { |
| .driver_name = "dt2801", |
| .module = THIS_MODULE, |
| .attach = dt2801_attach, |
| .detach = dt2801_detach, |
| }; |
| |
| COMEDI_INITCLEANUP(driver_dt2801); |
| |
| #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}, |
| }; |
| |
| #define boardtype (*(const struct dt2801_board *)dev->board_ptr) |
| |
| struct dt2801_private { |
| |
| const struct comedi_lrange *dac_range_types[2]; |
| unsigned int ao_readback[2]; |
| }; |
| |
| #define devpriv ((struct dt2801_private *)dev->private) |
| |
| static int dt2801_ai_insn_read(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, unsigned int *data); |
| static int dt2801_ao_insn_read(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, unsigned int *data); |
| static int dt2801_ao_insn_write(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, unsigned int *data); |
| static int dt2801_dio_insn_bits(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, unsigned int *data); |
| static int dt2801_dio_insn_config(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, unsigned int *data); |
| |
| /* 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; |
| } |
| |
| /* |
| 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) |
| { |
| struct comedi_subdevice *s; |
| unsigned long iobase; |
| int board_code, type; |
| int ret = 0; |
| int n_ai_chans; |
| |
| iobase = it->options[0]; |
| if (!request_region(iobase, DT2801_IOSIZE, "dt2801")) { |
| comedi_error(dev, "I/O port conflict"); |
| return -EIO; |
| } |
| dev->iobase = iobase; |
| |
| /* 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; |
| printk("dt2801: %s at port 0x%lx", boardtype.name, iobase); |
| |
| n_ai_chans = probe_number_of_ai_chans(dev); |
| printk(" (ai channels = %d)", n_ai_chans); |
| |
| ret = alloc_subdevices(dev, 4); |
| if (ret < 0) |
| goto out; |
| |
| ret = alloc_private(dev, sizeof(struct dt2801_private)); |
| if (ret < 0) |
| goto out; |
| |
| dev->board_name = boardtype.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 = boardtype.ad_chan; |
| else |
| s->n_chan = boardtype.ad_chan / 2; |
| #endif |
| s->maxdata = (1 << boardtype.adbits) - 1; |
| s->range_table = ai_range_lkup(boardtype.adrangetype, it->options[3]); |
| s->insn_read = dt2801_ai_insn_read; |
| |
| s++; |
| /* ao subdevice */ |
| s->type = COMEDI_SUBD_AO; |
| s->subdev_flags = SDF_WRITABLE; |
| s->n_chan = 2; |
| s->maxdata = (1 << boardtype.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++; |
| /* 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++; |
| /* 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: |
| printk("\n"); |
| |
| return ret; |
| } |
| |
| static int dt2801_detach(struct comedi_device *dev) |
| { |
| if (dev->iobase) |
| release_region(dev->iobase, DT2801_IOSIZE); |
| |
| return 0; |
| } |
| |
| 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) |
| { |
| 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) |
| { |
| 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 + 4) |
| which = 1; |
| |
| if (insn->n != 2) |
| return -EINVAL; |
| 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 2; |
| } |
| |
| 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 + 4) |
| which = 1; |
| |
| /* configure */ |
| if (data[0]) { |
| s->io_bits = 0xff; |
| dt2801_writecmd(dev, DT_C_SET_DIGOUT); |
| } else { |
| s->io_bits = 0; |
| dt2801_writecmd(dev, DT_C_SET_DIGIN); |
| } |
| dt2801_writedata(dev, which); |
| |
| return 1; |
| } |