From 50ed42513545391170b75b9a314a940782258f10 Mon Sep 17 00:00:00 2001 From: Frank Mori Hess Date: Tue, 3 Jul 2001 21:11:18 +0000 Subject: [PATCH] new driver, untested --- comedi/Config.in | 1 + comedi/drivers/Makefile | 1 + comedi/drivers/ni_at_a2150.c | 760 +++++++++++++++++++++++++++++++++++ 3 files changed, 762 insertions(+) create mode 100644 comedi/drivers/ni_at_a2150.c diff --git a/comedi/Config.in b/comedi/Config.in index ae34ed36..dabe3a2f 100644 --- a/comedi/Config.in +++ b/comedi/Config.in @@ -61,6 +61,7 @@ if [ "$CONFIG_COMEDI_NI" = "y" ];then fi fi dep_tristate ' AT-MIO-16D' CONFIG_COMEDI_NI_ATMIO16D $CONFIG_COMEDI + dep_tristate ' AT-A2150' CONFIG_COMEDI_NI_AT_A2150 $CONFIG_COMEDI fi dep_tristate 'DAS08 compatible driver' CONFIG_COMEDI_DAS08 $CONFIG_COMEDI diff --git a/comedi/drivers/Makefile b/comedi/drivers/Makefile index e9b05e90..9009226b 100644 --- a/comedi/drivers/Makefile +++ b/comedi/drivers/Makefile @@ -61,6 +61,7 @@ obj-$(CONFIG_COMEDI_NI_PCIMIO) += ni_pcimio.o obj-$(CONFIG_COMEDI_NI_PCIDIO) += ni_pcidio.o obj-$(CONFIG_COMEDI_MITE) += mite.o obj-$(CONFIG_COMEDI_NI_ATMIO16D) += ni_atmio16d.o +obj-$(CONFIG_COMEDI_NI_AT_A2150) += ni_at_a2150.o obj-$(CONFIG_COMEDI_PCM3730) += pcm3730.o diff --git a/comedi/drivers/ni_at_a2150.c b/comedi/drivers/ni_at_a2150.c new file mode 100644 index 00000000..a291052a --- /dev/null +++ b/comedi/drivers/ni_at_a2150.c @@ -0,0 +1,760 @@ +/* + ni_at_a2150.c driver for National Instruments AT-A2150 boards + Copyright (C) 2000 Frank Mori Hess + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + + 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. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +************************************************************************ + +Yet another driver for obsolete hardware brought to you by Frank Hess. + +This driver supports the boards: + +AT-A2150C +AT-A2150S + +The only difference is their master clock frequencies. + +Options: + [0] - base io address + [1] - irq + [2] - dma channel + +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "8253.h" + +#define A2150_SIZE 28 +#define A2150_DMA_BUFFER_SIZE 0xff00 // size in bytes of dma buffer + +/* Registers and bits */ +#define CONFIG_REG 0x0 +#define CHANNEL_BITS(x) ((x) & 0x7) +#define CHANNEL_MASK 0x7 +#define CLOCK_SELECT_BITS(x) (((x) & 0x3) << 3) +#define CLOCK_DIVISOR_BITS(x) (((x) & 0x3) << 5) +#define CLOCK_MASK (0xf << 3) +#define AC0_BIT 0x200 // ac couple channels 0,1 +#define AC1_BIT 0x400 // ac couple channels 2,3 +#define APD_BIT 0x800 // analog power down +#define DPD_BIT 0x1000 // digital power down +#define TRIGGER_REG 0x2 // trigger config register +#define DELAY_TRIGGER_BITS 0x3 +#define FIFO_START_REG 0x6 // software start aquistion trigger +#define FIFO_RESET_REG 0x8 // clears fifo + fifo flags +#define DMA_TC_CLEAR_REG 0xe // clear dma terminal count interrupt +#define STATUS_REG 0x12 // read only +#define FNE_BIT 0x1 // fifo not empty +#define OVFL_BIT 0x8 // fifo overflow +#define EDAQ_BIT 0x10 // end of aquisition interrupt +#define INTR_BIT 0x40 // interrupt has occured +#define DMA_TC_BIT 0x80 // dma terminal count interrupt has occured +#define ID_BITS(x) (((x) >> 8) & 0x3) +#define IRQ_DMA_CNTRL_REG 0x12 // write only +#define DMA_CHAN_BITS(x) ((x) & 0x7) // sets dma channel +#define DMA_EN_BIT 0x8 // enables dma +#define IRQ_LVL_BITS(x) (((x) & 0xf) << 4) // sets irq level +#define FIFO_INTR_EN_BIT 0x100 // enable fifo interrupts +#define FIFO_INTR_FHF_BIT 0x200 // interrupt fifo half full +#define DMA_INTR_EN_BIT 0x800 // enable interrupt on dma terminal count +#define DMA_DEM_EN_BIT 0x1000 // enables demand mode dma +#define I8253_BASE_REG 0x14 +#define I8253_MODE_REG 0x17 + +typedef struct a2150_board_struct{ + char *name; + int clock[4]; // master clock periods, in nanoseconds + int num_clocks; // number of available master clock speeds + int ai_speed; // maximum conversion rate in nanoseconds +}a2150_board; + +//analog input range +static comedi_lrange range_a2150 = { + 1, + { + RANGE( -2.828, 2.828 ), + } +}; + +// enum must match board indices +enum{a2150_c, a2150_s}; +a2150_board a2150_boards[] = +{ + { + name: "at-a2150c", + clock: {31250, 22676, 20833, 19531}, + num_clocks: 4, + ai_speed: 19531, + }, + { + name: "at-a2150s", + clock: {62500, 50000, 41667, 0}, + num_clocks: 3, + ai_speed: 41667, + }, +}; +/* + * Useful for shorthand access to the particular board structure + */ +#define thisboard ((a2150_board *)dev->board_ptr) + +typedef struct{ + volatile unsigned int count; /* number of data points left to be taken */ + volatile int forever; /* flag indicating whether we should take data forever */ + unsigned int dma; // dma channel + s16 *dma_buffer; // dma buffer + unsigned int dma_transfer_size; // size in bytes of dma transfers + int irq_dma_bits; // irq/dma register bits + int config_bits; // config register bits +}a2150_private; + +#define devpriv ((a2150_private *)dev->private) + +static int a2150_attach(comedi_device *dev,comedi_devconfig *it); +static int a2150_detach(comedi_device *dev); +static int a2150_cancel(comedi_device *dev, comedi_subdevice *s); + +comedi_driver driver_a2150={ + driver_name: "ni_at_a2150", + module: THIS_MODULE, + attach: a2150_attach, + detach: a2150_detach, +}; + +static void a2150_interrupt(int irq, void *d, struct pt_regs *regs); +static int a2150_ai_cmdtest(comedi_device *dev,comedi_subdevice *s,comedi_cmd *cmd); +static int a2150_ai_cmd(comedi_device *dev, comedi_subdevice *s); +static int a2150_ai_rinsn(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data); +static int a2150_get_timing(comedi_device *dev, unsigned int *period, int flags); +static int a2150_probe(comedi_device *dev); +static int a2150_set_chanlist(comedi_device *dev, unsigned int start_channel, unsigned int num_channels); +/* + * A convenient macro that defines init_module() and cleanup_module(), + * as necessary. + */ +COMEDI_INITCLEANUP(driver_a2150); + +/* interrupt service routine */ +static void a2150_interrupt(int irq, void *d, struct pt_regs *regs) +{ + int i; + int status; + unsigned long flags; + comedi_device *dev = d; + comedi_subdevice *s = dev->read_subdev; + comedi_async *async; + comedi_cmd *cmd; + unsigned int max_points, num_points, residue, leftover; + sampl_t dpnt; + static const int sample_size = sizeof(devpriv->dma_buffer[0]); + + if(dev->attached == 0) + { + comedi_error(dev, "premature interrupt"); + return; + } + // initialize async here to make sure s is not NULL + async = s->async; + cmd = &async->cmd; + + status = inw(dev->iobase + STATUS_REG); + + if((status & INTR_BIT ) == 0) + { + comedi_error(dev, "spurious interrupt"); + return; + } + + if(status & OVFL_BIT) + { + comedi_error(dev, "fifo overflow"); + a2150_cancel(dev, s); + async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + } + + if((status & DMA_TC_BIT) == 0) + { + comedi_error(dev, "caught non-dma interrupt? Aborting."); + a2150_cancel(dev, s); + async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + comedi_event(dev, s, async->events); + async->events = 0; + return; + } + + flags = claim_dma_lock(); + disable_dma(devpriv->dma); + /* clear flip-flop to make sure 2-byte registers for + * count and address get set correctly */ + clear_dma_ff(devpriv->dma); + + // figure out how many points to read + max_points = devpriv->dma_transfer_size / sample_size; + /* residue is the number of points left to be done on the dma + * transfer. It should always be zero at this point unless + * the stop_src is set to external triggering. + */ + residue = get_dma_residue(devpriv->dma) / sample_size; + num_points = max_points - residue; + if(devpriv->count < num_points && + cmd->stop_src == TRIG_COUNT) + num_points = devpriv->count; + + // figure out how many points will be stored next time + leftover = 0; + if(cmd->stop_src == TRIG_NONE) + { + leftover = devpriv->dma_transfer_size / sample_size; + }else if(devpriv->count > max_points) + { + leftover = devpriv->count - max_points; + if(leftover > max_points) + leftover = max_points; + } + /* there should only be a residue if collection was stopped by having + * the stop_src set to an external trigger, in which case there + * will be no more data + */ + if(residue) + leftover = 0; + + for(i = 0; i < num_points; i++) + { + /* write data point to comedi buffer */ + dpnt = devpriv->dma_buffer[i]; + // convert from 2's complement to unsigned coding + dpnt += 0x8000; + comedi_buf_put(async, dpnt); + if(devpriv->count > 0) devpriv->count--; + } + // re-enable dma + set_dma_addr(devpriv->dma, (unsigned int) devpriv->dma_buffer); + set_dma_count(devpriv->dma, leftover * sample_size); + enable_dma(devpriv->dma); + release_dma_lock(flags); + + async->events |= COMEDI_CB_BLOCK; + + if(devpriv->count == 0) + { /* end of acquisition */ + a2150_cancel(dev, s); + async->events |= COMEDI_CB_EOA; + } + + comedi_event(dev, s, async->events); + async->events = 0; + + /* clear interrupt */ + outb(0x00, dev->iobase + DMA_TC_CLEAR_REG); + + return; +} + +// probes board type, returns offset +static int a2150_probe(comedi_device *dev) +{ + int status = inw(dev->iobase + STATUS_REG); + return ID_BITS(status); +} + +static int a2150_attach(comedi_device *dev, comedi_devconfig *it) +{ + comedi_subdevice *s; + int iobase = it->options[0]; + int irq = it->options[1]; + int dma = it->options[2]; + + printk("comedi%d: %s: io 0x%x", dev->minor, driver_a2150.driver_name, iobase); + if(irq) + { + printk(", irq %i", irq); + }else + { + printk(", no irq"); + } + if(dma) + { + printk(", dma %i", dma); + }else + { + printk(", no dma"); + } + printk("\n"); + + /* allocate and initialize dev->private */ + if(alloc_private(dev, sizeof(a2150_private)) < 0) + return -ENOMEM; + + if(iobase == 0) + { + printk(" io base address required\n"); + return -EINVAL; + } + + /* check if io addresses are available */ + if(check_region(iobase, A2150_SIZE) < 0) + { + printk(" I/O port conflict\n"); + return -EIO; + } + request_region(iobase, A2150_SIZE, driver_a2150.driver_name); + dev->iobase = iobase; + + /* grab our IRQ */ + if(irq) + { + // check that irq is supported + if(irq < 3 || irq == 8 || irq == 13 || irq > 15) + { + printk(" invalid irq line\n"); + return -EINVAL; + } + if(comedi_request_irq( irq, a2150_interrupt, 0, driver_a2150.driver_name, dev )) + { + printk( "unable to allocate irq %d\n", irq); + return -EINVAL; + } + devpriv->irq_dma_bits |= IRQ_LVL_BITS(irq); + dev->irq = irq; + } + + // initialize dma + if(dma) + { + if(dma < 0 || dma == 4 || dma > 7) + { + printk(" invalid dma channel %i\n", dma); + return -EINVAL; + } + if(request_dma(dma, driver_a2150.driver_name)) + { + printk(" failed to allocate dma channel %i\n", dma); + return -EINVAL; + } + devpriv->dma_buffer = kmalloc(A2150_DMA_BUFFER_SIZE, GFP_BUFFER | GFP_DMA); + if(devpriv->dma_buffer == NULL) + return -ENOMEM; + + disable_dma(dma); + set_dma_mode(dma, DMA_MODE_READ); + + devpriv->dma = dma; + devpriv->irq_dma_bits |= DMA_DEM_EN_BIT | DMA_CHAN_BITS(dma); + } + + dev->board_ptr = a2150_boards + a2150_probe(dev); + dev->board_name = thisboard->name; + + dev->n_subdevices = 1; + if(alloc_subdevices(dev) < 0) + return -ENOMEM; + + /* analog input subdevice */ + s = dev->subdevices + 0; + dev->read_subdev = s; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 4; + s->len_chanlist = 4; + s->maxdata = 0xffff; + s->range_table = &range_a2150; + s->do_cmd = a2150_ai_cmd; + s->do_cmdtest = a2150_ai_cmdtest; +// s->insn_read = a2150_ai_rinsn; XXX + s->cancel = a2150_cancel; + + // set card's irq and dma levels + outw(devpriv->irq_dma_bits, dev->iobase + IRQ_DMA_CNTRL_REG); + // initialize configuration register + devpriv->config_bits = 0; + outw(devpriv->config_bits, dev->iobase + CONFIG_REG); + + return 0; +}; + +static int a2150_detach(comedi_device *dev) +{ + printk("comedi%d: %s: remove\n", dev->minor, driver_a2150.driver_name); + + // put board in power-down mode + outw(APD_BIT | DPD_BIT, dev->iobase + CONFIG_REG); + + /* only free stuff if it has been allocated by _attach */ + if(dev->iobase) + release_region(dev->iobase, A2150_SIZE); + if(dev->irq) + comedi_free_irq(dev->irq, dev); + if(devpriv) + { + if(devpriv->dma) + free_dma(devpriv->dma); + if(devpriv->dma_buffer) + kfree(devpriv->dma_buffer); + } + + return 0; +}; + +static int a2150_cancel(comedi_device *dev, comedi_subdevice *s) +{ + // disable dma on card + devpriv->irq_dma_bits &= ~DMA_INTR_EN_BIT & ~DMA_EN_BIT; + outw(devpriv->irq_dma_bits, dev->iobase + IRQ_DMA_CNTRL_REG); + + // disable computer's dma + disable_dma(devpriv->dma); + + // clear fifo and reset triggering circuitry + outw(0, dev->iobase + FIFO_RESET_REG); + + return 0; +} + +static int a2150_ai_cmdtest(comedi_device *dev,comedi_subdevice *s,comedi_cmd *cmd) +{ + int err = 0; + int tmp; + int startChan; + int i; + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW; // | TRIG_EXT; + if(!cmd->start_src || tmp != cmd->start_src) err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_TIMER; + if(!cmd->scan_begin_src || tmp != cmd->scan_begin_src) err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_NOW; + if(!cmd->convert_src || tmp != cmd->convert_src) err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if(!cmd->scan_end_src || tmp != cmd->scan_end_src) err++; + + tmp=cmd->stop_src; + cmd->stop_src &= TRIG_COUNT;// | TRIG_NONE; + if(!cmd->stop_src || tmp!=cmd->stop_src) err++; + + if(err) return 1; + + /* step 2: make sure trigger sources are unique and mutually compatible */ + + if(cmd->start_src != TRIG_NOW && + cmd->start_src != TRIG_EXT) err++; + if(cmd->stop_src != TRIG_COUNT && + cmd->stop_src != TRIG_NONE) err++; + + if(err)return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if(cmd->start_arg != 0) + { + cmd->start_arg = 0; + err++; + } + if(cmd->convert_src == TRIG_TIMER) + { + if(cmd->convert_arg < thisboard->ai_speed) + { + cmd->convert_arg = thisboard->ai_speed; + err++; + } + } + if(!cmd->chanlist_len) + { + cmd->chanlist_len = 1; + err++; + } + if(cmd->scan_end_arg != cmd->chanlist_len) + { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + if(cmd->stop_src == TRIG_COUNT) + { + if(!cmd->stop_arg) + { + cmd->stop_arg = 1; + err++; + } + } else + { /* TRIG_NONE */ + if(cmd->stop_arg != 0) + { + cmd->stop_arg = 0; + err++; + } + } + + // check channel/gain list against card's limitations + startChan = CR_CHAN(cmd->chanlist[0]); + for(i = 1; i < cmd->chanlist_len; i++) + { + if(CR_CHAN(cmd->chanlist[i]) != (startChan + i)) + { + comedi_error(dev, "entries in chanlist must be consecutive channels, counting upwards\n"); + err++; + } + } + if(cmd->chanlist_len == 2 && CR_CHAN(cmd->chanlist[0]) == 1) + { + comedi_error(dev, "length 2 chanlist must be channels 0,1 or channels 2,3"); + err++; + } + if(cmd->chanlist_len == 3) + { + comedi_error(dev, "chanlist must have 1,2 or 4 channels"); + err++; + } + if(CR_AREF(cmd->chanlist[0]) != CR_AREF(cmd->chanlist[1]) || + CR_AREF(cmd->chanlist[2]) != CR_AREF(cmd->chanlist[3])) + { + comedi_error(dev, "channels 0/1 and 2/3 must have the same analog reference"); + err++; + } + + if(err)return 3; + + /* step 4: fix up any arguments */ + + if(cmd->scan_begin_src == TRIG_TIMER) + { + tmp = cmd->scan_begin_arg; + a2150_get_timing(dev, &cmd->scan_begin_arg, cmd->flags); + if(tmp != cmd->convert_arg) err++; + } + + if(err)return 4; + + return 0; +} + +static int a2150_ai_cmd(comedi_device *dev, comedi_subdevice *s) +{ + comedi_async *async = s->async; + comedi_cmd *cmd = &async->cmd; + unsigned long lock_flags; + + if(!dev->irq || !devpriv->dma) + { + comedi_error(dev, " irq and dma required, cannot do hardware conversions"); + return -1; + } + if(cmd->flags & TRIG_RT) + { + comedi_error(dev, " dma incompatible with hard real-time interrupt (TRIG_RT), aborting"); + return -1; + } + + // clear fifo and reset triggering circuitry + outw(0, dev->iobase + FIFO_RESET_REG); + + /* setup chanlist */ + if(a2150_set_chanlist(dev, CR_CHAN(cmd->chanlist[0]), cmd->chanlist_len) < 0) + return -1; + + // setup ac/dc coupling + if(CR_AREF(cmd->chanlist[0]) == AREF_DIFF) + devpriv->config_bits |= AC0_BIT; + else + devpriv->config_bits &= ~AC0_BIT; + if(CR_AREF(cmd->chanlist[2]) == AREF_DIFF) + devpriv->config_bits |= AC1_BIT; + else + devpriv->config_bits &= ~AC1_BIT; + + // setup timing + a2150_get_timing(dev, &cmd->scan_begin_arg, cmd->flags); + + // send timing, channel, config bits + outw(devpriv->config_bits, dev->iobase + CONFIG_REG); + + // initialize number of samples remaining + devpriv->count = cmd->stop_arg * cmd->chanlist_len; + + // enable computer's dma + lock_flags = claim_dma_lock(); + disable_dma(devpriv->dma); + /* clear flip-flop to make sure 2-byte registers for + * count and address get set correctly */ + clear_dma_ff(devpriv->dma); + set_dma_addr(devpriv->dma, (unsigned int) devpriv->dma_buffer); + // set size of transfer to fill in 1/3 second + devpriv->dma_transfer_size = sizeof(devpriv->dma_buffer[0]) * cmd->chanlist_len * + (1000000000.0 / 3.0 ) / cmd->scan_begin_arg; + if(devpriv->dma_transfer_size > A2150_DMA_BUFFER_SIZE) + devpriv->dma_transfer_size = A2150_DMA_BUFFER_SIZE; + if(devpriv->dma_transfer_size < sizeof(devpriv->dma_buffer[0])) + devpriv->dma_transfer_size = sizeof(devpriv->dma_buffer[0]); + devpriv->dma_transfer_size -= devpriv->dma_transfer_size % sizeof(devpriv->dma_buffer[0]); + set_dma_count(devpriv->dma, devpriv->dma_transfer_size); + enable_dma(devpriv->dma); + release_dma_lock(lock_flags); + + // enable dma on card + devpriv->irq_dma_bits |= DMA_INTR_EN_BIT | DMA_EN_BIT; + outw(devpriv->irq_dma_bits, dev->iobase + IRQ_DMA_CNTRL_REG); + + // need to wait 72 sampling periods after changing timing + i8254_load(dev->iobase + I8253_BASE_REG, 2, 72, 0); + // set trigger source to delay trigger + outw(DELAY_TRIGGER_BITS, dev->iobase + TRIGGER_REG); + + async->events = 0; + + // start aquisition + outw(0, dev->iobase + FIFO_START_REG); + + return 0; +} + +static int a2150_ai_rinsn(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data) +{ +// XXX + return 0; +} + +/* sets bits in devpriv->clock_bits to nearest approximation of requested period, + * adjusts requested period to actual timing. */ +static int a2150_get_timing(comedi_device *dev, unsigned int *period, int flags) +{ + int lub, glb, temp; + int lub_divisor_shift, lub_index, glb_divisor_shift, glb_index; + int i, j; + + // initialize greatest upper and least lower bounds + lub_divisor_shift = 3; + lub_index = 0; + lub = thisboard->clock[lub_index] * (1 << lub_divisor_shift); + glb_divisor_shift = 0; + glb_index = thisboard->num_clocks - 1; + glb = thisboard->clock[glb_index] * (1 << lub_divisor_shift); + + // we can multiply period by 1, 2, 4, or 8, using (1 << i) + for(i = 0; i < 4; i = i++) + { + // there are a maximum of 4 master clocks + for(j = 0; j < 4; j++) + { + if(thisboard->clock[j] == 0) break; + // temp is the period in nanosec we are evaluating + temp = thisboard->clock[j] * (1 << i); + // if it is the best match yet + if(temp < lub && temp >= *period) + { + lub_divisor_shift = i; + lub_index = j; + lub = temp; + } + if(temp > glb && temp <= *period) + { + glb_divisor_shift = i; + glb_index = j; + glb = temp; + } + } + } + flags &= TRIG_ROUND_MASK; + switch (flags) + { + case TRIG_ROUND_NEAREST: + default: + // if least upper bound is better approximation + if(lub - *period < *period - glb) + { + *period = lub; + }else + { + *period = glb; + } + break; + case TRIG_ROUND_UP: + *period = lub; + break; + case TRIG_ROUND_DOWN: + *period = glb; + break; + } + + // set clock bits for config register appropriately + devpriv->config_bits &= ~CLOCK_MASK; + if(*period == lub) + { + devpriv->config_bits |= CLOCK_SELECT_BITS(lub_index) | CLOCK_DIVISOR_BITS(lub_divisor_shift); + }else + { + devpriv->config_bits |= CLOCK_SELECT_BITS(glb_index) | CLOCK_DIVISOR_BITS(glb_divisor_shift); + } + + return 0; +} + +static int a2150_set_chanlist(comedi_device *dev, unsigned int start_channel, unsigned int num_channels) +{ + if(start_channel + num_channels > 4) + return -1; + + devpriv->config_bits &= ~CHANNEL_MASK; + + switch(num_channels) + { + case 1: + devpriv->config_bits |= CHANNEL_BITS(0x100 | start_channel); + break; + case 2: + if(start_channel == 0) + { + devpriv->config_bits |= CHANNEL_BITS(0x010); + }else if(start_channel == 2) + { + devpriv->config_bits |= CHANNEL_BITS(0x011); + }else + { + return -1; + } + break; + case 4: + devpriv->config_bits |= CHANNEL_BITS(0x001); + break; + default: + return -1; + break; + } + + return 0; +} + -- 2.26.2