/*
 **********************************************************************
 *     timer.c
 *     Copyright (C) 1999, 2000 Creative Labs, inc.
 *
 **********************************************************************
 *
 *     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 "hwaccess.h"

static int sblive_timerIrqCallback(unsigned long event, unsigned long refdata, unsigned long param)
{
	struct sblive_hw *sb_hw = (struct sblive_hw *) refdata;
	struct emu_timer *t = dlFirstNode(&sb_hw->emu_timer);
	
	while (t != NULL) 
	{
		if (t->flags & TIMER_STATE_ENABLED) 
		  t->callback(0, t->callback_data, 0);
		t = dlNextNode(t);
	}
	
	return CTSTATUS_SUCCESS;
}

static int sblive_timerSetDelay(struct sblive_hw *sb_hw)
{
	struct emu_timer *t = dlFirstNode(&sb_hw->emu_timer);
	u32 delay = ~0;
	
	while (t != NULL) 
	{
		if ((t->flags & TIMER_STATE_ENABLED) && (t->maxdelay < delay)) 
		  delay = t->maxdelay;
		t = dlNextNode(t);		
	}
	
	if (delay == ~0)
	  sblive_irqmgrDisableIrq(sb_hw, INTE_INTERVALTIMERENB);
	else 
	{
		if (delay < 5)
		  delay = 5;
		else if (delay >= 1024)
		  delay = 0;
		WRITE_FN0(sb_hw, TIMR_RATE, delay);
		sblive_irqmgrEnableIrq(sb_hw, INTE_INTERVALTIMERENB);
	}
	
	return CTSTATUS_SUCCESS;
}

int sblive_timerInit(struct sblive_hw *sb_hw)
{
	sb_hw->emu_timer = NULL;
	sblive_irqmgrInstallIrqHandler(sb_hw, IRQTYPE_TIMER, sblive_timerIrqCallback, (unsigned long) sb_hw);
	return CTSTATUS_SUCCESS;
}

int sblive_timerExit(struct sblive_hw *sb_hw)
{
	struct emu_timer *t;
	unsigned long flags;

	sblive_irqmgrDisableIrq(sb_hw, INTE_INTERVALTIMERENB);
	sblive_irqmgrUninstallIrqHandler(sb_hw, IRQTYPE_TIMER);
	
	while (sb_hw->emu_timer != NULL) 
	{
		t = sb_hw->emu_timer;

		spin_lock_irqsave(&sb_hw->emu_lock, flags);
		
		dlDelNode(&sb_hw->emu_timer, t);
		
		spin_unlock_irqrestore(&sb_hw->emu_lock, flags);
		
		kfree(t);
	}

	return CTSTATUS_SUCCESS;
}

int sblive_timerInstallHandler(struct sblive_hw *sb_hw, CALLBACKFN callback, unsigned long callback_data, struct emu_timer **timer)
{
	struct emu_timer *t;
	unsigned long flags;

	t = (struct emu_timer *) kmalloc(sizeof(struct emu_timer), GFP_KERNEL);
	if (t == NULL) 
	{
		DPF("struct emu_timer alloc fail\n"); 
		return CTSTATUS_NOMEMORY;
	}
	
	t->maxdelay = ~0;
	t->flags = 0;
	t->callback = callback;
	t->callback_data = callback_data;
	t->sb_hw = sb_hw;
	
	spin_lock_irqsave(&sb_hw->emu_lock, flags);
	
	dlAddNode(&sb_hw->emu_timer, t);
	
	spin_unlock_irqrestore(&sb_hw->emu_lock, flags);
	
	*timer = t;

	return CTSTATUS_SUCCESS;
}

int sblive_timerUninstallHandler(struct emu_timer *timer)
{
	unsigned long flags;

	spin_lock_irqsave(&timer->sb_hw->emu_lock, flags);
	
	dlDelNode(&timer->sb_hw->emu_timer, timer);
	
	spin_unlock_irqrestore(&timer->sb_hw->emu_lock, flags);
	
	kfree(timer);

	return CTSTATUS_SUCCESS;
}

int sblive_timerSetMaxDelay(struct emu_timer *timer, u32 maxdelay)
{
	timer->maxdelay = maxdelay;
	if (timer->flags & TIMER_STATE_ENABLED)
	  return sblive_timerSetDelay(timer->sb_hw);

	return CTSTATUS_SUCCESS;
}

int sblive_timerEnable(struct emu_timer *timer)
{
	timer->flags |= TIMER_STATE_ENABLED;

	return sblive_timerSetDelay(timer->sb_hw);
}

int sblive_timerDisable(struct emu_timer *timer)
{
	timer->flags &= ~TIMER_STATE_ENABLED;

	return sblive_timerSetDelay(timer->sb_hw);
}
