/*
    IRctrl: irctrl.c:	A linux kernel IR remote control driver
    Copyright (C) 1998	Alan Yates <alany@ay.com.au>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#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,2,0) < LINUX_VERSION_CODE
#	define __NEW_LINUX__
#endif

#ifndef __KERNEL__
#  define __KERNEL__
#endif
#ifndef MODULE
#  define MODULE
#endif

#include <linux/module.h>
#include <linux/errno.h>
#include <linux/signal.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/malloc.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/ioport.h>
#include <asm/io.h>

/* version differences */
#ifdef __NEW_LINUX__

#	include <linux/poll.h>
#	include <asm/uaccess.h>
/* cool new junk */
MODULE_AUTHOR("Alan Yates <alany@ay.com.au>");
MODULE_DESCRIPTION("IR remote control driver");
MODULE_SUPPORTED_DEVICE("Alan's IR modem");
MODULE_PARM(irctrl_base, "i");
MODULE_PARM_DESC(irctrl_base, "IRctrl port base address");
MODULE_PARM(irctrl_irq, "i");
MODULE_PARM_DESC(irctrl_irq, "IRctrl interrupt request line");
MODULE_PARM(irctrl_major, "i");
MODULE_PARM_DESC(irctrl_major, "IRctrl char device major");

#else /* __NEW_LINUX__ */

#	include <asm/segment.h>
#define	signal_pending(current)  (current->signal & ~current->blocked)
#define mdelay(x) udelay(1000*x)

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 /* __NEW_LINUX__ */


/* defines */
#define irctrl_id "irctrl: irctrl 2.0 by Alan Yates <alany@ay.com.au>\n"
#define irctrl_bufsize 4096		/* one page buffers */

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

/* system resources */
int irctrl_base = 0x378;
int irctrl_irq = 7;
int irctrl_major = 0;

struct wait_queue *irctrl_rq = 0;		/* read sleeping queue */
struct wait_queue *irctrl_wq = 0;		/* write sleeping queue */

/* buffer for data */
volatile int irctrl_txbusy = 0;			/* tx lock */ 
volatile unsigned int irctrl_wcursor = 0;	/* write cursor */
unsigned char irctrl_rxbuf[irctrl_bufsize];	/* IR RX output buffer */
unsigned long irctrl_txbuf[irctrl_bufsize];	/* IR TX timing buffer */

/* private flip data struct */
struct pdata {
	unsigned int irctrl_rcursor;
};

/* IR leading edge interrupt handler */
static void
irctrl_interrupt(int irq, void *regs) {
	struct timeval tv;
	char buf[40];
	int size = 0, i = 0;

	if(!MOD_IN_USE) return;

	do_gettimeofday(&tv);
	size = sprintf(buf, "%9d %6d\n", (int)tv.tv_sec, (int)tv.tv_usec);

	/* circularly copy into the output buffer */
	for(i = 0; i < size; i++)
		irctrl_rxbuf[(irctrl_wcursor+i)%irctrl_bufsize] = buf[i];
	irctrl_wcursor += size;
	irctrl_wcursor %= irctrl_bufsize;

	wake_up_interruptible(&irctrl_rq);
}

/* generate a square waveform of length len and period t (us) */
static void
irctrl_irtone(unsigned long len, unsigned long t) {
	int i = 0;

	t++; /* div by zero avoid */

	for(i = 0; i < len/t; i++) {
		outb(0xff, irctrl_base);	/* mark */
		udelay(t/2);
		outb(0x00, irctrl_base);	/* space */
		udelay(t/2);
	}
}

/* TX driver */
void
irctrl_dotx(int txs) {
	int i = 0;
	unsigned long flags = 0;

	if(txs > irctrl_bufsize) {
		printk(KERN_INFO "irctrl: tx buffer overrun!\n");
		return;
	}

	/* clear interupts for timing accuracy */
	save_flags(flags);
	cli();

	/* do the TX of IR data */
	for(i = 1; i < txs; i+=2) {
		/* data MARK - carrier on */
		irctrl_irtone(irctrl_txbuf[i], irctrl_txbuf[0]);

		/* data SPACE - carrier off */
		udelay(irctrl_txbuf[i+1]);
	}

	restore_flags(flags);
}

/* filenode open */
static int
irctrl_open(struct inode *inode, struct file *flip) {
	MOD_INC_USE_COUNT;
	flip->private_data = kmalloc(sizeof(struct pdata), GFP_KERNEL);
	if(!flip->private_data) return -ENOMEM;
	((struct pdata *)flip->private_data)->irctrl_rcursor = irctrl_wcursor;
	return 0;
}

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

/* filenode read */
#ifdef __NEW_LINUX__
static size_t
irctrl_read(struct file *flip, char *buf, size_t count, loff_t *offset) {
#else
static int
irctrl_read(struct inode *inode, struct file *flip, char *buf, int count) {
#endif
	int bytes = 0;
	struct pdata *pd = (struct pdata *) flip->private_data;

	while(pd->irctrl_rcursor == irctrl_wcursor) {
		if(flip->f_flags & O_NONBLOCK)
			return -EAGAIN;
		interruptible_sleep_on(&irctrl_rq);		
		if(signal_pending(current))
			return -ERESTARTSYS;
	}

	if(irctrl_wcursor > pd->irctrl_rcursor) 
		bytes = min(count, irctrl_wcursor - pd->irctrl_rcursor);
	else	/* write pointer wrap */
		bytes = min(count, irctrl_bufsize - pd->irctrl_rcursor);

	if(copy_to_user(buf, &irctrl_rxbuf[pd->irctrl_rcursor], bytes))
		return -EFAULT;
	pd->irctrl_rcursor += bytes;
	pd->irctrl_rcursor %= irctrl_bufsize;

	return bytes;
}

/* filenode write */
#ifdef __NEW_LINUX__
static size_t
irctrl_write(struct file *flip, char *buf, size_t count, loff_t *offset) {
#else
static int
irctrl_write(struct inode *inode, struct file *flip, char *buf, int count) {
#endif
	char *kbuf = 0, *cp = 0;
	int i = 0, j = 0, bad = -1;

	while(irctrl_txbusy) {
		if(flip->f_flags & O_NONBLOCK) return -EAGAIN;
		interruptible_sleep_on(&irctrl_wq);		
		if(signal_pending(current)) return -ERESTARTSYS;
	}
	irctrl_txbusy = 1;

	kbuf = kmalloc(count, GFP_KERNEL);
	if(!kbuf) return -ENOMEM;

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

	/* get timing data */
	cp = kbuf;
	for(j = 0; j < count; j++) if(cp[j] == '\n') cp[j] = 0;
	while((irctrl_txbuf[i++] = simple_strtoul(cp, &cp, 0)) > 0) {
		while(!digit(*cp)) cp++;
		if(cp > kbuf+count) break;
	}

	kfree(kbuf);

	/* check the values for sanity */
	for(j = 1; j < i; j++) {
		if(irctrl_txbuf[j] <= 0) bad = j;
		if(irctrl_txbuf[j] > 999999) bad = j;
	}
	if(irctrl_txbuf[0] <= 0) bad = 1;

	/* do tx and awake any blocked writers */
	if((i < 3) || (bad > 0)) {
		printk(KERN_INFO "irctrl: bad TX data: data[%i] = %i\n",
			bad, (int) irctrl_txbuf[bad]);
	} else irctrl_dotx(i);
	irctrl_txbusy = 0;
	wake_up_interruptible(&irctrl_wq);

	return count;
}

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

	poll_wait(flip, &irctrl_rq, wait);
	poll_wait(flip, &irctrl_wq, wait);
	if(pd->irctrl_rcursor != irctrl_wcursor) mask |= POLLIN | POLLRDNORM;
	if(!irctrl_txbusy) mask |= POLLOUT | POLLWRNORM;
	
	return mask;
}
#else
/* filenode select */
static int
irctrl_select(struct inode *inode, struct file *flip, int mode, select_table *table) {
	struct pdata *pd = (struct pdata *) flip->private_data;

	if(mode == SEL_IN) {
		if(pd->irctrl_rcursor != irctrl_wcursor) return 1;
		select_wait(&irctrl_rq, table);
		return 0;
	}

	if(mode == SEL_OUT) {
		if(!irctrl_txbusy) return 1;
		select_wait(&irctrl_wq, table);
		return 0;
	}

	/* no exceptions */
	return 0;
}
#endif

/* filenode operations struct */
static struct file_operations irctrl_fops = {
	0,			/* lseek	*/
	(void *)irctrl_read,	/* read		*/
	(void *)irctrl_write,	/* write	*/
	0,			/* readdir	*/
	(void *)irctrl_select,	/* select/poll	*/
	0,			/* ioctl	*/
	0,			/* mmap		*/
	(void *)irctrl_open,	/* open		*/
#ifdef __NEW_LINUX__
	0,			/* flush	*/
#endif
	(void *)irctrl_release,	/* release	*/
	0,			/* fsync	*/
	0,			/* fasync	*/
};

/* module init */
int
init_module(void) {
	int ret;

	printk(KERN_INFO irctrl_id);

#ifndef __NEW_LINUX__
	register_symtab(0);
#endif

	if((ret = check_region(irctrl_base, 3)) < 0) {
		printk(KERN_INFO "irctrl: can't secure IO space 0x%x-0x%x, giving up\n", irctrl_base, irctrl_base+3);
		return ret;
	}
	request_region(irctrl_base, 3, "irctrl");

	if((ret = request_irq(irctrl_irq, (void *)irctrl_interrupt, SA_INTERRUPT | SA_SAMPLE_RANDOM, "irctrl", 0)) < 0) {
		printk(KERN_INFO "irctrl: can't get IRQ %d, giving up\n", irctrl_irq);
		release_region(irctrl_base, 3);
		return ret;
	}

	/* enable parport interrupts */
	outb(0x10, irctrl_base+2);

	if((ret = register_chrdev(irctrl_major, "irctrl", &irctrl_fops)) < 0) {
		printk(KERN_INFO "irctrl: can't register char dev %d\n", irctrl_major);
		outb(0x0, irctrl_base+2);
		release_region(irctrl_base, 3);
		return ret;
	}
	if(irctrl_major == 0) irctrl_major = ret;
	printk(KERN_INFO "irctrl: device major %d registered\n", irctrl_major);

	return 0;
}

/* module cleanup */
void
cleanup_module(void) {
	printk(KERN_INFO "irctrl: unloading module\n");

	unregister_chrdev(irctrl_major, "irctrl");

	/* release IRQ */
	outb(0x00, irctrl_base+2);
	free_irq(irctrl_irq, 0);

	release_region(irctrl_base, 3);
}
