/* $Id: isil7170.c,v 1.6 2010/06/05 14:37:27 fredette Exp $ */

/* ic/isil7170.c - implementation of Intersil 7170 emulation: */

/*
 * Copyright (c) 2004 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: isil7170.c,v 1.6 2010/06/05 14:37:27 fredette Exp $");

/* includes: */
#include <tme/generic/bus-device.h>
#include <tme/ic/isil7170.h>
#include <tme/misc.h>

/* macros: */

/* register addresses: */
#define TME_ISIL7170_REG_CSEC		(0)
#define TME_ISIL7170_REG_HOUR		(1)
#define TME_ISIL7170_REG_MIN		(2)
#define TME_ISIL7170_REG_SEC		(3)
#define TME_ISIL7170_REG_MON		(4)
#define TME_ISIL7170_REG_DAY		(5)
#define TME_ISIL7170_REG_YEAR		(6)
#define TME_ISIL7170_REG_DOW		(7)
#define TME_ISIL7170_REG_CMP_CSEC	(8)
#define TME_ISIL7170_REG_CMP_HOUR	(9)
#define TME_ISIL7170_REG_CMP_MIN	(10)
#define TME_ISIL7170_REG_CMP_SEC	(11)
#define TME_ISIL7170_REG_CMP_MON	(12)
#define TME_ISIL7170_REG_CMP_DAY	(13)
#define TME_ISIL7170_REG_CMP_YEAR	(14)
#define TME_ISIL7170_REG_CMP_DOW	(15)
#define TME_ISIL7170_REG_INT		(16)
#define TME_ISIL7170_REG_CMD		(17)
#define TME_ISIL7170_REGS_COUNT		(32)

/* bits in the Interrupt register: */
#define	TME_ISIL7170_INT_PENDING	TME_BIT(7)	/* interrupt pending */
#define	TME_ISIL7170_INT_DAY		TME_BIT(6)	/* day periodic interrupt */
#define	TME_ISIL7170_INT_HOUR		TME_BIT(5)	/* hour periodic interrupt */
#define	TME_ISIL7170_INT_MIN		TME_BIT(4)	/* minute periodic interrupt */
#define	TME_ISIL7170_INT_SEC		TME_BIT(3)	/* second periodic interrupt */
#define	TME_ISIL7170_INT_TSEC		TME_BIT(2)	/* 1/10 second periodic interrupt */
#define	TME_ISIL7170_INT_HSEC		TME_BIT(1)	/* 1/100 periodic second interrupt */
#define	TME_ISIL7170_INT_ALARM		TME_BIT(0)	/* time match interrupt */

/* bits in the Command register: */
#define	TME_ISIL7170_CMD_TEST		TME_BIT(5)	/* test mode */
#define	TME_ISIL7170_CMD_INTENA		TME_BIT(4)	/* interrupt enable */
#define	TME_ISIL7170_CMD_RUN		TME_BIT(3)	/* running */
#define	TME_ISIL7170_CMD_FMT24		TME_BIT(2)	/* 24-hour format (instead of 12-hour) */
#define TME_ISIL7170_CMD_FREQ_MASK	(0x3)		/* frequency mask */
#define  TME_ISIL7170_CMD_FREQ_4M	(0x3)		/* frequency 4.194304MHz */
#define  TME_ISIL7170_CMD_FREQ_2M	(0x2)		/* frequency 2.097152MHz */
#define  TME_ISIL7170_CMD_FREQ_1M	(0x1)		/* frequency 1.048576MHz */
#define  TME_ISIL7170_CMD_FREQ_32K	(0x0)		/* frequency 32.768KHz */

/* year zero in the chip corresponds to 1968: */
#define TME_ISIL7170_REG_YEAR_0		(1968)

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

#define TME_ISIL7170_LOG_HANDLE(am) (&(am)->tme_isil7170_element->tme_element_log_handle)

/* structures: */
struct tme_isil7170 {

  /* our simple bus device header: */
  struct tme_bus_device tme_isil7170_device;
#define tme_isil7170_element tme_isil7170_device.tme_bus_device_element

  /* our socket: */
  struct tme_isil7170_socket tme_isil7170_socket;
#define tme_isil7170_addr_shift tme_isil7170_socket.tme_isil7170_socket_addr_shift
#define tme_isil7170_port_least_lane tme_isil7170_socket.tme_isil7170_socket_port_least_lane
#define tme_isil7170_clock_basic tme_isil7170_socket.tme_isil7170_socket_clock_basic
#define tme_isil7170_int_signal tme_isil7170_socket.tme_isil7170_socket_int_signal

  /* our mutex: */
  tme_mutex_t tme_isil7170_mutex;

  /* our timer condition: */
  tme_cond_t tme_isil7170_cond_timer;

  /* our thread: */
  tme_threadid_t tme_isil7170_thread;

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

  /* it's easiest to just model the chip as a chunk of memory: */
  tme_uint8_t tme_isil7170_regs[TME_ISIL7170_REGS_COUNT];

  /* the real-time durations, in microseconds, of a hundredth of a
     second and a tenth of a second: */
  unsigned long tme_isil7170_clock_hsec_usec;
  unsigned long tme_isil7170_clock_tsec_usec;

  /* if nonzero, the internal time-of-day needs to be updated: */
  tme_uint8_t tme_isil7170_tod_update;

  /* if nonzero, the interrupt the timer thread is sleeping for: */
  tme_uint8_t tme_isil7170_timer_sleeping;

  /* the interrupt mask: */
  tme_uint8_t tme_isil7170_int_mask;

  /* nonzero if the interrupt signal is asserted: */
  int tme_isil7170_int_asserted;

  /* any scaling factor: */
  unsigned long tme_isil7170_clock_scale;

#ifdef TME_ISIL7170_TRACK_INT_RATE

  /* the end time of this sample: */
  tme_time_t tme_isil7170_int_sample_time;

  /* the number of distinct interrupts that have been delivered during
     this sample: */
  unsigned long tme_isil7170_int_sample;
#endif /* TME_ISIL7170_TRACK_INT_RATE */
};

/* the isil7170 bus router: */
static const tme_bus_lane_t tme_isil7170_router[TME_BUS_ROUTER_SIZE(TME_BUS8_LOG2)] = {
  
  /* [gen]  initiator port size: 8 bits
     [gen]  initiator port least lane: 0: */
  /* D7-D0 */	TME_BUS_LANE_ROUTE(0),
};

/* this sets the frequency on an isil7170: */
static void
_tme_isil7170_freq(struct tme_isil7170 *isil7170)
{
  tme_uint32_t clock_user, clock_basic;
  unsigned long hsec_usec, tsec_usec;

  /* get the user clock frequency: */
  switch (isil7170->tme_isil7170_regs[TME_ISIL7170_REG_CMD] & TME_ISIL7170_CMD_FREQ_MASK) {
  case TME_ISIL7170_CMD_FREQ_4M:  clock_user = TME_ISIL7170_FREQ_4M; break;
  case TME_ISIL7170_CMD_FREQ_2M:  clock_user = TME_ISIL7170_FREQ_2M; break;
  case TME_ISIL7170_CMD_FREQ_1M:  clock_user = TME_ISIL7170_FREQ_1M; break;
  default:
  case TME_ISIL7170_CMD_FREQ_32K: clock_user = TME_ISIL7170_FREQ_32K; break;
  }

  /* get the hardware basic clock frequency: */
  clock_basic = isil7170->tme_isil7170_clock_basic;

  /* calculate the real-time durations, in microseconds, of a tenth
     and hundredth of a second, given the actual basic clock into the
     chip, and what the user claims is the basic clock into the chip.
     we have to be careful to avoid overflow here: */
  if (clock_user == clock_basic) {
    hsec_usec = 10000;
    tsec_usec = 100000;
  }
  else {
    hsec_usec = ((1000 * clock_user) / (clock_basic / 10));
    tsec_usec = ((1000 * clock_user) / (clock_basic / 100));
  }
  
  /* scale the result: */
  hsec_usec *= isil7170->tme_isil7170_clock_scale;
  tsec_usec *= isil7170->tme_isil7170_clock_scale;

  isil7170->tme_isil7170_clock_hsec_usec = hsec_usec;
  isil7170->tme_isil7170_clock_tsec_usec = tsec_usec;
}

/* this makes isil7170 callouts.  it must be called with the mutex held: */
static void
_tme_isil7170_callout(struct tme_isil7170 *isil7170)
{
  struct tme_bus_connection *conn_bus;
  int again;
  int int_asserted;
  int rc;

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

  /* callouts are now running: */
  isil7170->tme_isil7170_callouts_running = TRUE;

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

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

    /* see if there are any pending, unmasked interrupts: */
    int_asserted = (isil7170->tme_isil7170_regs[TME_ISIL7170_REG_INT]
		    & isil7170->tme_isil7170_int_mask);

    /* update our interrupt-pending flag: */
    if (int_asserted) {
      isil7170->tme_isil7170_regs[TME_ISIL7170_REG_INT]
	= ((isil7170->tme_isil7170_regs[TME_ISIL7170_REG_INT]
	    & ~TME_ISIL7170_INT_PENDING)
	   | (int_asserted
	      ? TME_ISIL7170_INT_PENDING
	      : 0));
    }

    /* see if our interrupt signal should be asserted: */
    int_asserted
      = (int_asserted
	 && (isil7170->tme_isil7170_regs[TME_ISIL7170_REG_CMD]
	     & TME_ISIL7170_CMD_INTENA));

    /* if our interrupt signal has changed: */
    if (!int_asserted != !isil7170->tme_isil7170_int_asserted) {

      /* unlock our mutex: */
      tme_mutex_unlock(&isil7170->tme_isil7170_mutex);

      rc = (*conn_bus->tme_bus_signal)
	(conn_bus,
	 isil7170->tme_isil7170_int_signal
	 | (int_asserted
	    ? TME_BUS_SIGNAL_LEVEL_ASSERTED
	    : TME_BUS_SIGNAL_LEVEL_NEGATED));

      /* lock our mutex: */
      tme_mutex_lock(&isil7170->tme_isil7170_mutex);
      
      /* if this call out succeeded, update the interrupt-asserted flag: */
      if (rc == TME_OK) {
	isil7170->tme_isil7170_int_asserted = int_asserted;
	again = TRUE;
      }
    }
  }

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

/* this resets an isil7170: */
static void
_tme_isil7170_reset(struct tme_isil7170 *isil7170)
{

  /* clear the interrupt mask: */
  isil7170->tme_isil7170_int_mask = 0;

  /* clear the command register: */
  isil7170->tme_isil7170_regs[TME_ISIL7170_REG_CMD] = 0;

  /* update the frequency: */
  _tme_isil7170_freq(isil7170);

  /* callout to update our interrupt signal: */
  _tme_isil7170_callout(isil7170);
}

/* the isil7170 timer thread: */
static _tme_thret
_tme_isil7170_th_timer(struct tme_isil7170 *isil7170)
{
  tme_uint8_t int_mask;
  tme_uint32_t sleep_usec;
#ifdef TME_ISIL7170_TRACK_INT_RATE
  tme_time_t now;
#endif /* TME_ISIL7170_TRACK_INT_RATE */

  tme_thread_enter(&isil7170->tme_isil7170_mutex);

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

    /* if we were sleeping: */
    int_mask = isil7170->tme_isil7170_timer_sleeping;
    isil7170->tme_isil7170_timer_sleeping = 0;
    if (int_mask) {

#ifdef TME_ISIL7170_TRACK_INT_RATE

      /* if no interrupt is pending, and this interrupt is not masked,
	 we will deliver another interrupt: */
      if (!(isil7170->tme_isil7170_regs[TME_ISIL7170_REG_INT]
	    & TME_ISIL7170_INT_PENDING)
	  && (int_mask
	      & isil7170->tme_isil7170_int_mask)) {
	isil7170->tme_isil7170_int_sample++;
      }

      /* if the sample time has finished, report on the interrupt rate: */
      now = tme_thread_get_time();
      if (now > isil7170->tme_isil7170_int_sample_time) {
	if (isil7170->tme_isil7170_int_sample > 0) {
	  tme_log(TME_ISIL7170_LOG_HANDLE(isil7170),
		  0, TME_OK,
		  (TME_ISIL7170_LOG_HANDLE(isil7170),
		   "timer interrupt rate: %ld/sec",
		   (TME_TIME_SET_SEC(isil7170->tme_isil7170_int_sample)
		    / (now - (isil7170->tme_isil7170_int_sample_time - TME_ISIL7170_TRACK_INT_RATE
			      )))));
	}

	/* reset the sample: */
        now += TME_ISIL7170_TRACK_INT_RATE;
	isil7170->tme_isil7170_int_sample_time = now;
	isil7170->tme_isil7170_int_sample = 0;
      }

#endif /* TME_ISIL7170_TRACK_INT_RATE */

      /* update the interrupt register: */
      isil7170->tme_isil7170_regs[TME_ISIL7170_REG_INT] |= int_mask;

      /* callout to update our interrupt signal: */
      _tme_isil7170_callout(isil7170);
    }

    /* get the interrupt mask: */
    int_mask = isil7170->tme_isil7170_int_mask;

    /* if the 1/100 second periodic interrupt is unmasked: */
    if (int_mask & TME_ISIL7170_INT_HSEC) {
      int_mask = TME_ISIL7170_INT_HSEC;
      sleep_usec = isil7170->tme_isil7170_clock_hsec_usec;
    }

    /* if the 1/10 second periodic interrupt is unmasked: */
    else if (int_mask & TME_ISIL7170_INT_TSEC) {
      int_mask = TME_ISIL7170_INT_TSEC;
      sleep_usec = isil7170->tme_isil7170_clock_tsec_usec;
    }

    /* otherwise, all periodic interrupts are masked.  wait until one
       of them is not: */
    else {
      tme_cond_wait_yield(&isil7170->tme_isil7170_cond_timer,
			  &isil7170->tme_isil7170_mutex);
      continue;
    }

    /* we are sleeping: */
    isil7170->tme_isil7170_timer_sleeping = int_mask;

    /* sleep: */
    tme_thread_sleep_yield(TME_TIME_SET_USEC(sleep_usec), &isil7170->tme_isil7170_mutex);
    
  }
  /* NOTREACHED */
  tme_thread_exit(&isil7170->tme_isil7170_mutex);
}

/* the isil7170 bus cycle handler: */
static int
_tme_isil7170_bus_cycle(void *_isil7170, struct tme_bus_cycle *cycle_init)
{
  struct tme_isil7170 *isil7170;
  tme_bus_addr32_t address, isil7170_address_last;
  tme_uint8_t buffer, value, value_old;
  struct tme_bus_cycle cycle_resp;
  unsigned int reg;
  tme_time_t now;
  tme_date_t *now_date, now_date_buffer;

  /* recover our data structure: */
  isil7170 = (struct tme_isil7170 *) _isil7170;

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

  /* get the register being accessed: */
  address = cycle_init->tme_bus_cycle_address;
  reg = address >> isil7170->tme_isil7170_addr_shift;

  /* lock the mutex: */
  tme_mutex_lock(&isil7170->tme_isil7170_mutex);

  /* if the clock is running and this address is in the time-of-day
     registers, or if this is a write to the command register: */
  if (((isil7170->tme_isil7170_regs[TME_ISIL7170_REG_CMD] & TME_ISIL7170_CMD_RUN)
       && reg <= TME_ISIL7170_REG_DOW)
      || (cycle_init->tme_bus_cycle_type == TME_BUS_CYCLE_WRITE
	  && reg == TME_ISIL7170_REG_CMD)) {

    /* sample the time of day: */
    now = tme_thread_get_time();
    now_date = tme_time_get_date(now, &now_date_buffer);

    /* put the time-of-day into the registers: */
    isil7170->tme_isil7170_regs[TME_ISIL7170_REG_CSEC] = TME_TIME_GET_MSEC(now % TME_FRAC_PER_SEC) / 10;
    isil7170->tme_isil7170_regs[TME_ISIL7170_REG_HOUR] = TME_DATE_HOUR(now_date);
    isil7170->tme_isil7170_regs[TME_ISIL7170_REG_MIN] = TME_DATE_MIN(now_date);
    isil7170->tme_isil7170_regs[TME_ISIL7170_REG_SEC] = TME_DATE_SEC(now_date);
    isil7170->tme_isil7170_regs[TME_ISIL7170_REG_MON] = TME_DATE_MONTH(now_date);
    isil7170->tme_isil7170_regs[TME_ISIL7170_REG_DAY] = TME_DATE_MDAY(now_date);
    isil7170->tme_isil7170_regs[TME_ISIL7170_REG_YEAR] = TME_DATE_YEAR(now_date);
    isil7170->tme_isil7170_regs[TME_ISIL7170_REG_DOW] = TME_DATE_WDAY(now_date);
  }

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

    /* run the bus cycle: */
    cycle_resp.tme_bus_cycle_buffer = &buffer;
    cycle_resp.tme_bus_cycle_lane_routing = tme_isil7170_router;
    cycle_resp.tme_bus_cycle_address = 0;
    cycle_resp.tme_bus_cycle_buffer_increment = 1;
    cycle_resp.tme_bus_cycle_type = TME_BUS_CYCLE_READ;
    cycle_resp.tme_bus_cycle_size = sizeof(buffer);
    cycle_resp.tme_bus_cycle_port = 
      TME_BUS_CYCLE_PORT(isil7170->tme_isil7170_port_least_lane,
			 TME_BUS8_LOG2);
    tme_bus_cycle_xfer(cycle_init, &cycle_resp);
    value = buffer;
    
    /* log this write: */
    tme_log(TME_ISIL7170_LOG_HANDLE(isil7170), 100000, TME_OK,
	    (TME_ISIL7170_LOG_HANDLE(isil7170),
	     "reg %d write %02x",
	     reg, value));

    /* dispatch on the register: */
    switch (reg) {

    case TME_ISIL7170_REG_CSEC:
    case TME_ISIL7170_REG_HOUR:
    case TME_ISIL7170_REG_MIN:
    case TME_ISIL7170_REG_SEC:
    case TME_ISIL7170_REG_MON:
    case TME_ISIL7170_REG_DAY:
    case TME_ISIL7170_REG_YEAR:
    case TME_ISIL7170_REG_DOW:

      /* flag that the time-of-day needs to be updated: */
      isil7170->tme_isil7170_tod_update = TRUE;

      /* FALLTHROUGH */

    case TME_ISIL7170_REG_CMP_CSEC:
    case TME_ISIL7170_REG_CMP_HOUR:
    case TME_ISIL7170_REG_CMP_MIN:
    case TME_ISIL7170_REG_CMP_SEC:
    case TME_ISIL7170_REG_CMP_MON:
    case TME_ISIL7170_REG_CMP_DAY:
    case TME_ISIL7170_REG_CMP_YEAR:
    case TME_ISIL7170_REG_CMP_DOW:

      /* update the register: */
      isil7170->tme_isil7170_regs[reg] = value;
      break;

    case TME_ISIL7170_REG_INT:

      /* we don't support the alarm interrupt, or any of the daily,
	 hourly, etc., interrupts: */
      if (value & (TME_ISIL7170_INT_DAY
		   | TME_ISIL7170_INT_HOUR
		   | TME_ISIL7170_INT_MIN
		   | TME_ISIL7170_INT_SEC
		   | TME_ISIL7170_INT_ALARM)) {
	abort();
      }

      /* update the interrupt mask: */
      isil7170->tme_isil7170_int_mask = (value & ~TME_ISIL7170_INT_PENDING);

      /* callout to update our interrupt signal: */
      _tme_isil7170_callout(isil7170);

      /* notify the timer thread: */
      tme_cond_notify(&isil7170->tme_isil7170_cond_timer, FALSE);

      break;

    case TME_ISIL7170_REG_CMD:

      /* we don't support the test mode: */
      if (value & TME_ISIL7170_CMD_TEST) {
	abort();
      }

      /* update the command register: */
      value_old = isil7170->tme_isil7170_regs[TME_ISIL7170_REG_CMD];
      isil7170->tme_isil7170_regs[TME_ISIL7170_REG_CMD] = value;

      /* if the frequency changed, update our periodic intervals: */
      if ((value_old ^ value) & TME_ISIL7170_CMD_FREQ_MASK) {
	_tme_isil7170_freq(isil7170);
      }

      /* if the interrupt enable changed, callout to update our
         interrupt signal: */
      if ((value_old ^ value) & TME_ISIL7170_CMD_INTENA) {
	_tme_isil7170_callout(isil7170);
      }

      break;

    default:
      /* ignore */
      break;
    }
  }

  /* otherwise, this is a read: */
  else {
    assert(cycle_init->tme_bus_cycle_type == TME_BUS_CYCLE_READ);
    
    /* dispatch on the register: */
    switch (reg) {

    case TME_ISIL7170_REG_CSEC:
    case TME_ISIL7170_REG_HOUR:
    case TME_ISIL7170_REG_MIN:
    case TME_ISIL7170_REG_SEC:
    case TME_ISIL7170_REG_MON:
    case TME_ISIL7170_REG_DAY:
    case TME_ISIL7170_REG_YEAR:
    case TME_ISIL7170_REG_DOW:
    case TME_ISIL7170_REG_CMP_CSEC:
    case TME_ISIL7170_REG_CMP_HOUR:
    case TME_ISIL7170_REG_CMP_MIN:
    case TME_ISIL7170_REG_CMP_SEC:
    case TME_ISIL7170_REG_CMP_MON:
    case TME_ISIL7170_REG_CMP_DAY:
    case TME_ISIL7170_REG_CMP_YEAR:
    case TME_ISIL7170_REG_CMP_DOW:

      /* read the register: */
      value = isil7170->tme_isil7170_regs[reg];
      break;

    case TME_ISIL7170_REG_INT:

      /* reading the Interrupt register clears the interrupt: */
      value = isil7170->tme_isil7170_regs[TME_ISIL7170_REG_INT];
      isil7170->tme_isil7170_regs[TME_ISIL7170_REG_INT] = 0;

      /* callout to update our interrupt signal: */
      _tme_isil7170_callout(isil7170);

      break;

      /* the Command register, and all undefined registers, return
         garbage when read: */
    case TME_ISIL7170_REG_CMD:
    default:
      value = 0xff;
      break;
    }

    /* log this read: */
    tme_log(TME_ISIL7170_LOG_HANDLE(isil7170), 100000, TME_OK,
	    (TME_ISIL7170_LOG_HANDLE(isil7170),
	     "reg %d read %02x",
	     reg, value));

    /* run the bus cycle: */
    buffer = value;
    cycle_resp.tme_bus_cycle_buffer = &buffer;
    cycle_resp.tme_bus_cycle_lane_routing = tme_isil7170_router;
    cycle_resp.tme_bus_cycle_address = 0;
    cycle_resp.tme_bus_cycle_buffer_increment = 1;
    cycle_resp.tme_bus_cycle_type = TME_BUS_CYCLE_WRITE;
    cycle_resp.tme_bus_cycle_size = sizeof(buffer);
    cycle_resp.tme_bus_cycle_port = 
      TME_BUS_CYCLE_PORT(isil7170->tme_isil7170_port_least_lane,
			 TME_BUS8_LOG2);
    tme_bus_cycle_xfer(cycle_init, &cycle_resp);
  }

  /* if the time-of-day registers have been updated, and the clock
     is running: */
  if (isil7170->tme_isil7170_tod_update
      && (isil7170->tme_isil7170_regs[TME_ISIL7170_REG_CMD]
	  & TME_ISIL7170_CMD_RUN)) {
    
    /* XXX update the host's time-of-day clock? */
    isil7170->tme_isil7170_tod_update = FALSE;
  }
    
  /* unlock the mutex: */
  tme_mutex_unlock(&isil7170->tme_isil7170_mutex);

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

/* the isil7170 TLB filler: */
static int
_tme_isil7170_tlb_fill(void *_isil7170, struct tme_bus_tlb *tlb, 
		     tme_bus_addr_t address, unsigned int cycles)
{
  struct tme_isil7170 *isil7170;
  tme_bus_addr32_t isil7170_address_last;

  /* recover our data structure: */
  isil7170 = (struct tme_isil7170 *) _isil7170;

  /* the address must be within range: */
  isil7170_address_last = isil7170->tme_isil7170_device.tme_bus_device_address_last;
  assert(address <= isil7170_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 = isil7170_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 = isil7170;
  tlb->tme_bus_tlb_cycle = _tme_isil7170_bus_cycle;

  return (TME_OK);
}

/* the new isil7170 element function: */
TME_ELEMENT_NEW_DECL(tme_ic_isil7170) {
  const struct tme_isil7170_socket *socket;
  struct tme_isil7170 *isil7170;
  struct tme_isil7170_socket socket_real;
  tme_bus_addr_t address_mask;
  unsigned long scale;
  int arg_i;
  int usage;

  /* dispatch on our socket version: */
  socket = (const struct tme_isil7170_socket *) extra;
  if (socket == NULL) {
    tme_output_append_error(_output, _("need an ic socket"));
    return (ENXIO);
  }
  switch (socket->tme_isil7170_socket_version) {
  case TME_ISIL7170_SOCKET_0:
    socket_real = *socket;
    break;
  default: 
    tme_output_append_error(_output, _("socket type"));
    return (EOPNOTSUPP);
  }
    
  /* check our arguments: */
  usage = 0;
  scale = 1;
  arg_i = 1;
  for (;;) {

    /* a scale factor: */
    if (TME_ARG_IS(args[arg_i + 0], "scale")) {
      scale = tme_misc_unumber_parse(args[arg_i + 1], 0);
      if (scale == 0) {
	usage = TRUE;
	break;
      }
      arg_i += 2;
    }

    /* if we ran out of arguments: */
    else if (args[arg_i] == NULL) {

      break;
    }

    /* otherwise this is a bad argument: */
    else {
      tme_output_append_error(_output,
			      "%s %s, ",
			      args[arg_i],
			      _("unexpected"));
      usage = TRUE;
      break;
    }
  }

  if (usage) {
    tme_output_append_error(_output, 
			    "%s %s [ scale %s ]",
			    _("usage:"),
			    args[0],
			    _("SCALE"));
    return (EINVAL);
  }

  /* start the isil7170 structure: */
  isil7170 = tme_new0(struct tme_isil7170, 1);
  isil7170->tme_isil7170_socket = socket_real;
  isil7170->tme_isil7170_element = element;
  isil7170->tme_isil7170_clock_scale = scale;
  _tme_isil7170_reset(isil7170);

  /* figure our address mask, up to the nearest power of two: */
  address_mask = TME_ISIL7170_REGS_COUNT << isil7170->tme_isil7170_addr_shift;
  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: */
  isil7170->tme_isil7170_device.tme_bus_device_tlb_fill = _tme_isil7170_tlb_fill;
  isil7170->tme_isil7170_device.tme_bus_device_address_last = address_mask;

  /* start the timer thread: */
  tme_mutex_init(&isil7170->tme_isil7170_mutex);
  tme_cond_init(&isil7170->tme_isil7170_cond_timer);
  tme_thread_create(&isil7170->tme_isil7170_thread, (tme_thread_t) _tme_isil7170_th_timer, isil7170);

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

  return (TME_OK);
}
