/*
 * OSS compatible sequencer driver
 *
 * Timer control routines
 *
 * Copyright (C) 1998,99 Takashi Iwai <iwai@ww.uni-erlangen.de>
 *
 * 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.
 */

#include "seq_oss_timer.h"
#include "seq_oss_event.h"
#include "seq_oss_readq.h"
#include "timer.h"
#include "seq_oss_legacy.h"


/*
 * if you want to use system timer directly, uncomment the following line.
 * otherwise, ALSA timer routines will be used.
 */
/* #define USE_SYSTEM_TIMER */
#define TIMER_IS_REPROGRAMABLE


#define MAX_TICK	(unsigned int)-1

/*
 * timer information definition
 */
struct seq_oss_timer_t {
	int index;
	reltime_t cur_tick;
	abstime_t tick_offset;
	abstime_t next_tick;
	int realtime;
	seq_oss_devinfo_t *devinfo;
};	


/*
 * common variables
 */

int timer = SND_TIMER_TYPE_GLOBAL | SND_TIMER_GLOBAL_SYSTEM;   /* configurable */

#ifdef MODULE_PARM              /* hey - we have new 2.1.18+ kernel... */
MODULE_PARM(timer, "i");
MODULE_PARM_DESC(timer, "Timer number");
#endif

static int ntimers;	/* number of clients accessing to timer */
static snd_timer_instance_t *timerp = NULL;	/* assigned timer */
static seq_oss_timer_t *tlist[SND_SEQ_OSS_MAX_CLIENTS];	/* table for client informations */
static volatile abstime_t cur_sys_tick, next_sys_tick;	/* current and next interrupt tick */

static unsigned int timer_inc;	/* timer increment */
static unsigned int tick_err_inc, tick_err_max;	/* timer error increment and threshold */
static volatile unsigned int tick_err;	/* current timer error */

snd_mutex_define_static(register);
snd_spin_define_static(lock);


/*
 * prototypes
 */
static void timer_check_queues(void);
static void timer_reset(seq_oss_timer_t *rec);


#ifndef USE_SYSTEM_TIMER

static void timer_callback(snd_timer_instance_t *tmr, unsigned long resolution, unsigned long ticks, void *private);

/*
 * open ALSA timer
 */
static int
timer_open(void)
{
	unsigned int system_resolution = 1000000000UL / SND_SEQ_OSS_CTRLRATE;
	unsigned int timer_resolution;

	timerp = snd_timer_open("seq_oss", timer, SND_TIMER_STYPE_OSS_SEQUENCER, 0);
	if (! timerp) {
		snd_printk("sequencer: can't create timer\n");
		return -ENODEV;
	}

	if ((timer_resolution = snd_timer_resolution(timerp)) <= 0) {
		snd_printk("sequencer: invalid timer resolution %d\n", timer_resolution);
		snd_timer_close(timerp);
		timerp = NULL;
		return -ENODEV;
	}

	if (timer_resolution >= system_resolution)
		timer_resolution = system_resolution;

	/* increment at each timer interrupt */
	timer_inc = system_resolution / timer_resolution;
	/* accumulated error at each time checking queues */
	tick_err_inc = system_resolution % timer_resolution;
	tick_err_max = timer_resolution;

	timerp->callback = timer_callback;
	timerp->callback_data = NULL;
#ifndef TIMER_IS_REPROGRAMABLE
	timerp->flags |= SND_TIMER_IFLG_AUTO; /* call next timer automatically */
#endif

	return 0;
}

/*
 * close system timer
 */
static void
timer_close(void)
{
	if (timerp) {
		snd_timer_stop(timerp);
		snd_timer_close(timerp);
		timerp = NULL;
	}
}

/*
 * start system timer
 */
inline static void
timer_start(unsigned int inc)
{
	snd_timer_start(timerp, inc);
}

/* reprogram timer */
#ifdef TIMER_IS_REPROGRAMABLE
#define timer_reprogram(inc)	timer_start(inc)
#else
/* do nothing - ALSA timer routine does */
#define timer_reprogram(inc)	/**/
#endif

static void
timer_callback(snd_timer_instance_t *tmr, unsigned long resolution, unsigned long ticks, void *private)
{
	timer_check_queues();
}

#else

/* callback from system timer */
static void systimer_interrupt(unsigned long dummy);
/* for system timer */
static struct timer_list seq_timer = {NULL, NULL, 0, 0, systimer_interrupt};

/* open system timer */
static int
timer_open(void)
{
	timer_inc = HZ / SND_SEQ_OSS_CTRLRATE;
	tick_err_inc = HZ % SND_SEQ_OSS_CTRLRATE;
	tick_err_max = SND_SEQ_OSS_CTRLRATE;
	return 0;
}
	
/* start system timer */
inline static void
timer_start(unsigned int inc)
{
	seq_timer.expires = jiffies + inc;
	add_timer(&seq_timer);
}

/* reprogram next invoke */
#define timer_reprogram(inc)	timer_start(inc)

/* stop system timer */
static void
timer_close(void)
{
	del_timer(&seq_timer);
}

static void
systimer_interrupt(unsigned long dummy)
{
	timer_check_queues();
}

#endif


/*
 * create and register a new timer
 */
seq_oss_timer_t *
snd_seq_oss_timer_new(seq_oss_devinfo_t *dp)
{
	seq_oss_timer_t *rec;

	snd_mutex_down_static(register);
	if (! ntimers) {
		if (timer_open()) {
			snd_mutex_up_static(register);
			return NULL;
		}
		/* clear variables */
		cur_sys_tick = 0;
		next_sys_tick = MAX_TICK;
		tick_err = tick_err_inc;
	}

	rec = snd_malloc(sizeof(seq_oss_timer_t));
	if (! rec) {
		if (! ntimers)
			timer_close();
		snd_mutex_up_static(register);
		return NULL;
	}
	rec->devinfo = dp;
	rec->index = dp->index;
	snd_seq_oss_timer_init(rec);
	tlist[rec->index] = rec;
	ntimers++;

	if (ntimers == 1) {
		timer_start(timer_inc);
	}

	snd_mutex_up_static(register);
	return rec;
}

/*
 * delete and unregister timer
 */
void
snd_seq_oss_timer_delete(seq_oss_timer_t *rec)
{
	snd_mutex_down_static(register);
	tlist[rec->index] = NULL;
	ntimers--;
	if (! ntimers)
		timer_close();
	snd_free(rec, sizeof(*rec));
	snd_mutex_up_static(register);
}


/*
 * system timer callback function
 */
static void
timer_check_queues(void)
{
	int i;

	/* reprogram next invoke */
	tick_err += tick_err_inc;
	if (tick_err >= tick_err_max) {
		tick_err -= tick_err_max;
		timer_reprogram(timer_inc + 1);
	} else
		timer_reprogram(timer_inc);

	/* increment tick */
	cur_sys_tick++;
	if (cur_sys_tick < next_sys_tick)
		return;

	/* activate queues */
	next_sys_tick = MAX_TICK;
	for (i = 0; i < SND_SEQ_OSS_MAX_CLIENTS; i++) {
		seq_oss_timer_t *t;
		if ((t = tlist[i]) == NULL)
			continue;
		if (t->next_tick <= cur_sys_tick) {
			t->next_tick = MAX_TICK;
			snd_seq_oss_process_queue(t->devinfo, cur_sys_tick);
		}
		if (t->next_tick < next_sys_tick)
			next_sys_tick = t->next_tick;
	}
}


/*
 * set the next interrupt time
 */
void
snd_seq_oss_timer_set(seq_oss_timer_t *rec, abstime_t time)
{
	unsigned long flags;
	snd_spin_lock_static(lock, &flags);
	if (time < rec->next_tick) {
		rec->next_tick = time;
		if (time < next_sys_tick)
			next_sys_tick = time;
	}
	snd_spin_unlock_static(lock, &flags);
}


/*
 * set the next interrupt time
 */
void
snd_seq_oss_timer_init(seq_oss_timer_t *rec)
{
	rec->next_tick = MAX_TICK;
	rec->cur_tick = 0;
	rec->tick_offset = cur_sys_tick;
	rec->realtime = 0;
}


/*
 * get current processed time
 */
abstime_t
snd_seq_oss_timer_cur_tick(seq_oss_timer_t *rec)
{
	return rec->cur_tick + rec->tick_offset;
}

/*
 * get current system time
 */
abstime_t
snd_seq_oss_timer_system_tick(void)
{
	return cur_sys_tick;
}

/*
 * get current system time in relative unit
 */
reltime_t
snd_seq_oss_timer_get_tick(seq_oss_timer_t *rec)
{
	return cur_sys_tick - rec->tick_offset;
}

/*
 * return realtime flag
 */
int
snd_seq_oss_timer_is_realtime(seq_oss_timer_t *rec)
{
	return rec->realtime;
}


/*
 * reset timer offset
 */
static void
timer_reset(seq_oss_timer_t *rec)
{
	abstime_t curtick = rec->cur_tick + rec->tick_offset;
	abstime_t systick = cur_sys_tick;
	if (curtick > systick)
		systick = curtick;
	rec->tick_offset = systick;
	rec->cur_tick = 0;
	rec->realtime = 0;
}


/*
 * process one timing event
 * return 1 : event proceseed -- skip this event
 *        0 : not a timer event -- enqueue this event
 */
int
snd_seq_oss_process_timer_event(seq_oss_timer_t *rec, evrec_t *ev)
{
	abstime_t parm = ev->t.time;

	if (ev->t.code == SND_OSS_EV_TIMING) {
		switch (ev->t.cmd) {
		case SND_OSS_TMR_WAIT_REL:
			parm += rec->cur_tick;
			rec->realtime = 0;
			/* continue to next */
		case SND_OSS_TMR_WAIT_ABS:
			if (parm == 0) {
				rec->realtime = 1;
			} else if (parm >= rec->cur_tick) {
				rec->realtime = 0;
				rec->cur_tick = parm;
				if (rec->cur_tick + rec->tick_offset < cur_sys_tick)
					rec->cur_tick = cur_sys_tick - rec->tick_offset;
			}
			return 1;	/* skip this event */
			
		case SND_OSS_TMR_START:
			timer_reset(rec);
			return 1;

		case SND_OSS_TMR_STOP:
		case SND_OSS_TMR_CONTINUE:
		case SND_OSS_TMR_TEMPO:
			return 1;
		}
	} else if (ev->s.code == SND_OSS_SEQ_SYNCTIMER) {
		/* reset timer */
		timer_reset(rec);
		return 1;

	} else if (ev->s.code == SND_OSS_SEQ_WAIT) {
		/* time = from 1 to 3 bytes */
		parm = (ev->echo >> 8) & 0xffffff;
		if (parm > rec->cur_tick) {
			/* set next event time */
			rec->cur_tick = parm;
			rec->realtime = 0;
		}
		return 1;
	}

	return 0;
}

/*
 * proc output
 */
void
snd_seq_oss_timer_info_read(seq_oss_timer_t *rec, snd_info_buffer_t *buf)
{
	snd_iprintf(buf, "  current tick %d\n", rec->cur_tick + rec->tick_offset);
	snd_iprintf(buf, "  next tick %d\n", rec->next_tick);
	snd_iprintf(buf, "  system tick %d\n", cur_sys_tick);
	snd_iprintf(buf, "  next system tick %d\n", next_sys_tick);
}
