/********************************************************************
 * FILE: 	AIO16	
 * Author: 	Kenny Huang
 * Description: A simple Linux device driver for ICS PCAIO16 
 * 		NOTE: this is a specific driver enough for our
 * 		purposes, I didn't do error checking for buffer overflow
 * 		since our calls are always the same.
 * 		It shouldn't be hard to extend it though.	
 * to compile:  > cd /usr/bin
 * 		> ls -l cc
 * 		// if cc is not pointing to kgcc
 * 			> rm cc
 * 			> ln -s kgcc cc
 * 		> cd /home/ventana/source/c++/analog_interface/
 * 		> make	
 * to install:	> insmod -f AIO16.o
 *
 * to put back:
 *		> rm cc
 *		> ln -s g++ cc
 ********************************************************************/

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/ioport.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/system.h>
#include <linux/delay.h>

#define AIO16_BASEADDRESS 	0x300 
#define startADconvert  	0x300
#define ADloByte 		0x300 
#define ADhiByte		0x301
#define startStopChannel	0x302
#define DIG_IO			0x303
#define DAC0_lsb		0x304
#define DAC0_msb		0x305
#define DAC1_lsb		0x306
#define DAC1_msb		0x307
#define cardStatus		0x308
#define cardControl		0x309



int minor = 0;
int major = 125;


/************************ temporary buffer *******************************/
/* for reading: A to D conversion */

struct ADC {
	unsigned char byte[2];
	
} current_adc[16];

struct DAC {
	unsigned char byte[2];
} current_dac;

/************************ AIO16_read *************************************/


static ssize_t AIO16_read(struct file *filp, char *buf,
			   size_t channel, loff_t *offp)
{	
	unsigned char tmpbyte = 0;
	int timer = 100;
	
	tmpbyte = (unsigned char) channel | (unsigned char) (channel<<4);	
	outb_p(tmpbyte, startStopChannel);		
	wmb();
	
	// set trigger to start conversion 	
	outb_p(0xff, startADconvert);
	wmb();	
	
	// wait until conversion has finished
	//inp_b is the same as inp except a delay is added after the call
	while (((inb_p(cardStatus)&0x80) != 0) && timer > 0)
		timer--;
	
	//if (timer == 0)
	//	return -1;
	
	rmb();	
	// read the converted data into the two bytes 	
	current_adc[channel].byte[0] = inb_p(ADloByte);
	rmb();
	current_adc[channel].byte[1] = inb_p(ADhiByte);
	rmb();
	//printk("loByte: %X\n", current_adc[channel].byte[0]);
	//printk("hiByte: %X\n", current_adc[channel].byte[1]);
	
	put_user(current_adc[channel].byte[0], buf++);
	put_user(current_adc[channel].byte[1], buf++);
		

	
	return 2;
}


/************************ AIO16_write *************************************/


static int AIO16_write(struct file *filp, char *buf,
			size_t volt, loff_t *offp)
{

	char switch_card = buf[0]; 
	
	 //we are cheating here, we will just use
	 //the count field rather than converting the
	 //buffer from char* back to int
	
	if (volt > 4095)
		volt = 4095;
	if (volt < 0)
		volt = 0;
                         			//0 is dac 1 on the first board
	if (switch_card == '0') {
		outb( (int) volt << 4, DAC0_lsb);
       		outb( (int) volt >> 4, DAC0_msb);
	} else if (switch_card == '1'){           //1 is dac 2 on the first board
		outb( (int) volt << 4, DAC1_lsb);
       		outb( (int) volt >> 4, DAC1_msb);
						  //2 and 3 are for the second board in the other module
	} else if (switch_card == '4'){           //4 is the digital output for detritus interlock
		outb( ( (int) volt & 0x0F), DIG_IO);  //this is BDC but we just use the first 4 bits
	}	
	
	return 1;
}

/************************ AIO16_open *************************************/

static int AIO16_open(struct inode *inode, struct file *filp)
{
	//printk("opening AIO16 device driver...\n");
	
	/* increment usage count */
	MOD_INC_USE_COUNT;

	if (filp->f_mode & FMODE_READ) {
  		//printk("io port, ready for reading...");
	}

	major = MAJOR(inode->i_rdev);
	minor = MINOR(inode->i_rdev);
	//printk("major: %d   minor: %d\n", major, minor);
	
	return 0; 
}


/************************ AIO16_release *************************************/

static int AIO16_release(struct inode *inode, struct file *filp)
{
	MOD_DEC_USE_COUNT;
	//printk("AIO16 device driver closed\n");
	return 0;

}


/************************* file operations ********************************/

struct file_operations AIO16_fops = {
	NULL,		
	AIO16_read,		
	AIO16_write,	
	NULL,			
	NULL,	
	NULL,
	NULL,
	AIO16_open,
	NULL,
	AIO16_release
};


/**************************** init_module *************************************/

int init_module(void)
{
	
	int err;
	/* 
         * register the device with the kernel 
	 * NOTE: 60-63, 240-254 are experimental range 
	 * 	 our device is registered with 125 and 0
	 */
	if ((err = register_chrdev(major, "AIO16", &AIO16_fops)) < 0 )
        	//printk("unable to register device\n");
	//printk("registered device: %d successfully\n", err);

	if (check_region(AIO16_BASEADDRESS, 16) != 0) {
		//printk("bad region, unregistering device now...\n");
		unregister_chrdev(major, "AIO16");
		return -ENODEV;
	} 
	
        request_region(AIO16_BASEADDRESS, 16, "AIO16");
	//printk("IO mapped region requested successfully.\n"); 

	/* initialize the board */
	/* no interrupt, no DMA, software start A/D */
	outb(0x00, cardControl);

  	return 0;
}


/**************************** ges_cleanup *************************************/

void cleanup_module(void)
{
	release_region( AIO16_BASEADDRESS, 16);
	unregister_chrdev(major, "AIO16");
	MOD_DEC_USE_COUNT;
   	//printk("goodbye\n");
}


