| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * addi_apci_3120.c |
| * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. |
| * |
| * ADDI-DATA GmbH |
| * Dieselstrasse 3 |
| * D-77833 Ottersweier |
| * Tel: +19(0)7223/9493-0 |
| * Fax: +49(0)7223/9493-92 |
| * http://www.addi-data.com |
| * info@addi-data.com |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/interrupt.h> |
| |
| #include "../comedi_pci.h" |
| #include "amcc_s5933.h" |
| |
| /* |
| * PCI BAR 0 register map (devpriv->amcc) |
| * see amcc_s5933.h for register and bit defines |
| */ |
| #define APCI3120_FIFO_ADVANCE_ON_BYTE_2 BIT(29) |
| |
| /* |
| * PCI BAR 1 register map (dev->iobase) |
| */ |
| #define APCI3120_AI_FIFO_REG 0x00 |
| #define APCI3120_CTRL_REG 0x00 |
| #define APCI3120_CTRL_EXT_TRIG BIT(15) |
| #define APCI3120_CTRL_GATE(x) BIT(12 + (x)) |
| #define APCI3120_CTRL_PR(x) (((x) & 0xf) << 8) |
| #define APCI3120_CTRL_PA(x) (((x) & 0xf) << 0) |
| #define APCI3120_AI_SOFTTRIG_REG 0x02 |
| #define APCI3120_STATUS_REG 0x02 |
| #define APCI3120_STATUS_EOC_INT BIT(15) |
| #define APCI3120_STATUS_AMCC_INT BIT(14) |
| #define APCI3120_STATUS_EOS_INT BIT(13) |
| #define APCI3120_STATUS_TIMER2_INT BIT(12) |
| #define APCI3120_STATUS_INT_MASK (0xf << 12) |
| #define APCI3120_STATUS_TO_DI_BITS(x) (((x) >> 8) & 0xf) |
| #define APCI3120_STATUS_TO_VERSION(x) (((x) >> 4) & 0xf) |
| #define APCI3120_STATUS_FIFO_FULL BIT(2) |
| #define APCI3120_STATUS_FIFO_EMPTY BIT(1) |
| #define APCI3120_STATUS_DA_READY BIT(0) |
| #define APCI3120_TIMER_REG 0x04 |
| #define APCI3120_CHANLIST_REG 0x06 |
| #define APCI3120_CHANLIST_INDEX(x) (((x) & 0xf) << 8) |
| #define APCI3120_CHANLIST_UNIPOLAR BIT(7) |
| #define APCI3120_CHANLIST_GAIN(x) (((x) & 0x3) << 4) |
| #define APCI3120_CHANLIST_MUX(x) (((x) & 0xf) << 0) |
| #define APCI3120_AO_REG(x) (0x08 + (((x) / 4) * 2)) |
| #define APCI3120_AO_MUX(x) (((x) & 0x3) << 14) |
| #define APCI3120_AO_DATA(x) ((x) << 0) |
| #define APCI3120_TIMER_MODE_REG 0x0c |
| #define APCI3120_TIMER_MODE(_t, _m) ((_m) << ((_t) * 2)) |
| #define APCI3120_TIMER_MODE0 0 /* I8254_MODE0 */ |
| #define APCI3120_TIMER_MODE2 1 /* I8254_MODE2 */ |
| #define APCI3120_TIMER_MODE4 2 /* I8254_MODE4 */ |
| #define APCI3120_TIMER_MODE5 3 /* I8254_MODE5 */ |
| #define APCI3120_TIMER_MODE_MASK(_t) (3 << ((_t) * 2)) |
| #define APCI3120_CTR0_REG 0x0d |
| #define APCI3120_CTR0_DO_BITS(x) ((x) << 4) |
| #define APCI3120_CTR0_TIMER_SEL(x) ((x) << 0) |
| #define APCI3120_MODE_REG 0x0e |
| #define APCI3120_MODE_TIMER2_CLK(x) (((x) & 0x3) << 6) |
| #define APCI3120_MODE_TIMER2_CLK_OSC APCI3120_MODE_TIMER2_CLK(0) |
| #define APCI3120_MODE_TIMER2_CLK_OUT1 APCI3120_MODE_TIMER2_CLK(1) |
| #define APCI3120_MODE_TIMER2_CLK_EOC APCI3120_MODE_TIMER2_CLK(2) |
| #define APCI3120_MODE_TIMER2_CLK_EOS APCI3120_MODE_TIMER2_CLK(3) |
| #define APCI3120_MODE_TIMER2_CLK_MASK APCI3120_MODE_TIMER2_CLK(3) |
| #define APCI3120_MODE_TIMER2_AS(x) (((x) & 0x3) << 4) |
| #define APCI3120_MODE_TIMER2_AS_TIMER APCI3120_MODE_TIMER2_AS(0) |
| #define APCI3120_MODE_TIMER2_AS_COUNTER APCI3120_MODE_TIMER2_AS(1) |
| #define APCI3120_MODE_TIMER2_AS_WDOG APCI3120_MODE_TIMER2_AS(2) |
| #define APCI3120_MODE_TIMER2_AS_MASK APCI3120_MODE_TIMER2_AS(3) |
| #define APCI3120_MODE_SCAN_ENA BIT(3) |
| #define APCI3120_MODE_TIMER2_IRQ_ENA BIT(2) |
| #define APCI3120_MODE_EOS_IRQ_ENA BIT(1) |
| #define APCI3120_MODE_EOC_IRQ_ENA BIT(0) |
| |
| /* |
| * PCI BAR 2 register map (devpriv->addon) |
| */ |
| #define APCI3120_ADDON_ADDR_REG 0x00 |
| #define APCI3120_ADDON_DATA_REG 0x02 |
| #define APCI3120_ADDON_CTRL_REG 0x04 |
| #define APCI3120_ADDON_CTRL_AMWEN_ENA BIT(1) |
| #define APCI3120_ADDON_CTRL_A2P_FIFO_ENA BIT(0) |
| |
| /* |
| * Board revisions |
| */ |
| #define APCI3120_REVA 0xa |
| #define APCI3120_REVB 0xb |
| #define APCI3120_REVA_OSC_BASE 70 /* 70ns = 14.29MHz */ |
| #define APCI3120_REVB_OSC_BASE 50 /* 50ns = 20MHz */ |
| |
| static const struct comedi_lrange apci3120_ai_range = { |
| 8, { |
| BIP_RANGE(10), |
| BIP_RANGE(5), |
| BIP_RANGE(2), |
| BIP_RANGE(1), |
| UNI_RANGE(10), |
| UNI_RANGE(5), |
| UNI_RANGE(2), |
| UNI_RANGE(1) |
| } |
| }; |
| |
| enum apci3120_boardid { |
| BOARD_APCI3120, |
| BOARD_APCI3001, |
| }; |
| |
| struct apci3120_board { |
| const char *name; |
| unsigned int ai_is_16bit:1; |
| unsigned int has_ao:1; |
| }; |
| |
| static const struct apci3120_board apci3120_boardtypes[] = { |
| [BOARD_APCI3120] = { |
| .name = "apci3120", |
| .ai_is_16bit = 1, |
| .has_ao = 1, |
| }, |
| [BOARD_APCI3001] = { |
| .name = "apci3001", |
| }, |
| }; |
| |
| struct apci3120_dmabuf { |
| unsigned short *virt; |
| dma_addr_t hw; |
| unsigned int size; |
| unsigned int use_size; |
| }; |
| |
| struct apci3120_private { |
| unsigned long amcc; |
| unsigned long addon; |
| unsigned int osc_base; |
| unsigned int use_dma:1; |
| unsigned int use_double_buffer:1; |
| unsigned int cur_dmabuf:1; |
| struct apci3120_dmabuf dmabuf[2]; |
| unsigned char do_bits; |
| unsigned char timer_mode; |
| unsigned char mode; |
| unsigned short ctrl; |
| }; |
| |
| static void apci3120_addon_write(struct comedi_device *dev, |
| unsigned int val, unsigned int reg) |
| { |
| struct apci3120_private *devpriv = dev->private; |
| |
| /* 16-bit interface for AMCC add-on registers */ |
| |
| outw(reg, devpriv->addon + APCI3120_ADDON_ADDR_REG); |
| outw(val & 0xffff, devpriv->addon + APCI3120_ADDON_DATA_REG); |
| |
| outw(reg + 2, devpriv->addon + APCI3120_ADDON_ADDR_REG); |
| outw((val >> 16) & 0xffff, devpriv->addon + APCI3120_ADDON_DATA_REG); |
| } |
| |
| static void apci3120_init_dma(struct comedi_device *dev, |
| struct apci3120_dmabuf *dmabuf) |
| { |
| struct apci3120_private *devpriv = dev->private; |
| |
| /* AMCC - enable transfer count and reset A2P FIFO */ |
| outl(AGCSTS_TC_ENABLE | AGCSTS_RESET_A2P_FIFO, |
| devpriv->amcc + AMCC_OP_REG_AGCSTS); |
| |
| /* Add-On - enable transfer count and reset A2P FIFO */ |
| apci3120_addon_write(dev, AGCSTS_TC_ENABLE | AGCSTS_RESET_A2P_FIFO, |
| AMCC_OP_REG_AGCSTS); |
| |
| /* AMCC - enable transfers and reset A2P flags */ |
| outl(RESET_A2P_FLAGS | EN_A2P_TRANSFERS, |
| devpriv->amcc + AMCC_OP_REG_MCSR); |
| |
| /* Add-On - DMA start address */ |
| apci3120_addon_write(dev, dmabuf->hw, AMCC_OP_REG_AMWAR); |
| |
| /* Add-On - Number of acquisitions */ |
| apci3120_addon_write(dev, dmabuf->use_size, AMCC_OP_REG_AMWTC); |
| |
| /* AMCC - enable write complete (DMA) and set FIFO advance */ |
| outl(APCI3120_FIFO_ADVANCE_ON_BYTE_2 | AINT_WRITE_COMPL, |
| devpriv->amcc + AMCC_OP_REG_INTCSR); |
| |
| /* Add-On - enable DMA */ |
| outw(APCI3120_ADDON_CTRL_AMWEN_ENA | APCI3120_ADDON_CTRL_A2P_FIFO_ENA, |
| devpriv->addon + APCI3120_ADDON_CTRL_REG); |
| } |
| |
| static void apci3120_setup_dma(struct comedi_device *dev, |
| struct comedi_subdevice *s) |
| { |
| struct apci3120_private *devpriv = dev->private; |
| struct comedi_cmd *cmd = &s->async->cmd; |
| struct apci3120_dmabuf *dmabuf0 = &devpriv->dmabuf[0]; |
| struct apci3120_dmabuf *dmabuf1 = &devpriv->dmabuf[1]; |
| unsigned int dmalen0 = dmabuf0->size; |
| unsigned int dmalen1 = dmabuf1->size; |
| unsigned int scan_bytes; |
| |
| scan_bytes = comedi_samples_to_bytes(s, cmd->scan_end_arg); |
| |
| if (cmd->stop_src == TRIG_COUNT) { |
| /* |
| * Must we fill full first buffer? And must we fill |
| * full second buffer when first is once filled? |
| */ |
| if (dmalen0 > (cmd->stop_arg * scan_bytes)) |
| dmalen0 = cmd->stop_arg * scan_bytes; |
| else if (dmalen1 > (cmd->stop_arg * scan_bytes - dmalen0)) |
| dmalen1 = cmd->stop_arg * scan_bytes - dmalen0; |
| } |
| |
| if (cmd->flags & CMDF_WAKE_EOS) { |
| /* don't we want wake up every scan? */ |
| if (dmalen0 > scan_bytes) { |
| dmalen0 = scan_bytes; |
| if (cmd->scan_end_arg & 1) |
| dmalen0 += 2; |
| } |
| if (dmalen1 > scan_bytes) { |
| dmalen1 = scan_bytes; |
| if (cmd->scan_end_arg & 1) |
| dmalen1 -= 2; |
| if (dmalen1 < 4) |
| dmalen1 = 4; |
| } |
| } else { |
| /* isn't output buff smaller that our DMA buff? */ |
| if (dmalen0 > s->async->prealloc_bufsz) |
| dmalen0 = s->async->prealloc_bufsz; |
| if (dmalen1 > s->async->prealloc_bufsz) |
| dmalen1 = s->async->prealloc_bufsz; |
| } |
| dmabuf0->use_size = dmalen0; |
| dmabuf1->use_size = dmalen1; |
| |
| apci3120_init_dma(dev, dmabuf0); |
| } |
| |
| /* |
| * There are three timers on the board. They all use the same base |
| * clock with a fixed prescaler for each timer. The base clock used |
| * depends on the board version and type. |
| * |
| * APCI-3120 Rev A boards OSC = 14.29MHz base clock (~70ns) |
| * APCI-3120 Rev B boards OSC = 20MHz base clock (50ns) |
| * APCI-3001 boards OSC = 20MHz base clock (50ns) |
| * |
| * The prescalers for each timer are: |
| * Timer 0 CLK = OSC/10 |
| * Timer 1 CLK = OSC/1000 |
| * Timer 2 CLK = OSC/1000 |
| */ |
| static unsigned int apci3120_ns_to_timer(struct comedi_device *dev, |
| unsigned int timer, |
| unsigned int ns, |
| unsigned int flags) |
| { |
| struct apci3120_private *devpriv = dev->private; |
| unsigned int prescale = (timer == 0) ? 10 : 1000; |
| unsigned int timer_base = devpriv->osc_base * prescale; |
| unsigned int divisor; |
| |
| switch (flags & CMDF_ROUND_MASK) { |
| case CMDF_ROUND_UP: |
| divisor = DIV_ROUND_UP(ns, timer_base); |
| break; |
| case CMDF_ROUND_DOWN: |
| divisor = ns / timer_base; |
| break; |
| case CMDF_ROUND_NEAREST: |
| default: |
| divisor = DIV_ROUND_CLOSEST(ns, timer_base); |
| break; |
| } |
| |
| if (timer == 2) { |
| /* timer 2 is 24-bits */ |
| if (divisor > 0x00ffffff) |
| divisor = 0x00ffffff; |
| } else { |
| /* timers 0 and 1 are 16-bits */ |
| if (divisor > 0xffff) |
| divisor = 0xffff; |
| } |
| /* the timers require a minimum divisor of 2 */ |
| if (divisor < 2) |
| divisor = 2; |
| |
| return divisor; |
| } |
| |
| static void apci3120_clr_timer2_interrupt(struct comedi_device *dev) |
| { |
| /* a dummy read of APCI3120_CTR0_REG clears the timer 2 interrupt */ |
| inb(dev->iobase + APCI3120_CTR0_REG); |
| } |
| |
| static void apci3120_timer_write(struct comedi_device *dev, |
| unsigned int timer, unsigned int val) |
| { |
| struct apci3120_private *devpriv = dev->private; |
| |
| /* write 16-bit value to timer (lower 16-bits of timer 2) */ |
| outb(APCI3120_CTR0_DO_BITS(devpriv->do_bits) | |
| APCI3120_CTR0_TIMER_SEL(timer), |
| dev->iobase + APCI3120_CTR0_REG); |
| outw(val & 0xffff, dev->iobase + APCI3120_TIMER_REG); |
| |
| if (timer == 2) { |
| /* write upper 16-bits to timer 2 */ |
| outb(APCI3120_CTR0_DO_BITS(devpriv->do_bits) | |
| APCI3120_CTR0_TIMER_SEL(timer + 1), |
| dev->iobase + APCI3120_CTR0_REG); |
| outw((val >> 16) & 0xffff, dev->iobase + APCI3120_TIMER_REG); |
| } |
| } |
| |
| static unsigned int apci3120_timer_read(struct comedi_device *dev, |
| unsigned int timer) |
| { |
| struct apci3120_private *devpriv = dev->private; |
| unsigned int val; |
| |
| /* read 16-bit value from timer (lower 16-bits of timer 2) */ |
| outb(APCI3120_CTR0_DO_BITS(devpriv->do_bits) | |
| APCI3120_CTR0_TIMER_SEL(timer), |
| dev->iobase + APCI3120_CTR0_REG); |
| val = inw(dev->iobase + APCI3120_TIMER_REG); |
| |
| if (timer == 2) { |
| /* read upper 16-bits from timer 2 */ |
| outb(APCI3120_CTR0_DO_BITS(devpriv->do_bits) | |
| APCI3120_CTR0_TIMER_SEL(timer + 1), |
| dev->iobase + APCI3120_CTR0_REG); |
| val |= (inw(dev->iobase + APCI3120_TIMER_REG) << 16); |
| } |
| |
| return val; |
| } |
| |
| static void apci3120_timer_set_mode(struct comedi_device *dev, |
| unsigned int timer, unsigned int mode) |
| { |
| struct apci3120_private *devpriv = dev->private; |
| |
| devpriv->timer_mode &= ~APCI3120_TIMER_MODE_MASK(timer); |
| devpriv->timer_mode |= APCI3120_TIMER_MODE(timer, mode); |
| outb(devpriv->timer_mode, dev->iobase + APCI3120_TIMER_MODE_REG); |
| } |
| |
| static void apci3120_timer_enable(struct comedi_device *dev, |
| unsigned int timer, bool enable) |
| { |
| struct apci3120_private *devpriv = dev->private; |
| |
| if (enable) |
| devpriv->ctrl |= APCI3120_CTRL_GATE(timer); |
| else |
| devpriv->ctrl &= ~APCI3120_CTRL_GATE(timer); |
| outw(devpriv->ctrl, dev->iobase + APCI3120_CTRL_REG); |
| } |
| |
| static void apci3120_exttrig_enable(struct comedi_device *dev, bool enable) |
| { |
| struct apci3120_private *devpriv = dev->private; |
| |
| if (enable) |
| devpriv->ctrl |= APCI3120_CTRL_EXT_TRIG; |
| else |
| devpriv->ctrl &= ~APCI3120_CTRL_EXT_TRIG; |
| outw(devpriv->ctrl, dev->iobase + APCI3120_CTRL_REG); |
| } |
| |
| static void apci3120_set_chanlist(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| int n_chan, unsigned int *chanlist) |
| { |
| struct apci3120_private *devpriv = dev->private; |
| int i; |
| |
| /* set chanlist for scan */ |
| for (i = 0; i < n_chan; i++) { |
| unsigned int chan = CR_CHAN(chanlist[i]); |
| unsigned int range = CR_RANGE(chanlist[i]); |
| unsigned int val; |
| |
| val = APCI3120_CHANLIST_MUX(chan) | |
| APCI3120_CHANLIST_GAIN(range) | |
| APCI3120_CHANLIST_INDEX(i); |
| |
| if (comedi_range_is_unipolar(s, range)) |
| val |= APCI3120_CHANLIST_UNIPOLAR; |
| |
| outw(val, dev->iobase + APCI3120_CHANLIST_REG); |
| } |
| |
| /* a dummy read of APCI3120_TIMER_MODE_REG resets the ai FIFO */ |
| inw(dev->iobase + APCI3120_TIMER_MODE_REG); |
| |
| /* set scan length (PR) and scan start (PA) */ |
| devpriv->ctrl = APCI3120_CTRL_PR(n_chan - 1) | APCI3120_CTRL_PA(0); |
| outw(devpriv->ctrl, dev->iobase + APCI3120_CTRL_REG); |
| |
| /* enable chanlist scanning if necessary */ |
| if (n_chan > 1) |
| devpriv->mode |= APCI3120_MODE_SCAN_ENA; |
| } |
| |
| static void apci3120_interrupt_dma(struct comedi_device *dev, |
| struct comedi_subdevice *s) |
| { |
| struct apci3120_private *devpriv = dev->private; |
| struct comedi_async *async = s->async; |
| struct comedi_cmd *cmd = &async->cmd; |
| struct apci3120_dmabuf *dmabuf; |
| unsigned int nbytes; |
| unsigned int nsamples; |
| |
| dmabuf = &devpriv->dmabuf[devpriv->cur_dmabuf]; |
| |
| nbytes = dmabuf->use_size - inl(devpriv->amcc + AMCC_OP_REG_MWTC); |
| |
| if (nbytes < dmabuf->use_size) |
| dev_err(dev->class_dev, "Interrupted DMA transfer!\n"); |
| if (nbytes & 1) { |
| dev_err(dev->class_dev, "Odd count of bytes in DMA ring!\n"); |
| async->events |= COMEDI_CB_ERROR; |
| return; |
| } |
| |
| nsamples = comedi_bytes_to_samples(s, nbytes); |
| if (nsamples) { |
| comedi_buf_write_samples(s, dmabuf->virt, nsamples); |
| |
| if (!(cmd->flags & CMDF_WAKE_EOS)) |
| async->events |= COMEDI_CB_EOS; |
| } |
| |
| if ((async->events & COMEDI_CB_CANCEL_MASK) || |
| (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg)) |
| return; |
| |
| if (devpriv->use_double_buffer) { |
| /* switch DMA buffers for next interrupt */ |
| devpriv->cur_dmabuf = !devpriv->cur_dmabuf; |
| dmabuf = &devpriv->dmabuf[devpriv->cur_dmabuf]; |
| apci3120_init_dma(dev, dmabuf); |
| } else { |
| /* restart DMA if not using double buffering */ |
| apci3120_init_dma(dev, dmabuf); |
| } |
| } |
| |
| static irqreturn_t apci3120_interrupt(int irq, void *d) |
| { |
| struct comedi_device *dev = d; |
| struct apci3120_private *devpriv = dev->private; |
| struct comedi_subdevice *s = dev->read_subdev; |
| struct comedi_async *async = s->async; |
| struct comedi_cmd *cmd = &async->cmd; |
| unsigned int status; |
| unsigned int int_amcc; |
| |
| status = inw(dev->iobase + APCI3120_STATUS_REG); |
| int_amcc = inl(devpriv->amcc + AMCC_OP_REG_INTCSR); |
| |
| if (!(status & APCI3120_STATUS_INT_MASK) && |
| !(int_amcc & ANY_S593X_INT)) { |
| dev_err(dev->class_dev, "IRQ from unknown source\n"); |
| return IRQ_NONE; |
| } |
| |
| outl(int_amcc | AINT_INT_MASK, devpriv->amcc + AMCC_OP_REG_INTCSR); |
| |
| if (devpriv->ctrl & APCI3120_CTRL_EXT_TRIG) |
| apci3120_exttrig_enable(dev, false); |
| |
| if (int_amcc & MASTER_ABORT_INT) |
| dev_err(dev->class_dev, "AMCC IRQ - MASTER DMA ABORT!\n"); |
| if (int_amcc & TARGET_ABORT_INT) |
| dev_err(dev->class_dev, "AMCC IRQ - TARGET DMA ABORT!\n"); |
| |
| if ((status & APCI3120_STATUS_EOS_INT) && |
| (devpriv->mode & APCI3120_MODE_EOS_IRQ_ENA)) { |
| unsigned short val; |
| int i; |
| |
| for (i = 0; i < cmd->chanlist_len; i++) { |
| val = inw(dev->iobase + APCI3120_AI_FIFO_REG); |
| comedi_buf_write_samples(s, &val, 1); |
| } |
| |
| devpriv->mode |= APCI3120_MODE_EOS_IRQ_ENA; |
| outb(devpriv->mode, dev->iobase + APCI3120_MODE_REG); |
| } |
| |
| if (status & APCI3120_STATUS_TIMER2_INT) { |
| /* |
| * for safety... |
| * timer2 interrupts are not enabled in the driver |
| */ |
| apci3120_clr_timer2_interrupt(dev); |
| } |
| |
| if (status & APCI3120_STATUS_AMCC_INT) { |
| /* AMCC- Clear write complete interrupt (DMA) */ |
| outl(AINT_WT_COMPLETE, devpriv->amcc + AMCC_OP_REG_INTCSR); |
| |
| /* do some data transfer */ |
| apci3120_interrupt_dma(dev, s); |
| } |
| |
| if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg) |
| async->events |= COMEDI_CB_EOA; |
| |
| comedi_handle_events(dev, s); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static int apci3120_ai_cmd(struct comedi_device *dev, |
| struct comedi_subdevice *s) |
| { |
| struct apci3120_private *devpriv = dev->private; |
| struct comedi_cmd *cmd = &s->async->cmd; |
| unsigned int divisor; |
| |
| /* set default mode bits */ |
| devpriv->mode = APCI3120_MODE_TIMER2_CLK_OSC | |
| APCI3120_MODE_TIMER2_AS_TIMER; |
| |
| /* AMCC- Clear write complete interrupt (DMA) */ |
| outl(AINT_WT_COMPLETE, devpriv->amcc + AMCC_OP_REG_INTCSR); |
| |
| devpriv->cur_dmabuf = 0; |
| |
| /* load chanlist for command scan */ |
| apci3120_set_chanlist(dev, s, cmd->chanlist_len, cmd->chanlist); |
| |
| if (cmd->start_src == TRIG_EXT) |
| apci3120_exttrig_enable(dev, true); |
| |
| if (cmd->scan_begin_src == TRIG_TIMER) { |
| /* |
| * Timer 1 is used in MODE2 (rate generator) to set the |
| * start time for each scan. |
| */ |
| divisor = apci3120_ns_to_timer(dev, 1, cmd->scan_begin_arg, |
| cmd->flags); |
| apci3120_timer_set_mode(dev, 1, APCI3120_TIMER_MODE2); |
| apci3120_timer_write(dev, 1, divisor); |
| } |
| |
| /* |
| * Timer 0 is used in MODE2 (rate generator) to set the conversion |
| * time for each acquisition. |
| */ |
| divisor = apci3120_ns_to_timer(dev, 0, cmd->convert_arg, cmd->flags); |
| apci3120_timer_set_mode(dev, 0, APCI3120_TIMER_MODE2); |
| apci3120_timer_write(dev, 0, divisor); |
| |
| if (devpriv->use_dma) |
| apci3120_setup_dma(dev, s); |
| else |
| devpriv->mode |= APCI3120_MODE_EOS_IRQ_ENA; |
| |
| /* set mode to enable acquisition */ |
| outb(devpriv->mode, dev->iobase + APCI3120_MODE_REG); |
| |
| if (cmd->scan_begin_src == TRIG_TIMER) |
| apci3120_timer_enable(dev, 1, true); |
| apci3120_timer_enable(dev, 0, true); |
| |
| return 0; |
| } |
| |
| static int apci3120_ai_cmdtest(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_cmd *cmd) |
| { |
| unsigned int arg; |
| int err = 0; |
| |
| /* Step 1 : check if triggers are trivially valid */ |
| |
| err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT); |
| err |= comedi_check_trigger_src(&cmd->scan_begin_src, |
| TRIG_TIMER | TRIG_FOLLOW); |
| err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_TIMER); |
| err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); |
| err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); |
| |
| if (err) |
| return 1; |
| |
| /* Step 2a : make sure trigger sources are unique */ |
| |
| err |= comedi_check_trigger_is_unique(cmd->start_src); |
| err |= comedi_check_trigger_is_unique(cmd->scan_begin_src); |
| err |= comedi_check_trigger_is_unique(cmd->stop_src); |
| |
| /* Step 2b : and mutually compatible */ |
| |
| if (err) |
| return 2; |
| |
| /* Step 3: check if arguments are trivially valid */ |
| |
| err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); |
| |
| if (cmd->scan_begin_src == TRIG_TIMER) { /* Test Delay timing */ |
| err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, |
| 100000); |
| } |
| |
| /* minimum conversion time per sample is 10us */ |
| err |= comedi_check_trigger_arg_min(&cmd->convert_arg, 10000); |
| |
| err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1); |
| err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, |
| cmd->chanlist_len); |
| |
| if (cmd->stop_src == TRIG_COUNT) |
| err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); |
| else /* TRIG_NONE */ |
| err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); |
| |
| if (err) |
| return 3; |
| |
| /* Step 4: fix up any arguments */ |
| |
| if (cmd->scan_begin_src == TRIG_TIMER) { |
| /* scan begin must be larger than the scan time */ |
| arg = cmd->convert_arg * cmd->scan_end_arg; |
| err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, arg); |
| } |
| |
| if (err) |
| return 4; |
| |
| /* Step 5: check channel list if it exists */ |
| |
| return 0; |
| } |
| |
| static int apci3120_cancel(struct comedi_device *dev, |
| struct comedi_subdevice *s) |
| { |
| struct apci3120_private *devpriv = dev->private; |
| |
| /* Add-On - disable DMA */ |
| outw(0, devpriv->addon + 4); |
| |
| /* Add-On - disable bus master */ |
| apci3120_addon_write(dev, 0, AMCC_OP_REG_AGCSTS); |
| |
| /* AMCC - disable bus master */ |
| outl(0, devpriv->amcc + AMCC_OP_REG_MCSR); |
| |
| /* disable all counters, ext trigger, and reset scan */ |
| devpriv->ctrl = 0; |
| outw(devpriv->ctrl, dev->iobase + APCI3120_CTRL_REG); |
| |
| /* DISABLE_ALL_INTERRUPT */ |
| devpriv->mode = 0; |
| outb(devpriv->mode, dev->iobase + APCI3120_MODE_REG); |
| |
| inw(dev->iobase + APCI3120_STATUS_REG); |
| devpriv->cur_dmabuf = 0; |
| |
| return 0; |
| } |
| |
| static int apci3120_ai_eoc(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned long context) |
| { |
| unsigned int status; |
| |
| status = inw(dev->iobase + APCI3120_STATUS_REG); |
| if ((status & APCI3120_STATUS_EOC_INT) == 0) |
| return 0; |
| return -EBUSY; |
| } |
| |
| static int apci3120_ai_insn_read(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| struct apci3120_private *devpriv = dev->private; |
| unsigned int divisor; |
| int ret; |
| int i; |
| |
| /* set mode for A/D conversions by software trigger with timer 0 */ |
| devpriv->mode = APCI3120_MODE_TIMER2_CLK_OSC | |
| APCI3120_MODE_TIMER2_AS_TIMER; |
| outb(devpriv->mode, dev->iobase + APCI3120_MODE_REG); |
| |
| /* load chanlist for single channel scan */ |
| apci3120_set_chanlist(dev, s, 1, &insn->chanspec); |
| |
| /* |
| * Timer 0 is used in MODE4 (software triggered strobe) to set the |
| * conversion time for each acquisition. Each conversion is triggered |
| * when the divisor is written to the timer, The conversion is done |
| * when the EOC bit in the status register is '0'. |
| */ |
| apci3120_timer_set_mode(dev, 0, APCI3120_TIMER_MODE4); |
| apci3120_timer_enable(dev, 0, true); |
| |
| /* fixed conversion time of 10 us */ |
| divisor = apci3120_ns_to_timer(dev, 0, 10000, CMDF_ROUND_NEAREST); |
| |
| for (i = 0; i < insn->n; i++) { |
| /* trigger conversion */ |
| apci3120_timer_write(dev, 0, divisor); |
| |
| ret = comedi_timeout(dev, s, insn, apci3120_ai_eoc, 0); |
| if (ret) |
| return ret; |
| |
| data[i] = inw(dev->iobase + APCI3120_AI_FIFO_REG); |
| } |
| |
| return insn->n; |
| } |
| |
| static int apci3120_ao_ready(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned long context) |
| { |
| unsigned int status; |
| |
| status = inw(dev->iobase + APCI3120_STATUS_REG); |
| if (status & APCI3120_STATUS_DA_READY) |
| return 0; |
| return -EBUSY; |
| } |
| |
| static int apci3120_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); |
| int i; |
| |
| for (i = 0; i < insn->n; i++) { |
| unsigned int val = data[i]; |
| int ret; |
| |
| ret = comedi_timeout(dev, s, insn, apci3120_ao_ready, 0); |
| if (ret) |
| return ret; |
| |
| outw(APCI3120_AO_MUX(chan) | APCI3120_AO_DATA(val), |
| dev->iobase + APCI3120_AO_REG(chan)); |
| |
| s->readback[chan] = val; |
| } |
| |
| return insn->n; |
| } |
| |
| static int apci3120_di_insn_bits(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| unsigned int status; |
| |
| status = inw(dev->iobase + APCI3120_STATUS_REG); |
| data[1] = APCI3120_STATUS_TO_DI_BITS(status); |
| |
| return insn->n; |
| } |
| |
| static int apci3120_do_insn_bits(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| struct apci3120_private *devpriv = dev->private; |
| |
| if (comedi_dio_update_state(s, data)) { |
| devpriv->do_bits = s->state; |
| outb(APCI3120_CTR0_DO_BITS(devpriv->do_bits), |
| dev->iobase + APCI3120_CTR0_REG); |
| } |
| |
| data[1] = s->state; |
| |
| return insn->n; |
| } |
| |
| static int apci3120_timer_insn_config(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| struct apci3120_private *devpriv = dev->private; |
| unsigned int divisor; |
| unsigned int status; |
| unsigned int mode; |
| unsigned int timer_mode; |
| |
| switch (data[0]) { |
| case INSN_CONFIG_ARM: |
| apci3120_clr_timer2_interrupt(dev); |
| divisor = apci3120_ns_to_timer(dev, 2, data[1], |
| CMDF_ROUND_DOWN); |
| apci3120_timer_write(dev, 2, divisor); |
| apci3120_timer_enable(dev, 2, true); |
| break; |
| |
| case INSN_CONFIG_DISARM: |
| apci3120_timer_enable(dev, 2, false); |
| apci3120_clr_timer2_interrupt(dev); |
| break; |
| |
| case INSN_CONFIG_GET_COUNTER_STATUS: |
| data[1] = 0; |
| data[2] = COMEDI_COUNTER_ARMED | COMEDI_COUNTER_COUNTING | |
| COMEDI_COUNTER_TERMINAL_COUNT; |
| |
| if (devpriv->ctrl & APCI3120_CTRL_GATE(2)) { |
| data[1] |= COMEDI_COUNTER_ARMED; |
| data[1] |= COMEDI_COUNTER_COUNTING; |
| } |
| status = inw(dev->iobase + APCI3120_STATUS_REG); |
| if (status & APCI3120_STATUS_TIMER2_INT) { |
| data[1] &= ~COMEDI_COUNTER_COUNTING; |
| data[1] |= COMEDI_COUNTER_TERMINAL_COUNT; |
| } |
| break; |
| |
| case INSN_CONFIG_SET_COUNTER_MODE: |
| switch (data[1]) { |
| case I8254_MODE0: |
| mode = APCI3120_MODE_TIMER2_AS_COUNTER; |
| timer_mode = APCI3120_TIMER_MODE0; |
| break; |
| case I8254_MODE2: |
| mode = APCI3120_MODE_TIMER2_AS_TIMER; |
| timer_mode = APCI3120_TIMER_MODE2; |
| break; |
| case I8254_MODE4: |
| mode = APCI3120_MODE_TIMER2_AS_TIMER; |
| timer_mode = APCI3120_TIMER_MODE4; |
| break; |
| case I8254_MODE5: |
| mode = APCI3120_MODE_TIMER2_AS_WDOG; |
| timer_mode = APCI3120_TIMER_MODE5; |
| break; |
| default: |
| return -EINVAL; |
| } |
| apci3120_timer_enable(dev, 2, false); |
| apci3120_clr_timer2_interrupt(dev); |
| apci3120_timer_set_mode(dev, 2, timer_mode); |
| devpriv->mode &= ~APCI3120_MODE_TIMER2_AS_MASK; |
| devpriv->mode |= mode; |
| outb(devpriv->mode, dev->iobase + APCI3120_MODE_REG); |
| break; |
| |
| default: |
| return -EINVAL; |
| } |
| |
| return insn->n; |
| } |
| |
| static int apci3120_timer_insn_read(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| int i; |
| |
| for (i = 0; i < insn->n; i++) |
| data[i] = apci3120_timer_read(dev, 2); |
| |
| return insn->n; |
| } |
| |
| static void apci3120_dma_alloc(struct comedi_device *dev) |
| { |
| struct apci3120_private *devpriv = dev->private; |
| struct apci3120_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->use_dma = 1; |
| if (i == 1) |
| devpriv->use_double_buffer = 1; |
| } |
| } |
| |
| static void apci3120_dma_free(struct comedi_device *dev) |
| { |
| struct apci3120_private *devpriv = dev->private; |
| struct apci3120_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 void apci3120_reset(struct comedi_device *dev) |
| { |
| /* disable all interrupt sources */ |
| outb(0, dev->iobase + APCI3120_MODE_REG); |
| |
| /* disable all counters, ext trigger, and reset scan */ |
| outw(0, dev->iobase + APCI3120_CTRL_REG); |
| |
| /* clear interrupt status */ |
| inw(dev->iobase + APCI3120_STATUS_REG); |
| } |
| |
| static int apci3120_auto_attach(struct comedi_device *dev, |
| unsigned long context) |
| { |
| struct pci_dev *pcidev = comedi_to_pci_dev(dev); |
| const struct apci3120_board *board = NULL; |
| struct apci3120_private *devpriv; |
| struct comedi_subdevice *s; |
| unsigned int status; |
| int ret; |
| |
| if (context < ARRAY_SIZE(apci3120_boardtypes)) |
| board = &apci3120_boardtypes[context]; |
| if (!board) |
| return -ENODEV; |
| dev->board_ptr = board; |
| dev->board_name = board->name; |
| |
| devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); |
| if (!devpriv) |
| return -ENOMEM; |
| |
| ret = comedi_pci_enable(dev); |
| if (ret) |
| return ret; |
| pci_set_master(pcidev); |
| |
| dev->iobase = pci_resource_start(pcidev, 1); |
| devpriv->amcc = pci_resource_start(pcidev, 0); |
| devpriv->addon = pci_resource_start(pcidev, 2); |
| |
| apci3120_reset(dev); |
| |
| if (pcidev->irq > 0) { |
| ret = request_irq(pcidev->irq, apci3120_interrupt, IRQF_SHARED, |
| dev->board_name, dev); |
| if (ret == 0) { |
| dev->irq = pcidev->irq; |
| |
| apci3120_dma_alloc(dev); |
| } |
| } |
| |
| status = inw(dev->iobase + APCI3120_STATUS_REG); |
| if (APCI3120_STATUS_TO_VERSION(status) == APCI3120_REVB || |
| context == BOARD_APCI3001) |
| devpriv->osc_base = APCI3120_REVB_OSC_BASE; |
| else |
| devpriv->osc_base = APCI3120_REVA_OSC_BASE; |
| |
| ret = comedi_alloc_subdevices(dev, 5); |
| 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 = 16; |
| s->maxdata = board->ai_is_16bit ? 0xffff : 0x0fff; |
| s->range_table = &apci3120_ai_range; |
| s->insn_read = apci3120_ai_insn_read; |
| if (dev->irq) { |
| dev->read_subdev = s; |
| s->subdev_flags |= SDF_CMD_READ; |
| s->len_chanlist = s->n_chan; |
| s->do_cmdtest = apci3120_ai_cmdtest; |
| s->do_cmd = apci3120_ai_cmd; |
| s->cancel = apci3120_cancel; |
| } |
| |
| /* Analog Output subdevice */ |
| s = &dev->subdevices[1]; |
| if (board->has_ao) { |
| s->type = COMEDI_SUBD_AO; |
| s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; |
| s->n_chan = 8; |
| s->maxdata = 0x3fff; |
| s->range_table = &range_bipolar10; |
| s->insn_write = apci3120_ao_insn_write; |
| |
| ret = comedi_alloc_subdev_readback(s); |
| if (ret) |
| return ret; |
| } else { |
| s->type = COMEDI_SUBD_UNUSED; |
| } |
| |
| /* 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 = apci3120_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 = apci3120_do_insn_bits; |
| |
| /* Timer subdevice */ |
| s = &dev->subdevices[4]; |
| s->type = COMEDI_SUBD_TIMER; |
| s->subdev_flags = SDF_READABLE; |
| s->n_chan = 1; |
| s->maxdata = 0x00ffffff; |
| s->insn_config = apci3120_timer_insn_config; |
| s->insn_read = apci3120_timer_insn_read; |
| |
| return 0; |
| } |
| |
| static void apci3120_detach(struct comedi_device *dev) |
| { |
| comedi_pci_detach(dev); |
| apci3120_dma_free(dev); |
| } |
| |
| static struct comedi_driver apci3120_driver = { |
| .driver_name = "addi_apci_3120", |
| .module = THIS_MODULE, |
| .auto_attach = apci3120_auto_attach, |
| .detach = apci3120_detach, |
| }; |
| |
| static int apci3120_pci_probe(struct pci_dev *dev, |
| const struct pci_device_id *id) |
| { |
| return comedi_pci_auto_config(dev, &apci3120_driver, id->driver_data); |
| } |
| |
| static const struct pci_device_id apci3120_pci_table[] = { |
| { PCI_VDEVICE(AMCC, 0x818d), BOARD_APCI3120 }, |
| { PCI_VDEVICE(AMCC, 0x828d), BOARD_APCI3001 }, |
| { 0 } |
| }; |
| MODULE_DEVICE_TABLE(pci, apci3120_pci_table); |
| |
| static struct pci_driver apci3120_pci_driver = { |
| .name = "addi_apci_3120", |
| .id_table = apci3120_pci_table, |
| .probe = apci3120_pci_probe, |
| .remove = comedi_pci_auto_unconfig, |
| }; |
| module_comedi_pci_driver(apci3120_driver, apci3120_pci_driver); |
| |
| MODULE_AUTHOR("Comedi https://www.comedi.org"); |
| MODULE_DESCRIPTION("ADDI-DATA APCI-3120, Analog input board"); |
| MODULE_LICENSE("GPL"); |