/* * addi_apci_3xxx.c * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. * Project manager: S. Weber * * 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 * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. */ #include #include #include #include "../comedidev.h" #include "comedi_fc.h" #define CONV_UNIT_NS (1 << 0) #define CONV_UNIT_US (1 << 1) #define CONV_UNIT_MS (1 << 2) static const struct comedi_lrange apci3xxx_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) } }; static const struct comedi_lrange apci3xxx_ao_range = { 2, { BIP_RANGE(10), UNI_RANGE(10) } }; enum apci3xxx_boardid { BOARD_APCI3000_16, BOARD_APCI3000_8, BOARD_APCI3000_4, BOARD_APCI3006_16, BOARD_APCI3006_8, BOARD_APCI3006_4, BOARD_APCI3010_16, BOARD_APCI3010_8, BOARD_APCI3010_4, BOARD_APCI3016_16, BOARD_APCI3016_8, BOARD_APCI3016_4, BOARD_APCI3100_16_4, BOARD_APCI3100_8_4, BOARD_APCI3106_16_4, BOARD_APCI3106_8_4, BOARD_APCI3110_16_4, BOARD_APCI3110_8_4, BOARD_APCI3116_16_4, BOARD_APCI3116_8_4, BOARD_APCI3003, BOARD_APCI3002_16, BOARD_APCI3002_8, BOARD_APCI3002_4, BOARD_APCI3500, }; struct apci3xxx_boardinfo { const char *name; int ai_subdev_flags; int ai_n_chan; unsigned int ai_maxdata; unsigned char ai_conv_units; unsigned int ai_min_acq_ns; unsigned int has_ao:1; unsigned int has_dig_in:1; unsigned int has_dig_out:1; unsigned int has_ttl_io:1; }; static const struct apci3xxx_boardinfo apci3xxx_boardtypes[] = { [BOARD_APCI3000_16] = { .name = "apci3000-16", .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, .ai_n_chan = 16, .ai_maxdata = 0x0fff, .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, .ai_min_acq_ns = 10000, .has_ttl_io = 1, }, [BOARD_APCI3000_8] = { .name = "apci3000-8", .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, .ai_n_chan = 8, .ai_maxdata = 0x0fff, .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, .ai_min_acq_ns = 10000, .has_ttl_io = 1, }, [BOARD_APCI3000_4] = { .name = "apci3000-4", .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, .ai_n_chan = 4, .ai_maxdata = 0x0fff, .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, .ai_min_acq_ns = 10000, .has_ttl_io = 1, }, [BOARD_APCI3006_16] = { .name = "apci3006-16", .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, .ai_n_chan = 16, .ai_maxdata = 0xffff, .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, .ai_min_acq_ns = 10000, .has_ttl_io = 1, }, [BOARD_APCI3006_8] = { .name = "apci3006-8", .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, .ai_n_chan = 8, .ai_maxdata = 0xffff, .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, .ai_min_acq_ns = 10000, .has_ttl_io = 1, }, [BOARD_APCI3006_4] = { .name = "apci3006-4", .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, .ai_n_chan = 4, .ai_maxdata = 0xffff, .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, .ai_min_acq_ns = 10000, .has_ttl_io = 1, }, [BOARD_APCI3010_16] = { .name = "apci3010-16", .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, .ai_n_chan = 16, .ai_maxdata = 0x0fff, .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, .ai_min_acq_ns = 5000, .has_dig_in = 1, .has_dig_out = 1, .has_ttl_io = 1, }, [BOARD_APCI3010_8] = { .name = "apci3010-8", .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, .ai_n_chan = 8, .ai_maxdata = 0x0fff, .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, .ai_min_acq_ns = 5000, .has_dig_in = 1, .has_dig_out = 1, .has_ttl_io = 1, }, [BOARD_APCI3010_4] = { .name = "apci3010-4", .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, .ai_n_chan = 4, .ai_maxdata = 0x0fff, .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, .ai_min_acq_ns = 5000, .has_dig_in = 1, .has_dig_out = 1, .has_ttl_io = 1, }, [BOARD_APCI3016_16] = { .name = "apci3016-16", .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, .ai_n_chan = 16, .ai_maxdata = 0xffff, .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, .ai_min_acq_ns = 5000, .has_dig_in = 1, .has_dig_out = 1, .has_ttl_io = 1, }, [BOARD_APCI3016_8] = { .name = "apci3016-8", .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, .ai_n_chan = 8, .ai_maxdata = 0xffff, .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, .ai_min_acq_ns = 5000, .has_dig_in = 1, .has_dig_out = 1, .has_ttl_io = 1, }, [BOARD_APCI3016_4] = { .name = "apci3016-4", .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, .ai_n_chan = 4, .ai_maxdata = 0xffff, .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, .ai_min_acq_ns = 5000, .has_dig_in = 1, .has_dig_out = 1, .has_ttl_io = 1, }, [BOARD_APCI3100_16_4] = { .name = "apci3100-16-4", .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, .ai_n_chan = 16, .ai_maxdata = 0x0fff, .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, .ai_min_acq_ns = 10000, .has_ao = 1, .has_ttl_io = 1, }, [BOARD_APCI3100_8_4] = { .name = "apci3100-8-4", .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, .ai_n_chan = 8, .ai_maxdata = 0x0fff, .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, .ai_min_acq_ns = 10000, .has_ao = 1, .has_ttl_io = 1, }, [BOARD_APCI3106_16_4] = { .name = "apci3106-16-4", .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, .ai_n_chan = 16, .ai_maxdata = 0xffff, .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, .ai_min_acq_ns = 10000, .has_ao = 1, .has_ttl_io = 1, }, [BOARD_APCI3106_8_4] = { .name = "apci3106-8-4", .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, .ai_n_chan = 8, .ai_maxdata = 0xffff, .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, .ai_min_acq_ns = 10000, .has_ao = 1, .has_ttl_io = 1, }, [BOARD_APCI3110_16_4] = { .name = "apci3110-16-4", .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, .ai_n_chan = 16, .ai_maxdata = 0x0fff, .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, .ai_min_acq_ns = 5000, .has_ao = 1, .has_dig_in = 1, .has_dig_out = 1, .has_ttl_io = 1, }, [BOARD_APCI3110_8_4] = { .name = "apci3110-8-4", .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, .ai_n_chan = 8, .ai_maxdata = 0x0fff, .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, .ai_min_acq_ns = 5000, .has_ao = 1, .has_dig_in = 1, .has_dig_out = 1, .has_ttl_io = 1, }, [BOARD_APCI3116_16_4] = { .name = "apci3116-16-4", .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, .ai_n_chan = 16, .ai_maxdata = 0xffff, .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, .ai_min_acq_ns = 5000, .has_ao = 1, .has_dig_in = 1, .has_dig_out = 1, .has_ttl_io = 1, }, [BOARD_APCI3116_8_4] = { .name = "apci3116-8-4", .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF, .ai_n_chan = 8, .ai_maxdata = 0xffff, .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, .ai_min_acq_ns = 5000, .has_ao = 1, .has_dig_in = 1, .has_dig_out = 1, .has_ttl_io = 1, }, [BOARD_APCI3003] = { .name = "apci3003", .ai_subdev_flags = SDF_DIFF, .ai_n_chan = 4, .ai_maxdata = 0xffff, .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US | CONV_UNIT_NS, .ai_min_acq_ns = 2500, .has_dig_in = 1, .has_dig_out = 1, }, [BOARD_APCI3002_16] = { .name = "apci3002-16", .ai_subdev_flags = SDF_DIFF, .ai_n_chan = 16, .ai_maxdata = 0xffff, .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, .ai_min_acq_ns = 5000, .has_dig_in = 1, .has_dig_out = 1, }, [BOARD_APCI3002_8] = { .name = "apci3002-8", .ai_subdev_flags = SDF_DIFF, .ai_n_chan = 8, .ai_maxdata = 0xffff, .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, .ai_min_acq_ns = 5000, .has_dig_in = 1, .has_dig_out = 1, }, [BOARD_APCI3002_4] = { .name = "apci3002-4", .ai_subdev_flags = SDF_DIFF, .ai_n_chan = 4, .ai_maxdata = 0xffff, .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US, .ai_min_acq_ns = 5000, .has_dig_in = 1, .has_dig_out = 1, }, [BOARD_APCI3500] = { .name = "apci3500", .has_ao = 1, .has_ttl_io = 1, }, }; struct apci3xxx_private { unsigned int ai_timer; unsigned char ai_time_base; }; static irqreturn_t apci3xxx_irq_handler(int irq, void *d) { struct comedi_device *dev = d; struct comedi_subdevice *s = dev->read_subdev; unsigned int status; unsigned int val; /* Test if interrupt occur */ status = readl(dev->mmio + 16); if ((status & 0x2) == 0x2) { /* Reset the interrupt */ writel(status, dev->mmio + 16); val = readl(dev->mmio + 28); comedi_buf_write_samples(s, &val, 1); s->async->events |= COMEDI_CB_EOA; comedi_handle_events(dev, s); return IRQ_HANDLED; } return IRQ_NONE; } static int apci3xxx_ai_started(struct comedi_device *dev) { if ((readl(dev->mmio + 8) & 0x80000) == 0x80000) return 1; return 0; } static int apci3xxx_ai_setup(struct comedi_device *dev, unsigned int chanspec) { unsigned int chan = CR_CHAN(chanspec); unsigned int range = CR_RANGE(chanspec); unsigned int aref = CR_AREF(chanspec); unsigned int delay_mode; unsigned int val; if (apci3xxx_ai_started(dev)) return -EBUSY; /* Clear the FIFO */ writel(0x10000, dev->mmio + 12); /* Get and save the delay mode */ delay_mode = readl(dev->mmio + 4); delay_mode &= 0xfffffef0; /* Channel configuration selection */ writel(delay_mode, dev->mmio + 4); /* Make the configuration */ val = (range & 3) | ((range >> 2) << 6) | ((aref == AREF_DIFF) << 7); writel(val, dev->mmio + 0); /* Channel selection */ writel(delay_mode | 0x100, dev->mmio + 4); writel(chan, dev->mmio + 0); /* Restore delay mode */ writel(delay_mode, dev->mmio + 4); /* Set the number of sequence to 1 */ writel(1, dev->mmio + 48); return 0; } static int apci3xxx_ai_eoc(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned long context) { unsigned int status; status = readl(dev->mmio + 20); if (status & 0x1) return 0; return -EBUSY; } static int apci3xxx_ai_insn_read(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { int ret; int i; ret = apci3xxx_ai_setup(dev, insn->chanspec); if (ret) return ret; for (i = 0; i < insn->n; i++) { /* Start the conversion */ writel(0x80000, dev->mmio + 8); /* Wait the EOS */ ret = comedi_timeout(dev, s, insn, apci3xxx_ai_eoc, 0); if (ret) return ret; /* Read the analog value */ data[i] = readl(dev->mmio + 28); } return insn->n; } static int apci3xxx_ai_ns_to_timer(struct comedi_device *dev, unsigned int *ns, unsigned int flags) { const struct apci3xxx_boardinfo *board = dev->board_ptr; struct apci3xxx_private *devpriv = dev->private; unsigned int base; unsigned int timer; int time_base; /* time_base: 0 = ns, 1 = us, 2 = ms */ for (time_base = 0; time_base < 3; time_base++) { /* skip unsupported time bases */ if (!(board->ai_conv_units & (1 << time_base))) continue; switch (time_base) { case 0: base = 1; break; case 1: base = 1000; break; case 2: base = 1000000; break; } switch (flags & CMDF_ROUND_MASK) { case CMDF_ROUND_NEAREST: default: timer = (*ns + base / 2) / base; break; case CMDF_ROUND_DOWN: timer = *ns / base; break; case CMDF_ROUND_UP: timer = (*ns + base - 1) / base; break; } if (timer < 0x10000) { devpriv->ai_time_base = time_base; devpriv->ai_timer = timer; *ns = timer * time_base; return 0; } } return -EINVAL; } static int apci3xxx_ai_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_cmd *cmd) { const struct apci3xxx_boardinfo *board = dev->board_ptr; int err = 0; unsigned int arg; /* Step 1 : check if triggers are trivially valid */ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW); err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER); err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); if (err) return 1; /* Step 2a : make sure trigger sources are unique */ err |= cfc_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 |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); err |= cfc_check_trigger_arg_min(&cmd->convert_arg, board->ai_min_acq_ns); err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); if (cmd->stop_src == TRIG_COUNT) err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1); else /* TRIG_NONE */ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0); if (err) return 3; /* step 4: fix up any arguments */ arg = cmd->convert_arg; err |= apci3xxx_ai_ns_to_timer(dev, &arg, cmd->flags); err |= cfc_check_trigger_arg_is(&cmd->convert_arg, arg); if (err) return 4; return 0; } static int apci3xxx_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) { struct apci3xxx_private *devpriv = dev->private; struct comedi_cmd *cmd = &s->async->cmd; int ret; ret = apci3xxx_ai_setup(dev, cmd->chanlist[0]); if (ret) return ret; /* Set the convert timing unit */ writel(devpriv->ai_time_base, dev->mmio + 36); /* Set the convert timing */ writel(devpriv->ai_timer, dev->mmio + 32); /* Start the conversion */ writel(0x180000, dev->mmio + 8); return 0; } static int apci3xxx_ai_cancel(struct comedi_device *dev, struct comedi_subdevice *s) { return 0; } static int apci3xxx_ao_eoc(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned long context) { unsigned int status; status = readl(dev->mmio + 96); if (status & 0x100) return 0; return -EBUSY; } static int apci3xxx_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 range = CR_RANGE(insn->chanspec); int ret; int i; for (i = 0; i < insn->n; i++) { unsigned int val = data[i]; /* Set the range selection */ writel(range, dev->mmio + 96); /* Write the analog value to the selected channel */ writel((val << 8) | chan, dev->mmio + 100); /* Wait the end of transfer */ ret = comedi_timeout(dev, s, insn, apci3xxx_ao_eoc, 0); if (ret) return ret; s->readback[chan] = val; } return insn->n; } static int apci3xxx_di_insn_bits(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { data[1] = inl(dev->iobase + 32) & 0xf; return insn->n; } static int apci3xxx_do_insn_bits(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { s->state = inl(dev->iobase + 48) & 0xf; if (comedi_dio_update_state(s, data)) outl(s->state, dev->iobase + 48); data[1] = s->state; return insn->n; } static int apci3xxx_dio_insn_config(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { unsigned int chan = CR_CHAN(insn->chanspec); unsigned int mask = 0; int ret; /* * Port 0 (channels 0-7) are always inputs * Port 1 (channels 8-15) are always outputs * Port 2 (channels 16-23) are programmable i/o */ if (data[0] != INSN_CONFIG_DIO_QUERY) { /* ignore all other instructions for ports 0 and 1 */ if (chan < 16) return -EINVAL; /* changing any channel in port 2 changes the entire port */ mask = 0xff0000; } ret = comedi_dio_insn_config(dev, s, insn, data, mask); if (ret) return ret; /* update port 2 configuration */ outl((s->io_bits >> 24) & 0xff, dev->iobase + 224); return insn->n; } static int apci3xxx_dio_insn_bits(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { unsigned int mask; unsigned int val; mask = comedi_dio_update_state(s, data); if (mask) { if (mask & 0xff) outl(s->state & 0xff, dev->iobase + 80); if (mask & 0xff0000) outl((s->state >> 16) & 0xff, dev->iobase + 112); } val = inl(dev->iobase + 80); val |= (inl(dev->iobase + 64) << 8); if (s->io_bits & 0xff0000) val |= (inl(dev->iobase + 112) << 16); else val |= (inl(dev->iobase + 96) << 16); data[1] = val; return insn->n; } static int apci3xxx_reset(struct comedi_device *dev) { unsigned int val; int i; /* Disable the interrupt */ disable_irq(dev->irq); /* Clear the start command */ writel(0, dev->mmio + 8); /* Reset the interrupt flags */ val = readl(dev->mmio + 16); writel(val, dev->mmio + 16); /* clear the EOS */ readl(dev->mmio + 20); /* Clear the FIFO */ for (i = 0; i < 16; i++) val = readl(dev->mmio + 28); /* Enable the interrupt */ enable_irq(dev->irq); return 0; } static int apci3xxx_auto_attach(struct comedi_device *dev, unsigned long context) { struct pci_dev *pcidev = comedi_to_pci_dev(dev); const struct apci3xxx_boardinfo *board = NULL; struct apci3xxx_private *devpriv; struct comedi_subdevice *s; int n_subdevices; int subdev; int ret; if (context < ARRAY_SIZE(apci3xxx_boardtypes)) board = &apci3xxx_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; dev->iobase = pci_resource_start(pcidev, 2); dev->mmio = pci_ioremap_bar(pcidev, 3); if (pcidev->irq > 0) { ret = request_irq(pcidev->irq, apci3xxx_irq_handler, IRQF_SHARED, dev->board_name, dev); if (ret == 0) dev->irq = pcidev->irq; } n_subdevices = (board->ai_n_chan ? 0 : 1) + board->has_ao + board->has_dig_in + board->has_dig_out + board->has_ttl_io; ret = comedi_alloc_subdevices(dev, n_subdevices); if (ret) return ret; subdev = 0; /* Analog Input subdevice */ if (board->ai_n_chan) { s = &dev->subdevices[subdev]; s->type = COMEDI_SUBD_AI; s->subdev_flags = SDF_READABLE | board->ai_subdev_flags; s->n_chan = board->ai_n_chan; s->maxdata = board->ai_maxdata; s->range_table = &apci3xxx_ai_range; s->insn_read = apci3xxx_ai_insn_read; if (dev->irq) { /* * FIXME: The hardware supports multiple scan modes * but the original addi-data driver only supported * reading a single channel with interrupts. Need a * proper datasheet to fix this. * * The following scan modes are supported by the * hardware: * 1) Single software scan * 2) Single hardware triggered scan * 3) Continuous software scan * 4) Continuous software scan with timer delay * 5) Continuous hardware triggered scan * 6) Continuous hardware triggered scan with timer * delay * * For now, limit the chanlist to a single channel. */ dev->read_subdev = s; s->subdev_flags |= SDF_CMD_READ; s->len_chanlist = 1; s->do_cmdtest = apci3xxx_ai_cmdtest; s->do_cmd = apci3xxx_ai_cmd; s->cancel = apci3xxx_ai_cancel; } subdev++; } /* Analog Output subdevice */ if (board->has_ao) { s = &dev->subdevices[subdev]; s->type = COMEDI_SUBD_AO; s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; s->n_chan = 4; s->maxdata = 0x0fff; s->range_table = &apci3xxx_ao_range; s->insn_write = apci3xxx_ao_insn_write; ret = comedi_alloc_subdev_readback(s); if (ret) return ret; subdev++; } /* Digital Input subdevice */ if (board->has_dig_in) { s = &dev->subdevices[subdev]; 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 = apci3xxx_di_insn_bits; subdev++; } /* Digital Output subdevice */ if (board->has_dig_out) { s = &dev->subdevices[subdev]; 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 = apci3xxx_do_insn_bits; subdev++; } /* TTL Digital I/O subdevice */ if (board->has_ttl_io) { s = &dev->subdevices[subdev]; s->type = COMEDI_SUBD_DIO; s->subdev_flags = SDF_READABLE | SDF_WRITABLE; s->n_chan = 24; s->maxdata = 1; s->io_bits = 0xff; /* channels 0-7 are always outputs */ s->range_table = &range_digital; s->insn_config = apci3xxx_dio_insn_config; s->insn_bits = apci3xxx_dio_insn_bits; subdev++; } apci3xxx_reset(dev); return 0; } static void apci3xxx_detach(struct comedi_device *dev) { if (dev->iobase) apci3xxx_reset(dev); comedi_pci_detach(dev); } static struct comedi_driver apci3xxx_driver = { .driver_name = "addi_apci_3xxx", .module = THIS_MODULE, .auto_attach = apci3xxx_auto_attach, .detach = apci3xxx_detach, }; static int apci3xxx_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) { return comedi_pci_auto_config(dev, &apci3xxx_driver, id->driver_data); } static const struct pci_device_id apci3xxx_pci_table[] = { { PCI_VDEVICE(ADDIDATA, 0x3010), BOARD_APCI3000_16 }, { PCI_VDEVICE(ADDIDATA, 0x300f), BOARD_APCI3000_8 }, { PCI_VDEVICE(ADDIDATA, 0x300e), BOARD_APCI3000_4 }, { PCI_VDEVICE(ADDIDATA, 0x3013), BOARD_APCI3006_16 }, { PCI_VDEVICE(ADDIDATA, 0x3014), BOARD_APCI3006_8 }, { PCI_VDEVICE(ADDIDATA, 0x3015), BOARD_APCI3006_4 }, { PCI_VDEVICE(ADDIDATA, 0x3016), BOARD_APCI3010_16 }, { PCI_VDEVICE(ADDIDATA, 0x3017), BOARD_APCI3010_8 }, { PCI_VDEVICE(ADDIDATA, 0x3018), BOARD_APCI3010_4 }, { PCI_VDEVICE(ADDIDATA, 0x3019), BOARD_APCI3016_16 }, { PCI_VDEVICE(ADDIDATA, 0x301a), BOARD_APCI3016_8 }, { PCI_VDEVICE(ADDIDATA, 0x301b), BOARD_APCI3016_4 }, { PCI_VDEVICE(ADDIDATA, 0x301c), BOARD_APCI3100_16_4 }, { PCI_VDEVICE(ADDIDATA, 0x301d), BOARD_APCI3100_8_4 }, { PCI_VDEVICE(ADDIDATA, 0x301e), BOARD_APCI3106_16_4 }, { PCI_VDEVICE(ADDIDATA, 0x301f), BOARD_APCI3106_8_4 }, { PCI_VDEVICE(ADDIDATA, 0x3020), BOARD_APCI3110_16_4 }, { PCI_VDEVICE(ADDIDATA, 0x3021), BOARD_APCI3110_8_4 }, { PCI_VDEVICE(ADDIDATA, 0x3022), BOARD_APCI3116_16_4 }, { PCI_VDEVICE(ADDIDATA, 0x3023), BOARD_APCI3116_8_4 }, { PCI_VDEVICE(ADDIDATA, 0x300B), BOARD_APCI3003 }, { PCI_VDEVICE(ADDIDATA, 0x3002), BOARD_APCI3002_16 }, { PCI_VDEVICE(ADDIDATA, 0x3003), BOARD_APCI3002_8 }, { PCI_VDEVICE(ADDIDATA, 0x3004), BOARD_APCI3002_4 }, { PCI_VDEVICE(ADDIDATA, 0x3024), BOARD_APCI3500 }, { 0 } }; MODULE_DEVICE_TABLE(pci, apci3xxx_pci_table); static struct pci_driver apci3xxx_pci_driver = { .name = "addi_apci_3xxx", .id_table = apci3xxx_pci_table, .probe = apci3xxx_pci_probe, .remove = comedi_pci_auto_unconfig, }; module_comedi_pci_driver(apci3xxx_driver, apci3xxx_pci_driver); MODULE_AUTHOR("Comedi http://www.comedi.org"); MODULE_DESCRIPTION("Comedi low-level driver"); MODULE_LICENSE("GPL");