/********************************************************************
 * FILE: 	AIO16B	
 * 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 AIO16B.o
 ********************************************************************/

#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>

/* a second board should be supported by changing the AIO16 ADDRESSES */
#define AIO16_BASEADDRESSB 	0x310 
#define startADconvertB  	AIO16_BASEADDRESSB
#define ADloByteB 		AIO16_BASEADDRESSB
#define ADhiByteB		AIO16_BASEADDRESSB + 1
#define startStopChannelB	AIO16_BASEADDRESSB + 2 
#define DAC0_lsbB		AIO16_BASEADDRESSB + 4
#define DAC0_msbB		AIO16_BASEADDRESSB + 5
#define DAC1_lsbB		AIO16_BASEADDRESSB + 6
#define DAC1_msbB		AIO16_BASEADDRESSB + 7
#define cardStatusB		AIO16_BASEADDRESSB + 8
#define cardControlB		AIO16_BASEADDRESSB + 9



int minor = 0;
int major = 126;



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

struct ADCB {
	unsigned char byte[2];
} current_adcB[16];

struct DACB {
	unsigned char byte[2];
} current_dacB;

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


static ssize_t AIO16B_read(struct file *filp, char *buf,
			   size_t channel, loff_t *offp)
{	
	unsigned char tmpbyte = 0;
	int timer = 1000;
	
	tmpbyte = (unsigned char) channel | (unsigned char) (channel<<4);	
	outb_p(tmpbyte, startStopChannelB);		
	wmb();
	
	// set trigger to start conversion 	
	outb_p(0xff, startADconvertB);
	wmb();	
	
	// wait until conversion has finished 
	while (((inb_p(cardStatusB)&0x80) != 0) && timer > 0)
		timer--;
	
	if (timer == 0) 
		return -1;
	
	rmb();	
	// read the converted data into the two bytes 	
	current_adcB[channel].byte[0] = inb_p(ADloByteB);
	rmb();
	current_adcB[channel].byte[1] = inb_p(ADhiByteB);
	rmb();
	//printk("loByte: %X\n", current_adcB[channel].byte[0]);
	//printk("hiByte: %X\n", current_adcB[channel].byte[1]);
	
	put_user(current_adcB[channel].byte[0], buf++);
	put_user(current_adcB[channel].byte[1], buf++);
		
	return 2;
}


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


ssize_t AIO16B_write(struct file *filp, char *buf,
			size_t volt, loff_t *offp)
{	
	/* we are cheating here, we will just use
	 * the count field rather than converting the
	 * buffer from char* back to int
	 */
	char switch_card = buf[0];

	if (switch_card == '0') { 
		outb( (int) volt << 4, DAC0_lsbB);
       		outb( (int) volt >> 4, DAC0_msbB);
	} else {
		outb( (int) volt << 4, DAC1_lsbB);
       		outb( (int) volt >> 4, DAC1_msbB);
	}	
	
	return 1;
}

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

static int AIO16B_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 AIO16B_release(struct inode *inode, struct file *filp)
{
	MOD_DEC_USE_COUNT;
	//printk("AIO16 device driver closed\n");
	return 0;

}


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

struct file_operations AIO16B_fops = {
	NULL,		
	AIO16B_read,		
	AIO16B_write,	// don't need write for our case	
	NULL,			
	NULL,	
	NULL,
	NULL,
	AIO16B_open,
	NULL,
	AIO16B_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, "AIO16B", &AIO16B_fops)) < 0 )
        	//printk("unable to register device\n");
	//printk("registered device: %d successfully\n", err);

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

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

  	return 0;
}


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

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


