/* driver to read ADC, Burr Brown ADS 7841 
   2/3/01 John August, released into public domain, do as you will
        with it !

   Hard wired to printer port starting at 0x278, major device number
   15, but this is arbitrary. Used with 2.2.17 kernel.

   Allocations to printer port pins :

   port 0x278
   
   bit 0, pin 2, Clock
   bit 1, pin 3, Chip Select
   bits 2-7 user available

   port 0x279 (input)

   bit 3, pin 15, Busy
   bit 4, pin 13, Data out
   bits 5-7 user available

   port 0x27a

   bits 0-4 user available
   
   Device nodes :

crw-rw-r--    1 root     root      15,   0 Feb 27 18:12 adc0
crw-rw-r--    1 root     root      15,   1 Feb 27 18:12 adc1
crw-rw-r--    1 root     root      15,   2 Feb 27 18:12 adc2
crw-rw-r--    1 root     root      15,   3 Feb 27 18:13 adc3
crw-rw-r--    1 root     root      15,   4 Feb 27 18:13 adc4
crw-rw-r--    1 root     root      15,  32 Mar  2 19:07 adcgt0
crw-rw-r--    1 root     root      15,  16 Mar  1 21:08 adct0
crw-rw-r--    1 root     root      15,  17 Mar  1 21:24 adct1
crw-rw-r--    1 root     root      15,  18 Mar  1 21:24 adct2
crw-rw-r--    1 root     root      15,  19 Mar  1 21:24 adct3
crw-rw-r--    1 root     root      15,  33 Mar  3 08:29 adcti

   Reads from devices adc0, adc1, adc2, adc3 - minor 0-3
   	devices without timing delay, each device number corresponds
   	to a single channel, ask and you get
   Reads from devices adct0, adct1, adct2, adct3 - minor 16-19
   	devices with a fixed delay between samples, set by ioctl,
   	driver delays till jiffies is up, and takes a sample from
   	a single channel
   Reads from device adcgt0 - minor 32
   	waits till jiffies is up, and delivers all four channels in
   	an eight byte group
   Reads from device adcti0 - minor 33 
   	experimental buffered interrupt driven system

   Ioctls:

   SET_OUTPUT_1

   Lets user set bits on port 1 (0x278)
   
   SET_DELAY 2

   Sets the delay for the interrupt driven system, number jiffies,
   1/100s of a second normally
   
   READ_ERROR 3

   Reads error bit in case cyclic buffer overruns. Not useful as with
   1/100 th second sample rate maximum you'd need a really slow system
   to have problems.

   READ_INPUT_1 

   Reads value at printer port 2 (0x279)
   
   READ_INPUT_2 
   
   Reads value at printer port 3 (0x27a)

   An example of use :

head -c 20 /dev/adcti >tmp ; hexdump -dv tmp
0000000   03650   02618   02234   02046   03650   02618   02233   02046
0000010   03650   02618

*/ 

#include <linux/module.h>
#include <linux/mm.h>  
#include <linux/ioport.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/fs.h>
#include <linux/time.h>
#include <linux/sched.h>

#define ADC_MAJOR 15
#define SET_OUTPUT_1 1
#define SET_DELAY 2
#define READ_ERROR 3
#define READ_INPUT_1 4
#define READ_INPUT_2 5

#define printer_port_1 0x278
#define printer_port_2 0x279
#define printer_port_3 0x27a

static short adc_read_port(int channel);
static void output_byte(int byte) ;
static short input_byte() ;
void int_read_adc(unsigned long ptr);
void dd(void);

static int port_1_out, error;
static unsigned long nextjif, delay, intcount;

static unsigned long base;

struct timer_list int_info;
static int stop_sample;

struct set_four { 
	short ch0;
	short ch1;
	short ch2;
	short ch3;
};

struct set_four rw_buffer[10], *read_ptr, *write_ptr;

static short adc_ioctl (struct inode *inode,
		     struct file *file,
		     unsigned int cmd,
		     unsigned long arg)
{
	if (MAJOR (inode->i_rdev) != ADC_MAJOR)
		return -EINVAL;
/*	printk ("ioctl : command %d input %d \n", cmd, arg);	*/
	switch (cmd) {
	case SET_OUTPUT_1:
		port_1_out=(int) arg;
		break;
	case SET_DELAY:
		delay=arg;
		printk ("delay is %ld \n", delay);
		break;
	case READ_ERROR:
		if ((verify_area(VERIFY_WRITE, (void *) arg,
	                    sizeof(unsigned int))) == 0) {
	         	put_user(error, (unsigned int *) arg);
	        }
		break;
	case READ_INPUT_1:
		if ((verify_area(VERIFY_WRITE, (void *) arg,
	                    sizeof(int))) == 0) {
	         	put_user((int) inb (printer_port_2), (int *) arg);
	        }

		break;
	case READ_INPUT_2:
		outb ( printer_port_3, (unsigned int) 255 );		
		if ((verify_area(VERIFY_WRITE, (void *) arg,
	                    sizeof(int))) == 0) {
	         	put_user((int) inb (printer_port_3), (int *) arg);
	        }

		break;
	default:
		return -EINVAL;
	}
	return 0;
}

static int adc_open (struct inode *inode, struct file *file)
{
	unsigned int minor=MINOR(file->f_dentry->d_inode->i_rdev);
	/* if it is a timer request */
	nextjif = jiffies+delay;
	error=0; 	
	/* if experimental device, set up timing process */
	if (minor == 33) {
		base=jiffies;
		intcount=(unsigned long) 1;
		stop_sample=0;
		write_ptr=rw_buffer;
		read_ptr=rw_buffer;
		init_timer(&int_info);
		int_info.function = int_read_adc;
		int_info.data = 0;
		int_info.expires = base + intcount * delay;
		add_timer(&int_info);
	}	
	
	return 0;
}

static int adc_release (struct inode *inode, struct file *file)
{
	unsigned int minor=MINOR(file->f_dentry->d_inode->i_rdev);
	if (minor == 33) stop_sample=1;
	return 0;
}

static ssize_t adc_read (struct file *file, char *buf, size_t count, loff_t *ppos)
{
	unsigned int minor=MINOR(file->f_dentry->d_inode->i_rdev);
	unsigned int i, rval, icount, channel;
	icount=count;
	channel=minor;
	if (channel >= 16) channel = channel -16; 
	if (channel >= 16) channel = channel -16;
        if (minor == 33) { /* interrupt driven beast */
		while (read_ptr==write_ptr) {
			/* delay while they're equal */
	                current->state = TASK_INTERRUPTIBLE;
        	        schedule_timeout(delay);
        	}
        	if ((verify_area(VERIFY_WRITE, (void *) buf, 
        		sizeof(struct set_four))) == 0) {
 			copy_to_user (buf, &read_ptr->ch0, 2);
	 		buf=buf+2;
 			copy_to_user (buf, &read_ptr->ch1, 2);
 			buf=buf+2;
	 		copy_to_user (buf, &read_ptr->ch2, 2);
 			buf=buf+2;
 			copy_to_user (buf, &read_ptr->ch3, 2);
	 		++read_ptr;
 			if (read_ptr==rw_buffer+10) read_ptr=rw_buffer;
 			return 8;
 		}
 		return 0;
 	}	
 		
        
/* "normal" single channel samples, untimed and timed */

	for (i=0; i<icount ; i = i+2, buf = buf + 2 ) {
			rval = 0;
			rval = adc_read_port( channel );
			copy_to_user (buf, &rval, 2);
	}
		
	return (ssize_t) i;
}

void int_read_adc(unsigned long ptr) {

int rval;
/* now, read value */
write_ptr->ch0 = adc_read_port( 0 );
write_ptr->ch1 = adc_read_port( 1 );
write_ptr->ch2 = adc_read_port( 2 );
write_ptr->ch3 = adc_read_port( 3 );
++write_ptr;
if (write_ptr==rw_buffer + 10) write_ptr=rw_buffer;
if (write_ptr==read_ptr) {
/*	printk ("interrupt timer error !\n");*/
	error=1;
}	
intcount++;
init_timer(&int_info);
int_info.function = int_read_adc;
int_info.data = 0;
int_info.expires = base + intcount * delay;
if (stop_sample == 0 ) add_timer(&int_info);
}


static short adc_read_port(int channel) {

int code;
short rval;
static int codes[3];

codes[0]=0x9f;
codes[1]=0xdf;
codes[2]=0xaf;
codes[3]=0xef;

code=codes[channel];

/* printk ("channel %d code %d \n", channel, code);*/
/* toggle cs bit, to reset adc */

port_1_out=port_1_out | 2;
outb( port_1_out, printer_port_1 );
port_1_out=port_1_out & (255-2);
outb( port_1_out, printer_port_1 );
rval=0;
output_byte(code) ;
rval = input_byte();
/* printk ("input byte : %d  \n", rval);*/
return(rval);
}

static short input_byte() {
int index, outbyte, read_value;
outbyte=read_value=0;
read_value=0;
for (index=11; index >= -1 ; index--) {

	/* clock set high */
	port_1_out=port_1_out | 1; 
	outb( port_1_out, printer_port_1 );

	/* clock set low */
        port_1_out=port_1_out & (255-1);
        outb( port_1_out, printer_port_1 );

	/* now, read that bit */
	if ((read_value & 32) == 32) outbyte=outbyte + (1 << (index+1));
	read_value=(int) inb (printer_port_2);
/*	printk ("index %d read_value %d  outb %d \n", index, read_value, outbyte );*/
	      
}
return outbyte;
}

static void output_byte(int byte) {
int index;

for (index=7;index >= 0; index--) {

/* set clock low, and set data bit apptly */

	if ( ( byte & (1 << index) ) == (1 << index) ) 
	       { port_1_out=(port_1_out & 254) | 4;}
	  else { port_1_out=port_1_out & (255 - 1 - 4 - 2);} 

/* printk ("byte %d index %d outb( printer_port_1, index, port_1_out );); */
	outb( port_1_out, printer_port_1 );
/* set clock high, to put out data */
        port_1_out=port_1_out | 1;
        outb( port_1_out, printer_port_1 );
}
/* set clock & data low on exit */

port_1_out=port_1_out & (255 - 1 - 4 );
outb( port_1_out, printer_port_1 );

}

void dd(void) {
int j;
j=jiffies+2;
while (jiffies<j) schedule();
}

static struct file_operations adc_fops =
{
	NULL,			/* adc_lseek */
	adc_read,		/* adc_read */
	NULL,			/* adc_write*/
	NULL,			/* adc_readaddr*/
	NULL,			/* adc_select */
	adc_ioctl,		/* adc_ioctl*/
	NULL,			/* adc_mmap */
	adc_open,		/* adc_open*/
	NULL,			/* flush */
	adc_release,		/* adc_release*/
	NULL,			/* adc_fsync */
	NULL			/* fasync */
};

#define adc_init init_module

void cleanup_module (void)
{
	if (unregister_chrdev (ADC_MAJOR, "adc input"))
		printk ("adc: cleanup_module failed\n");
	release_region(printer_port_1, 0x2);
}

int adc_init(void)
{
release_region(printer_port_1, 0x4);
	if (check_region(printer_port_1, 0x2)) {
		printk("adc_init: input port already in use\n");
		return -EBUSY;
	}

	if (register_chrdev (ADC_MAJOR, "adc input", &adc_fops)) {
		printk ("Unable to get major=%d for adc\n",
					ADC_MAJOR);
	}
	request_region(printer_port_1, 0x2, "adc input");
	delay =(unsigned long) 5;
	return 0;
}

