From d5e9a113de78c5aab298f507c7bb5bc3e6ce33b3 Mon Sep 17 00:00:00 2001 From: David Schleef Date: Wed, 3 Apr 2002 07:12:25 +0000 Subject: [PATCH] From Juan Grigera --- comedi/drivers/pcl816.c | 1263 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 1263 insertions(+) create mode 100644 comedi/drivers/pcl816.c diff --git a/comedi/drivers/pcl816.c b/comedi/drivers/pcl816.c new file mode 100644 index 00000000..1fec344a --- /dev/null +++ b/comedi/drivers/pcl816.c @@ -0,0 +1,1263 @@ +/* + module/pcl816.c + + Author: Juan Grigera + based on pcl818 by Michal Dobes and bits of pcl812 + + hardware driver for Advantech cards: + card: PCL-816, PCL814B + driver: pcl816 +*/ +/* +Driver: pcl816.o +Description: Advantech PCL-816 cards, PCL-814 +Author: Juan Grigera +Devices: [Advantech] PCL-816, PCL-814B +Status: works + +PCL 816 and 814B have 16 SE/DIFF ADCs, 16 DACs, 16 DI and 16 DO. +Differences are at resolution (16 vs 12 bits). + +The driver support AI command mode, other subdevices not written. +See the head of the source file pcl816.c for configuration options. +*/ +/* + Options for PCL-816: + [0] - IO Base + [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7) + [2] - DMA (0=disable, 1, 3) + [3] - 0, 10=10MHz clock for 8254 + 1= 1MHz clock for 8254 + + Notes: + WARNING: AO, DI, and DO are not implemented!!!! + +*/ +#define DEBUG(x) x + +#include +#include +#include +#include +#include +#include +#include "8253.h" + + +// boards constants +// IO space len +#define PCLx1x_RANGE 16 + +//#define outb(x,y) printk("OUTB(%x, 200+%d)\n", x,y-0x200); outb(x,y) + +// INTEL 8254 counters +#define PCL816_CTR0 4 +#define PCL816_CTR1 5 +#define PCL816_CTR2 6 +// R: counter read-back register W: counter control +#define PCL816_CTRCTL 7 + +// R: A/D high byte W: A/D range control +#define PCL816_RANGE 9 +// W: clear INT request +#define PCL816_CLRINT 10 +// R: next mux scan channel W: mux scan channel & range control pointer +#define PCL816_MUX 11 +// R/W: operation control register +#define PCL816_CONTROL 12 + +// R: return status byte W: set DMA/IRQ +#define PCL816_STATUS 13 +#define PCL816_STATUS_DRDY_MASK 0x80 + +// R: low byte of A/D W: soft A/D trigger +#define PCL816_AD_LO 8 +// R: high byte of A/D W: A/D range control +#define PCL816_AD_HI 9 + + +// type of interrupt handler +#define INT_TYPE_AI1_INT 1 +#define INT_TYPE_AI1_DMA 2 +#define INT_TYPE_AI3_INT 4 +#define INT_TYPE_AI3_DMA 5 +#define INT_TYPE_AI1_DMA_RTC 9 +#define INT_TYPE_AI3_DMA_RTC 10 + + +// RTC stuff... +#define RTC_IRQ 8 +#define RTC_IO_EXTENT 0x10 + +#define MAGIC_DMA_WORD 0x5a5a + +static comedi_lrange range_pcl816 = { 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), + } +}; +typedef struct { + char *name; // driver name + int n_ranges; // len of range list + int n_aichan; // num of A/D chans in diferencial mode + unsigned int ai_ns_min; // minimal alllowed delay between samples (in ns) + int n_aochan; // num of D/A chans + int n_dichan; // num of DI chans + int n_dochan; // num of DO chans + comedi_lrange *ai_range_type; // default A/D rangelist + comedi_lrange *ao_range_type; // dafault D/A rangelist + int io_range; // len of IO space + unsigned int IRQbits; // allowed interrupts + unsigned int DMAbits; // allowed DMA chans + int ai_maxdata; // maxdata for A/D + int ao_maxdata; // maxdata for D/A + int ai_chanlist; // allowed len of channel list A/D + int ao_chanlist; // allowed len of channel list D/A + int i8254_osc_base; // 1/frequency of on board oscilator in ns +} +boardtype; + +static boardtype boardtypes[] = { + {"pcl816", 8, 16, 10000, 1, 16, 16, &range_pcl816, + &range_pcl816, PCLx1x_RANGE, + 0x00fc, // IRQ mask + 0x0a, // DMA mask + 0xffff, // 16-bit card + 0xffff, // D/A maxdata + 1024, + 1, // ao chan list + 100 }, + {"pcl814b", 8, 16, 10000, 1, 16, 16, &range_pcl816, + &range_pcl816, PCLx1x_RANGE, + 0x00fc, + 0x0a, + 0x3fff, /* 14 bit card */ + 0x3fff, + 1024, + 1, + 100}, +}; + +#define n_boardtypes (sizeof(boardtypes)/sizeof(boardtype)) +#define devpriv ((pcl816_private *)dev->private) +#define this_board ((boardtype *)dev->board_ptr) + +static int pcl816_attach (comedi_device * dev, comedi_devconfig * it); +static int pcl816_detach (comedi_device * dev); + +static int RTC_lock = 0; /* RTC lock */ +static int RTC_timer_lock = 0; /* RTC int lock */ + +comedi_driver driver_pcl816 = { + driver_name: "pcl816", + module: THIS_MODULE, + attach: pcl816_attach, + detach: pcl816_detach, + board_name: boardtypes, + num_names: n_boardtypes, + offset: sizeof(boardtype), +}; +COMEDI_INITCLEANUP(driver_pcl816); + + +typedef struct +{ + int dma; // used DMA, 0=don't use DMA + int dma_rtc; // 1=RTC used with DMA, 0=no RTC alloc + unsigned int rtc_iobase; // RTC port region + unsigned int rtc_iosize; + unsigned int rtc_irq; + unsigned long dmabuf[2]; // pointers to begin of DMA buffers + unsigned int dmapages[2]; // len of DMA buffers in PAGE_SIZEs + unsigned int hwdmaptr[2]; // hardware address of DMA buffers + unsigned int hwdmasize[2]; // len of DMA buffers in Bytes + unsigned int dmasamplsize; // size in samples hwdmasize[0]/2 + unsigned int last_top_dma; // DMA pointer in last RTC int + int next_dma_buf; // which DMA buffer will be used next round + long dma_runs_to_end; // how many we must permorm DMA transfer to end of record + unsigned long last_dma_run; // how many bytes we must transfer on last DMA page + + unsigned int ai_scans; // len of scanlist + unsigned char ai_neverending; // if=1, then we do neverending record (you must use cancel()) + int irq_free; // 1=have allocated IRQ + int irq_blocked; // 1=IRQ now uses any subdev + int rtc_irq_blocked; // 1=we now do AI with DMA&RTC + int irq_was_now_closed; // when IRQ finish, there's stored int816_mode for last interrupt + int int816_mode; // who now uses IRQ - 1=AI1 int, 2=AI1 dma, 3=AI3 int, 4AI3 dma + comedi_subdevice *last_int_sub; // ptr to subdevice which now finish + int ai_act_scan; // how many scans we finished + unsigned int ai_act_chanlist[16]; // MUX setting for actual AI operations + unsigned int ai_act_chanlist_len; // how long is actual MUX list + unsigned int ai_act_chanlist_pos; // actual position in MUX list + unsigned int ai_poll_ptr; // how many sampes transfer poll + unsigned int buf_ptr; // data buffer ptr in samples + comedi_subdevice *sub_ai; // ptr to AI subdevice + struct timer_list rtc_irq_timer; // timer for RTC sanity check + unsigned long rtc_freq; // RTC int freq +} pcl816_private; + + + + +/* +============================================================================== +*/ +static int check_and_setup_channel_list (comedi_device * dev, comedi_subdevice * s, unsigned int *chanlist, int chanlen); +static int pcl816_ai_cancel (comedi_device * dev, comedi_subdevice * s); +static void start_pacer (comedi_device * dev, int mode, unsigned int divisor1, + unsigned int divisor2); +static int set_rtc_irq_bit (unsigned char bit); + +static int pcl816_ai_cmdtest(comedi_device *dev, comedi_subdevice *s, comedi_cmd *cmd); +static int pcl816_ai_cmd(comedi_device *dev, comedi_subdevice *s); + + +/* +============================================================================== + ANALOG INPUT MODE0, 816 cards, slow version +*/ +static int pcl816_ai_insn_read(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) +{ + int n; + int timeout; + + DPRINTK("mode 0 analog input\n"); + + for(n=0;nn;n++){ + + // software trigger, DMA and INT off + outb (0, dev->iobase + PCL816_CONTROL); + // clear INT (conversion end) flag + outb (0, dev->iobase + PCL816_CLRINT); + + // Set the input channel + outb (CR_CHAN(insn->chanspec) & 0xf, dev->iobase + PCL816_MUX); + outb (CR_RANGE(insn->chanspec), dev->iobase + PCL816_RANGE); /* select gain */ + + udelay (5); + outb (0, dev->iobase + PCL816_AD_LO); /* start conversion */ + + timeout=100; + while (timeout--) { + if (!(inb (dev->iobase + PCL816_STATUS) & PCL816_STATUS_DRDY_MASK)) { + // return read value + data[n] = + ((inb (dev->iobase + PCL816_AD_HI) << 8 ) | + (inb (dev->iobase + PCL816_AD_LO))); + + outb (0, dev->iobase + PCL816_CLRINT); /* clear INT (conversion end) flag */ + break; + } + udelay (1); + } + // Return timeout error + if(!timeout) { + comedi_error (dev, "A/D insn timeout\n"); + data[0] = 0; + outb (0, dev->iobase + PCL816_CLRINT); /* clear INT (conversion end) flag */ + return -EIO; + } + + } + return n; +} + +/* +============================================================================== + analog input interrupt mode 1 & 3, 818 cards + one sample per interrupt version +*/ +static void +interrupt_pcl816_ai_mode13_int (int irq, void *d, struct pt_regs *regs) +{ + comedi_device *dev = d; + comedi_subdevice *s = dev->subdevices + 0; + int low, hi; + int timeout = 50; /* wait max 50us */ + + while (timeout--) { + if (!(inb (dev->iobase + PCL816_STATUS) & PCL816_STATUS_DRDY_MASK)) + break; + udelay (1); + } + if(!timeout) { // timeout, bail error + outb (0, dev->iobase + PCL816_CLRINT); /* clear INT request */ + comedi_error (dev, "A/D mode1/3 IRQ without DRDY!"); + pcl816_ai_cancel (dev, s); + s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + comedi_event(dev, s, s->async->events); + return; + + } + + + // get the sample + low = inb (dev->iobase + PCL816_AD_LO); + hi = inb (dev->iobase + PCL816_AD_HI); + ((sampl_t *)s->async->data)[devpriv->buf_ptr++] = (hi << 8)|low; + + outb (0, dev->iobase + PCL816_CLRINT); /* clear INT request */ + + s->async->buf_int_ptr+=sizeof(sampl_t); + s->async->buf_int_count+=sizeof(sampl_t); + + if (++devpriv->ai_act_chanlist_pos >= devpriv->ai_act_chanlist_len) + devpriv->ai_act_chanlist_pos = 0; + + s->async->buf_int_ptr+=sizeof(sampl_t); + s->async->buf_int_count+=sizeof(sampl_t); + s->async->cur_chan++; + + if (s->async->cur_chan>=s->async->cmd.chanlist_len){ + s->async->cur_chan=0; + s->async->events |= COMEDI_CB_BLOCK; + devpriv->ai_act_scan++; + } + + if (s->async->buf_int_ptr >= s->async->data_len) { /* buffer rollover */ + s->async->buf_int_ptr=0; + devpriv->buf_ptr=0; + //printk("B "); + s->async->events |= COMEDI_CB_EOBUF; + } + + if (!devpriv->ai_neverending) + if (devpriv->ai_act_scan >= devpriv->ai_scans) { /* all data sampled */ + /* all data sampled */ + pcl816_ai_cancel (dev, s); + s->async->events |= COMEDI_CB_EOA; + return; + } + comedi_event(dev, s, s->async->events); +} + +/* +============================================================================== + analog input dma mode 1 & 3, 816 cards +*/ +static void transfer_from_dma_buf(comedi_device *dev,comedi_subdevice *s, + sampl_t *ptr, unsigned int bufptr, unsigned int len) +{ + int i; + + s->async->events = 0; + + for (i = 0; i < len; i++) { + + ((sampl_t*)s->async->data)[devpriv->buf_ptr++] = ptr[bufptr++]; + s->async->buf_int_ptr+=sizeof(sampl_t); + s->async->buf_int_count+=sizeof(sampl_t); + + + if (++devpriv->ai_act_chanlist_pos >= devpriv->ai_act_chanlist_len) { + devpriv->ai_act_chanlist_pos = 0; + devpriv->ai_act_scan++; + } + + if (s->async->buf_int_ptr >= s->async->data_len) { // buffer rollover + devpriv->buf_ptr=0; + s->async->buf_int_ptr=0; + s->async->events |= COMEDI_CB_EOBUF; + comedi_event(dev,s,s->async->events); + s->async->events = 0; + } + + if (!devpriv->ai_neverending) + if (devpriv->ai_act_scan >= devpriv->ai_scans) { // all data sampled + pcl816_ai_cancel(dev,s); + s->async->events |= COMEDI_CB_EOA; + s->async->events |= COMEDI_CB_BLOCK; + comedi_event(dev, s, s->async->events); + return; + } + } + + if (len > 0) { + comedi_event(dev, s, s->async->events); + } +} + + +static void interrupt_pcl816_ai_mode13_dma(int irq, void *d, struct pt_regs *regs) +{ + comedi_device *dev = d; + comedi_subdevice *s = dev->subdevices + 0; + int len, bufptr, this_dma_buf; + unsigned long dma_flags; + sampl_t *ptr; + + disable_dma (devpriv->dma); + this_dma_buf = devpriv->next_dma_buf; + + if((devpriv->dma_runs_to_end > -1) || + devpriv->ai_neverending) { // switch dma bufs + + devpriv->next_dma_buf = 1 - devpriv->next_dma_buf; + set_dma_mode (devpriv->dma, DMA_MODE_READ); + dma_flags = claim_dma_lock (); +// clear_dma_ff (devpriv->dma); + set_dma_addr (devpriv->dma, devpriv->hwdmaptr[devpriv->next_dma_buf]); + if (devpriv->dma_runs_to_end) { + set_dma_count (devpriv->dma, devpriv->hwdmasize[devpriv->next_dma_buf]); + } else { + set_dma_count (devpriv->dma, devpriv->last_dma_run); + } + release_dma_lock (dma_flags); + enable_dma (devpriv->dma); + } + + devpriv->dma_runs_to_end--; + outb (0, dev->iobase + PCL816_CLRINT); /* clear INT request */ + + ptr = (sampl_t *) devpriv->dmabuf[this_dma_buf]; + + len = (devpriv->hwdmasize[0] >> 1) - devpriv->ai_poll_ptr; + bufptr = devpriv->ai_poll_ptr; + devpriv->ai_poll_ptr = 0; + + transfer_from_dma_buf(dev, s, ptr, bufptr, len); +} + + +/* +============================================================================== + INT procedure +*/ +static void +interrupt_pcl816 (int irq, void *d, struct pt_regs *regs) +{ + comedi_device *dev = d; + DPRINTK(""); + + if(!dev->attached) { + comedi_error(dev, "premature interrupt"); + return; + } + + + switch (devpriv->int816_mode) { + case INT_TYPE_AI1_DMA: + case INT_TYPE_AI3_DMA: + interrupt_pcl816_ai_mode13_dma (irq, d, regs); + return; + case INT_TYPE_AI1_INT: + case INT_TYPE_AI3_INT: + interrupt_pcl816_ai_mode13_int (irq, d, regs); + return; + } + + + outb (0, dev->iobase + PCL816_CLRINT); /* clear INT request */ + if ((!dev->irq) | (!devpriv->irq_free) | (!devpriv->irq_blocked) | + (!devpriv->int816_mode)) + { + if (devpriv->irq_was_now_closed) { + devpriv->irq_was_now_closed = 0; + // comedi_error(dev,"last IRQ.."); + return; + } + comedi_error (dev, "bad IRQ!"); + return; + } + comedi_error (dev, "IRQ from unknow source!"); +} + + +/* +============================================================================== + COMMAND MODE +*/ +static void pcl816_cmdtest_out(int e,comedi_cmd *cmd) { + rt_printk("pcl816 e=%d startsrc=%x scansrc=%x convsrc=%x\n",e,cmd->start_src,cmd->scan_begin_src,cmd->convert_src); + rt_printk("pcl816 e=%d startarg=%d scanarg=%d convarg=%d\n",e,cmd->start_arg,cmd->scan_begin_arg,cmd->convert_arg); + rt_printk("pcl816 e=%d stopsrc=%x scanend=%x\n",e,cmd->stop_src,cmd->scan_end_src); + rt_printk("pcl816 e=%d stoparg=%d scanendarg=%d chanlistlen=%d\n",e,cmd->stop_arg,cmd->scan_end_arg,cmd->chanlist_len); +} + +/* +============================================================================== +*/ +static int pcl816_ai_cmdtest(comedi_device *dev,comedi_subdevice *s,comedi_cmd *cmd) +{ + int err=0; + int tmp,divisor1,divisor2; + +DEBUG( + rt_printk("pcl816 pcl812_ai_cmdtest\n"); + pcl816_cmdtest_out(-1, cmd); +); + + /* step 1: make sure trigger sources are trivially 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++; + + + if(!cmd->convert_src & (TRIG_EXT | TRIG_TIMER)) 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_NOW; + err++; + } + + if(cmd->scan_begin_src!=TRIG_FOLLOW) { + cmd->scan_begin_src=TRIG_FOLLOW; + err++; + } + + if(cmd->convert_src!=TRIG_EXT && + cmd->convert_src!=TRIG_TIMER) { + cmd->convert_src=TRIG_TIMER; + err++; + } + + + if(cmd->scan_end_src!=TRIG_COUNT) { + cmd->scan_end_src=TRIG_COUNT; + err++; + } + + if(cmd->stop_src!=TRIG_NONE && + cmd->stop_src!=TRIG_COUNT) 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_arg!=0){ + cmd->scan_begin_arg=0; + err++; + } + if(cmd->convert_src==TRIG_TIMER){ + if(cmd->convert_argai_ns_min){ + cmd->convert_arg=this_board->ai_ns_min; + err++; + } + } else { /* TRIG_EXT */ + if(cmd->convert_arg!=0){ + cmd->convert_arg=0; + err++; + } + } + + if(!cmd->chanlist_len){ + cmd->chanlist_len=1; + err++; + } + if(cmd->chanlist_len > this_board->n_aichan){ + cmd->chanlist_len=this_board->n_aichan; + 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++; + } + } + + if(err) { + return 3; + } + + /* step 4: fix up any arguments */ + if(cmd->convert_src==TRIG_TIMER){ + tmp=cmd->convert_arg; + i8253_cascade_ns_to_timer (this_board->i8254_osc_base, + &divisor1, &divisor2, &cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + if(cmd->convert_argai_ns_min) + cmd->convert_arg=this_board->ai_ns_min; + if(tmp!=cmd->convert_arg)err++; + } + + if(err) { + return 4; + } + + return 0; +} + +static int pcl816_ai_cmd(comedi_device *dev,comedi_subdevice *s) +{ + unsigned int divisor1, divisor2, dma_flags, bytes, dmairq; + comedi_cmd *cmd=&s->async->cmd; + + + if(cmd->start_src!=TRIG_NOW) return -EINVAL; + if(cmd->scan_begin_src!=TRIG_FOLLOW) return -EINVAL; + if(cmd->scan_end_src!=TRIG_COUNT) return -EINVAL; + if(cmd->scan_end_arg!=cmd->chanlist_len) return -EINVAL; +// if(cmd->chanlist_len>MAX_CHANLIST_LEN) return -EINVAL; + if (devpriv->irq_blocked) + return -EBUSY; + + if (cmd->convert_src==TRIG_TIMER) { + if(cmd->convert_arg < this_board->ai_ns_min) + cmd->convert_arg=this_board->ai_ns_min; + + i8253_cascade_ns_to_timer (this_board->i8254_osc_base, &divisor1, + &divisor2, &cmd->convert_arg, cmd->flags&TRIG_ROUND_MASK); + if (divisor1 == 1) { // PCL816 crash if any divisor is set to 1 + divisor1 = 2; + divisor2 /= 2; + } + if (divisor2 == 1) { + divisor2 = 2; + divisor1 /= 2; + } + } + + start_pacer(dev, -1, 0, 0); // stop pacer + + if (!check_and_setup_channel_list (dev, s, cmd->chanlist, cmd->chanlist_len)) + return -EINVAL; + udelay (1); + + + devpriv->ai_act_scan=0; + s->async->cur_chan=0; + devpriv->irq_blocked = 1; + devpriv->ai_poll_ptr=0; + devpriv->irq_was_now_closed = 0; + devpriv->buf_ptr = 0; + + if (cmd->stop_src==TRIG_COUNT) { + devpriv->ai_scans = cmd->stop_arg; + devpriv->ai_neverending = 0; + } else { + devpriv->ai_scans = 0; + devpriv->ai_neverending = 1; + } + + + if ((cmd->flags & TRIG_WAKE_EOS)) { // don't we want wake up every scan? + printk("pl816: You wankt WAKE_EOS but I dont want handle it"); + // devpriv->ai_eos=1; + //if (devpriv->ai_n_chan==1) + // devpriv->dma=0; // DMA is useless for this situation + } + + if (devpriv->dma) { + bytes = devpriv->hwdmasize[0]; + if (!devpriv->ai_neverending) { + bytes = s->async->cmd.chanlist_len * s->async->cmd.chanlist_len * sizeof (sampl_t); // how many + devpriv->dma_runs_to_end = bytes / devpriv->hwdmasize[0]; // how many DMA pages we must fill + devpriv->last_dma_run = bytes % devpriv->hwdmasize[0]; //on last dma transfer must be moved + devpriv->dma_runs_to_end--; + if (devpriv->dma_runs_to_end >= 0) + bytes = devpriv->hwdmasize[0]; + } else + devpriv->dma_runs_to_end = -1; + + devpriv->next_dma_buf = 0; + set_dma_mode (devpriv->dma, DMA_MODE_READ); + dma_flags = claim_dma_lock (); + clear_dma_ff (devpriv->dma); + set_dma_addr (devpriv->dma, devpriv->hwdmaptr[0]); + set_dma_count (devpriv->dma, bytes); + release_dma_lock (dma_flags); + enable_dma (devpriv->dma); + } + + + start_pacer(dev, 1, divisor1, divisor2); + dmairq = ((devpriv->dma & 0x3)<<4) | (dev->irq & 0x7); + + switch (cmd->convert_src) { + case TRIG_TIMER: + devpriv->int816_mode = INT_TYPE_AI1_DMA; + outb (0x32, dev->iobase + PCL816_CONTROL); // Pacer+IRQ+DMA + outb(dmairq, dev->iobase + PCL816_STATUS); // write irq and DMA to card + break; + + default: + devpriv->int816_mode = INT_TYPE_AI3_DMA; + outb (0x34, dev->iobase + PCL816_CONTROL); // Ext trig+IRQ+DMA + outb(dmairq, dev->iobase + PCL816_STATUS); // write irq to card + break; + } + + + DPRINTK("pcl816 END: pcl812_ai_cmd()\n"); + return 0; +} + + +static int pcl816_ai_poll(comedi_device *dev,comedi_subdevice *s) +{ + unsigned long flags; + unsigned int top1,top2,i; + + if (!devpriv->dma) return 0; // poll is valid only for DMA transfer + + comedi_spin_lock_irqsave(&dev->spinlock, flags); + + for (i=0; i<20; i++) { + top1 = get_dma_residue(devpriv->dma); // where is now DMA + top2 = get_dma_residue(devpriv->dma); + if (top1 == top2) break; + } + if (top1 != top2) { + comedi_spin_unlock_irqrestore(&dev->spinlock, flags); + return 0; + } + + top1 = devpriv->hwdmasize [0] - top1; // where is now DMA in buffer + top1 >>= 1; // sample position + top2 = top1 - devpriv->ai_poll_ptr; + if (top2 < 1) { // no new samples + comedi_spin_unlock_irqrestore(&dev->spinlock,flags); + return 0; + } + + transfer_from_dma_buf(dev, s, (sampl_t*)devpriv->dmabuf[devpriv->next_dma_buf], + devpriv->ai_poll_ptr, top2); + + devpriv->ai_poll_ptr = top1; // new buffer position + comedi_spin_unlock_irqrestore(&dev->spinlock,flags); + + return s->async->buf_int_count - s->async->buf_user_count; +} + + +/* +============================================================================== + cancel any mode 1-4 AI +*/ +int +pcl816_ai_cancel (comedi_device * dev, comedi_subdevice * s) +{ +// DEBUG(rt_printk("pcl816_ai_cancel()\n");) + + if (devpriv->irq_blocked > 0) { + switch (devpriv->int816_mode) { + case INT_TYPE_AI1_DMA_RTC: + case INT_TYPE_AI3_DMA_RTC: + set_rtc_irq_bit (0); // stop RTC + del_timer (&devpriv->rtc_irq_timer); + case INT_TYPE_AI1_DMA: + case INT_TYPE_AI3_DMA: + disable_dma (devpriv->dma); + case INT_TYPE_AI1_INT: + case INT_TYPE_AI3_INT: + outb (inb (dev->iobase + PCL816_CONTROL) & 0x73, dev->iobase + PCL816_CONTROL); /* Stop A/D */ + udelay (1); + outb (0, dev->iobase + PCL816_CONTROL); /* Stop A/D */ + outb (0xb0, dev->iobase + PCL816_CTRCTL); /* Stop pacer */ + outb (0x70, dev->iobase + PCL816_CTRCTL); + outb (0, dev->iobase + PCL816_AD_LO); + inb (dev->iobase + PCL816_AD_LO); + inb (dev->iobase + PCL816_AD_HI); + outb (0, dev->iobase + PCL816_CLRINT); /* clear INT request */ + outb (0, dev->iobase + PCL816_CONTROL); /* Stop A/D */ + devpriv->irq_blocked = 0; + devpriv->irq_was_now_closed = devpriv->int816_mode; + devpriv->int816_mode = 0; + devpriv->last_int_sub = s; +// s->busy = 0; + break; + } + } + + DEBUG(rt_printk("comedi: pcl816_ai_cancel() successful\n");) + return 0; +} + +/* +============================================================================== + chech for PCL816 +*/ +int pcl816_check (int iobase) +{ + outb (0x00, iobase + PCL816_MUX); + udelay (1); + if (inb (iobase + PCL816_MUX) != 0x00) + return 1; //there isn't card + outb (0x55, iobase + PCL816_MUX); + udelay (1); + if (inb (iobase + PCL816_MUX) != 0x55) + return 1; //there isn't card + outb (0x00, iobase + PCL816_MUX); + udelay (1); + outb (0x18, iobase + PCL816_CONTROL); + udelay (1); + if (inb (iobase + PCL816_CONTROL) != 0x18) + return 1; //there isn't card + return 0; // ok, card exist +} + +/* +============================================================================== + reset whole PCL-816 cards +*/ +void +pcl816_reset (comedi_device * dev) +{ +// outb (0, dev->iobase + PCL818_DA_LO); // DAC=0V +// outb (0, dev->iobase + PCL818_DA_HI); +// udelay (1); +// outb (0, dev->iobase + PCL818_DO_HI); // DO=$0000 +// outb (0, dev->iobase + PCL818_DO_LO); +// udelay (1); + outb (0, dev->iobase + PCL816_CONTROL); + outb (0, dev->iobase + PCL816_MUX); + outb (0, dev->iobase + PCL816_CLRINT); + outb (0xb0, dev->iobase + PCL816_CTRCTL); /* Stop pacer */ + outb (0x70, dev->iobase + PCL816_CTRCTL); + outb (0x30, dev->iobase + PCL816_CTRCTL); + outb (0, dev->iobase + PCL816_RANGE); +} + + +/* +============================================================================== + Start/stop pacer onboard pacer +*/ +static void +start_pacer (comedi_device * dev, int mode, unsigned int divisor1, + unsigned int divisor2) +{ + outb (0x32, dev->iobase + PCL816_CTRCTL); + outb (0xff, dev->iobase + PCL816_CTR0); + outb (0x00, dev->iobase + PCL816_CTR0); + udelay (1); + outb(0xb4, dev->iobase + PCL816_CTRCTL); // set counter 2 as mode 3 + outb(0x74, dev->iobase + PCL816_CTRCTL); // set counter 1 as mode 3 + udelay(1); + + if (mode == 1) { + DPRINTK("mode %d, divisor1 %d, divisor2 %d\n", mode, divisor1, divisor2); + outb(divisor2 & 0xff, dev->iobase + PCL816_CTR2); + outb((divisor2 >> 8) & 0xff, dev->iobase + PCL816_CTR2); + outb(divisor1 & 0xff, dev->iobase + PCL816_CTR1); + outb((divisor1 >> 8) & 0xff, dev->iobase + PCL816_CTR1); + } + + /* clear pending interrupts (just in case) */ +// outb(0, dev->iobase + PCL816_CLRINT); +} + +/* +============================================================================== + Check if channel list from user is builded correctly + If it's ok, then program scan/gain logic +*/ +static int +check_and_setup_channel_list (comedi_device * dev, comedi_subdevice * s, unsigned int *chanlist, int chanlen) +{ + unsigned int chansegment[16]; + unsigned int i, nowmustbechan, seglen, segpos; + + // correct channel and range number check itself comedi/range.c + if (chanlen < 1) { + comedi_error (dev, "range/channel list is empty!"); + return 0; + } + + if (chanlen > 1) + { + chansegment[0] = chanlist[0]; // first channel is everytime ok + for (i = 1, seglen = 1; i < chanlen; i++, seglen++) { + // build part of chanlist + DEBUG(rt_printk("%d. %d %d\n",i,CR_CHAN(chanlist[i]),CR_RANGE(chanlist[i]));) + if (chanlist[0] == chanlist[i]) + break; // we detect loop, this must by finish + nowmustbechan = (CR_CHAN (chansegment[i - 1]) + 1) % chanlen; + if (nowmustbechan != CR_CHAN (chanlist[i])) { + // channel list isn't continous :-( + rt_printk("comedi%d: pcl816: channel list must be continous! chanlist[%i]=%d but must be %d or %d!\n", + dev->minor, i, CR_CHAN (chanlist[i]), nowmustbechan, CR_CHAN (chanlist[0])); + return 0; + } + chansegment[i] = chanlist[i]; // well, this is next correct channel in list + } + + for (i = 0, segpos = 0; i < chanlen; i++) + { // check whole chanlist + DEBUG(rt_printk("%d %d=%d %d\n",CR_CHAN(chansegment[i%seglen]),CR_RANGE(chansegment[i%seglen]),CR_CHAN(chanlist[i]),CR_RANGE(chanlist[i]));) + if (chanlist[i] != chansegment[i % seglen]) { + rt_printk("comedi%d: pcl816: bad channel or range number! chanlist[%i]=%d,%d,%d and not %d,%d,%d!\n", + dev->minor, i, CR_CHAN (chansegment[i]), + CR_RANGE (chansegment[i]), CR_AREF (chansegment[i]), + CR_CHAN (chanlist[i % seglen]), + CR_RANGE(chanlist[i % seglen]), + CR_AREF (chansegment[i % seglen])); + return 0; // chan/gain list is strange + } + } + } + else + { + seglen = 1; + } + + devpriv->ai_act_chanlist_len = seglen; + devpriv->ai_act_chanlist_pos = 0; + + for (i = 0; i < seglen; i++) { // store range list to card + devpriv->ai_act_chanlist[i] = CR_CHAN (chanlist[i]); + outb (CR_CHAN(chanlist[0]) & 0xf, dev->iobase + PCL816_MUX); + outb (CR_RANGE(chanlist[0]), dev->iobase + PCL816_RANGE); /* select gain */ + } + + udelay (1); + + outb (devpriv->ai_act_chanlist[0] | (devpriv->ai_act_chanlist[seglen - 1] << 4), + dev->iobase + PCL816_MUX); /* select channel interval to scan */ + + return 1; // we can serve this with MUX logic +} + +/* +============================================================================== + Enable(1)/disable(0) periodic interrupts from RTC +*/ +static int set_rtc_irq_bit(unsigned char bit) +{ + unsigned char val; + unsigned long flags; + + if (bit==1) { + RTC_timer_lock++; + if (RTC_timer_lock>1) return 0; + } else { + RTC_timer_lock--; + if (RTC_timer_lock<0) RTC_timer_lock=0; + if (RTC_timer_lock>0) return 0; + } + + save_flags(flags); + cli(); + val = CMOS_READ(RTC_CONTROL); + if (bit) { val |= RTC_PIE; } + else { val &= ~RTC_PIE; } + CMOS_WRITE(val, RTC_CONTROL); + CMOS_READ(RTC_INTR_FLAGS); + restore_flags(flags); + return 0; +} + + +/* +============================================================================== + Free any resources that we have claimed +*/ +static void +free_resources (comedi_device * dev) +{ + //rt_printk("free_resource()\n"); + if (dev->private) + { + pcl816_ai_cancel (dev, devpriv->sub_ai); + pcl816_reset (dev); + if (devpriv->dma) + free_dma (devpriv->dma); + if (devpriv->dmabuf[0]) + free_pages (devpriv->dmabuf[0], devpriv->dmapages[0]); + if (devpriv->dmabuf[1]) + free_pages (devpriv->dmabuf[1], devpriv->dmapages[1]); + if (devpriv->rtc_irq) + free_irq (devpriv->rtc_irq, dev); + if ((devpriv->dma_rtc) && (RTC_lock == 1)) + { + if (devpriv->rtc_iobase) + release_region (devpriv->rtc_iobase, devpriv->rtc_iosize); + } + } + + if (dev->irq) + free_irq (dev->irq, dev); + if (dev->iobase) + release_region (dev->iobase, this_board->io_range); + //rt_printk("free_resource() end\n"); +} + + +/* +============================================================================== + + Initialization + +*/ +static int +pcl816_attach (comedi_device * dev, comedi_devconfig * it) +{ + int ret; + int iobase; + int irq, dma; + unsigned long pages; + int i; + comedi_subdevice *s; + int num_of_subdevs, subdevs[5]; + + /* claim our I/O space */ + iobase = it->options[0]; + printk("comedi%d: pcl816: board=%s, ioport=0x%03x", dev->minor, + this_board->name, iobase); + + if (check_region (iobase, this_board->io_range) < 0) { + rt_printk ("I/O port conflict\n"); + return -EIO; + } + + request_region (iobase, this_board->io_range, "pcl816"); + dev->iobase = iobase; + + if (pcl816_check (iobase)) { + rt_printk (", I cann't detect board. FAIL!\n"); + return -EIO; + } + + if ((ret = alloc_private (dev, sizeof (pcl816_private))) < 0) + return ret; /* Can't alloc mem */ + + /* set up some name stuff */ + dev->board_name = this_board->name; + + /* grab our IRQ */ + irq = 0; + if (this_board->IRQbits != 0) { /* board support IRQ */ + irq = it->options[1]; + if (irq > 0) + { /* we want to use IRQ */ + if (((1 << irq) & this_board->IRQbits) == 0) + { + rt_printk (", IRQ %d is out of allowed range, DISABLING IT", irq); + irq = 0; /* Bad IRQ */ + } + else + { + if (request_irq(irq, interrupt_pcl816, SA_INTERRUPT, "pcl816", dev)) { + rt_printk (", unable to allocate IRQ %d, DISABLING IT",irq); + irq = 0; /* Can't use IRQ */ + } + else { + rt_printk (", irq=%d", irq); + } + } + } + } + + dev->irq = irq; + if (irq) { + devpriv->irq_free = 1; + } /* 1=we have allocated irq */ + else { + devpriv->irq_free = 0; + } + devpriv->irq_blocked = 0; /* number of subdevice which use IRQ */ + devpriv->int816_mode = 0; /* mode of irq */ + + /* grab RTC for DMA operations */ + devpriv->dma_rtc = 0; + if (it->options[2] > 0) + { // we want to use DMA + if (RTC_lock == 0) + { + if (check_region (RTC_PORT (0), RTC_IO_EXTENT) < 0) + goto no_rtc; + request_region (RTC_PORT (0), RTC_IO_EXTENT, "pcl816 (RTC)"); + } + devpriv->rtc_iobase = RTC_PORT (0); + devpriv->rtc_iosize = RTC_IO_EXTENT; + RTC_lock++; +#ifdef UNTESTED_CODE + if (!request_irq (RTC_IRQ, interrupt_pcl816_ai_mode13_dma_rtc, + SA_INTERRUPT | SA_SHIRQ, "pcl816 DMA (RTC)", dev)) { + devpriv->dma_rtc = 1; + devpriv->rtc_irq = RTC_IRQ; + rt_printk (", dma_irq=%d", devpriv->rtc_irq); + } else { + RTC_lock--; + if (RTC_lock == 0) + { + if (devpriv->rtc_iobase) + release_region (devpriv->rtc_iobase, devpriv->rtc_iosize); + } + devpriv->rtc_iobase = 0; + devpriv->rtc_iosize = 0; + } +#else + printk("pcl816: RTC code missing"); +#endif + + } + +no_rtc: + /* grab our DMA */ + dma = 0; + devpriv->dma = dma; + if ((devpriv->irq_free == 0) && (devpriv->dma_rtc == 0)) + goto no_dma; /* if we haven't IRQ, we can't use DMA */ + + if (this_board->DMAbits != 0) { /* board support DMA */ + dma = it->options[2]; + if (dma < 1) + goto no_dma; /* DMA disabled */ + + if (((1 << dma) & this_board->DMAbits) == 0) { + rt_printk (", DMA is out of allowed range, FAIL!\n"); + return -EINVAL; /* Bad DMA */ + } + ret = request_dma (dma, "pcl816"); + if (ret) { + rt_printk (", unable to allocate DMA %d, FAIL!\n", dma); + return -EBUSY; /* DMA isn't free */ + } + + devpriv->dma = dma; + rt_printk (", dma=%d", dma); + pages = 2; /* we need 16KB */ + devpriv->dmabuf[0] = __get_dma_pages (GFP_KERNEL, pages); + + if (!devpriv->dmabuf[0]) { + rt_printk (", unable to allocate DMA buffer, FAIL!\n"); + /* maybe experiment with try_to_free_pages() will help .... */ + return -EBUSY; /* no buffer :-( */ + } + devpriv->dmapages[0] = pages; + devpriv->hwdmaptr[0] = virt_to_bus ((void *) devpriv->dmabuf[0]); + devpriv->hwdmasize[0] = (1 << pages) * PAGE_SIZE; + //rt_printk("%d %d %ld, ",devpriv->dmapages[0],devpriv->hwdmasize[0],PAGE_SIZE); + + if (devpriv->dma_rtc == 0) { // we must do duble buff :-( + devpriv->dmabuf[1] = __get_dma_pages (GFP_KERNEL, pages); + if (!devpriv->dmabuf[1]) { + rt_printk (", unable to allocate DMA buffer, FAIL!\n"); + return -EBUSY; + } + devpriv->dmapages[1] = pages; + devpriv->hwdmaptr[1] = virt_to_bus ((void *) devpriv->dmabuf[1]); + devpriv->hwdmasize[1] = (1 << pages) * PAGE_SIZE; + } + } + +no_dma: + + num_of_subdevs = 0; + + if (this_board->n_aichan > 0) + subdevs[num_of_subdevs++] = COMEDI_SUBD_AI; +/* if (this_board->n_aochan > 0) + subdevs[num_of_subdevs++] = COMEDI_SUBD_AO; + if (this_board->n_dichan > 0) + subdevs[num_of_subdevs++] = COMEDI_SUBD_DI; + if (this_board->n_dochan > 0) + subdevs[num_of_subdevs++] = COMEDI_SUBD_DO; +*/ + dev->n_subdevices = num_of_subdevs; + if ((ret = alloc_subdevices (dev)) < 0) + return ret; + + s = dev->subdevices + 0; + for (i = 0; i < num_of_subdevs; i++) { + s->type = subdevs[i]; + switch (s->type) { + + case COMEDI_SUBD_AI: + devpriv->sub_ai = s; + dev->read_subdev = s; + s->subdev_flags = SDF_READABLE | SDF_RT; + s->n_chan = this_board->n_aichan; + s->subdev_flags |= SDF_DIFF; + rt_printk (", %dchans DIFF DAC - %d", s->n_chan, i); + s->maxdata = this_board->ai_maxdata; + s->len_chanlist = this_board->ai_chanlist; + s->range_table = this_board->ai_range_type; + s->cancel = pcl816_ai_cancel; + s->do_cmdtest = pcl816_ai_cmdtest; + s->do_cmd = pcl816_ai_cmd; + s->poll = pcl816_ai_poll; + s->insn_read = pcl816_ai_insn_read; + break; + + case COMEDI_SUBD_AO: + s->subdev_flags = SDF_WRITEABLE | SDF_GROUND | SDF_RT; + s->n_chan = this_board->n_aochan; + s->maxdata = this_board->ao_maxdata; + s->len_chanlist = this_board->ao_chanlist; + s->range_table = this_board->ao_range_type; + break; + + case COMEDI_SUBD_DI: + s->subdev_flags = SDF_READABLE | SDF_RT; + s->n_chan = this_board->n_dichan; + s->maxdata = 1; + s->len_chanlist = this_board->n_dichan; + s->range_table = &range_digital; + break; + + case COMEDI_SUBD_DO: + s->subdev_flags = SDF_WRITEABLE | SDF_RT; + s->n_chan = this_board->n_dochan; + s->maxdata = 1; + s->len_chanlist = this_board->n_dochan; + s->range_table = &range_digital; + break; + } + s++; + } + + + + pcl816_reset (dev); + + rt_printk ("\n"); + + return 0; +} + + +/* +============================================================================== + Removes device + */ +static int +pcl816_detach (comedi_device * dev) +{ + DEBUG(rt_printk("comedi%d: pcl816: remove\n", dev->minor);) + free_resources (dev); + if (devpriv->dma_rtc) + RTC_lock--; + return 0; +} -- 2.26.2