mirror of
https://github.com/torvalds/linux.git
synced 2024-12-28 22:02:28 +00:00
df0e68c1e9
Move the main COMEDI driver headers out of "drivers/comedi/" into new directory "include/linux/comedi/". These are "comedidev.h", "comedilib.h", "comedi_pci.h", "comedi_pcmcia.h", and "comedi_usb.h". Additionally, move the user-space API header "comedi.h" into "include/uapi/linux/" and add "WITH Linux-syscall-note" to its SPDX-License-Identifier. Update the "COMEDI DRIVERS" section of the MAINTAINERS file to account for these changes. Signed-off-by: Ian Abbott <abbotti@mev.co.uk> Link: https://lore.kernel.org/r/20211117120604.117740-2-abbotti@mev.co.uk Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1118 lines
30 KiB
C
1118 lines
30 KiB
C
// 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 <linux/comedi/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");
|