/****************************************************************************/
/*	Copyright (c) 2009, Bendor Research Pty. Ltd. All rights reserved.		*/
/*	This software is released under the GNU General Public License, version	*/
/*	3, with the exemptions that 1) the header files installed by this 	 	*/
/*	package can be included in closed-source code and 2) the libraries		*/
/*	built by this package can be statically linked into a closed-source 	*/
/*	binary. See the file LICENSE for the details.							*/
/*	This software is provided "AS IS", without any warranty or guarantee.	*/
/*	By using this software you acknowledge that neither Bendor Research nor	*/
/*	any of its personnel or affiliates can be held liable for any damages,	*/
/*	including, but not limited to, loss of business, loss of money, loss of */
/*	life, loss of reputation, arising out of this software being faulty or	*/
/*	not performing as advertised, even if Bendor Research was aware of such	*/
/*	faults in the software. You use this software at your own risk, any and */
/*	all liabilities that arise out of your using this software are strictly	*/
/*	yours.																	*/
/****************************************************************************/

#include "kernel.h"
#include <misc/multiply.h>

/****************************************************************************/
/*					Public API functions									*/
/****************************************************************************/

/** @addtogroup bx_time */
/** @{ */

/**
*	Timer tick function, a function from the timer ISR.
*
*	The function receives the number of microsecs that elapsed since
*	it was called the last time. It then handles all the software
*	timers and also maintains the uptime and absolute time in the
*	system. If your timer interrupt period is not an exact multiple
*	of a microsec, see the example code for a solution to keep your
*	timing precise.
*
*	@param	usec	The number of microseconds elapsed since the last call.
*					The number <b>must</b> be at least 1us. Note that this
*					is a signed number, so the max. value is 0x7fffffff
*					microsecs.
*
*	@retval	>0				Success
*	@retval	BX_ERR_CONTEXT	The function was not called from an ISR
*	@retval	BX_ERR_PARAM	The argument was not inside the limits
*
*	@note
*		This function <b>must</b> be called from a real IRQ ISR context.
*/

int		BX_TimerTick( int usec )
{
BX_STAT		*stat = &_bx_state;
unsigned long long	temp;

	if ( _bx_state.exec[ BX_EXEC_MOD ] != BX_MODE_IRQ )

		return( BX_ERR_CONTEXT );

	if ( usec <= 0 )

		return( BX_ERR_PARAM );

	// Maintain the uptime

	stat->usec += usec;

	while ( stat->usec >= 1000000 ) {

		temp = umul32x32_64( stat->usec, 0x431bde83 ) >> 50;
		stat->secs += temp;
		stat->usec -= 1000000 * temp;
	}

	// If we have a timer waiting, check if it expired

	if ( stat->tlk1 || stat->tlk2 ) {

		// The timer queue is locked. Accumulate the
		// elapsed time and return immediately.

		stat->tacc += usec;
	}
	else {

		// The timer queue is not locked

		if ( stat->tqhd ) {

			// We have a timer on our queue
			// If we have accumulated time since the last check on
			// the queue, we have to take that into account as well.

			usec += stat->tacc;

			if ( stat->tqhd->wait <= (unsigned) usec ) {

				// The timer has expired. Indicate that we're running
				// the timer queue and initiate the processing

				stat->tlk1 = 1;
				_bx_QueueEvent( _bx_CpsrGet(), _bx_TimerExpired,
							    stat, (void *) usec );
			}
			else {

				// The timer have not expired yet.
				// Just decrement it and we're done

				stat->tqhd->wait -= usec;
			}
		}

		// Clear all accumulated time

		stat->tacc = 0;
	}

	return( 1 );
}

/** @} */

/****************************************************************************/
/*					Library internal public functions						*/
/****************************************************************************/

/*
*	A timer expired
*	~~~~~~~~~~~~~~~
*	This function is called through the event queue handler when the
*	timer interrupt routine detects that the first timer on the queue
*	expired. The first parameter is the pointer to the kernel's state
*	structure and the second is the number of microseconds elapsed since
*	the time base (i.e. the 0 point of the timer queue).
*/

void	_bx_TimerExpired( void *par1, void *par2 )
{
BX_STAT			*stat = par1;
unsigned		step = (unsigned) par2;
BX_DISR_FUNC	func;
BX_TIME			*tptr;
unsigned		cpsr;
unsigned		load;

	cpsr = _bx_IrqDis();

	while ( step ) {

		// Get the first timer on the queue

		if ( ! ( tptr = stat->tqhd ) ) {

			// There are no timers left

			break;
		}

		if ( tptr->wait > step ) {

			// The timer still has time. Decrement it and leave.

			tptr->wait -= step;
			break;
		}
		else {

			// The first timer expired. Remove it from the queue.

			if ( ( stat->tqhd = tptr->next ) )

				tptr->next->prev = NULL;
			else
				stat->tqtl = NULL;

			// Indicate that it's not on the queue

			tptr->runs = 0;

			// Work out the remaining time

			step -= tptr->wait;

			if ( tptr->stat == TIMER_CANCEL ) {

				// The timer is marked for cancellation.
				// Do not call its callback.

				tptr->stat = TIMER_UNUSED;

				// Give interrupts a chance

				_bx_IrqEnaSys( cpsr );
			}
			else {

				// If the timer is marked for re-load, we don't execute its
				// function, otherwise we do.

				if ( tptr->stat == TIMER_RELOAD ) {

					func = NULL;
					load = tptr->load;
				}
				else {

					func = tptr->func;
					par1 = tptr->par1;
					par2 = tptr->par2;
					load = 0;
				}

				// Mark the timer currently being unused

				tptr->stat = TIMER_UNUSED;

				if ( ! load ) load = tptr->peri;

				if ( load ) {

					// The timer has a reload value, reload it with that.
					// We re-enable the interrupts for that.

					tptr->load = load;
					tptr->stat = TIMER_RELOAD;
					_bx_IrqEnaSys( cpsr );
					_bx_TimerProcess( tptr, (void *) 0 );
				}

				// Now call the associated callback function, with
				// interrupts enabled

				_bx_IrqEnaSys( cpsr );
				if ( func ) (*func)( par1, par2 );
			}
		}

		// Make sure the interrupt is disabled

		cpsr = _bx_IrqDis();

		// If we collected accumulated time while we were running, we'd
		// better get it now so that we can save a coming back very soon

		if ( stat->tacc ) {

			step += stat->tacc;
			stat->tacc = 0;
		}
	}

	// Unlock the queue and leave. Interrupts are disabled now, but they
	// will be enabled as soon as we return.

	stat->tlk1 = 0;
}

