// SPDX-License-Identifier: GPL-2.0+
|
/*
|
* s526.c
|
* Sensoray s526 Comedi driver
|
*
|
* COMEDI - Linux Control and Measurement Device Interface
|
* Copyright (C) 2000 David A. Schleef <ds@schleef.org>
|
*/
|
|
/*
|
* Driver: s526
|
* Description: Sensoray 526 driver
|
* Devices: [Sensoray] 526 (s526)
|
* Author: Richie
|
* Everett Wang <everett.wang@everteq.com>
|
* Updated: Thu, 14 Sep. 2006
|
* Status: experimental
|
*
|
* Encoder works
|
* Analog input works
|
* Analog output works
|
* PWM output works
|
* Commands are not supported yet.
|
*
|
* Configuration Options:
|
* [0] - I/O port base address
|
*/
|
|
#include <linux/module.h>
|
#include "../comedidev.h"
|
|
/*
|
* Register I/O map
|
*/
|
#define S526_TIMER_REG 0x00
|
#define S526_TIMER_LOAD(x) (((x) & 0xff) << 8)
|
#define S526_TIMER_MODE ((x) << 1)
|
#define S526_TIMER_MANUAL S526_TIMER_MODE(0)
|
#define S526_TIMER_AUTO S526_TIMER_MODE(1)
|
#define S526_TIMER_RESTART BIT(0)
|
#define S526_WDOG_REG 0x02
|
#define S526_WDOG_INVERTED BIT(4)
|
#define S526_WDOG_ENA BIT(3)
|
#define S526_WDOG_INTERVAL(x) (((x) & 0x7) << 0)
|
#define S526_AO_CTRL_REG 0x04
|
#define S526_AO_CTRL_RESET BIT(3)
|
#define S526_AO_CTRL_CHAN(x) (((x) & 0x3) << 1)
|
#define S526_AO_CTRL_START BIT(0)
|
#define S526_AI_CTRL_REG 0x06
|
#define S526_AI_CTRL_DELAY BIT(15)
|
#define S526_AI_CTRL_CONV(x) (1 << (5 + ((x) & 0x9)))
|
#define S526_AI_CTRL_READ(x) (((x) & 0xf) << 1)
|
#define S526_AI_CTRL_START BIT(0)
|
#define S526_AO_REG 0x08
|
#define S526_AI_REG 0x08
|
#define S526_DIO_CTRL_REG 0x0a
|
#define S526_DIO_CTRL_DIO3_NEG BIT(15) /* irq on DIO3 neg/pos edge */
|
#define S526_DIO_CTRL_DIO2_NEG BIT(14) /* irq on DIO2 neg/pos edge */
|
#define S526_DIO_CTRL_DIO1_NEG BIT(13) /* irq on DIO1 neg/pos edge */
|
#define S526_DIO_CTRL_DIO0_NEG BIT(12) /* irq on DIO0 neg/pos edge */
|
#define S526_DIO_CTRL_GRP2_OUT BIT(11)
|
#define S526_DIO_CTRL_GRP1_OUT BIT(10)
|
#define S526_DIO_CTRL_GRP2_NEG BIT(8) /* irq on DIO[4-7] neg/pos edge */
|
#define S526_INT_ENA_REG 0x0c
|
#define S526_INT_STATUS_REG 0x0e
|
#define S526_INT_DIO(x) BIT(8 + ((x) & 0x7))
|
#define S526_INT_EEPROM BIT(7) /* status only */
|
#define S526_INT_CNTR(x) BIT(3 + (3 - ((x) & 0x3)))
|
#define S526_INT_AI BIT(2)
|
#define S526_INT_AO BIT(1)
|
#define S526_INT_TIMER BIT(0)
|
#define S526_MISC_REG 0x10
|
#define S526_MISC_LED_OFF BIT(0)
|
#define S526_GPCT_LSB_REG(x) (0x12 + ((x) * 8))
|
#define S526_GPCT_MSB_REG(x) (0x14 + ((x) * 8))
|
#define S526_GPCT_MODE_REG(x) (0x16 + ((x) * 8))
|
#define S526_GPCT_MODE_COUT_SRC(x) ((x) << 0)
|
#define S526_GPCT_MODE_COUT_SRC_MASK S526_GPCT_MODE_COUT_SRC(0x1)
|
#define S526_GPCT_MODE_COUT_SRC_RCAP S526_GPCT_MODE_COUT_SRC(0)
|
#define S526_GPCT_MODE_COUT_SRC_RTGL S526_GPCT_MODE_COUT_SRC(1)
|
#define S526_GPCT_MODE_COUT_POL(x) ((x) << 1)
|
#define S526_GPCT_MODE_COUT_POL_MASK S526_GPCT_MODE_COUT_POL(0x1)
|
#define S526_GPCT_MODE_COUT_POL_NORM S526_GPCT_MODE_COUT_POL(0)
|
#define S526_GPCT_MODE_COUT_POL_INV S526_GPCT_MODE_COUT_POL(1)
|
#define S526_GPCT_MODE_AUTOLOAD(x) ((x) << 2)
|
#define S526_GPCT_MODE_AUTOLOAD_MASK S526_GPCT_MODE_AUTOLOAD(0x7)
|
#define S526_GPCT_MODE_AUTOLOAD_NONE S526_GPCT_MODE_AUTOLOAD(0)
|
/* these 3 bits can be OR'ed */
|
#define S526_GPCT_MODE_AUTOLOAD_RO S526_GPCT_MODE_AUTOLOAD(0x1)
|
#define S526_GPCT_MODE_AUTOLOAD_IXFALL S526_GPCT_MODE_AUTOLOAD(0x2)
|
#define S526_GPCT_MODE_AUTOLOAD_IXRISE S526_GPCT_MODE_AUTOLOAD(0x4)
|
#define S526_GPCT_MODE_HWCTEN_SRC(x) ((x) << 5)
|
#define S526_GPCT_MODE_HWCTEN_SRC_MASK S526_GPCT_MODE_HWCTEN_SRC(0x3)
|
#define S526_GPCT_MODE_HWCTEN_SRC_CEN S526_GPCT_MODE_HWCTEN_SRC(0)
|
#define S526_GPCT_MODE_HWCTEN_SRC_IX S526_GPCT_MODE_HWCTEN_SRC(1)
|
#define S526_GPCT_MODE_HWCTEN_SRC_IXRF S526_GPCT_MODE_HWCTEN_SRC(2)
|
#define S526_GPCT_MODE_HWCTEN_SRC_NRCAP S526_GPCT_MODE_HWCTEN_SRC(3)
|
#define S526_GPCT_MODE_CTEN_CTRL(x) ((x) << 7)
|
#define S526_GPCT_MODE_CTEN_CTRL_MASK S526_GPCT_MODE_CTEN_CTRL(0x3)
|
#define S526_GPCT_MODE_CTEN_CTRL_DIS S526_GPCT_MODE_CTEN_CTRL(0)
|
#define S526_GPCT_MODE_CTEN_CTRL_ENA S526_GPCT_MODE_CTEN_CTRL(1)
|
#define S526_GPCT_MODE_CTEN_CTRL_HW S526_GPCT_MODE_CTEN_CTRL(2)
|
#define S526_GPCT_MODE_CTEN_CTRL_INVHW S526_GPCT_MODE_CTEN_CTRL(3)
|
#define S526_GPCT_MODE_CLK_SRC(x) ((x) << 9)
|
#define S526_GPCT_MODE_CLK_SRC_MASK S526_GPCT_MODE_CLK_SRC(0x3)
|
/* if count direction control set to quadrature */
|
#define S526_GPCT_MODE_CLK_SRC_QUADX1 S526_GPCT_MODE_CLK_SRC(0)
|
#define S526_GPCT_MODE_CLK_SRC_QUADX2 S526_GPCT_MODE_CLK_SRC(1)
|
#define S526_GPCT_MODE_CLK_SRC_QUADX4 S526_GPCT_MODE_CLK_SRC(2)
|
#define S526_GPCT_MODE_CLK_SRC_QUADX4_ S526_GPCT_MODE_CLK_SRC(3)
|
/* if count direction control set to software control */
|
#define S526_GPCT_MODE_CLK_SRC_ARISE S526_GPCT_MODE_CLK_SRC(0)
|
#define S526_GPCT_MODE_CLK_SRC_AFALL S526_GPCT_MODE_CLK_SRC(1)
|
#define S526_GPCT_MODE_CLK_SRC_INT S526_GPCT_MODE_CLK_SRC(2)
|
#define S526_GPCT_MODE_CLK_SRC_INTHALF S526_GPCT_MODE_CLK_SRC(3)
|
#define S526_GPCT_MODE_CT_DIR(x) ((x) << 11)
|
#define S526_GPCT_MODE_CT_DIR_MASK S526_GPCT_MODE_CT_DIR(0x1)
|
/* if count direction control set to software control */
|
#define S526_GPCT_MODE_CT_DIR_UP S526_GPCT_MODE_CT_DIR(0)
|
#define S526_GPCT_MODE_CT_DIR_DOWN S526_GPCT_MODE_CT_DIR(1)
|
#define S526_GPCT_MODE_CTDIR_CTRL(x) ((x) << 12)
|
#define S526_GPCT_MODE_CTDIR_CTRL_MASK S526_GPCT_MODE_CTDIR_CTRL(0x1)
|
#define S526_GPCT_MODE_CTDIR_CTRL_QUAD S526_GPCT_MODE_CTDIR_CTRL(0)
|
#define S526_GPCT_MODE_CTDIR_CTRL_SOFT S526_GPCT_MODE_CTDIR_CTRL(1)
|
#define S526_GPCT_MODE_LATCH_CTRL(x) ((x) << 13)
|
#define S526_GPCT_MODE_LATCH_CTRL_MASK S526_GPCT_MODE_LATCH_CTRL(0x1)
|
#define S526_GPCT_MODE_LATCH_CTRL_READ S526_GPCT_MODE_LATCH_CTRL(0)
|
#define S526_GPCT_MODE_LATCH_CTRL_EVENT S526_GPCT_MODE_LATCH_CTRL(1)
|
#define S526_GPCT_MODE_PR_SELECT(x) ((x) << 14)
|
#define S526_GPCT_MODE_PR_SELECT_MASK S526_GPCT_MODE_PR_SELECT(0x1)
|
#define S526_GPCT_MODE_PR_SELECT_PR0 S526_GPCT_MODE_PR_SELECT(0)
|
#define S526_GPCT_MODE_PR_SELECT_PR1 S526_GPCT_MODE_PR_SELECT(1)
|
/* Control/Status - R = readable, W = writeable, C = write 1 to clear */
|
#define S526_GPCT_CTRL_REG(x) (0x18 + ((x) * 8))
|
#define S526_GPCT_CTRL_EV_STATUS(x) ((x) << 0) /* RC */
|
#define S526_GPCT_CTRL_EV_STATUS_MASK S526_GPCT_EV_STATUS(0xf)
|
#define S526_GPCT_CTRL_EV_STATUS_NONE S526_GPCT_EV_STATUS(0)
|
/* these 4 bits can be OR'ed */
|
#define S526_GPCT_CTRL_EV_STATUS_ECAP S526_GPCT_EV_STATUS(0x1)
|
#define S526_GPCT_CTRL_EV_STATUS_ICAPN S526_GPCT_EV_STATUS(0x2)
|
#define S526_GPCT_CTRL_EV_STATUS_ICAPP S526_GPCT_EV_STATUS(0x4)
|
#define S526_GPCT_CTRL_EV_STATUS_RCAP S526_GPCT_EV_STATUS(0x8)
|
#define S526_GPCT_CTRL_COUT_STATUS BIT(4) /* R */
|
#define S526_GPCT_CTRL_INDEX_STATUS BIT(5) /* R */
|
#define S525_GPCT_CTRL_INTEN(x) ((x) << 6) /* W */
|
#define S525_GPCT_CTRL_INTEN_MASK S526_GPCT_CTRL_INTEN(0xf)
|
#define S525_GPCT_CTRL_INTEN_NONE S526_GPCT_CTRL_INTEN(0)
|
/* these 4 bits can be OR'ed */
|
#define S525_GPCT_CTRL_INTEN_ERROR S526_GPCT_CTRL_INTEN(0x1)
|
#define S525_GPCT_CTRL_INTEN_IXFALL S526_GPCT_CTRL_INTEN(0x2)
|
#define S525_GPCT_CTRL_INTEN_IXRISE S526_GPCT_CTRL_INTEN(0x4)
|
#define S525_GPCT_CTRL_INTEN_RO S526_GPCT_CTRL_INTEN(0x8)
|
#define S525_GPCT_CTRL_LATCH_SEL(x) ((x) << 10) /* W */
|
#define S525_GPCT_CTRL_LATCH_SEL_MASK S526_GPCT_CTRL_LATCH_SEL(0x7)
|
#define S525_GPCT_CTRL_LATCH_SEL_NONE S526_GPCT_CTRL_LATCH_SEL(0)
|
/* these 3 bits can be OR'ed */
|
#define S525_GPCT_CTRL_LATCH_SEL_IXFALL S526_GPCT_CTRL_LATCH_SEL(0x1)
|
#define S525_GPCT_CTRL_LATCH_SEL_IXRISE S526_GPCT_CTRL_LATCH_SEL(0x2)
|
#define S525_GPCT_CTRL_LATCH_SEL_ITIMER S526_GPCT_CTRL_LATCH_SEL(0x4)
|
#define S525_GPCT_CTRL_CT_ARM BIT(13) /* W */
|
#define S525_GPCT_CTRL_CT_LOAD BIT(14) /* W */
|
#define S526_GPCT_CTRL_CT_RESET BIT(15) /* W */
|
#define S526_EEPROM_DATA_REG 0x32
|
#define S526_EEPROM_CTRL_REG 0x34
|
#define S526_EEPROM_CTRL_ADDR(x) (((x) & 0x3f) << 3)
|
#define S526_EEPROM_CTRL(x) (((x) & 0x3) << 1)
|
#define S526_EEPROM_CTRL_READ S526_EEPROM_CTRL(2)
|
#define S526_EEPROM_CTRL_START BIT(0)
|
|
struct s526_private {
|
unsigned int gpct_config[4];
|
unsigned short ai_ctrl;
|
};
|
|
static void s526_gpct_write(struct comedi_device *dev,
|
unsigned int chan, unsigned int val)
|
{
|
/* write high word then low word */
|
outw((val >> 16) & 0xffff, dev->iobase + S526_GPCT_MSB_REG(chan));
|
outw(val & 0xffff, dev->iobase + S526_GPCT_LSB_REG(chan));
|
}
|
|
static unsigned int s526_gpct_read(struct comedi_device *dev,
|
unsigned int chan)
|
{
|
unsigned int val;
|
|
/* read the low word then high word */
|
val = inw(dev->iobase + S526_GPCT_LSB_REG(chan)) & 0xffff;
|
val |= (inw(dev->iobase + S526_GPCT_MSB_REG(chan)) & 0xff) << 16;
|
|
return val;
|
}
|
|
static int s526_gpct_rinsn(struct comedi_device *dev,
|
struct comedi_subdevice *s,
|
struct comedi_insn *insn,
|
unsigned int *data)
|
{
|
unsigned int chan = CR_CHAN(insn->chanspec);
|
int i;
|
|
for (i = 0; i < insn->n; i++)
|
data[i] = s526_gpct_read(dev, chan);
|
|
return insn->n;
|
}
|
|
static int s526_gpct_insn_config(struct comedi_device *dev,
|
struct comedi_subdevice *s,
|
struct comedi_insn *insn,
|
unsigned int *data)
|
{
|
struct s526_private *devpriv = dev->private;
|
unsigned int chan = CR_CHAN(insn->chanspec);
|
unsigned int val;
|
|
/*
|
* Check what type of Counter the user requested
|
* data[0] contains the Application type
|
*/
|
switch (data[0]) {
|
case INSN_CONFIG_GPCT_QUADRATURE_ENCODER:
|
/*
|
* data[0]: Application Type
|
* data[1]: Counter Mode Register Value
|
* data[2]: Pre-load Register Value
|
* data[3]: Conter Control Register
|
*/
|
devpriv->gpct_config[chan] = data[0];
|
|
#if 1
|
/* Set Counter Mode Register */
|
val = data[1] & 0xffff;
|
outw(val, dev->iobase + S526_GPCT_MODE_REG(chan));
|
|
/* Reset the counter if it is software preload */
|
if ((val & S526_GPCT_MODE_AUTOLOAD_MASK) ==
|
S526_GPCT_MODE_AUTOLOAD_NONE) {
|
/* Reset the counter */
|
outw(S526_GPCT_CTRL_CT_RESET,
|
dev->iobase + S526_GPCT_CTRL_REG(chan));
|
/*
|
* Load the counter from PR0
|
* outw(S526_GPCT_CTRL_CT_LOAD,
|
* dev->iobase + S526_GPCT_CTRL_REG(chan));
|
*/
|
}
|
#else
|
val = S526_GPCT_MODE_CTDIR_CTRL_QUAD;
|
|
/* data[1] contains GPCT_X1, GPCT_X2 or GPCT_X4 */
|
if (data[1] == GPCT_X2)
|
val |= S526_GPCT_MODE_CLK_SRC_QUADX2;
|
else if (data[1] == GPCT_X4)
|
val |= S526_GPCT_MODE_CLK_SRC_QUADX4;
|
else
|
val |= S526_GPCT_MODE_CLK_SRC_QUADX1;
|
|
/* When to take into account the indexpulse: */
|
/*
|
* if (data[2] == GPCT_IndexPhaseLowLow) {
|
* } else if (data[2] == GPCT_IndexPhaseLowHigh) {
|
* } else if (data[2] == GPCT_IndexPhaseHighLow) {
|
* } else if (data[2] == GPCT_IndexPhaseHighHigh) {
|
* }
|
*/
|
/* Take into account the index pulse? */
|
if (data[3] == GPCT_RESET_COUNTER_ON_INDEX) {
|
/* Auto load with INDEX^ */
|
val |= S526_GPCT_MODE_AUTOLOAD_IXRISE;
|
}
|
|
/* Set Counter Mode Register */
|
val = data[1] & 0xffff;
|
outw(val, dev->iobase + S526_GPCT_MODE_REG(chan));
|
|
/* Load the pre-load register */
|
s526_gpct_write(dev, chan, data[2]);
|
|
/* Write the Counter Control Register */
|
if (data[3])
|
outw(data[3] & 0xffff,
|
dev->iobase + S526_GPCT_CTRL_REG(chan));
|
|
/* Reset the counter if it is software preload */
|
if ((val & S526_GPCT_MODE_AUTOLOAD_MASK) ==
|
S526_GPCT_MODE_AUTOLOAD_NONE) {
|
/* Reset the counter */
|
outw(S526_GPCT_CTRL_CT_RESET,
|
dev->iobase + S526_GPCT_CTRL_REG(chan));
|
/* Load the counter from PR0 */
|
outw(S526_GPCT_CTRL_CT_LOAD,
|
dev->iobase + S526_GPCT_CTRL_REG(chan));
|
}
|
#endif
|
break;
|
|
case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR:
|
/*
|
* data[0]: Application Type
|
* data[1]: Counter Mode Register Value
|
* data[2]: Pre-load Register 0 Value
|
* data[3]: Pre-load Register 1 Value
|
* data[4]: Conter Control Register
|
*/
|
devpriv->gpct_config[chan] = data[0];
|
|
/* Set Counter Mode Register */
|
val = data[1] & 0xffff;
|
/* Select PR0 */
|
val &= ~S526_GPCT_MODE_PR_SELECT_MASK;
|
val |= S526_GPCT_MODE_PR_SELECT_PR0;
|
outw(val, dev->iobase + S526_GPCT_MODE_REG(chan));
|
|
/* Load the pre-load register 0 */
|
s526_gpct_write(dev, chan, data[2]);
|
|
/* Set Counter Mode Register */
|
val = data[1] & 0xffff;
|
/* Select PR1 */
|
val &= ~S526_GPCT_MODE_PR_SELECT_MASK;
|
val |= S526_GPCT_MODE_PR_SELECT_PR1;
|
outw(val, dev->iobase + S526_GPCT_MODE_REG(chan));
|
|
/* Load the pre-load register 1 */
|
s526_gpct_write(dev, chan, data[3]);
|
|
/* Write the Counter Control Register */
|
if (data[4]) {
|
val = data[4] & 0xffff;
|
outw(val, dev->iobase + S526_GPCT_CTRL_REG(chan));
|
}
|
break;
|
|
case INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR:
|
/*
|
* data[0]: Application Type
|
* data[1]: Counter Mode Register Value
|
* data[2]: Pre-load Register 0 Value
|
* data[3]: Pre-load Register 1 Value
|
* data[4]: Conter Control Register
|
*/
|
devpriv->gpct_config[chan] = data[0];
|
|
/* Set Counter Mode Register */
|
val = data[1] & 0xffff;
|
/* Select PR0 */
|
val &= ~S526_GPCT_MODE_PR_SELECT_MASK;
|
val |= S526_GPCT_MODE_PR_SELECT_PR0;
|
outw(val, dev->iobase + S526_GPCT_MODE_REG(chan));
|
|
/* Load the pre-load register 0 */
|
s526_gpct_write(dev, chan, data[2]);
|
|
/* Set Counter Mode Register */
|
val = data[1] & 0xffff;
|
/* Select PR1 */
|
val &= ~S526_GPCT_MODE_PR_SELECT_MASK;
|
val |= S526_GPCT_MODE_PR_SELECT_PR1;
|
outw(val, dev->iobase + S526_GPCT_MODE_REG(chan));
|
|
/* Load the pre-load register 1 */
|
s526_gpct_write(dev, chan, data[3]);
|
|
/* Write the Counter Control Register */
|
if (data[4]) {
|
val = data[4] & 0xffff;
|
outw(val, dev->iobase + S526_GPCT_CTRL_REG(chan));
|
}
|
break;
|
|
default:
|
return -EINVAL;
|
}
|
|
return insn->n;
|
}
|
|
static int s526_gpct_winsn(struct comedi_device *dev,
|
struct comedi_subdevice *s,
|
struct comedi_insn *insn,
|
unsigned int *data)
|
{
|
struct s526_private *devpriv = dev->private;
|
unsigned int chan = CR_CHAN(insn->chanspec);
|
|
inw(dev->iobase + S526_GPCT_MODE_REG(chan)); /* Is this required? */
|
|
/* Check what Application of Counter this channel is configured for */
|
switch (devpriv->gpct_config[chan]) {
|
case INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR:
|
/*
|
* data[0] contains the PULSE_WIDTH
|
* data[1] contains the PULSE_PERIOD
|
* @pre PULSE_PERIOD > PULSE_WIDTH > 0
|
* The above periods must be expressed as a multiple of the
|
* pulse frequency on the selected source
|
*/
|
if ((data[1] <= data[0]) || !data[0])
|
return -EINVAL;
|
/* to write the PULSE_WIDTH */
|
/* fall through */
|
case INSN_CONFIG_GPCT_QUADRATURE_ENCODER:
|
case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR:
|
s526_gpct_write(dev, chan, data[0]);
|
break;
|
|
default:
|
return -EINVAL;
|
}
|
|
return insn->n;
|
}
|
|
static int s526_eoc(struct comedi_device *dev,
|
struct comedi_subdevice *s,
|
struct comedi_insn *insn,
|
unsigned long context)
|
{
|
unsigned int status;
|
|
status = inw(dev->iobase + S526_INT_STATUS_REG);
|
if (status & context) {
|
/* we got our eoc event, clear it */
|
outw(context, dev->iobase + S526_INT_STATUS_REG);
|
return 0;
|
}
|
return -EBUSY;
|
}
|
|
static int s526_ai_insn_read(struct comedi_device *dev,
|
struct comedi_subdevice *s,
|
struct comedi_insn *insn,
|
unsigned int *data)
|
{
|
struct s526_private *devpriv = dev->private;
|
unsigned int chan = CR_CHAN(insn->chanspec);
|
unsigned int ctrl;
|
unsigned int val;
|
int ret;
|
int i;
|
|
ctrl = S526_AI_CTRL_CONV(chan) | S526_AI_CTRL_READ(chan) |
|
S526_AI_CTRL_START;
|
if (ctrl != devpriv->ai_ctrl) {
|
/*
|
* The multiplexor needs to change, enable the 15us
|
* delay for the first sample.
|
*/
|
devpriv->ai_ctrl = ctrl;
|
ctrl |= S526_AI_CTRL_DELAY;
|
}
|
|
for (i = 0; i < insn->n; i++) {
|
/* trigger conversion */
|
outw(ctrl, dev->iobase + S526_AI_CTRL_REG);
|
ctrl &= ~S526_AI_CTRL_DELAY;
|
|
/* wait for conversion to end */
|
ret = comedi_timeout(dev, s, insn, s526_eoc, S526_INT_AI);
|
if (ret)
|
return ret;
|
|
val = inw(dev->iobase + S526_AI_REG);
|
data[i] = comedi_offset_munge(s, val);
|
}
|
|
return insn->n;
|
}
|
|
static int s526_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);
|
unsigned int ctrl = S526_AO_CTRL_CHAN(chan);
|
unsigned int val = s->readback[chan];
|
int ret;
|
int i;
|
|
outw(ctrl, dev->iobase + S526_AO_CTRL_REG);
|
ctrl |= S526_AO_CTRL_START;
|
|
for (i = 0; i < insn->n; i++) {
|
val = data[i];
|
outw(val, dev->iobase + S526_AO_REG);
|
outw(ctrl, dev->iobase + S526_AO_CTRL_REG);
|
|
/* wait for conversion to end */
|
ret = comedi_timeout(dev, s, insn, s526_eoc, S526_INT_AO);
|
if (ret)
|
return ret;
|
}
|
s->readback[chan] = val;
|
|
return insn->n;
|
}
|
|
static int s526_dio_insn_bits(struct comedi_device *dev,
|
struct comedi_subdevice *s,
|
struct comedi_insn *insn,
|
unsigned int *data)
|
{
|
if (comedi_dio_update_state(s, data))
|
outw(s->state, dev->iobase + S526_DIO_CTRL_REG);
|
|
data[1] = inw(dev->iobase + S526_DIO_CTRL_REG) & 0xff;
|
|
return insn->n;
|
}
|
|
static int s526_dio_insn_config(struct comedi_device *dev,
|
struct comedi_subdevice *s,
|
struct comedi_insn *insn,
|
unsigned int *data)
|
{
|
unsigned int chan = CR_CHAN(insn->chanspec);
|
unsigned int mask;
|
int ret;
|
|
/*
|
* Digital I/O can be configured as inputs or outputs in
|
* groups of 4; DIO group 1 (DIO0-3) and DIO group 2 (DIO4-7).
|
*/
|
if (chan < 4)
|
mask = 0x0f;
|
else
|
mask = 0xf0;
|
|
ret = comedi_dio_insn_config(dev, s, insn, data, mask);
|
if (ret)
|
return ret;
|
|
if (s->io_bits & 0x0f)
|
s->state |= S526_DIO_CTRL_GRP1_OUT;
|
else
|
s->state &= ~S526_DIO_CTRL_GRP1_OUT;
|
if (s->io_bits & 0xf0)
|
s->state |= S526_DIO_CTRL_GRP2_OUT;
|
else
|
s->state &= ~S526_DIO_CTRL_GRP2_OUT;
|
|
outw(s->state, dev->iobase + S526_DIO_CTRL_REG);
|
|
return insn->n;
|
}
|
|
static int s526_attach(struct comedi_device *dev, struct comedi_devconfig *it)
|
{
|
struct s526_private *devpriv;
|
struct comedi_subdevice *s;
|
int ret;
|
|
ret = comedi_request_region(dev, it->options[0], 0x40);
|
if (ret)
|
return ret;
|
|
devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
|
if (!devpriv)
|
return -ENOMEM;
|
|
ret = comedi_alloc_subdevices(dev, 4);
|
if (ret)
|
return ret;
|
|
/* General-Purpose Counter/Timer (GPCT) */
|
s = &dev->subdevices[0];
|
s->type = COMEDI_SUBD_COUNTER;
|
s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL;
|
s->n_chan = 4;
|
s->maxdata = 0x00ffffff;
|
s->insn_read = s526_gpct_rinsn;
|
s->insn_config = s526_gpct_insn_config;
|
s->insn_write = s526_gpct_winsn;
|
|
/*
|
* Analog Input subdevice
|
* channels 0 to 7 are the regular differential inputs
|
* channel 8 is "reference 0" (+10V)
|
* channel 9 is "reference 1" (0V)
|
*/
|
s = &dev->subdevices[1];
|
s->type = COMEDI_SUBD_AI;
|
s->subdev_flags = SDF_READABLE | SDF_DIFF;
|
s->n_chan = 10;
|
s->maxdata = 0xffff;
|
s->range_table = &range_bipolar10;
|
s->len_chanlist = 16;
|
s->insn_read = s526_ai_insn_read;
|
|
/* Analog Output subdevice */
|
s = &dev->subdevices[2];
|
s->type = COMEDI_SUBD_AO;
|
s->subdev_flags = SDF_WRITABLE;
|
s->n_chan = 4;
|
s->maxdata = 0xffff;
|
s->range_table = &range_bipolar10;
|
s->insn_write = s526_ao_insn_write;
|
|
ret = comedi_alloc_subdev_readback(s);
|
if (ret)
|
return ret;
|
|
/* Digital I/O subdevice */
|
s = &dev->subdevices[3];
|
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 = s526_dio_insn_bits;
|
s->insn_config = s526_dio_insn_config;
|
|
return 0;
|
}
|
|
static struct comedi_driver s526_driver = {
|
.driver_name = "s526",
|
.module = THIS_MODULE,
|
.attach = s526_attach,
|
.detach = comedi_legacy_detach,
|
};
|
module_comedi_driver(s526_driver);
|
|
MODULE_AUTHOR("Comedi http://www.comedi.org");
|
MODULE_DESCRIPTION("Comedi low-level driver");
|
MODULE_LICENSE("GPL");
|