/*
 * sampler.c:	A Linux kernel driver for the EA pocket sampler.
 *
 *	By:	Alan Yates <alany@ay.com.au>
 */

#ifndef LINUX_VERSION_CODE
#	include <linux/version.h>
#endif
#ifndef VERSION_CODE
#	define VERSION_CODE(vers,rel,seq) ( ((vers)<<16) | ((rel)<<8) | (seq) )
#endif
#if VERSION_CODE(2,1,18) < LINUX_VERSION_CODE
#	define __NEW_LINUX__
#endif

#define MODULE
#define __KERNEL__
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/ioport.h>
#include <asm/io.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/malloc.h>
#include <linux/delay.h>
#include <linux/tqueue.h>
#ifdef __NEW_LINUX__
#	include <asm/uaccess.h>
#       include <linux/poll.h>
#else
#	include <asm/segment.h>
static inline unsigned long
copy_to_user(void *to, const void *from, unsigned long n) {
	memcpy_tofs(to,from,n);
	return 0;
}

static inline unsigned long
copy_from_user(void *to, const void *from, unsigned long n)
{
	memcpy_fromfs(to,from,n);
	return 0;
}
#endif

#define min(x,y) ((x)<(y)?(x):(y))	/* why isn't this standard */
#define digit(c) (((c)>='0')&&((c)<='9'))

/* globals */
int			eapsampler_port = 0x378;
int			eapsampler_major = 0;
char			eapsampler_name[] = "eapsampler";
struct wait_queue	*eapsampler_wq = 0;
struct timer_list	eapsampler_timer;
int			eapsampler_period = 0;
char			eapsampler_buf[PAGE_SIZE];
unsigned int		eapsampler_wp = 0;

struct eapsampler_file {
	unsigned int rp;
	int oneshot;
};

#ifdef __NEW_LINUX__
MODULE_AUTHOR("Alan Yates <alany@ay.com.au>");
MODULE_DESCRIPTION("EA Pocket Sampler Driver v2.0");
MODULE_SUPPORTED_DEVICE("EA Pocket Sampler");
MODULE_PARM(eapsampler_port, "i");
MODULE_PARM_DESC(eapsampler_port, "IRctrl port base address");
MODULE_PARM(eapsampler_major, "i");
MODULE_PARM_DESC(eapsampler_major, "sampler char device major");
#endif

/* turn sampler power on */
static int
eas_poweron(void) {
	outb(0xff, eapsampler_port);
	udelay(1000);
	return 0;
}

/* turn sampler power off */
static int
eas_poweroff(void) {
	outb(0x00, eapsampler_port);
	udelay(1000);
	return 0;
}

/* read sampler scale switch setting */
static int
eas_scale(void) {
        unsigned char nb = 0;

        /* switch off adc, enable high nible */
        outb(0x0f, eapsampler_port+2);
        udelay(1000);

        /* get and mask switch pos bit */
        nb = inb(eapsampler_port+1) & 0x80;

        if(nb) return 20;
        return 2;
}

/* take a sample */
static unsigned char
eas_sample(void) {
	int loops = 0;
	unsigned char data = 0;

	/* toggle ~WR to start conversion */
	outb(0x03, eapsampler_port+2);
	udelay(1000);
	outb(0x02, eapsampler_port+2);

	/* wait until EOC */
	while(inb(eapsampler_port+1) & 0x08) {
		if(loops++ > 10000) {
			printk(KERN_ERR "eapsampler: warning: waited 10000 loops for EOC\n");
			return 0;
		}
	}

	/* read data from ADC */
	data = ((inb(eapsampler_port+1) & 0xf0) >> 4) ^ 0x08;
	outb(0x03, eapsampler_port+2);
	udelay(1000);
	data |= (inb(eapsampler_port+1) & 0xf0) ^ 0x80; 

	return data;
}

static void
eapsampler_takesample(void) {
	int i = 0, bytes = 0;
	unsigned long flags;
	struct timeval tv;
	char buf[40];

	if(MOD_IN_USE) {
		save_flags(flags);
		cli();

		do_gettimeofday(&tv);
		bytes = sprintf(buf, "%ld %ld %d %d\n", tv.tv_sec, tv.tv_usec, eas_scale(), eas_sample());
		for(i = 0; i < bytes; i++)
			eapsampler_buf[(eapsampler_wp + i) % PAGE_SIZE] = buf[i];
		eapsampler_wp += bytes;
		eapsampler_wp %= PAGE_SIZE;

		restore_flags(flags);
	}
	wake_up_interruptible(&eapsampler_wq);

	if(eapsampler_period) {
		init_timer(&eapsampler_timer);
		eapsampler_timer.function = (void *) eapsampler_takesample;
		eapsampler_timer.data = 0;
		eapsampler_timer.expires = jiffies + eapsampler_period;
		add_timer(&eapsampler_timer);
	}
}


/* file node open */
static int
eapsampler_open(struct inode *inode, struct file *flip) {
	MOD_INC_USE_COUNT;

        flip->private_data = kmalloc(sizeof(struct eapsampler_file), GFP_KERNEL);
        if(!flip->private_data) return -ENOMEM;
        ((struct eapsampler_file *)flip->private_data)->rp = eapsampler_wp;
        ((struct eapsampler_file *)flip->private_data)->oneshot = 0;

	return 0;
}

/* file node release */
#ifdef __NEW_LINUX__
static int
#else
static void
#endif
eapsampler_release(struct inode *inode, struct file *flip) {
	MOD_DEC_USE_COUNT;
	kfree(flip->private_data);
#ifdef __NEW_LINUX__
	return 0;
#endif
}

/* file node read */
#ifdef __NEW_LINUX__
static size_t
eapsampler_read(struct file *flip, char *buf, size_t count, loff_t *off) {
#else
static int
eapsampler_read(struct inode *inode, struct file *flip, char *buf, int count) {
#endif
        int bytes = 0, i = 0;
	char *kbuf = (char *) get_free_page(GFP_KERNEL);
	struct eapsampler_file *sf = (struct eapsampler_file *) flip->private_data;
	
	if(!kbuf)
		return -ENOMEM;

	if(sf->oneshot)
		return 0;

	if(!eapsampler_period) {
		eapsampler_takesample();
		sf->oneshot = 1;
	}

	while(-1) {
		(eapsampler_wp >= sf->rp) ? 
			(bytes = eapsampler_wp - sf->rp)
			:
			(bytes = PAGE_SIZE - sf->rp);
		bytes = min(bytes, count);
		if(bytes) break;
		if(flip->f_flags & O_NONBLOCK)
			return -EAGAIN;
		interruptible_sleep_on(&eapsampler_wq);             
		if(signal_pending(current))
			return -ERESTARTSYS;
	}

	for(i = 0; i < bytes; i++)
		kbuf[i] = eapsampler_buf[(sf->rp + i) % PAGE_SIZE];
	sf->rp += bytes;
	sf->rp %= PAGE_SIZE;

	if(copy_to_user(buf, kbuf, bytes))
		return -EFAULT;

	free_page((unsigned long) kbuf);
	return bytes;
}

/* file node write */
#ifdef __NEW_LINUX__
static size_t
eapsampler_write(struct file *flip, char *buf, size_t count, loff_t *off) {
#else
static int
eapsampler_write(struct inode *inode, struct file *flip, char *buf, int count) {
#endif
	int i;
	char *kbuf = kmalloc((unsigned int) count, GFP_KERNEL), *cp = 0;

	if(!kbuf)
		return -ENOMEM;

	if(copy_from_user(kbuf, buf, count) > 0) {
		kfree(kbuf);
		return -EFAULT;
	}

	cp = kbuf;
	for(i = 0; i < count; i++)
		if(cp[i] == '\n') cp[i] = 0;
	i = eapsampler_period;
	eapsampler_period = simple_strtoul(cp, &cp, 0);

	printk(KERN_INFO "eapsampler: sampler period set to %d ms\n", eapsampler_period * 10);

	if(eapsampler_period && !i)
//	if(eapsampler_period)		/* subtle enter US with cli(); bug! */
		eapsampler_takesample();

	kfree(kbuf);

	return count;
}

#ifdef __NEW_LINUX__
/* filenode poll */
static unsigned int
eapsampler_select(struct file *flip, poll_table *wait) {
        struct eapsampler_file *sf = (struct eapsampler_file *) flip->private_data;
        unsigned int mask = 0;

        poll_wait(flip, &eapsampler_wq, wait);
        if(sf->rp != eapsampler_wp) mask |= POLLIN | POLLRDNORM;
        
        return mask;
}
#else
/* filenode select */
static int
eapsampler_select(struct inode *inode, struct file *flip, int mode, select_table *table) {
        struct eapsampler_file *sf = (struct eapsampler_file *) flip->private_data;

        if(mode == SEL_IN) {
                if(sf->rp != eapsampler_wp) return 1;
                select_wait(&eapsampler_wq, table);
                return 0;
        }

        /* no write blocking or exceptions */
        return 0;
}
#endif

/* file node ops */
static struct file_operations eapsampler_fops = {
	0,				/* lseek        */
	(void *)eapsampler_read,	/* read         */
	(void *)eapsampler_write,	/* write        */
	0,				/* readdir      */
	(void *)eapsampler_select,	/* select/poll  */
	0,				/* ioctl        */
	0,				/* mmap         */
	(void *)eapsampler_open,	/* open         */
#ifdef __NEW_LINUX__
	0,				/* flush	*/
#endif
	(void *)eapsampler_release,	/* release      */
	0,				/* fsync        */
	0,				/* fasync       */
};

int
init_module(void) {
	int err;

	printk(KERN_INFO "eapsampler: v2.0, Alan Yates <alany@ay.com.au>\n");

#ifndef __NEW_LINUX__
	register_symtab(0);
#endif

	if((err = check_region(eapsampler_port, 3)) < 0) {
		printk(KERN_ERR "eapsampler: can't secure IO space 0x%x-0x%x, giving up\n", eapsampler_port, eapsampler_port+3);
		return err;
	}
	request_region(eapsampler_port, 3, eapsampler_name);

        if((err = register_chrdev(eapsampler_major, eapsampler_name, &eapsampler_fops)) < 0) {
		printk(KERN_ERR "eapsampler: can't register char dev %d\n", eapsampler_major);
		release_region(eapsampler_port, 3);
		return err;
        }
	if(!eapsampler_major) eapsampler_major = err;
	printk(KERN_INFO "eapsampler: device major %d registered\n", eapsampler_major);

	eas_poweron();

	if(eapsampler_period)
		eapsampler_takesample();

	return 0;
}

void
cleanup_module(void) {

	eas_poweroff();

	del_timer(&eapsampler_timer);
	release_region(eapsampler_port, 3);
	unregister_chrdev(eapsampler_major, eapsampler_name);

	printk(KERN_INFO "eapsampler: done, bye\n");
}
