/* $Id: sun4-timer.c,v 1.3 2010/06/05 14:38:23 fredette Exp $ */

/* machine/sun4/sun4-timer.c - implementation of Sun 4 timer emulation: */

/*
 * Copyright (c) 2006 Matt Fredette
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Matt Fredette.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <tme/common.h>
_TME_RCSID("$Id: sun4-timer.c,v 1.3 2010/06/05 14:38:23 fredette Exp $");

/* includes: */
#include <tme/generic/bus-device.h>
#include "sun4-impl.h"

/* macros: */

/* real sun4/4c timer bits: */
#define TME_SUN4_32_TIMER_LIMIT		TME_BIT(31)
#define	TME_SUN44C_TIMER_MASK		(0x7ffffc00)
#define	TME_SUN4M_TIMER_MASK		(0x7ffffe00)

/* define this to track interrupt rates, reporting once every N
   seconds: */
#if 1
#define TME_SUN4_TIMER_TRACK_INT_RATE		TME_TIME_SET_SEC(10)
#endif

/* this makes timer callouts.  it must be called with the mutex held: */
static void
_tme_sun4_timer_callout(struct tme_sun4 *sun4)
{
  struct tme_bus_connection *conn_bus;
  struct tme_sun4_timer *timer;
  unsigned int int_asserted;
  int again;
  int rc;

  /* get our bus connection: */
  conn_bus = sun4->tme_sun4_buses[TME_SUN4_32_CONN_REG_TIMER];

  /* if this function is already running in another thread, return
     now.  the other thread will do our work: */
  if (sun4->tme_sun4_timer_callouts_running || !conn_bus) {
    return;
  }

  /* callouts are now running: */
  sun4->tme_sun4_timer_callouts_running = TRUE;

  /* loop forever: */
  for (again = TRUE; again;) {
    again = FALSE;

    /* check all of the timers for changes: */
    timer = &sun4->tme_sun4_timers[0];
    do {
      
      /* if this timer needs an interrupt callout: */
      int_asserted = (timer->tme_sun4_timer_counter & TME_SUN4_32_TIMER_LIMIT) != 0;
      if (!int_asserted
	  != !timer->tme_sun4_timer_int_asserted) {

	/* unlock our mutex: */
	tme_mutex_unlock(&sun4->tme_sun4_mutex);

	/* call out the bus interrupt signal edge: */
	rc = (*conn_bus->tme_bus_signal)
	  (conn_bus,
	   ((timer == &sun4->tme_sun4_timers[0]
	     ? TME_BUS_SIGNAL_INT(10)
	     : TME_BUS_SIGNAL_INT(14))
	    | (int_asserted
	       ? TME_BUS_SIGNAL_LEVEL_ASSERTED
	       : TME_BUS_SIGNAL_LEVEL_NEGATED)));

	/* lock our mutex: */
	tme_mutex_lock(&sun4->tme_sun4_mutex);

	/* if this callout was successful, note the new state of the
	   interrupt signal: */
	if (rc == TME_OK) {
	  timer->tme_sun4_timer_int_asserted = int_asserted;
	  again = TRUE;
	}

	/* otherwise, abort: */
	else {
	  abort();
	}
      }
    } while (++timer != (&sun4->tme_sun4_timers[0] + TME_ARRAY_ELS(sun4->tme_sun4_timers)));

  }

  /* there are no more callouts to make: */
  sun4->tme_sun4_timer_callouts_running = FALSE;
}

/* this can be called to force an immediate timer interrupt: */
void
_tme_sun4_timer_int_force(struct tme_sun4 *sun4,
			  struct tme_sun4_timer *timer)
{

  /* lock our mutex: */
  tme_mutex_lock(&sun4->tme_sun4_mutex);

  /* force an immediate timer interrupt: */
  timer->tme_sun4_timer_counter = TME_SUN4_32_TIMER_LIMIT;
  timer->tme_sun4_timer_limit |= TME_SUN4_32_TIMER_LIMIT;

  /* call out any interrupts: */
  _tme_sun4_timer_callout(sun4);

  /* unlock our mutex: */
  tme_mutex_unlock(&sun4->tme_sun4_mutex);
}

/* the sun4 timer update function.  it must be called with the mutex
   locked: */
static tme_time_t
_tme_sun4_timer_update(struct tme_sun4_timer *timer, tme_time_t *now)
{

  /* get the current time: */
  *now = tme_thread_get_time();

#ifdef TME_SUN4_TIMER_TRACK_INT_RATE

  /* if the sample time has finished: */
  if (*now > timer->tme_sun4_timer_track_sample) {
    /* if the timer has made any interrupts during the sample time: */
    if (timer->tme_sun4_timer_track_ints > 0) {

      /* log the interrupt rate: */
      tme_log(TME_SUN4_LOG_HANDLE(timer->tme_sun4_timer_sun4),
	      0, TME_OK,
	      (TME_SUN4_LOG_HANDLE(timer->tme_sun4_timer_sun4),
	       "level %d timer interrupt rate: %ld/sec",
	       (timer == &timer->tme_sun4_timer_sun4->tme_sun4_timers[0]
		? 10
		: 14),
	       (TME_TIME_SET_SEC(timer->tme_sun4_timer_track_ints)
		/ (*now - (timer->tme_sun4_timer_track_sample
			   - TME_SUN4_TIMER_TRACK_INT_RATE)))));
    }

    /* reset the sampling: */
    timer->tme_sun4_timer_track_ints = 0;
    timer->tme_sun4_timer_track_sample = *now;
    timer->tme_sun4_timer_track_sample += TME_SUN4_TIMER_TRACK_INT_RATE;
  }

#endif /* TME_SUN4_TIMER_TRACK_INT_RATE */

  /* if this timer has not reached its next limit: */
  if (__tme_predict_false(timer->tme_sun4_timer_limit_next > *now)) {

    return timer->tme_sun4_timer_limit_next - *now;
  }

  /* set this timer's next limit time: */
  do {
    timer->tme_sun4_timer_limit_next += timer->tme_sun4_timer_period;
  } while (*now > timer->tme_sun4_timer_limit_next);

  /* mark this timer as having reached its limit: */
#ifdef TME_SUN4_TIMER_TRACK_INT_RATE
  if (!(timer->tme_sun4_timer_counter
	& TME_SUN4_32_TIMER_LIMIT)) {
    timer->tme_sun4_timer_track_ints++;
  }
#endif /* TME_SUN4_TIMER_TRACK_INT_RATE */
  timer->tme_sun4_timer_counter = TME_SUN4_32_TIMER_LIMIT;
  timer->tme_sun4_timer_limit |= TME_SUN4_32_TIMER_LIMIT;

  /* sleep for the normal period: */
  return timer->tme_sun4_timer_period;
}

/* this resets a timer: */
static void
_tme_sun4_timer_reset(struct tme_sun4_timer *timer)
{
  tme_uint32_t counter_one;
  tme_uint32_t ticks;
  tme_uint32_t ticks_max;

  /* to keep things simpler, we always use the sun4m 500ns tick: */
  counter_one = TME_SUN4_IS_SUN44C(timer->tme_sun4_timer_sun4) ? 2 : 1;
  ticks_max = (TME_SUN4M_TIMER_MASK / _TME_FIELD_MASK_FACTOR(TME_SUN4M_TIMER_MASK)) + 1;

  /* get this timer's period, in 500ns ticks.  NB that we account for
     the fact that timers count from [1..limit), and not [0..limit): */
  /* XXX FIXME - we assume that the limit value of one gives the
     longest possible period.  is this right? */
  ticks = TME_FIELD_MASK_EXTRACTU(timer->tme_sun4_timer_limit, TME_SUN4M_TIMER_MASK);
  ticks = (ticks - counter_one) & (ticks_max - counter_one);
  if (__tme_predict_false(ticks == 0)) {
    ticks = ticks_max;
  }

  /* convert the timer's period from 500ns ticks to a tme_time_t
     and save it: */
  
  timer->tme_sun4_timer_period = TME_TIME_SET_USEC(ticks / 2);

  /* set the next limit time for this timer: */
  timer->tme_sun4_timer_limit_next = tme_thread_get_time();
  timer->tme_sun4_timer_limit_next += timer->tme_sun4_timer_period;
}

/* the sun4 timer thread: */
static _tme_thret
_tme_sun4_timer_th(struct tme_sun4_timer *timer)
{
  struct tme_sun4 *sun4;
  tme_time_t now;
  tme_time_t sleep;

  /* recover our sun4: */
  sun4 = timer->tme_sun4_timer_sun4;

  tme_thread_enter(&sun4->tme_sun4_mutex);

  /* loop forever: */
  for (;;) {

    /* update this timer: */
    sleep = _tme_sun4_timer_update(timer, &now);

    /* call out any interrupts: */
    _tme_sun4_timer_callout(sun4);

    /* sleep, but wake up if our timer configuration changes: */
    tme_cond_sleep_yield(&timer->tme_sun4_timer_cond,
			 &sun4->tme_sun4_mutex,
			 sleep);
  }
  /* NOTREACHED */
  tme_thread_exit(&sun4->tme_sun4_mutex);
}

/* the sun4 timer control bus cycle handler: */
int
_tme_sun4_timer_cycle_control(void *_sun4, struct tme_bus_cycle *cycle_init)
{
  struct tme_sun4 *sun4;
  unsigned int timer_i;
  struct tme_sun4_timer *timer;
  tme_uint32_t reg;
  tme_uint32_t value32;
  struct tme_bus_cycle cycle_resp;
  tme_time_t now;
  tme_time_t last_reset;
  tme_uint32_t counter_one;
  tme_uint32_t usecs;
  tme_uint32_t ticks;

  /* recover our sun4: */
  sun4 = (struct tme_sun4 *) _sun4;

  /* this must be a full 32-bit register access: */
  if ((cycle_init->tme_bus_cycle_address % sizeof(tme_uint32_t)) != 0
      || cycle_init->tme_bus_cycle_size != sizeof(tme_uint32_t)) {
    abort();
  }

  /* get the timer and register accessed: */
  if (TME_SUN4_IS_SUN44C(sun4)) {
    timer_i = cycle_init->tme_bus_cycle_address / TME_SUN44C_TIMER_SIZ_REG;
    reg = cycle_init->tme_bus_cycle_address & TME_SUN4_32_TIMER_SIZ_COUNTER;
  }
  else {
    abort();
  }
  timer = &sun4->tme_sun4_timers[timer_i];

  /* lock our mutex: */
  tme_mutex_lock(&sun4->tme_sun4_mutex);

  /* if this is a read: */
  if (cycle_init->tme_bus_cycle_type == TME_BUS_CYCLE_READ) {

    /* dispatch on the register: */
    switch (reg) {
    default: assert(FALSE);
    case TME_SUN4_32_TIMER_REG_COUNTER:

      /* update the timers: */
      last_reset = _tme_sun4_timer_update(timer, &now);

      /* get the time of the last reset: */
      last_reset = timer->tme_sun4_timer_limit_next
	- timer->tme_sun4_timer_period;

      /* get the number of microseconds since the last reset: */
      usecs = TME_TIME_GET_USEC(now - last_reset);

      /* to keep things simpler, we always use the sun4m 500ns tick: */
      counter_one = TME_SUN4_IS_SUN44C(sun4) ? 2 : 1;

      /* convert the number of microseconds until this timer resets again,
	 to the 500ns tick counter value for the timer.  NB that we
	 account for the fact that timers count from [1..limit), and not
	 [0..limit): */
      ticks = (usecs * 2) + counter_one;
      TME_FIELD_MASK_DEPOSITU(timer->tme_sun4_timer_counter, 
			      TME_SUN4M_TIMER_MASK,
			      ticks);

      /* read this timer's counter register: */
      value32 = timer->tme_sun4_timer_counter;
      break;

    case TME_SUN4_32_TIMER_REG_LIMIT:

      /* read this timer's limit register: */
      value32 = timer->tme_sun4_timer_limit;	

      /* a read of the limit register is used to acknowledge an
	 interrupt, which probably means clearing the limit bit on the
	 counter register: */
      timer->tme_sun4_timer_counter = 0;
      timer->tme_sun4_timer_limit &= ~TME_SUN4_32_TIMER_LIMIT;
      break;
    }

    tme_log(TME_SUN4_LOG_HANDLE(sun4), 2000, TME_OK,
	    (TME_SUN4_LOG_HANDLE(sun4),
	     _("timer #%d %s -> 0x%08x"),
	     timer_i,
	     (reg == TME_SUN4_32_TIMER_REG_COUNTER
	      ? "counter"
	      : "limit"),
	     value32));

    /* byteswap the register value: */
    value32 = tme_htobe_u32(value32);
  }

  /* run the bus cycle: */
  cycle_resp.tme_bus_cycle_buffer = (tme_uint8_t *) &value32;
  cycle_resp.tme_bus_cycle_buffer_increment = 1;
  cycle_resp.tme_bus_cycle_lane_routing = cycle_init->tme_bus_cycle_lane_routing;
  cycle_resp.tme_bus_cycle_address = 0;
  cycle_resp.tme_bus_cycle_type = (cycle_init->tme_bus_cycle_type
				   ^ (TME_BUS_CYCLE_WRITE
				      | TME_BUS_CYCLE_READ));
  cycle_resp.tme_bus_cycle_port = TME_BUS_CYCLE_PORT(0, TME_BUS32_LOG2);
  tme_bus_cycle_xfer(cycle_init, &cycle_resp);

  /* if this is a write: */
  if (cycle_init->tme_bus_cycle_type == TME_BUS_CYCLE_WRITE) {

    /* byteswap the register value: */
    value32 = tme_htobe_u32(value32);

    tme_log(TME_SUN4_LOG_HANDLE(sun4), 2000, TME_OK,
	    (TME_SUN4_LOG_HANDLE(sun4),
	     _("timer #%d %s <- 0x%08x"),
	     timer_i,
	     (reg == TME_SUN4_32_TIMER_REG_COUNTER
	      ? "counter"
	      : "limit"),
	     value32));

    /* dispatch on the register: */
    switch (reg) {
    default: assert(FALSE);
    case TME_SUN4_32_TIMER_REG_COUNTER:
      abort();

    case TME_SUN4_32_TIMER_REG_LIMIT:

      /* write the timer's limit register: */
      timer->tme_sun4_timer_limit = value32;

      /* reset this timer: */
      _tme_sun4_timer_reset(timer);

      /* wake up the thread for this timer: */
      tme_cond_notify(&timer->tme_sun4_timer_cond, FALSE);
      break;
    }
  }

  /* make any callouts: */
  _tme_sun4_timer_callout(sun4);
    
  /* unlock the mutex: */
  tme_mutex_unlock(&sun4->tme_sun4_mutex);

  /* no faults: */
  return (TME_OK);
}

/* this creates the sun4 timers: */
void
_tme_sun4_timer_new(struct tme_sun4 *sun4)
{
  struct tme_sun4_timer *timer;

  /* loop over the timers: */
  timer = &sun4->tme_sun4_timers[0];
  do {

    /* initialize and reset the timer: */
    timer->tme_sun4_timer_sun4 = sun4;
    tme_cond_init(&timer->tme_sun4_timer_cond);
    _tme_sun4_timer_reset(timer);

    /* start the thread for this timer: */
    tme_thread_create(&timer->tme_sun4_timer_thread, (tme_thread_t) _tme_sun4_timer_th, timer);

  } while (++timer != (&sun4->tme_sun4_timers[0] + TME_ARRAY_ELS(sun4->tme_sun4_timers)));
}
