diff options
Diffstat (limited to 'drivers/staging/comedi/drivers/pcl816.c')
| -rw-r--r-- | drivers/staging/comedi/drivers/pcl816.c | 1134 |
1 files changed, 1134 insertions, 0 deletions
diff --git a/drivers/staging/comedi/drivers/pcl816.c b/drivers/staging/comedi/drivers/pcl816.c new file mode 100644 index 00000000..91bd2071 --- /dev/null +++ b/drivers/staging/comedi/drivers/pcl816.c @@ -0,0 +1,1134 @@ +/* + comedi/drivers/pcl816.c + + Author: Juan Grigera <juan@grigera.com.ar> + based on pcl818 by Michal Dobes <dobes@tesnet.cz> and bits of pcl812 + + hardware driver for Advantech cards: + card: PCL-816, PCL814B + driver: pcl816 +*/ +/* +Driver: pcl816 +Description: Advantech PCL-816 cards, PCL-814 +Author: Juan Grigera <juan@grigera.com.ar> +Devices: [Advantech] PCL-816 (pcl816), PCL-814B (pcl814b) +Status: works +Updated: Tue, 2 Apr 2002 23:15:21 -0800 + +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. + +Analog output and digital input and output are not supported. + +Configuration Options: + [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 + +*/ + +#include "../comedidev.h" + +#include <linux/ioport.h> +#include <linux/gfp.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <asm/dma.h> + +#include "comedi_fc.h" +#include "8253.h" + +#define DEBUG(x) x + +/* 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 MAGIC_DMA_WORD 0x5a5a + +static const struct 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), + } +}; + +struct pcl816_board { + + const char *name; /* board 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 allowed 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 */ + const struct comedi_lrange *ai_range_type; /* default A/D rangelist */ + const struct comedi_lrange *ao_range_type; /* default D/A rangelist */ + unsigned 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 */ +}; + +struct pcl816_private { + + unsigned int dma; /* used DMA, 0=don't use DMA */ + 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 */ + 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 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 */ + struct 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_n_chan; /* how many channels per scan */ + unsigned int ai_poll_ptr; /* how many sampes transfer poll */ + struct comedi_subdevice *sub_ai; /* ptr to AI subdevice */ +}; + +/* +============================================================================== +*/ +static int check_channel_list(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int *chanlist, unsigned int chanlen); +static void setup_channel_list(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int *chanlist, unsigned int seglen); +static int pcl816_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s); +static void start_pacer(struct comedi_device *dev, int mode, + unsigned int divisor1, unsigned int divisor2); + +static int pcl816_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd); +static int pcl816_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s); + +/* +============================================================================== + ANALOG INPUT MODE0, 816 cards, slow version +*/ +static int pcl816_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n; + int timeout; + + DPRINTK("mode 0 analog input\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); + /* select gain */ + outb(CR_RANGE(insn->chanspec), dev->iobase + PCL816_RANGE); + + for (n = 0; n < insn->n; n++) { + + 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))); + /* clear INT (conversion end) flag */ + outb(0, dev->iobase + PCL816_CLRINT); + break; + } + udelay(1); + } + /* Return timeout error */ + if (!timeout) { + comedi_error(dev, "A/D insn timeout\n"); + data[0] = 0; + /* clear INT (conversion end) flag */ + outb(0, dev->iobase + PCL816_CLRINT); + return -EIO; + } + + } + return n; +} + +/* +============================================================================== + analog input interrupt mode 1 & 3, 818 cards + one sample per interrupt version +*/ +static irqreturn_t interrupt_pcl816_ai_mode13_int(int irq, void *d) +{ + struct comedi_device *dev = d; + struct pcl816_private *devpriv = dev->private; + struct 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); + return IRQ_HANDLED; + + } + + /* get the sample */ + low = inb(dev->iobase + PCL816_AD_LO); + hi = inb(dev->iobase + PCL816_AD_HI); + + comedi_buf_put(s->async, (hi << 8) | low); + + outb(0, dev->iobase + PCL816_CLRINT); /* clear INT request */ + + if (++devpriv->ai_act_chanlist_pos >= devpriv->ai_act_chanlist_len) + devpriv->ai_act_chanlist_pos = 0; + + s->async->cur_chan++; + if (s->async->cur_chan >= devpriv->ai_n_chan) { + s->async->cur_chan = 0; + devpriv->ai_act_scan++; + } + + if (!devpriv->ai_neverending) + /* all data sampled */ + if (devpriv->ai_act_scan >= devpriv->ai_scans) { + /* all data sampled */ + pcl816_ai_cancel(dev, s); + s->async->events |= COMEDI_CB_EOA; + } + comedi_event(dev, s); + return IRQ_HANDLED; +} + +/* +============================================================================== + analog input dma mode 1 & 3, 816 cards +*/ +static void transfer_from_dma_buf(struct comedi_device *dev, + struct comedi_subdevice *s, short *ptr, + unsigned int bufptr, unsigned int len) +{ + struct pcl816_private *devpriv = dev->private; + int i; + + s->async->events = 0; + + for (i = 0; i < len; i++) { + + comedi_buf_put(s->async, ptr[bufptr++]); + + if (++devpriv->ai_act_chanlist_pos >= + devpriv->ai_act_chanlist_len) { + devpriv->ai_act_chanlist_pos = 0; + } + + s->async->cur_chan++; + if (s->async->cur_chan >= devpriv->ai_n_chan) { + s->async->cur_chan = 0; + devpriv->ai_act_scan++; + } + + if (!devpriv->ai_neverending) + /* all data sampled */ + if (devpriv->ai_act_scan >= devpriv->ai_scans) { + pcl816_ai_cancel(dev, s); + s->async->events |= COMEDI_CB_EOA; + s->async->events |= COMEDI_CB_BLOCK; + break; + } + } + + comedi_event(dev, s); +} + +static irqreturn_t interrupt_pcl816_ai_mode13_dma(int irq, void *d) +{ + struct comedi_device *dev = d; + struct pcl816_private *devpriv = dev->private; + struct comedi_subdevice *s = &dev->subdevices[0]; + int len, bufptr, this_dma_buf; + unsigned long dma_flags; + short *ptr; + + disable_dma(devpriv->dma); + this_dma_buf = devpriv->next_dma_buf; + + /* switch dma bufs */ + if ((devpriv->dma_runs_to_end > -1) || devpriv->ai_neverending) { + + 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 = (short *)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); + return IRQ_HANDLED; +} + +/* +============================================================================== + INT procedure +*/ +static irqreturn_t interrupt_pcl816(int irq, void *d) +{ + struct comedi_device *dev = d; + struct pcl816_private *devpriv = dev->private; + + DPRINTK("<I>"); + + if (!dev->attached) { + comedi_error(dev, "premature interrupt"); + return IRQ_HANDLED; + } + + switch (devpriv->int816_mode) { + case INT_TYPE_AI1_DMA: + case INT_TYPE_AI3_DMA: + return interrupt_pcl816_ai_mode13_dma(irq, d); + case INT_TYPE_AI1_INT: + case INT_TYPE_AI3_INT: + return interrupt_pcl816_ai_mode13_int(irq, d); + } + + 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 IRQ_HANDLED; + } + comedi_error(dev, "bad IRQ!"); + return IRQ_NONE; + } + comedi_error(dev, "IRQ from unknown source!"); + return IRQ_NONE; +} + +/* +============================================================================== + COMMAND MODE +*/ +static void pcl816_cmdtest_out(int e, struct comedi_cmd *cmd) +{ + printk(KERN_INFO "pcl816 e=%d startsrc=%x scansrc=%x convsrc=%x\n", e, + cmd->start_src, cmd->scan_begin_src, cmd->convert_src); + printk(KERN_INFO "pcl816 e=%d startarg=%d scanarg=%d convarg=%d\n", e, + cmd->start_arg, cmd->scan_begin_arg, cmd->convert_arg); + printk(KERN_INFO "pcl816 e=%d stopsrc=%x scanend=%x\n", e, + cmd->stop_src, cmd->scan_end_src); + printk(KERN_INFO "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(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + const struct pcl816_board *board = comedi_board(dev); + int err = 0; + int tmp, divisor1 = 0, divisor2 = 0; + + DEBUG(printk(KERN_INFO "pcl816 pcl812_ai_cmdtest\n"); + pcl816_cmdtest_out(-1, cmd); + ); + + /* Step 1 : check if triggers are trivially valid */ + + err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); + err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW); + err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_EXT | TRIG_TIMER); + err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); + err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (err) + return 1; + + /* Step 2a : make sure trigger sources are unique */ + + err |= cfc_check_trigger_is_unique(cmd->convert_src); + err |= cfc_check_trigger_is_unique(cmd->stop_src); + + /* Step 2b : and mutually compatible */ + + if (err) + return 2; + + + /* Step 3: check if arguments are trivially valid */ + + err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); + err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); + + if (cmd->convert_src == TRIG_TIMER) + err |= cfc_check_trigger_arg_min(&cmd->convert_arg, + board->ai_ns_min); + else /* TRIG_EXT */ + err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); + + err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); + + if (cmd->stop_src == TRIG_COUNT) + err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1); + else /* TRIG_NONE */ + err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0); + + 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(board->i8254_osc_base, + &divisor1, &divisor2, + &cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + if (cmd->convert_arg < board->ai_ns_min) + cmd->convert_arg = board->ai_ns_min; + if (tmp != cmd->convert_arg) + err++; + } + + if (err) + return 4; + + + /* step 5: complain about special chanlist considerations */ + + if (cmd->chanlist) { + if (!check_channel_list(dev, s, cmd->chanlist, + cmd->chanlist_len)) + return 5; /* incorrect channels list */ + } + + return 0; +} + +static int pcl816_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + const struct pcl816_board *board = comedi_board(dev); + struct pcl816_private *devpriv = dev->private; + unsigned int divisor1 = 0, divisor2 = 0, dma_flags, bytes, dmairq; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int seglen; + + 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 < board->ai_ns_min) + cmd->convert_arg = board->ai_ns_min; + + i8253_cascade_ns_to_timer(board->i8254_osc_base, &divisor1, + &divisor2, &cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + + /* PCL816 crash if any divisor is set to 1 */ + if (divisor1 == 1) { + divisor1 = 2; + divisor2 /= 2; + } + if (divisor2 == 1) { + divisor2 = 2; + divisor1 /= 2; + } + } + + start_pacer(dev, -1, 0, 0); /* stop pacer */ + + seglen = check_channel_list(dev, s, cmd->chanlist, cmd->chanlist_len); + if (seglen < 1) + return -EINVAL; + setup_channel_list(dev, s, cmd->chanlist, seglen); + udelay(1); + + devpriv->ai_n_chan = cmd->chanlist_len; + 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; + + 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; + } + + /* don't we want wake up every scan? */ + if ((cmd->flags & TRIG_WAKE_EOS)) { + printk(KERN_INFO + "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) { + /* how many */ + bytes = s->async->cmd.chanlist_len * + s->async->cmd.chanlist_len * + sizeof(short); + + /* how many DMA pages we must fill */ + devpriv->dma_runs_to_end = bytes / + devpriv->hwdmasize[0]; + + /* on last dma transfer must be moved */ + devpriv->last_dma_run = bytes % devpriv->hwdmasize[0]; + 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; + + /* Pacer+IRQ+DMA */ + outb(0x32, dev->iobase + PCL816_CONTROL); + + /* write irq and DMA to card */ + outb(dmairq, dev->iobase + PCL816_STATUS); + break; + + default: + devpriv->int816_mode = INT_TYPE_AI3_DMA; + + /* Ext trig+IRQ+DMA */ + outb(0x34, dev->iobase + PCL816_CONTROL); + + /* write irq to card */ + outb(dmairq, dev->iobase + PCL816_STATUS); + break; + } + + DPRINTK("pcl816 END: pcl812_ai_cmd()\n"); + return 0; +} + +static int pcl816_ai_poll(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct pcl816_private *devpriv = dev->private; + unsigned long flags; + unsigned int top1, top2, i; + + if (!devpriv->dma) + return 0; /* poll is valid only for DMA transfer */ + + 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) { + spin_unlock_irqrestore(&dev->spinlock, flags); + return 0; + } + + /* where is now DMA in buffer */ + top1 = devpriv->hwdmasize[0] - top1; + top1 >>= 1; /* sample position */ + top2 = top1 - devpriv->ai_poll_ptr; + if (top2 < 1) { /* no new samples */ + spin_unlock_irqrestore(&dev->spinlock, flags); + return 0; + } + + transfer_from_dma_buf(dev, s, + (short *)devpriv->dmabuf[devpriv->next_dma_buf], + devpriv->ai_poll_ptr, top2); + + devpriv->ai_poll_ptr = top1; /* new buffer position */ + spin_unlock_irqrestore(&dev->spinlock, flags); + + return s->async->buf_write_count - s->async->buf_read_count; +} + +/* +============================================================================== + cancel any mode 1-4 AI +*/ +static int pcl816_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct pcl816_private *devpriv = dev->private; + +/* DEBUG(printk("pcl816_ai_cancel()\n");) */ + + if (devpriv->irq_blocked > 0) { + switch (devpriv->int816_mode) { + 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 */ + + /* Stop pacer */ + outb(0xb0, dev->iobase + PCL816_CTRCTL); + 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); + + /* clear INT request */ + outb(0, dev->iobase + PCL816_CLRINT); + + /* Stop A/D */ + outb(0, dev->iobase + PCL816_CONTROL); + 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(printk("comedi: pcl816_ai_cancel() successful\n");) + return 0; +} + +/* +============================================================================== + chech for PCL816 +*/ +static int pcl816_check(unsigned long 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 +*/ +static void pcl816_reset(struct 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(struct 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); + + /* set counter 2 as mode 3 */ + outb(0xb4, dev->iobase + PCL816_CTRCTL); + /* set counter 1 as mode 3 */ + outb(0x74, dev->iobase + PCL816_CTRCTL); + 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 built correctly + If it's ok, then return non-zero length of repeated segment of channel list +*/ +static int +check_channel_list(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned int *chanlist, + unsigned 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) { + /* first channel is every time ok */ + chansegment[0] = chanlist[0]; + for (i = 1, seglen = 1; i < chanlen; i++, seglen++) { + /* build part of chanlist */ + DEBUG(printk(KERN_INFO "%d. %d %d\n", i, + CR_CHAN(chanlist[i]), + CR_RANGE(chanlist[i]));) + + /* we detect loop, this must by finish */ + if (chanlist[0] == chanlist[i]) + break; + nowmustbechan = + (CR_CHAN(chansegment[i - 1]) + 1) % chanlen; + if (nowmustbechan != CR_CHAN(chanlist[i])) { + /* channel list isn't continuous :-( */ + printk(KERN_WARNING + "comedi%d: pcl816: channel list must " + "be continuous! chanlist[%i]=%d but " + "must be %d or %d!\n", dev->minor, + i, CR_CHAN(chanlist[i]), nowmustbechan, + CR_CHAN(chanlist[0])); + return 0; + } + /* well, this is next correct channel in list */ + chansegment[i] = chanlist[i]; + } + + /* check whole chanlist */ + for (i = 0, segpos = 0; i < chanlen; i++) { + DEBUG(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]) { + printk(KERN_WARNING + "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; + } + + return seglen; /* we can serve this with MUX logic */ +} + +/* +============================================================================== + Program scan/gain logic with channel list. +*/ +static void +setup_channel_list(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned int *chanlist, + unsigned int seglen) +{ + struct pcl816_private *devpriv = dev->private; + unsigned int i; + + 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); + /* select gain */ + outb(CR_RANGE(chanlist[0]), dev->iobase + PCL816_RANGE); + } + + udelay(1); + /* select channel interval to scan */ + outb(devpriv->ai_act_chanlist[0] | + (devpriv->ai_act_chanlist[seglen - 1] << 4), + dev->iobase + PCL816_MUX); +} + +static int pcl816_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + const struct pcl816_board *board = comedi_board(dev); + struct pcl816_private *devpriv; + int ret; + unsigned int irq, dma; + unsigned long pages; + /* int i; */ + struct comedi_subdevice *s; + + ret = comedi_request_region(dev, it->options[0], board->io_range); + if (ret) + return ret; + + if (pcl816_check(dev->iobase)) { + printk(KERN_ERR ", I cann't detect board. FAIL!\n"); + return -EIO; + } + + devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL); + if (!devpriv) + return -ENOMEM; + dev->private = devpriv; + + /* grab our IRQ */ + irq = 0; + if (board->IRQbits != 0) { /* board support IRQ */ + irq = it->options[1]; + if (irq) { /* we want to use IRQ */ + if (((1 << irq) & board->IRQbits) == 0) { + printk + (", IRQ %u is out of allowed range, " + "DISABLING IT", irq); + irq = 0; /* Bad IRQ */ + } else { + if (request_irq(irq, interrupt_pcl816, 0, + dev->board_name, dev)) { + printk + (", unable to allocate IRQ %u, " + "DISABLING IT", irq); + irq = 0; /* Can't use IRQ */ + } else { + printk(KERN_INFO ", irq=%u", irq); + } + } + } + } + + dev->irq = irq; + if (irq) /* 1=we have allocated irq */ + devpriv->irq_free = 1; + else + devpriv->irq_free = 0; + + devpriv->irq_blocked = 0; /* number of subdevice which use IRQ */ + devpriv->int816_mode = 0; /* mode of irq */ + + /* grab our DMA */ + dma = 0; + devpriv->dma = dma; + if (!devpriv->irq_free) + goto no_dma; /* if we haven't IRQ, we can't use DMA */ + + if (board->DMAbits != 0) { /* board support DMA */ + dma = it->options[2]; + if (dma < 1) + goto no_dma; /* DMA disabled */ + + if (((1 << dma) & board->DMAbits) == 0) { + printk(", DMA is out of allowed range, FAIL!\n"); + return -EINVAL; /* Bad DMA */ + } + ret = request_dma(dma, dev->board_name); + if (ret) { + printk(KERN_ERR + ", unable to allocate DMA %u, FAIL!\n", dma); + return -EBUSY; /* DMA isn't free */ + } + + devpriv->dma = dma; + printk(KERN_INFO ", dma=%u", dma); + pages = 2; /* we need 16KB */ + devpriv->dmabuf[0] = __get_dma_pages(GFP_KERNEL, pages); + + if (!devpriv->dmabuf[0]) { + 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; + /* printk("%d %d %ld, ",devpriv->dmapages[0],devpriv->hwdmasize[0],PAGE_SIZE); */ + + devpriv->dmabuf[1] = __get_dma_pages(GFP_KERNEL, pages); + if (!devpriv->dmabuf[1]) { + printk(KERN_ERR + ", 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: + +/* if (board->n_aochan > 0) + subdevs[1] = COMEDI_SUBD_AO; + if (board->n_dichan > 0) + subdevs[2] = COMEDI_SUBD_DI; + if (board->n_dochan > 0) + subdevs[3] = COMEDI_SUBD_DO; +*/ + + ret = comedi_alloc_subdevices(dev, 1); + if (ret) + return ret; + + s = &dev->subdevices[0]; + if (board->n_aichan > 0) { + s->type = COMEDI_SUBD_AI; + devpriv->sub_ai = s; + dev->read_subdev = s; + s->subdev_flags = SDF_READABLE | SDF_CMD_READ; + s->n_chan = board->n_aichan; + s->subdev_flags |= SDF_DIFF; + /* printk (", %dchans DIFF DAC - %d", s->n_chan, i); */ + s->maxdata = board->ai_maxdata; + s->len_chanlist = board->ai_chanlist; + s->range_table = 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; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + +#if 0 +case COMEDI_SUBD_AO: + s->subdev_flags = SDF_WRITABLE | SDF_GROUND; + s->n_chan = board->n_aochan; + s->maxdata = board->ao_maxdata; + s->len_chanlist = board->ao_chanlist; + s->range_table = board->ao_range_type; + break; + +case COMEDI_SUBD_DI: + s->subdev_flags = SDF_READABLE; + s->n_chan = board->n_dichan; + s->maxdata = 1; + s->len_chanlist = board->n_dichan; + s->range_table = &range_digital; + break; + +case COMEDI_SUBD_DO: + s->subdev_flags = SDF_WRITABLE; + s->n_chan = board->n_dochan; + s->maxdata = 1; + s->len_chanlist = board->n_dochan; + s->range_table = &range_digital; + break; +#endif + + pcl816_reset(dev); + + printk("\n"); + + return 0; +} + +static void pcl816_detach(struct comedi_device *dev) +{ + struct pcl816_private *devpriv = dev->private; + + 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]); + } + comedi_legacy_detach(dev); +} + +static const struct pcl816_board 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}, +}; + +static struct comedi_driver pcl816_driver = { + .driver_name = "pcl816", + .module = THIS_MODULE, + .attach = pcl816_attach, + .detach = pcl816_detach, + .board_name = &boardtypes[0].name, + .num_names = ARRAY_SIZE(boardtypes), + .offset = sizeof(struct pcl816_board), +}; +module_comedi_driver(pcl816_driver); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); |
