/***************************************************************************
*	NAME:  TIMER.C  $Revision: 1.3 $
**	COPYRIGHT:
**	"Copyright (c) 1994,1995 by e-Tek Labs"
**
**       "This software is furnished under a license and may be used,
**       copied, or disclosed only in accordance with the terms of such
**       license and with the inclusion of the above copyright notice.
**       This software or any other copies thereof may not be provided or
**       otherwise made available to any other person. No title to and
**       ownership of the software is hereby transfered."
****************************************************************************
* $Log: timer.c $
* Revision 1.3  1995/11/21 17:28:57  sdsmith
* Changes for new Windows init.
* Revision 1.2  1995/10/13 17:29:28  mleibow
* Added software virtual timer code from MIDI engine so that Effects engine
* could also have access to these functions.
* Revision 1.1  1995/02/23 11:07:55  unknown
* Initial revision
***************************************************************************/

#include <dos.h>
#include "iw.h"
#include "iwl.h"
#include "globals.h"

static int adlib_timer_mask;
extern unsigned char iwl_adlib_control;

/* virtual timer structures */
static struct iwl_list active_cs_timers;
static struct timer_struct cs_timer_struct[MAX_CS_TIMERS];

/***************************************************************************

FUNCTION DEFINTION:
iwl_timer_init - initialize timer control data

NOTE: part of kernel auto-init feature

RETURNS: int - IW_OK
*/
int iwl_timer_init(void)
{
    struct timer_struct RFAR *ts;
    int i;

    adlib_timer_mask = 0;
    iwl_adlib_control = 0;
    iwl_InitList(&active_cs_timers);
    for (i=0, ts = cs_timer_struct; i < MAX_CS_TIMERS; i++, ts++) {
	ts->status = 0;
    }
#ifdef IWL_USE_SYNTH_AS_TIMER
    if (iw_add_handler(IW_IRQ_HANDLER_VOICE, (int (RFAR *)(void))voice_handler)) {
	return(IW_NO_MORE_HANDLERS);
    }
#endif
    return(IW_OK);
}

void iwl_timer_deinit(void)
{
#ifdef IWL_USE_SYNTH_AS_TIMER
    iw_remove_handler(IW_IRQ_HANDLER_VOICE, (int (RFAR *)(void))voice_handler);
#endif
}

/***************************************************************************

FUNCTION DEFINTION:
iw_enable_timer2 - set the 320uS timer to go off

DESCRIPTION:
iw_enable_timer2 sets the 320uS timer to generate an interrupt after
a certain amount of time has gone by.  The resolution parameter is
used to determine the number of timer ticks that will occur before the 
interrupt is generated.  Each tick of timer 2 is 320uS.

When the interrupt is generated the handler funtion pointed to by the
callback parameter is called.

RETURNS: int - IW_OK
               IW_NO_MORE_HANDLERS if timer is already in use
*/
int	iw_enable_timer2(
  int (RFAR *callback)(void),  /* function to call when timer goes off */
  int resolution)              /* number of 320uS ticks before interrupt */
{
	int stat;

	ENTER;
	stat = iw_add_handler(IW_IRQ_HANDLER_TIMER2, (int (RFAR *)(void))callback);
	if (stat) {
	    LEAVE;
	    return(stat);
	}
	OS_OUTPORTB(iwl_register_select, ADLIB_TIMER2);
	OS_OUTPORTB(iwl_data_high, resolution);
	iwl_adlib_control |= 0x8;
	OS_OUTPORTB(iwl_register_select, ADLIB_CONTROL);
	OS_OUTPORTB(iwl_data_high, iwl_adlib_control);
	/* Start timer 2. */
	adlib_timer_mask |= 0x02;
	OS_OUTPORTB(iwl_timer_control, 0x04); /* adlib register 4 */
	OS_OUTPORTB(iwl_timer_data, adlib_timer_mask);		
	LEAVE;
	return(IW_OK);
}

/***************************************************************************

FUNCTION DEFINTION:
iw_enable_timer1 - set the 80uS timer to go off

DESCRIPTION:
iw_enable_timer1 sets the 80uS timer to generate an interrupt after
a certain amount of time has gone by.  The resolution parameter is
used to determine the number of timer ticks that will occur before the 
interrupt is generated.  Each tick of timer 1 is 80uS.

When the interrupt is generated the handler funtion pointed to by the
callback parameter is called.

RETURNS: int - IW_OK
               IW_NO_MORE_HANDLERS if timer is already in use
*/
int	iw_enable_timer1(
  int (RFAR *callback)(void),  /* function to call when timer goes off */
  int resolution)              /* number of 80uS ticks before interrupt */
{
	int stat;

	ENTER;
	stat = iw_add_handler(IW_IRQ_HANDLER_TIMER1, (int (RFAR *)(void))callback);
	if (stat) {
	    LEAVE;
	    return(stat);
	}
	OS_OUTPORTB(iwl_register_select, ADLIB_TIMER1);
	OS_OUTPORTB(iwl_data_high, resolution);
	iwl_adlib_control |= 0x4;
	OS_OUTPORTB(iwl_register_select, ADLIB_CONTROL);
	OS_OUTPORTB(iwl_data_high, iwl_adlib_control);
	/* Start timer 1. */
	adlib_timer_mask |= 0x01;
	OS_OUTPORTB(iwl_timer_control, 0x04); /* adlib register 4 */
	OS_OUTPORTB(iwl_timer_data, adlib_timer_mask);		
	LEAVE;
	return(IW_OK);
}

void iw_change_timer1(int resolution)
{
    OS_OUTPORTB(iwl_register_select, ADLIB_TIMER1);
    OS_OUTPORTB(iwl_data_high, resolution);
}

void iw_change_timer2(int resolution)
{
    OS_OUTPORTB(iwl_register_select, ADLIB_TIMER2);
    OS_OUTPORTB(iwl_data_high, resolution);
}

/***************************************************************************

FUNCTION DEFINTION:
iw_disable_timer1 - shut off the 80uS timer 

RETURNS: void
*/
void	iw_disable_timer1(void)
{
	ENTER;
	iw_remove_handler(IW_IRQ_HANDLER_TIMER1, 0L);
	iwl_adlib_control &= ~0x4;
	adlib_timer_mask &= ~0x1;
	OS_OUTPORTB(iwl_register_select, ADLIB_CONTROL);
	OS_OUTPORTB(iwl_data_high, iwl_adlib_control);
	LEAVE;
}

/***************************************************************************

FUNCTION DEFINTION:
iw_disable_timer2 - shut off the 320uS timer 

RETURNS: void
*/
void	iw_disable_timer2(void)
{
	ENTER;
	iw_remove_handler(IW_IRQ_HANDLER_TIMER2, 0L);
	iwl_adlib_control &= ~0x8;
	adlib_timer_mask &= ~0x2;
	OS_OUTPORTB(iwl_register_select, ADLIB_CONTROL);
	OS_OUTPORTB(iwl_data_high, iwl_adlib_control);
	LEAVE;
}

/* local timer functions */
static struct iwl_list active_cs_timers;
static struct timer_struct cs_timer_struct[MAX_CS_TIMERS];

/****************************************************************************/
/* NOTE: These timer functions have a side effect of changing the voice     */
/* page index register.  Make sure that your code resets the page register, */
/* if necessary, after using these calls.                                   */
/****************************************************************************/
#ifdef IWL_USE_SYNTH_AS_TIMER
static int timer_voice=-1;
static int (RFAR *timer_callback_addr)(void);
static int voice_handler(int voice)
{
    if (voice == timer_voice) {
	(*timer_callback_addr)();
	return(1);
    }
    return(0);
}
void iwl_start_synth_timer(int (RFAR *callback_addr)(void), unsigned short resolution)
{
    unsigned long sample_pos;

    ENTER;
    timer_callback_addr = callback_addr;
    timer_voice = iw_allocate_voice(0, 0); /* allocate permanent voice */
    IWL_SET_PAGE(timer_voice);
    iwl_set_addr_regs(timer_voice, 0, SET_ACC_LOW, SET_ACC_HIGH);
    iwl_set_addr_regs(timer_voice, 0, SET_START_LOW, SET_START_HIGH);
    /* (256 - resolution) is period of timer in 320 microsecond intervals. */
    /* convert to 1/44100 second intervals */
    sample_pos = ((256 - resolution) * 44100) / 3125;
    iwl_set_addr_regs(timer_voice, sample_pos, SET_END_LOW, SET_END_HIGH);
    IWL_OUT_W(SET_FREQUENCY, 1024);
    IWL_OUT_CONTROL(SET_VOLUME_CONTROL, IWL_STOP);
    IWL_OUT_CONTROL(SET_CONTROL, IWL_IRQE|IWL_LPE);
    LEAVE;
}
void iwl_note_stop_synth_timer(void)
{
    ENTER;
    IWL_SET_PAGE(timer_voice);
    IWL_OUT_CONTROL(SET_CONTROL, IWL_STOP);
    iw_free_voice(timer_voice);
    timer_voice = -1;
    LEAVE;
}
#endif

static char in_cs_handler = 0;

static int RFAR cs_handler(void)
{
    struct timer_struct RFAR *ts, RFAR *nts;

    /* make sure calls to iwl_stop_cs_timer() are ignored while in this loop */
    in_cs_handler = 1;
    for (ts=(struct timer_struct RFAR *)active_cs_timers.head; ts != 0; ts = (struct timer_struct RFAR *)nts) {
	nts = (struct timer_struct RFAR *)ts->n.next;
	if (ts->status & TIMER_STOP) goto kill_timer;
	if (--ts->cs_time <= 1) {
	    ts->cs_time = 0;
	    (*ts->rtn)(ts->data);
	    if (ts->status & TIMER_ACTIVE && ts->cs_time == 0) {
		kill_timer:
		ts->status = 0;
		iwl_DeleteNode(&active_cs_timers, (struct iwl_node RFAR *)ts);
		if (active_cs_timers.head == 0) {
#ifdef IWL_USE_SYNTH_AS_TIMER
		    iwl_note_stop_synth_timer();
#else
		    iw_disable_timer2();
#endif
		    in_cs_handler = 0;
		    return(1); /* cancel timer */
		}
	    }
	}
    }
    in_cs_handler = 0;
    return(0); /* retrigger timer */
}

void iwl_stop_cs_timer(
    int tn)
{
    struct timer_struct RFAR *ts = &cs_timer_struct[tn];

    if (in_cs_handler) {
	if (ts->status & TIMER_ACTIVE) {
	    ts->status |= TIMER_STOP;
	}
	return;
    }
    if (ts->status & TIMER_ACTIVE) {
	ts->status = 0;
	iwl_DeleteNode(&active_cs_timers, (struct iwl_node RFAR *)ts);
	if (active_cs_timers.head == 0) {
#ifdef IWL_USE_SYNTH_AS_TIMER
	    iwl_stop_synth_timer();
#else
	    iw_disable_timer2();
#endif
	}
    }
}

void iwl_set_cs_timer(
    int tn,
    USHORT cs_time,
    void (*rtn)(USHORT data),
    USHORT data)
{
    struct timer_struct RFAR *ts = &cs_timer_struct[tn];
    int (RFAR *callback_addr)(void);

    ts->cs_time = cs_time;
    ts->rtn = rtn;
    ts->data = data;
    if (ts->status & TIMER_ACTIVE) return;
    ts->status = TIMER_ACTIVE;
    if (active_cs_timers.head == 0) {
	/* list is empty, so enable hardware timer */
#if defined (__BORLANDC__) && NEARCODE == 1
	asm mov word ptr callback_addr, offset cs_handler
	asm mov word ptr callback_addr+2, cs
#else
	callback_addr = cs_handler;
#endif
#ifdef IWL_USE_SYNTH_AS_TIMER
	/* 31 * 0.00032s = 9.92ms interval, close enough to 10ms */
	iwl_start_synth_timer(callback_addr, 256-31);
#else
	/* 31 * 0.00032s = 9.92ms interval, close enough to 10ms */
	iw_enable_timer2(callback_addr, 256-31);
#endif
    }
    /* insert timer at beginning in case we are already in timer service */
    iwl_AddNode(&active_cs_timers, (struct iwl_node RFAR *)ts, (struct iwl_node RFAR *)0, IWL_PREPEND);
}
