mirror of
https://github.com/phabrics/Run-Sun3-SunOS-4.1.1.git
synced 2026-04-29 11:02:59 -04:00
769 lines
24 KiB
C
769 lines
24 KiB
C
/* $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>
|
|
#include <time.h>
|
|
#include <sys/time.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 (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;
|
|
|
|
/* 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: */
|
|
struct timeval 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 void
|
|
_tme_isil7170_th_timer(struct tme_isil7170 *isil7170)
|
|
{
|
|
tme_uint8_t int_mask;
|
|
tme_uint32_t sleep_usec;
|
|
#ifdef TME_ISIL7170_TRACK_INT_RATE
|
|
struct timeval now;
|
|
#endif /* TME_ISIL7170_TRACK_INT_RATE */
|
|
|
|
/* lock the mutex: */
|
|
tme_mutex_lock(&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: */
|
|
gettimeofday(&now, NULL);
|
|
if (now.tv_sec > isil7170->tme_isil7170_int_sample_time.tv_sec
|
|
|| (now.tv_sec == isil7170->tme_isil7170_int_sample_time.tv_sec
|
|
&& now.tv_usec > isil7170->tme_isil7170_int_sample_time.tv_usec)) {
|
|
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",
|
|
(isil7170->tme_isil7170_int_sample
|
|
/ (TME_ISIL7170_TRACK_INT_RATE
|
|
+ (unsigned long) (now.tv_sec
|
|
- isil7170->tme_isil7170_int_sample_time.tv_sec)))));
|
|
}
|
|
|
|
/* reset the sample: */
|
|
isil7170->tme_isil7170_int_sample_time.tv_sec = now.tv_sec + TME_ISIL7170_TRACK_INT_RATE;
|
|
isil7170->tme_isil7170_int_sample_time.tv_usec = now.tv_usec;
|
|
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;
|
|
|
|
/* unlock our mutex: */
|
|
tme_mutex_unlock(&isil7170->tme_isil7170_mutex);
|
|
|
|
/* sleep: */
|
|
tme_thread_sleep_yield(0, sleep_usec);
|
|
|
|
/* lock our mutex: */
|
|
tme_mutex_unlock(&isil7170->tme_isil7170_mutex);
|
|
}
|
|
/* NOTREACHED */
|
|
}
|
|
|
|
/* 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;
|
|
struct timeval now;
|
|
time_t _now;
|
|
struct tm *now_tm, now_tm_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: */
|
|
gettimeofday(&now, NULL);
|
|
_now = now.tv_sec;
|
|
now_tm = gmtime_r(&_now, &now_tm_buffer);
|
|
|
|
/* put the time-of-day into the registers: */
|
|
isil7170->tme_isil7170_regs[TME_ISIL7170_REG_CSEC] = now.tv_usec / 10000;
|
|
isil7170->tme_isil7170_regs[TME_ISIL7170_REG_HOUR] = now_tm->tm_hour;
|
|
isil7170->tme_isil7170_regs[TME_ISIL7170_REG_MIN] = now_tm->tm_min;
|
|
isil7170->tme_isil7170_regs[TME_ISIL7170_REG_SEC] = now_tm->tm_sec;
|
|
isil7170->tme_isil7170_regs[TME_ISIL7170_REG_MON] = now_tm->tm_mon + 1;
|
|
isil7170->tme_isil7170_regs[TME_ISIL7170_REG_DAY] = now_tm->tm_mday;
|
|
isil7170->tme_isil7170_regs[TME_ISIL7170_REG_YEAR] = (1900 + now_tm->tm_year) - TME_ISIL7170_REG_YEAR_0;
|
|
isil7170->tme_isil7170_regs[TME_ISIL7170_REG_DOW] = now_tm->tm_wday;
|
|
}
|
|
|
|
/* 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_reader);
|
|
tme_thread_create((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);
|
|
}
|