The comedi code came into the kernel back in 2008, but traces its lifetime to much much earlier. It's been polished and buffed and there's really nothing preventing it from being part of the "real" portion of the kernel. So move it to drivers/comedi/ as it belongs there. Many thanks to the hundreds of developers who did the work to make this happen. Cc: Ian Abbott <abbotti@mev.co.uk> Cc: H Hartley Sweeten <hsweeten@visionengravers.com> Link: https://lore.kernel.org/r/YHauop4u3sP6lz8j@kroah.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
645 lines
13 KiB
C
645 lines
13 KiB
C
// 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 "../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");
|