From a9c5b6ad9d3f3c81ff9252bcdfa8f5a154ab8367 Mon Sep 17 00:00:00 2001 From: David Schleef Date: Thu, 19 Feb 2009 09:58:16 -0800 Subject: Staging: comedi: add das16 driver From: David Schleef Driver for DAS16 compatible boards From: David Schleef Cc: Ian Abbott Cc: Frank Mori Hess Signed-off-by: Greg Kroah-Hartman --- drivers/staging/comedi/drivers/das16.c | 1730 +++++++++++++++++++++++++++++++++ 1 file changed, 1730 insertions(+) --- /dev/null +++ b/drivers/staging/comedi/drivers/das16.c @@ -0,0 +1,1730 @@ +/* + comedi/drivers/das16.c + DAS16 driver + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + Copyright (C) 2000 Chris R. Baugher + Copyright (C) 2001,2002 Frank Mori Hess + + 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. + +************************************************************************ +*/ +/* +Driver: das16 +Description: DAS16 compatible boards +Author: Sam Moore, Warren Jasper, ds, Chris Baugher, Frank Hess, Roman Fietze +Devices: [Keithley Metrabyte] DAS-16 (das-16), DAS-16G (das-16g), + DAS-16F (das-16f), DAS-1201 (das-1201), DAS-1202 (das-1202), + DAS-1401 (das-1401), DAS-1402 (das-1402), DAS-1601 (das-1601), + DAS-1602 (das-1602), + [ComputerBoards] PC104-DAS16/JR (pc104-das16jr), + PC104-DAS16JR/16 (pc104-das16jr/16), + CIO-DAS16JR/16 (cio-das16jr/16), + CIO-DAS16/JR (cio-das16/jr), CIO-DAS1401/12 (cio-das1401/12), + CIO-DAS1402/12 (cio-das1402/12), CIO-DAS1402/16 (cio-das1402/16), + CIO-DAS1601/12 (cio-das1601/12), CIO-DAS1602/12 (cio-das1602/12), + CIO-DAS1602/16 (cio-das1602/16), CIO-DAS16/330 (cio-das16/330) +Status: works +Updated: 2003-10-12 + +A rewrite of the das16 and das1600 drivers. +Options: + [0] - base io address + [1] - irq (does nothing, irq is not used anymore) + [2] - dma (optional, required for comedi_command support) + [3] - master clock speed in MHz (optional, 1 or 10, ignored if + board can probe clock, defaults to 1) + [4] - analog input range lowest voltage in microvolts (optional, + only useful if your board does not have software + programmable gain) + [5] - analog input range highest voltage in microvolts (optional, + only useful if board does not have software programmable + gain) + [6] - analog output range lowest voltage in microvolts (optional) + [7] - analog output range highest voltage in microvolts (optional) + [8] - use timer mode for DMA. Timer mode is needed e.g. for + buggy DMA controllers in NS CS5530A (Geode Companion), and for + 'jr' cards that lack a hardware fifo. This option is no + longer needed, since timer mode is _always_ used. + +Passing a zero for an option is the same as leaving it unspecified. + +*/ +/* + +Testing and debugging help provided by Daniel Koch. + +Keithley Manuals: + 2309.PDF (das16) + 4919.PDF (das1400, 1600) + 4922.PDF (das-1400) + 4923.PDF (das1200, 1400, 1600) + +Computer boards manuals also available from their website www.measurementcomputing.com + +*/ + +#include +#include +#include "../comedidev.h" + +#include "8253.h" +#include "8255.h" +#include "comedi_fc.h" + +#undef DEBUG +//#define DEBUG + +#ifdef DEBUG +#define DEBUG_PRINT(format, args...) rt_printk("das16: " format, ## args) +#else +#define DEBUG_PRINT(format, args...) +#endif + +#define DAS16_SIZE 20 // number of ioports +#define DAS16_DMA_SIZE 0xff00 // size in bytes of allocated dma buffer + +/* + cio-das16.pdf + + "das16" + "das16/f" + + 0 a/d bits 0-3 start 12 bit + 1 a/d bits 4-11 unused + 2 mux read mux set + 3 di 4 bit do 4 bit + 4 unused ao0_lsb + 5 unused ao0_msb + 6 unused ao1_lsb + 7 unused ao1_msb + 8 status eoc uni/bip interrupt reset + 9 dma, int, trig ctrl set dma, int + a pacer control unused + b reserved reserved + cdef 8254 + 0123 8255 + +*/ + +/* + cio-das16jr.pdf + + "das16jr" + + 0 a/d bits 0-3 start 12 bit + 1 a/d bits 4-11 unused + 2 mux read mux set + 3 di 4 bit do 4 bit + 4567 unused unused + 8 status eoc uni/bip interrupt reset + 9 dma, int, trig ctrl set dma, int + a pacer control unused + b gain status gain control + cdef 8254 + +*/ + +/* + cio-das16jr_16.pdf + + "das16jr_16" + + 0 a/d bits 0-7 start 16 bit + 1 a/d bits 8-15 unused + 2 mux read mux set + 3 di 4 bit do 4 bit + 4567 unused unused + 8 status eoc uni/bip interrupt reset + 9 dma, int, trig ctrl set dma, int + a pacer control unused + b gain status gain control + cdef 8254 + +*/ +/* + cio-das160x-1x.pdf + + "das1601/12" + "das1602/12" + "das1602/16" + + 0 a/d bits 0-3 start 12 bit + 1 a/d bits 4-11 unused + 2 mux read mux set + 3 di 4 bit do 4 bit + 4 unused ao0_lsb + 5 unused ao0_msb + 6 unused ao1_lsb + 7 unused ao1_msb + 8 status eoc uni/bip interrupt reset + 9 dma, int, trig ctrl set dma, int + a pacer control unused + b gain status gain control + cdef 8254 + 400 8255 + 404 unused conversion enable + 405 unused burst enable + 406 unused das1600 enable + 407 status + +*/ + +static const int sample_size = 2; // size in bytes of a sample from board + +#define DAS16_TRIG 0 +#define DAS16_AI_LSB 0 +#define DAS16_AI_MSB 1 +#define DAS16_MUX 2 +#define DAS16_DIO 3 +#define DAS16_AO_LSB(x) ((x)?6:4) +#define DAS16_AO_MSB(x) ((x)?7:5) +#define DAS16_STATUS 8 +#define BUSY (1<<7) +#define UNIPOLAR (1<<6) +#define DAS16_MUXBIT (1<<5) +#define DAS16_INT (1<<4) +#define DAS16_CONTROL 9 +#define DAS16_INTE (1<<7) +#define DAS16_IRQ(x) (((x) & 0x7) << 4) +#define DMA_ENABLE (1<<2) +#define PACING_MASK 0x3 +#define INT_PACER 0x03 +#define EXT_PACER 0x02 +#define DAS16_SOFT 0x00 +#define DAS16_PACER 0x0A +#define DAS16_CTR0 (1<<1) +#define DAS16_TRIG0 (1<<0) +#define BURST_LEN_BITS(x) (((x) & 0xf) << 4) +#define DAS16_GAIN 0x0B +#define DAS16_CNTR0_DATA 0x0C +#define DAS16_CNTR1_DATA 0x0D +#define DAS16_CNTR2_DATA 0x0E +#define DAS16_CNTR_CONTROL 0x0F +#define DAS16_TERM_CNT 0x00 +#define DAS16_ONE_SHOT 0x02 +#define DAS16_RATE_GEN 0x04 +#define DAS16_CNTR_LSB_MSB 0x30 +#define DAS16_CNTR0 0x00 +#define DAS16_CNTR1 0x40 +#define DAS16_CNTR2 0x80 + +#define DAS1600_CONV 0x404 +#define DAS1600_CONV_DISABLE 0x40 +#define DAS1600_BURST 0x405 +#define DAS1600_BURST_VAL 0x40 +#define DAS1600_ENABLE 0x406 +#define DAS1600_ENABLE_VAL 0x40 +#define DAS1600_STATUS_B 0x407 +#define DAS1600_BME 0x40 +#define DAS1600_ME 0x20 +#define DAS1600_CD 0x10 +#define DAS1600_WS 0x02 +#define DAS1600_CLK_10MHZ 0x01 + +static const comedi_lrange range_das1x01_bip = { 4, { + BIP_RANGE(10), + BIP_RANGE(1), + BIP_RANGE(0.1), + BIP_RANGE(0.01), + } +}; +static const comedi_lrange range_das1x01_unip = { 4, { + UNI_RANGE(10), + UNI_RANGE(1), + UNI_RANGE(0.1), + UNI_RANGE(0.01), + } +}; +static const comedi_lrange range_das1x02_bip = { 4, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + } +}; +static const comedi_lrange range_das1x02_unip = { 4, { + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25), + } +}; +static const comedi_lrange range_das16jr = { 9, { + // also used by 16/330 + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + BIP_RANGE(0.625), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25), + } +}; +static const comedi_lrange range_das16jr_16 = { 8, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25), + } +}; + +static const int das16jr_gainlist[] = { 8, 0, 1, 2, 3, 4, 5, 6, 7 }; +static const int das16jr_16_gainlist[] = { 0, 1, 2, 3, 4, 5, 6, 7 }; +static const int das1600_gainlist[] = { 0, 1, 2, 3 }; +enum { + das16_pg_none = 0, + das16_pg_16jr, + das16_pg_16jr_16, + das16_pg_1601, + das16_pg_1602, +}; +static const int *const das16_gainlists[] = { + NULL, + das16jr_gainlist, + das16jr_16_gainlist, + das1600_gainlist, + das1600_gainlist, +}; +static const comedi_lrange *const das16_ai_uni_lranges[] = { + &range_unknown, + &range_das16jr, + &range_das16jr_16, + &range_das1x01_unip, + &range_das1x02_unip, +}; +static const comedi_lrange *const das16_ai_bip_lranges[] = { + &range_unknown, + &range_das16jr, + &range_das16jr_16, + &range_das1x01_bip, + &range_das1x02_bip, +}; + +struct munge_info { + uint8_t byte; + unsigned have_byte:1; +}; + +static int das16_ao_winsn(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data); +static int das16_do_wbits(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data); +static int das16_di_rbits(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data); +static int das16_ai_rinsn(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data); + +static int das16_cmd_test(comedi_device * dev, comedi_subdevice * s, + comedi_cmd * cmd); +static int das16_cmd_exec(comedi_device * dev, comedi_subdevice * s); +static int das16_cancel(comedi_device * dev, comedi_subdevice * s); +static void das16_ai_munge(comedi_device * dev, comedi_subdevice * s, + void *array, unsigned int num_bytes, unsigned int start_chan_index); + +static void das16_reset(comedi_device * dev); +static irqreturn_t das16_dma_interrupt(int irq, void *d PT_REGS_ARG); +static void das16_timer_interrupt(unsigned long arg); +static void das16_interrupt(comedi_device * dev); + +static unsigned int das16_set_pacer(comedi_device * dev, unsigned int ns, + int flags); +static int das1600_mode_detect(comedi_device * dev); +static unsigned int das16_suggest_transfer_size(comedi_device * dev, + comedi_cmd cmd); + +static void reg_dump(comedi_device * dev); + +typedef struct das16_board_struct { + const char *name; + void *ai; + unsigned int ai_nbits; + unsigned int ai_speed; // max conversion speed in nanosec + unsigned int ai_pg; + void *ao; + unsigned int ao_nbits; + void *di; + void *do_; + + unsigned int i8255_offset; + unsigned int i8254_offset; + + unsigned int size; + unsigned int id; +} das16_board; + +static const struct das16_board_struct das16_boards[] = { + { + name: "das-16", + ai: das16_ai_rinsn, + ai_nbits:12, + ai_speed:15000, + ai_pg: das16_pg_none, + ao: das16_ao_winsn, + ao_nbits:12, + di: das16_di_rbits, + do_: das16_do_wbits, + i8255_offset:0x10, + i8254_offset:0x0c, + size: 0x14, + id: 0x00, + }, + { + name: "das-16g", + ai: das16_ai_rinsn, + ai_nbits:12, + ai_speed:15000, + ai_pg: das16_pg_none, + ao: das16_ao_winsn, + ao_nbits:12, + di: das16_di_rbits, + do_: das16_do_wbits, + i8255_offset:0x10, + i8254_offset:0x0c, + size: 0x14, + id: 0x00, + }, + { + name: "das-16f", + ai: das16_ai_rinsn, + ai_nbits:12, + ai_speed:8500, + ai_pg: das16_pg_none, + ao: das16_ao_winsn, + ao_nbits:12, + di: das16_di_rbits, + do_: das16_do_wbits, + i8255_offset:0x10, + i8254_offset:0x0c, + size: 0x14, + id: 0x00, + }, + { + name: "cio-das16", // cio-das16.pdf + ai: das16_ai_rinsn, + ai_nbits:12, + ai_speed:20000, + ai_pg: das16_pg_none, + ao: das16_ao_winsn, + ao_nbits:12, + di: das16_di_rbits, + do_: das16_do_wbits, + i8255_offset:0x10, + i8254_offset:0x0c, + size: 0x14, + id: 0x80, + }, + { + name: "cio-das16/f", // das16.pdf + ai: das16_ai_rinsn, + ai_nbits:12, + ai_speed:10000, + ai_pg: das16_pg_none, + ao: das16_ao_winsn, + ao_nbits:12, + di: das16_di_rbits, + do_: das16_do_wbits, + i8255_offset:0x10, + i8254_offset:0x0c, + size: 0x14, + id: 0x80, + }, + { + name: "cio-das16/jr", // cio-das16jr.pdf + ai: das16_ai_rinsn, + ai_nbits:12, + ai_speed:7692, + ai_pg: das16_pg_16jr, + ao: NULL, + di: das16_di_rbits, + do_: das16_do_wbits, + i8255_offset:0, + i8254_offset:0x0c, + size: 0x10, + id: 0x00, + }, + { + name: "pc104-das16jr", // pc104-das16jr_xx.pdf + ai: das16_ai_rinsn, + ai_nbits:12, + ai_speed:3300, + ai_pg: das16_pg_16jr, + ao: NULL, + di: das16_di_rbits, + do_: das16_do_wbits, + i8255_offset:0, + i8254_offset:0x0c, + size: 0x10, + id: 0x00, + }, + { + name: "cio-das16jr/16", // cio-das16jr_16.pdf + ai: das16_ai_rinsn, + ai_nbits:16, + ai_speed:10000, + ai_pg: das16_pg_16jr_16, + ao: NULL, + di: das16_di_rbits, + do_: das16_do_wbits, + i8255_offset:0, + i8254_offset:0x0c, + size: 0x10, + id: 0x00, + }, + { + name: "pc104-das16jr/16", // pc104-das16jr_xx.pdf + ai: das16_ai_rinsn, + ai_nbits:16, + ai_speed:10000, + ai_pg: das16_pg_16jr_16, + ao: NULL, + di: das16_di_rbits, + do_: das16_do_wbits, + i8255_offset:0, + i8254_offset:0x0c, + size: 0x10, + id: 0x00, + }, + { + name: "das-1201", // 4924.pdf (keithley user's manual) + ai: das16_ai_rinsn, + ai_nbits:12, + ai_speed:20000, + ai_pg: das16_pg_none, + ao: NULL, + di: das16_di_rbits, + do_: das16_do_wbits, + i8255_offset:0x400, + i8254_offset:0x0c, + size: 0x408, + id: 0x20, + }, + { + name: "das-1202", // 4924.pdf (keithley user's manual) + ai: das16_ai_rinsn, + ai_nbits:12, + ai_speed:10000, + ai_pg: das16_pg_none, + ao: NULL, + di: das16_di_rbits, + do_: das16_do_wbits, + i8255_offset:0x400, + i8254_offset:0x0c, + size: 0x408, + id: 0x20, + }, + { + name: "das-1401", // 4919.pdf and 4922.pdf (keithley user's manual) + ai: das16_ai_rinsn, + ai_nbits:12, + ai_speed:10000, + ai_pg: das16_pg_1601, + ao: NULL, + di: das16_di_rbits, + do_: das16_do_wbits, + i8255_offset:0x0, + i8254_offset:0x0c, + size: 0x408, + id: 0xc0 // 4919.pdf says id bits are 0xe0, 4922.pdf says 0xc0 + }, + { + name: "das-1402", // 4919.pdf and 4922.pdf (keithley user's manual) + ai: das16_ai_rinsn, + ai_nbits:12, + ai_speed:10000, + ai_pg: das16_pg_1602, + ao: NULL, + di: das16_di_rbits, + do_: das16_do_wbits, + i8255_offset:0x0, + i8254_offset:0x0c, + size: 0x408, + id: 0xc0 // 4919.pdf says id bits are 0xe0, 4922.pdf says 0xc0 + }, + { + name: "das-1601", // 4919.pdf + ai: das16_ai_rinsn, + ai_nbits:12, + ai_speed:10000, + ai_pg: das16_pg_1601, + ao: das16_ao_winsn, + ao_nbits:12, + di: das16_di_rbits, + do_: das16_do_wbits, + i8255_offset:0x400, + i8254_offset:0x0c, + size: 0x408, + id: 0xc0}, + { + name: "das-1602", // 4919.pdf + ai: das16_ai_rinsn, + ai_nbits:12, + ai_speed:10000, + ai_pg: das16_pg_1602, + ao: das16_ao_winsn, + ao_nbits:12, + di: das16_di_rbits, + do_: das16_do_wbits, + i8255_offset:0x400, + i8254_offset:0x0c, + size: 0x408, + id: 0xc0}, + { + name: "cio-das1401/12", // cio-das1400_series.pdf + ai: das16_ai_rinsn, + ai_nbits:12, + ai_speed:6250, + ai_pg: das16_pg_1601, + ao: NULL, + di: das16_di_rbits, + do_: das16_do_wbits, + i8255_offset:0, + i8254_offset:0x0c, + size: 0x408, + id: 0xc0}, + { + name: "cio-das1402/12", // cio-das1400_series.pdf + ai: das16_ai_rinsn, + ai_nbits:12, + ai_speed:6250, + ai_pg: das16_pg_1602, + ao: NULL, + di: das16_di_rbits, + do_: das16_do_wbits, + i8255_offset:0, + i8254_offset:0x0c, + size: 0x408, + id: 0xc0}, + { + name: "cio-das1402/16", // cio-das1400_series.pdf + ai: das16_ai_rinsn, + ai_nbits:16, + ai_speed:10000, + ai_pg: das16_pg_1602, + ao: NULL, + di: das16_di_rbits, + do_: das16_do_wbits, + i8255_offset:0, + i8254_offset:0x0c, + size: 0x408, + id: 0xc0}, + { + name: "cio-das1601/12", // cio-das160x-1x.pdf + ai: das16_ai_rinsn, + ai_nbits:12, + ai_speed:6250, + ai_pg: das16_pg_1601, + ao: das16_ao_winsn, + ao_nbits:12, + di: das16_di_rbits, + do_: das16_do_wbits, + i8255_offset:0x400, + i8254_offset:0x0c, + size: 0x408, + id: 0xc0}, + { + name: "cio-das1602/12", // cio-das160x-1x.pdf + ai: das16_ai_rinsn, + ai_nbits:12, + ai_speed:10000, + ai_pg: das16_pg_1602, + ao: das16_ao_winsn, + ao_nbits:12, + di: das16_di_rbits, + do_: das16_do_wbits, + i8255_offset:0x400, + i8254_offset:0x0c, + size: 0x408, + id: 0xc0}, + { + name: "cio-das1602/16", // cio-das160x-1x.pdf + ai: das16_ai_rinsn, + ai_nbits:16, + ai_speed:10000, + ai_pg: das16_pg_1602, + ao: das16_ao_winsn, + ao_nbits:12, + di: das16_di_rbits, + do_: das16_do_wbits, + i8255_offset:0x400, + i8254_offset:0x0c, + size: 0x408, + id: 0xc0}, + { + name: "cio-das16/330", // ? + ai: das16_ai_rinsn, + ai_nbits:12, + ai_speed:3030, + ai_pg: das16_pg_16jr, + ao: NULL, + di: das16_di_rbits, + do_: das16_do_wbits, + i8255_offset:0, + i8254_offset:0x0c, + size: 0x14, + id: 0xf0}, +#if 0 + { + name: "das16/330i", // ? + }, + { + name: "das16/jr/ctr5", // ? + }, + { + name: "cio-das16/m1/16", // cio-das16_m1_16.pdf, this board is a bit quirky, no dma + }, +#endif +}; + +#define n_das16_boards ((sizeof(das16_boards))/(sizeof(das16_board))) + +static int das16_attach(comedi_device * dev, comedi_devconfig * it); +static int das16_detach(comedi_device * dev); +static comedi_driver driver_das16 = { + driver_name:"das16", + module:THIS_MODULE, + attach:das16_attach, + detach:das16_detach, + board_name:&das16_boards[0].name, + num_names:n_das16_boards, + offset:sizeof(das16_boards[0]), +}; + +#define DAS16_TIMEOUT 1000 + +/* Period for timer interrupt in jiffies. It's a function + * to deal with possibility of dynamic HZ patches */ +static inline int timer_period(void) +{ + return HZ / 20; +} +struct das16_private_struct { + unsigned int ai_unipolar; // unipolar flag + unsigned int ai_singleended; // single ended flag + unsigned int clockbase; // master clock speed in ns + volatile unsigned int control_state; // dma, interrupt and trigger control bits + volatile unsigned long adc_byte_count; // number of bytes remaining + unsigned int divisor1; // divisor dividing master clock to get conversion frequency + unsigned int divisor2; // divisor dividing master clock to get conversion frequency + unsigned int dma_chan; // dma channel + uint16_t *dma_buffer[2]; + dma_addr_t dma_buffer_addr[2]; + unsigned int current_buffer; + volatile unsigned int dma_transfer_size; // target number of bytes to transfer per dma shot + // user-defined analog input and output ranges defined from config options + comedi_lrange *user_ai_range_table; + comedi_lrange *user_ao_range_table; + + struct timer_list timer; // for timed interrupt + volatile short timer_running; + volatile short timer_mode; // true if using timer mode +}; +#define devpriv ((struct das16_private_struct *)(dev->private)) +#define thisboard ((struct das16_board_struct *)(dev->board_ptr)) + +static int das16_cmd_test(comedi_device * dev, comedi_subdevice * s, + comedi_cmd * cmd) +{ + int err = 0, tmp; + int gain, start_chan, i; + int mask; + + /* make sure triggers are valid */ + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + mask = TRIG_FOLLOW; + // if board supports burst mode + if (thisboard->size > 0x400) + mask |= TRIG_TIMER | TRIG_EXT; + cmd->scan_begin_src &= mask; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + mask = TRIG_TIMER | TRIG_EXT; + // if board supports burst mode + if (thisboard->size > 0x400) + mask |= TRIG_NOW; + cmd->convert_src &= mask; + 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->scan_begin_src != TRIG_TIMER && + cmd->scan_begin_src != TRIG_EXT && + cmd->scan_begin_src != TRIG_FOLLOW) + err++; + if (cmd->convert_src != TRIG_TIMER && + cmd->convert_src != TRIG_EXT && cmd->convert_src != TRIG_NOW) + err++; + if (cmd->stop_src != TRIG_NONE && cmd->stop_src != TRIG_COUNT) + err++; + + // make sure scan_begin_src and convert_src dont conflict + if (cmd->scan_begin_src == TRIG_FOLLOW && cmd->convert_src == TRIG_NOW) + err++; + if (cmd->scan_begin_src != TRIG_FOLLOW && cmd->convert_src != TRIG_NOW) + 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->scan_begin_src == TRIG_FOLLOW) { + /* internal trigger */ + if (cmd->scan_begin_arg != 0) { + cmd->scan_begin_arg = 0; + err++; + } + } + + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + // check against maximum frequency + if (cmd->scan_begin_src == TRIG_TIMER) { + if (cmd->scan_begin_arg < + thisboard->ai_speed * cmd->chanlist_len) { + cmd->scan_begin_arg = + thisboard->ai_speed * cmd->chanlist_len; + err++; + } + } + if (cmd->convert_src == TRIG_TIMER) { + if (cmd->convert_arg < thisboard->ai_speed) { + cmd->convert_arg = thisboard->ai_speed; + err++; + } + } + + if (cmd->stop_src == TRIG_NONE) { + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + if (err) + return 3; + + // step 4: fix up arguments + if (cmd->scan_begin_src == TRIG_TIMER) { + unsigned int tmp = cmd->scan_begin_arg; + // set divisors, correct timing arguments + i8253_cascade_ns_to_timer_2div(devpriv->clockbase, + &(devpriv->divisor1), &(devpriv->divisor2), + &(cmd->scan_begin_arg), cmd->flags & TRIG_ROUND_MASK); + err += (tmp != cmd->scan_begin_arg); + } + if (cmd->convert_src == TRIG_TIMER) { + unsigned int tmp = cmd->convert_arg; + // set divisors, correct timing arguments + i8253_cascade_ns_to_timer_2div(devpriv->clockbase, + &(devpriv->divisor1), &(devpriv->divisor2), + &(cmd->convert_arg), cmd->flags & TRIG_ROUND_MASK); + err += (tmp != cmd->convert_arg); + } + if (err) + return 4; + + // check channel/gain list against card's limitations + if (cmd->chanlist) { + gain = CR_RANGE(cmd->chanlist[0]); + start_chan = CR_CHAN(cmd->chanlist[0]); + for (i = 1; i < cmd->chanlist_len; i++) { + if (CR_CHAN(cmd->chanlist[i]) != + (start_chan + i) % s->n_chan) { + comedi_error(dev, + "entries in chanlist must be consecutive channels, counting upwards\n"); + err++; + } + if (CR_RANGE(cmd->chanlist[i]) != gain) { + comedi_error(dev, + "entries in chanlist must all have the same gain\n"); + err++; + } + } + } + if (err) + return 5; + + return 0; +} + +static int das16_cmd_exec(comedi_device * dev, comedi_subdevice * s) +{ + comedi_async *async = s->async; + comedi_cmd *cmd = &async->cmd; + unsigned int byte; + unsigned long flags; + int range; + + if (devpriv->dma_chan == 0 || (dev->irq == 0 + && devpriv->timer_mode == 0)) { + comedi_error(dev, + "irq (or use of 'timer mode') dma required to execute comedi_cmd"); + return -1; + } + if (cmd->flags & TRIG_RT) { + comedi_error(dev, + "isa dma transfers cannot be performed with TRIG_RT, aborting"); + return -1; + } + + devpriv->adc_byte_count = + cmd->stop_arg * cmd->chanlist_len * sizeof(uint16_t); + + // disable conversions for das1600 mode + if (thisboard->size > 0x400) { + outb(DAS1600_CONV_DISABLE, dev->iobase + DAS1600_CONV); + } + // set scan limits + byte = CR_CHAN(cmd->chanlist[0]); + byte |= CR_CHAN(cmd->chanlist[cmd->chanlist_len - 1]) << 4; + outb(byte, dev->iobase + DAS16_MUX); + + /* set gain (this is also burst rate register but according to + * computer boards manual, burst rate does nothing, even on keithley cards) */ + if (thisboard->ai_pg != das16_pg_none) { + range = CR_RANGE(cmd->chanlist[0]); + outb((das16_gainlists[thisboard->ai_pg])[range], + dev->iobase + DAS16_GAIN); + } + + /* set counter mode and counts */ + cmd->convert_arg = + das16_set_pacer(dev, cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + DEBUG_PRINT("pacer period: %d ns\n", cmd->convert_arg); + + /* enable counters */ + byte = 0; + /* Enable burst mode if appropriate. */ + if (thisboard->size > 0x400) { + if (cmd->convert_src == TRIG_NOW) { + outb(DAS1600_BURST_VAL, dev->iobase + DAS1600_BURST); + // set burst length + byte |= BURST_LEN_BITS(cmd->chanlist_len - 1); + } else { + outb(0, dev->iobase + DAS1600_BURST); + } + } + outb(byte, dev->iobase + DAS16_PACER); + + // set up dma transfer + flags = claim_dma_lock(); + disable_dma(devpriv->dma_chan); + /* clear flip-flop to make sure 2-byte registers for + * count and address get set correctly */ + clear_dma_ff(devpriv->dma_chan); + devpriv->current_buffer = 0; + set_dma_addr(devpriv->dma_chan, + devpriv->dma_buffer_addr[devpriv->current_buffer]); + // set appropriate size of transfer + devpriv->dma_transfer_size = das16_suggest_transfer_size(dev, *cmd); + set_dma_count(devpriv->dma_chan, devpriv->dma_transfer_size); + enable_dma(devpriv->dma_chan); + release_dma_lock(flags); + + // set up interrupt + if (devpriv->timer_mode) { + devpriv->timer_running = 1; + devpriv->timer.expires = jiffies + timer_period(); + add_timer(&devpriv->timer); + devpriv->control_state &= ~DAS16_INTE; + } else { + /* clear interrupt bit */ + outb(0x00, dev->iobase + DAS16_STATUS); + /* enable interrupts */ + devpriv->control_state |= DAS16_INTE; + } + devpriv->control_state |= DMA_ENABLE; + devpriv->control_state &= ~PACING_MASK; + if (cmd->convert_src == TRIG_EXT) + devpriv->control_state |= EXT_PACER; + else + devpriv->control_state |= INT_PACER; + outb(devpriv->control_state, dev->iobase + DAS16_CONTROL); + + /* Enable conversions if using das1600 mode */ + if (thisboard->size > 0x400) { + outb(0, dev->iobase + DAS1600_CONV); + } + + return 0; +} + +static int das16_cancel(comedi_device * dev, comedi_subdevice * s) +{ + unsigned long flags; + + comedi_spin_lock_irqsave(&dev->spinlock, flags); + /* disable interrupts, dma and pacer clocked conversions */ + devpriv->control_state &= ~DAS16_INTE & ~PACING_MASK & ~DMA_ENABLE; + outb(devpriv->control_state, dev->iobase + DAS16_CONTROL); + if (devpriv->dma_chan) + disable_dma(devpriv->dma_chan); + + // disable SW timer + if (devpriv->timer_mode && devpriv->timer_running) { + devpriv->timer_running = 0; + del_timer(&devpriv->timer); + } + + /* disable burst mode */ + if (thisboard->size > 0x400) { + outb(0, dev->iobase + DAS1600_BURST); + } + + comedi_spin_unlock_irqrestore(&dev->spinlock, flags); + + return 0; +} + +static void das16_reset(comedi_device * dev) +{ + outb(0, dev->iobase + DAS16_STATUS); + outb(0, dev->iobase + DAS16_CONTROL); + outb(0, dev->iobase + DAS16_PACER); + outb(0, dev->iobase + DAS16_CNTR_CONTROL); +} + +static int das16_ai_rinsn(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + int i, n; + int range; + int chan; + int msb, lsb; + + // disable interrupts and pacing + devpriv->control_state &= ~DAS16_INTE & ~DMA_ENABLE & ~PACING_MASK; + outb(devpriv->control_state, dev->iobase + DAS16_CONTROL); + + /* set multiplexer */ + chan = CR_CHAN(insn->chanspec); + chan |= CR_CHAN(insn->chanspec) << 4; + outb(chan, dev->iobase + DAS16_MUX); + + /* set gain */ + if (thisboard->ai_pg != das16_pg_none) { + range = CR_RANGE(insn->chanspec); + outb((das16_gainlists[thisboard->ai_pg])[range], + dev->iobase + DAS16_GAIN); + } + + for (n = 0; n < insn->n; n++) { + /* trigger conversion */ + outb_p(0, dev->iobase + DAS16_TRIG); + + for (i = 0; i < DAS16_TIMEOUT; i++) { + if (!(inb(dev->iobase + DAS16_STATUS) & BUSY)) + break; + } + if (i == DAS16_TIMEOUT) { + rt_printk("das16: timeout\n"); + return -ETIME; + } + msb = inb(dev->iobase + DAS16_AI_MSB); + lsb = inb(dev->iobase + DAS16_AI_LSB); + if (thisboard->ai_nbits == 12) { + data[n] = ((lsb >> 4) & 0xf) | (msb << 4); + } else { + data[n] = lsb | (msb << 8); + } + } + + return n; +} + +static int das16_di_rbits(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + lsampl_t bits; + + bits = inb(dev->iobase + DAS16_DIO) & 0xf; + data[1] = bits; + data[0] = 0; + + return 2; +} + +static int das16_do_wbits(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + lsampl_t wbits; + + // only set bits that have been masked + data[0] &= 0xf; + wbits = s->state; + // zero bits that have been masked + wbits &= ~data[0]; + // set masked bits + wbits |= data[0] & data[1]; + s->state = wbits; + data[1] = wbits; + + outb(s->state, dev->iobase + DAS16_DIO); + + return 2; +} + +static int das16_ao_winsn(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + int i; + int lsb, msb; + int chan; + + chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) { + if (thisboard->ao_nbits == 12) { + lsb = (data[i] << 4) & 0xff; + msb = (data[i] >> 4) & 0xff; + } else { + lsb = data[i] & 0xff; + msb = (data[i] >> 8) & 0xff; + } + outb(lsb, dev->iobase + DAS16_AO_LSB(chan)); + outb(msb, dev->iobase + DAS16_AO_MSB(chan)); + } + + return i; +} + +static irqreturn_t das16_dma_interrupt(int irq, void *d PT_REGS_ARG) +{ + int status; + comedi_device *dev = d; + + status = inb(dev->iobase + DAS16_STATUS); + + if ((status & DAS16_INT) == 0) { + DEBUG_PRINT("spurious interrupt\n"); + return IRQ_NONE; + } + + /* clear interrupt */ + outb(0x00, dev->iobase + DAS16_STATUS); + das16_interrupt(dev); + return IRQ_HANDLED; +} + +static void das16_timer_interrupt(unsigned long arg) +{ + comedi_device *dev = (comedi_device *) arg; + + das16_interrupt(dev); + + if (devpriv->timer_running) + mod_timer(&devpriv->timer, jiffies + timer_period()); +} + +/* the pc104-das16jr (at least) has problems if the dma + transfer is interrupted in the middle of transferring + a 16 bit sample, so this function takes care to get + an even transfer count after disabling dma + channel. +*/ +static int disable_dma_on_even(comedi_device * dev) +{ + int residue; + int i; + static const int disable_limit = 100; + static const int enable_timeout = 100; + disable_dma(devpriv->dma_chan); + residue = get_dma_residue(devpriv->dma_chan); + for (i = 0; i < disable_limit && (residue % 2); ++i) { + int j; + enable_dma(devpriv->dma_chan); + for (j = 0; j < enable_timeout; ++j) { + int new_residue; + comedi_udelay(2); + new_residue = get_dma_residue(devpriv->dma_chan); + if (new_residue != residue) + break; + } + disable_dma(devpriv->dma_chan); + residue = get_dma_residue(devpriv->dma_chan); + } + if (i == disable_limit) { + comedi_error(dev, + "failed to get an even dma transfer, could be trouble."); + } + return residue; +} + +static void das16_interrupt(comedi_device * dev) +{ + unsigned long dma_flags, spin_flags; + comedi_subdevice *s = dev->read_subdev; + comedi_async *async; + comedi_cmd *cmd; + int num_bytes, residue; + int buffer_index; + + if (dev->attached == 0) { + comedi_error(dev, "premature interrupt"); + return; + } + // initialize async here to make sure it is not NULL + async = s->async; + cmd = &async->cmd; + + if (devpriv->dma_chan == 0) { + comedi_error(dev, "interrupt with no dma channel?"); + return; + } + + comedi_spin_lock_irqsave(&dev->spinlock, spin_flags); + if ((devpriv->control_state & DMA_ENABLE) == 0) { + comedi_spin_unlock_irqrestore(&dev->spinlock, spin_flags); + DEBUG_PRINT("interrupt while dma disabled?\n"); + return; + } + + dma_flags = claim_dma_lock(); + clear_dma_ff(devpriv->dma_chan); + residue = disable_dma_on_even(dev); + + // figure out how many points to read + if (residue > devpriv->dma_transfer_size) { + comedi_error(dev, "residue > transfer size!\n"); + async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + num_bytes = 0; + } else + num_bytes = devpriv->dma_transfer_size - residue; + + if (cmd->stop_src == TRIG_COUNT && num_bytes >= devpriv->adc_byte_count) { + num_bytes = devpriv->adc_byte_count; + async->events |= COMEDI_CB_EOA; + } + + buffer_index = devpriv->current_buffer; + devpriv->current_buffer = (devpriv->current_buffer + 1) % 2; + devpriv->adc_byte_count -= num_bytes; + + // figure out how many bytes for next transfer + if (cmd->stop_src == TRIG_COUNT && devpriv->timer_mode == 0 && + devpriv->dma_transfer_size > devpriv->adc_byte_count) + devpriv->dma_transfer_size = devpriv->adc_byte_count; + + // re-enable dma + if ((async->events & COMEDI_CB_EOA) == 0) { + set_dma_addr(devpriv->dma_chan, + devpriv->dma_buffer_addr[devpriv->current_buffer]); + set_dma_count(devpriv->dma_chan, devpriv->dma_transfer_size); + enable_dma(devpriv->dma_chan); + /* reenable conversions for das1600 mode, (stupid hardware) */ + if (thisboard->size > 0x400 && devpriv->timer_mode == 0) { + outb(0x00, dev->iobase + DAS1600_CONV); + } + } + release_dma_lock(dma_flags); + + comedi_spin_unlock_irqrestore(&dev->spinlock, spin_flags); + + cfc_write_array_to_buffer(s, + devpriv->dma_buffer[buffer_index], num_bytes); + + cfc_handle_events(dev, s); +} + +static unsigned int das16_set_pacer(comedi_device * dev, unsigned int ns, + int rounding_flags) +{ + i8253_cascade_ns_to_timer_2div(devpriv->clockbase, &(devpriv->divisor1), + &(devpriv->divisor2), &ns, rounding_flags & TRIG_ROUND_MASK); + + /* Write the values of ctr1 and ctr2 into counters 1 and 2 */ + i8254_load(dev->iobase + DAS16_CNTR0_DATA, 0, 1, devpriv->divisor1, 2); + i8254_load(dev->iobase + DAS16_CNTR0_DATA, 0, 2, devpriv->divisor2, 2); + + return ns; +} + +static void reg_dump(comedi_device * dev) +{ + DEBUG_PRINT("********DAS1600 REGISTER DUMP********\n"); + DEBUG_PRINT("DAS16_MUX: %x\n", inb(dev->iobase + DAS16_MUX)); + DEBUG_PRINT("DAS16_DIO: %x\n", inb(dev->iobase + DAS16_DIO)); + DEBUG_PRINT("DAS16_STATUS: %x\n", inb(dev->iobase + DAS16_STATUS)); + DEBUG_PRINT("DAS16_CONTROL: %x\n", inb(dev->iobase + DAS16_CONTROL)); + DEBUG_PRINT("DAS16_PACER: %x\n", inb(dev->iobase + DAS16_PACER)); + DEBUG_PRINT("DAS16_GAIN: %x\n", inb(dev->iobase + DAS16_GAIN)); + DEBUG_PRINT("DAS16_CNTR_CONTROL: %x\n", + inb(dev->iobase + DAS16_CNTR_CONTROL)); + DEBUG_PRINT("DAS1600_CONV: %x\n", inb(dev->iobase + DAS1600_CONV)); + DEBUG_PRINT("DAS1600_BURST: %x\n", inb(dev->iobase + DAS1600_BURST)); + DEBUG_PRINT("DAS1600_ENABLE: %x\n", inb(dev->iobase + DAS1600_ENABLE)); + DEBUG_PRINT("DAS1600_STATUS_B: %x\n", + inb(dev->iobase + DAS1600_STATUS_B)); +} + +static int das16_probe(comedi_device * dev, comedi_devconfig * it) +{ + int status; + int diobits; + + /* status is available on all boards */ + + status = inb(dev->iobase + DAS16_STATUS); + + if ((status & UNIPOLAR)) { + devpriv->ai_unipolar = 1; + } else { + devpriv->ai_unipolar = 0; + } + + if ((status & DAS16_MUXBIT)) { + devpriv->ai_singleended = 1; + } else { + devpriv->ai_singleended = 0; + } + + /* diobits indicates boards */ + + diobits = inb(dev->iobase + DAS16_DIO) & 0xf0; + + printk(" id bits are 0x%02x\n", diobits); + if (thisboard->id != diobits) { + printk(" requested board's id bits are 0x%x (ignore)\n", + thisboard->id); + } + + return 0; +} + +static int das1600_mode_detect(comedi_device * dev) +{ + int status = 0; + + status = inb(dev->iobase + DAS1600_STATUS_B); + + if (status & DAS1600_CLK_10MHZ) { + devpriv->clockbase = 100; + printk(" 10MHz pacer clock\n"); + } else { + devpriv->clockbase = 1000; + printk(" 1MHz pacer clock\n"); + } + + reg_dump(dev); + + return 0; +} + +/* + * + * Options list: + * 0 I/O base + * 1 IRQ + * 2 DMA + * 3 Clock speed (in MHz) + */ + +static int das16_attach(comedi_device * dev, comedi_devconfig * it) +{ + comedi_subdevice *s; + int ret; + unsigned int irq; + unsigned long iobase; + unsigned int dma_chan; + int timer_mode; + unsigned long flags; + comedi_krange *user_ai_range, *user_ao_range; + + iobase = it->options[0]; +#if 0 + irq = it->options[1]; + timer_mode = it->options[8]; +#endif + /* always use time_mode since using irq can drop samples while + * waiting for dma done interrupt (due to hardware limitations) */ + irq = 0; + timer_mode = 1; + if (timer_mode) + irq = 0; + + printk("comedi%d: das16:", dev->minor); + + // check that clock setting is valid + if (it->options[3]) { + if (it->options[3] != 0 && + it->options[3] != 1 && it->options[3] != 10) { + printk("\n Invalid option. Master clock must be set to 1 or 10 (MHz)\n"); + return -EINVAL; + } + } + + if ((ret = alloc_private(dev, sizeof(struct das16_private_struct))) < 0) + return ret; + + if (thisboard->size < 0x400) { + printk(" 0x%04lx-0x%04lx\n", iobase, iobase + thisboard->size); + if (!request_region(iobase, thisboard->size, "das16")) { + printk(" I/O port conflict\n"); + return -EIO; + } + } else { + printk(" 0x%04lx-0x%04lx 0x%04lx-0x%04lx\n", + iobase, iobase + 0x0f, + iobase + 0x400, + iobase + 0x400 + (thisboard->size & 0x3ff)); + if (!request_region(iobase, 0x10, "das16")) { + printk(" I/O port conflict: 0x%04lx-0x%04lx\n", + iobase, iobase + 0x0f); + return -EIO; + } + if (!request_region(iobase + 0x400, thisboard->size & 0x3ff, + "das16")) { + release_region(iobase, 0x10); + printk(" I/O port conflict: 0x%04lx-0x%04lx\n", + iobase + 0x400, + iobase + 0x400 + (thisboard->size & 0x3ff)); + return -EIO; + } + } + + dev->iobase = iobase; + + // probe id bits to make sure they are consistent + if (das16_probe(dev, it)) { + printk(" id bits do not match selected board, aborting\n"); + return -EINVAL; + } + dev->board_name = thisboard->name; + + // get master clock speed + if (thisboard->size < 0x400) { + if (it->options[3]) + devpriv->clockbase = 1000 / it->options[3]; + else + devpriv->clockbase = 1000; // 1 MHz default + } else { + das1600_mode_detect(dev); + } + + /* now for the irq */ + if (irq > 1 && irq < 8) { + if ((ret = comedi_request_irq(irq, das16_dma_interrupt, 0, + "das16", dev)) < 0) + return ret; + dev->irq = irq; + printk(" ( irq = %u )", irq); + } else if (irq == 0) { + printk(" ( no irq )"); + } else { + printk(" invalid irq\n"); + return -EINVAL; + } + + // initialize dma + dma_chan = it->options[2]; + if (dma_chan == 1 || dma_chan == 3) { + // allocate dma buffers + int i; + for (i = 0; i < 2; i++) { + devpriv->dma_buffer[i] = pci_alloc_consistent(NULL, + DAS16_DMA_SIZE, &devpriv->dma_buffer_addr[i]); + if (devpriv->dma_buffer[i] == NULL) + return -ENOMEM; + } + if (request_dma(dma_chan, "das16")) { + printk(" failed to allocate dma channel %i\n", + dma_chan); + return -EINVAL; + } + devpriv->dma_chan = dma_chan; + flags = claim_dma_lock(); + disable_dma(devpriv->dma_chan); + set_dma_mode(devpriv->dma_chan, DMA_MODE_READ); + release_dma_lock(flags); + printk(" ( dma = %u)\n", dma_chan); + } else if (dma_chan == 0) { + printk(" ( no dma )\n"); + } else { + printk(" invalid dma channel\n"); + return -EINVAL; + } + + // get any user-defined input range + if (thisboard->ai_pg == das16_pg_none && + (it->options[4] || it->options[5])) { + // allocate single-range range table + devpriv->user_ai_range_table = + kmalloc(sizeof(comedi_lrange) + sizeof(comedi_krange), + GFP_KERNEL); + // initialize ai range + devpriv->user_ai_range_table->length = 1; + user_ai_range = devpriv->user_ai_range_table->range; + user_ai_range->min = it->options[4]; + user_ai_range->max = it->options[5]; + user_ai_range->flags = UNIT_volt; + } + // get any user-defined output range + if (it->options[6] || it->options[7]) { + // allocate single-range range table + devpriv->user_ao_range_table = + kmalloc(sizeof(comedi_lrange) + sizeof(comedi_krange), + GFP_KERNEL); + // initialize ao range + devpriv->user_ao_range_table->length = 1; + user_ao_range = devpriv->user_ao_range_table->range; + user_ao_range->min = it->options[6]; + user_ao_range->max = it->options[7]; + user_ao_range->flags = UNIT_volt; + } + + if (timer_mode) { + init_timer(&(devpriv->timer)); + devpriv->timer.function = das16_timer_interrupt; + devpriv->timer.data = (unsigned long)dev; + } + devpriv->timer_mode = timer_mode ? 1 : 0; + + if ((ret = alloc_subdevices(dev, 5)) < 0) + return ret; + + s = dev->subdevices + 0; + dev->read_subdev = s; + /* ai */ + if (thisboard->ai) { + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_CMD_READ; + if (devpriv->ai_singleended) { + s->n_chan = 16; + s->len_chanlist = 16; + s->subdev_flags |= SDF_GROUND; + } else { + s->n_chan = 8; + s->len_chanlist = 8; + s->subdev_flags |= SDF_DIFF; + } + s->maxdata = (1 << thisboard->ai_nbits) - 1; + if (devpriv->user_ai_range_table) { // user defined ai range + s->range_table = devpriv->user_ai_range_table; + } else if (devpriv->ai_unipolar) { + s->range_table = das16_ai_uni_lranges[thisboard->ai_pg]; + } else { + s->range_table = das16_ai_bip_lranges[thisboard->ai_pg]; + } + s->insn_read = thisboard->ai; + s->do_cmdtest = das16_cmd_test; + s->do_cmd = das16_cmd_exec; + s->cancel = das16_cancel; + s->munge = das16_ai_munge; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s = dev->subdevices + 1; + /* ao */ + if (thisboard->ao) { + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 2; + s->maxdata = (1 << thisboard->ao_nbits) - 1; + if (devpriv->user_ao_range_table) { // user defined ao range + s->range_table = devpriv->user_ao_range_table; + } else { + s->range_table = &range_unknown; + } + s->insn_write = thisboard->ao; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s = dev->subdevices + 2; + /* di */ + if (thisboard->di) { + 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 = thisboard->di; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s = dev->subdevices + 3; + /* do */ + if (thisboard->do_) { + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = 4; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = thisboard->do_; + // initialize digital output lines + outb(s->state, dev->iobase + DAS16_DIO); + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s = dev->subdevices + 4; + /* 8255 */ + if (thisboard->i8255_offset != 0) { + subdev_8255_init(dev, s, NULL, (dev->iobase + + thisboard->i8255_offset)); + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + das16_reset(dev); + /* set the interrupt level */ + devpriv->control_state = DAS16_IRQ(dev->irq); + outb(devpriv->control_state, dev->iobase + DAS16_CONTROL); + + // turn on das1600 mode if available + if (thisboard->size > 0x400) { + outb(DAS1600_ENABLE_VAL, dev->iobase + DAS1600_ENABLE); + outb(0, dev->iobase + DAS1600_CONV); + outb(0, dev->iobase + DAS1600_BURST); + } + + return 0; +} + +static int das16_detach(comedi_device * dev) +{ + printk("comedi%d: das16: remove\n", dev->minor); + + das16_reset(dev); + + if (dev->subdevices) + subdev_8255_cleanup(dev, dev->subdevices + 4); + + if (devpriv) { + int i; + for (i = 0; i < 2; i++) { + if (devpriv->dma_buffer[i]) + pci_free_consistent(NULL, DAS16_DMA_SIZE, + devpriv->dma_buffer[i], + devpriv->dma_buffer_addr[i]); + } + if (devpriv->dma_chan) + free_dma(devpriv->dma_chan); + if (devpriv->user_ai_range_table) + kfree(devpriv->user_ai_range_table); + if (devpriv->user_ao_range_table) + kfree(devpriv->user_ao_range_table); + } + + if (dev->irq) + comedi_free_irq(dev->irq, dev); + + if (dev->iobase) { + if (thisboard->size < 0x400) { + release_region(dev->iobase, thisboard->size); + } else { + release_region(dev->iobase, 0x10); + release_region(dev->iobase + 0x400, + thisboard->size & 0x3ff); + } + } + + return 0; +} + +COMEDI_INITCLEANUP(driver_das16); + +// utility function that suggests a dma transfer size in bytes +static unsigned int das16_suggest_transfer_size(comedi_device * dev, + comedi_cmd cmd) +{ + unsigned int size; + unsigned int freq; + + /* if we are using timer interrupt, we don't care how long it + * will take to complete transfer since it will be interrupted + * by timer interrupt */ + if (devpriv->timer_mode) + return DAS16_DMA_SIZE; + + /* otherwise, we are relying on dma terminal count interrupt, + * so pick a reasonable size */ + if (cmd.convert_src == TRIG_TIMER) + freq = 1000000000 / cmd.convert_arg; + else if (cmd.scan_begin_src == TRIG_TIMER) + freq = (1000000000 / cmd.scan_begin_arg) * cmd.chanlist_len; + // return some default value + else + freq = 0xffffffff; + + if (cmd.flags & TRIG_WAKE_EOS) { + size = sample_size * cmd.chanlist_len; + } else { + // make buffer fill in no more than 1/3 second + size = (freq / 3) * sample_size; + } + + // set a minimum and maximum size allowed + if (size > DAS16_DMA_SIZE) + size = DAS16_DMA_SIZE - DAS16_DMA_SIZE % sample_size; + else if (size < sample_size) + size = sample_size; + + if (cmd.stop_src == TRIG_COUNT && size > devpriv->adc_byte_count) + size = devpriv->adc_byte_count; + + return size; +} + +static void das16_ai_munge(comedi_device * dev, comedi_subdevice * s, + void *array, unsigned int num_bytes, unsigned int start_chan_index) +{ + unsigned int i, num_samples = num_bytes / sizeof(sampl_t); + sampl_t *data = array; + + for (i = 0; i < num_samples; i++) { + data[i] = le16_to_cpu(data[i]); + if (thisboard->ai_nbits == 12) { + data[i] = (data[i] >> 4) & 0xfff; + } + } +}