162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * adl_pci9111.c 462306a36Sopenharmony_ci * Hardware driver for PCI9111 ADLink cards: PCI-9111HR 562306a36Sopenharmony_ci * Copyright (C) 2002-2005 Emmanuel Pacaud <emmanuel.pacaud@univ-poitiers.fr> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci/* 962306a36Sopenharmony_ci * Driver: adl_pci9111 1062306a36Sopenharmony_ci * Description: Adlink PCI-9111HR 1162306a36Sopenharmony_ci * Devices: [ADLink] PCI-9111HR (adl_pci9111) 1262306a36Sopenharmony_ci * Author: Emmanuel Pacaud <emmanuel.pacaud@univ-poitiers.fr> 1362306a36Sopenharmony_ci * Status: experimental 1462306a36Sopenharmony_ci * 1562306a36Sopenharmony_ci * Configuration options: not applicable, uses PCI auto config 1662306a36Sopenharmony_ci * 1762306a36Sopenharmony_ci * Supports: 1862306a36Sopenharmony_ci * - ai_insn read 1962306a36Sopenharmony_ci * - ao_insn read/write 2062306a36Sopenharmony_ci * - di_insn read 2162306a36Sopenharmony_ci * - do_insn read/write 2262306a36Sopenharmony_ci * - ai_do_cmd mode with the following sources: 2362306a36Sopenharmony_ci * - start_src TRIG_NOW 2462306a36Sopenharmony_ci * - scan_begin_src TRIG_FOLLOW TRIG_TIMER TRIG_EXT 2562306a36Sopenharmony_ci * - convert_src TRIG_TIMER TRIG_EXT 2662306a36Sopenharmony_ci * - scan_end_src TRIG_COUNT 2762306a36Sopenharmony_ci * - stop_src TRIG_COUNT TRIG_NONE 2862306a36Sopenharmony_ci * 2962306a36Sopenharmony_ci * The scanned channels must be consecutive and start from 0. They must 3062306a36Sopenharmony_ci * all have the same range and aref. 3162306a36Sopenharmony_ci */ 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci/* 3462306a36Sopenharmony_ci * TODO: 3562306a36Sopenharmony_ci * - Really test implemented functionality. 3662306a36Sopenharmony_ci * - Add support for the PCI-9111DG with a probe routine to identify 3762306a36Sopenharmony_ci * the card type (perhaps with the help of the channel number readback 3862306a36Sopenharmony_ci * of the A/D Data register). 3962306a36Sopenharmony_ci * - Add external multiplexer support. 4062306a36Sopenharmony_ci */ 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci#include <linux/module.h> 4362306a36Sopenharmony_ci#include <linux/delay.h> 4462306a36Sopenharmony_ci#include <linux/interrupt.h> 4562306a36Sopenharmony_ci#include <linux/comedi/comedi_pci.h> 4662306a36Sopenharmony_ci#include <linux/comedi/comedi_8254.h> 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci#include "plx9052.h" 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci#define PCI9111_FIFO_HALF_SIZE 512 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci#define PCI9111_AI_ACQUISITION_PERIOD_MIN_NS 10000 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci#define PCI9111_RANGE_SETTING_DELAY 10 5562306a36Sopenharmony_ci#define PCI9111_AI_INSTANT_READ_UDELAY_US 2 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci/* 5862306a36Sopenharmony_ci * IO address map and bit defines 5962306a36Sopenharmony_ci */ 6062306a36Sopenharmony_ci#define PCI9111_AI_FIFO_REG 0x00 6162306a36Sopenharmony_ci#define PCI9111_AO_REG 0x00 6262306a36Sopenharmony_ci#define PCI9111_DIO_REG 0x02 6362306a36Sopenharmony_ci#define PCI9111_EDIO_REG 0x04 6462306a36Sopenharmony_ci#define PCI9111_AI_CHANNEL_REG 0x06 6562306a36Sopenharmony_ci#define PCI9111_AI_RANGE_STAT_REG 0x08 6662306a36Sopenharmony_ci#define PCI9111_AI_STAT_AD_BUSY BIT(7) 6762306a36Sopenharmony_ci#define PCI9111_AI_STAT_FF_FF BIT(6) 6862306a36Sopenharmony_ci#define PCI9111_AI_STAT_FF_HF BIT(5) 6962306a36Sopenharmony_ci#define PCI9111_AI_STAT_FF_EF BIT(4) 7062306a36Sopenharmony_ci#define PCI9111_AI_RANGE(x) (((x) & 0x7) << 0) 7162306a36Sopenharmony_ci#define PCI9111_AI_RANGE_MASK PCI9111_AI_RANGE(7) 7262306a36Sopenharmony_ci#define PCI9111_AI_TRIG_CTRL_REG 0x0a 7362306a36Sopenharmony_ci#define PCI9111_AI_TRIG_CTRL_TRGEVENT BIT(5) 7462306a36Sopenharmony_ci#define PCI9111_AI_TRIG_CTRL_POTRG BIT(4) 7562306a36Sopenharmony_ci#define PCI9111_AI_TRIG_CTRL_PTRG BIT(3) 7662306a36Sopenharmony_ci#define PCI9111_AI_TRIG_CTRL_ETIS BIT(2) 7762306a36Sopenharmony_ci#define PCI9111_AI_TRIG_CTRL_TPST BIT(1) 7862306a36Sopenharmony_ci#define PCI9111_AI_TRIG_CTRL_ASCAN BIT(0) 7962306a36Sopenharmony_ci#define PCI9111_INT_CTRL_REG 0x0c 8062306a36Sopenharmony_ci#define PCI9111_INT_CTRL_ISC2 BIT(3) 8162306a36Sopenharmony_ci#define PCI9111_INT_CTRL_FFEN BIT(2) 8262306a36Sopenharmony_ci#define PCI9111_INT_CTRL_ISC1 BIT(1) 8362306a36Sopenharmony_ci#define PCI9111_INT_CTRL_ISC0 BIT(0) 8462306a36Sopenharmony_ci#define PCI9111_SOFT_TRIG_REG 0x0e 8562306a36Sopenharmony_ci#define PCI9111_8254_BASE_REG 0x40 8662306a36Sopenharmony_ci#define PCI9111_INT_CLR_REG 0x48 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci/* PLX 9052 Local Interrupt 1 enabled and active */ 8962306a36Sopenharmony_ci#define PCI9111_LI1_ACTIVE (PLX9052_INTCSR_LI1ENAB | \ 9062306a36Sopenharmony_ci PLX9052_INTCSR_LI1STAT) 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci/* PLX 9052 Local Interrupt 2 enabled and active */ 9362306a36Sopenharmony_ci#define PCI9111_LI2_ACTIVE (PLX9052_INTCSR_LI2ENAB | \ 9462306a36Sopenharmony_ci PLX9052_INTCSR_LI2STAT) 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_cistatic const struct comedi_lrange pci9111_ai_range = { 9762306a36Sopenharmony_ci 5, { 9862306a36Sopenharmony_ci BIP_RANGE(10), 9962306a36Sopenharmony_ci BIP_RANGE(5), 10062306a36Sopenharmony_ci BIP_RANGE(2.5), 10162306a36Sopenharmony_ci BIP_RANGE(1.25), 10262306a36Sopenharmony_ci BIP_RANGE(0.625) 10362306a36Sopenharmony_ci } 10462306a36Sopenharmony_ci}; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_cistruct pci9111_private_data { 10762306a36Sopenharmony_ci unsigned long lcr_io_base; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci unsigned int scan_delay; 11062306a36Sopenharmony_ci unsigned int chunk_counter; 11162306a36Sopenharmony_ci unsigned int chunk_num_samples; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci unsigned short ai_bounce_buffer[2 * PCI9111_FIFO_HALF_SIZE]; 11462306a36Sopenharmony_ci}; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_cistatic void plx9050_interrupt_control(unsigned long io_base, 11762306a36Sopenharmony_ci bool int1_enable, 11862306a36Sopenharmony_ci bool int1_active_high, 11962306a36Sopenharmony_ci bool int2_enable, 12062306a36Sopenharmony_ci bool int2_active_high, 12162306a36Sopenharmony_ci bool interrupt_enable) 12262306a36Sopenharmony_ci{ 12362306a36Sopenharmony_ci int flags = 0; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci if (int1_enable) 12662306a36Sopenharmony_ci flags |= PLX9052_INTCSR_LI1ENAB; 12762306a36Sopenharmony_ci if (int1_active_high) 12862306a36Sopenharmony_ci flags |= PLX9052_INTCSR_LI1POL; 12962306a36Sopenharmony_ci if (int2_enable) 13062306a36Sopenharmony_ci flags |= PLX9052_INTCSR_LI2ENAB; 13162306a36Sopenharmony_ci if (int2_active_high) 13262306a36Sopenharmony_ci flags |= PLX9052_INTCSR_LI2POL; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci if (interrupt_enable) 13562306a36Sopenharmony_ci flags |= PLX9052_INTCSR_PCIENAB; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci outb(flags, io_base + PLX9052_INTCSR); 13862306a36Sopenharmony_ci} 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_cienum pci9111_ISC0_sources { 14162306a36Sopenharmony_ci irq_on_eoc, 14262306a36Sopenharmony_ci irq_on_fifo_half_full 14362306a36Sopenharmony_ci}; 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_cienum pci9111_ISC1_sources { 14662306a36Sopenharmony_ci irq_on_timer_tick, 14762306a36Sopenharmony_ci irq_on_external_trigger 14862306a36Sopenharmony_ci}; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_cistatic void pci9111_interrupt_source_set(struct comedi_device *dev, 15162306a36Sopenharmony_ci enum pci9111_ISC0_sources irq_0_source, 15262306a36Sopenharmony_ci enum pci9111_ISC1_sources irq_1_source) 15362306a36Sopenharmony_ci{ 15462306a36Sopenharmony_ci int flags; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci /* Read the current interrupt control bits */ 15762306a36Sopenharmony_ci flags = inb(dev->iobase + PCI9111_AI_TRIG_CTRL_REG); 15862306a36Sopenharmony_ci /* Shift the bits so they are compatible with the write register */ 15962306a36Sopenharmony_ci flags >>= 4; 16062306a36Sopenharmony_ci /* Mask off the ISCx bits */ 16162306a36Sopenharmony_ci flags &= 0xc0; 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci /* Now set the new ISCx bits */ 16462306a36Sopenharmony_ci if (irq_0_source == irq_on_fifo_half_full) 16562306a36Sopenharmony_ci flags |= PCI9111_INT_CTRL_ISC0; 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci if (irq_1_source == irq_on_external_trigger) 16862306a36Sopenharmony_ci flags |= PCI9111_INT_CTRL_ISC1; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci outb(flags, dev->iobase + PCI9111_INT_CTRL_REG); 17162306a36Sopenharmony_ci} 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_cistatic void pci9111_fifo_reset(struct comedi_device *dev) 17462306a36Sopenharmony_ci{ 17562306a36Sopenharmony_ci unsigned long int_ctrl_reg = dev->iobase + PCI9111_INT_CTRL_REG; 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci /* To reset the FIFO, set FFEN sequence as 0 -> 1 -> 0 */ 17862306a36Sopenharmony_ci outb(0, int_ctrl_reg); 17962306a36Sopenharmony_ci outb(PCI9111_INT_CTRL_FFEN, int_ctrl_reg); 18062306a36Sopenharmony_ci outb(0, int_ctrl_reg); 18162306a36Sopenharmony_ci} 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_cistatic int pci9111_ai_cancel(struct comedi_device *dev, 18462306a36Sopenharmony_ci struct comedi_subdevice *s) 18562306a36Sopenharmony_ci{ 18662306a36Sopenharmony_ci struct pci9111_private_data *dev_private = dev->private; 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci /* Disable interrupts */ 18962306a36Sopenharmony_ci plx9050_interrupt_control(dev_private->lcr_io_base, true, true, true, 19062306a36Sopenharmony_ci true, false); 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci /* disable A/D triggers (software trigger mode) and auto scan off */ 19362306a36Sopenharmony_ci outb(0, dev->iobase + PCI9111_AI_TRIG_CTRL_REG); 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci pci9111_fifo_reset(dev); 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci return 0; 19862306a36Sopenharmony_ci} 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_cistatic int pci9111_ai_check_chanlist(struct comedi_device *dev, 20162306a36Sopenharmony_ci struct comedi_subdevice *s, 20262306a36Sopenharmony_ci struct comedi_cmd *cmd) 20362306a36Sopenharmony_ci{ 20462306a36Sopenharmony_ci unsigned int range0 = CR_RANGE(cmd->chanlist[0]); 20562306a36Sopenharmony_ci unsigned int aref0 = CR_AREF(cmd->chanlist[0]); 20662306a36Sopenharmony_ci int i; 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci for (i = 1; i < cmd->chanlist_len; i++) { 20962306a36Sopenharmony_ci unsigned int chan = CR_CHAN(cmd->chanlist[i]); 21062306a36Sopenharmony_ci unsigned int range = CR_RANGE(cmd->chanlist[i]); 21162306a36Sopenharmony_ci unsigned int aref = CR_AREF(cmd->chanlist[i]); 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci if (chan != i) { 21462306a36Sopenharmony_ci dev_dbg(dev->class_dev, 21562306a36Sopenharmony_ci "entries in chanlist must be consecutive channels,counting upwards from 0\n"); 21662306a36Sopenharmony_ci return -EINVAL; 21762306a36Sopenharmony_ci } 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci if (range != range0) { 22062306a36Sopenharmony_ci dev_dbg(dev->class_dev, 22162306a36Sopenharmony_ci "entries in chanlist must all have the same gain\n"); 22262306a36Sopenharmony_ci return -EINVAL; 22362306a36Sopenharmony_ci } 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci if (aref != aref0) { 22662306a36Sopenharmony_ci dev_dbg(dev->class_dev, 22762306a36Sopenharmony_ci "entries in chanlist must all have the same reference\n"); 22862306a36Sopenharmony_ci return -EINVAL; 22962306a36Sopenharmony_ci } 23062306a36Sopenharmony_ci } 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci return 0; 23362306a36Sopenharmony_ci} 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_cistatic int pci9111_ai_do_cmd_test(struct comedi_device *dev, 23662306a36Sopenharmony_ci struct comedi_subdevice *s, 23762306a36Sopenharmony_ci struct comedi_cmd *cmd) 23862306a36Sopenharmony_ci{ 23962306a36Sopenharmony_ci int err = 0; 24062306a36Sopenharmony_ci unsigned int arg; 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci /* Step 1 : check if triggers are trivially valid */ 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); 24562306a36Sopenharmony_ci err |= comedi_check_trigger_src(&cmd->scan_begin_src, 24662306a36Sopenharmony_ci TRIG_TIMER | TRIG_FOLLOW | TRIG_EXT); 24762306a36Sopenharmony_ci err |= comedi_check_trigger_src(&cmd->convert_src, 24862306a36Sopenharmony_ci TRIG_TIMER | TRIG_EXT); 24962306a36Sopenharmony_ci err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); 25062306a36Sopenharmony_ci err |= comedi_check_trigger_src(&cmd->stop_src, 25162306a36Sopenharmony_ci TRIG_COUNT | TRIG_NONE); 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_ci if (err) 25462306a36Sopenharmony_ci return 1; 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci /* Step 2a : make sure trigger sources are unique */ 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci err |= comedi_check_trigger_is_unique(cmd->scan_begin_src); 25962306a36Sopenharmony_ci err |= comedi_check_trigger_is_unique(cmd->convert_src); 26062306a36Sopenharmony_ci err |= comedi_check_trigger_is_unique(cmd->stop_src); 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci /* Step 2b : and mutually compatible */ 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci if (cmd->scan_begin_src != TRIG_FOLLOW) { 26562306a36Sopenharmony_ci if (cmd->scan_begin_src != cmd->convert_src) 26662306a36Sopenharmony_ci err |= -EINVAL; 26762306a36Sopenharmony_ci } 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci if (err) 27062306a36Sopenharmony_ci return 2; 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci /* Step 3: check if arguments are trivially valid */ 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci if (cmd->convert_src == TRIG_TIMER) { 27762306a36Sopenharmony_ci err |= comedi_check_trigger_arg_min(&cmd->convert_arg, 27862306a36Sopenharmony_ci PCI9111_AI_ACQUISITION_PERIOD_MIN_NS); 27962306a36Sopenharmony_ci } else { /* TRIG_EXT */ 28062306a36Sopenharmony_ci err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); 28162306a36Sopenharmony_ci } 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci if (cmd->scan_begin_src == TRIG_TIMER) { 28462306a36Sopenharmony_ci err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, 28562306a36Sopenharmony_ci PCI9111_AI_ACQUISITION_PERIOD_MIN_NS); 28662306a36Sopenharmony_ci } else { /* TRIG_FOLLOW || TRIG_EXT */ 28762306a36Sopenharmony_ci err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); 28862306a36Sopenharmony_ci } 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, 29162306a36Sopenharmony_ci cmd->chanlist_len); 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci if (cmd->stop_src == TRIG_COUNT) 29462306a36Sopenharmony_ci err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); 29562306a36Sopenharmony_ci else /* TRIG_NONE */ 29662306a36Sopenharmony_ci err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci if (err) 29962306a36Sopenharmony_ci return 3; 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci /* Step 4: fix up any arguments */ 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci if (cmd->convert_src == TRIG_TIMER) { 30462306a36Sopenharmony_ci arg = cmd->convert_arg; 30562306a36Sopenharmony_ci comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags); 30662306a36Sopenharmony_ci err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg); 30762306a36Sopenharmony_ci } 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci /* 31062306a36Sopenharmony_ci * There's only one timer on this card, so the scan_begin timer 31162306a36Sopenharmony_ci * must be a multiple of chanlist_len*convert_arg 31262306a36Sopenharmony_ci */ 31362306a36Sopenharmony_ci if (cmd->scan_begin_src == TRIG_TIMER) { 31462306a36Sopenharmony_ci arg = cmd->chanlist_len * cmd->convert_arg; 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci if (arg < cmd->scan_begin_arg) 31762306a36Sopenharmony_ci arg *= (cmd->scan_begin_arg / arg); 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg); 32062306a36Sopenharmony_ci } 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci if (err) 32362306a36Sopenharmony_ci return 4; 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci /* Step 5: check channel list if it exists */ 32662306a36Sopenharmony_ci if (cmd->chanlist && cmd->chanlist_len > 0) 32762306a36Sopenharmony_ci err |= pci9111_ai_check_chanlist(dev, s, cmd); 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci if (err) 33062306a36Sopenharmony_ci return 5; 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ci return 0; 33362306a36Sopenharmony_ci} 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_cistatic int pci9111_ai_do_cmd(struct comedi_device *dev, 33662306a36Sopenharmony_ci struct comedi_subdevice *s) 33762306a36Sopenharmony_ci{ 33862306a36Sopenharmony_ci struct pci9111_private_data *dev_private = dev->private; 33962306a36Sopenharmony_ci struct comedi_cmd *cmd = &s->async->cmd; 34062306a36Sopenharmony_ci unsigned int last_chan = CR_CHAN(cmd->chanlist[cmd->chanlist_len - 1]); 34162306a36Sopenharmony_ci unsigned int range0 = CR_RANGE(cmd->chanlist[0]); 34262306a36Sopenharmony_ci unsigned int trig = 0; 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_ci /* Set channel scan limit */ 34562306a36Sopenharmony_ci /* PCI9111 allows only scanning from channel 0 to channel n */ 34662306a36Sopenharmony_ci /* TODO: handle the case of an external multiplexer */ 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci if (cmd->chanlist_len > 1) 34962306a36Sopenharmony_ci trig |= PCI9111_AI_TRIG_CTRL_ASCAN; 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_ci outb(last_chan, dev->iobase + PCI9111_AI_CHANNEL_REG); 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci /* Set gain - all channels use the same range */ 35462306a36Sopenharmony_ci outb(PCI9111_AI_RANGE(range0), dev->iobase + PCI9111_AI_RANGE_STAT_REG); 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_ci /* Set timer pacer */ 35762306a36Sopenharmony_ci dev_private->scan_delay = 0; 35862306a36Sopenharmony_ci if (cmd->convert_src == TRIG_TIMER) { 35962306a36Sopenharmony_ci trig |= PCI9111_AI_TRIG_CTRL_TPST; 36062306a36Sopenharmony_ci comedi_8254_update_divisors(dev->pacer); 36162306a36Sopenharmony_ci comedi_8254_pacer_enable(dev->pacer, 1, 2, true); 36262306a36Sopenharmony_ci pci9111_fifo_reset(dev); 36362306a36Sopenharmony_ci pci9111_interrupt_source_set(dev, irq_on_fifo_half_full, 36462306a36Sopenharmony_ci irq_on_timer_tick); 36562306a36Sopenharmony_ci plx9050_interrupt_control(dev_private->lcr_io_base, true, true, 36662306a36Sopenharmony_ci false, true, true); 36762306a36Sopenharmony_ci 36862306a36Sopenharmony_ci if (cmd->scan_begin_src == TRIG_TIMER) { 36962306a36Sopenharmony_ci dev_private->scan_delay = (cmd->scan_begin_arg / 37062306a36Sopenharmony_ci (cmd->convert_arg * cmd->chanlist_len)) - 1; 37162306a36Sopenharmony_ci } 37262306a36Sopenharmony_ci } else { /* TRIG_EXT */ 37362306a36Sopenharmony_ci trig |= PCI9111_AI_TRIG_CTRL_ETIS; 37462306a36Sopenharmony_ci pci9111_fifo_reset(dev); 37562306a36Sopenharmony_ci pci9111_interrupt_source_set(dev, irq_on_fifo_half_full, 37662306a36Sopenharmony_ci irq_on_timer_tick); 37762306a36Sopenharmony_ci plx9050_interrupt_control(dev_private->lcr_io_base, true, true, 37862306a36Sopenharmony_ci false, true, true); 37962306a36Sopenharmony_ci } 38062306a36Sopenharmony_ci outb(trig, dev->iobase + PCI9111_AI_TRIG_CTRL_REG); 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_ci dev_private->chunk_counter = 0; 38362306a36Sopenharmony_ci dev_private->chunk_num_samples = cmd->chanlist_len * 38462306a36Sopenharmony_ci (1 + dev_private->scan_delay); 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_ci return 0; 38762306a36Sopenharmony_ci} 38862306a36Sopenharmony_ci 38962306a36Sopenharmony_cistatic void pci9111_ai_munge(struct comedi_device *dev, 39062306a36Sopenharmony_ci struct comedi_subdevice *s, void *data, 39162306a36Sopenharmony_ci unsigned int num_bytes, 39262306a36Sopenharmony_ci unsigned int start_chan_index) 39362306a36Sopenharmony_ci{ 39462306a36Sopenharmony_ci unsigned short *array = data; 39562306a36Sopenharmony_ci unsigned int maxdata = s->maxdata; 39662306a36Sopenharmony_ci unsigned int invert = (maxdata + 1) >> 1; 39762306a36Sopenharmony_ci unsigned int shift = (maxdata == 0xffff) ? 0 : 4; 39862306a36Sopenharmony_ci unsigned int num_samples = comedi_bytes_to_samples(s, num_bytes); 39962306a36Sopenharmony_ci unsigned int i; 40062306a36Sopenharmony_ci 40162306a36Sopenharmony_ci for (i = 0; i < num_samples; i++) 40262306a36Sopenharmony_ci array[i] = ((array[i] >> shift) & maxdata) ^ invert; 40362306a36Sopenharmony_ci} 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_cistatic void pci9111_handle_fifo_half_full(struct comedi_device *dev, 40662306a36Sopenharmony_ci struct comedi_subdevice *s) 40762306a36Sopenharmony_ci{ 40862306a36Sopenharmony_ci struct pci9111_private_data *devpriv = dev->private; 40962306a36Sopenharmony_ci struct comedi_cmd *cmd = &s->async->cmd; 41062306a36Sopenharmony_ci unsigned short *buf = devpriv->ai_bounce_buffer; 41162306a36Sopenharmony_ci unsigned int samples; 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_ci samples = comedi_nsamples_left(s, PCI9111_FIFO_HALF_SIZE); 41462306a36Sopenharmony_ci insw(dev->iobase + PCI9111_AI_FIFO_REG, buf, samples); 41562306a36Sopenharmony_ci 41662306a36Sopenharmony_ci if (devpriv->scan_delay < 1) { 41762306a36Sopenharmony_ci comedi_buf_write_samples(s, buf, samples); 41862306a36Sopenharmony_ci } else { 41962306a36Sopenharmony_ci unsigned int pos = 0; 42062306a36Sopenharmony_ci unsigned int to_read; 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_ci while (pos < samples) { 42362306a36Sopenharmony_ci if (devpriv->chunk_counter < cmd->chanlist_len) { 42462306a36Sopenharmony_ci to_read = cmd->chanlist_len - 42562306a36Sopenharmony_ci devpriv->chunk_counter; 42662306a36Sopenharmony_ci 42762306a36Sopenharmony_ci if (to_read > samples - pos) 42862306a36Sopenharmony_ci to_read = samples - pos; 42962306a36Sopenharmony_ci 43062306a36Sopenharmony_ci comedi_buf_write_samples(s, buf + pos, to_read); 43162306a36Sopenharmony_ci } else { 43262306a36Sopenharmony_ci to_read = devpriv->chunk_num_samples - 43362306a36Sopenharmony_ci devpriv->chunk_counter; 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_ci if (to_read > samples - pos) 43662306a36Sopenharmony_ci to_read = samples - pos; 43762306a36Sopenharmony_ci } 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_ci pos += to_read; 44062306a36Sopenharmony_ci devpriv->chunk_counter += to_read; 44162306a36Sopenharmony_ci 44262306a36Sopenharmony_ci if (devpriv->chunk_counter >= 44362306a36Sopenharmony_ci devpriv->chunk_num_samples) 44462306a36Sopenharmony_ci devpriv->chunk_counter = 0; 44562306a36Sopenharmony_ci } 44662306a36Sopenharmony_ci } 44762306a36Sopenharmony_ci} 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_cistatic irqreturn_t pci9111_interrupt(int irq, void *p_device) 45062306a36Sopenharmony_ci{ 45162306a36Sopenharmony_ci struct comedi_device *dev = p_device; 45262306a36Sopenharmony_ci struct pci9111_private_data *dev_private = dev->private; 45362306a36Sopenharmony_ci struct comedi_subdevice *s = dev->read_subdev; 45462306a36Sopenharmony_ci struct comedi_async *async; 45562306a36Sopenharmony_ci struct comedi_cmd *cmd; 45662306a36Sopenharmony_ci unsigned int status; 45762306a36Sopenharmony_ci unsigned long irq_flags; 45862306a36Sopenharmony_ci unsigned char intcsr; 45962306a36Sopenharmony_ci 46062306a36Sopenharmony_ci if (!dev->attached) { 46162306a36Sopenharmony_ci /* Ignore interrupt before device fully attached. */ 46262306a36Sopenharmony_ci /* Might not even have allocated subdevices yet! */ 46362306a36Sopenharmony_ci return IRQ_NONE; 46462306a36Sopenharmony_ci } 46562306a36Sopenharmony_ci 46662306a36Sopenharmony_ci async = s->async; 46762306a36Sopenharmony_ci cmd = &async->cmd; 46862306a36Sopenharmony_ci 46962306a36Sopenharmony_ci spin_lock_irqsave(&dev->spinlock, irq_flags); 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_ci /* Check if we are source of interrupt */ 47262306a36Sopenharmony_ci intcsr = inb(dev_private->lcr_io_base + PLX9052_INTCSR); 47362306a36Sopenharmony_ci if (!(((intcsr & PLX9052_INTCSR_PCIENAB) != 0) && 47462306a36Sopenharmony_ci (((intcsr & PCI9111_LI1_ACTIVE) == PCI9111_LI1_ACTIVE) || 47562306a36Sopenharmony_ci ((intcsr & PCI9111_LI2_ACTIVE) == PCI9111_LI2_ACTIVE)))) { 47662306a36Sopenharmony_ci /* Not the source of the interrupt. */ 47762306a36Sopenharmony_ci /* (N.B. not using PLX9052_INTCSR_SOFTINT) */ 47862306a36Sopenharmony_ci spin_unlock_irqrestore(&dev->spinlock, irq_flags); 47962306a36Sopenharmony_ci return IRQ_NONE; 48062306a36Sopenharmony_ci } 48162306a36Sopenharmony_ci 48262306a36Sopenharmony_ci if ((intcsr & PCI9111_LI1_ACTIVE) == PCI9111_LI1_ACTIVE) { 48362306a36Sopenharmony_ci /* Interrupt comes from fifo_half-full signal */ 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_ci status = inb(dev->iobase + PCI9111_AI_RANGE_STAT_REG); 48662306a36Sopenharmony_ci 48762306a36Sopenharmony_ci /* '0' means FIFO is full, data may have been lost */ 48862306a36Sopenharmony_ci if (!(status & PCI9111_AI_STAT_FF_FF)) { 48962306a36Sopenharmony_ci spin_unlock_irqrestore(&dev->spinlock, irq_flags); 49062306a36Sopenharmony_ci dev_dbg(dev->class_dev, "fifo overflow\n"); 49162306a36Sopenharmony_ci outb(0, dev->iobase + PCI9111_INT_CLR_REG); 49262306a36Sopenharmony_ci async->events |= COMEDI_CB_ERROR; 49362306a36Sopenharmony_ci comedi_handle_events(dev, s); 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_ci return IRQ_HANDLED; 49662306a36Sopenharmony_ci } 49762306a36Sopenharmony_ci 49862306a36Sopenharmony_ci /* '0' means FIFO is half-full */ 49962306a36Sopenharmony_ci if (!(status & PCI9111_AI_STAT_FF_HF)) 50062306a36Sopenharmony_ci pci9111_handle_fifo_half_full(dev, s); 50162306a36Sopenharmony_ci } 50262306a36Sopenharmony_ci 50362306a36Sopenharmony_ci if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg) 50462306a36Sopenharmony_ci async->events |= COMEDI_CB_EOA; 50562306a36Sopenharmony_ci 50662306a36Sopenharmony_ci outb(0, dev->iobase + PCI9111_INT_CLR_REG); 50762306a36Sopenharmony_ci 50862306a36Sopenharmony_ci spin_unlock_irqrestore(&dev->spinlock, irq_flags); 50962306a36Sopenharmony_ci 51062306a36Sopenharmony_ci comedi_handle_events(dev, s); 51162306a36Sopenharmony_ci 51262306a36Sopenharmony_ci return IRQ_HANDLED; 51362306a36Sopenharmony_ci} 51462306a36Sopenharmony_ci 51562306a36Sopenharmony_cistatic int pci9111_ai_eoc(struct comedi_device *dev, 51662306a36Sopenharmony_ci struct comedi_subdevice *s, 51762306a36Sopenharmony_ci struct comedi_insn *insn, 51862306a36Sopenharmony_ci unsigned long context) 51962306a36Sopenharmony_ci{ 52062306a36Sopenharmony_ci unsigned int status; 52162306a36Sopenharmony_ci 52262306a36Sopenharmony_ci status = inb(dev->iobase + PCI9111_AI_RANGE_STAT_REG); 52362306a36Sopenharmony_ci if (status & PCI9111_AI_STAT_FF_EF) 52462306a36Sopenharmony_ci return 0; 52562306a36Sopenharmony_ci return -EBUSY; 52662306a36Sopenharmony_ci} 52762306a36Sopenharmony_ci 52862306a36Sopenharmony_cistatic int pci9111_ai_insn_read(struct comedi_device *dev, 52962306a36Sopenharmony_ci struct comedi_subdevice *s, 53062306a36Sopenharmony_ci struct comedi_insn *insn, unsigned int *data) 53162306a36Sopenharmony_ci{ 53262306a36Sopenharmony_ci unsigned int chan = CR_CHAN(insn->chanspec); 53362306a36Sopenharmony_ci unsigned int range = CR_RANGE(insn->chanspec); 53462306a36Sopenharmony_ci unsigned int maxdata = s->maxdata; 53562306a36Sopenharmony_ci unsigned int invert = (maxdata + 1) >> 1; 53662306a36Sopenharmony_ci unsigned int shift = (maxdata == 0xffff) ? 0 : 4; 53762306a36Sopenharmony_ci unsigned int status; 53862306a36Sopenharmony_ci int ret; 53962306a36Sopenharmony_ci int i; 54062306a36Sopenharmony_ci 54162306a36Sopenharmony_ci outb(chan, dev->iobase + PCI9111_AI_CHANNEL_REG); 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_ci status = inb(dev->iobase + PCI9111_AI_RANGE_STAT_REG); 54462306a36Sopenharmony_ci if ((status & PCI9111_AI_RANGE_MASK) != range) { 54562306a36Sopenharmony_ci outb(PCI9111_AI_RANGE(range), 54662306a36Sopenharmony_ci dev->iobase + PCI9111_AI_RANGE_STAT_REG); 54762306a36Sopenharmony_ci } 54862306a36Sopenharmony_ci 54962306a36Sopenharmony_ci pci9111_fifo_reset(dev); 55062306a36Sopenharmony_ci 55162306a36Sopenharmony_ci for (i = 0; i < insn->n; i++) { 55262306a36Sopenharmony_ci /* Generate a software trigger */ 55362306a36Sopenharmony_ci outb(0, dev->iobase + PCI9111_SOFT_TRIG_REG); 55462306a36Sopenharmony_ci 55562306a36Sopenharmony_ci ret = comedi_timeout(dev, s, insn, pci9111_ai_eoc, 0); 55662306a36Sopenharmony_ci if (ret) { 55762306a36Sopenharmony_ci pci9111_fifo_reset(dev); 55862306a36Sopenharmony_ci return ret; 55962306a36Sopenharmony_ci } 56062306a36Sopenharmony_ci 56162306a36Sopenharmony_ci data[i] = inw(dev->iobase + PCI9111_AI_FIFO_REG); 56262306a36Sopenharmony_ci data[i] = ((data[i] >> shift) & maxdata) ^ invert; 56362306a36Sopenharmony_ci } 56462306a36Sopenharmony_ci 56562306a36Sopenharmony_ci return i; 56662306a36Sopenharmony_ci} 56762306a36Sopenharmony_ci 56862306a36Sopenharmony_cistatic int pci9111_ao_insn_write(struct comedi_device *dev, 56962306a36Sopenharmony_ci struct comedi_subdevice *s, 57062306a36Sopenharmony_ci struct comedi_insn *insn, 57162306a36Sopenharmony_ci unsigned int *data) 57262306a36Sopenharmony_ci{ 57362306a36Sopenharmony_ci unsigned int chan = CR_CHAN(insn->chanspec); 57462306a36Sopenharmony_ci unsigned int val = s->readback[chan]; 57562306a36Sopenharmony_ci int i; 57662306a36Sopenharmony_ci 57762306a36Sopenharmony_ci for (i = 0; i < insn->n; i++) { 57862306a36Sopenharmony_ci val = data[i]; 57962306a36Sopenharmony_ci outw(val, dev->iobase + PCI9111_AO_REG); 58062306a36Sopenharmony_ci } 58162306a36Sopenharmony_ci s->readback[chan] = val; 58262306a36Sopenharmony_ci 58362306a36Sopenharmony_ci return insn->n; 58462306a36Sopenharmony_ci} 58562306a36Sopenharmony_ci 58662306a36Sopenharmony_cistatic int pci9111_di_insn_bits(struct comedi_device *dev, 58762306a36Sopenharmony_ci struct comedi_subdevice *s, 58862306a36Sopenharmony_ci struct comedi_insn *insn, 58962306a36Sopenharmony_ci unsigned int *data) 59062306a36Sopenharmony_ci{ 59162306a36Sopenharmony_ci data[1] = inw(dev->iobase + PCI9111_DIO_REG); 59262306a36Sopenharmony_ci 59362306a36Sopenharmony_ci return insn->n; 59462306a36Sopenharmony_ci} 59562306a36Sopenharmony_ci 59662306a36Sopenharmony_cistatic int pci9111_do_insn_bits(struct comedi_device *dev, 59762306a36Sopenharmony_ci struct comedi_subdevice *s, 59862306a36Sopenharmony_ci struct comedi_insn *insn, 59962306a36Sopenharmony_ci unsigned int *data) 60062306a36Sopenharmony_ci{ 60162306a36Sopenharmony_ci if (comedi_dio_update_state(s, data)) 60262306a36Sopenharmony_ci outw(s->state, dev->iobase + PCI9111_DIO_REG); 60362306a36Sopenharmony_ci 60462306a36Sopenharmony_ci data[1] = s->state; 60562306a36Sopenharmony_ci 60662306a36Sopenharmony_ci return insn->n; 60762306a36Sopenharmony_ci} 60862306a36Sopenharmony_ci 60962306a36Sopenharmony_cistatic int pci9111_reset(struct comedi_device *dev) 61062306a36Sopenharmony_ci{ 61162306a36Sopenharmony_ci struct pci9111_private_data *dev_private = dev->private; 61262306a36Sopenharmony_ci 61362306a36Sopenharmony_ci /* Set trigger source to software */ 61462306a36Sopenharmony_ci plx9050_interrupt_control(dev_private->lcr_io_base, true, true, true, 61562306a36Sopenharmony_ci true, false); 61662306a36Sopenharmony_ci 61762306a36Sopenharmony_ci /* disable A/D triggers (software trigger mode) and auto scan off */ 61862306a36Sopenharmony_ci outb(0, dev->iobase + PCI9111_AI_TRIG_CTRL_REG); 61962306a36Sopenharmony_ci 62062306a36Sopenharmony_ci return 0; 62162306a36Sopenharmony_ci} 62262306a36Sopenharmony_ci 62362306a36Sopenharmony_cistatic int pci9111_auto_attach(struct comedi_device *dev, 62462306a36Sopenharmony_ci unsigned long context_unused) 62562306a36Sopenharmony_ci{ 62662306a36Sopenharmony_ci struct pci_dev *pcidev = comedi_to_pci_dev(dev); 62762306a36Sopenharmony_ci struct pci9111_private_data *dev_private; 62862306a36Sopenharmony_ci struct comedi_subdevice *s; 62962306a36Sopenharmony_ci int ret; 63062306a36Sopenharmony_ci 63162306a36Sopenharmony_ci dev_private = comedi_alloc_devpriv(dev, sizeof(*dev_private)); 63262306a36Sopenharmony_ci if (!dev_private) 63362306a36Sopenharmony_ci return -ENOMEM; 63462306a36Sopenharmony_ci 63562306a36Sopenharmony_ci ret = comedi_pci_enable(dev); 63662306a36Sopenharmony_ci if (ret) 63762306a36Sopenharmony_ci return ret; 63862306a36Sopenharmony_ci dev_private->lcr_io_base = pci_resource_start(pcidev, 1); 63962306a36Sopenharmony_ci dev->iobase = pci_resource_start(pcidev, 2); 64062306a36Sopenharmony_ci 64162306a36Sopenharmony_ci pci9111_reset(dev); 64262306a36Sopenharmony_ci 64362306a36Sopenharmony_ci if (pcidev->irq) { 64462306a36Sopenharmony_ci ret = request_irq(pcidev->irq, pci9111_interrupt, 64562306a36Sopenharmony_ci IRQF_SHARED, dev->board_name, dev); 64662306a36Sopenharmony_ci if (ret == 0) 64762306a36Sopenharmony_ci dev->irq = pcidev->irq; 64862306a36Sopenharmony_ci } 64962306a36Sopenharmony_ci 65062306a36Sopenharmony_ci dev->pacer = comedi_8254_init(dev->iobase + PCI9111_8254_BASE_REG, 65162306a36Sopenharmony_ci I8254_OSC_BASE_2MHZ, I8254_IO16, 0); 65262306a36Sopenharmony_ci if (!dev->pacer) 65362306a36Sopenharmony_ci return -ENOMEM; 65462306a36Sopenharmony_ci 65562306a36Sopenharmony_ci ret = comedi_alloc_subdevices(dev, 4); 65662306a36Sopenharmony_ci if (ret) 65762306a36Sopenharmony_ci return ret; 65862306a36Sopenharmony_ci 65962306a36Sopenharmony_ci s = &dev->subdevices[0]; 66062306a36Sopenharmony_ci s->type = COMEDI_SUBD_AI; 66162306a36Sopenharmony_ci s->subdev_flags = SDF_READABLE | SDF_COMMON; 66262306a36Sopenharmony_ci s->n_chan = 16; 66362306a36Sopenharmony_ci s->maxdata = 0xffff; 66462306a36Sopenharmony_ci s->range_table = &pci9111_ai_range; 66562306a36Sopenharmony_ci s->insn_read = pci9111_ai_insn_read; 66662306a36Sopenharmony_ci if (dev->irq) { 66762306a36Sopenharmony_ci dev->read_subdev = s; 66862306a36Sopenharmony_ci s->subdev_flags |= SDF_CMD_READ; 66962306a36Sopenharmony_ci s->len_chanlist = s->n_chan; 67062306a36Sopenharmony_ci s->do_cmdtest = pci9111_ai_do_cmd_test; 67162306a36Sopenharmony_ci s->do_cmd = pci9111_ai_do_cmd; 67262306a36Sopenharmony_ci s->cancel = pci9111_ai_cancel; 67362306a36Sopenharmony_ci s->munge = pci9111_ai_munge; 67462306a36Sopenharmony_ci } 67562306a36Sopenharmony_ci 67662306a36Sopenharmony_ci s = &dev->subdevices[1]; 67762306a36Sopenharmony_ci s->type = COMEDI_SUBD_AO; 67862306a36Sopenharmony_ci s->subdev_flags = SDF_WRITABLE | SDF_COMMON; 67962306a36Sopenharmony_ci s->n_chan = 1; 68062306a36Sopenharmony_ci s->maxdata = 0x0fff; 68162306a36Sopenharmony_ci s->len_chanlist = 1; 68262306a36Sopenharmony_ci s->range_table = &range_bipolar10; 68362306a36Sopenharmony_ci s->insn_write = pci9111_ao_insn_write; 68462306a36Sopenharmony_ci 68562306a36Sopenharmony_ci ret = comedi_alloc_subdev_readback(s); 68662306a36Sopenharmony_ci if (ret) 68762306a36Sopenharmony_ci return ret; 68862306a36Sopenharmony_ci 68962306a36Sopenharmony_ci s = &dev->subdevices[2]; 69062306a36Sopenharmony_ci s->type = COMEDI_SUBD_DI; 69162306a36Sopenharmony_ci s->subdev_flags = SDF_READABLE; 69262306a36Sopenharmony_ci s->n_chan = 16; 69362306a36Sopenharmony_ci s->maxdata = 1; 69462306a36Sopenharmony_ci s->range_table = &range_digital; 69562306a36Sopenharmony_ci s->insn_bits = pci9111_di_insn_bits; 69662306a36Sopenharmony_ci 69762306a36Sopenharmony_ci s = &dev->subdevices[3]; 69862306a36Sopenharmony_ci s->type = COMEDI_SUBD_DO; 69962306a36Sopenharmony_ci s->subdev_flags = SDF_WRITABLE; 70062306a36Sopenharmony_ci s->n_chan = 16; 70162306a36Sopenharmony_ci s->maxdata = 1; 70262306a36Sopenharmony_ci s->range_table = &range_digital; 70362306a36Sopenharmony_ci s->insn_bits = pci9111_do_insn_bits; 70462306a36Sopenharmony_ci 70562306a36Sopenharmony_ci return 0; 70662306a36Sopenharmony_ci} 70762306a36Sopenharmony_ci 70862306a36Sopenharmony_cistatic void pci9111_detach(struct comedi_device *dev) 70962306a36Sopenharmony_ci{ 71062306a36Sopenharmony_ci if (dev->iobase) 71162306a36Sopenharmony_ci pci9111_reset(dev); 71262306a36Sopenharmony_ci comedi_pci_detach(dev); 71362306a36Sopenharmony_ci} 71462306a36Sopenharmony_ci 71562306a36Sopenharmony_cistatic struct comedi_driver adl_pci9111_driver = { 71662306a36Sopenharmony_ci .driver_name = "adl_pci9111", 71762306a36Sopenharmony_ci .module = THIS_MODULE, 71862306a36Sopenharmony_ci .auto_attach = pci9111_auto_attach, 71962306a36Sopenharmony_ci .detach = pci9111_detach, 72062306a36Sopenharmony_ci}; 72162306a36Sopenharmony_ci 72262306a36Sopenharmony_cistatic int pci9111_pci_probe(struct pci_dev *dev, 72362306a36Sopenharmony_ci const struct pci_device_id *id) 72462306a36Sopenharmony_ci{ 72562306a36Sopenharmony_ci return comedi_pci_auto_config(dev, &adl_pci9111_driver, 72662306a36Sopenharmony_ci id->driver_data); 72762306a36Sopenharmony_ci} 72862306a36Sopenharmony_ci 72962306a36Sopenharmony_cistatic const struct pci_device_id pci9111_pci_table[] = { 73062306a36Sopenharmony_ci { PCI_DEVICE(PCI_VENDOR_ID_ADLINK, 0x9111) }, 73162306a36Sopenharmony_ci /* { PCI_DEVICE(PCI_VENDOR_ID_ADLINK, PCI9111_HG_DEVICE_ID) }, */ 73262306a36Sopenharmony_ci { 0 } 73362306a36Sopenharmony_ci}; 73462306a36Sopenharmony_ciMODULE_DEVICE_TABLE(pci, pci9111_pci_table); 73562306a36Sopenharmony_ci 73662306a36Sopenharmony_cistatic struct pci_driver adl_pci9111_pci_driver = { 73762306a36Sopenharmony_ci .name = "adl_pci9111", 73862306a36Sopenharmony_ci .id_table = pci9111_pci_table, 73962306a36Sopenharmony_ci .probe = pci9111_pci_probe, 74062306a36Sopenharmony_ci .remove = comedi_pci_auto_unconfig, 74162306a36Sopenharmony_ci}; 74262306a36Sopenharmony_cimodule_comedi_pci_driver(adl_pci9111_driver, adl_pci9111_pci_driver); 74362306a36Sopenharmony_ci 74462306a36Sopenharmony_ciMODULE_AUTHOR("Comedi https://www.comedi.org"); 74562306a36Sopenharmony_ciMODULE_DESCRIPTION("Comedi low-level driver"); 74662306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 747