<commit_before>/*
   comedi/drivers/pcl816.c

   Author:  <NAME> <<EMAIL>>
	    based on pcl818 by Michal Dobes <<EMAIL>> 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: <NAME> <<EMAIL>>
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 <linux/module.h>
#include "../comedidev.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 PCL816_TIMER_BASE			0x04

/* 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_DMA 2
#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;
	int ai_maxdata;
	int ao_maxdata;
	int ai_chanlist;
};

static const struct pcl816_board boardtypes[] = {
	{
		.name		= "pcl816",
		.ai_maxdata	= 0xffff,
		.ao_maxdata	= 0xffff,
		.ai_chanlist	= 1024,
	}, {
		.name		= "pcl814b",
		.ai_maxdata	= 0x3fff,
		.ao_maxdata	= 0x3fff,
		.ai_chanlist	= 1024,
	},
};

struct pcl816_private {
	unsigned int dma;	/*  used DMA, 0=don't use DMA */
	unsigned int dmapages;
	unsigned int hwdmasize;
	unsigned long dmabuf[2];	/*  pointers to begin of DMA buffers */
	unsigned int hwdmaptr[2];	/*  hardware address of DMA buffers */
	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 */
	int int816_mode;	/*  who now uses IRQ - 1=AI1 int, 2=AI1 dma, 3=AI3 int, 4AI3 dma */
	int ai_act_scan;	/*  how many scans we finished */
	unsigned int ai_act_chanlist[16];	/*  MUX setting for actual AI operations */
	unsigned int ai_poll_ptr;	/*  how many sampes transfer poll */
	unsigned int divisor1;
	unsigned int divisor2;
	unsigned int ai_cmd_running:1;
	unsigned int irq_was_now_closed:1;
};

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 void pcl816_start_pacer(struct comedi_device *dev, bool load_counters)
{
	struct pcl816_private *devpriv = dev->private;
	unsigned long timer_base = dev->iobase + PCL816_TIMER_BASE;

	i8254_set_mode(timer_base, 0, 0, I8254_MODE1 | I8254_BINARY);
	i8254_write(timer_base, 0, 0, 0x00ff);
	udelay(1);

	i8254_set_mode(timer_base, 0, 2, I8254_MODE2 | I8254_BINARY);
	i8254_set_mode(timer_base, 0, 1, I8254_MODE2 | I8254_BINARY);
	udelay(1);

	if (load_counters) {
		i8254_write(timer_base, 0, 2, devpriv->divisor2);
		i8254_write(timer_base, 0, 1, devpriv->divisor1);
	}
}

static void pcl816_ai_setup_dma(struct comedi_device *dev,
				struct comedi_subdevice *s)
{
	struct pcl816_private *devpriv = dev->private;
	struct comedi_cmd *cmd = &s->async->cmd;
	unsigned int dma_flags;
	unsigned int bytes;

	bytes = devpriv->hwdmasize;
	if (cmd->stop_src == TRIG_COUNT) {
		/*  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;

		/* on last dma transfer must be moved */
		devpriv->last_dma_run = bytes % devpriv->hwdmasize;
		devpriv->dma_runs_to_end--;
		if (devpriv->dma_runs_to_end >= 0)
			bytes = devpriv->hwdmasize;
	} 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);
}

static void pcl816_ai_setup_next_dma(struct comedi_device *dev,
				     struct comedi_subdevice *s)
{
	struct pcl816_private *devpriv = dev->private;
	struct comedi_cmd *cmd = &s->async->cmd;
	unsigned long dma_flags;

	disable_dma(devpriv->dma);
	if (devpriv->dma_runs_to_end > -1 || cmd->stop_src == TRIG_NONE) {
		/* 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();
		set_dma_addr(devpriv->dma,
			     devpriv->hwdmaptr[devpriv->next_dma_buf]);
		if (devpriv->dma_runs_to_end)
			set_dma_count(devpriv->dma, devpriv->hwdmasize);
		else
			set_dma_count(devpriv->dma, devpriv->last_dma_run);
		release_dma_lock(dma_flags);
		enable_dma(devpriv->dma);
	}

	devpriv->dma_runs_to_end--;
}

static unsigned int pcl816_ai_get_sample(struct comedi_device *dev,
					 struct comedi_subdevice *s)
{
	unsigned int val;

	val = inb(dev->iobase + PCL816_AD_HI) << 8;
	val |= inb(dev->iobase + PCL816_AD_LO);

	return val & s->maxdata;
}

static int pcl816_ai_eoc(struct comedi_device *dev,
			 struct comedi_subdevice *s,
			 struct comedi_insn *insn,
			 unsigned long context)
{
	unsigned int status;

	status = inb(dev->iobase + PCL816_STATUS);
	if ((status & PCL816_STATUS_DRDY_MASK) == 0)
		return 0;
	return -EBUSY;
}

static int pcl816_ai_insn_read(struct comedi_device *dev,
			       struct comedi_subdevice *s,
			       struct comedi_insn *insn, unsigned int *data)
{
	int ret;
	int 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 */

		ret = comedi_timeout(dev, s, insn, pcl816_ai_eoc, 0);
		if (ret) {
			/* clear INT (conversion end) flag */
			outb(0, dev->iobase + PCL816_CLRINT);
			return ret;
		}

		data[n] = pcl816_ai_get_sample(dev, s);

		/* clear INT (conversion end) flag */
		outb(0, dev->iobase + PCL816_CLRINT);
	}
	return n;
}

static bool pcl816_ai_next_chan(struct comedi_device *dev,
				struct comedi_subdevice *s)
{
	struct pcl816_private *devpriv = dev->private;
	struct comedi_cmd *cmd = &s->async->cmd;

	s->async->events |= COMEDI_CB_BLOCK;

	s->async->cur_chan++;
	if (s->async->cur_chan >= cmd->chanlist_len) {
		s->async->cur_chan = 0;
		devpriv->ai_act_scan++;
		s->async->events |= COMEDI_CB_EOS;
	}

	if (cmd->stop_src == TRIG_COUNT &&
	    devpriv->ai_act_scan >= cmd->stop_arg) {
		/* all data sampled */
		s->cancel(dev, s);
		s->async->events |= COMEDI_CB_EOA;
		comedi_event(dev, s);
		return false;
	}

	return true;
}

static void transfer_from_dma_buf(struct comedi_device *dev,
				  struct comedi_subdevice *s,
				  unsigned short *ptr,
				  unsigned int bufptr, unsigned int len)
{
	int i;

	for (i = 0; i < len; i++) {
		comedi_buf_put(s->async, ptr[bufptr++]);

		if (!pcl816_ai_next_chan(dev, s))
			return;
	}

	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->read_subdev;
	int len, bufptr, this_dma_buf;
	unsigned short *ptr;

	this_dma_buf = devpriv->next_dma_buf;

	pcl816_ai_setup_next_dma(dev, s);

	outb(0, dev->iobase + PCL816_CLRINT);	/* clear INT request */

	ptr = (unsigned short *)devpriv->dmabuf[this_dma_buf];

	len = (devpriv->hwdmasize >> 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;
}

static irqreturn_t interrupt_pcl816(int irq, void *d)
{
	struct comedi_device *dev = d;
	struct pcl816_private *devpriv = dev->private;

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

	outb(0, dev->iobase + PCL816_CLRINT);	/* clear INT request */
	if (!devpriv->ai_cmd_running || !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;
}

static int pcl816_ai_cmdtest(struct comedi_device *dev,
			     struct comedi_subdevice *s, struct comedi_cmd *cmd)
{
	struct pcl816_private *devpriv = dev->private;
	int err = 0;
	int tmp;

	/* 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, 10000);
	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(I8254_OSC_BASE_10MHZ,
					  &devpriv->divisor1,
					  &devpriv->divisor2,
					  &cmd->convert_arg, cmd->flags);
		if (cmd->convert_arg < 10000)
			cmd->convert_arg = 10000;
		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)
{
	struct pcl816_private *devpriv = dev->private;
	struct comedi_cmd *cmd = &s->async->cmd;
	unsigned int dmairq;
	unsigned int seglen;

	if (devpriv->ai_cmd_running)
		return -EBUSY;

	pcl816_start_pacer(dev, false);

	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_act_scan = 0;
	s->async->cur_chan = 0;
	devpriv->ai_cmd_running = 1;
	devpriv->ai_poll_ptr = 0;
	devpriv->irq_was_now_closed = 0;

	if (devpriv->dma)
		pcl816_ai_setup_dma(dev, s);

	pcl816_start_pacer(dev, true);
	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;
	}

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

static int pcl816_ai_cancel(struct comedi_device *dev,
			    struct comedi_subdevice *s)
{
	struct pcl816_private *devpriv = dev->private;

	if (devpriv->ai_cmd_running) {
		switch (devpriv->int816_mode) {
		case INT_TYPE_AI1_DMA:
		case INT_TYPE_AI3_DMA:
			disable_dma(devpriv->dma);
			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 */
			i8254_set_mode(dev->iobase + PCL816_TIMER_BASE, 0,
				       2, I8254_MODE0 | I8254_BINARY);
			i8254_set_mode(dev->iobase + PCL816_TIMER_BASE, 0,
				       1, I8254_MODE0 | I8254_BINARY);

			outb(0, dev->iobase + PCL816_AD_LO);
			pcl816_ai_get_sample(dev, s);

			/* clear INT request */
			outb(0, dev->iobase + PCL816_CLRINT);

			/* Stop A/D */
			outb(0, dev->iobase + PCL816_CONTROL);
			devpriv->ai_cmd_running = 0;
			devpriv->irq_was_now_closed = 1;
			devpriv->int816_mode = 0;
/* s->busy = 0; */
			break;
		}
	}
	return 0;
}

static void pcl816_reset(struct comedi_device *dev)
{
	unsigned long timer_base = dev->iobase + PCL816_TIMER_BASE;

/* 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);
<commit_msg>staging: comedi: pcl816: reduce indent level in pcl816_ai_cancel()<commit_after>/*
   comedi/drivers/pcl816.c

   Author:  <NAME> <<EMAIL>>
	    based on pcl818 by <NAME> <<EMAIL>> 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: <NAME> <<EMAIL>>
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 <linux/module.h>
#include "../comedidev.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 PCL816_TIMER_BASE			0x04

/* 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_DMA 2
#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;
	int ai_maxdata;
	int ao_maxdata;
	int ai_chanlist;
};

static const struct pcl816_board boardtypes[] = {
	{
		.name		= "pcl816",
		.ai_maxdata	= 0xffff,
		.ao_maxdata	= 0xffff,
		.ai_chanlist	= 1024,
	}, {
		.name		= "pcl814b",
		.ai_maxdata	= 0x3fff,
		.ao_maxdata	= 0x3fff,
		.ai_chanlist	= 1024,
	},
};

struct pcl816_private {
	unsigned int dma;	/*  used DMA, 0=don't use DMA */
	unsigned int dmapages;
	unsigned int hwdmasize;
	unsigned long dmabuf[2];	/*  pointers to begin of DMA buffers */
	unsigned int hwdmaptr[2];	/*  hardware address of DMA buffers */
	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 */
	int int816_mode;	/*  who now uses IRQ - 1=AI1 int, 2=AI1 dma, 3=AI3 int, 4AI3 dma */
	int ai_act_scan;	/*  how many scans we finished */
	unsigned int ai_act_chanlist[16];	/*  MUX setting for actual AI operations */
	unsigned int ai_poll_ptr;	/*  how many sampes transfer poll */
	unsigned int divisor1;
	unsigned int divisor2;
	unsigned int ai_cmd_running:1;
	unsigned int irq_was_now_closed:1;
};

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 void pcl816_start_pacer(struct comedi_device *dev, bool load_counters)
{
	struct pcl816_private *devpriv = dev->private;
	unsigned long timer_base = dev->iobase + PCL816_TIMER_BASE;

	i8254_set_mode(timer_base, 0, 0, I8254_MODE1 | I8254_BINARY);
	i8254_write(timer_base, 0, 0, 0x00ff);
	udelay(1);

	i8254_set_mode(timer_base, 0, 2, I8254_MODE2 | I8254_BINARY);
	i8254_set_mode(timer_base, 0, 1, I8254_MODE2 | I8254_BINARY);
	udelay(1);

	if (load_counters) {
		i8254_write(timer_base, 0, 2, devpriv->divisor2);
		i8254_write(timer_base, 0, 1, devpriv->divisor1);
	}
}

static void pcl816_ai_setup_dma(struct comedi_device *dev,
				struct comedi_subdevice *s)
{
	struct pcl816_private *devpriv = dev->private;
	struct comedi_cmd *cmd = &s->async->cmd;
	unsigned int dma_flags;
	unsigned int bytes;

	bytes = devpriv->hwdmasize;
	if (cmd->stop_src == TRIG_COUNT) {
		/*  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;

		/* on last dma transfer must be moved */
		devpriv->last_dma_run = bytes % devpriv->hwdmasize;
		devpriv->dma_runs_to_end--;
		if (devpriv->dma_runs_to_end >= 0)
			bytes = devpriv->hwdmasize;
	} 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);
}

static void pcl816_ai_setup_next_dma(struct comedi_device *dev,
				     struct comedi_subdevice *s)
{
	struct pcl816_private *devpriv = dev->private;
	struct comedi_cmd *cmd = &s->async->cmd;
	unsigned long dma_flags;

	disable_dma(devpriv->dma);
	if (devpriv->dma_runs_to_end > -1 || cmd->stop_src == TRIG_NONE) {
		/* 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();
		set_dma_addr(devpriv->dma,
			     devpriv->hwdmaptr[devpriv->next_dma_buf]);
		if (devpriv->dma_runs_to_end)
			set_dma_count(devpriv->dma, devpriv->hwdmasize);
		else
			set_dma_count(devpriv->dma, devpriv->last_dma_run);
		release_dma_lock(dma_flags);
		enable_dma(devpriv->dma);
	}

	devpriv->dma_runs_to_end--;
}

static unsigned int pcl816_ai_get_sample(struct comedi_device *dev,
					 struct comedi_subdevice *s)
{
	unsigned int val;

	val = inb(dev->iobase + PCL816_AD_HI) << 8;
	val |= inb(dev->iobase + PCL816_AD_LO);

	return val & s->maxdata;
}

static int pcl816_ai_eoc(struct comedi_device *dev,
			 struct comedi_subdevice *s,
			 struct comedi_insn *insn,
			 unsigned long context)
{
	unsigned int status;

	status = inb(dev->iobase + PCL816_STATUS);
	if ((status & PCL816_STATUS_DRDY_MASK) == 0)
		return 0;
	return -EBUSY;
}

static int pcl816_ai_insn_read(struct comedi_device *dev,
			       struct comedi_subdevice *s,
			       struct comedi_insn *insn, unsigned int *data)
{
	int ret;
	int 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 */

		ret = comedi_timeout(dev, s, insn, pcl816_ai_eoc, 0);
		if (ret) {
			/* clear INT (conversion end) flag */
			outb(0, dev->iobase + PCL816_CLRINT);
			return ret;
		}

		data[n] = pcl816_ai_get_sample(dev, s);

		/* clear INT (conversion end) flag */
		outb(0, dev->iobase + PCL816_CLRINT);
	}
	return n;
}

static bool pcl816_ai_next_chan(struct comedi_device *dev,
				struct comedi_subdevice *s)
{
	struct pcl816_private *devpriv = dev->private;
	struct comedi_cmd *cmd = &s->async->cmd;

	s->async->events |= COMEDI_CB_BLOCK;

	s->async->cur_chan++;
	if (s->async->cur_chan >= cmd->chanlist_len) {
		s->async->cur_chan = 0;
		devpriv->ai_act_scan++;
		s->async->events |= COMEDI_CB_EOS;
	}

	if (cmd->stop_src == TRIG_COUNT &&
	    devpriv->ai_act_scan >= cmd->stop_arg) {
		/* all data sampled */
		s->cancel(dev, s);
		s->async->events |= COMEDI_CB_EOA;
		comedi_event(dev, s);
		return false;
	}

	return true;
}

static void transfer_from_dma_buf(struct comedi_device *dev,
				  struct comedi_subdevice *s,
				  unsigned short *ptr,
				  unsigned int bufptr, unsigned int len)
{
	int i;

	for (i = 0; i < len; i++) {
		comedi_buf_put(s->async, ptr[bufptr++]);

		if (!pcl816_ai_next_chan(dev, s))
			return;
	}

	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->read_subdev;
	int len, bufptr, this_dma_buf;
	unsigned short *ptr;

	this_dma_buf = devpriv->next_dma_buf;

	pcl816_ai_setup_next_dma(dev, s);

	outb(0, dev->iobase + PCL816_CLRINT);	/* clear INT request */

	ptr = (unsigned short *)devpriv->dmabuf[this_dma_buf];

	len = (devpriv->hwdmasize >> 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;
}

static irqreturn_t interrupt_pcl816(int irq, void *d)
{
	struct comedi_device *dev = d;
	struct pcl816_private *devpriv = dev->private;

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

	outb(0, dev->iobase + PCL816_CLRINT);	/* clear INT request */
	if (!devpriv->ai_cmd_running || !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;
}

static int pcl816_ai_cmdtest(struct comedi_device *dev,
			     struct comedi_subdevice *s, struct comedi_cmd *cmd)
{
	struct pcl816_private *devpriv = dev->private;
	int err = 0;
	int tmp;

	/* 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, 10000);
	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(I8254_OSC_BASE_10MHZ,
					  &devpriv->divisor1,
					  &devpriv->divisor2,
					  &cmd->convert_arg, cmd->flags);
		if (cmd->convert_arg < 10000)
			cmd->convert_arg = 10000;
		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)
{
	struct pcl816_private *devpriv = dev->private;
	struct comedi_cmd *cmd = &s->async->cmd;
	unsigned int dmairq;
	unsigned int seglen;

	if (devpriv->ai_cmd_running)
		return -EBUSY;

	pcl816_start_pacer(dev, false);

	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_act_scan = 0;
	s->async->cur_chan = 0;
	devpriv->ai_cmd_running = 1;
	devpriv->ai_poll_ptr = 0;
	devpriv->irq_was_now_closed = 0;

	if (devpriv->dma)
		pcl816_ai_setup_dma(dev, s);

	pcl816_start_pacer(dev, true);
	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;
	}

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

static int pcl816_ai_cancel(struct comedi_device *dev,
			    struct comedi_subdevice *s)
{
	struct pcl816_private *devpriv = dev->private;

	if (!devpriv->ai_cmd_running)
		return 0;

	switch (devpriv->int816_mode) {
	case INT_TYPE_AI1_DMA:
	case INT_TYPE_AI3_DMA:
		disable_dma(devpriv->dma);
		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 */
		i8254_set_mode(dev->iobase + PCL816_TIMER_BASE, 0,
				2, I8254_MODE0 | I8254_BINARY);
		i8254_set_mode(dev->iobase + PCL816_TIMER_BASE, 0,
				1, I8254_MODE0 | I8254_BINARY);

		outb(0, dev->iobase + PCL816_AD_LO);
		pcl816_ai_get_sample(dev, s);

		/* clear INT request */
		outb(0, dev->iobase + PCL816_CLRINT);

		/* Stop A/D */
		outb(0, dev->iobase + PCL816_CONTROL);
		devpriv->ai_cmd_running = 0;
		devpriv->irq_was_now_closed = 1;
		devpriv->int816_mode = 0;
		break;
	}

	return 0;
}

static void pcl816_reset(struct comedi_device *dev)
{
	unsigned long timer_base = dev->iobase + PCL816_TIMER_BASE;

/* 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);

{'fraction_non_alphanumeric': 0.13302280758111146, 'fraction_numerical': 0.0447157083199486, 'mean_word_length': 3.984149855907781, 'pattern_counts': {'":': 0, '<': 52, '<?xml version=': 0, '>': 437, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '961631', 'n_tokens_mistral': 14167, 'n_tokens_neox': 12603, 'n_words': 2830}