/*
   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/mc146818rtc.h>
#include <linux/gfp.h>
#include <linux/delay.h>
#include <asm/dma.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
#ifdef unused
#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
#endif

#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 */
};

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},
};

#define n_boardtypes (sizeof(boardtypes)/sizeof(struct pcl816_board))
#define devpriv ((struct pcl816_private *)dev->private)
#define this_board ((const struct pcl816_board *)dev->board_ptr)

static int pcl816_attach(struct comedi_device *dev,
			 struct comedi_devconfig *it);
static int pcl816_detach(struct comedi_device *dev);

#ifdef unused
static int RTC_lock = 0;	/* RTC lock */
static int RTC_timer_lock = 0;	/* RTC int lock */
#endif

static struct comedi_driver driver_pcl816 = {
	.driver_name = "pcl816",
	.module = THIS_MODULE,
	.attach = pcl816_attach,
	.detach = pcl816_detach,
	.board_name = &boardtypes[0].name,
	.num_names = n_boardtypes,
	.offset = sizeof(struct pcl816_board),
};

static int __init driver_pcl816_init_module(void)
{
	return comedi_driver_register(&driver_pcl816);
}

static void __exit driver_pcl816_cleanup_module(void)
{
	comedi_driver_unregister(&driver_pcl816);
}

module_init(driver_pcl816_init_module);
module_exit(driver_pcl816_cleanup_module);

struct pcl816_private {

	unsigned int dma;	/*  used DMA, 0=don't use DMA */
	int dma_rtc;		/*  1=RTC used with DMA, 0=no RTC alloc */
#ifdef unused
	unsigned long rtc_iobase;	/*  RTC port region */
	unsigned int rtc_iosize;
	unsigned int rtc_irq;
#endif
	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 */
#ifdef unused
	int rtc_irq_blocked;	/*  1=we now do AI with DMA&RTC */
#endif
	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 */
#ifdef unused
	struct timer_list rtc_irq_timer;	/*  timer for RTC sanity check */
	unsigned long rtc_freq;	/*  RTC int freq */
#endif
};

/*
==============================================================================
*/
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);
#ifdef unused
static int set_rtc_irq_bit(unsigned char bit);
#endif

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 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)
{
	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 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;
	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)
{
	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: 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++;

	tmp = cmd->convert_src;
	cmd->convert_src &= TRIG_EXT | 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
	 */

	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_arg < this_board->ai_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->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_arg < this_board->ai_ns_min)
			cmd->convert_arg = this_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)
{
	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 < 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);

		/*  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)
{
	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)
{
/* DEBUG(printk("pcl816_ai_cancel()\n");) */

	if (devpriv->irq_blocked > 0) {
		switch (devpriv->int816_mode) {
#ifdef unused
		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);
#endif
		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 builded 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)
{
	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);
}

#ifdef unused
/*
==============================================================================
  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;
}
#endif

/*
==============================================================================
  Free any resources that we have claimed
*/
static void free_resources(struct comedi_device *dev)
{
	/* 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]);
#ifdef unused
		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);
		}
#endif
	}

	if (dev->irq)
		free_irq(dev->irq, dev);
	if (dev->iobase)
		release_region(dev->iobase, this_board->io_range);
	/* printk("free_resource() end\n"); */
}

/*
==============================================================================

   Initialization

*/
static int pcl816_attach(struct comedi_device *dev, struct comedi_devconfig *it)
{
	int ret;
	unsigned long iobase;
	unsigned int irq, dma;
	unsigned long pages;
	/* int i; */
	struct comedi_subdevice *s;

	/* claim our I/O space */
	iobase = it->options[0];
	printk("comedi%d: pcl816:  board=%s, ioport=0x%03lx", dev->minor,
	       this_board->name, iobase);

	if (!request_region(iobase, this_board->io_range, "pcl816")) {
		printk("I/O port conflict\n");
		return -EIO;
	}

	dev->iobase = iobase;

	if (pcl816_check(iobase)) {
		printk(KERN_ERR ", I cann't detect board. FAIL!\n");
		return -EIO;
	}

	ret = alloc_private(dev, sizeof(struct pcl816_private));
	if (ret < 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) {	/* we want to use IRQ */
			if (((1 << irq) & this_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, "pcl816", 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 */

#ifdef unused
	/* grab RTC for DMA operations */
	devpriv->dma_rtc = 0;
	if (it->options[2] > 0) {	/*  we want to use DMA */
		if (RTC_lock == 0) {
			if (!request_region(RTC_PORT(0), RTC_IO_EXTENT,
					    "pcl816 (RTC)"))
				goto no_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, 0,
				 "pcl816 DMA (RTC)", dev)) {
			devpriv->dma_rtc = 1;
			devpriv->rtc_irq = RTC_IRQ;
			printk(", dma_irq=%u", 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:
#endif
	/* 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) {
			printk(", DMA is out of allowed range, FAIL!\n");
			return -EINVAL;	/* Bad DMA */
		}
		ret = request_dma(dma, "pcl816");
		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); */

		if (devpriv->dma_rtc == 0) {	/*  we must do duble buff :-( */
			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 (this_board->n_aochan > 0)
    subdevs[1] = COMEDI_SUBD_AO;
  if (this_board->n_dichan > 0)
    subdevs[2] = COMEDI_SUBD_DI;
  if (this_board->n_dochan > 0)
    subdevs[3] = COMEDI_SUBD_DO;
*/

	ret = alloc_subdevices(dev, 1);
	if (ret < 0)
		return ret;

	s = dev->subdevices + 0;
	if (this_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 = this_board->n_aichan;
		s->subdev_flags |= SDF_DIFF;
		/* 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;
	} else {
		s->type = COMEDI_SUBD_UNUSED;
	}

#if 0
case COMEDI_SUBD_AO:
	s->subdev_flags = SDF_WRITABLE | SDF_GROUND;
	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;
	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_WRITABLE;
	s->n_chan = this_board->n_dochan;
	s->maxdata = 1;
	s->len_chanlist = this_board->n_dochan;
	s->range_table = &range_digital;
	break;
#endif

	pcl816_reset(dev);

	printk("\n");

	return 0;
}

/*
==============================================================================
  Removes device
 */
static int pcl816_detach(struct comedi_device *dev)
{
	DEBUG(printk(KERN_INFO "comedi%d: pcl816: remove\n", dev->minor);)
	    free_resources(dev);
#ifdef unused
	if (devpriv->dma_rtc)
		RTC_lock--;
#endif
	return 0;
}

MODULE_AUTHOR("Comedi http://www.comedi.org");
MODULE_DESCRIPTION("Comedi low-level driver");
MODULE_LICENSE("GPL");
