/*
 *  Timers abstract layer
 *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
 *
 *
 *   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.
 *
 */

#define SND_MAIN_OBJECT_FILE
#include "../include/driver.h"
#include "../include/timer.h"
#include "../include/control.h"
#include "../include/info.h"
#include "../include/minors.h"
#ifdef CONFIG_KMOD
#include <linux/kmod.h>
#endif
#ifdef CONFIG_KERNELD
#include <linux/kerneld.h>
#endif

int snd_timer_limit = SND_TIMER_GLOBAL_SYSTEM + 1;
MODULE_PARM(snd_timer_limit, "i");
MODULE_PARM_DESC(snd_timer_limit, "Maximum global timers in system. (1 by default)");

typedef struct {
	snd_timer_instance_t *timeri;
	unsigned long ticks;
	unsigned long overrun;
	int qhead;
	int qtail;
	int qused;
	int queue_size;
	snd_timer_read_t *queue;
	spinlock_t qlock;
	wait_queue_head_t qchange_sleep;
} snd_timer_user_t;

snd_timer_t *snd_timer_devices = NULL;
snd_timer_instance_t *snd_timer_slave_devices = NULL;

static spinlock_t snd_timer_slave = SPIN_LOCK_UNLOCKED;
static DECLARE_MUTEX(register_mutex);

static void snd_timer_reschedule(snd_timer_t * timer, unsigned long ticks_left);

/*

 */

static snd_timer_instance_t *__snd_timer_open(char *owner, snd_timer_t *timer,
					      unsigned int slave_type,
					      unsigned int slave_id)
{
	snd_timer_instance_t *timeri;
	unsigned long flags;
	
	if (!timer)
		return NULL;
	timeri = snd_kcalloc(sizeof(*timeri), GFP_KERNEL);
	if (!timeri)
		return NULL;
	MOD_INC_USE_COUNT;
	if (timer->card)
		timer->card->use_inc(timer->card);
	timeri->timer = timer;
	timeri->owner = snd_kmalloc_strdup(owner, GFP_KERNEL);
	timeri->slave_type = slave_type;
	timeri->slave_id = slave_id;
	spin_lock_irqsave(&timer->lock, flags);
	if (timer->first != NULL) {
		timeri->next = timer->first;
	} else {
		if (timer->hw.open)
		timer->hw.open(timer);
	}
	timer->first = timeri;
	spin_unlock_irqrestore(&timer->lock, flags);
	return timeri;
}

static snd_timer_t *snd_timer_find(int timer_no)
{
	snd_timer_t *timer;

	timer = snd_timer_devices;
	while (timer) {
		if (timer->number == timer_no)
			break;
		if (timer->number > timer_no) {
			timer = NULL;
			break;
		}
		timer = timer->next;
	}
	return timer;
}

#if defined(CONFIG_KERNELD) || defined(CONFIG_KMOD)

static void snd_timer_request(int timer_no)
{
	char str[32];
	
	if (timer_no < 256) {
		if (timer_no >= snd_timer_limit)
			return;
		sprintf(str, "snd-timer-%i", timer_no);
	} else {
		if (timer_no < 512) {
			timer_no -= 256;
			timer_no >>= 4;
		} else {
			timer_no -= 512;
			timer_no >>= 5;
		}
		if (timer_no >= snd_ecards_limit)
			return;
		sprintf(str, "snd-card-%i", timer_no);
	}
	request_module(str);
}

#endif

snd_timer_instance_t *snd_timer_open(char *owner, int timer_no,
				     unsigned int slave_type,
				     unsigned int slave_id)
{
	snd_timer_t *timer;
	snd_timer_instance_t *timeri = NULL;
	
	down(&register_mutex);
	timer = snd_timer_find(timer_no);
#ifdef CONFIG_KMOD
	if (timer == NULL) {
		up(&register_mutex);
		snd_timer_request(timer_no);
		down(&register_mutex);
		timer = snd_timer_find(timer_no);
	}
#endif
	if (timer)
		timeri = __snd_timer_open(owner, timer, slave_type, slave_id);
	up(&register_mutex);
	return timeri;
}

snd_timer_instance_t *snd_timer_open1(char *owner, snd_timer_t *timer,
				      unsigned int slave_type,
				      unsigned int slave_id)
{
	snd_timer_instance_t *timeri;

	down(&register_mutex);
	timeri = __snd_timer_open(owner, timer, slave_type, slave_id);
	up(&register_mutex);
	return timeri;
}

snd_timer_instance_t *snd_timer_open_slave(char *owner,
					   unsigned int slave_type,
					   unsigned int slave_id)
{
	snd_timer_instance_t *timeri;
	unsigned long flags;
	
	timeri = snd_kcalloc(sizeof(*timeri), GFP_KERNEL);
	if (!timeri)
		return NULL;
	MOD_INC_USE_COUNT;
	timeri->owner = snd_kmalloc_strdup(owner, GFP_KERNEL);
	timeri->slave_type = slave_type;
	timeri->slave_id = slave_id;
	spin_lock_irqsave(&snd_timer_slave, flags);
	timeri->flags |= SND_TIMER_IFLG_SLAVE;
	if (snd_timer_slave_devices != NULL)
		timeri->next = snd_timer_slave_devices;
	snd_timer_slave_devices = timeri;
	timeri->slave_type = slave_type;
	timeri->slave_id = slave_id;
	spin_unlock_irqrestore(&snd_timer_slave, flags);
	return timeri;
}

int snd_timer_close(snd_timer_instance_t * timeri)
{
	unsigned long flags;
	snd_timer_t *timer = NULL;
	snd_timer_instance_t *timeri1;

	if (!timeri)
		return -EINVAL;
	snd_timer_stop(timeri);
	if (!(timeri->flags & SND_TIMER_IFLG_SLAVE)) {
		if ((timer = timeri->timer) == NULL)
			return -EINVAL;
		spin_lock_irqsave(&timer->lock, flags);
		if ((timeri1 = timer->first) == timeri) {
			timer->first = timeri->next;
		} else {
			while (timeri1) {
				if (timeri1->next == timeri)
					break;
				timeri1 = timeri1->next;
			}
			if (!timeri1) {
				spin_unlock_irqrestore(&timer->lock, flags);
				return -EINVAL;
			}
			timeri1->next = timeri->next;
		}
		if (!timer->first) {
			if (timer->hw.close)
				timer->hw.close(timer);
		}
		spin_unlock_irqrestore(&timer->lock, flags);
	} else {
		spin_lock_irqsave(&snd_timer_slave, flags);
		if ((timeri1 = snd_timer_slave_devices) == timeri) {
			snd_timer_slave_devices = timeri->next;
		} else {
			while (timeri1) {
				if (timeri1->next == timeri)
					break;
				timeri1 = timeri1->next;
			}
			if (!timeri1) {
				spin_unlock_irqrestore(&snd_timer_slave, flags);
				return -EINVAL;
			}
			timeri1->next = timeri->next;
		}
		spin_unlock_irqrestore(&snd_timer_slave, flags);
	}
	if (timeri->private_free)
		timeri->private_free(timeri->private_data);
	snd_kfree(timeri->owner);
	snd_kfree(timeri);
	MOD_DEC_USE_COUNT;
	if (timer && timer->card)
		timer->card->use_dec(timer->card);
	return 0;
}

unsigned long snd_timer_resolution(snd_timer_instance_t * timeri)
{
	snd_timer_t * timer;

	if (!timeri)
		return 0;
	if ((timer = timeri->timer) != NULL) {
		if (timer->hw.c_resolution)
			return timer->hw.c_resolution(timer);
		return timer->hw.resolution;
	}
	return 0;
}

int snd_timer_start(snd_timer_instance_t * timeri, unsigned int ticks)
{
	unsigned long flags;
	snd_timer_t *timer;
	int result = -EINVAL;

	if (!timeri || ticks < 1)
		return result;
	if (!(timeri->flags & SND_TIMER_IFLG_SLAVE)) {
		if ((timer = timeri->timer) != NULL) {
			spin_lock_irqsave(&timer->lock, flags);
			timeri->ticks = timeri->cticks = ticks;
			if (timer->running) {
				timer->flags |= SND_TIMER_FLG_RESCHED;
				timeri->flags |= SND_TIMER_IFLG_START;
				result = 1;	/* delayed start */
			} else {
				timer->sticks = ticks;
				timer->hw.start(timer);
				timer->running++;
				timeri->flags |= SND_TIMER_IFLG_RUNNING;
				result = 0;
			}
			spin_unlock_irqrestore(&timer->lock, flags);
		}
	} else {
		spin_lock_irqsave(&snd_timer_slave, flags);
		timeri->flags |= SND_TIMER_IFLG_RUNNING;
		spin_unlock_irqrestore(&snd_timer_slave, flags);
		result = 0;
	}
	return result;
}

int snd_timer_stop(snd_timer_instance_t * timeri)
{
	unsigned long flags;
	snd_timer_t *timer;

	if (!timeri)
		return -EINVAL;
	if (!(timeri->flags & SND_TIMER_IFLG_SLAVE)) {
		if ((timer = timeri->timer) != NULL) {
			spin_lock_irqsave(&timer->lock, flags);
			if (timeri->flags & SND_TIMER_IFLG_RUNNING) {
				timeri->flags &= ~SND_TIMER_IFLG_RUNNING;
				if (!(--timer->running)) {
					timer->hw.stop(timer);
					if (timer->flags & SND_TIMER_FLG_RESCHED) {
						timer->flags &= ~SND_TIMER_FLG_RESCHED;
						snd_timer_reschedule(timer, 0);
						if (timer->flags & SND_TIMER_FLG_CHANGE) {
							timer->flags &= ~SND_TIMER_FLG_CHANGE;
							timer->hw.start(timer);
						}
					}
				}
			}		
			spin_unlock_irqrestore(&timer->lock, flags);
			return 0;
		}
	} else {
		spin_lock_irqsave(&snd_timer_slave, flags);
		timeri->flags &= ~SND_TIMER_IFLG_RUNNING;
		spin_unlock_irqrestore(&snd_timer_slave, flags);
		return 0;
	}
	return -EINVAL;
}

int snd_timer_continue(snd_timer_instance_t * timeri)
{
	unsigned long flags;
	snd_timer_t * timer;
	int result = -EINVAL;

	if (!timeri)
		return result;
	if (timeri->flags & SND_TIMER_IFLG_SLAVE)
		return snd_timer_start(timeri, 1);
	if ((timer = timeri->timer) != NULL) {
		spin_lock_irqsave(&timer->lock, flags);
		if (!timer->running) {
			if (!timeri->cticks)
				timeri->cticks = 1;
			timer->hw.start(timer);
			timer->running++;
			timeri->flags |= SND_TIMER_IFLG_RUNNING;
			result = 0;
		} else {
			if (!timeri->cticks)
				timeri->cticks = 1;
			timer->flags |= SND_TIMER_FLG_RESCHED;
			timeri->flags |= SND_TIMER_IFLG_START;
			result = 1;	/* delayed start */
		}
		spin_unlock_irqrestore(&timer->lock, flags);
	}
	return result;
}

static void snd_timer_reschedule(snd_timer_t * timer, unsigned long ticks_left)
{
	snd_timer_instance_t *ti;
	unsigned long ticks = ~0UL;

	ti = timer->first;
	if (!ti) {
		timer->flags &= ~SND_TIMER_FLG_RESCHED;
		return;
	}
	while (ti) {
		if (ti->flags & SND_TIMER_IFLG_START) {
			ti->flags &= ~SND_TIMER_IFLG_START;
			ti->flags |= SND_TIMER_IFLG_RUNNING;
			timer->running++;
		}
		if (ti->flags & SND_TIMER_IFLG_RUNNING) {
			ticks = ti->cticks;
			ti = ti->next;
			break;
		}
		ti = ti->next;
	}
	while (ti) {
		if (ti->flags & SND_TIMER_IFLG_START) {
			ti->flags &= ~SND_TIMER_IFLG_START;
			ti->flags |= SND_TIMER_IFLG_RUNNING;
			timer->running++;
		}
		if (ti->flags & SND_TIMER_IFLG_RUNNING) {
			if (ticks > ti->cticks)
				ticks = ti->cticks;
		}
		ti = ti->next;
	}
	if (ticks == ~0UL) {
		timer->flags &= ~SND_TIMER_FLG_RESCHED;
		return;
	}		
	if (ticks > timer->hw.ticks)
		ticks = timer->hw.ticks;
	if (ticks_left != ticks)
		timer->flags |= SND_TIMER_FLG_CHANGE;
	timer->sticks = ticks;
}

void snd_timer_interrupt(snd_timer_t * timer, unsigned long ticks_left)
{
	unsigned long flags, sflags;
	snd_timer_instance_t *ti, *tc = NULL, *tp;
	snd_timer_instance_t ts, tx;
	unsigned long resolution = 0, ticks;

	if (!timer)
		return;
	ts.iprev = NULL;
	ts.inext = NULL;
	spin_lock_irqsave(&timer->lock, flags);
	ti = timer->first;
	while (ti) {
		if (ti->flags & SND_TIMER_IFLG_RUNNING) {
			if (ti->cticks < ticks_left) {
				ti->cticks = 0;
			} else {
				ti->cticks -= ticks_left;
			}
			if (!ti->cticks) {
				if (ti->flags & SND_TIMER_IFLG_AUTO) {
					ti->cticks = ti->ticks;
				} else {
					ti->flags &= ~SND_TIMER_IFLG_RUNNING;
					timer->running--;
				}
				/* remove timer from old queue (tick lost) */
				if (ti->iprev != NULL) {
					ti->iprev->inext = ti->inext;
					if (ti->inext)
						ti->inext->iprev = ti->iprev;
					ti->lost++;
				}
				/* add timer to next queue */
				if (!ts.inext) {
					tc = ts.inext = ti;
					ti->iprev = &ts;
					ti->inext = NULL;
				} else {
					tc->inext = ti;
					ti->iprev = tc;
					tc = ti;
				}
				ti->inext = NULL;
			}
		}
		ti = ti->next;
	}
	if (timer->flags & SND_TIMER_FLG_RESCHED)
		snd_timer_reschedule(timer, ticks_left);
	if (timer->running) {
		if (timer->hw.flags & SND_TIMER_HW_STOP) {
			timer->hw.stop(timer);
			timer->flags |= SND_TIMER_FLG_CHANGE;
		}
		if (!(timer->hw.flags & SND_TIMER_HW_AUTO) ||
		    (timer->flags & SND_TIMER_FLG_CHANGE)) {
			timer->flags &= ~SND_TIMER_FLG_CHANGE;
			timer->hw.start(timer);
		}
	} else {
		timer->hw.stop(timer);
	}
	while (ts.inext != NULL) {
		ti = ts.inext;
		ts.inext = ti->inext;
		if (ts.inext)
			ts.inext->iprev = &ts;
		ti->iprev = ti->inext = NULL;
		tx.inext = tx.iprev = NULL;
		tp = NULL;
		spin_lock_irqsave(&snd_timer_slave, sflags);
		tc = snd_timer_slave_devices;
		while (tc != NULL) {
			if ((tc->flags & SND_TIMER_IFLG_RUNNING) &&
			    tc->slave_type == ti->slave_type &&
			    tc->slave_id == ti->slave_id) {
				if (tc->iprev != NULL) {
					tc->iprev->inext = tc->inext;
					if (tc->inext)
						tc->inext->iprev = tc->iprev;
					tc->lost++;
				}
				if (tx.inext == NULL) {
					tx.inext = tp = tc;
					tc->iprev = &tx;
				} else {
					tp->inext = tc;
					tc->iprev = tp;
					tp = tc;
				}
				tc->inext = NULL;
			}
			tc = tc->next;
		}
		spin_unlock_irqrestore(&snd_timer_slave, sflags);
		ticks = ti->ticks;
		resolution = snd_timer_resolution(ti);
		spin_unlock_irqrestore(&timer->lock, flags);
		if (ti->callback)
			ti->callback(ti, resolution, ticks, ti->callback_data);
		while (tx.inext) {
			spin_lock_irqsave(&snd_timer_slave, sflags);
			tc = tx.inext;
			tx.inext = tc->inext;
			if (tx.inext)
				tx.inext->iprev = &tx;
			tc->iprev = tc->inext = NULL;
			spin_unlock_irqrestore(&snd_timer_slave, sflags);
			if (tc->callback)
				tc->callback(tc, resolution, ticks, tc->callback_data);
		}
		spin_lock_irqsave(&timer->lock, flags);
	}
	spin_unlock_irqrestore(&timer->lock, flags);
}

/*

 */

snd_timer_t *snd_timer_new_device(snd_card_t * card, char *id)
{
	snd_timer_t *timer;

	timer = (snd_timer_t *) snd_kcalloc(sizeof(snd_timer_t), GFP_KERNEL);
	if (!timer)
		return NULL;
	timer->card = card;
	if (id) {
		strncpy(timer->id, id, sizeof(timer->id) - 1);
	}
	timer->lock = SPIN_LOCK_UNLOCKED;
	return timer;
}

int snd_timer_free(snd_timer_t * timer)
{
	if (!timer)
		return -EINVAL;
	if (timer->private_free)
		timer->private_free(timer->private_data);
	snd_kfree(timer);
	return 0;
}

int snd_timer_register(snd_timer_t * timer, int device)
{
	snd_timer_t *timer1, *timer2;

	if (!timer || !timer->hw.start || !timer->hw.stop)
		return -EINVAL;
	if (!(timer->hw.flags & SND_TIMER_HW_SLAVE) &&
	    !timer->hw.resolution)
	    	return -EINVAL;
	if (device & SND_TIMER_DEV_FLG_PCM) {
		if (!timer->card)
			return -EINVAL;
		device = SND_TIMER_TYPE_PCM |
		         (timer->card->number << SND_TIMER_PCM_CARD_SHIFT) |
		         (device & ((SND_TIMER_PCM_DEV_MAX << SND_TIMER_PCM_DEV_SHIFT) | SND_TIMER_PCM_SUBDEV_MAX));
	} else {
		if (timer->card) {
			device &= SND_TIMER_SOUNDCARD_DEV_MAX;
			device |= SND_TIMER_TYPE_SOUNDCARD |
				  (timer->card->number << SND_TIMER_SOUNDCARD_CARD_SHIFT);
		} else {
			device &= SND_TIMER_GLOBAL_MAX;
			device |= SND_TIMER_TYPE_GLOBAL;
		}
	}

	timer->number = device;
	down(&register_mutex);
	if ((timer1 = snd_timer_devices) == NULL) {
		snd_timer_devices = timer;
	} else {
		timer2 = NULL;
		while (timer1) {
			if (timer1->number >= device)
				break;
			timer2 = timer1;
			timer1 = timer1->next;
		}
		if (timer1 && timer1->number == device) {
			up(&register_mutex);
			return -EBUSY;
		}
		if (timer2) {
			timer->next = timer2->next;
			timer2->next = timer;
		} else {
			timer->next = snd_timer_devices;
			snd_timer_devices = timer;
		}
	}
	up(&register_mutex);
	return 0;
}

int snd_timer_unregister(snd_timer_t * timer)
{
	snd_timer_t *timer1;

	if (!timer)
		return -EINVAL;
	if (timer->first) {
		snd_printd("timer 0x%lx is busy?\n", (long)timer);
		return -EBUSY;
	}
	
	down(&register_mutex);
	if ((timer1 = snd_timer_devices) == timer) {
		snd_timer_devices = timer->next;
	} else {
		while (timer1) {
			if (timer1->next == timer)
				break;
			timer1 = timer1->next;
		}
		if (timer1 == NULL) {
			up(&register_mutex);
			return -EINVAL;
		} else {
			timer1->next = timer->next;			
		}
	}
	up(&register_mutex);
	if (timer->private_free)
		timer->private_free(timer->private_data);
	snd_kfree(timer);
	return 0;
}

/* 
 *  System timer
 */

unsigned int snd_timer_system_resolution(void)
{
	return 1000000000L / HZ;
}

static void snd_timer_s_function(unsigned long data)
{
	snd_timer_t *timer = (snd_timer_t *)data;
	snd_timer_interrupt(timer, timer->sticks);
}

static void snd_timer_s_start(snd_timer_t * timer)
{
	struct timer_list *tlist;

	tlist = (struct timer_list *) timer->private_data;
	tlist->expires = jiffies + timer->sticks;
	add_timer(tlist);
}

static void snd_timer_s_stop(snd_timer_t * timer)
{
	struct timer_list *tlist;

	tlist = (struct timer_list *) timer->private_data;
	del_timer(tlist);
	timer->sticks = tlist->expires - jiffies;
}

static struct snd_stru_timer_hardware snd_timer_system =
{
	SND_TIMER_HW_FIRST,	/* flags */
	1000000000L / HZ,	/* resolution in us */
	10000000L,		/* ticks */
	NULL,			/* open */
	NULL,			/* close */
	NULL,			/* resolution */
	snd_timer_s_start,	/* start */
	snd_timer_s_stop	/* stop */
};

static void snd_timer_free_system(void *private_data)
{
	if (private_data)
		snd_kfree(private_data);
}

static int snd_timer_register_system(void)
{
	snd_timer_t *timer;
	struct timer_list *tlist;

	timer = snd_timer_new_device(NULL, "system");
	if (!timer)
		return -ENOMEM;
	strcpy(timer->name, "system timer");
	memcpy(&timer->hw, &snd_timer_system, sizeof(snd_timer_system));
	tlist = (struct timer_list *) snd_kcalloc(sizeof(struct timer_list), GFP_KERNEL);
	if (!tlist) {
		snd_timer_free(timer);
		return -ENOMEM;
	}
	tlist->function = snd_timer_s_function;
	tlist->data = (unsigned long) timer;
	timer->private_data = tlist;
	timer->private_free = snd_timer_free_system;
	return snd_timer_register(timer, SND_TIMER_GLOBAL_SYSTEM);
}

/*
 *  Info interface
 */

static void snd_timer_proc_read(snd_info_buffer_t * buffer, void *private_data)
{
	unsigned long flags;
	snd_timer_t *timer;
	snd_timer_instance_t *ti;

	down(&register_mutex);
	timer = snd_timer_devices;
	while (timer) {
		switch (timer->number & SND_TIMER_TYPE_MAX) {
		case SND_TIMER_TYPE_GLOBAL:
			snd_iprintf(buffer, "G%i: ",
					timer->number & SND_TIMER_GLOBAL_MAX);
			break;
		case SND_TIMER_TYPE_SOUNDCARD:
			snd_iprintf(buffer, "C%i-%i: ",
					SND_TIMER_SOUNDCARD_CARD(timer->number),
					SND_TIMER_SOUNDCARD_DEV(timer->number));
			break;
		case SND_TIMER_TYPE_PCM:
			snd_iprintf(buffer, "P%i-%i-%i: ",
					SND_TIMER_PCM_CARD(timer->number),
					SND_TIMER_PCM_DEV(timer->number),
					SND_TIMER_PCM_SUBDEV(timer->number));
			break;
		default:
			snd_iprintf(buffer, "?%i: ", timer->number);
		}
		snd_iprintf(buffer, "%s :", timer->name);
		if (timer->hw.resolution)
			snd_iprintf(buffer, " %u.%uus (%u ticks)", timer->hw.resolution / 1000, timer->hw.resolution % 1000, timer->hw.ticks);
		if (timer->hw.flags & SND_TIMER_HW_SLAVE)
			snd_iprintf(buffer, " SLAVE");
		snd_iprintf(buffer, "\n");
		spin_lock_irqsave(&timer->lock, flags);
		if (timer->first) {
			ti = timer->first;
			while (ti) {
				snd_iprintf(buffer, "  Client %s : %s : lost interrupts %i\n",
						ti->owner ? ti->owner : "unknown",
						ti->flags & (SND_TIMER_IFLG_START|SND_TIMER_IFLG_RUNNING) ? "running" : "stopped",
						ti->lost);
				ti = ti->next;
			}
		}
		spin_unlock_irqrestore(&timer->lock, flags);
		timer = timer->next;
	}
	up(&register_mutex);
}

/*
 *  Control interface
 */
 
static int snd_timer_control_ioctl(snd_card_t * card, snd_control_t * control,
				   unsigned int cmd, unsigned long arg)
{
	switch (cmd) {
	case SND_CTL_IOCTL_HW_INFO:
		{
			int dev;
			snd_timer_t *timer;
			struct snd_ctl_hw_info *ptr = (struct snd_ctl_hw_info *) arg;
		
			dev = (card->number * 32) + 256;
			down(&register_mutex);
			timer = snd_timer_devices;
			ptr->timerdevs = 0;
			while (timer) {
				if (timer->number >= dev + 32)
					break;
				if (timer->number >= dev)
					ptr->timerdevs++;
				timer = timer->next;
			}
			up(&register_mutex);
			return 0;
		}
	}
	return -EAGAIN;
}

/*
 *  USER SPACE interface
 */

static void snd_timer_user_interrupt(snd_timer_instance_t *timeri,
				     unsigned long resolution,
				     unsigned long ticks,
				     void *data)
{
	unsigned long flags;
	snd_timer_user_t *tu = (snd_timer_user_t *)data;
	snd_timer_read_t *r;
	
	if (tu->qused >= tu->queue_size) {
		tu->overrun++;
	} else {
		spin_lock_irqsave(&tu->qlock, flags);
		r = &tu->queue[tu->qtail++];
		tu->qtail %= tu->queue_size;
		r->resolution = resolution;
		r->ticks = ticks;
		tu->qused++;
		spin_unlock_irqrestore(&tu->qlock, flags);
		wake_up(&tu->qchange_sleep);
	}
}

static int snd_timer_user_open(unsigned short minor, int cardnum, int device,
			       struct file *file)
{
	snd_timer_user_t *tu;
	
	tu = (snd_timer_user_t *)snd_kcalloc(sizeof(*tu), GFP_KERNEL);
	if (!tu)
		return -ENOMEM;
	tu->qlock = SPIN_LOCK_UNLOCKED;
	init_waitqueue_head(&tu->qchange_sleep);
	tu->ticks = 1;
	tu->queue_size = 128;
	tu->queue = (snd_timer_read_t *)snd_kmalloc(tu->queue_size * sizeof(snd_timer_read_t), GFP_KERNEL);
	if (!tu->queue) {
		snd_kfree(tu);
		return -ENOMEM;
	}
	file->private_data = tu;
	MOD_INC_USE_COUNT;
	return 0;
}

static int snd_timer_user_release(unsigned short minor, int cardnum, int device,
				  struct file *file)
{
	snd_timer_user_t *tu;

	if (file->private_data) {
		tu = (snd_timer_user_t *)file->private_data;
		file->private_data = NULL;
		if (tu->timeri)
			snd_timer_close(tu->timeri);
		if (tu->queue)
			snd_kfree(tu->queue);
		snd_kfree(tu);
	}
	MOD_DEC_USE_COUNT;
	return 0;
}

static int snd_timer_user_general_info(snd_timer_general_info_t *_ginfo)
{
	snd_timer_general_info_t ginfo;
	snd_timer_t *timer;
	
	memset(&ginfo, 0, sizeof(ginfo));
	ginfo.count = snd_timer_limit;
	down(&register_mutex);
	timer = snd_timer_devices;
	while (timer) {
		if (timer->number >= 256)
			break;
		if (ginfo.count < timer->number)
			ginfo.count = timer->number + 1;
		timer = timer->next;
	}
	up(&register_mutex);
	if (copy_to_user(_ginfo, &ginfo, sizeof(*_ginfo)))
		return -EFAULT;
	return 0;
} 

static int snd_timer_user_tselect(struct file *file, snd_timer_select_t *_tselect)
{
	snd_timer_user_t *tu;
	snd_timer_select_t tselect;
	char str[32];
	
	tu = (snd_timer_user_t *)file->private_data;
	if (!tu)
		return -EINVAL;
	if (tu->timeri)
		snd_timer_close(tu->timeri);
	if (copy_from_user(&tselect, _tselect, sizeof(tselect)))
		return -EFAULT;
	if (!tselect.slave && tselect.data.number < 0)
		return -EINVAL;
	if (tselect.slave && tselect.data.slave.type == SND_TIMER_STYPE_NONE)
		return -EINVAL;
	sprintf(str, "application %i", current->pid);
	if (!tselect.slave) {
		tu->timeri = snd_timer_open(str, tselect.data.number, 
					    SND_TIMER_STYPE_APPLICATION,
					    current->pid);
	} else {
		tu->timeri = snd_timer_open_slave(str, tselect.data.slave.type,
						  tselect.data.slave.id);
	}
	if (tu->timeri == NULL)
		return -ENODEV;
	tu->timeri->callback = snd_timer_user_interrupt;
	tu->timeri->callback_data = (void *)tu;
	return 0;
}

static int snd_timer_user_info(struct file *file, snd_timer_info_t *_info)
{
	snd_timer_user_t *tu;
	snd_timer_info_t info;
	snd_timer_t *t;
	
	tu = (snd_timer_user_t *)file->private_data;
	if (!tu || !tu->timeri)
		return -EINVAL;
	t = tu->timeri->timer;
	if (!t)
		return -EINVAL;
	memset(&info, 0, sizeof(info));
	if (t->hw.flags & SND_TIMER_HW_SLAVE)
		info.flags |= SND_TIMER_FLG_SLAVE;
	strncpy(info.id, t->id, sizeof(info.id)-1);
	strncpy(info.name, t->name, sizeof(info.name)-1);
	info.ticks = t->hw.ticks;
	info.resolution = t->hw.resolution;
	if (copy_to_user(_info, &info, sizeof(*_info)))
		return -EFAULT;
	return 0;
}

static int snd_timer_user_params(struct file *file, snd_timer_params_t *_params)
{
	unsigned long flags;
	snd_timer_user_t *tu;
	snd_timer_params_t params;
	snd_timer_t *t;
	snd_timer_read_t *tr;
	
	tu = (snd_timer_user_t *)file->private_data;
	if (!tu || !tu->timeri)
		return -EINVAL;
	t = tu->timeri->timer;
	if (!t)
		return -EINVAL;
	if (copy_from_user(&params, _params, sizeof(params)))
		return -EFAULT;
	if (!(t->hw.flags & SND_TIMER_HW_SLAVE) && params.ticks < 1)
		return -EINVAL;
	if (params.queue_size > 0 && (params.queue_size < 32 || params.queue_size > 1024))
		return -EINVAL;
	snd_timer_stop(tu->timeri);
	spin_lock_irqsave(&t->lock, flags);
	if (params.flags & SND_TIMER_PSFLG_AUTO) {
		tu->timeri->flags |= SND_TIMER_IFLG_AUTO;
	} else {
		tu->timeri->flags &= ~SND_TIMER_IFLG_AUTO;
	}
	spin_unlock_irqrestore(&t->lock, flags);
	if (params.queue_size > 0 && tu->queue_size != params.queue_size) {
		tr = (snd_timer_read_t *)snd_kmalloc(params.queue_size * sizeof(snd_timer_read_t), GFP_KERNEL);
		if (tr) {
			snd_kfree(tu->queue);
			tu->queue_size = params.queue_size;
			tu->queue = tr;
		}
	}
	if (t->hw.flags & SND_TIMER_HW_SLAVE) {
		tu->ticks = 1;
	} else {
		tu->ticks = params.ticks;
	}
	return 0;
}

static int snd_timer_user_status(struct file *file, snd_timer_status_t *_status)
{
	unsigned long flags;
	snd_timer_user_t *tu;
	snd_timer_status_t status;
	
	tu = (snd_timer_user_t *)file->private_data;
	if (!tu || !tu->timeri)
		return -EINVAL;
	memset(&status, 0, sizeof(status));
	status.resolution = snd_timer_resolution(tu->timeri);
	status.lost = tu->timeri->lost;
	status.overrun = tu->overrun;
	spin_lock_irqsave(&tu->qlock, flags);
	status.queue_size = tu->queue_size;
	status.queue = tu->qused;
	spin_unlock_irqrestore(&tu->qlock, flags);
	if (copy_to_user(_status, &status, sizeof(status)))
		return -EFAULT;
	return 0;
}

static int snd_timer_user_start(struct file *file)
{
	int err;
	snd_timer_user_t *tu;
		
	tu = (snd_timer_user_t *)file->private_data;
	if (!tu || !tu->timeri)
		return -EINVAL;
	snd_timer_stop(tu->timeri);
	tu->timeri->lost = 0;
	return (err = snd_timer_start(tu->timeri, tu->ticks)) < 0 ? err : 0;
}

static int snd_timer_user_stop(struct file *file)
{
	int err;
	snd_timer_user_t *tu;
		
	tu = (snd_timer_user_t *)file->private_data;
	if (!tu || !tu->timeri)
		return -EINVAL;
	return (err = snd_timer_stop(tu->timeri)) < 0 ? err : 0;
}

static int snd_timer_user_continue(struct file *file)
{
	int err;
	snd_timer_user_t *tu;
		
	tu = (snd_timer_user_t *)file->private_data;
	if (!tu || !tu->timeri)
		return -EINVAL;
	tu->timeri->lost = 0;
	return (err = snd_timer_continue(tu->timeri)) < 0 ? err : 0;
}

static int snd_timer_user_ioctl(struct file *file,
				unsigned int cmd,
				unsigned long arg)
{
	snd_timer_user_t *tu;
	
	tu = (snd_timer_user_t *)file->private_data;
	if (!tu)
		return -EINVAL;
	switch (cmd) {
	case SND_TIMER_IOCTL_PVERSION:
		return snd_ioctl_out((long *)arg, SND_TIMER_VERSION);
	case SND_TIMER_IOCTL_GINFO:
		return snd_timer_user_general_info((snd_timer_general_info_t *)arg);
	case SND_TIMER_IOCTL_SELECT:
		return snd_timer_user_tselect(file, (snd_timer_select_t *)arg);
	case SND_TIMER_IOCTL_INFO:
		return snd_timer_user_info(file, (snd_timer_info_t *)arg);
	case SND_TIMER_IOCTL_PARAMS:
		return snd_timer_user_params(file, (snd_timer_params_t *)arg);
	case SND_TIMER_IOCTL_STATUS:
		return snd_timer_user_status(file, (snd_timer_status_t *)arg);
	case SND_TIMER_IOCTL_START:
		return snd_timer_user_start(file);
	case SND_TIMER_IOCTL_STOP:
		return snd_timer_user_stop(file);
	case SND_TIMER_IOCTL_CONTINUE:
		return snd_timer_user_continue(file);
	}
	return -ENXIO;
}

static long snd_timer_user_read(struct file *file, char *buffer, long count)
{
	unsigned long flags;
	snd_timer_user_t *tu;
	long result = 0;
	
	tu = (snd_timer_user_t *)file->private_data;
	if (!tu)
		return -EINVAL;
	while (count - result >= sizeof(snd_timer_read_t)) {
		spin_lock_irqsave(&tu->qlock, flags);
		if (!tu->qused) {
			spin_unlock_irqrestore(&tu->qlock, flags);
			break;
		}
		if (copy_to_user(buffer, &tu->queue[tu->qhead++], sizeof(snd_timer_read_t))) {
			spin_unlock_irqrestore(&tu->qlock, flags);
			return -EFAULT;
		}
		tu->qhead %= tu->queue_size;
		tu->qused--;
		spin_unlock_irqrestore(&tu->qlock, flags);
		result += sizeof(snd_timer_read_t);
		buffer += sizeof(snd_timer_read_t);
	}
	return result;
}

static unsigned int snd_timer_user_poll(struct file *file, poll_table * wait)
{
        unsigned int mask;
        snd_timer_user_t *tu;

        tu = (snd_timer_user_t *) file->private_data;
        if (!tu)
                return -EIO;

        poll_wait(file, &tu->qchange_sleep, wait);
	
	mask = 0;
	if (tu->qused)
		mask |= POLLIN | POLLRDNORM;

	return mask;
}

static snd_minor_t snd_timer_reg =
{
	"timer",		/* comment */
	NULL,			/* reserved */

	NULL,			/* unregister */

	NULL,			/* lseek */
	snd_timer_user_read,	/* read */
	NULL,			/* write */
	snd_timer_user_open,	/* open */
	snd_timer_user_release,	/* release */
	snd_timer_user_poll,	/* poll */
	snd_timer_user_ioctl,	/* ioctl */
	NULL			/* mmap */
};

/*
 *  ENTRY functions
 */

static snd_info_entry_t *snd_timer_proc_entry = NULL;

int init_module(void)
{
	int err;
	snd_info_entry_t *entry;

#ifdef CONFIG_SND_OSSEMUL
	snd_oss_info_register(SND_OSS_INFO_DEV_TIMERS, SND_CARDS - 1, "system timer");
#endif
	if ((entry = snd_info_create_entry(NULL, "timers")) != NULL) {
		entry->t.text.read_size = SND_TIMER_DEVICES * 128;
		entry->t.text.read = snd_timer_proc_read;
		if (snd_info_register(entry) < 0) {
			snd_info_free_entry(entry);
			entry = NULL;
		}
	}
	snd_timer_proc_entry = entry;
	snd_control_register_ioctl(snd_timer_control_ioctl);
	if ((err = snd_timer_register_system()) < 0)
		snd_printk("unable to register system timer (%i)\n", err);
	if ((err = snd_register_device(SND_DEVICE_TYPE_TIMER,
					NULL, 0, &snd_timer_reg, "timer"))<0)
		snd_printk("unable to register timer device (%i)\n", err);
	return 0;
}

void cleanup_module(void)
{
	snd_unregister_device(SND_DEVICE_TYPE_TIMER, NULL, 0);
	/* unregister the system timer */
	if (snd_timer_devices)
		snd_timer_unregister(snd_timer_devices);
	snd_control_unregister_ioctl(snd_timer_control_ioctl);
	if (snd_timer_proc_entry) {
		snd_info_unregister(snd_timer_proc_entry);
		snd_timer_proc_entry = NULL;
	}
#ifdef CONFIG_SND_OSSEMUL
	snd_oss_info_unregister(SND_OSS_INFO_DEV_TIMERS, SND_CARDS - 1);
#endif
}

EXPORT_SYMBOL(snd_timer_open);
EXPORT_SYMBOL(snd_timer_open1);
EXPORT_SYMBOL(snd_timer_open_slave);
EXPORT_SYMBOL(snd_timer_close);
EXPORT_SYMBOL(snd_timer_resolution);
EXPORT_SYMBOL(snd_timer_start);
EXPORT_SYMBOL(snd_timer_stop);
EXPORT_SYMBOL(snd_timer_continue);
EXPORT_SYMBOL(snd_timer_new_device);
EXPORT_SYMBOL(snd_timer_free);
EXPORT_SYMBOL(snd_timer_register);
EXPORT_SYMBOL(snd_timer_unregister);
EXPORT_SYMBOL(snd_timer_interrupt);
EXPORT_SYMBOL(snd_timer_system_resolution);
