/*
 * Callouts a la FreeBSD - sort of.
 *
 * Copyright (C) 2002 Gary Jennejohn (gj@denx.de)
 */

#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/devfs_fs_kernel.h>
#include <linux/proc_fs.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/poll.h>
#include <linux/stat.h>
#include <linux/wait.h>
#include <linux/ctype.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <asm/semaphore.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
#include <asm/arch/hardware.h>
#include <asm/arch/irqs.h>

#include <linux/trab_callout.h>

static void trab_callout_timer(unsigned long);
static void trab_callout_handle_timer(unsigned long);
struct timer_list callout_timer;
#define CNLIST 5
static struct callout_list clist[CNLIST] = {
	{0, 0, 0, NULL, 0},
	{0, 0, 0, NULL, 0},
	{0, 0, 0, NULL, 0},
	{0, 0, 0, NULL, 0},
	{0, 0, 0, NULL, 0},
};
static int callout_timer_active;
/* a kernel thread to handle timer ticks */
DECLARE_TASKLET_DISABLED(tasklet_handle_callout_timer, trab_callout_handle_timer, 0);

static void
trab_callout_handle_timer(unsigned long not_used)
{
	int i;
	struct callout_list *cl;
	unsigned long flags;

	save_flags(flags);
	cli();
	for (i = 0; i < CNLIST; i++)
	{
		cl = &clist[i];
		if (cl->when == 0)
			continue;
		if (cl->func == NULL)
			continue;
		if (cl->when <= jiffies)
		{
			cl->func(cl->arg);
			if (cl->reload == 0)
			{
				cl->when = 0;
				cl->func = NULL;
			}
			else
				cl->when = jiffies + cl->period;
		}
	}
	restore_flags(flags);
}

static void
trab_callout_timer(unsigned long junk)
{
	callout_timer_active = 0;
	/* initialize the timer */
	init_timer(&callout_timer);
	callout_timer.function = trab_callout_timer;
	/* assumes that HZ=100 XXX */
	/* 10ms for receive timeout and watchdog */
	callout_timer.expires = jiffies + 1;
	add_timer(&callout_timer);
	callout_timer_active = 1;

	/* schedule trab_rs485_handle_timer() to be run */
	tasklet_schedule(&tasklet_handle_callout_timer);
}

struct callout_list *
add_callout(void (*func)(void *), void *arg, unsigned long p, int r)
{
	int i;
	struct callout_list *cl;
	unsigned long flags;

	save_flags(flags);
	cli();

	for (i = 0; i < CNLIST; i++)
	{
		cl = &clist[i];
		if (cl->when != 0 && cl->func != NULL)
			continue;
		cl->period = p;
		cl->when = jiffies + p;
		cl->reload = r;
		cl->func = func;
		cl->arg = arg;
		restore_flags(flags);
		return cl;
	}
	restore_flags(flags);
	return NULL;
}

void
mod_callout(struct callout_list *cl, unsigned long when)
{
	int i;
	unsigned long flags;

	/* don't want a timer interrupt here */
	save_flags(flags);
	cli();
	for (i = 0; i < CNLIST; i++)
	{
		if (cl != &clist[i])
			continue;
		cl->when = jiffies + when;
		break;
	}
	restore_flags(flags);
}

void
rmv_callout(struct callout_list *cl)
{
	int i;
	unsigned long flags;

	save_flags(flags);
	cli();

	for (i = 0; i < CNLIST; i++)
	{
		if (cl != &clist[i])
			continue;
		cl->when = 0;
		cl->func = NULL;
		break;
	}
	restore_flags(flags);
}

static int __init
trab_callout_init (struct inode *inode, struct file *file)
{
	/* initialize the timer */
	init_timer(&callout_timer);
	callout_timer.function = trab_callout_timer;
	/* assumes that HZ=100 XXX */
	/* 10ms for receive timeout and watchdog */
	callout_timer.expires = jiffies + 1;
	add_timer(&callout_timer);
	callout_timer_active = 1;
	/* initialize the timer tick handler */
	tasklet_enable(&tasklet_handle_callout_timer);
	return 0;
}

static void __exit
trab_callout_exit (void) 
{
	if (callout_timer_active)
		del_timer(&callout_timer);
	tasklet_kill(&tasklet_handle_callout_timer);
}

module_init(trab_callout_init);
module_exit(trab_callout_exit);

MODULE_AUTHOR("Gary jennejohn <gj@denx.de>");
MODULE_DESCRIPTION("TRAB callout functions");
MODULE_LICENSE("GPL");
