// SPDX-License-Identifier: GPL-2.0+
/*
* pcmmio.c
* Driver for Winsystems PC-104 based multifunction IO board.
*
* COMEDI - Linux Control and Measurement Device Interface
* Copyright (C) 2007 Calin A. Culianu <calin@ajvar.org>
*/
/*
* Driver: pcmmio
* Description: A driver for the PCM-MIO multifunction board
* Devices: [Winsystems] PCM-MIO (pcmmio)
* Author: Calin Culianu <calin@ajvar.org>
* Updated: Wed, May 16 2007 16:21:10 -0500
* Status: works
*
* A driver for the PCM-MIO multifunction board from Winsystems. This
* is a PC-104 based I/O board. It contains four subdevices:
*
* subdevice 0 - 16 channels of 16-bit AI
* subdevice 1 - 8 channels of 16-bit AO
* subdevice 2 - first 24 channels of the 48 channel of DIO
* (with edge-triggered interrupt support)
* subdevice 3 - last 24 channels of the 48 channel DIO
* (no interrupt support for this bank of channels)
*
* Some notes:
*
* Synchronous reads and writes are the only things implemented for analog
* input and output. The hardware itself can do streaming acquisition, etc.
*
* Asynchronous I/O for the DIO subdevices *is* implemented, however! They
* are basically edge-triggered interrupts for any configuration of the
* channels in subdevice 2.
*
* Also note that this interrupt support is untested.
*
* A few words about edge-detection IRQ support (commands on DIO):
*
* To use edge-detection IRQ support for the DIO subdevice, pass the IRQ
* of the board to the comedi_config command. The board IRQ is not jumpered
* but rather configured through software, so any IRQ from 1-15 is OK.
*
* Due to the genericity of the comedi API, you need to create a special
* comedi_command in order to use edge-triggered interrupts for DIO.
*
* Use comedi_commands with TRIG_NOW. Your callback will be called each
* time an edge is detected on the specified DIO line(s), and the data
* values will be two sample_t's, which should be concatenated to form
* one 32-bit unsigned int. This value is the mask of channels that had
* edges detected from your channel list. Note that the bits positions
* in the mask correspond to positions in your chanlist when you
* specified the command and *not* channel id's!
*
* To set the polarity of the edge-detection interrupts pass a nonzero value
* for either CR_RANGE or CR_AREF for edge-up polarity, or a zero
* value for both CR_RANGE and CR_AREF if you want edge-down polarity.
*
* Configuration Options:
* [0] - I/O port base address
* [1] - IRQ (optional -- for edge-detect interrupt support only,
* leave out if you don't need this feature)
*/
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/comedi/comedidev.h>
/*
* Register I/O map
*/
#define PCMMIO_AI_LSB_REG
0x00
#define PCMMIO_AI_MSB_REG
0x01
#define PCMMIO_AI_CMD_REG
0x02
#define PCMMIO_AI_CMD_SE BIT(
7)
#define PCMMIO_AI_CMD_ODD_CHAN BIT(
6)
#define PCMMIO_AI_CMD_CHAN_SEL(x) (((x) &
0x3) <<
4)
#define PCMMIO_AI_CMD_RANGE(x) (((x) &
0x3) <<
2)
#define PCMMIO_RESOURCE_REG
0x02
#define PCMMIO_RESOURCE_IRQ(x) (((x) &
0xf) <<
0)
#define PCMMIO_AI_STATUS_REG
0x03
#define PCMMIO_AI_STATUS_DATA_READY BIT(
7)
#define PCMMIO_AI_STATUS_DATA_DMA_PEND BIT(
6)
#define PCMMIO_AI_STATUS_CMD_DMA_PEND BIT(
5)
#define PCMMIO_AI_STATUS_IRQ_PEND BIT(
4)
#define PCMMIO_AI_STATUS_DATA_DRQ_ENA BIT(
2)
#define PCMMIO_AI_STATUS_REG_SEL BIT(
3)
#define PCMMIO_AI_STATUS_CMD_DRQ_ENA BIT(
1)
#define PCMMIO_AI_STATUS_IRQ_ENA BIT(
0)
#define PCMMIO_AI_RES_ENA_REG
0x03
#define PCMMIO_AI_RES_ENA_CMD_REG_ACCESS (
0 <<
3)
#define PCMMIO_AI_RES_ENA_AI_RES_ACCESS BIT(
3)
#define PCMMIO_AI_RES_ENA_DIO_RES_ACCESS BIT(
4)
#define PCMMIO_AI_2ND_ADC_OFFSET
0x04
#define PCMMIO_AO_LSB_REG
0x08
#define PCMMIO_AO_LSB_SPAN(x) (((x) &
0xf) <<
0)
#define PCMMIO_AO_MSB_REG
0x09
#define PCMMIO_AO_CMD_REG
0x0a
#define PCMMIO_AO_CMD_WR_SPAN (
0x2 <<
4)
#define PCMMIO_AO_CMD_WR_CODE (
0x3 <<
4)
#define PCMMIO_AO_CMD_UPDATE (
0x4 <<
4)
#define PCMMIO_AO_CMD_UPDATE_ALL (
0x5 <<
4)
#define PCMMIO_AO_CMD_WR_SPAN_UPDATE (
0x6 <<
4)
#define PCMMIO_AO_CMD_WR_CODE_UPDATE (
0x7 <<
4)
#define PCMMIO_AO_CMD_WR_SPAN_UPDATE_ALL (
0x8 <<
4)
#define PCMMIO_AO_CMD_WR_CODE_UPDATE_ALL (
0x9 <<
4)
#define PCMMIO_AO_CMD_RD_B1_SPAN (
0xa <<
4)
#define PCMMIO_AO_CMD_RD_B1_CODE (
0xb <<
4)
#define PCMMIO_AO_CMD_RD_B2_SPAN (
0xc <<
4)
#define PCMMIO_AO_CMD_RD_B2_CODE (
0xd <<
4)
#define PCMMIO_AO_CMD_NOP (
0xf <<
4)
#define PCMMIO_AO_CMD_CHAN_SEL(x) (((x) &
0x03) <<
1)
#define PCMMIO_AO_CMD_CHAN_SEL_ALL (
0x0f <<
0)
#define PCMMIO_AO_STATUS_REG
0x0b
#define PCMMIO_AO_STATUS_DATA_READY BIT(
7)
#define PCMMIO_AO_STATUS_DATA_DMA_PEND BIT(
6)
#define PCMMIO_AO_STATUS_CMD_DMA_PEND BIT(
5)
#define PCMMIO_AO_STATUS_IRQ_PEND BIT(
4)
#define PCMMIO_AO_STATUS_DATA_DRQ_ENA BIT(
2)
#define PCMMIO_AO_STATUS_REG_SEL BIT(
3)
#define PCMMIO_AO_STATUS_CMD_DRQ_ENA BIT(
1)
#define PCMMIO_AO_STATUS_IRQ_ENA BIT(
0)
#define PCMMIO_AO_RESOURCE_ENA_REG
0x0b
#define PCMMIO_AO_2ND_DAC_OFFSET
0x04
/*
* WinSystems WS16C48
*
* Offset Page 0 Page 1 Page 2 Page 3
* ------ ----------- ----------- ----------- -----------
* 0x10 Port 0 I/O Port 0 I/O Port 0 I/O Port 0 I/O
* 0x11 Port 1 I/O Port 1 I/O Port 1 I/O Port 1 I/O
* 0x12 Port 2 I/O Port 2 I/O Port 2 I/O Port 2 I/O
* 0x13 Port 3 I/O Port 3 I/O Port 3 I/O Port 3 I/O
* 0x14 Port 4 I/O Port 4 I/O Port 4 I/O Port 4 I/O
* 0x15 Port 5 I/O Port 5 I/O Port 5 I/O Port 5 I/O
* 0x16 INT_PENDING INT_PENDING INT_PENDING INT_PENDING
* 0x17 Page/Lock Page/Lock Page/Lock Page/Lock
* 0x18 N/A POL_0 ENAB_0 INT_ID0
* 0x19 N/A POL_1 ENAB_1 INT_ID1
* 0x1a N/A POL_2 ENAB_2 INT_ID2
*/
#define PCMMIO_PORT_REG(x) (
0x10 + (x))
#define PCMMIO_INT_PENDING_REG
0x16
#define PCMMIO_PAGE_LOCK_REG
0x17
#define PCMMIO_LOCK_PORT(x) ((
1 << (x)) &
0x3f)
#define PCMMIO_PAGE(x) (((x) &
0x3) <<
6)
#define PCMMIO_PAGE_MASK PCMUIO_PAGE(
3)
#define PCMMIO_PAGE_POL
1
#define PCMMIO_PAGE_ENAB
2
#define PCMMIO_PAGE_INT_ID
3
#define PCMMIO_PAGE_REG(x) (
0x18 + (x))
static const struct comedi_lrange pcmmio_ai_ranges = {
4, {
BIP_RANGE(
5),
BIP_RANGE(
10),
UNI_RANGE(
5),
UNI_RANGE(
10)
}
};
static const struct comedi_lrange pcmmio_ao_ranges = {
6, {
UNI_RANGE(
5),
UNI_RANGE(
10),
BIP_RANGE(
5),
BIP_RANGE(
10),
BIP_RANGE(
2.
5),
RANGE(-
2.
5,
7.
5)
}
};
struct pcmmio_private {
spinlock_t pagelock;
/* protects the page registers */
spinlock_t spinlock;
/* protects the member variables */
unsigned int enabled_mask;
unsigned int active:
1;
};
static void pcmmio_dio_write(
struct comedi_device *dev,
unsigned int val,
int page,
int port)
{
struct pcmmio_private *devpriv = dev->
private;
unsigned long iobase = dev->iobase;
unsigned long flags;
spin_lock_irqsave(&devpriv->pagelock, flags);
if (page ==
0) {
/* Port registers are valid for any page */
outb(val &
0xff, iobase + PCMMIO_PORT_REG(port +
0));
outb((val >>
8) &
0xff, iobase + PCMMIO_PORT_REG(port +
1));
outb((val >>
16) &
0xff, iobase + PCMMIO_PORT_REG(port +
2));
}
else {
outb(PCMMIO_PAGE(page), iobase + PCMMIO_PAGE_LOCK_REG);
outb(val &
0xff, iobase + PCMMIO_PAGE_REG(
0));
outb((val >>
8) &
0xff, iobase + PCMMIO_PAGE_REG(
1));
outb((val >>
16) &
0xff, iobase + PCMMIO_PAGE_REG(
2));
}
spin_unlock_irqrestore(&devpriv->pagelock, flags);
}
static unsigned int pcmmio_dio_read(
struct comedi_device *dev,
int page,
int port)
{
struct pcmmio_private *devpriv = dev->
private;
unsigned long iobase = dev->iobase;
unsigned long flags;
unsigned int val;
spin_lock_irqsave(&devpriv->pagelock, flags);
if (page ==
0) {
/* Port registers are valid for any page */
val = inb(iobase + PCMMIO_PORT_REG(port +
0));
val |= (inb(iobase + PCMMIO_PORT_REG(port +
1)) <<
8);
val |= (inb(iobase + PCMMIO_PORT_REG(port +
2)) <<
16);
}
else {
outb(PCMMIO_PAGE(page), iobase + PCMMIO_PAGE_LOCK_REG);
val = inb(iobase + PCMMIO_PAGE_REG(
0));
val |= (inb(iobase + PCMMIO_PAGE_REG(
1)) <<
8);
val |= (inb(iobase + PCMMIO_PAGE_REG(
2)) <<
16);
}
spin_unlock_irqrestore(&devpriv->pagelock, flags);
return val;
}
/*
* Each channel can be individually programmed for input or output.
* Writing a '0' to a channel causes the corresponding output pin
* to go to a high-z state (pulled high by an external 10K resistor).
* This allows it to be used as an input. When used in the input mode,
* a read reflects the inverted state of the I/O pin, such that a
* high on the pin will read as a '0' in the register. Writing a '1'
* to a bit position causes the pin to sink current (up to 12mA),
* effectively pulling it low.
*/
static int pcmmio_dio_insn_bits(
struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned int *data)
{
/* subdevice 2 uses ports 0-2, subdevice 3 uses ports 3-5 */
int port = s->index ==
2 ?
0 :
3;
unsigned int chanmask = (
1 << s->n_chan) -
1;
unsigned int mask;
unsigned int val;
mask = comedi_dio_update_state(s, data);
if (mask) {
/*
* Outputs are inverted, invert the state and
* update the channels.
*
* The s->io_bits mask makes sure the input channels
* are '0' so that the outputs pins stay in a high
* z-state.
*/
val = ~s->state & chanmask;
val &= s->io_bits;
pcmmio_dio_write(dev, val,
0, port);
}
/* get inverted state of the channels from the port */
val = pcmmio_dio_read(dev,
0, port);
/* return the true state of the channels */
data[
1] = ~val & chanmask;
return insn->n;
}
static int pcmmio_dio_insn_config(
struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned int *data)
{
/* subdevice 2 uses ports 0-2, subdevice 3 uses ports 3-5 */
int port = s->index ==
2 ?
0 :
3;
int ret;
ret = comedi_dio_insn_config(dev, s, insn, data,
0);
if (ret)
return ret;
if (data[
0] == INSN_CONFIG_DIO_INPUT)
pcmmio_dio_write(dev, s->io_bits,
0, port);
return insn->n;
}
static void pcmmio_reset(
struct comedi_device *dev)
{
/* Clear all the DIO port bits */
pcmmio_dio_write(dev,
0,
0,
0);
pcmmio_dio_write(dev,
0,
0,
3);
/* Clear all the paged registers */
pcmmio_dio_write(dev,
0, PCMMIO_PAGE_POL,
0);
pcmmio_dio_write(dev,
0, PCMMIO_PAGE_ENAB,
0);
pcmmio_dio_write(dev,
0, PCMMIO_PAGE_INT_ID,
0);
}
/* devpriv->spinlock is already locked */
static void pcmmio_stop_intr(
struct comedi_device *dev,
struct comedi_subdevice *s)
{
struct pcmmio_private *devpriv = dev->
private;
devpriv->enabled_mask =
0;
devpriv->active =
0;
s->async->inttrig = NULL;
/* disable all dio interrupts */
pcmmio_dio_write(dev,
0, PCMMIO_PAGE_ENAB,
0);
}
static void pcmmio_handle_dio_intr(
struct comedi_device *dev,
struct comedi_subdevice *s,
unsigned int triggered)
{
struct pcmmio_private *devpriv = dev->
private;
struct comedi_cmd *cmd = &s->async->cmd;
unsigned int val =
0;
unsigned long flags;
int i;
spin_lock_irqsave(&devpriv->spinlock, flags);
if (!devpriv->active)
goto done;
if (!(triggered & devpriv->enabled_mask))
goto done;
for (i =
0; i < cmd->chanlist_len; i++) {
unsigned int chan = CR_CHAN(cmd->chanlist[i]);
if (triggered & (
1 << chan))
val |= (
1 << i);
}
comedi_buf_write_samples(s, &val,
1);
if (cmd->stop_src == TRIG_COUNT &&
s->async->scans_done >= cmd->stop_arg)
s->async->events |= COMEDI_CB_EOA;
done:
spin_unlock_irqrestore(&devpriv->spinlock, flags);
comedi_handle_events(dev, s);
}
static irqreturn_t interrupt_pcmmio(
int irq,
void *d)
{
struct comedi_device *dev = d;
struct comedi_subdevice *s = dev->read_subdev;
unsigned int triggered;
unsigned char int_pend;
/* are there any interrupts pending */
int_pend = inb(dev->iobase + PCMMIO_INT_PENDING_REG) &
0x07;
if (!int_pend)
return IRQ_NONE;
/* get, and clear, the pending interrupts */
triggered = pcmmio_dio_read(dev, PCMMIO_PAGE_INT_ID,
0);
pcmmio_dio_write(dev,
0, PCMMIO_PAGE_INT_ID,
0);
pcmmio_handle_dio_intr(dev, s, triggered);
return IRQ_HANDLED;
}
/* devpriv->spinlock is already locked */
static void pcmmio_start_intr(
struct comedi_device *dev,
struct comedi_subdevice *s)
{
struct pcmmio_private *devpriv = dev->
private;
struct comedi_cmd *cmd = &s->async->cmd;
unsigned int bits =
0;
unsigned int pol_bits =
0;
int i;
devpriv->enabled_mask =
0;
devpriv->active =
1;
if (cmd->chanlist) {
for (i =
0; i < cmd->chanlist_len; i++) {
unsigned int chanspec = cmd->chanlist[i];
unsigned int chan = CR_CHAN(chanspec);
unsigned int range = CR_RANGE(chanspec);
unsigned int aref = CR_AREF(chanspec);
bits |= (
1 << chan);
pol_bits |= (((aref || range) ?
1 :
0) << chan);
}
}
bits &= ((
1 << s->n_chan) -
1);
devpriv->enabled_mask = bits;
/* set polarity and enable interrupts */
pcmmio_dio_write(dev, pol_bits, PCMMIO_PAGE_POL,
0);
pcmmio_dio_write(dev, bits, PCMMIO_PAGE_ENAB,
0);
}
static int pcmmio_cancel(
struct comedi_device *dev,
struct comedi_subdevice *s)
{
struct pcmmio_private *devpriv = dev->
private;
unsigned long flags;
spin_lock_irqsave(&devpriv->spinlock, flags);
if (devpriv->active)
pcmmio_stop_intr(dev, s);
spin_unlock_irqrestore(&devpriv->spinlock, flags);
return 0;
}
static int pcmmio_inttrig_start_intr(
struct comedi_device *dev,
struct comedi_subdevice *s,
unsigned int trig_num)
{
struct pcmmio_private *devpriv = dev->
private;
struct comedi_cmd *cmd = &s->async->cmd;
unsigned long flags;
if (trig_num != cmd->start_arg)
return -EINVAL;
spin_lock_irqsave(&devpriv->spinlock, flags);
s->async->inttrig = NULL;
if (devpriv->active)
pcmmio_start_intr(dev, s);
spin_unlock_irqrestore(&devpriv->spinlock, flags);
return 1;
}
/*
* 'do_cmd' function for an 'INTERRUPT' subdevice.
*/
static int pcmmio_cmd(
struct comedi_device *dev,
struct comedi_subdevice *s)
{
struct pcmmio_private *devpriv = dev->
private;
struct comedi_cmd *cmd = &s->async->cmd;
unsigned long flags;
spin_lock_irqsave(&devpriv->spinlock, flags);
devpriv->active =
1;
/* Set up start of acquisition. */
if (cmd->start_src == TRIG_INT)
s->async->inttrig = pcmmio_inttrig_start_intr;
else /* TRIG_NOW */
pcmmio_start_intr(dev, s);
spin_unlock_irqrestore(&devpriv->spinlock, flags);
return 0;
}
static int pcmmio_cmdtest(
struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_cmd *cmd)
{
int err =
0;
/* Step 1 : check if triggers are trivially valid */
err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT);
err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW);
err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
if (err)
return 1;
/* Step 2a : make sure trigger sources are unique */
err |= comedi_check_trigger_is_unique(cmd->start_src);
err |= comedi_check_trigger_is_unique(cmd->stop_src);
/* Step 2b : and mutually compatible */
if (err)
return 2;
/* Step 3: check if arguments are trivially valid */
err |= comedi_check_trigger_arg_is(&cmd->start_arg,
0);
err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg,
0);
err |= comedi_check_trigger_arg_is(&cmd->convert_arg,
0);
err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
cmd->chanlist_len);
if (cmd->stop_src == TRIG_COUNT)
err |= comedi_check_trigger_arg_min(&cmd->stop_arg,
1);
else /* TRIG_NONE */
err |= comedi_check_trigger_arg_is(&cmd->stop_arg,
0);
if (err)
return 3;
/* step 4: fix up any arguments */
/* if (err) return 4; */
return 0;
}
static int pcmmio_ai_eoc(
struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned long context)
{
unsigned char status;
status = inb(dev->iobase + PCMMIO_AI_STATUS_REG);
if (status & PCMMIO_AI_STATUS_DATA_READY)
return 0;
return -EBUSY;
}
static int pcmmio_ai_insn_read(
struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned int *data)
{
unsigned long iobase = dev->iobase;
unsigned int chan = CR_CHAN(insn->chanspec);
unsigned int range = CR_RANGE(insn->chanspec);
unsigned int aref = CR_AREF(insn->chanspec);
unsigned char cmd =
0;
unsigned int val;
int ret;
int i;
/*
* The PCM-MIO uses two Linear Tech LTC1859CG 8-channel A/D converters.
* The devices use a full duplex serial interface which transmits and
* receives data simultaneously. An 8-bit command is shifted into the
* ADC interface to configure it for the next conversion. At the same
* time, the data from the previous conversion is shifted out of the
* device. Consequently, the conversion result is delayed by one
* conversion from the command word.
*
* Setup the cmd for the conversions then do a dummy conversion to
* flush the junk data. Then do each conversion requested by the
* comedi_insn. Note that the last conversion will leave junk data
* in ADC which will get flushed on the next comedi_insn.
*/
if (chan >
7) {
chan -=
8;
iobase += PCMMIO_AI_2ND_ADC_OFFSET;
}
if (aref == AREF_GROUND)
cmd |= PCMMIO_AI_CMD_SE;
if (chan %
2)
cmd |= PCMMIO_AI_CMD_ODD_CHAN;
cmd |= PCMMIO_AI_CMD_CHAN_SEL(chan /
2);
cmd |= PCMMIO_AI_CMD_RANGE(range);
outb(cmd, iobase + PCMMIO_AI_CMD_REG);
ret = comedi_timeout(dev, s, insn, pcmmio_ai_eoc,
0);
if (ret)
return ret;
val = inb(iobase + PCMMIO_AI_LSB_REG);
val |= inb(iobase + PCMMIO_AI_MSB_REG) <<
8;
for (i =
0; i < insn->n; i++) {
outb(cmd, iobase + PCMMIO_AI_CMD_REG);
ret = comedi_timeout(dev, s, insn, pcmmio_ai_eoc,
0);
if (ret)
return ret;
val = inb(iobase + PCMMIO_AI_LSB_REG);
val |= inb(iobase + PCMMIO_AI_MSB_REG) <<
8;
/* bipolar data is two's complement */
if (comedi_range_is_bipolar(s, range))
val = comedi_offset_munge(s, val);
data[i] = val;
}
return insn->n;
}
static int pcmmio_ao_eoc(
struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned long context)
{
unsigned char status;
status = inb(dev->iobase + PCMMIO_AO_STATUS_REG);
if (status & PCMMIO_AO_STATUS_DATA_READY)
return 0;
return -EBUSY;
}
static int pcmmio_ao_insn_write(
struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned int *data)
{
unsigned long iobase = dev->iobase;
unsigned int chan = CR_CHAN(insn->chanspec);
unsigned int range = CR_RANGE(insn->chanspec);
unsigned char cmd =
0;
int ret;
int i;
/*
* The PCM-MIO has two Linear Tech LTC2704 DAC devices. Each device
* is a 4-channel converter with software-selectable output range.
*/
if (chan >
3) {
cmd |= PCMMIO_AO_CMD_CHAN_SEL(chan -
4);
iobase += PCMMIO_AO_2ND_DAC_OFFSET;
}
else {
cmd |= PCMMIO_AO_CMD_CHAN_SEL(chan);
}
/* set the range for the channel */
outb(PCMMIO_AO_LSB_SPAN(range), iobase + PCMMIO_AO_LSB_REG);
outb(
0, iobase + PCMMIO_AO_MSB_REG);
outb(cmd | PCMMIO_AO_CMD_WR_SPAN_UPDATE, iobase + PCMMIO_AO_CMD_REG);
ret = comedi_timeout(dev, s, insn, pcmmio_ao_eoc,
0);
if (ret)
return ret;
for (i =
0; i < insn->n; i++) {
unsigned int val = data[i];
/* write the data to the channel */
outb(val &
0xff, iobase + PCMMIO_AO_LSB_REG);
outb((val >>
8) &
0xff, iobase + PCMMIO_AO_MSB_REG);
outb(cmd | PCMMIO_AO_CMD_WR_CODE_UPDATE,
iobase + PCMMIO_AO_CMD_REG);
ret = comedi_timeout(dev, s, insn, pcmmio_ao_eoc,
0);
if (ret)
return ret;
s->readback[chan] = val;
}
return insn->n;
}
static int pcmmio_attach(
struct comedi_device *dev,
struct comedi_devconfig *it)
{
struct pcmmio_private *devpriv;
struct comedi_subdevice *s;
int ret;
ret = comedi_request_region(dev, it->options[
0],
32);
if (ret)
return ret;
devpriv = comedi_alloc_devpriv(dev,
sizeof(*devpriv));
if (!devpriv)
return -ENOMEM;
spin_lock_init(&devpriv->pagelock);
spin_lock_init(&devpriv->spinlock);
pcmmio_reset(dev);
if (it->options[
1]) {
ret = request_irq(it->options[
1], interrupt_pcmmio,
0,
dev->board_name, dev);
if (ret ==
0) {
dev->irq = it->options[
1];
/* configure the interrupt routing on the board */
outb(PCMMIO_AI_RES_ENA_DIO_RES_ACCESS,
dev->iobase + PCMMIO_AI_RES_ENA_REG);
outb(PCMMIO_RESOURCE_IRQ(dev->irq),
dev->iobase + PCMMIO_RESOURCE_REG);
}
}
ret = comedi_alloc_subdevices(dev,
4);
if (ret)
return ret;
/* Analog Input subdevice */
s = &dev->subdevices[
0];
s->type = COMEDI_SUBD_AI;
s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF;
s->n_chan =
16;
s->maxdata =
0xffff;
s->range_table = &pcmmio_ai_ranges;
s->insn_read = pcmmio_ai_insn_read;
/* initialize the resource enable register by clearing it */
outb(PCMMIO_AI_RES_ENA_CMD_REG_ACCESS,
dev->iobase + PCMMIO_AI_RES_ENA_REG);
outb(PCMMIO_AI_RES_ENA_CMD_REG_ACCESS,
dev->iobase + PCMMIO_AI_RES_ENA_REG + PCMMIO_AI_2ND_ADC_OFFSET);
/* Analog Output subdevice */
s = &dev->subdevices[
1];
s->type = COMEDI_SUBD_AO;
s->subdev_flags = SDF_READABLE;
s->n_chan =
8;
s->maxdata =
0xffff;
s->range_table = &pcmmio_ao_ranges;
s->insn_write = pcmmio_ao_insn_write;
ret = comedi_alloc_subdev_readback(s);
if (ret)
return ret;
/* initialize the resource enable register by clearing it */
outb(
0, dev->iobase + PCMMIO_AO_RESOURCE_ENA_REG);
outb(
0, dev->iobase + PCMMIO_AO_2ND_DAC_OFFSET +
PCMMIO_AO_RESOURCE_ENA_REG);
/* Digital I/O subdevice with interrupt support */
s = &dev->subdevices[
2];
s->type = COMEDI_SUBD_DIO;
s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
s->n_chan =
24;
s->maxdata =
1;
s->len_chanlist =
1;
s->range_table = &range_digital;
s->insn_bits = pcmmio_dio_insn_bits;
s->insn_config = pcmmio_dio_insn_config;
if (dev->irq) {
dev->read_subdev = s;
s->subdev_flags |= SDF_CMD_READ | SDF_LSAMPL | SDF_PACKED;
s->len_chanlist = s->n_chan;
s->cancel = pcmmio_cancel;
s->do_cmd = pcmmio_cmd;
s->do_cmdtest = pcmmio_cmdtest;
}
/* Digital I/O subdevice */
s = &dev->subdevices[
3];
s->type = COMEDI_SUBD_DIO;
s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
s->n_chan =
24;
s->maxdata =
1;
s->range_table = &range_digital;
s->insn_bits = pcmmio_dio_insn_bits;
s->insn_config = pcmmio_dio_insn_config;
return 0;
}
static struct comedi_driver pcmmio_driver = {
.driver_name =
"pcmmio",
.module = THIS_MODULE,
.attach = pcmmio_attach,
.detach = comedi_legacy_detach,
};
module_comedi_driver(pcmmio_driver);
MODULE_AUTHOR(
"Comedi https://www.comedi.org");
MODULE_DESCRIPTION(
"Comedi driver for Winsystems PCM-MIO PC/104 board");
MODULE_LICENSE(
"GPL");