/*
 * linux/drivers/char/mecflame.c
 *
 * Flame sensor for Matt Cross's robot.
 */

#include <linux/config.h>
#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/malloc.h>
#include <linux/proc_fs.h>
#include <linux/stat.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <asm/irq.h>

#include <linux/mecflame.h>

#define MECFLAME_TIMER_JIFFIES	(HZ/10)
struct timer_list mecflame_timer;

/* Debug counters. */
unsigned int mecflame_timers;
unsigned int mecflame_total_pulses;

/* Pulses incremented by interrupt handler. */
unsigned int mecflame_cur_pulses;

/* Array of pulse counts of the last second, split up into 1/10 second
   increments. */
unsigned int mecflame_pulse_array[10];

/* Pulse count from the last second. */
unsigned int mecflame_pulse_second_count;

void mecflame_timer_handler(unsigned long data)
{
	unsigned int cur_pulses, sum;
	int i;

	/* debug counter. */
	mecflame_timers++;

	/* First, capture the current pulse count. */
	cli();
	cur_pulses = mecflame_cur_pulses;
	mecflame_cur_pulses = 0;
	sti();

	/* Next, put it into the array, and calc sum while we're at it. */
	sum = cur_pulses;
	for (i=1; i<10; i++) {
		mecflame_pulse_array[i-1] = mecflame_pulse_array[i];
		sum += mecflame_pulse_array[i];
	}
	mecflame_pulse_array[9] = cur_pulses;

	/* Update global count. */
	mecflame_pulse_second_count = sum;

	/* Re-add the timer */
	init_timer(&mecflame_timer);
	mecflame_timer.expires = jiffies + MECFLAME_TIMER_JIFFIES;
	mecflame_timer.data = 0;
	mecflame_timer.function = mecflame_timer_handler;
	add_timer(&mecflame_timer);
}

void mecflame_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	/* Increment pulse count. */
	mecflame_cur_pulses++;
	mecflame_total_pulses++;

	/* Clear interrupt. */
	(*(volatile unsigned int *)0xfffff30c) = 0x00080000;
}

int mecflame_open(struct inode * inode, struct file * file)
{
	int minor = MINOR(inode->i_rdev);

	if (minor != 0) {
		return -ENODEV;
	}

	return 0;
}

int mecflame_ioctl (struct inode * inode, struct file * filp, unsigned int cmd,
		    unsigned long arg)
{
	int err;

	switch(cmd) {
	case MECFLAME_IOC_FLAMECNT:
		err = verify_area(VERIFY_WRITE, (int *) arg, sizeof(int));
		if (err)
			return err;
		put_user(mecflame_pulse_second_count, (int *) arg);
		return 0;
	case MECFLAME_IOC_DEBUG1:
		err = verify_area(VERIFY_WRITE, (int *) arg, sizeof(int));
		if (err)
			return err;
		put_user(mecflame_timers, (int *) arg);
		return 0;
	case MECFLAME_IOC_DEBUG2:
		err = verify_area(VERIFY_WRITE, (int *) arg, sizeof(int));
		if (err)
			return err;
		put_user(mecflame_total_pulses, (int *) arg);
		return 0;
	default:
		return -ENOTTY;
	}
}

static struct file_operations mecflame_fops = {
        NULL,		/* seek */
	NULL,		/* read */
	NULL,		/* write */
	NULL,		/* readdir */
	NULL,		/* select */
	mecflame_ioctl,	/* ioctl */
	NULL,		/* mmap */
        mecflame_open,
        NULL		/* release */
};

int mecflame_init(void)
{
	int i;

	/* basic init. */
	mecflame_cur_pulses = 0;
	mecflame_pulse_second_count = 0;
	for(i=0; i<10; i++)
		mecflame_pulse_array[i] = 0;

	if (register_chrdev(MECFLAME_MAJOR,"mecflame",&mecflame_fops)) {
		printk("unable to get major %d for mecflame devices\n",
		       MECFLAME_MAJOR);
		return -EIO;
	}

	/* Set up 10Hz timer. */
	init_timer(&mecflame_timer);
	mecflame_timer.expires = jiffies + MECFLAME_TIMER_JIFFIES;
	mecflame_timer.data = 0;
	mecflame_timer.function = mecflame_timer_handler;
	add_timer(&mecflame_timer);

	/* Set up interrupt handler. */
	if (request_irq(IRQ_MACHSPEC | 19,
			mecflame_interrupt,
			IRQ_FLG_STD,
			"mecflame", NULL)) {
		printk("Unable to get interrupt for mecflame driver\n");
		unregister_chrdev(MECFLAME_MAJOR, "mecflame");
		return -EIO;
	}

	/* Set bit 7 of port D to be IRQ6# (instead of a gpio) */
	(*(volatile unsigned char *)0xfffff41b) &= ~0x80;

	/* Set IRQ6# to be falling edge sensitive. */
	(*(volatile unsigned short *)0xfffff302) &= ~0x1000;
	(*(volatile unsigned short *)0xfffff302) |= 0x0100;

	/* Enable the interrupt. */
	(*(volatile unsigned int *)0xfffff304) &= ~0x00080000;
	
	printk("mecflame initialized\n");
	return 0;
}
