// SPDX-License-Identifier: GPL-2.0 /* * 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 #include "../comedidev.h" #include #define DT2801_TIMEOUT 1000 /* Hardware Configuration */ /* ====================== */ #define DT2801_MAX_DMA_SIZE (64 * 1024) /* 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 BIT(4) #define DT_MOD_CONT BIT(5) #define DT_MOD_EXTCLK BIT(6) #define DT_MOD_EXTTRIG BIT(7) /* Bits in status register */ #define DT_S_DATA_OUT_READY BIT(0) #define DT_S_DATA_IN_FULL BIT(1) #define DT_S_READY BIT(2) #define DT_S_COMMAND BIT(3) #define DT_S_COMPOSITE_ERROR BIT(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, { BIP_RANGE(10), BIP_RANGE(5), BIP_RANGE(2.5), BIP_RANGE(1.25) } }; #endif static const struct comedi_lrange range_dt2801_ai_pgl_bipolar = { 4, { BIP_RANGE(10), BIP_RANGE(1), BIP_RANGE(0.1), BIP_RANGE(0.02) } }; #if 0 /* ignore 'defined but not used' warning */ static const struct comedi_lrange range_dt2801_ai_pgh_unipolar = { 4, { UNI_RANGE(10), UNI_RANGE(5), UNI_RANGE(2.5), UNI_RANGE(1.25) } }; #endif static const struct comedi_lrange range_dt2801_ai_pgl_unipolar = { 4, { UNI_RANGE(10), UNI_RANGE(1), UNI_RANGE(0.1), UNI_RANGE(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]; }; /* * 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 = 0; int hb = 0; 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; } } 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 void 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) { dev_dbg(dev->class_dev, "composite-error in %s, ignoring\n", __func__); } if (!(stat & DT_S_READY)) dev_dbg(dev->class_dev, "!ready in %s, ignoring\n", __func__); outb_p(command, dev->iobase + DT2801_CMD); } static int dt2801_reset(struct comedi_device *dev) { int board_code = 0; unsigned int stat; int timeout; /* 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); /* dt2801_writecmd(dev,DT_C_STOP); */ outb_p(DT_C_STOP, dev->iobase + DT2801_CMD); /* dt2801_wait_for_ready(dev); */ usleep_range(100, 200); timeout = 10000; do { stat = inb_p(dev->iobase + DT2801_STATUS); if (stat & DT_S_READY) break; } while (timeout--); if (!timeout) dev_dbg(dev->class_dev, "timeout 1 status=0x%02x\n", stat); /* dt2801_readdata(dev,&board_code); */ outb_p(DT_C_RESET, dev->iobase + DT2801_CMD); /* dt2801_writecmd(dev,DT_C_RESET); */ usleep_range(100, 200); timeout = 10000; do { stat = inb_p(dev->iobase + DT2801_STATUS); if (stat & DT_S_READY) break; } while (timeout--); if (!timeout) dev_dbg(dev->class_dev, "timeout 2 status=0x%02x\n", stat); dt2801_readdata(dev, &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++) { 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) dev_dbg(dev->class_dev, "timeout\n"); else dev_dbg(dev->class_dev, "error %d\n", stat); return stat; } dev_dbg(dev->class_dev, "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++) { 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_write(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { unsigned int chan = CR_CHAN(insn->chanspec); dt2801_writecmd(dev, DT_C_WRITE_DAIM); dt2801_writedata(dev, chan); dt2801_writedata2(dev, data[0]); s->readback[chan] = 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 = (s == &dev->subdevices[3]) ? 1 : 0; unsigned int val = 0; if (comedi_dio_update_state(s, data)) { 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, &val); data[1] = val; 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 ret; ret = comedi_dio_insn_config(dev, s, insn, data, 0xff); if (ret) return ret; dt2801_writecmd(dev, s->io_bits ? DT_C_SET_DIGOUT : DT_C_SET_DIGIN); dt2801_writedata(dev, (s == &dev->subdevices[3]) ? 1 : 0); return insn->n; } /* * 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; 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], 0x2); 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; } dev_dbg(dev->class_dev, "unrecognized board code=0x%02x, contact author\n", board_code); type = 0; havetype: dev->board_ptr = boardtypes + type; board = dev->board_ptr; n_ai_chans = probe_number_of_ai_chans(dev); ret = comedi_alloc_subdevices(dev, 4); if (ret) goto out; devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); if (!devpriv) return -ENOMEM; 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_write = dt2801_ao_insn_write; ret = comedi_alloc_subdev_readback(s); if (ret) return ret; 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");