diff options
Diffstat (limited to 'drivers/comedi/drivers/dt2801.c')
-rw-r--r-- | drivers/comedi/drivers/dt2801.c | 645 |
1 files changed, 645 insertions, 0 deletions
diff --git a/drivers/comedi/drivers/dt2801.c b/drivers/comedi/drivers/dt2801.c new file mode 100644 index 000000000..230d25010 --- /dev/null +++ b/drivers/comedi/drivers/dt2801.c @@ -0,0 +1,645 @@ +// 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 <linux/module.h> +#include <linux/comedi/comedidev.h> +#include <linux/delay.h> + +#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 https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); |