/*
 ***************************************************************************
 *			PC 8253/8254 Timer control
 * Refer to the file timer.h for complete description of the timer chip
 *
 ***************************************************************************
 */

#include <dos.h>
#include "timer.h"
#include "assert.h"
#include "stdio.h"
				/* Timer hardware description	*/
#define Latch_reg_port0	0x40		/* For the 0. channel	*/
#define Control_port	0x43

				/* Control port bits meaning	*/
#define CP_BCD_counting	  	1
#define CP_mode3		(3<<1)
#define CP_latch_from_counter	(0<<4)	/* Load latch reg from counter*/
#define CP_hi_latch_byte        (1<<4)	/* Transmit only HI byte of the latch reg through the port*/
#define CP_lo_latch_byte	(2<<4)	/* Transmit only LO byte*/
#define CP_lohi_latch_bytes	(3<<4)	/* LO then HI bytes	*/
#define CP_channel0		(0<<6)
#define CP_channel1		(1<<6)
#define CP_channel2		(2<<6)

#define Max_frequency		1193180L	/* Ticks per second	*/

				/* Settings for the channel being operated*/
static unsigned CP_channel;		/* Channel being operated	*/
static unsigned Data_port;		/* Latch reg port for the channel*/

/*
 *------------------------------------------------------------------------
 *	      		Function executors
 */

typedef unsigned EXECUTOR(const int value);

					/* Return the latch reg contents*/
static EXECUTOR get_latch
{
  assure( value == 0, "Value other than 0 is unexpected for get_latch");
  _AL = CP_channel + CP_mode3 + CP_lohi_latch_bytes;
  asm out Control_port,al
  _DX = Data_port;
  asm in    al,dx			/* Read LO latch byte first	*/
  asm mov   ah,al			/* Save it temporary in AH	*/
  asm in    al,dx			/* Read HI latch byte		*/
  asm xchg  ah,al			/* Set the right order of bytes	*/
  return _AX;
}

					/* Set the latch reg counter	*/
					/* Return the value set		*/
static EXECUTOR set_latch
{
  _AL = CP_channel + CP_mode3 + CP_lohi_latch_bytes;
  asm out Control_port,al
  _DX = Data_port;
  _AX = value;
  asm out dx,al			/* Push LO latch byte first		*/
  asm mov al,ah
  asm out dx,al			/* then push HI latch byte to timer	*/
  return value;
}

					/* Set the latch reg counter 	*/
					/* corr. interval im microsec	*/
					/* Return the value set		*/
static EXECUTOR set_period
{
  return set_latch((Max_frequency*value/1000000L));
}

					/* Return the current timer counter*/
static EXECUTOR get_counter
{
  int old_latch;
  int counter_through_latch;

  assure( value == 0, "Value other than 0 is unexpected for get_counter");
  old_latch = get_latch(0);		/* Save prev. contents of latch reg*/
  _AL = CP_channel + CP_mode3 + CP_latch_from_counter;
  asm out Control_port,al		/* Load latch from the current counter*/
  counter_through_latch = get_latch(0);	/* Read the counter having been */
					/* loaded to latch		*/
  set_latch(old_latch);			/* Restore the latch reg	*/
  return counter_through_latch;
}

static struct LIST_ITEM {
	const char * name;		/* Function name 	*/
	EXECUTOR   * executor;    	/* Function executor	*/
	}
Functions_list [] =
{	"get_latch",	get_latch,
	"set_latch",	set_latch,
	"set_period",	set_period,
	"get_counter",	get_counter,
	"", 		(EXECUTOR *)0
};

/*
 *------------------------------------------------------------------------
 *	       		  Root module
 */

unsigned Timer_control(function, timer_no, value)
const char *   function;		/* Function to do		*/
const int      timer_no;       		/* 8253/8254 CHIP timer no	*/
const unsigned value;      		/* Value required by certain op */
{
  struct LIST_ITEM * pitem;

				/* Analyze timer no information	*/
  assure( timer_no >= 0 && timer_no <= 2,
	"Timer 8253 has channels with nos. 0, 1, and 2. You request other");
  assure( timer_no != 1,
	"By no occasion You should touch timer channel #1!");
  Data_port = Latch_reg_port0 + timer_no;
  if( timer_no == 2 )
    CP_channel = CP_channel2;
  else
    CP_channel = CP_channel0;

  for( pitem = Functions_list; *(pitem->name) != '\0'; pitem++ )
     if( strcmp(pitem->name,function) == 0 )
       return (*pitem->executor)(value);

  message("\nTimer_control can do the following functions:\n");
  for( pitem = Functions_list; *(pitem->name) != '\0'; pitem++ )
     message("'%s' ",pitem->name);
  _error("\nThe function '%s' You have specified is unknown",function);

}

				/* For debugging purposes		*/
#if 0

void main()
{
  const int freq_set = 1000;		/* in microsec		*/
  message("\n\nDebugging Timer control routine\n");
  message("\nChannel 0 current Latch reg is %u",
	 Timer_control("get_latch",0,0));
  message("\nSetting timer channel #2 to count the interval %u microsec.\n\
  Latch reg is thereby set to %u",
	 freq_set,Timer_control("set_period",2,freq_set));
  delay(15);
  message("\nCurrent timer #2 counter is %u",
	 Timer_control("get_counter",2,0));
  message("\nTimer #2 latch reg is still %u",
	 Timer_control("get_latch",2,0));
}
#endif
