/******************************************************************************
 *
 * Name:	sktimer.c
 * Project:	SysKonnect SK-9Dxx Gigabit Ethernet
 * Version:	$Revision: 1.1 $
 * Date:	$Date: 2001/06/05 08:28:25 $
 * Purpose:	High level timer functions.
 *
 ******************************************************************************/

/******************************************************************************
 *
 *	(C)Copyright 2001 SysKonnect GmbH.
 *
 *	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.
 *
 *	The information in this file is provided "AS IS" without warranty.
 *
 ******************************************************************************/

/******************************************************************************
 *
 * History:
 *
 *	$Log: sktimer.c,v $
 *	Revision 1.1  2001/06/05 08:28:25  rassmann
 *	First public version.
 *	
 *
 ******************************************************************************/

/******************************************************************************
 *
 * Description:
 *
 * This module is intended to manage high-level timers.
 *
 * Include File Hierarchy:
 *
 *	"skdrv1st.h"
 *	"skdrv2nd.h"
 *
 ******************************************************************************/

static const char SysKonnectFileId[] =
	"@(#) $Id: sktimer.c,v 1.1 2001/06/05 08:28:25 rassmann Exp $ (C) SysKonnect.";

#include "h/skdrv1st.h"		/* Driver-specific definitions */
#include "h/skqueue.h"
#include "h/skdrv2nd.h"		/* Adapter control and driver-specific defs. */


/* Forward declaration */
static void TimerDone(SK_AC *pAC, SK_IOC IoC, int Restart);


/******************************************************************************
 * Init the timer module
 *
 */
void	SkTimerInit(
SK_AC	*pAC, 		/* Pointer to Adapter Context */
SK_IOC	IoC, 		/* I/O Context */
int		Level)		/* Init Level */
{
	switch (Level) {
	case SK_INIT_DATA:
		pAC->Tim.StQueue = NULL;
		SkOsTimerInit(pAC, IoC);
		break;

	case SK_INIT_IO:
		SkTimerDone(pAC, IoC);
		break;

	case SK_INIT_RUN:
		break;

	default:
		break;
	}
}	/* SkTimerInit */


/******************************************************************************
 * Stops a high level timer
 * - If a timer is not in the queue the function returns normally, too.
 */
void	SkTimerStop(
SK_AC		*pAC, 		/* Pointer to Adapter Context */
SK_IOC		IoC, 		/* I/O Context */
SK_TIMER	*pTimer)	/* Pointer to Timer to be started */
{
	SK_TIMER	**ppTimPrev;
	SK_TIMER	*pTm;

	/* Remove timer from queue. */
	pTimer->TmActive = SK_FALSE;
#ifdef SK_OSTIMER_STOPPABLE
	if (pAC->Tim.StQueue == pTimer && pTimer->TmNext == NULL) {
		/* This was the only timer. */
		SkOsTimerStop(pAC, IoC);
	}
#endif	/* SK_OSTIMER_STOPPABLE */

	for (ppTimPrev = &pAC->Tim.StQueue; (pTm = *ppTimPrev);
		ppTimPrev = &pTm->TmNext ) {
		if (pTm == pTimer) {
			/*
			 * Timer found in queue.
			 * Dequeue it and correct delta of the next timer.
			 */
			*ppTimPrev = pTm->TmNext;

			if (pTm->TmNext != NULL) {
				/* Correct delta of next timer in queue. */
				pTm->TmNext->TmDelta += pTm->TmDelta;
			}
			return;
		}
	}
}	/* SkTimerStop */


/******************************************************************************
 * Start a high level software timer
 *
 */
void	SkTimerStart(
SK_AC		*pAC, 		/* Pointer to Adapter Context */
SK_IOC		IoC, 		/* I/O Context */
SK_TIMER	*pTimer, 	/* Pointer to Timer to be started */
SK_U32		Time, 		/* Time value */
SK_U32		Class, 		/* Event Class for this timer */
SK_U32		Event, 		/* Event Value for this timer */
SK_EVPARA	Para)		/* Event Parameter for this timer */
{
	SK_TIMER	**ppTimPrev;
	SK_TIMER	*pTm;
	SK_U32		Delta;

	Time /= SK_USEC_PER_TICK;
	if (Time == 0) {
		Time = 1;
	}

	SkTimerStop(pAC, IoC, pTimer);

	pTimer->TmClass = Class;
	pTimer->TmEvent = Event;
	pTimer->TmPara = Para;
	pTimer->TmActive = SK_TRUE;

	if (pAC->Tim.StQueue == NULL) {
		/* First Timer to be started. */
		pAC->Tim.StQueue = pTimer;
		pTimer->TmNext = 0;
		pTimer->TmDelta = Time;
#ifdef SK_OSTIMER_STOPPABLE
		SkOsTimerStart(pAC, IoC, Time);
#endif	/* SK_OSTIMER_STOPPABLE */
		return;
	}

	/* Timer correction. */
	TimerDone(pAC, IoC, 0);

	/* Find position in queue. */
	Delta = 0;
	for (ppTimPrev = &pAC->Tim.StQueue; (pTm = *ppTimPrev) != NULL;
		ppTimPrev = &pTm->TmNext ) {
		if (Delta + pTm->TmDelta > Time) {
			/* Position found. Here the timer needs to be inserted. */
			break;
		}
		Delta += pTm->TmDelta;
	}

	/* Insert in queue. */
	*ppTimPrev = pTimer;
	pTimer->TmNext = pTm;
	pTimer->TmDelta = Time - Delta;

	if (pTm != NULL) {
		/* There is a next timer -> correct its Delta value. */
		pTm->TmDelta -= pTimer->TmDelta;
	}

#ifdef SK_OSTIMER_STOPPABLE
	/* Restart with first. */
	SkOsTimerStart(pAC, IoC, pAC->Tim.StQueue->TmDelta);
#endif	/* SK_OSTIMER_STOPPABLE */
}	/* SkTimerStart */


/******************************************************************************
 * Timer fired
 *
 */
void	SkTimerDone(
SK_AC	*pAC, 		/* Pointer to Adapter Context */
SK_IOC	IoC)		/* I/O Context */
{
	TimerDone(pAC, IoC, 1);
}	/* SkTimerDone */


/******************************************************************************
 *
 *
 */
static void	TimerDone(
SK_AC	*pAC, 		/* Pointer to Adapter Context */
SK_IOC	IoC, 		/* I/O Context */
int		Restart)	/* Do we need to restart the Hardware timer? */
{
	SK_TIMER	**ppLast;	/* Next field of last timer to be dequeued */
	SK_TIMER	*pTComp;	/* Timer completed now */
	SK_TIMER	*pTm;
	SK_BOOL		Done;
	SK_U32		Delta;

	Done = SK_FALSE;
	Delta = (SK_U32)SkOsGetTime(pAC);
	ppLast = &pAC->Tim.StQueue;
	pTm = pAC->Tim.StQueue;
	while (pTm != NULL && !Done) {
		if (Delta >= pTm->TmDelta) {
			/* Timer ran out. */
			pTm->TmActive = SK_FALSE;
			Delta -= pTm->TmDelta;
			ppLast = &pTm->TmNext;
			pTm = pTm->TmNext;
		} else {
			/* We found the first timer that did not run out. */
			pTm->TmDelta -= Delta;
			Done = SK_TRUE;
		}
	}
	*ppLast = NULL;

	/*
	 * pTm points to the first Timer that did not run out.
	 * StQueue points to the first Timer that run out.
	 */

	for (pTComp = pAC->Tim.StQueue; pTComp != NULL; pTComp = pTComp->TmNext) {
		SkEventQueue(pAC, pTComp->TmClass, pTComp->TmEvent, pTComp->TmPara);
	}

	/* Set head of timer queue to the first timer that did not run out. */
	pAC->Tim.StQueue = pTm;

#ifdef SK_OSTIMER_STOPPABLE
	if (Restart && pAC->Tim.StQueue != NULL) {
		/* Restart OS timer. */
		SkOsTimerStart(pAC, IoC, pAC->Tim.StQueue->TmDelta);
	}
#endif	/* SK_OSTIMER_STOPPABLE */
}	/* TimerDone */

