Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/drivers/comedi/drivers/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 48 kB image not shown  

Quelle  adl_pci9118.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0
/*
 *  comedi/drivers/adl_pci9118.c
 *
 *  hardware driver for ADLink cards:
 *   card:   PCI-9118DG, PCI-9118HG, PCI-9118HR
 *   driver: pci9118dg,  pci9118hg,  pci9118hr
 *
 * Author: Michal Dobes <dobes@tesnet.cz>
 *
 */


/*
 * Driver: adl_pci9118
 * Description: Adlink PCI-9118DG, PCI-9118HG, PCI-9118HR
 * Author: Michal Dobes <dobes@tesnet.cz>
 * Devices: [ADLink] PCI-9118DG (pci9118dg), PCI-9118HG (pci9118hg),
 * PCI-9118HR (pci9118hr)
 * Status: works
 *
 * This driver supports AI, AO, DI and DO subdevices.
 * AI subdevice supports cmd and insn interface,
 * other subdevices support only insn interface.
 * For AI:
 * - If cmd->scan_begin_src=TRIG_EXT then trigger input is TGIN (pin 46).
 * - If cmd->convert_src=TRIG_EXT then trigger input is EXTTRG (pin 44).
 * - If cmd->start_src/stop_src=TRIG_EXT then trigger input is TGIN (pin 46).
 * - It is not necessary to have cmd.scan_end_arg=cmd.chanlist_len but
 * cmd.scan_end_arg modulo cmd.chanlist_len must by 0.
 * - If return value of cmdtest is 5 then you've bad channel list
 * (it isn't possible mixture S.E. and DIFF inputs or bipolar and unipolar
 * ranges).
 *
 * There are some hardware limitations:
 * a) You can't use mixture of unipolar/bipolar ranges or differential/single
 *  ended inputs.
 * b) DMA transfers must have the length aligned to two samples (32 bit),
 *  so there is some problems if cmd->chanlist_len is odd. This driver tries
 *  bypass this with adding one sample to the end of the every scan and discard
 *  it on output but this can't be used if cmd->scan_begin_src=TRIG_FOLLOW
 *  and is used flag CMDF_WAKE_EOS, then driver switch to interrupt driven mode
 *  with interrupt after every sample.
 * c) If isn't used DMA then you can use only mode where
 *  cmd->scan_begin_src=TRIG_FOLLOW.
 *
 * Configuration options:
 * [0] - PCI bus of device (optional)
 * [1] - PCI slot of device (optional)
 *  If bus/slot is not specified, then first available PCI
 *  card will be used.
 * [2] - 0= standard 8 DIFF/16 SE channels configuration
 *  n = external multiplexer connected, 1 <= n <= 256
 * [3] - ignored
 * [4] - sample&hold signal - card can generate signal for external S&H board
 *  0 = use SSHO(pin 45) signal is generated in onboard hardware S&H logic
 *  0 != use ADCHN7(pin 23) signal is generated from driver, number say how
 * long delay is requested in ns and sign polarity of the hold
 * (in this case external multiplexor can serve only 128 channels)
 * [5] - ignored
 */


/*
 * FIXME
 *
 * All the supported boards have the same PCI vendor and device IDs, so
 * auto-attachment of PCI devices will always find the first board type.
 *
 * Perhaps the boards have different subdevice IDs that we could use to
 * distinguish them?
 *
 * Need some device attributes so the board type can be corrected after
 * attachment if necessary, and possibly to set other options supported by
 * manual attachment.
 */


#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"

/*
 * PCI BAR2 Register map (dev->iobase)
 */

#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; /* virtual address of buffer */
 dma_addr_t hw;  /* hardware (bus) address of buffer */
 unsigned int size; /* size of dma buffer in bytes */
 unsigned int use_size; /* which size we may now use for transfer */
};

struct pci9118_private {
 unsigned long iobase_a; /* base+size for AMCC chip */
 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;  /* what do AI? 0=nothing, 1 to 4 mode */
 unsigned int ai_n_realscanlen; /*
 * what we must transfer for one
 * outgoing scan include front/back adds
 */

 unsigned int ai_act_dmapos; /* position in actual real stream */
 unsigned int ai_add_front; /*
 * how many channels we must add
 * before scan to satisfy S&H?
 */

 unsigned int ai_add_back; /*
 * how many channels we must add
 * before scan to satisfy DMA?
 */

 unsigned int ai_flags;
 char ai12_startstop;  /*
 * measure can start/stop
 * on external trigger
 */

 unsigned int dma_actbuf;  /* which buffer is used now */
 struct pci9118_dmabuf dmabuf[2];
 int softsshdelay;  /*
 * >0 use software S&H,
 * number is requested delay in ns
 */

 unsigned char softsshsample; /*
 * polarity of S&H signal
 * in sample state
 */

 unsigned char softsshhold; /*
 * polarity of S&H signal
 * in hold state
 */

 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];

 /* set the master write address and transfer count */
 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;

 /* enable/disable interrupt for AMCC Incoming Mailbox 4 (32-bit) */
 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)
{
 /* writing any value resets the A/D FIFO */
 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;

 /* single channel scans are always ok */
 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;

 /*
 * Configure analog input based on the first chanlist entry.
 * All entries are either unipolar or bipolar and single-ended
 * or differential.
 */

 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);

 /* gods know why this sequence! */
 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);

 /* insert channels for S&H */
 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;
  }
 }

 /* store chanlist */
 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);
 }

 /* insert channels to fit onto 32bit DMA */
 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);
 }
 /* close scan queue */
 outl(0, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG);
 /* udelay(100); important delay, or first sample will be crippled */
}

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; /* use all samples */

 /*
 * Not all samples are to be used.  Buffer contents consist of a
 * possibly non-whole number of spans and a region of each span
 * is to be used.
 *
 * Account for samples in whole number of spans.
 */

 whole_spans = n_raw_samples / span_len;
 n_samples = whole_spans * cmd->chanlist_len;
 n_raw_samples -= whole_spans * span_len;

 /*
 * Deal with remaining samples which could overlap up to two spans.
 */

 while (n_raw_samples) {
  if (dma_pos < start_pos) {
   /* Skip samples before start position. */
   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) {
   /* Include samples before stop position. */
   x = stop_pos - dma_pos;
   if (x > n_raw_samples)
    x = n_raw_samples;
   n_samples += x;
   dma_pos += x;
   n_raw_samples -= x;
  }
  /* Advance to next span. */
  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) {
  /* All samples are to be copied. */
  comedi_buf_write_samples(s, dma_buffer, n_raw_samples);
  dma_pos += n_raw_samples;
 } else {
  /*
 * Not all samples are to be copied.  Buffer contents consist
 * of a possibly non-whole number of spans and a region of
 * each span is to be copied.
 */

  while (n_raw_samples) {
   if (dma_pos < start_pos) {
    /* Skip samples before start position. */
    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) {
    /* Copy samples before stop position. */
    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;
   }
   /* Advance to next span. */
   start_pos += span_len;
   stop_pos += span_len;
  }
 }
 /* Update position in span for next time. */
 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; /* convert timer (burst) */
 *div2 = *tim1 / pacer->osc_base; /* scan timer */
 *div2 = *div2 / *div1;   /* major timer is c1*c2 */
 if (*div2 < chans)
  *div2 = chans;

 *tim2 = *div1 * pacer->osc_base; /* real convert timer */

 if (cmd->convert_src == TRIG_NOW && !chnsshfront) {
  /* use BSSH signal */
  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);
 /* set default config (disable burst and triggers) */
 devpriv->ai_cfg = PCI9118_AI_CFG_PDTRG | PCI9118_AI_CFG_PETRG;
 outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG);
 /* reset acquisition control */
 devpriv->ai_ctrl = 0;
 outl(devpriv->ai_ctrl, dev->iobase + PCI9118_AI_CTRL_REG);
 outl(0, dev->iobase + PCI9118_AI_BURST_NUM_REG);
 /* reset scan queue */
 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;

 /* determine whether more DMA buffers to do after this one */
 n_valid = pci9118_ai_samples_ready(dev, s, n_all);
 more_dma = n_valid < comedi_nsamples_left(s, n_valid + 1);

 /* switch DMA buffers and restart DMA if double buffering */
 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;

 /* restart DMA if not double buffering */
 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; /* IRQ reasons from card */
 unsigned int intcsr; /* INT register from AMCC chip */
 unsigned int adstat; /* STATUS register */

 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)) {
   /* start/stop of measure */
   if (devpriv->ai12_startstop & START_AI_EXT) {
    /* deactivate EXT trigger */
    devpriv->ai12_startstop &= ~START_AI_EXT;
    if (!(devpriv->ai12_startstop & STOP_AI_EXT))
     pci9118_exttrg_enable(dev, false);

    /* start pacer */
    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) {
    /* deactivate EXT trigger */
    devpriv->ai12_startstop &= ~STOP_AI_EXT;
    pci9118_exttrg_enable(dev, false);

    /* on next interrupt measure will stop */
    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);

 /* isn't output buff smaller that our DMA buff? */
 if (dmalen0 > s->async->prealloc_bufsz) {
  /* align to 32bit down */
  dmalen0 = s->async->prealloc_bufsz & ~3L;
 }
 if (dmalen1 > s->async->prealloc_bufsz) {
  /* align to 32bit down */
  dmalen1 = s->async->prealloc_bufsz & ~3L;
 }

 /* we want wake up every scan? */
 if (devpriv->ai_flags & CMDF_WAKE_EOS) {
  if (dmalen0 < scan_bytes) {
   /* uff, too short DMA buffer, disable EOS support! */
   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 {
   /* short first DMA buffer to one scan */
   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) {
   /* uff, too short DMA buffer, disable EOS support! */
   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 {
   /* short second DMA buffer to one scan */
   dmalen1 = scan_bytes;
   if (dmalen1 < 4) {
    dev_info(dev->class_dev,
      "ERR: DMA1 buf len bug? (%d<4)\n",
      dmalen1);
    dmalen1 = 4;
   }
  }
 }

 /* transfer without CMDF_WAKE_EOS */
 if (!(devpriv->ai_flags & CMDF_WAKE_EOS)) {
  unsigned int tmp;

  /* if it's possible then align DMA buffers to length of scan */
  tmp = dmalen0;
  dmalen0 = (dmalen0 / scan_bytes) * scan_bytes;
  dmalen0 &= ~3L;
  if (!dmalen0)
   dmalen0 = tmp; /* uff. very long scan? */
  tmp = dmalen1;
  dmalen1 = (dmalen1 / scan_bytes) * scan_bytes;
  dmalen1 &= ~3L;
  if (!dmalen1)
   dmalen1 = tmp; /* uff. very long scan? */
  /*
 * if measure isn't neverending then test, if it fits whole
 * into one or two DMA buffers
 */

  if (!devpriv->ai_neverending) {
   unsigned long long scanlen;

   scanlen = (unsigned long long)scan_bytes *
      cmd->stop_arg;

   /* fits whole measure into one DMA buffer? */
   if (dmalen0 > scanlen) {
    dmalen0 = scanlen;
    dmalen0 &= ~3L;
   } else {
    /* fits whole measure into two DMA buffer? */
    if (dmalen1 > (scanlen - dmalen0)) {
     dmalen1 = scanlen - dmalen0;
     dmalen1 &= ~3L;
    }
   }
  }
 }

 /* these DMA buffer size will be used */
 devpriv->dma_actbuf = 0;
 dmabuf0->use_size = dmalen0;
 dmabuf1->use_size = dmalen1;

 pci9118_amcc_dma_ena(dev, false);
 pci9118_amcc_setup_dma(dev, 0);
 /* init DMA transfer */
 outl(0x00000000 | AINT_WRITE_COMPL,
      devpriv->iobase_a + AMCC_OP_REG_INTCSR);
/* outl(0x02000000|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);
      /* allow bus mastering */

 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;

 /* prepare for start/stop conditions */
 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;

 /*
 * use additional sample at end of every scan
 * to satisty DMA 32 bit transfer?
 */

 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;
     /*
 * use INT transfer if scanlist
 * have only one channel
 */

   }
  }
  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;
    /*
 * XXX maybe can be corrected to use 16 bit DMA
 */

   } else { /*
 * well, we must insert one sample
 * to end of EOS to meet 32 bit transfer
 */

    devpriv->ai_add_back = 1;
   }
  }
 } else { /* interrupt transfer don't need any correction */
  devpriv->usedma = 0;
 }

 /*
 * we need software S&H signal?
 * It adds two samples before every scan as minimum
 */

 if (cmd->convert_src == TRIG_NOW && devpriv->softsshdelay) {
  devpriv->ai_add_front = 2;
  if ((devpriv->usedma == 1) && (devpriv->ai_add_back == 1)) {
       /* move it to front */
   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)) {
       /* uff, still short */
   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++;
       /* round up to 32 bit */
  }
 }
 /* well, we now know what must be all added */
 scanlen = devpriv->ai_add_front + cmd->chanlist_len +
    devpriv->ai_add_back;
 /*
 * what we must take from card in real to have cmd->scan_end_arg
 * on output?
 */

 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;
 }

 /*
 * Configure analog input and load the chanlist.
 * The acquisition control bits are enabled later.
 */

 pci9118_set_chanlist(dev, s, cmd->chanlist_len, cmd->chanlist,
        devpriv->ai_add_front, devpriv->ai_add_back);

 /* Determine acquisition mode and calculate timing */
 devpriv->ai_do = 0;
 if (cmd->scan_begin_src != TRIG_TIMER &&
     cmd->convert_src == TRIG_TIMER) {
  /* cascaded timers 1 and 2 are used for convert timing */
  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;
  }

  /* double timed action */
  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) {
  /* external trigger conversion */
  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;

 /* set default config (disable burst and triggers) */
 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);

 /* clear A/D and INT status registers */
 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);
 }

 /* start async command now or wait for internal trigger */
 if (cmd->start_src == TRIG_NOW)
  pci9118_ai_cmd_start(dev);
 else if (cmd->start_src == TRIG_INT)
  s->async->inttrig = pci9118_ai_inttrig;

 /* enable external trigger for command start/stop */
 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;

 /* Step 1 : check if triggers are trivially valid */

 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;

 /* Step 2a : make sure trigger sources are unique */

 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);

 /* Step 2b : and mutually compatible */

 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;

 /* Step 3: check if arguments are trivially valid */

 switch (cmd->start_src) {
 case TRIG_NOW:
 case TRIG_EXT:
  err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
  break;
 case TRIG_INT:
  /* start_arg is the internal trigger (any value) */
  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 /* TRIG_NONE */
  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;

 /* step 4: fix up any arguments */

 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;

 /* Step 5: check channel list if it exists */

 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)
{
 /* writing any value triggers an A/D conversion */
 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;

       /*
* Configure analog input based on the chanspec.
* Acqusition is software controlled without interrupts.
*/

 pci9118_set_chanlist(dev, s, 1, &insn->chanspec, 0, 0);

 /* set default config (disable burst and triggers) */
 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)
{
 /*
 * The digital inputs and outputs share the read register.
 * bits [7:4] are the digital outputs
 * bits [3:0] are the digital inputs
 */

 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)
{
 /*
 * The digital outputs are set with the same register that
 * the digital inputs and outputs are read from. But the
 * outputs are set with bits [3:0] so we can simply write
 * the s->state to set them.
 */

 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)
{
 /* reset analog input subsystem */
 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);

 /* clear any pending interrupts and status */
 inl(dev->iobase + PCI9118_INT_CTRL_REG);
 inl(dev->iobase + PCI9118_AI_STATUS_REG);

 /* reset DMA and scan queue */
 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);

 /* reset analog outputs to 0V */
 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) {
   /* requested particular 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_io_alloc(dev->iobase + PCI9118_TIMER_BASE,
       I8254_OSC_BASE_4MHZ, I8254_IO32, 0);
 if (IS_ERR(dev->pacer))
  return PTR_ERR(dev->pacer);

 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; /* max 256 channels! */
  if (softsshdelay > 0)
   if (ext_mux > 128)
    ext_mux = 128;
  devpriv->usemux = 1;
 } else {
  devpriv->usemux = 0;
 }

 if (softsshdelay < 0) {
  /* select sample&hold signal polarity */
  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);
    /* Enable parity check for parity error */

 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_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) {
  /*
 * 16-bit samples are from an ADS7805 A/D converter.
 * Minimum sampling rate is 10us.
 */

  devpriv->ai_ns_min = 10000;
 } else {
  /*
 * 12-bit samples are from an ADS7800 A/D converter.
 * Minimum sampling rate is 3us.
 */

  devpriv->ai_ns_min = 3000;
 }

 /* Analog Output subdevice */
 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;

 /* the analog outputs were reset to 0V, make the readback match */
 for (i = 0; i < s->n_chan; i++)
  s->readback[i] = 2047;

 /* Digital Input subdevice */
 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;

 /* Digital Output subdevice */
 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;

 /* get the current state of the digital outputs */
 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;

 /*
 * Need to 'get' the PCI device to match the 'put' in pci9118_detach().
 * (The 'put' also matches the implicit 'get' by pci9118_find_pci().)
 */

 pci_dev_get(pcidev);
 /* no external mux, no sample-hold delay */
 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);
}

/* FIXME: All the supported board types have the same device ID! */
static const struct pci_device_id adl_pci9118_pci_table[] = {
 { PCI_VDEVICE(AMCC, 0x80d9), BOARD_PCI9118DG },
/* { PCI_VDEVICE(AMCC, 0x80d9), BOARD_PCI9118HG }, */
/* { PCI_VDEVICE(AMCC, 0x80d9), BOARD_PCI9118HR }, */
 { 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");

Messung V0.5
C=93 H=97 G=94

¤ Dauer der Verarbeitung: 0.17 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.