--- /dev/null
+/*
+ comedi/drivers/das16m1.c
+ CIO-DAS16/M1 driver
+ Author: Frank Mori Hess, based on code from the das16
+ driver by Chris Baugher.
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@stm.lbl.gov>
+
+ 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.
+
+************************************************************************
+
+TODO: add cio-das16/m1/16 support
+
+*/
+
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/comedidev.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <asm/io.h>
+#include <linux/malloc.h>
+#include <linux/delay.h>
+#include <8255.h>
+#include <8253.h>
+
+//#define DEBUG
+
+#define DAS16M1_SIZE 16
+#define DAS16M1_SIZE2 8
+
+#define DAS16M1_XTAL 100 //10 MHz master clock
+
+#define HALF_FIFO 512 // 1024 sample fifo
+
+/*
+ CIO-DAS16_M1.pdf
+
+ "cio-das16/m1"
+
+ 0 a/d bits 0-3, mux start 12 bit
+ 1 a/d bits 4-11 unused
+ 2 status control
+ 3 di 4 bit do 4 bit
+ 4 unused clear interrupt
+ 5 interrupt, pacer
+ 6 channel/gain queue address
+ 7 channel/gain queue data
+ 89ab 8254
+ cdef 8254
+ 400 8255
+ 404-407 8254
+
+*/
+
+#define DAS16M1_TRIG 0
+#define DAS16M1_AI 0 // 16-bit wide register
+#define AI_CHAN(x) ((x) & 0xf)
+#define AI_DATA(x) (((x) >> 4) & 0xfff)
+#define DAS16M1_CS 2
+#define EXT_TRIG 0x1
+#define CLK_SRC 0x2
+#define OVRUN 0x20
+#define IRQDATA 0x80
+#define DAS16M1_DIO 3
+#define DAS16M1_CLEAR_INTR 4
+#define DAS16M1_INTR_CONTROL 5
+#define INT_PACER 0x3
+#define PACER_MASK 0x3
+#define IRQ(x) (((x) & 0x7) << 4)
+#define INTE 0x80
+#define DAS16M1_QUEUE_ADDR 6
+#define DAS16M1_QUEUE_DATA 7
+#define Q_CHAN(x) ((x) & 0x7)
+#define Q_RANGE(x) (((x) & 0xf) << 4)
+#define UNIPOLAR 0x40
+#define DAS16M1_8254_FIRST 8
+#define DAS16M1_8254_SECOND 0xc
+#define DAS16M1_82C55 0x400
+#define DAS16M1_8254_THIRD 0x404
+
+static comedi_lrange range_das16m1 =
+{ 9,
+ {
+ 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),
+ BIP_RANGE(10),
+ }
+};
+
+static int das16m1_do_wbits(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data);
+static int das16m1_di_rbits(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data);
+static int das16m1_ai_rinsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data);
+
+static int das16m1_cmd_test(comedi_device *dev,comedi_subdevice *s,comedi_cmd *cmd);
+static int das16m1_cmd_exec(comedi_device *dev,comedi_subdevice *s);
+static int das16m1_cancel(comedi_device *dev, comedi_subdevice *s);
+
+static void das16m1_reset(comedi_device *dev);
+static void das16m1_interrupt(int irq, void *d, struct pt_regs *regs);
+
+static unsigned int das16m1_set_pacer(comedi_device *dev, unsigned int ns, int round_flag);
+#ifdef DEBUG
+static void reg_dump(comedi_device *dev);
+#endif
+
+typedef struct das16m1_board_struct{
+ char *name;
+ unsigned int ai_speed;
+}das16m1_board;
+
+static das16m1_board das16m1_boards[]={
+ {
+ name: "das16m1", // CIO-DAS16_M1.pdf
+ ai_speed: 1000, // 1MHz max speed
+ },
+};
+
+#define das16m1_num_boards ((sizeof(das16m1_boards)) / (sizeof(das16m1_boards[0])))
+
+static int das16m1_attach(comedi_device *dev, comedi_devconfig *it);
+static int das16m1_detach(comedi_device *dev);
+comedi_driver driver_das16m1={
+ driver_name: "das16m1",
+ module: THIS_MODULE,
+ attach: das16m1_attach,
+ detach: das16m1_detach,
+ board_name: das16m1_boards,
+ num_names: das16m1_num_boards,
+ offset: sizeof(das16m1_boards[0]),
+};
+
+struct das16m1_private_struct {
+ unsigned int control_state;
+ volatile unsigned int adc_count;
+ unsigned int divisor1; // divides master clock to obtain conversion speed
+ unsigned int divisor2; // divides master clock to obtain conversion speed
+};
+#define devpriv ((struct das16m1_private_struct *)(dev->private))
+#define thisboard ((struct das16m1_board_struct *)(dev->board_ptr))
+
+COMEDI_INITCLEANUP(driver_das16m1);
+
+static int das16m1_cmd_test(comedi_device *dev,comedi_subdevice *s, comedi_cmd *cmd)
+{
+ unsigned int err=0, tmp;
+
+ /* 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;
+ cmd->scan_begin_src &= TRIG_FOLLOW;
+ if(!cmd->scan_begin_src || tmp!=cmd->scan_begin_src) err++;
+
+ tmp=cmd->convert_src;
+ cmd->convert_src &= TRIG_TIMER;
+ 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 */
+ /* note that mutual compatiblity is not an issue here */
+ 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->scan_begin_src == TRIG_FOLLOW){
+ /* internal trigger */
+ if(cmd->scan_begin_arg != 0){
+ cmd->scan_begin_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->scan_end_arg != cmd->chanlist_len){
+ cmd->scan_end_arg = cmd->chanlist_len;
+ err++;
+ }
+
+ if(cmd->stop_src==TRIG_COUNT){
+ /* any count is allowed */
+ }else{
+ /* TRIG_NONE */
+ if(cmd->stop_arg!=0){
+ cmd->stop_arg=0;
+ err++;
+ }
+ }
+
+ if(err) return 3;
+
+ /* step 4: fix up arguments */
+
+ if(cmd->convert_src == TRIG_TIMER)
+ {
+ tmp = cmd->convert_arg;
+ /* calculate counter values that give desired timing */
+ i8253_cascade_ns_to_timer_2div(DAS16M1_XTAL, &(devpriv->divisor1),
+ &(devpriv->divisor2), &(cmd->convert_arg), cmd->flags & TRIG_ROUND_MASK);
+ if(tmp != cmd->convert_arg) err++;
+ }
+ // XXX we need to check constraints on chanlist here
+
+ if(err) return 4;
+
+ return 0;
+}
+
+static int das16m1_cmd_exec(comedi_device *dev,comedi_subdevice *s)
+{
+ comedi_cmd *cmd = &s->async->cmd;
+ unsigned int byte, i;
+
+ devpriv->adc_count = cmd->stop_arg * cmd->chanlist_len;
+
+ /* setup channel/gain queue */
+ for(i = 0; i < cmd->chanlist_len; i++)
+ {
+ outb(i, dev->iobase + DAS16M1_QUEUE_ADDR);
+ byte = Q_CHAN(CR_CHAN(cmd->chanlist[i])) | Q_RANGE(CR_RANGE(cmd->chanlist[i]));
+ outb(byte, dev->iobase + DAS16M1_QUEUE_DATA);
+ }
+
+ /* enable pacer clocked conversions */
+ devpriv->control_state |= INT_PACER;
+
+ /* set counter mode and counts */
+ das16m1_set_pacer(dev, cmd->scan_begin_arg, cmd->flags & TRIG_ROUND_MASK);
+
+ /* clear interrupt bit */
+ outb(0, dev->iobase + DAS16M1_CLEAR_INTR);
+ /* enable interrupts and internal pacer */
+ devpriv->control_state |= INTE | INT_PACER;
+ outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL);
+
+ return 0;
+}
+
+static int das16m1_cancel(comedi_device *dev, comedi_subdevice *s)
+{
+ devpriv->adc_count = 0;
+
+ return 0;
+}
+
+#if 0 // insns haven't been converted from das16 driver yet
+static int das16m1_ai_rinsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data)
+{
+ int i,n;
+ int range;
+ int chan;
+ int msb,lsb;
+
+ /* clear crap */
+ inb(dev->iobase+DAS16M1_AI_LSB);
+ inb(dev->iobase+DAS16M1_AI_MSB);
+
+ /* set multiplexer */
+ chan = CR_CHAN(insn->chanspec);
+ outb_p(chan,dev->iobase+DAS16M1_MUX);
+
+ /* set gain */
+ if(thisboard->ai_pg != das16m1_pg_none){
+ range = CR_RANGE(insn->chanspec);
+ outb((das16m1_gainlists[thisboard->ai_pg])[range],
+ dev->iobase+DAS16M1_GAIN);
+ }
+
+ /* How long should we wait for MUX to settle? */
+ //udelay(5);
+
+ for(n=0;n<insn->n;n++){
+ /* trigger conversion */
+ outb_p(0,dev->iobase+DAS16M1_TRIG);
+
+ for(i=0;i<DAS16M1_TIMEOUT;i++){
+ if(!(inb(DAS16M1_STATUS)&DAS16M1_EOC))
+ break;
+ }
+ if(i==DAS16M1_TIMEOUT){
+ rt_printk("das16m1: timeout\n");
+ return -ETIME;
+ }
+ msb = inb(dev->iobase + DAS16M1_AI_MSB);
+ lsb = inb(dev->iobase + DAS16M1_AI_LSB);
+ if(thisboard->ai_nbits==12){
+ data[n] = (lsb>>4) | (msb << 4);
+ }else{
+ data[n] = lsb | (msb << 8);
+ }
+ }
+
+ return n;
+}
+
+static int das16m1_di_rbits(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data)
+{
+ data[0]=inb(dev->iobase+DAS16M1_DIO)&0xf;
+
+ return 1;
+}
+
+static int das16m1_do_wbits(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data)
+{
+ outb(data[0],dev->iobase+DAS16M1_DIO);
+
+ return 1;
+}
+
+static int das16m1_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;
+ }
+
+#if 0
+ outb(lsb,dev->iobase+devpriv->ao_offset_lsb[chan]);
+ outb(msb,dev->iobase+devpriv->ao_offset_msb[chan]);
+#else
+ outb(lsb,dev->iobase+DAS16M1_AO_LSB(chan));
+ outb(msb,dev->iobase+DAS16M1_AO_MSB(chan));
+#endif
+ }
+
+ return i;
+}
+
+#endif // insns haven't been converted from das16 driver yet
+
+static void das16m1_interrupt(int irq, void *d, struct pt_regs *regs)
+{
+ int i, status;
+ sampl_t data_point;
+
+ comedi_device *dev = d;
+ comedi_subdevice *s = dev->subdevices;
+ comedi_async *async = s->async;
+
+ status = inb(dev->iobase + DAS16M1_CS);
+
+ if((status & IRQDATA) == 0 || dev->attached == 0)
+ {
+ comedi_error(dev, "spurious interrupt");
+ return;
+ }
+
+ for(i = 0; i < HALF_FIFO; i++)
+ {
+ data_point = inw(dev->iobase + DAS16M1_AI);
+ data_point = AI_DATA(data_point);
+ comedi_buf_put(async, data_point);
+
+ if(--devpriv->adc_count <= 0) { /* end of acquisition */
+ devpriv->control_state &= ~INTE;
+ devpriv->control_state &= ~PACER_MASK;
+ outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL);
+ async->events |= COMEDI_CB_EOA;
+ break;
+ }
+ }
+
+ comedi_event(dev, s, async->events);
+ async->events = 0;
+
+ /* clear interrupt */
+ outb(0, dev->iobase + DAS16M1_CLEAR_INTR);
+}
+
+/* This function takes a time in nanoseconds and sets the *
+ * 2 pacer clocks to the closest frequency possible. It also *
+ * returns the actual sampling period. */
+static unsigned int das16m1_set_pacer(comedi_device *dev, unsigned int ns, int rounding_flags)
+{
+ i8253_cascade_ns_to_timer_2div(DAS16M1_XTAL, &(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 + DAS16M1_8254_SECOND, 1, devpriv->divisor1, 2);
+ i8254_load(dev->iobase + DAS16M1_8254_SECOND, 2, devpriv->divisor2, 2);
+
+ return ns;
+}
+
+#ifdef DEBUG
+static void reg_dump(comedi_device *dev)
+{
+ printk("********DAS16M100 REGISTER DUMP********\n");
+ printk("DAS16M1_MUX: %x\n", inb(dev->iobase+DAS16M1_MUX) );
+ printk("DAS16M1_DIO: %x\n", inb(dev->iobase+DAS16M1_DIO) );
+ printk("DAS16M1_STATUS: %x\n", inb(dev->iobase+DAS16M1_STATUS) );
+ printk("DAS16M1_CONTROL: %x\n", inb(dev->iobase+DAS16M1_CONTROL) );
+ printk("DAS16M1_PACER: %x\n", inb(dev->iobase+DAS16M1_PACER) );
+ printk("DAS16M1_GAIN: %x\n", inb(dev->iobase+DAS16M1_GAIN) );
+ printk("DAS16M1_CNTR_CONTROL: %x\n", inb(dev->iobase+DAS16M1_CNTR_CONTROL) );
+ printk("DAS16M100_CONV: %x\n", inb(dev->iobase+DAS16M100_CONV) );
+ printk("DAS16M100_BURST: %x\n", inb(dev->iobase+DAS16M100_BURST) );
+ printk("DAS16M100_ENABLE: %x\n", inb(dev->iobase+DAS16M100_ENABLE) );
+ printk("DAS16M100_STATUS_B: %x\n", inb(dev->iobase+DAS16M100_STATUS_B) );
+}
+#endif
+
+/*
+ * Options list:
+ * 0 I/O base
+ * 1 IRQ
+ */
+
+static int das16m1_attach(comedi_device *dev, comedi_devconfig *it)
+{
+ comedi_subdevice *s;
+ int ret, irq;
+ int iobase;
+
+ iobase = it->options[0];
+
+ printk("comedi%d: das16m1:", dev->minor);
+
+ if((ret = alloc_private(dev, sizeof(struct das16m1_private_struct))) < 0)
+ return ret;
+
+ dev->board_name = thisboard->name;
+
+ printk(" io= 0x%04x-0x%04x 0x%04x-0x%04x",
+ iobase, iobase + DAS16M1_SIZE,
+ iobase + DAS16M1_82C55, iobase + DAS16M1_82C55 + DAS16M1_SIZE2);
+ if(check_region(iobase, DAS16M1_SIZE) < 0) {
+ printk(" I/O port conflict\n");
+ return -EIO;
+ }
+ if(check_region(iobase + DAS16M1_82C55, DAS16M1_SIZE2) < 0){
+ printk(" I/O port conflict\n");
+ return -EIO;
+ }
+ request_region(iobase, DAS16M1_SIZE, driver_das16m1.driver_name);
+ request_region(iobase + DAS16M1_82C55, DAS16M1_SIZE2, driver_das16m1.driver_name);
+ dev->iobase = iobase;
+
+ /* now for the irq */
+ irq = it->options[1];
+ // make sure it is valid
+ if(irq == 2 || irq == 3 || irq == 5 || irq == 7 ||
+ irq == 10 || irq == 11 || irq == 12 || irq == 15)
+ {
+ if((ret = comedi_request_irq(irq, das16m1_interrupt, 0,
+ driver_das16m1.driver_name,dev)) < 0)
+ return ret;
+ dev->irq = irq;
+ printk(" irq = %d\n", irq);
+ }else if(irq == 0){
+ printk(" irq = none\n");
+ }else {
+ printk(" invalid irq\n");
+ return -EINVAL;
+ }
+
+ dev->n_subdevices = 4;
+ if((ret = alloc_subdevices(dev)) < 0)
+ return ret;
+
+ s = dev->subdevices + 0;
+ dev->read_subdev = s;
+ /* ai */
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 8;
+ s->subdev_flags |= SDF_DIFF;
+ s->len_chanlist = 256;
+ s->maxdata = (1 << 12) - 1;
+ s->range_table = &range_das16m1;
+// s->insn_read = das16m1_ai_rinsn;
+ s->do_cmdtest = das16m1_cmd_test;
+ s->do_cmd = das16m1_cmd_exec;
+ s->cancel = das16m1_cancel;
+
+ s = dev->subdevices + 1;
+ /* 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 = das16m1_di_rbits;
+
+ s = dev->subdevices + 2;
+ /* do */
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITEABLE;
+ s->n_chan = 4;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+// s->insn_write = das16m1_do_wbits;
+
+ s = dev->subdevices + 3;
+ /* 8255 */
+ subdev_8255_init(dev, s, NULL, (void*)(dev->iobase + DAS16M1_82C55));
+
+ // XXX should init digital output levels here also
+ /* set the interrupt level */
+ devpriv->control_state = IRQ(dev->irq);
+ outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL);
+
+ return 0;
+}
+
+
+static int das16m1_detach(comedi_device *dev)
+{
+ printk("comedi%d: das16m1: remove\n", dev->minor);
+
+// das16m1_reset(dev);
+
+ if(dev->subdevices)
+ subdev_8255_cleanup(dev,dev->subdevices+4);
+
+ if(dev->irq)
+ free_irq(dev->irq, dev);
+
+ if(dev->iobase){
+ release_region(dev->iobase , DAS16M1_SIZE);
+ release_region(dev->iobase + DAS16M1_82C55, DAS16M1_SIZE2);
+ }
+
+ return 0;
+}
+
+