#include <linux/module.h>
#include <linux/delay.h>
#include <linux/gfp.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/comedi/comedi_pci.h>
#include <linux/comedi/comedi_8254.h>
#include "amcc_s5933.h"
#define PCI9118_TIMER_BASE 0x00
#define PCI9118_AI_FIFO_REG 0x10
#define PCI9118_AO_REG(x) (0x10 + ((x) * 4))
#define PCI9118_AI_STATUS_REG 0x18
#define PCI9118_AI_STATUS_NFULL BIT(8) /* 0=FIFO full (fatal) */
#define PCI9118_AI_STATUS_NHFULL BIT(7) /* 0=FIFO half full */
#define PCI9118_AI_STATUS_NEPTY BIT(6) /* 0=FIFO empty */
#define PCI9118_AI_STATUS_ACMP BIT(5) /* 1=about trigger complete */
#define PCI9118_AI_STATUS_DTH BIT(4) /* 1=ext. digital trigger */
#define PCI9118_AI_STATUS_BOVER BIT(3) /* 1=burst overrun (fatal) */
#define PCI9118_AI_STATUS_ADOS BIT(2) /* 1=A/D over speed (warn) */
#define PCI9118_AI_STATUS_ADOR BIT(1) /* 1=A/D overrun (fatal) */
#define PCI9118_AI_STATUS_ADRDY BIT(0) /* 1=A/D ready */
#define PCI9118_AI_CTRL_REG 0x18
#define PCI9118_AI_CTRL_UNIP BIT(7) /* 1=unipolar */
#define PCI9118_AI_CTRL_DIFF BIT(6) /* 1=differential inputs */
#define PCI9118_AI_CTRL_SOFTG BIT(5) /* 1=8254 software gate */
#define PCI9118_AI_CTRL_EXTG BIT(4) /* 1=8254 TGIN(pin 46) gate */
#define PCI9118_AI_CTRL_EXTM BIT(3) /* 1=ext. trigger (pin 44) */
#define PCI9118_AI_CTRL_TMRTR BIT(2) /* 1=8254 is trigger source */
#define PCI9118_AI_CTRL_INT BIT(1) /* 1=enable interrupt */
#define PCI9118_AI_CTRL_DMA BIT(0) /* 1=enable DMA */
#define PCI9118_DIO_REG 0x1c
#define PCI9118_SOFTTRG_REG 0x20
#define PCI9118_AI_CHANLIST_REG 0x24
#define PCI9118_AI_CHANLIST_RANGE(x) (((x) & 0x3) << 8)
#define PCI9118_AI_CHANLIST_CHAN(x) ((x) << 0)
#define PCI9118_AI_BURST_NUM_REG 0x28
#define PCI9118_AI_AUTOSCAN_MODE_REG 0x2c
#define PCI9118_AI_CFG_REG 0x30
#define PCI9118_AI_CFG_PDTRG BIT(7) /* 1=positive trigger */
#define PCI9118_AI_CFG_PETRG BIT(6) /* 1=positive ext. trigger */
#define PCI9118_AI_CFG_BSSH BIT(5) /* 1=with sample & hold */
#define PCI9118_AI_CFG_BM BIT(4) /* 1=burst mode */
#define PCI9118_AI_CFG_BS BIT(3) /* 1=burst mode start */
#define PCI9118_AI_CFG_PM BIT(2) /* 1=post trigger */
#define PCI9118_AI_CFG_AM BIT(1) /* 1=about trigger */
#define PCI9118_AI_CFG_START BIT(0) /* 1=trigger start */
#define PCI9118_FIFO_RESET_REG 0x34
#define PCI9118_INT_CTRL_REG 0x38
#define PCI9118_INT_CTRL_TIMER BIT(3) /* timer interrupt */
#define PCI9118_INT_CTRL_ABOUT BIT(2) /* about trigger complete */
#define PCI9118_INT_CTRL_HFULL BIT(1) /* A/D FIFO half full */
#define PCI9118_INT_CTRL_DTRG BIT(0) /* ext. digital trigger */
#define START_AI_EXT 0x01 /* start measure on external trigger */
#define STOP_AI_EXT 0x02 /* stop measure on external trigger */
#define STOP_AI_INT 0x08 /* stop measure on internal trigger */
static const struct comedi_lrange pci9118_ai_range = {
8, {
BIP_RANGE(5),
BIP_RANGE(2.5),
BIP_RANGE(1.25),
BIP_RANGE(0.625),
UNI_RANGE(10),
UNI_RANGE(5),
UNI_RANGE(2.5),
UNI_RANGE(1.25)
}
};
static const struct comedi_lrange pci9118hg_ai_range = {
8, {
BIP_RANGE(5),
BIP_RANGE(0.5),
BIP_RANGE(0.05),
BIP_RANGE(0.005),
UNI_RANGE(10),
UNI_RANGE(1),
UNI_RANGE(0.1),
UNI_RANGE(0.01)
}
};
enum pci9118_boardid {
BOARD_PCI9118DG,
BOARD_PCI9118HG,
BOARD_PCI9118HR,
};
struct pci9118_boardinfo {
const char *name;
unsigned int ai_is_16bit:1;
unsigned int is_hg:1;
};
static const struct pci9118_boardinfo pci9118_boards[] = {
[BOARD_PCI9118DG] = {
.name = "pci9118dg",
},
[BOARD_PCI9118HG] = {
.name = "pci9118hg",
.is_hg = 1,
},
[BOARD_PCI9118HR] = {
.name = "pci9118hr",
.ai_is_16bit = 1,
},
};
struct pci9118_dmabuf {
unsigned short *virt;
dma_addr_t hw;
unsigned int size;
unsigned int use_size;
};
struct pci9118_private {
unsigned long iobase_a;
unsigned int master:1;
unsigned int dma_doublebuf:1;
unsigned int ai_neverending:1;
unsigned int usedma:1;
unsigned int usemux:1;
unsigned char ai_ctrl;
unsigned char int_ctrl;
unsigned char ai_cfg;
unsigned int ai_do;
unsigned int ai_n_realscanlen;
unsigned int ai_act_dmapos;
unsigned int ai_add_front;
unsigned int ai_add_back;
unsigned int ai_flags;
char ai12_startstop;
unsigned int dma_actbuf;
struct pci9118_dmabuf dmabuf[2];
int softsshdelay;
unsigned char softsshsample;
unsigned char softsshhold;
unsigned int ai_ns_min;
};
static void pci9118_amcc_setup_dma(struct comedi_device *dev, unsigned int buf)
{
struct pci9118_private *devpriv = dev->private;
struct pci9118_dmabuf *dmabuf = &devpriv->dmabuf[buf];
outl(dmabuf->hw, devpriv->iobase_a + AMCC_OP_REG_MWAR);
outl(dmabuf->use_size, devpriv->iobase_a + AMCC_OP_REG_MWTC);
}
static void pci9118_amcc_dma_ena(struct comedi_device *dev, bool enable)
{
struct pci9118_private *devpriv = dev->private;
unsigned int mcsr;
mcsr = inl(devpriv->iobase_a + AMCC_OP_REG_MCSR);
if (enable)
mcsr |= RESET_A2P_FLAGS | A2P_HI_PRIORITY | EN_A2P_TRANSFERS;
else
mcsr &= ~EN_A2P_TRANSFERS;
outl(mcsr, devpriv->iobase_a + AMCC_OP_REG_MCSR);
}
static void pci9118_amcc_int_ena(struct comedi_device *dev, bool enable)
{
struct pci9118_private *devpriv = dev->private;
unsigned int intcsr;
intcsr = inl(devpriv->iobase_a + AMCC_OP_REG_INTCSR);
if (enable)
intcsr |= 0x1f00;
else
intcsr &= ~0x1f00;
outl(intcsr, devpriv->iobase_a + AMCC_OP_REG_INTCSR);
}
static void pci9118_ai_reset_fifo(struct comedi_device *dev)
{
outl(0, dev->iobase + PCI9118_FIFO_RESET_REG);
}
static int pci9118_ai_check_chanlist(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_cmd *cmd)
{
struct pci9118_private *devpriv = dev->private;
unsigned int range0 = CR_RANGE(cmd->chanlist[0]);
unsigned int aref0 = CR_AREF(cmd->chanlist[0]);
int i;
if (cmd->chanlist_len == 1)
return 0;
for (i = 1; i < cmd->chanlist_len; i++) {
unsigned int chan = CR_CHAN(cmd->chanlist[i]);
unsigned int range = CR_RANGE(cmd->chanlist[i]);
unsigned int aref = CR_AREF(cmd->chanlist[i]);
if (aref != aref0) {
dev_err(dev->class_dev,
"Differential and single ended inputs can't be mixed!\n");
return -EINVAL;
}
if (comedi_range_is_bipolar(s, range) !=
comedi_range_is_bipolar(s, range0)) {
dev_err(dev->class_dev,
"Bipolar and unipolar ranges can't be mixed!\n");
return -EINVAL;
}
if (!devpriv->usemux && aref == AREF_DIFF &&
(chan >= (s->n_chan / 2))) {
dev_err(dev->class_dev,
"AREF_DIFF is only available for the first 8 channels!\n");
return -EINVAL;
}
}
return 0;
}
static void pci9118_set_chanlist(struct comedi_device *dev,
struct comedi_subdevice *s,
int n_chan, unsigned int *chanlist,
int frontadd, int backadd)
{
struct pci9118_private *devpriv = dev->private;
unsigned int chan0 = CR_CHAN(chanlist[0]);
unsigned int range0 = CR_RANGE(chanlist[0]);
unsigned int aref0 = CR_AREF(chanlist[0]);
unsigned int ssh = 0x00;
unsigned int val;
int i;
devpriv->ai_ctrl = 0;
if (comedi_range_is_unipolar(s, range0))
devpriv->ai_ctrl |= PCI9118_AI_CTRL_UNIP;
if (aref0 == AREF_DIFF)
devpriv->ai_ctrl |= PCI9118_AI_CTRL_DIFF;
outl(devpriv->ai_ctrl, dev->iobase + PCI9118_AI_CTRL_REG);
outl(2, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG);
outl(0, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG);
outl(1, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG);
if (frontadd) {
val = PCI9118_AI_CHANLIST_CHAN(chan0) |
PCI9118_AI_CHANLIST_RANGE(range0);
ssh = devpriv->softsshsample;
for (i = 0; i < frontadd; i++) {
outl(val | ssh, dev->iobase + PCI9118_AI_CHANLIST_REG);
ssh = devpriv->softsshhold;
}
}
for (i = 0; i < n_chan; i++) {
unsigned int chan = CR_CHAN(chanlist[i]);
unsigned int range = CR_RANGE(chanlist[i]);
val = PCI9118_AI_CHANLIST_CHAN(chan) |
PCI9118_AI_CHANLIST_RANGE(range);
outl(val | ssh, dev->iobase + PCI9118_AI_CHANLIST_REG);
}
if (backadd) {
val = PCI9118_AI_CHANLIST_CHAN(chan0) |
PCI9118_AI_CHANLIST_RANGE(range0);
for (i = 0; i < backadd; i++)
outl(val | ssh, dev->iobase + PCI9118_AI_CHANLIST_REG);
}
outl(0, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG);
}
static void pci9118_ai_mode4_switch(struct comedi_device *dev,
unsigned int next_buf)
{
struct pci9118_private *devpriv = dev->private;
struct pci9118_dmabuf *dmabuf = &devpriv->dmabuf[next_buf];
devpriv->ai_cfg = PCI9118_AI_CFG_PDTRG | PCI9118_AI_CFG_PETRG |
PCI9118_AI_CFG_AM;
outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG);
comedi_8254_load(dev->pacer, 0, dmabuf->hw >> 1,
I8254_MODE0 | I8254_BINARY);
devpriv->ai_cfg |= PCI9118_AI_CFG_START;
outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG);
}
static unsigned int pci9118_ai_samples_ready(struct comedi_device *dev,
struct comedi_subdevice *s,
unsigned int n_raw_samples)
{
struct pci9118_private *devpriv = dev->private;
struct comedi_cmd *cmd = &s->async->cmd;
unsigned int start_pos = devpriv->ai_add_front;
unsigned int stop_pos = start_pos + cmd->chanlist_len;
unsigned int span_len = stop_pos + devpriv->ai_add_back;
unsigned int dma_pos = devpriv->ai_act_dmapos;
unsigned int whole_spans, n_samples, x;
if (span_len == cmd->chanlist_len)
return n_raw_samples;
whole_spans = n_raw_samples / span_len;
n_samples = whole_spans * cmd->chanlist_len;
n_raw_samples -= whole_spans * span_len;
while (n_raw_samples) {
if (dma_pos < start_pos) {
x = start_pos - dma_pos;
if (x > n_raw_samples)
x = n_raw_samples;
dma_pos += x;
n_raw_samples -= x;
if (!n_raw_samples)
break;
}
if (dma_pos < stop_pos) {
x = stop_pos - dma_pos;
if (x > n_raw_samples)
x = n_raw_samples;
n_samples += x;
dma_pos += x;
n_raw_samples -= x;
}
start_pos += span_len;
stop_pos += span_len;
}
return n_samples;
}
static void pci9118_ai_dma_xfer(struct comedi_device *dev,
struct comedi_subdevice *s,
unsigned short *dma_buffer,
unsigned int n_raw_samples)
{
struct pci9118_private *devpriv = dev->private;
struct comedi_cmd *cmd = &s->async->cmd;
unsigned int start_pos = devpriv->ai_add_front;
unsigned int stop_pos = start_pos + cmd->chanlist_len;
unsigned int span_len = stop_pos + devpriv->ai_add_back;
unsigned int dma_pos = devpriv->ai_act_dmapos;
unsigned int x;
if (span_len == cmd->chanlist_len) {
comedi_buf_write_samples(s, dma_buffer, n_raw_samples);
dma_pos += n_raw_samples;
} else {
while (n_raw_samples) {
if (dma_pos < start_pos) {
x = start_pos - dma_pos;
if (x > n_raw_samples)
x = n_raw_samples;
dma_pos += x;
n_raw_samples -= x;
if (!n_raw_samples)
break;
}
if (dma_pos < stop_pos) {
x = stop_pos - dma_pos;
if (x > n_raw_samples)
x = n_raw_samples;
comedi_buf_write_samples(s, dma_buffer, x);
dma_pos += x;
n_raw_samples -= x;
}
start_pos += span_len;
stop_pos += span_len;
}
}
devpriv->ai_act_dmapos = dma_pos % span_len;
}
static void pci9118_exttrg_enable(struct comedi_device *dev, bool enable)
{
struct pci9118_private *devpriv = dev->private;
if (enable)
devpriv->int_ctrl |= PCI9118_INT_CTRL_DTRG;
else
devpriv->int_ctrl &= ~PCI9118_INT_CTRL_DTRG;
outl(devpriv->int_ctrl, dev->iobase + PCI9118_INT_CTRL_REG);
if (devpriv->int_ctrl)
pci9118_amcc_int_ena(dev, true);
else
pci9118_amcc_int_ena(dev, false);
}
static void pci9118_calc_divisors(struct comedi_device *dev,
struct comedi_subdevice *s,
unsigned int *tim1, unsigned int *tim2,
unsigned int flags, int chans,
unsigned int *div1, unsigned int *div2,
unsigned int chnsshfront)
{
struct comedi_8254 *pacer = dev->pacer;
struct comedi_cmd *cmd = &s->async->cmd;
*div1 = *tim2 / pacer->osc_base;
*div2 = *tim1 / pacer->osc_base;
*div2 = *div2 / *div1;
if (*div2 < chans)
*div2 = chans;
*tim2 = *div1 * pacer->osc_base;
if (cmd->convert_src == TRIG_NOW && !chnsshfront) {
if (*div2 < (chans + 2))
*div2 = chans + 2;
}
*tim1 = *div1 * *div2 * pacer->osc_base;
}
static void pci9118_start_pacer(struct comedi_device *dev, int mode)
{
if (mode == 1 || mode == 2 || mode == 4)
comedi_8254_pacer_enable(dev->pacer, 1, 2, true);
}
static int pci9118_ai_cancel(struct comedi_device *dev,
struct comedi_subdevice *s)
{
struct pci9118_private *devpriv = dev->private;
if (devpriv->usedma)
pci9118_amcc_dma_ena(dev, false);
pci9118_exttrg_enable(dev, false);
comedi_8254_pacer_enable(dev->pacer, 1, 2, false);
devpriv->ai_cfg = PCI9118_AI_CFG_PDTRG | PCI9118_AI_CFG_PETRG;
outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG);
devpriv->ai_ctrl = 0;
outl(devpriv->ai_ctrl, dev->iobase + PCI9118_AI_CTRL_REG);
outl(0, dev->iobase + PCI9118_AI_BURST_NUM_REG);
outl(1, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG);
outl(2, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG);
pci9118_ai_reset_fifo(dev);
devpriv->int_ctrl = 0;
outl(devpriv->int_ctrl, dev->iobase + PCI9118_INT_CTRL_REG);
pci9118_amcc_int_ena(dev, false);
devpriv->ai_do = 0;
devpriv->usedma = 0;
devpriv->ai_act_dmapos = 0;
s->async->inttrig = NULL;
devpriv->ai_neverending = 0;
devpriv->dma_actbuf = 0;
return 0;
}
static void pci9118_ai_munge(struct comedi_device *dev,
struct comedi_subdevice *s, void *data,
unsigned int num_bytes,
unsigned int start_chan_index)
{
struct pci9118_private *devpriv = dev->private;
unsigned short *array = data;
unsigned int num_samples = comedi_bytes_to_samples(s, num_bytes);
unsigned int i;
__be16 *barray = data;
for (i = 0; i < num_samples; i++) {
if (devpriv->usedma)
array[i] = be16_to_cpu(barray[i]);
if (s->maxdata == 0xffff)
array[i] ^= 0x8000;
else
array[i] = (array[i] >> 4) & 0x0fff;
}
}
static void pci9118_ai_get_onesample(struct comedi_device *dev,
struct comedi_subdevice *s)
{
struct pci9118_private *devpriv = dev->private;
struct comedi_cmd *cmd = &s->async->cmd;
unsigned short sampl;
sampl = inl(dev->iobase + PCI9118_AI_FIFO_REG);
comedi_buf_write_samples(s, &sampl, 1);
if (!devpriv->ai_neverending) {
if (s->async->scans_done >= cmd->stop_arg)
s->async->events |= COMEDI_CB_EOA;
}
}
static void pci9118_ai_get_dma(struct comedi_device *dev,
struct comedi_subdevice *s)
{
struct pci9118_private *devpriv = dev->private;
struct comedi_cmd *cmd = &s->async->cmd;
struct pci9118_dmabuf *dmabuf = &devpriv->dmabuf[devpriv->dma_actbuf];
unsigned int n_all = comedi_bytes_to_samples(s, dmabuf->use_size);
unsigned int n_valid;
bool more_dma;
n_valid = pci9118_ai_samples_ready(dev, s, n_all);
more_dma = n_valid < comedi_nsamples_left(s, n_valid + 1);
if (more_dma && devpriv->dma_doublebuf) {
devpriv->dma_actbuf = 1 - devpriv->dma_actbuf;
pci9118_amcc_setup_dma(dev, devpriv->dma_actbuf);
if (devpriv->ai_do == 4)
pci9118_ai_mode4_switch(dev, devpriv->dma_actbuf);
}
if (n_all)
pci9118_ai_dma_xfer(dev, s, dmabuf->virt, n_all);
if (!devpriv->ai_neverending) {
if (s->async->scans_done >= cmd->stop_arg)
s->async->events |= COMEDI_CB_EOA;
}
if (s->async->events & COMEDI_CB_CANCEL_MASK)
more_dma = false;
if (more_dma && !devpriv->dma_doublebuf) {
pci9118_amcc_setup_dma(dev, 0);
if (devpriv->ai_do == 4)
pci9118_ai_mode4_switch(dev, 0);
}
}
static irqreturn_t pci9118_interrupt(int irq, void *d)
{
struct comedi_device *dev = d;
struct comedi_subdevice *s = dev->read_subdev;
struct pci9118_private *devpriv = dev->private;
unsigned int intsrc;
unsigned int intcsr;
unsigned int adstat;
if (!dev->attached)
return IRQ_NONE;
intsrc = inl(dev->iobase + PCI9118_INT_CTRL_REG) & 0xf;
intcsr = inl(devpriv->iobase_a + AMCC_OP_REG_INTCSR);
if (!intsrc && !(intcsr & ANY_S593X_INT))
return IRQ_NONE;
outl(intcsr | 0x00ff0000, devpriv->iobase_a + AMCC_OP_REG_INTCSR);
if (intcsr & MASTER_ABORT_INT) {
dev_err(dev->class_dev, "AMCC IRQ - MASTER DMA ABORT!\n");
s->async->events |= COMEDI_CB_ERROR;
goto interrupt_exit;
}
if (intcsr & TARGET_ABORT_INT) {
dev_err(dev->class_dev, "AMCC IRQ - TARGET DMA ABORT!\n");
s->async->events |= COMEDI_CB_ERROR;
goto interrupt_exit;
}
adstat = inl(dev->iobase + PCI9118_AI_STATUS_REG);
if ((adstat & PCI9118_AI_STATUS_NFULL) == 0) {
dev_err(dev->class_dev,
"A/D FIFO Full status (Fatal Error!)\n");
s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW;
goto interrupt_exit;
}
if (adstat & PCI9118_AI_STATUS_BOVER) {
dev_err(dev->class_dev,
"A/D Burst Mode Overrun Status (Fatal Error!)\n");
s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW;
goto interrupt_exit;
}
if (adstat & PCI9118_AI_STATUS_ADOS) {
dev_err(dev->class_dev, "A/D Over Speed Status (Warning!)\n");
s->async->events |= COMEDI_CB_ERROR;
goto interrupt_exit;
}
if (adstat & PCI9118_AI_STATUS_ADOR) {
dev_err(dev->class_dev, "A/D Overrun Status (Fatal Error!)\n");
s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW;
goto interrupt_exit;
}
if (!devpriv->ai_do)
return IRQ_HANDLED;
if (devpriv->ai12_startstop) {
if ((adstat & PCI9118_AI_STATUS_DTH) &&
(intsrc & PCI9118_INT_CTRL_DTRG)) {
if (devpriv->ai12_startstop & START_AI_EXT) {
devpriv->ai12_startstop &= ~START_AI_EXT;
if (!(devpriv->ai12_startstop & STOP_AI_EXT))
pci9118_exttrg_enable(dev, false);
pci9118_start_pacer(dev, devpriv->ai_do);
outl(devpriv->ai_ctrl,
dev->iobase + PCI9118_AI_CTRL_REG);
} else if (devpriv->ai12_startstop & STOP_AI_EXT) {
devpriv->ai12_startstop &= ~STOP_AI_EXT;
pci9118_exttrg_enable(dev, false);
devpriv->ai_neverending = 0;
}
}
}
if (devpriv->usedma)
pci9118_ai_get_dma(dev, s);
else
pci9118_ai_get_onesample(dev, s);
interrupt_exit:
comedi_handle_events(dev, s);
return IRQ_HANDLED;
}
static void pci9118_ai_cmd_start(struct comedi_device *dev)
{
struct pci9118_private *devpriv = dev->private;
outl(devpriv->int_ctrl, dev->iobase + PCI9118_INT_CTRL_REG);
outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG);
if (devpriv->ai_do != 3) {
pci9118_start_pacer(dev, devpriv->ai_do);
devpriv->ai_ctrl |= PCI9118_AI_CTRL_SOFTG;
}
outl(devpriv->ai_ctrl, dev->iobase + PCI9118_AI_CTRL_REG);
}
static int pci9118_ai_inttrig(struct comedi_device *dev,
struct comedi_subdevice *s,
unsigned int trig_num)
{
struct comedi_cmd *cmd = &s->async->cmd;
if (trig_num != cmd->start_arg)
return -EINVAL;
s->async->inttrig = NULL;
pci9118_ai_cmd_start(dev);
return 1;
}
static int pci9118_ai_setup_dma(struct comedi_device *dev,
struct comedi_subdevice *s)
{
struct pci9118_private *devpriv = dev->private;
struct comedi_cmd *cmd = &s->async->cmd;
struct pci9118_dmabuf *dmabuf0 = &devpriv->dmabuf[0];
struct pci9118_dmabuf *dmabuf1 = &devpriv->dmabuf[1];
unsigned int dmalen0 = dmabuf0->size;
unsigned int dmalen1 = dmabuf1->size;
unsigned int scan_bytes = devpriv->ai_n_realscanlen *
comedi_bytes_per_sample(s);
if (dmalen0 > s->async->prealloc_bufsz) {
dmalen0 = s->async->prealloc_bufsz & ~3L;
}
if (dmalen1 > s->async->prealloc_bufsz) {
dmalen1 = s->async->prealloc_bufsz & ~3L;
}
if (devpriv->ai_flags & CMDF_WAKE_EOS) {
if (dmalen0 < scan_bytes) {
devpriv->ai_flags &= (~CMDF_WAKE_EOS);
dev_info(dev->class_dev,
"WAR: DMA0 buf too short, can't support CMDF_WAKE_EOS (%d<%d)\n",
dmalen0, scan_bytes);
} else {
dmalen0 = scan_bytes;
if (dmalen0 < 4) {
dev_info(dev->class_dev,
"ERR: DMA0 buf len bug? (%d<4)\n",
dmalen0);
dmalen0 = 4;
}
}
}
if (devpriv->ai_flags & CMDF_WAKE_EOS) {
if (dmalen1 < scan_bytes) {
devpriv->ai_flags &= (~CMDF_WAKE_EOS);
dev_info(dev->class_dev,
"WAR: DMA1 buf too short, can't support CMDF_WAKE_EOS (%d<%d)\n",
dmalen1, scan_bytes);
} else {
dmalen1 = scan_bytes;
if (dmalen1 < 4) {
dev_info(dev->class_dev,
"ERR: DMA1 buf len bug? (%d<4)\n",
dmalen1);
dmalen1 = 4;
}
}
}
if (!(devpriv->ai_flags & CMDF_WAKE_EOS)) {
unsigned int tmp;
tmp = dmalen0;
dmalen0 = (dmalen0 / scan_bytes) * scan_bytes;
dmalen0 &= ~3L;
if (!dmalen0)
dmalen0 = tmp;
tmp = dmalen1;
dmalen1 = (dmalen1 / scan_bytes) * scan_bytes;
dmalen1 &= ~3L;
if (!dmalen1)
dmalen1 = tmp;
if (!devpriv->ai_neverending) {
unsigned long long scanlen;
scanlen = (unsigned long long)scan_bytes *
cmd->stop_arg;
if (dmalen0 > scanlen) {
dmalen0 = scanlen;
dmalen0 &= ~3L;
} else {
if (dmalen1 > (scanlen - dmalen0)) {
dmalen1 = scanlen - dmalen0;
dmalen1 &= ~3L;
}
}
}
}
devpriv->dma_actbuf = 0;
dmabuf0->use_size = dmalen0;
dmabuf1->use_size = dmalen1;
pci9118_amcc_dma_ena(dev, false);
pci9118_amcc_setup_dma(dev, 0);
outl(0x00000000 | AINT_WRITE_COMPL,
devpriv->iobase_a + AMCC_OP_REG_INTCSR);
pci9118_amcc_dma_ena(dev, true);
outl(inl(devpriv->iobase_a + AMCC_OP_REG_INTCSR) | EN_A2P_TRANSFERS,
devpriv->iobase_a + AMCC_OP_REG_INTCSR);
return 0;
}
static int pci9118_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
{
struct pci9118_private *devpriv = dev->private;
struct comedi_8254 *pacer = dev->pacer;
struct comedi_cmd *cmd = &s->async->cmd;
unsigned int addchans = 0;
unsigned int scanlen;
devpriv->ai12_startstop = 0;
devpriv->ai_flags = cmd->flags;
devpriv->ai_add_front = 0;
devpriv->ai_add_back = 0;
if (cmd->start_src == TRIG_EXT)
devpriv->ai12_startstop |= START_AI_EXT;
if (cmd->stop_src == TRIG_EXT) {
devpriv->ai_neverending = 1;
devpriv->ai12_startstop |= STOP_AI_EXT;
}
if (cmd->stop_src == TRIG_NONE)
devpriv->ai_neverending = 1;
if (cmd->stop_src == TRIG_COUNT)
devpriv->ai_neverending = 0;
devpriv->ai_add_front = 0;
devpriv->ai_add_back = 0;
if (devpriv->master) {
devpriv->usedma = 1;
if ((cmd->flags & CMDF_WAKE_EOS) &&
(cmd->scan_end_arg == 1)) {
if (cmd->convert_src == TRIG_NOW)
devpriv->ai_add_back = 1;
if (cmd->convert_src == TRIG_TIMER) {
devpriv->usedma = 0;
}
}
if ((cmd->flags & CMDF_WAKE_EOS) &&
(cmd->scan_end_arg & 1) &&
(cmd->scan_end_arg > 1)) {
if (cmd->scan_begin_src == TRIG_FOLLOW) {
devpriv->usedma = 0;
} else {
devpriv->ai_add_back = 1;
}
}
} else {
devpriv->usedma = 0;
}
if (cmd->convert_src == TRIG_NOW && devpriv->softsshdelay) {
devpriv->ai_add_front = 2;
if ((devpriv->usedma == 1) && (devpriv->ai_add_back == 1)) {
devpriv->ai_add_front++;
devpriv->ai_add_back = 0;
}
if (cmd->convert_arg < devpriv->ai_ns_min)
cmd->convert_arg = devpriv->ai_ns_min;
addchans = devpriv->softsshdelay / cmd->convert_arg;
if (devpriv->softsshdelay % cmd->convert_arg)
addchans++;
if (addchans > (devpriv->ai_add_front - 1)) {
devpriv->ai_add_front = addchans + 1;
if (devpriv->usedma == 1)
if ((devpriv->ai_add_front +
cmd->chanlist_len +
devpriv->ai_add_back) & 1)
devpriv->ai_add_front++;
}
}
scanlen = devpriv->ai_add_front + cmd->chanlist_len +
devpriv->ai_add_back;
devpriv->ai_n_realscanlen = scanlen *
(cmd->scan_end_arg / cmd->chanlist_len);
if (scanlen > s->len_chanlist) {
dev_err(dev->class_dev,
"range/channel list is too long for actual configuration!\n");
return -EINVAL;
}
pci9118_set_chanlist(dev, s, cmd->chanlist_len, cmd->chanlist,
devpriv->ai_add_front, devpriv->ai_add_back);
devpriv->ai_do = 0;
if (cmd->scan_begin_src != TRIG_TIMER &&
cmd->convert_src == TRIG_TIMER) {
if (cmd->scan_begin_src == TRIG_EXT)
devpriv->ai_do = 4;
else
devpriv->ai_do = 1;
comedi_8254_cascade_ns_to_timer(pacer, &cmd->convert_arg,
devpriv->ai_flags &
CMDF_ROUND_NEAREST);
comedi_8254_update_divisors(pacer);
devpriv->ai_ctrl |= PCI9118_AI_CTRL_TMRTR;
if (!devpriv->usedma) {
devpriv->ai_ctrl |= PCI9118_AI_CTRL_INT;
devpriv->int_ctrl |= PCI9118_INT_CTRL_TIMER;
}
if (cmd->scan_begin_src == TRIG_EXT) {
struct pci9118_dmabuf *dmabuf = &devpriv->dmabuf[0];
devpriv->ai_cfg |= PCI9118_AI_CFG_AM;
outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG);
comedi_8254_load(pacer, 0, dmabuf->hw >> 1,
I8254_MODE0 | I8254_BINARY);
devpriv->ai_cfg |= PCI9118_AI_CFG_START;
}
}
if (cmd->scan_begin_src == TRIG_TIMER &&
cmd->convert_src != TRIG_EXT) {
if (!devpriv->usedma) {
dev_err(dev->class_dev,
"cmd->scan_begin_src=TRIG_TIMER works only with bus mastering!\n");
return -EIO;
}
devpriv->ai_do = 2;
pci9118_calc_divisors(dev, s,
&cmd->scan_begin_arg, &cmd->convert_arg,
devpriv->ai_flags,
devpriv->ai_n_realscanlen,
&pacer->divisor1,
&pacer->divisor2,
devpriv->ai_add_front);
devpriv->ai_ctrl |= PCI9118_AI_CTRL_TMRTR;
devpriv->ai_cfg |= PCI9118_AI_CFG_BM | PCI9118_AI_CFG_BS;
if (cmd->convert_src == TRIG_NOW && !devpriv->softsshdelay)
devpriv->ai_cfg |= PCI9118_AI_CFG_BSSH;
outl(devpriv->ai_n_realscanlen,
dev->iobase + PCI9118_AI_BURST_NUM_REG);
}
if (cmd->scan_begin_src == TRIG_FOLLOW &&
cmd->convert_src == TRIG_EXT) {
devpriv->ai_do = 3;
devpriv->ai_ctrl |= PCI9118_AI_CTRL_EXTM;
}
if (devpriv->ai_do == 0) {
dev_err(dev->class_dev,
"Unable to determine acquisition mode! BUG in (*do_cmdtest)?\n");
return -EINVAL;
}
if (devpriv->usedma)
devpriv->ai_ctrl |= PCI9118_AI_CTRL_DMA;
devpriv->ai_cfg = PCI9118_AI_CFG_PDTRG | PCI9118_AI_CFG_PETRG;
outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG);
udelay(1);
pci9118_ai_reset_fifo(dev);
inl(dev->iobase + PCI9118_AI_STATUS_REG);
inl(dev->iobase + PCI9118_INT_CTRL_REG);
devpriv->ai_act_dmapos = 0;
if (devpriv->usedma) {
pci9118_ai_setup_dma(dev, s);
outl(0x02000000 | AINT_WRITE_COMPL,
devpriv->iobase_a + AMCC_OP_REG_INTCSR);
} else {
pci9118_amcc_int_ena(dev, true);
}
if (cmd->start_src == TRIG_NOW)
pci9118_ai_cmd_start(dev);
else if (cmd->start_src == TRIG_INT)
s->async->inttrig = pci9118_ai_inttrig;
if (cmd->start_src == TRIG_EXT || cmd->stop_src == TRIG_EXT)
pci9118_exttrg_enable(dev, true);
return 0;
}
static int pci9118_ai_cmdtest(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_cmd *cmd)
{
struct pci9118_private *devpriv = dev->private;
int err = 0;
unsigned int flags;
unsigned int arg;
err |= comedi_check_trigger_src(&cmd->start_src,
TRIG_NOW | TRIG_EXT | TRIG_INT);
flags = TRIG_FOLLOW;
if (devpriv->master)
flags |= TRIG_TIMER | TRIG_EXT;
err |= comedi_check_trigger_src(&cmd->scan_begin_src, flags);
flags = TRIG_TIMER | TRIG_EXT;
if (devpriv->master)
flags |= TRIG_NOW;
err |= comedi_check_trigger_src(&cmd->convert_src, flags);
err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
err |= comedi_check_trigger_src(&cmd->stop_src,
TRIG_COUNT | TRIG_NONE | TRIG_EXT);
if (err)
return 1;
err |= comedi_check_trigger_is_unique(cmd->start_src);
err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
err |= comedi_check_trigger_is_unique(cmd->convert_src);
err |= comedi_check_trigger_is_unique(cmd->stop_src);
if (cmd->start_src == TRIG_EXT && cmd->scan_begin_src == TRIG_EXT)
err |= -EINVAL;
if ((cmd->scan_begin_src & (TRIG_TIMER | TRIG_EXT)) &&
(!(cmd->convert_src & (TRIG_TIMER | TRIG_NOW))))
err |= -EINVAL;
if ((cmd->scan_begin_src == TRIG_FOLLOW) &&
(!(cmd->convert_src & (TRIG_TIMER | TRIG_EXT))))
err |= -EINVAL;
if (cmd->stop_src == TRIG_EXT && cmd->scan_begin_src == TRIG_EXT)
err |= -EINVAL;
if (err)
return 2;
switch (cmd->start_src) {
case TRIG_NOW:
case TRIG_EXT:
err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
break;
case TRIG_INT:
break;
}
if (cmd->scan_begin_src & (TRIG_FOLLOW | TRIG_EXT))
err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
if ((cmd->scan_begin_src == TRIG_TIMER) &&
(cmd->convert_src == TRIG_TIMER) && (cmd->scan_end_arg == 1)) {
cmd->scan_begin_src = TRIG_FOLLOW;
cmd->convert_arg = cmd->scan_begin_arg;
cmd->scan_begin_arg = 0;
}
if (cmd->scan_begin_src == TRIG_TIMER) {
err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
devpriv->ai_ns_min);
}
if (cmd->scan_begin_src == TRIG_EXT) {
if (cmd->scan_begin_arg) {
cmd->scan_begin_arg = 0;
err |= -EINVAL;
err |= comedi_check_trigger_arg_max(&cmd->scan_end_arg,
65535);
}
}
if (cmd->convert_src & (TRIG_TIMER | TRIG_NOW)) {
err |= comedi_check_trigger_arg_min(&cmd->convert_arg,
devpriv->ai_ns_min);
}
if (cmd->convert_src == TRIG_EXT)
err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
if (cmd->stop_src == TRIG_COUNT)
err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
else
err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1);
err |= comedi_check_trigger_arg_min(&cmd->scan_end_arg,
cmd->chanlist_len);
if ((cmd->scan_end_arg % cmd->chanlist_len)) {
cmd->scan_end_arg =
cmd->chanlist_len * (cmd->scan_end_arg / cmd->chanlist_len);
err |= -EINVAL;
}
if (err)
return 3;
if (cmd->scan_begin_src == TRIG_TIMER) {
arg = cmd->scan_begin_arg;
comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags);
err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
}
if (cmd->convert_src & (TRIG_TIMER | TRIG_NOW)) {
arg = cmd->convert_arg;
comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags);
err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg);
if (cmd->scan_begin_src == TRIG_TIMER &&
cmd->convert_src == TRIG_NOW) {
if (cmd->convert_arg == 0) {
arg = devpriv->ai_ns_min *
(cmd->scan_end_arg + 2);
} else {
arg = cmd->convert_arg * cmd->chanlist_len;
}
err |= comedi_check_trigger_arg_min(
&cmd->scan_begin_arg, arg);
}
}
if (err)
return 4;
if (cmd->chanlist)
err |= pci9118_ai_check_chanlist(dev, s, cmd);
if (err)
return 5;
return 0;
}
static int pci9118_ai_eoc(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned long context)
{
unsigned int status;
status = inl(dev->iobase + PCI9118_AI_STATUS_REG);
if (status & PCI9118_AI_STATUS_ADRDY)
return 0;
return -EBUSY;
}
static void pci9118_ai_start_conv(struct comedi_device *dev)
{
outl(0, dev->iobase + PCI9118_SOFTTRG_REG);
}
static int pci9118_ai_insn_read(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned int *data)
{
struct pci9118_private *devpriv = dev->private;
unsigned int val;
int ret;
int i;
pci9118_set_chanlist(dev, s, 1, &insn->chanspec, 0, 0);
devpriv->ai_cfg = PCI9118_AI_CFG_PDTRG | PCI9118_AI_CFG_PETRG;
outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG);
pci9118_ai_reset_fifo(dev);
for (i = 0; i < insn->n; i++) {
pci9118_ai_start_conv(dev);
ret = comedi_timeout(dev, s, insn, pci9118_ai_eoc, 0);
if (ret)
return ret;
val = inl(dev->iobase + PCI9118_AI_FIFO_REG);
if (s->maxdata == 0xffff)
data[i] = (val & 0xffff) ^ 0x8000;
else
data[i] = (val >> 4) & 0xfff;
}
return insn->n;
}
static int pci9118_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 val = s->readback[chan];
int i;
for (i = 0; i < insn->n; i++) {
val = data[i];
outl(val, dev->iobase + PCI9118_AO_REG(chan));
}
s->readback[chan] = val;
return insn->n;
}
static int pci9118_di_insn_bits(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned int *data)
{
data[1] = inl(dev->iobase + PCI9118_DIO_REG) & 0xf;
return insn->n;
}
static int pci9118_do_insn_bits(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned int *data)
{
if (comedi_dio_update_state(s, data))
outl(s->state, dev->iobase + PCI9118_DIO_REG);
data[1] = s->state;
return insn->n;
}
static void pci9118_reset(struct comedi_device *dev)
{
outl(0, dev->iobase + PCI9118_INT_CTRL_REG);
outl(0, dev->iobase + PCI9118_AI_CTRL_REG);
outl(0, dev->iobase + PCI9118_AI_CFG_REG);
pci9118_ai_reset_fifo(dev);
inl(dev->iobase + PCI9118_INT_CTRL_REG);
inl(dev->iobase + PCI9118_AI_STATUS_REG);
outl(0, dev->iobase + PCI9118_AI_BURST_NUM_REG);
outl(1, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG);
outl(2, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG);
outl(2047, dev->iobase + PCI9118_AO_REG(0));
outl(2047, dev->iobase + PCI9118_AO_REG(1));
}
static struct pci_dev *pci9118_find_pci(struct comedi_device *dev,
struct comedi_devconfig *it)
{
struct pci_dev *pcidev = NULL;
int bus = it->options[0];
int slot = it->options[1];
for_each_pci_dev(pcidev) {
if (pcidev->vendor != PCI_VENDOR_ID_AMCC)
continue;
if (pcidev->device != 0x80d9)
continue;
if (bus || slot) {
if (pcidev->bus->number != bus ||
PCI_SLOT(pcidev->devfn) != slot)
continue;
}
return pcidev;
}
dev_err(dev->class_dev,
"no supported board found! (req. bus/slot : %d/%d)\n",
bus, slot);
return NULL;
}
static void pci9118_alloc_dma(struct comedi_device *dev)
{
struct pci9118_private *devpriv = dev->private;
struct pci9118_dmabuf *dmabuf;
int order;
int i;
for (i = 0; i < 2; i++) {
dmabuf = &devpriv->dmabuf[i];
for (order = 2; order >= 0; order--) {
dmabuf->virt =
dma_alloc_coherent(dev->hw_dev, PAGE_SIZE << order,
&dmabuf->hw, GFP_KERNEL);
if (dmabuf->virt)
break;
}
if (!dmabuf->virt)
break;
dmabuf->size = PAGE_SIZE << order;
if (i == 0)
devpriv->master = 1;
if (i == 1)
devpriv->dma_doublebuf = 1;
}
}
static void pci9118_free_dma(struct comedi_device *dev)
{
struct pci9118_private *devpriv = dev->private;
struct pci9118_dmabuf *dmabuf;
int i;
if (!devpriv)
return;
for (i = 0; i < 2; i++) {
dmabuf = &devpriv->dmabuf[i];
if (dmabuf->virt) {
dma_free_coherent(dev->hw_dev, dmabuf->size,
dmabuf->virt, dmabuf->hw);
}
}
}
static int pci9118_common_attach(struct comedi_device *dev,
int ext_mux, int softsshdelay)
{
const struct pci9118_boardinfo *board = dev->board_ptr;
struct pci_dev *pcidev = comedi_to_pci_dev(dev);
struct pci9118_private *devpriv;
struct comedi_subdevice *s;
int ret;
int i;
u16 u16w;
devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
if (!devpriv)
return -ENOMEM;
ret = comedi_pci_enable(dev);
if (ret)
return ret;
pci_set_master(pcidev);
devpriv->iobase_a = pci_resource_start(pcidev, 0);
dev->iobase = pci_resource_start(pcidev, 2);
dev->pacer = comedi_8254_init(dev->iobase + PCI9118_TIMER_BASE,
I8254_OSC_BASE_4MHZ, I8254_IO32, 0);
if (!dev->pacer)
return -ENOMEM;
pci9118_reset(dev);
if (pcidev->irq) {
ret = request_irq(pcidev->irq, pci9118_interrupt, IRQF_SHARED,
dev->board_name, dev);
if (ret == 0) {
dev->irq = pcidev->irq;
pci9118_alloc_dma(dev);
}
}
if (ext_mux > 0) {
if (ext_mux > 256)
ext_mux = 256;
if (softsshdelay > 0)
if (ext_mux > 128)
ext_mux = 128;
devpriv->usemux = 1;
} else {
devpriv->usemux = 0;
}
if (softsshdelay < 0) {
devpriv->softsshdelay = -softsshdelay;
devpriv->softsshsample = 0x80;
devpriv->softsshhold = 0x00;
} else {
devpriv->softsshdelay = softsshdelay;
devpriv->softsshsample = 0x00;
devpriv->softsshhold = 0x80;
}
pci_read_config_word(pcidev, PCI_COMMAND, &u16w);
pci_write_config_word(pcidev, PCI_COMMAND, u16w | 64);
ret = comedi_alloc_subdevices(dev, 4);
if (ret)
return ret;
s = &dev->subdevices[0];
s->type = COMEDI_SUBD_AI;
s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF;
s->n_chan = (devpriv->usemux) ? ext_mux : 16;
s->maxdata = board->ai_is_16bit ? 0xffff : 0x0fff;
s->range_table = board->is_hg ? &pci9118hg_ai_range
: &pci9118_ai_range;
s->insn_read = pci9118_ai_insn_read;
if (dev->irq) {
dev->read_subdev = s;
s->subdev_flags |= SDF_CMD_READ;
s->len_chanlist = 255;
s->do_cmdtest = pci9118_ai_cmdtest;
s->do_cmd = pci9118_ai_cmd;
s->cancel = pci9118_ai_cancel;
s->munge = pci9118_ai_munge;
}
if (s->maxdata == 0xffff) {
devpriv->ai_ns_min = 10000;
} else {
devpriv->ai_ns_min = 3000;
}
s = &dev->subdevices[1];
s->type = COMEDI_SUBD_AO;
s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
s->n_chan = 2;
s->maxdata = 0x0fff;
s->range_table = &range_bipolar10;
s->insn_write = pci9118_ao_insn_write;
ret = comedi_alloc_subdev_readback(s);
if (ret)
return ret;
for (i = 0; i < s->n_chan; i++)
s->readback[i] = 2047;
s = &dev->subdevices[2];
s->type = COMEDI_SUBD_DI;
s->subdev_flags = SDF_READABLE;
s->n_chan = 4;
s->maxdata = 1;
s->range_table = &range_digital;
s->insn_bits = pci9118_di_insn_bits;
s = &dev->subdevices[3];
s->type = COMEDI_SUBD_DO;
s->subdev_flags = SDF_WRITABLE;
s->n_chan = 4;
s->maxdata = 1;
s->range_table = &range_digital;
s->insn_bits = pci9118_do_insn_bits;
s->state = inl(dev->iobase + PCI9118_DIO_REG) >> 4;
return 0;
}
static int pci9118_attach(struct comedi_device *dev,
struct comedi_devconfig *it)
{
struct pci_dev *pcidev;
int ext_mux, softsshdelay;
ext_mux = it->options[2];
softsshdelay = it->options[4];
pcidev = pci9118_find_pci(dev, it);
if (!pcidev)
return -EIO;
comedi_set_hw_dev(dev, &pcidev->dev);
return pci9118_common_attach(dev, ext_mux, softsshdelay);
}
static int pci9118_auto_attach(struct comedi_device *dev,
unsigned long context)
{
struct pci_dev *pcidev = comedi_to_pci_dev(dev);
const struct pci9118_boardinfo *board = NULL;
if (context < ARRAY_SIZE(pci9118_boards))
board = &pci9118_boards[context];
if (!board)
return -ENODEV;
dev->board_ptr = board;
dev->board_name = board->name;
pci_dev_get(pcidev);
return pci9118_common_attach(dev, 0, 0);
}
static void pci9118_detach(struct comedi_device *dev)
{
struct pci_dev *pcidev = comedi_to_pci_dev(dev);
if (dev->iobase)
pci9118_reset(dev);
comedi_pci_detach(dev);
pci9118_free_dma(dev);
pci_dev_put(pcidev);
}
static struct comedi_driver adl_pci9118_driver = {
.driver_name = "adl_pci9118",
.module = THIS_MODULE,
.attach = pci9118_attach,
.auto_attach = pci9118_auto_attach,
.detach = pci9118_detach,
.num_names = ARRAY_SIZE(pci9118_boards),
.board_name = &pci9118_boards[0].name,
.offset = sizeof(struct pci9118_boardinfo),
};
static int adl_pci9118_pci_probe(struct pci_dev *dev,
const struct pci_device_id *id)
{
return comedi_pci_auto_config(dev, &adl_pci9118_driver,
id->driver_data);
}
static const struct pci_device_id adl_pci9118_pci_table[] = {
{ PCI_VDEVICE(AMCC, 0x80d9), BOARD_PCI9118DG },
{ 0 }
};
MODULE_DEVICE_TABLE(pci, adl_pci9118_pci_table);
static struct pci_driver adl_pci9118_pci_driver = {
.name = "adl_pci9118",
.id_table = adl_pci9118_pci_table,
.probe = adl_pci9118_pci_probe,
.remove = comedi_pci_auto_unconfig,
};
module_comedi_pci_driver(adl_pci9118_driver, adl_pci9118_pci_driver);
MODULE_AUTHOR("Comedi https://www.comedi.org");
MODULE_DESCRIPTION("Comedi low-level driver");
MODULE_LICENSE("GPL"