diff options
Diffstat (limited to 'kernel/drivers/staging/comedi/drivers/adl_pci9118.c')
-rw-r--r-- | kernel/drivers/staging/comedi/drivers/adl_pci9118.c | 1761 |
1 files changed, 1761 insertions, 0 deletions
diff --git a/kernel/drivers/staging/comedi/drivers/adl_pci9118.c b/kernel/drivers/staging/comedi/drivers/adl_pci9118.c new file mode 100644 index 000000000..fb3043dcf --- /dev/null +++ b/kernel/drivers/staging/comedi/drivers/adl_pci9118.c @@ -0,0 +1,1761 @@ +/* + * 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 cann't use mixture of unipolar/bipoar ranges or differencial/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 "../comedi_pci.h" + +#include "amcc_s5933.h" +#include "comedi_8254.h" + +#define IORANGE_9118 64 /* I hope */ +#define PCI9118_CHANLEN 255 /* + * len of chanlist, some source say 256, + * but reality looks like 255 :-( + */ + +/* + * 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 (1 << 8) /* 0=FIFO full (fatal) */ +#define PCI9118_AI_STATUS_NHFULL (1 << 7) /* 0=FIFO half full */ +#define PCI9118_AI_STATUS_NEPTY (1 << 6) /* 0=FIFO empty */ +#define PCI9118_AI_STATUS_ACMP (1 << 5) /* 1=about trigger complete */ +#define PCI9118_AI_STATUS_DTH (1 << 4) /* 1=ext. digital trigger */ +#define PCI9118_AI_STATUS_BOVER (1 << 3) /* 1=burst overrun (fatal) */ +#define PCI9118_AI_STATUS_ADOS (1 << 2) /* 1=A/D over speed (warn) */ +#define PCI9118_AI_STATUS_ADOR (1 << 1) /* 1=A/D overrun (fatal) */ +#define PCI9118_AI_STATUS_ADRDY (1 << 0) /* 1=A/D ready */ +#define PCI9118_AI_CTRL_REG 0x18 +#define PCI9118_AI_CTRL_UNIP (1 << 7) /* 1=unipolar */ +#define PCI9118_AI_CTRL_DIFF (1 << 6) /* 1=differential inputs */ +#define PCI9118_AI_CTRL_SOFTG (1 << 5) /* 1=8254 software gate */ +#define PCI9118_AI_CTRL_EXTG (1 << 4) /* 1=8254 TGIN(pin 46) gate */ +#define PCI9118_AI_CTRL_EXTM (1 << 3) /* 1=ext. trigger (pin 44) */ +#define PCI9118_AI_CTRL_TMRTR (1 << 2) /* 1=8254 is trigger source */ +#define PCI9118_AI_CTRL_INT (1 << 1) /* 1=enable interrupt */ +#define PCI9118_AI_CTRL_DMA (1 << 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 (1 << 7) /* 1=positive trigger */ +#define PCI9118_AI_CFG_PETRG (1 << 6) /* 1=positive ext. trigger */ +#define PCI9118_AI_CFG_BSSH (1 << 5) /* 1=with sample & hold */ +#define PCI9118_AI_CFG_BM (1 << 4) /* 1=burst mode */ +#define PCI9118_AI_CFG_BS (1 << 3) /* 1=burst mode start */ +#define PCI9118_AI_CFG_PM (1 << 2) /* 1=post trigger */ +#define PCI9118_AI_CFG_AM (1 << 1) /* 1=about trigger */ +#define PCI9118_AI_CFG_START (1 << 0) /* 1=trigger start */ +#define PCI9118_FIFO_RESET_REG 0x34 +#define PCI9118_INT_CTRL_REG 0x38 +#define PCI9118_INT_CTRL_TIMER (1 << 3) /* timer interrupt */ +#define PCI9118_INT_CTRL_ABOUT (1 << 2) /* about trigger complete */ +#define PCI9118_INT_CTRL_HFULL (1 << 1) /* A/D FIFO half full */ +#define PCI9118_INT_CTRL_DTRG (1 << 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 */ + +#define PCI9118_HALF_FIFO_SZ (1024 / 2) + +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) + } +}; + +#define PCI9118_BIPOLAR_RANGES 4 /* + * used for test on mixture + * of BIP/UNI ranges + */ + +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, + * numer 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 check_channel_list(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 i, differencial = 0, bipolar = 0; + + /* correct channel and range number check itself comedi/range.c */ + if (n_chan < 1) { + dev_err(dev->class_dev, "range/channel list is empty!\n"); + return 0; + } + if ((frontadd + n_chan + backadd) > s->len_chanlist) { + dev_err(dev->class_dev, + "range/channel list is too long for actual configuration!\n"); + return 0; + } + + if (CR_AREF(chanlist[0]) == AREF_DIFF) + differencial = 1; /* all input must be diff */ + if (CR_RANGE(chanlist[0]) < PCI9118_BIPOLAR_RANGES) + bipolar = 1; /* all input must be bipolar */ + if (n_chan > 1) + for (i = 1; i < n_chan; i++) { /* check S.E/diff */ + if ((CR_AREF(chanlist[i]) == AREF_DIFF) != + (differencial)) { + dev_err(dev->class_dev, + "Differential and single ended inputs can't be mixed!\n"); + return 0; + } + if ((CR_RANGE(chanlist[i]) < PCI9118_BIPOLAR_RANGES) != + (bipolar)) { + dev_err(dev->class_dev, + "Bipolar and unipolar ranges can't be mixed!\n"); + return 0; + } + if (!devpriv->usemux && differencial && + (CR_CHAN(chanlist[i]) >= (s->n_chan / 2))) { + dev_err(dev->class_dev, + "AREF_DIFF is only available for the first 8 channels!\n"); + return 0; + } + } + + return 1; +} + +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 interrupt_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 valid_samples_in_act_dma_buf(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 move_block_from_dma(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 acqusition 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; + + for (i = 0; i < num_samples; i++) { + if (devpriv->usedma) + array[i] = be16_to_cpu(array[i]); + if (s->maxdata == 0xffff) + array[i] ^= 0x8000; + else + array[i] = (array[i] >> 4) & 0x0fff; + } +} + +static void interrupt_pci9118_ai_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 interrupt_pci9118_ai_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 = valid_samples_in_act_dma_buf(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) { + interrupt_pci9118_ai_mode4_switch(dev, + devpriv->dma_actbuf); + } + } + + if (n_all) + move_block_from_dma(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) + interrupt_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) + interrupt_pci9118_ai_dma(dev, s); + else + interrupt_pci9118_ai_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 Compute_and_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, dmalen1, i; + + dmalen0 = dmabuf0->size; + dmalen1 = dmabuf1->size; + /* 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 < (devpriv->ai_n_realscanlen << 1)) { + /* 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, devpriv->ai_n_realscanlen << 1); + } else { + /* short first DMA buffer to one scan */ + dmalen0 = devpriv->ai_n_realscanlen << 1; + 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 < (devpriv->ai_n_realscanlen << 1)) { + /* 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, devpriv->ai_n_realscanlen << 1); + } else { + /* short second DMA buffer to one scan */ + dmalen1 = devpriv->ai_n_realscanlen << 1; + 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)) { + /* if it's possible then align DMA buffers to length of scan */ + i = dmalen0; + dmalen0 = + (dmalen0 / (devpriv->ai_n_realscanlen << 1)) * + (devpriv->ai_n_realscanlen << 1); + dmalen0 &= ~3L; + if (!dmalen0) + dmalen0 = i; /* uff. very long scan? */ + i = dmalen1; + dmalen1 = + (dmalen1 / (devpriv->ai_n_realscanlen << 1)) * + (devpriv->ai_n_realscanlen << 1); + dmalen1 &= ~3L; + if (!dmalen1) + dmalen1 = i; /* 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) { + /* fits whole measure into one DMA buffer? */ + if (dmalen0 > + ((devpriv->ai_n_realscanlen << 1) * + cmd->stop_arg)) { + dmalen0 = + (devpriv->ai_n_realscanlen << 1) * + cmd->stop_arg; + dmalen0 &= ~3L; + } else { /* + * fits whole measure into + * two DMA buffer? + */ + if (dmalen1 > + ((devpriv->ai_n_realscanlen << 1) * + cmd->stop_arg - dmalen0)) + dmalen1 = + (devpriv->ai_n_realscanlen << 1) * + cmd->stop_arg - 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; + + 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 */ + devpriv->ai_n_realscanlen = /* + * what we must take from card in real + * to have cmd->scan_end_arg on output? + */ + (devpriv->ai_add_front + cmd->chanlist_len + + devpriv->ai_add_back) * (cmd->scan_end_arg / + cmd->chanlist_len); + + /* check and setup channel list */ + if (!check_channel_list(dev, s, cmd->chanlist_len, + cmd->chanlist, devpriv->ai_add_front, + devpriv->ai_add_back)) + return -EINVAL; + + /* + * Configure analog input and load the chanlist. + * The acqusition control bits are enabled later. + */ + pci9118_set_chanlist(dev, s, cmd->chanlist_len, cmd->chanlist, + devpriv->ai_add_front, devpriv->ai_add_back); + + /* Determine acqusition 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 acqusition 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) { + Compute_and_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->start_src == TRIG_INT && cmd->scan_begin_src == TRIG_INT) + 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; + + if (cmd->chanlist) + if (!check_channel_list(dev, s, cmd->chanlist_len, + cmd->chanlist, 0, 0)) + return 5; /* incorrect channels list */ + + 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_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; /* 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 = PCI9118_CHANLEN; + 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); + if (pcidev) + 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 http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); |