/* $Id: am9513.c,v 1.17 2010/06/05 14:36:59 fredette Exp $ */

/* ic/am9513.c - implementation of Am9513 emulation: */

/*
 * Copyright (c) 2003 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: am9513.c,v 1.17 2010/06/05 14:36:59 fredette Exp $");

/* includes: */
#include <tme/generic/bus-device.h>
#include <tme/ic/am9513.h>
#include <sys/types.h>

/* macros: */
#define TME_AM9513_CMD_SET_BUS_16BIT		(0xffef)
#define TME_AM9513_CMD_RESET			(0xffff)
#define TME_AM9513_CMD_LOAD_COUNTERS		(0xff40)
#define TME_AM9513_CMD_CLEAR_TOGGLE_OUT		(0xffe0)
#define TME_AM9513_CMD_LOAD_ARM_COUNTERS	(0xff60)
#define TME_AM9513_CMD_DISARM_COUNTERS		(0xffc0)
#define TME_AM9513_CMD_ARM_COUNTERS		(0xff20)
#define _TME_AM9513_CMD_TIMERS_MASK	(0x001f)
#define _TME_AM9513_CMD_TIMER_MASK	(0x0007)

/* bits in the Master Mode Register: */
#define TME_AM9513_MMR_BUS_16BIT	TME_BIT(13)
#define TME_AM9513_MMR_NO_INCREMENT	TME_BIT(14)

/* bits in the Status Register: */
#define TME_AM9513_STATUS_BYTE_POINTER	TME_BIT(0)

/* the Counter Mode register: */
#define TME_AM9513_CM_SOURCE_MASK		(0x0f00)
#define  TME_AM9513_CM_SOURCE_F1		(0x0b00)
#define  TME_AM9513_CM_SOURCE_F2		(0x0c00)
#define TME_AM9513_CM_REPEAT_ENA		(0x0020)
#define TME_AM9513_CM_OUTPUT_MASK		(0x0007)
#define  TME_AM9513_CM_OUTPUT_INACTIVE		(0x0000)
#define  TME_AM9513_CM_OUTPUT_TC_TOGGLED	(0x0002)

/* counter flags: */
#define TME_AM9513_COUNTER_FLAG_ARMED		TME_BIT(0)

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

#define TME_AM9513_LOG_HANDLE(am) (&(am)->tme_am9513_element->tme_element_log_handle)

/* structures: */
struct tme_am9513 {

  /* our simple bus device header: */
  struct tme_bus_device tme_am9513_device;
#define tme_am9513_element tme_am9513_device.tme_bus_device_element

  /* our socket: */
  struct tme_am9513_socket tme_am9513_socket;
#define tme_am9513_address_cmd tme_am9513_socket.tme_am9513_socket_address_cmd
#define tme_am9513_address_data tme_am9513_socket.tme_am9513_socket_address_data
#define tme_am9513_port_least_lane tme_am9513_socket.tme_am9513_socket_port_least_lane
#define tme_am9513_basic_clock tme_am9513_socket.tme_am9513_socket_basic_clock
  tme_uint32_t tme_am9513_basic_clock_msec;

  /* our mutex: */
  tme_mutex_t tme_am9513_mutex;

  /* our thread: */
  tme_threadid_t tme_am9513_thread;

  /* this is nonzero iff callouts are running: */
  int tme_am9513_callouts_running;

  /* the control registers: */
  tme_uint16_t tme_am9513_control_regs[4];
#define tme_am9513_alarm1 tme_am9513_control_regs[0]
#define tme_am9513_alarm2 tme_am9513_control_regs[1]
#define tme_am9513_mmr tme_am9513_control_regs[2]
#define tme_am9513_status tme_am9513_control_regs[3]

  /* our counters: */
  struct tme_am9513_counter {
    tme_uint16_t tme_am9513_counter_regs[4];
#define tme_am9513_counter_mode tme_am9513_counter_regs[0]
#define tme_am9513_counter_load tme_am9513_counter_regs[1]
#define tme_am9513_counter_hold tme_am9513_counter_regs[2]
#define tme_am9513_counter_cntr tme_am9513_counter_regs[3]
    unsigned int tme_am9513_counter_flags;
#ifdef TME_AM9513_TRACK_INT_RATE
    unsigned long tme_am9513_counter_int_sample;
    tme_time_t tme_am9513_counter_int_sample_time;
#endif /* TME_AM9513_TRACK_INT_RATE */
  } tme_am9513_counters[5];

  /* the data pointer register: */
  tme_uint8_t tme_am9513_data_pointer;

  /* the current output pins: */
  tme_uint8_t tme_am9513_output_pins;

  /* the last output pins we called out: */
  tme_uint8_t tme_am9513_output_pins_last;

  /* the last time our connection thread ran: */
  tme_time_t tme_am9513_conn_last;
};

/* the Am9513 doesn't have any concept of endianness because it has a
   single "address bit" (the C/!D pin), so when we're running cycles
   we always read and write an entire 16-bit port using a data cycle
   buffer that points to a 16-bit variable, routing the least
   significant lane to the least significant part of the variable,
   etc.: */
#ifdef WORDS_BIGENDIAN
#define REG_LO	(1)
#define REG_HI	(0)
#else
#define REG_LO  (0)
#define REG_HI	(1)
#endif

/* the am9513 bus router: */
static const tme_bus_lane_t tme_am9513_router[TME_BUS_ROUTER_SIZE(TME_BUS16_LOG2)] = {
  
  /* [gen]  initiator port size: 8 bits
     [gen]  initiator port least lane: 0: */
  /* D7-D0 */	TME_BUS_LANE_ROUTE(REG_LO),
  /* D15-D8 */	TME_BUS_LANE_UNDEF,

  /* [gen]  initiator port size: 8 bits
     [gen]  initiator port least lane: 1: */
  /* D7-D0 */	TME_BUS_LANE_UNDEF,
  /* D15-D8 */	TME_BUS_LANE_ROUTE(REG_HI),

  /* [gen]  initiator port size: 16 bits
     [gen]  initiator port least lane: 0: */
  /* D7-D0 */	TME_BUS_LANE_ROUTE(REG_LO),
  /* D15-D8 */	TME_BUS_LANE_ROUTE(REG_HI),

  /* [gen]  initiator port size: 16 bits
     [gen]  initiator port least lane: 1 - invalid, array placeholder: */
  /* D7-D0 */	TME_BUS_LANE_ABORT,
  /* D15-D8 */	TME_BUS_LANE_ABORT
};

/* this resets an am9513: */
static void
_tme_am9513_reset(struct tme_am9513 *am9513)
{
  unsigned int counter_i;
  struct tme_am9513_counter *counter;

  /* disarm all counters: */
  /* XXX */

  /* enter 0000 into the Master Mode register: */
  am9513->tme_am9513_mmr = 0;

  /* initialize all of the counters: */
  for (counter_i = 0;
       counter_i < TME_ARRAY_ELS(am9513->tme_am9513_counters);
       counter_i++) {
    counter = &am9513->tme_am9513_counters[counter_i];
    
    counter->tme_am9513_counter_mode = 0x0b00;
    counter->tme_am9513_counter_load = 0x0000;
    counter->tme_am9513_counter_hold = 0x0000;
    counter->tme_am9513_counter_cntr = 0x0000;
    counter->tme_am9513_counter_flags = 0;
  }
}

/* this loads counters: */
static void
_tme_am9513_counters_load(struct tme_am9513 *am9513, tme_uint16_t counters_mask)
{
  unsigned int counter_i;
  struct tme_am9513_counter *counter;

  /* load all selected counters: */
  for (counter_i = 0;
       counter_i < TME_ARRAY_ELS(am9513->tme_am9513_counters);
       counter_i++) {
    if (counters_mask & TME_BIT(counter_i)) {
      counter = &am9513->tme_am9513_counters[counter_i];
      counter->tme_am9513_counter_cntr = counter->tme_am9513_counter_load;
    }
  }
}

/* this arms counters: */
static void
_tme_am9513_counters_arm(struct tme_am9513 *am9513, tme_uint16_t counters_mask)
{
  unsigned int counter_i;
  struct tme_am9513_counter *counter;

  /* load all selected counters: */
  for (counter_i = 0;
       counter_i < TME_ARRAY_ELS(am9513->tme_am9513_counters);
       counter_i++) {
    if (counters_mask & TME_BIT(counter_i)) {
      counter = &am9513->tme_am9513_counters[counter_i];
      counter->tme_am9513_counter_flags |= TME_AM9513_COUNTER_FLAG_ARMED;
    }
  }
}

/* this disarms counters: */
static void
_tme_am9513_counters_disarm(struct tme_am9513 *am9513, tme_uint16_t counters_mask)
{
  unsigned int counter_i;
  struct tme_am9513_counter *counter;

  /* load all selected counters: */
  for (counter_i = 0;
       counter_i < TME_ARRAY_ELS(am9513->tme_am9513_counters);
       counter_i++) {
    if (counters_mask & TME_BIT(counter_i)) {
      counter = &am9513->tme_am9513_counters[counter_i];
      counter->tme_am9513_counter_flags &= ~TME_AM9513_COUNTER_FLAG_ARMED;
    }
  }
}

/* this makes am9513 callouts.  it must be called with the mutex held: */
static void
_tme_am9513_callout(struct tme_am9513 *am9513)
{
  struct tme_bus_connection *conn_bus;
  unsigned int counter_i;
  int again;
  int pin_high;
  unsigned int signal;
  int rc;

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

  /* callouts are now running: */
  am9513->tme_am9513_callouts_running = TRUE;

  /* get our bus connection: */
  conn_bus = tme_memory_atomic_pointer_read(struct tme_bus_connection *,
					    am9513->tme_am9513_device.tme_bus_device_connection,
					    &am9513->tme_am9513_device.tme_bus_device_connection_rwlock);

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

    /* check all of the counter output pins for changes: */
    for (counter_i = 0;
	 counter_i < TME_ARRAY_ELS(am9513->tme_am9513_counters);
	 counter_i++) {
      
      /* skip this output pin if it hasn't changed: */
      if (((am9513->tme_am9513_output_pins
	    ^ am9513->tme_am9513_output_pins_last)
	   & TME_BIT(counter_i)) == 0) {
	continue;
      }
	
      /* see if this pin is edging high or low: */
      pin_high = am9513->tme_am9513_output_pins & TME_BIT(counter_i);
      
      /* get any bus signal this pin maps to: */
      signal = am9513->tme_am9513_socket.tme_am9513_socket_counter_signals[counter_i];
      
      /* if this signal is ignored: */
      if (TME_BUS_SIGNAL_WHICH(signal) == TME_BUS_SIGNAL_IGNORE) {
	rc = TME_OK;
      }
      
      /* call out this signal edge: */
      else {

	/* unlock our mutex: */
	tme_mutex_unlock(&am9513->tme_am9513_mutex);

	rc = (*conn_bus->tme_bus_signal)
	  (conn_bus,
	   signal
	   ^ (pin_high
	      ? TME_BUS_SIGNAL_LEVEL_HIGH
	      : TME_BUS_SIGNAL_LEVEL_LOW));

	/* lock our mutex: */
	tme_mutex_lock(&am9513->tme_am9513_mutex);
      }
      
      /* if this call out succeeded, update the pin: */
      if (rc == TME_OK) {
	am9513->tme_am9513_output_pins_last =
	  ((am9513->tme_am9513_output_pins_last
	    & ~TME_BIT(counter_i))
	   | pin_high);
	again = TRUE;
      }
    }
  }

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

/* the am9513 timer thread: */
static _tme_thret
_tme_am9513_th_timer(struct tme_am9513 *am9513)
{
  tme_time_t then, elapsed;
  tme_uint16_t counter_mode;
  tme_uint32_t basic_elapsed;
  tme_uint32_t basic_sleep;
  tme_uint32_t divisor;
  unsigned int counter_i;
  struct tme_am9513_counter *counter;
  tme_uint32_t counter_elapsed;

  tme_thread_enter(&am9513->tme_am9513_mutex);

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

    /* figure out how much time has elapsed since our last run: */
    elapsed = tme_thread_get_time();
    then = am9513->tme_am9513_conn_last;
    am9513->tme_am9513_conn_last = elapsed;
    elapsed -= then;

    /* calculate the number of basic ticks that have elapsed: */
    basic_elapsed = am9513->tme_am9513_basic_clock;
    basic_elapsed *= TME_TIME_GET_SEC(elapsed);
    basic_elapsed += am9513->tme_am9513_basic_clock_msec * TME_TIME_GET_MSEC(elapsed % TME_FRAC_PER_SEC);

    /* assume that we will sleep for one second: */
    basic_sleep = am9513->tme_am9513_basic_clock;

    /* check all of the counters: */
    for (counter_i = 0;
	 counter_i < TME_ARRAY_ELS(am9513->tme_am9513_counters);
	 counter_i++) {
      counter = &am9513->tme_am9513_counters[counter_i];
      counter_mode = counter->tme_am9513_counter_mode;

      /* dispatch on the counter source: */
      switch (counter_mode & TME_AM9513_CM_SOURCE_MASK) {

      case TME_AM9513_CM_SOURCE_F2:
      case TME_AM9513_CM_SOURCE_F1:
	
	/* if this counter is armed: */
	if (counter->tme_am9513_counter_flags & TME_AM9513_COUNTER_FLAG_ARMED) {

	  /* calculate the divisor: */
	  divisor = (1 << (((counter->tme_am9513_counter_mode & TME_AM9513_CM_SOURCE_MASK)
			    - TME_AM9513_CM_SOURCE_F1) >> 6));
	  
	  /* calculate the number of ticks on this counter that have happened: */
	  counter_elapsed = basic_elapsed / divisor;
	  
	  /* while this counter (repeatedly) reaches zero: */
	  for (; counter->tme_am9513_counter_cntr <= counter_elapsed; ) {
	    counter_elapsed -= counter->tme_am9513_counter_cntr;

	    /* dispatch on this counter's output control: */
	    switch (counter_mode & TME_AM9513_CM_OUTPUT_MASK) {
	    case TME_AM9513_CM_OUTPUT_INACTIVE:
	      break;
	    case TME_AM9513_CM_OUTPUT_TC_TOGGLED:

	      /* as a slightly sun2-specific hack, if this timer
		 output becomes an interrupt signal, we only set it,
		 and never clear it.  if we find it set, that means
		 clock interrupt latency is high: */
	      if (TME_BUS_SIGNAL_IS_INT(am9513->tme_am9513_socket.tme_am9513_socket_counter_signals[counter_i])) {
#ifdef TME_AM9513_TRACK_INT_RATE
		if (!(am9513->tme_am9513_output_pins & TME_BIT(counter_i))) {
		  counter->tme_am9513_counter_int_sample++;
		  am9513->tme_am9513_output_pins |= TME_BIT(counter_i);
		}
#else  /* !TME_AM9513_TRACK_INT_RATE */
		am9513->tme_am9513_output_pins |= TME_BIT(counter_i);
#endif /* !TME_AM9513_TRACK_INT_RATE */
	      }

	      /* otherwise, always toggle it like we're supposed to: */
	      else {
		am9513->tme_am9513_output_pins ^= TME_BIT(counter_i);
	      }
	      break;
	    default:
	      abort();
	    }
	    
	    /* if this counter reloads, reload it, otherwise disarm it: */
	    /* XXX is disarming it the right thing to do? */
	    if (counter_mode & TME_AM9513_CM_REPEAT_ENA) {
	      counter->tme_am9513_counter_cntr = counter->tme_am9513_counter_load;
	    }
	    else {
	      counter_elapsed = counter->tme_am9513_counter_cntr;
	      counter->tme_am9513_counter_flags &= ~TME_AM9513_COUNTER_FLAG_ARMED;
	      break;
	    }
	  }
	  counter->tme_am9513_counter_cntr -= counter_elapsed;
	
	  /* calculate the number of basic ticks until this counter expires: */
	  if (counter->tme_am9513_counter_cntr > 0) {
	    basic_sleep = TME_MIN(basic_sleep, 
				  counter->tme_am9513_counter_cntr * divisor);
	  }
	}

	break;

      default:
	abort();
      }

      /* no other bits can be set in the mode: */
      if (counter_mode & ~(TME_AM9513_CM_SOURCE_MASK
			   | TME_AM9513_CM_REPEAT_ENA
			   | TME_AM9513_CM_OUTPUT_MASK)) {
	abort();
      }

#ifdef TME_AM9513_TRACK_INT_RATE

      /* update the sample time: */
      counter->tme_am9513_counter_int_sample_time += elapsed;

      /* if the sample time has finished, report on the interrupt
         rate: */
      if (TME_TIME_GET_SEC(counter->tme_am9513_counter_int_sample_time)
	  >= TME_AM9513_TRACK_INT_RATE) {
	if (counter->tme_am9513_counter_int_sample > 0) {
	  tme_log(TME_AM9513_LOG_HANDLE(am9513),
		  0, TME_OK,
		  (TME_AM9513_LOG_HANDLE(am9513),
		   "timer %d interrupt rate: %ld %ld/sec",
		   counter_i,
		   counter->tme_am9513_counter_int_sample,
		   TME_TIME_GET_SEC(counter->tme_am9513_counter_int_sample_time)));
	}

	/* reset the sample: */
	counter->tme_am9513_counter_int_sample_time = 0;
	counter->tme_am9513_counter_int_sample = 0;
      }
#endif /* TME_AM9513_TRACK_INT_RATE */
    }

    /* if we need to, call out for the output pins: */
    if (am9513->tme_am9513_output_pins
	!= am9513->tme_am9513_output_pins_last) {
      _tme_am9513_callout(am9513);
    }

    /* sleep: */
    tme_thread_sleep_yield(TME_TIME_SET_USEC((basic_sleep * 1000) / am9513->tme_am9513_basic_clock_msec), &am9513->tme_am9513_mutex);
  }
  /* NOTREACHED */
  tme_thread_exit(&am9513->tme_am9513_mutex);
}

/* the am9513 bus cycle handler: */
static int
_tme_am9513_bus_cycle(void *_am9513, struct tme_bus_cycle *cycle_init)
{
  struct tme_am9513 *am9513;
  tme_bus_addr32_t am9513_address_last;
  tme_uint8_t data_pointer, group_pointer, element_pointer, data_pointer_next;
  tme_uint16_t byte_pointer;
  int is_cmd;
  tme_uint16_t *value, buffer, cmd;
  struct tme_am9513_counter *counter;
  struct tme_bus_cycle cycle_resp;
  int need_callout;

  /* recover our data structure: */
  am9513 = (struct tme_am9513 *) _am9513;

  /* the requested cycle must be within range: */
  am9513_address_last = am9513->tme_am9513_device.tme_bus_device_address_last;
  assert(cycle_init->tme_bus_cycle_address <= am9513_address_last);
  assert(cycle_init->tme_bus_cycle_size <= (am9513_address_last - cycle_init->tme_bus_cycle_address) + 1);

  /* see if this is a command or data access: */
  is_cmd = ((am9513->tme_am9513_address_cmd
	     > am9513->tme_am9513_address_data)
	    == (cycle_init->tme_bus_cycle_address
		>= TME_MAX(am9513->tme_am9513_address_cmd,
			   am9513->tme_am9513_address_data)));

  /* assume that we won't need to call out: */
  need_callout = FALSE;

  /* lock the mutex: */
  tme_mutex_lock(&am9513->tme_am9513_mutex);

  /* assume there is no counter involved in this cycle, get out
     the data pointer, and assume that the data pointer will be
     unchanged: */
  counter = NULL;
  data_pointer = am9513->tme_am9513_data_pointer;
  data_pointer_next = data_pointer;
  byte_pointer = (am9513->tme_am9513_status & TME_AM9513_STATUS_BYTE_POINTER);

  /* initialize value to silence -Wuninitialized: */
  value = NULL;

  /* reads of the command register get the status, and writes to the
     command register are just processed: */
  if (is_cmd) {
    value = (cycle_init->tme_bus_cycle_type == TME_BUS_CYCLE_READ
	     ? &am9513->tme_am9513_status
	     : NULL);
    byte_pointer = TME_AM9513_STATUS_BYTE_POINTER;
  }

  /* transfers to or from the data register involve whatever the Data
     Pointer Register is pointing to: */
  else {

    /* take out the Group Pointer and Element Pointer fields: */
    group_pointer = TME_FIELD_EXTRACTU(data_pointer, 0, 3);
    element_pointer = TME_FIELD_EXTRACTU(data_pointer, 3, 2);
    
    /* dispatch on the Group Pointer: */
    switch (group_pointer) {
      
      /* Groups 000 and 110 are illegal: */
    case 0:
    case 6:
      value = &buffer;
      break;
      
      /* Groups 001 through 101 are Counters: */
    case 1:
    case 2:
    case 3:
    case 4:
    case 5:
      counter = &am9513->tme_am9513_counters[group_pointer - 1];
      
      /* dispatch on the Element Pointer: */
      switch (element_pointer) {
	
	/* 00 = Mode Register, Element Cycle Increment: */
	/* 01 = Load Register, Element Cycle Increment: */
	/* 10 = Hold Register, Element Cycle Increment: */
      case 0:
      case 1:
      case 2:
	value = &counter->tme_am9513_counter_regs[element_pointer];
	element_pointer++;
	if (element_pointer == TME_ARRAY_ELS(counter->tme_am9513_counter_regs)) {
	  element_pointer = 0;
	  group_pointer++;
	  if (group_pointer - 1 == TME_ARRAY_ELS(am9513->tme_am9513_counters)) {
	    group_pointer = 1;
	  }
	}
	break;
	
	/* 11 = Hold Register, Hold Cycle Increment: */
      case 3:
	value = &counter->tme_am9513_counter_hold;
	group_pointer++;
	if (group_pointer - 1 == TME_ARRAY_ELS(am9513->tme_am9513_counters)) {
	  group_pointer = 1;
	}
	break;
      }

      /* if this is a read, log it.  remember that the element pointer
	 has been incremented: */
      if (cycle_init->tme_bus_cycle_type == TME_BUS_CYCLE_READ) {
	tme_log(TME_AM9513_LOG_HANDLE(am9513),
		100000, TME_OK,
		(TME_AM9513_LOG_HANDLE(am9513),
		 "read Timer %d %s Register (data pointer 0x%02x) = 0x%04x",
		 group_pointer,
		 (element_pointer == 1
		  ? "Mode"
		  : element_pointer == 2
		  ? "Load"
		  : "Hold"),
		 data_pointer,
		 *value));
      }

      break;
      
      /* Group 111 is the Control Group: */
    case 7:
      
      /* dispatch on the Element Pointer: */
      switch (element_pointer) {
	
	/* 00 = Alarm Register 1, Control Cycle Increment: */
	/* 01 = Alarm Register 2, Control Cycle Increment: */
	/* 10 = Master Mode Register, Control Cycle Increment: */
      case 0:
      case 1:
      case 2:
	value = &am9513->tme_am9513_control_regs[element_pointer];
	element_pointer++;
	if (element_pointer == TME_ARRAY_ELS(am9513->tme_am9513_control_regs)) {
	  element_pointer = 0;
	}
	break;
	
	/* 11 = Status Register, No Increment: */
      case 3:
	value = &am9513->tme_am9513_status;
	break;
      }

      /* if this is a read, log it.  remember that the element pointer
	 has been incremented: */
      if (cycle_init->tme_bus_cycle_type == TME_BUS_CYCLE_READ) {
	tme_log(TME_AM9513_LOG_HANDLE(am9513),
		100000, TME_OK,
		(TME_AM9513_LOG_HANDLE(am9513),
		 "read %s (data pointer 0x%02x) = 0x%04x",
		 (element_pointer == 1
		  ? "Alarm Register 1"
		  : element_pointer == 2
		  ? "Alarm Register 2"
		  : element_pointer == 0
		  ? "Master Mode Register"
		  : "Status Register"),
		 data_pointer,
		 *value));
      }

      break;
    }

    /* set what the next data pointer might be: */
    data_pointer_next = (element_pointer << 3) | group_pointer;
  }

  /* get the value to route on the bus.  if we're in 8-bit bus mode,
     route in D7-D0 either the low byte of the value or the high byte,
     depending on the Byte Pointer bit, with D15-D8 as all-bits-one: */
  buffer = (value == NULL ? 0xffff : *value);
  if ((am9513->tme_am9513_mmr & TME_AM9513_MMR_BUS_16BIT) == 0) {
    if (byte_pointer == 0) {
      buffer >>= 8;
    }
    buffer |= 0xff00;
  }

  /* run the bus cycle: */
  cycle_resp.tme_bus_cycle_buffer = (tme_uint8_t *) &buffer;
  cycle_resp.tme_bus_cycle_lane_routing = tme_am9513_router;
  cycle_resp.tme_bus_cycle_address = 0;
  cycle_resp.tme_bus_cycle_buffer_increment = 1;
  cycle_resp.tme_bus_cycle_type =
    (cycle_init->tme_bus_cycle_type
     ^ (TME_BUS_CYCLE_READ
	| TME_BUS_CYCLE_WRITE));
  cycle_resp.tme_bus_cycle_size = sizeof(tme_uint16_t);
  cycle_resp.tme_bus_cycle_port = 
    TME_BUS_CYCLE_PORT(am9513->tme_am9513_port_least_lane,
		       TME_BUS16_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) {
    
    /* if this is a write to the command register: */
    if (is_cmd) {

      /* dispatch on the command: */
      cmd = buffer;

      /* set 16-bit mode: */
      if (cmd == TME_AM9513_CMD_SET_BUS_16BIT) {
	am9513->tme_am9513_mmr |= TME_AM9513_MMR_BUS_16BIT;
	tme_log(TME_AM9513_LOG_HANDLE(am9513), 100000, TME_OK,
		(TME_AM9513_LOG_HANDLE(am9513), "set 16-bit bus mode"));
      }
      
      /* load data pointer: */
      else if ((cmd & 0xe0) == 0x00) {
	am9513->tme_am9513_data_pointer = (cmd & 0x1f);
	am9513->tme_am9513_status |= TME_AM9513_STATUS_BYTE_POINTER;
	tme_log(TME_AM9513_LOG_HANDLE(am9513), 100000, TME_OK,
		(TME_AM9513_LOG_HANDLE(am9513),
		 "set data pointer to %02x",
		 am9513->tme_am9513_data_pointer));
      }
      
      /* master reset: */
      else if (cmd == TME_AM9513_CMD_RESET) {
	_tme_am9513_reset(am9513);
	tme_log(TME_AM9513_LOG_HANDLE(am9513), 100000, TME_OK,
		(TME_AM9513_LOG_HANDLE(am9513), "master reset"));
      }

      /* load counters: */
      else if ((cmd & ~_TME_AM9513_CMD_TIMERS_MASK)
	       == TME_AM9513_CMD_LOAD_COUNTERS) {
	_tme_am9513_counters_load(am9513, cmd & _TME_AM9513_CMD_TIMERS_MASK);
	tme_log(TME_AM9513_LOG_HANDLE(am9513), 100000, TME_OK,
		(TME_AM9513_LOG_HANDLE(am9513), 
		 "load counters, timer mask 0x%02x",
		 (cmd & _TME_AM9513_CMD_TIMERS_MASK)));
      }

      /* clear toggle out: */
      else if ((cmd & ~_TME_AM9513_CMD_TIMER_MASK)
	       == TME_AM9513_CMD_CLEAR_TOGGLE_OUT) {

	/* clear the output pin for the given timer: */
	group_pointer = cmd & _TME_AM9513_CMD_TIMER_MASK;
	am9513->tme_am9513_output_pins &= ~TME_BIT(group_pointer - 1);
	need_callout = TRUE;

	tme_log(TME_AM9513_LOG_HANDLE(am9513), 100000, TME_OK,
		(TME_AM9513_LOG_HANDLE(am9513), 
		 "Timer %d clear toggle out",
		 (cmd & _TME_AM9513_CMD_TIMER_MASK)));
      }

      /* load and arm counters: */
      else if ((cmd & ~_TME_AM9513_CMD_TIMERS_MASK)
	       == TME_AM9513_CMD_LOAD_ARM_COUNTERS) {
	_tme_am9513_counters_load(am9513, cmd & _TME_AM9513_CMD_TIMERS_MASK);
	_tme_am9513_counters_arm(am9513, cmd & _TME_AM9513_CMD_TIMERS_MASK);
	tme_log(TME_AM9513_LOG_HANDLE(am9513), 100000, TME_OK,
		(TME_AM9513_LOG_HANDLE(am9513), 
		 "load and arm counters, timer mask 0x%02x",
		 (cmd & _TME_AM9513_CMD_TIMERS_MASK)));
      }

      /* disarm counters: */
      else if ((cmd & ~_TME_AM9513_CMD_TIMERS_MASK)
	       == TME_AM9513_CMD_DISARM_COUNTERS) {
	_tme_am9513_counters_disarm(am9513, cmd & _TME_AM9513_CMD_TIMERS_MASK);
	tme_log(TME_AM9513_LOG_HANDLE(am9513), 100000, TME_OK,
		(TME_AM9513_LOG_HANDLE(am9513), 
		 "disarm counters, timer mask 0x%02x",
		 (cmd & _TME_AM9513_CMD_TIMERS_MASK)));
      }

      /* arm counters: */
      else if ((cmd & ~_TME_AM9513_CMD_TIMERS_MASK)
	       == TME_AM9513_CMD_ARM_COUNTERS) {
	_tme_am9513_counters_arm(am9513, cmd & _TME_AM9513_CMD_TIMERS_MASK);
	tme_log(TME_AM9513_LOG_HANDLE(am9513), 100000, TME_OK,
		(TME_AM9513_LOG_HANDLE(am9513), 
		 "arm counters, timer mask 0x%02x",
		 (cmd & _TME_AM9513_CMD_TIMERS_MASK)));
      }

      else {
	abort();
      }
    }

    /* otherwise this is a write to a data register: */
    else {

      /* take out the Group Pointer and Element Pointer fields: */
      group_pointer = TME_FIELD_EXTRACTU(data_pointer, 0, 3);
      element_pointer = TME_FIELD_EXTRACTU(data_pointer, 3, 2);
    
      /* update the register: */
      if ((am9513->tme_am9513_mmr & TME_AM9513_MMR_BUS_16BIT) == 0) {      
	if (byte_pointer) {
	  *value = (*value & 0xff00) | (buffer & 0xff);
	}
	else {
	  *value = (*value & 0x00ff) | ((buffer & 0xff) << 8);
	}
      }
      else {
	*value = buffer;
      }

      /* if this changed a counter register: */
      if (counter != NULL) {

	/* log the write: */
	tme_log(TME_AM9513_LOG_HANDLE(am9513), 100000, TME_OK,
		(TME_AM9513_LOG_HANDLE(am9513),
		 "write Timer %d %s Register (data pointer 0x%02x) = 0x%04x",
		 group_pointer,
		 (element_pointer == 0
		  ? "Mode"
		  : element_pointer == 1
		  ? "Load"
		  : "Hold"),
		 data_pointer,
		 *value));
	
	/* dispatch on the Element Pointer: */
	switch (element_pointer) {
	
	  /* 00 = Mode Register: */
	case 0:
	  /* XXX TBD */
	  break;
	  
	  /* 01 = Load Register: */
	case 1:
	  /* XXX TBD */
	  break;
	  
	  /* 10 = Hold Register: */
	  /* 11 = Hold Register: */
	case 2:
	case 3:
	  /* XXX TBD */
	  break;
	}
      }

      /* if this changed a control register: */
      else {

	/* log the write: */
	tme_log(TME_AM9513_LOG_HANDLE(am9513), 100000, TME_OK,
		(TME_AM9513_LOG_HANDLE(am9513),
		 "write %s (data pointer 0x%02x) = 0x%04x",
		 (element_pointer == 0
		  ? "Alarm Register 1"
		  : element_pointer == 1
		  ? "Alarm Register 2"
		  : element_pointer == 2
		  ? "Master Mode Register"
		  : "Status Register"),
		 data_pointer,
		 *value));

	/* dispatch on the Element Pointer: */
	switch (element_pointer) {
	  
	  /* 00 = Alarm Register 1: */
	case 0:
	  /* XXX TBD */
	  break;
	  
	  /* 01 = Alarm Register 2: */
	case 1:
	  /* XXX TBD */
	  break;
	  
	  /* 10 = Master Mode Register: */
	case 2:
	  /* XXX TBD */
	  break;
	  
	  /* 11 = Status Register: */
	case 3:
	  /* XXX TBD */
	  break;
	}

      }
    }
  }

  /* update the byte pointer and data pointer register as needed: */
  if (!is_cmd) {
    if ((am9513->tme_am9513_mmr & TME_AM9513_MMR_BUS_16BIT) == 0) {
      byte_pointer ^= TME_AM9513_STATUS_BYTE_POINTER;
    }
    am9513->tme_am9513_status = ((am9513->tme_am9513_status
				  & ~TME_AM9513_STATUS_BYTE_POINTER)
				 | byte_pointer);
    if (byte_pointer != 0
	&& !(am9513->tme_am9513_mmr & TME_AM9513_MMR_NO_INCREMENT)) {
      am9513->tme_am9513_data_pointer = data_pointer_next;
    }
    tme_log(TME_AM9513_LOG_HANDLE(am9513), 100000, TME_OK,
	    (TME_AM9513_LOG_HANDLE(am9513),
	     "data pointer now 0x%02x (byte pointer %d)",
	     am9513->tme_am9513_data_pointer,
	     byte_pointer / TME_AM9513_STATUS_BYTE_POINTER));
  }

  /* if we need callouts, make them: */
  if (need_callout) {
    _tme_am9513_callout(am9513);
  }
    
  /* unlock the mutex: */
  tme_mutex_unlock(&am9513->tme_am9513_mutex);

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

/* the am9513 TLB filler: */
static int
_tme_am9513_tlb_fill(void *_am9513, struct tme_bus_tlb *tlb, 
		     tme_bus_addr_t address, unsigned int cycles)
{
  struct tme_am9513 *am9513;
  tme_bus_addr32_t am9513_address_last;

  /* recover our data structure: */
  am9513 = (struct tme_am9513 *) _am9513;

  /* the address must be within range: */
  am9513_address_last = am9513->tme_am9513_device.tme_bus_device_address_last;
  assert(address <= am9513_address_last);

  /* initialize the TLB entry: */
  tme_bus_tlb_initialize(tlb);

  /* this TLB entry can cover the whole device: */
  tlb->tme_bus_tlb_addr_first = 0;
  tlb->tme_bus_tlb_addr_last = am9513_address_last;

  /* allow reading and writing: */
  tlb->tme_bus_tlb_cycles_ok = TME_BUS_CYCLE_READ | TME_BUS_CYCLE_WRITE;

  /* our bus cycle handler: */
  tlb->tme_bus_tlb_cycle_private = am9513;
  tlb->tme_bus_tlb_cycle = _tme_am9513_bus_cycle;

  return (TME_OK);
}

/* the new am9513 element function: */
TME_ELEMENT_NEW_DECL(tme_ic_am9513) {
  const struct tme_am9513_socket *socket;
  struct tme_am9513 *am9513;
  struct tme_am9513_socket socket_real;
  tme_bus_addr_t address_mask;

  /* dispatch on our socket version: */
  socket = (const struct tme_am9513_socket *) extra;
  if (socket == NULL) {
    tme_output_append_error(_output, _("need an ic socket"));
    return (ENXIO);
  }
  switch (socket->tme_am9513_socket_version) {
  case TME_AM9513_SOCKET_0:
    socket_real = *socket;
    break;
  default: 
    tme_output_append_error(_output, _("socket type"));
    return (EOPNOTSUPP);
  }
    
  /* we take no arguments: */
  if (args[1] != NULL) {
    tme_output_append_error(_output,
			    "%s %s, %s %s",
			    args[1],
			    _("unexpected"),
			    _("usage:"),
			    args[0]);
    return (EINVAL);
  }

  /* start the am9513 structure: */
  am9513 = tme_new0(struct tme_am9513, 1);
  am9513->tme_am9513_socket = socket_real;
  am9513->tme_am9513_basic_clock_msec = am9513->tme_am9513_basic_clock / 1000;
  am9513->tme_am9513_element = element;
  _tme_am9513_reset(am9513);

  /* figure our address mask, up to the nearest power of two: */
  address_mask = TME_MAX(am9513->tme_am9513_address_cmd,
			 am9513->tme_am9513_address_data);
  address_mask += sizeof(tme_uint16_t);
  if (address_mask & (address_mask - 1)) {
    for (; address_mask & (address_mask - 1); address_mask &= (address_mask - 1));
    address_mask <<= 1;
  }
  address_mask -= 1;

  /* initialize our simple bus device descriptor: */
  am9513->tme_am9513_device.tme_bus_device_tlb_fill = _tme_am9513_tlb_fill;
  am9513->tme_am9513_device.tme_bus_device_address_last = address_mask;

  /* start the timer thread: */
  tme_mutex_init(&am9513->tme_am9513_mutex);
  tme_thread_create(&am9513->tme_am9513_thread, (tme_thread_t) _tme_am9513_th_timer, am9513);

  /* fill the element: */
  element->tme_element_private = am9513;
  element->tme_element_connections_new = tme_bus_device_connections_new;

  return (TME_OK);
}
