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


    Motorola 6840 PTM interface and emulation

    This function is a simple emulation of up to 4 MC6840
    Programmable Timer Module

    Todo:
         everything and more

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

#include "driver.h"
#include "timer.h"
#include "6840ptm.h"

#define PTMVERBOSE 1

#if PTMVERBOSE
#define PLOG(x)	logerror x
#else
#define PLOG(x)
#endif

#define PTM6840_CYCLES_TO_TIME(a) ((double)a*1000000L)

#define PTM_6840_CTRL1   0
#define PTM_6840_CTRL2   1
#define PTM_6840_MSBBUF1 2
#define PTM_6840_LSB1	 3
#define PTM_6840_MSBBUF2 4
#define PTM_6840_LSB2    5
#define PTM_6840_MSBBUF3 6
#define PTM_6840_LSB3    7


struct ptm6840
{
  const struct ptm6840_interface *intf;

  UINT8 control_reg[3],
		lsb_latch[3],
	    msb_latch[3],
		lsb_counter[3],
		msb_counter[3],

		output[3],		/* output states */
		input[3],		/* input  gate states */
		clock[3],		/* clock  states */

		status_reg,
		lsb_buffer,
		msb_buffer;

  /* each PTM has 3 timers */

  mame_timer *timer1,
	         *timer2,
			 *timer3;

};


/* local prototypes /////////////////////////////////////////////////////// */

static void ptm6840_t1_timeout(int which);
static void ptm6840_t2_timeout(int which);
static void ptm6840_t3_timeout(int which);

/* local vars ///////////////////////////////////////////////////////////// */

static struct ptm6840 ptm[PTM_6840_MAX];

#ifdef PTMVERBOSE
static const char *opmode[] =
{
  "000 continous mode",
  "001 freq comparison mode",
  "010 continous mode",
  "011 pulse width comparison mode",
  "100 single shot mode",
  "101 freq comparison mode",
  "110 single shot mode",
  "111 pulse width comparison mode"
};
#endif

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

void ptm6840_unconfig(void)
{
  int i;

  i = 0;
  while ( i < PTM_6840_MAX )
  {
	if ( ptm[i].timer1 );/* mame_timer_remove( ptm[i].timer1 ); */
	timer_adjust(ptm[i].timer1, TIME_NEVER, 0, 0);
	ptm[i].timer1 = NULL;

	if ( ptm[i].timer2 );/* mame_timer_remove( ptm[i].timer2 ); */
	timer_adjust(ptm[i].timer2, TIME_NEVER, 0, 0);
	ptm[i].timer2 = NULL;

	if ( ptm[i].timer3 );/* mame_timer_remove( ptm[i].timer3 ); */
	timer_adjust(ptm[i].timer3, TIME_NEVER, 0, 0);
	ptm[i].timer3 = NULL;
	i++;
  }
  memset (&ptm, 0, sizeof (ptm));
}

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

void ptm6840_config(int which, const struct ptm6840_interface *intf)
{
  if (which >= PTM_6840_MAX) return;
  ptm[which].intf = intf;

  ptm[which].timer1 = timer_alloc(ptm6840_t1_timeout);
  ptm[which].timer2 = timer_alloc(ptm6840_t2_timeout);
  ptm[which].timer3 = timer_alloc(ptm6840_t3_timeout);
  ptm6840_reset(which);
}

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

void ptm6840_reset(int which)
{
  int i;

  for ( i = 0; i < 3; i++ )
  {
	ptm[which].control_reg[i] = 0x00;
	ptm[which].lsb_latch[i]   = 0xff;
	ptm[which].msb_latch[i]   = 0xff;
	ptm[which].lsb_counter[i] = ptm[which].lsb_latch[i];
	ptm[which].msb_counter[i] = ptm[which].msb_latch[i];
	ptm[which].output[i]      = 0;
  }
}

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

int ptm6840_read(int which, int offset)
{
  int	val = 0;

  struct ptm6840 *currptr = ptm + which;

  switch ( offset )
  {
  case PTM_6840_CTRL1 :/*0 */

			  break;

  case PTM_6840_CTRL2 :/*1 */

			  if ( currptr->status_reg & 0x07 ) currptr->status_reg |=  0x80;
			  else                              currptr->status_reg &= ~0x80;
			  val = currptr->status_reg;
			  break;

  case PTM_6840_MSBBUF1:/*2 */

			  currptr->status_reg &= ~0x01;
			  break;

  case PTM_6840_LSB1:/*3 */

			  currptr->status_reg &= ~0x01;
			  break;

  case PTM_6840_MSBBUF2:/*4 */

			  currptr->status_reg &= ~0x02;
			  break;

  case PTM_6840_LSB2:/*5 */

			  currptr->status_reg &= ~0x02;
			  break;

  case PTM_6840_MSBBUF3:/*6 */

			  currptr->status_reg &= ~0x04;
			  break;

  case PTM_6840_LSB3:/*7 */

			  currptr->status_reg &= ~0x04;
			  break;

  }
  return val;
}


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

void ptm6840_write (int which, int offset, int data)
{
  struct ptm6840 *currptr = ptm + which;

  int  idx = 0;
  long time;
if (offset < 2)
{
 idx = (offset == 1) ? 1 : (currptr->control_reg[1] & 0x01) ? 0 : 2;

logerror("MC6840 #%d : Control register %d selected\n",which,idx);

		  currptr->control_reg[idx] = data;

/* bit 1 - 7  are the same for all timer control registers

            bit 1 --> 0 = timer uses external clock line
                      1 = timer uses internal E clock ( CPU Clock / 10, most likely 1 Mhz)

            bit 2 --> counting mode
                      0 = normal 16 bit counts down (msb*256 + lsb)+1 ticks
                      1 = dual    8 bit counts down (lsb+1)*(msb+1)   ticks

            bit 5 4 3 --> operation mode

                      0 0 0 --> continous mode
                      0 0 1 --> freq comparison mode
                      0 1 0 --> continous mode
                      0 1 1 --> pulse width comparison mode
                      1 0 0 --> single shot mode
                      1 0 1 --> freq comparison mode
                      1 1 0 --> single shot mode
                      1 1 1 --> pulse width comparison mode

            bit 6 --> IRQ enable,     1 = enabled, 0 = disabled
            bit 7 --> Output enable,  1 = enabled, 0 = disabled

  // 0100 0010 --> IRQ enable,
*/

		  if ( data & 0x04 )
		  { /* dual    8 bit counts down (lsb+1)*(msb+1)   ticks */
			time = ( currptr->lsb_latch[idx] + 1 ) * ( currptr->msb_latch[idx] + 1 );
		  }
		  else
		  { /* normal 16 bit counts down (msb*256 + lsb)+1 ticks */
			time = ( currptr->msb_latch[idx] * 256  + currptr->lsb_latch[idx] ) + 1;
		  }
		  if ( data & 0x02 )
		  { /* clock select = 1, use E-clock */
			time = PTM6840_CYCLES_TO_TIME(time);
		  }
		  else
		  { /* clock select = 0, use external clock */
			/* ????damn */
		  }

		  switch ( idx )
		  {
		  case 0: /* control reg 1 */

				  timer_adjust(currptr->timer1, TIME_IN_NSEC(time), 0, 0);
				  break;

		  case 1: /* control reg 2 */

				  timer_adjust(currptr->timer2, TIME_IN_NSEC(time), 0, 0);
				  break;

		  case 2: /* control reg 3 */

				  timer_adjust(currptr->timer3, TIME_IN_NSEC(time), 0, 0);
				  break;
		  }

		  PLOG(("6840PTM #%d control reg %d = %02X\n", which, idx, currptr->control_reg[idx]));
		  PLOG( ("  output    enable = %d\n"
			        "  interrupt enable = %d\n"
					"  operation mode   = %s\n"
					"  use %s clock \n"
					"  %s counting mode\n",

					(data & 0x80)?1:0,
					(data & 0x40)?1:0,
					opmode[ (data>>3)&0x07 ],
					(data & 0x02)?"E":"external",
					(data & 0x04)?"16 bit": "dual 8 bit"));
  }

  switch ( offset )
  {
  case PTM_6840_MSBBUF1:/*2 */

		  PLOG(("6840PTM #%d msbbuf1 = %02X\n", which, data));

		  currptr->status_reg &= ~0x01;
		  currptr->msb_buffer = data;
		  break;

  case PTM_6840_LSB1:/*3 */

		  PLOG(("6840PTM #%d lsb latch 1 = %02X\n", which, data));

		  currptr->status_reg &= ~0x01;
		  currptr->lsb_latch[0] = data;
		  break;


  case PTM_6840_MSBBUF2:/*4 */

		  PLOG(("6840PTM #%d msbbuf2 = %02X\n", which, data));

		  currptr->status_reg &= ~0x02;
		  currptr->msb_buffer = data;
		  break;

  case PTM_6840_LSB2:/*5 */

		  PLOG(("6840PTM #%d lsb latch 2 = %02X\n", which, data));

		  currptr->status_reg &= ~0x02;
		  currptr->lsb_latch[1] = data;
		  break;

  case PTM_6840_MSBBUF3:/*6 */

		  PLOG(("6840PTM #%d msbbuf3 = %02X\n", which, data));

		  currptr->status_reg &= ~0x04;
		  currptr->msb_buffer = data;
		  break;

  case PTM_6840_LSB3:/*7 */

		  PLOG(("6840PTM #%d lsb latch 3 = %02X\n", which, data));

		  currptr->status_reg &= ~0x04;
		  currptr->lsb_latch[2] = data;
		  break;

  /*case PTM_6840_CTRL2://1 */
}
}
/*///////////////////////////////////////////////////////////////////////// */
/*                                                                       // */
/* ptm6840_t1_timeout: called if timer1 is mature                        // */
/*                                                                       // */
/*///////////////////////////////////////////////////////////////////////// */

static void ptm6840_t1_timeout(int which)
{
  struct ptm6840 *p = ptm + which;

  logerror("**ptm6840 %d t1 timeout**\n", which);

  if ( p->control_reg[0] & 0x40 )
  { /* interrupt enabled */
	p->status_reg |= 0x01;
	if ( p->intf->irq_func  ) p->intf->irq_func(0);
  }

  if ( p->intf )
  {
	if ( p->intf->out1_func ) p->intf->out1_func(0, p->output[0]);
  }
}

/*///////////////////////////////////////////////////////////////////////// */
/*                                                                       // */
/* ptm6840_t2_timeout: called if timer2 is mature                        // */
/*                                                                       // */
/*///////////////////////////////////////////////////////////////////////// */

static void ptm6840_t2_timeout(int which)
{
  struct ptm6840 *p = ptm + which;

  logerror("**ptm6840 %d t2 timeout**\n", which);

  if ( p->control_reg[1] & 0x40 )
  { /* interrupt enabled */
	p->status_reg |= 0x02;
	if ( p->intf->irq_func  ) p->intf->irq_func(0);
  }

  if ( p->intf )
  {
	if ( p->intf->out2_func ) p->intf->out2_func(0, p->output[1]);
  }
}

/*///////////////////////////////////////////////////////////////////////// */
/*                                                                       // */
/* ptm6840_t3_timeout: called if timer3 is mature                        // */
/*                                                                       // */
/*///////////////////////////////////////////////////////////////////////// */

static void ptm6840_t3_timeout(int which)
{
  struct ptm6840 *p = ptm + which;

  logerror("**ptm6840 %d t3 timeout**\n", which);

  if ( p->control_reg[1] & 0x40 )
  { /* interrupt enabled */
	p->status_reg |= 0x04;
	if ( p->intf->irq_func  ) p->intf->irq_func(0);
  }

  if ( p->intf )
  {
	if ( p->intf->out3_func ) p->intf->out3_func(0, p->output[3]);
  }
}

/*///////////////////////////////////////////////////////////////////////// */
/*                                                                       // */
/* ptm6840_set_g1: set gate1 status (0 Or 1)                             // */
/*                                                                       // */
/*///////////////////////////////////////////////////////////////////////// */

void ptm6840_set_g1(int which, int state)
{
  struct ptm6840 *p = ptm + which;

  p->input[0] = state;
}

void ptm6840_set_c1(int which, int state)
{
  struct ptm6840 *p = ptm + which;
  p->input[0] = state;
}

/*///////////////////////////////////////////////////////////////////////// */
/*                                                                       // */
/* ptm6840_set_g2: set gate2 status (0 Or 1)                             // */
/*                                                                       // */
/*///////////////////////////////////////////////////////////////////////// */

void ptm6840_set_g2(int which, int state)
{
  struct ptm6840 *p = ptm + which;

  p->input[1] = state;
}
void ptm6840_set_c2(int which, int state)
{
  struct ptm6840 *p = ptm + which;
  p->input[1] = state;
}

/*///////////////////////////////////////////////////////////////////////// */
/*                                                                       // */
/* ptm6840_set_g3: set gate3 status (0 Or 1)                             // */
/*                                                                       // */
/*///////////////////////////////////////////////////////////////////////// */

void ptm6840_set_g3(int which, int state)
{
  struct ptm6840 *p = ptm + which;

  p->input[2] = state;
}
void ptm6840_set_c3(int which, int state)
{
  struct ptm6840 *p = ptm + which;
  p->input[2] = state;
}

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

READ8_HANDLER( ptm6840_0_r ) { return ptm6840_read(0, offset); }
READ8_HANDLER( ptm6840_1_r ) { return ptm6840_read(1, offset); }
READ8_HANDLER( ptm6840_2_r ) { return ptm6840_read(2, offset); }
READ8_HANDLER( ptm6840_3_r ) { return ptm6840_read(3, offset); }

WRITE8_HANDLER( ptm6840_0_w ) { ptm6840_write(0, offset, data); }
WRITE8_HANDLER( ptm6840_1_w ) { ptm6840_write(1, offset, data); }
WRITE8_HANDLER( ptm6840_2_w ) { ptm6840_write(2, offset, data); }
WRITE8_HANDLER( ptm6840_3_w ) { ptm6840_write(3, offset, data); }

READ16_HANDLER( ptm6840_0_r16u ) { return ptm6840_read(0, offset << 8 | 0x00ff); }
READ16_HANDLER( ptm6840_1_r16u ) { return ptm6840_read(1, offset << 8 | 0x00ff); }
READ16_HANDLER( ptm6840_2_r16u ) { return ptm6840_read(2, offset << 8 | 0x00ff); }
READ16_HANDLER( ptm6840_3_r16u ) { return ptm6840_read(3, offset << 8 | 0x00ff); }

WRITE16_HANDLER( ptm6840_0_w16u ) { if (ACCESSING_MSB) ptm6840_write(0, offset, (data >> 8) & 0xff); }
WRITE16_HANDLER( ptm6840_1_w16u ) { if (ACCESSING_MSB) ptm6840_write(1, offset, (data >> 8) & 0xff); }
WRITE16_HANDLER( ptm6840_2_w16u ) { if (ACCESSING_MSB) ptm6840_write(2, offset, (data >> 8) & 0xff); }
WRITE16_HANDLER( ptm6840_3_w16u ) { if (ACCESSING_MSB) ptm6840_write(3, offset, (data >> 8) & 0xff); }

READ16_HANDLER( ptm6840_0_r16l ) { return ptm6840_read(0, offset | 0xff00); }
READ16_HANDLER( ptm6840_1_r16l ) { return ptm6840_read(1, offset | 0xff00); }
READ16_HANDLER( ptm6840_2_r16l ) { return ptm6840_read(2, offset | 0xff00); }
READ16_HANDLER( ptm6840_3_r16l ) { return ptm6840_read(3, offset | 0xff00); }

WRITE16_HANDLER( ptm6840_0_w16 ) {if (ACCESSING_LSB) ptm6840_write(0, offset, data & 0xff); if (ACCESSING_MSB) ptm6840_write(0, offset, (data >> 8) & 0xff); }
WRITE16_HANDLER( ptm6840_1_w16 ) {if (ACCESSING_LSB) ptm6840_write(1, offset, data & 0xff); if (ACCESSING_MSB) ptm6840_write(0, offset, (data >> 8) & 0xff); }
WRITE16_HANDLER( ptm6840_2_w16 ) {if (ACCESSING_LSB) ptm6840_write(2, offset, data & 0xff); if (ACCESSING_MSB) ptm6840_write(0, offset, (data >> 8) & 0xff); }
WRITE16_HANDLER( ptm6840_3_w16 ) {if (ACCESSING_LSB) ptm6840_write(3, offset, data & 0xff); if (ACCESSING_MSB) ptm6840_write(0, offset, (data >> 8) & 0xff); }
