/* sage_stddev.c: Standard devices for sage-II system

   Copyright (c) 2009-2010 Holger Veit

   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
   Holger Veit BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
   IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

   Except as contained in this notice, the name of Holger Veit et al shall not be
   used in advertising or otherwise to promote the sale, use or other dealings
   in this Software without prior written authorization from Holger Veit et al.

   04-Oct-09    HV      Initial version
*/

#include "sim_defs.h"
#include "m68k_cpu.h"
#include "sage_defs.h"

/***********************************************************************************
 * 8259-5 interrupt controller
 * 
 * IRQ output hardwired to Interrupt Priority Level 1 in the Sage
 * Level 2: from external bus (wired to HDC board, AUX devices)
 * Level 3: from external bus
 * Level 4: IEEE 488 Interrupt U6
 * Level 5: Console Uart U67 Receiver Interrupt
 * Level 6: FDI floppy controller
 * Level 7: nonmaskable RAM parity error (not possible in simh)
 * 
 * hardwired inputs:
 * IR0 = Output 2 of U74 real time clock
 * IR1 = Modem Uart U58 Receiver Interrupt
 * IR2 = Console Uart U67 Transmitter Interrupt
 * IR3 = Modem Uart U58 Receiver Interrupt
 * IR4 = Modem Carrier Detect Interrupt U38
 * IR5 = LP Port Acknowledge U39/U38
 * IR6 = Output 0 of U74 real time clock
 * IR7 = Output C2 of U39
 *  
 * Notes: 
 *  INTA- is hardwired to VCC, so vectoring is not possible
 *  SP- is hardwired to VCC, so buffered mode is not possible, and device is a master.
 *  CAS0-2 lines are open, no need to handle
 *  UCSD bios and boot prom do not program the PIC for rotating priorities,
 *  so effectively prio is always 7.
 * 
 **********************************************************************************/
extern DEVICE sagepic_dev;
static t_stat sagepic_reset(DEVICE* dptr);
static I8259 u73 = { {0,0,U73_ADDR,4,2},
            &sagepic_dev,NULL,NULL,i8259_reset
};

UNIT sagepic_unit = {
    UDATA (NULL, UNIT_IDLE, 0)
};

REG sagepic_reg[] = {
    { DRDATA(STATE, u73.state, 8) },
    { HRDATA(IRR,   u73.irr, 8) },
    { HRDATA(IMR,   u73.imr, 8) },
    { HRDATA(ISR,   u73.isr, 8) },
    { HRDATA(ICW1,  u73.icw1, 8) },
    { HRDATA(ICW2,  u73.icw2, 8) },
    { HRDATA(ICW4,  u73.icw4, 8) },
    { HRDATA(OCW2,  u73.prio, 3) },
    { NULL }
};

static MTAB sagepic_mod[] = {
    { MTAB_XTD|MTAB_VDV, 0, "IO", "IO", &set_iobase, &show_iobase, NULL },
    { 0 }
};

DEVICE sagepic_dev = {
    "PIC", &sagepic_unit, sagepic_reg, sagepic_mod,
    1, 16, 32, 2, 16, 16,
    NULL, NULL, &sagepic_reset,
    NULL, NULL, NULL,
    &u73, DEV_DEBUG, 0,
    i8259_dt, NULL, NULL
};

static t_stat sagepic_reset(DEVICE* dptr) 
{
    t_stat rc;
    if ((rc = (dptr->flags & DEV_DIS) ?
        del_iohandler(dptr->ctxt) :
        add_iohandler(&sagepic_unit,dptr->ctxt,i8259_io)) != SCPE_OK) return rc;
    return u73.reset(&u73);
}

t_stat sage_raiseint(int level)
{
    return i8259_raiseint(&u73,level);
}

/******************************************************************************************************
 * DIP switches at the back panel.
 * 
 * In the technical manual, switches are layed out 12345678 left to right,
 * but here seen as two HEX digits 8765 4321, i.e. 0xc0 is bit 8 and bit 7 set on
 * 
 * a "d" (down) means switch is off or "0", and a "u" (up) means switch is on or "1"
 * 
 * Note that programatically dip switches are port a and b of the onboard 8255 U22
 * which also through port c serves part of the FDC signals
 * 
 * group-a:
 * 8 7 6 5 4 3 2 1
 * | | | | | d d d--- 19,2K baud
 * | | | | | d d u--- 9600 baud
 * | | | | | d u d--- 4800 baud
 * | | | | | d u u--- 2400 baud
 * | | | | | u d d--- 1200 baud
 * | | | | | u d u--- 600 baud
 * | | | | | u u d--- 300 baud
 * | | | | | u u u--- reserved
 * | | | | d--------- even parity
 * | | | | u--------- parity disabled
 * | | d d----------- boot to debugger
 * | | d u----------- boot to floppy 0
 * | | u d----------- boot to harddisk 0 partition 0
 * | | u u----------- reserved
 * | d--------------- 96 tpi drive
 * | u--------------- 48 tpi drive
 * x----------------- reserved
 * 
 * group-b:
 * 8 7 6 5 4 3 2 1
 * | | | +-+-+-+-+--- device talk and listen address
 * | | u------------- enable talk
 * | | d------------- disable talk
 * | u--------------- enable listen
 * | d--------------- disable listen
 * u----------------- 2 consecutive addresses
 * d----------------- 1 address
 */

#if defined(SAGE_IV)
       uint32 groupa = 0xd7;    /* used by cons device, 19k2, no parity, boot floppy 0 */
       uint32 groupb = 0xf8;    /* used by ieee device */
#else
       uint32 groupa = 0xe7;    /* used by cons device, 19k2, no parity, boot winchester 0 */
       uint32 groupb = 0xf8;    /* used by ieee device */
#endif
       
static t_stat sagedip_reset(DEVICE* dptr);
static t_stat set_groupa(UNIT *uptr, int32 val, CONST char *cptr, void *desc);
static t_stat show_groupa(FILE *st, UNIT *uptr, int32 val, CONST void *desc);
static t_stat set_groupb(UNIT *uptr, int32 val, CONST char *cptr, void *desc);
static t_stat show_groupb(FILE *st, UNIT *uptr, int32 val, CONST void *desc);
static t_stat u22_reset(I8255* chip);
static t_stat u22_calla(I8255* chip,int rw);
static t_stat u22_callb(I8255* chip,int rw);
static t_stat u22_callc(I8255* chip,int rw);
static t_stat u22_ckmode(I8255* chip,uint32 data);

extern DEVICE sagedip_dev;
static I8255 u22 = { 
        { 0,0,U22_ADDR,8,2 }, 
        &sagedip_dev,i8255_write,i8255_read,u22_reset,u22_calla,u22_callb,u22_callc,u22_ckmode
};
uint32* u22_portc = &u22.portc; /* this is used in the FD device as well, but whole 8255 is handled here */

UNIT sagedip_unit = {
    UDATA (NULL, UNIT_IDLE, 0)
};

REG sagedip_reg[] = {
    { HRDATA(PORTA, u22.porta, 8) },
    { HRDATA(PORTB, u22.portb, 8) },
    { HRDATA(PORTC, u22.portc, 8) },
    { HRDATA(CTRL,  u22.ctrl, 8) },
    { NULL }
};

static MTAB sagedip_mod[] = {
    { MTAB_XTD|MTAB_VDV,    0,  "IO",       "IO",       &set_iobase, &show_iobase, NULL },
    { MTAB_XTD|MTAB_VDV,    0,  "GROUPA",   "GROUPA",   &set_groupa, &show_groupa, NULL },
    { MTAB_XTD|MTAB_VDV,    0,  "GROUPB",   "GROUPB",   &set_groupb, &show_groupb, NULL },
    { 0 }
};

/* Debug Flags */
DEBTAB sagedip_dt[] = {
    { "RDA", DBG_PP_RDA },
    { "RDB", DBG_PP_RDB },
    { "WRC", DBG_PP_WRC },
    { "WRMODE", DBG_PP_MODE },
    { NULL, 0 }
};

DEVICE sagedip_dev = {
    "DIP", &sagedip_unit, sagedip_reg, sagedip_mod,
    1, 16, 32, 2, 16, 16,
    NULL, NULL, &sagedip_reset,
    NULL, NULL, NULL,
    &u22, DEV_DEBUG, 0,
    sagedip_dt, NULL, NULL
};

static t_stat sagedip_reset(DEVICE* dptr) 
{
    t_stat rc;

    if ((rc = (dptr->flags & DEV_DIS) ? /* Disconnect I/O Ports */
        del_iohandler(dptr->ctxt) :
        add_iohandler(&sagedip_unit,dptr->ctxt,i8255_io)) != SCPE_OK) return rc;

    /* clear 8255 ctrl register */
    return u22.reset(&u22);
}

static t_stat set_gr(const char* cptr, uint32* sw)
{
    int i;
    char c;

    if (!cptr) return SCPE_ARG;

    *sw = 0;
    for (i=0; *cptr && i<8; i++) {
        c = *cptr++;
        *sw <<= 1;
        if (c=='1') *sw |= 1;
        else if (c=='0') continue;
        else if (c==0) break;
        else return SCPE_ARG;
    }
    return SCPE_OK;
}

static t_stat set_groupa(UNIT *uptr, int32 val, CONST char *cptr, void *desc)
{
    return set_gr(cptr,&groupa);
}

static t_stat set_groupb(UNIT *uptr, int32 val, CONST char *cptr, void *desc)
{
    return set_gr(cptr,&groupb);
}

static t_stat show_gr(FILE* st, const char* prefix, uint32 gr)
{
    int i;
    fputs(prefix, st);
    for (i = 0x80; i > 0; i = i >> 1)
        fprintf(st,"%c", gr&i ? '1' : '0');
    return SCPE_OK;
}

static t_stat show_groupa(FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
    return show_gr(st, "GROUPA=", groupa);
}

static t_stat show_groupb(FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
    return show_gr(st, "GROUPB=", groupb);
}

static t_stat u22_reset(I8255* chip)
{
    chip->ctrl = 0;
    chip->portc = 0;
    return SCPE_OK;
}

extern I8272 u21;

static t_stat u22_calla(I8255* chip,int rw)
{
    if (rw==0) {
        chip->porta = groupa & 0xff;
        TRACE_PRINT1(DBG_PP_RDA,"WR PortA: 0x%x",groupa);
    }
    return SCPE_OK;
}

static t_stat u22_callb(I8255* chip,int rw)
{
    if (rw==0) {
        chip->portb = groupb & 0xff;
        TRACE_PRINT1(DBG_PP_RDA,"WR PortB: 0x%x",groupb);
    }
    return SCPE_OK;
}

/* callback handler for FDC bits */
static t_stat u22_callc(I8255* chip,int rw)
{
    /* bit0: TC+ positive enforce that internal data counter of FDC is reset
     * bit1: RDY+ positive enable the FDC
     * bit2: FDIE+ positive enable FDC interrupt (handled directly by reading portc in sage_fd.c)
     * bit3: SL0- negative select of drive 0
     * bit4: SL1- negative select of drive 1
     * bit5: MOT- negative switch on drive motor (ignored)
     * bit6: PCRMP- negative precompensation (ignored)
     * bit7: FRES+ positive FDC reset
     */

    if (I8255_ISSET(portc,U22C_TC)) { /* TC+ */
        i8272_finish(&u21); /* terminate a read/write in progress */
    }
    if (I8255_ISCLR(portc,U22C_RDY)) { /* RDY+ */
        i8272_abortio(&u21); /* abort current op */
    }
    if (I8255_ISCLR(portc,U22C_SL0)) { /* SL0- */
        u21.fdc_curdrv = 0;
    } else if (I8255_ISCLR(portc,U22C_SL1)) { /* SL1- */
        u21.fdc_curdrv = 1;
    } else if (I8255_ISSET(portc,U22C_SL0|U22C_SL1)) { /* deselect drives */
        u21.fdc_curdrv = 0;
    }
    if (I8255_ISSET(portc,U22C_FRES)) { /* FRES+ */
        i8272_reset(&u21);
    }
    TRACE_PRINT(DBG_PP_WRC,(sim_deb,"PORTC Flags: %s%s%s%s%s%s%s%s",
            I8255_ISSET(portc,U22C_TC)?"TC ":"",
            I8255_ISSET(portc,U22C_RDY)?"RDY ":"",
            I8255_ISSET(portc,U22C_FDIE)?"FDIE ":"",
            I8255_ISSET(portc,U22C_SL0)?"":"SL0 ",
            I8255_ISSET(portc,U22C_SL1)?"":"SL1 ",
            I8255_ISSET(portc,U22C_MOT)?"":"MOT ",
            I8255_ISSET(portc,U22C_PCRMP)?"":"PCRMP ",
            I8255_ISSET(portc,U22C_FRES)?"FRES ":""));
    return SCPE_OK;
}

static t_stat u22_ckmode(I8255* chip, uint32 data)
{
    /* hardwired:
     * d7=1 -- mode set flag
     * d6=0 -+ group a mode 0: basic I/O
     * d5=0 -+
     * d4=1 -- porta = input
     * d3=0 -- portc upper = output
     * d2=0 -- group b mode 0: basic I/O
     * d1=1 -- portb = input
     * d0=0 -- portc lower = output
     */ 
    TRACE_PRINT1(DBG_PP_MODE,"WR Mode: 0x%x",data);
    if (data != 0x92) {
        printf("u22_ckmode: unsupported ctrl=0x%02x\n",data);
        return STOP_IMPL;
    }
    return SCPE_OK;
}

/***********************************************************************************
 * Two 8553 timers U75 (TIMER1) and U74 (TIMER2)
 * Each contain three 8/16 bit timers
 * In the sage hardwired in the following way:
 *
 *            +---------+
 * 615kHz--+->|Timer1 C1|---> Baud ser0
 *         |  +---------+
 *         +->|Timer1 C2|---> Baud ser1
 *            +---------+
 *            +---------+    +---------+
 * 64kHz---+->|Timer1 C0|--->|Timer2 C0|--> PIC IR6   
 *         |  |div 64000|    |mode0    |
 *         |  +---------+    +---------+
 *         |  +---------+    +---------+
 *         +->|Timer2 C1|--->|Timer2 C2|--> PIC IR0  
 *            |         |    |         |
 *            +---------+    +---------+
 * 
 * NOT UNITS: Timer1 C1 and C2 are programmed in mode 2 as clock dividers for the USARTs
 * In this emulation we allow programming them, but since they don't produce interrupts,
 * their work is ignored.
 * 
 * Timer1 C0 and timer2 C0 form a clock divider which produces an interrupt at PIC level 6
 * Likewise, timer2 C1 and timer2 C2 form a clock divider which produces an interrupt at PIC level 0
 * 
 * Typically, the first one in cascade is programmed in mode 2, the second one in mode 0.
 * Timer1 C0 is explicitly programmed as a divider by 64k, so that it feeds timer2 C0 
 * with a 1Hz clock.
 * 
 * The way the timers are hardwired makes certain mode settings impossible: all GATE
 * inputs are set to VCC, so MODE1 and MODE5 are impossible, and MODE4 becomes a
 * variant of MODE0. MODE3 is used by the baudrate generators. The timers may run in 
 * 8 bit mode, but analysis of existing BIOS code (BOOT PROM and UCSD BIOS) uncovered
 * they are used in 16 bit mode only.
 * So, this implementation only contains the most likely usages, and the other ones have
 * to be added when there is a necessity.
 *
 * Notes on actual implementation:
 * Since we know the input clocks, we have just to take care about the division factors
 * stored in T1C0 and T2C1. Whenever one of these timers are read out, the actual count
 * has to be calculated on the fly. The actual cnt registers only hold the count factors
 * programmed, but are never counted down, as, in the case of the 64kHz clock this would
 * mean to trigger sim_* events 64000 times a second.
 *
 ***********************************************************************************/

/************************************************************************************
 *  timer 1
 ***********************************************************************************/
static t_stat sagetimer1_reset(DEVICE* dptr);
static t_stat timer1_svc(UNIT* uptr);
static t_stat u75_ckmode(I8253* chip, uint32 data);
static t_stat u75_call0(I8253* chip,int addr, uint32* value);

static t_stat sagetimer2_reset(DEVICE* dptr);
static t_stat timer2_svc(UNIT* uptr);
static t_stat u74_ckmode(I8253* chip, uint32 data);
static t_stat u74_call1(I8253* chip,int addr, uint32* value);

extern DEVICE sagetimer1_dev;
extern DEVICE sagetimer2_dev;

/* forward timer 2 */
UNIT sagetimer2_unit = {
    UDATA (&timer2_svc, UNIT_IDLE, 0)
};

static I8253 u74 = { {0,0,U74_ADDR,8,2},
    &sagetimer2_dev,&sagetimer2_unit,i8253_reset,u74_ckmode,
    { { 0, }, { u74_call1, }, { 0, } } 
};

/* timer 1 */
UNIT sagetimer1_unit = {
    UDATA (&timer1_svc, UNIT_IDLE, 1)
};

static I8253 u75 = { {0,0,U75_ADDR,8,2},
    &sagetimer1_dev,&sagetimer1_unit,i8253_reset,u75_ckmode,
    { { u75_call0, }, { 0, }, { 0, } } 
};

REG sagetimer1_reg[] = {
    { HRDATA(INIT,  u75.init, 8), REG_HRO },
    { HRDATA(STATE0,u75.cntr[0].state, 8),REG_HRO },
    { HRDATA(STATE1,u75.cntr[1].state, 8),REG_HRO },
    { HRDATA(STATE2,u75.cntr[2].state, 8),REG_HRO },
    { HRDATA(MODE0, u75.cntr[0].mode, 8) },
    { HRDATA(MODE1, u75.cntr[1].mode, 8) },
    { HRDATA(MODE2, u75.cntr[2].mode, 8) },
    { HRDATA(CNT0,  u75.cntr[0].count, 16) },
    { HRDATA(CNT1,  u75.cntr[1].count, 16) },
    { HRDATA(CNT2,  u75.cntr[2].count, 16) },
    { HRDATA(LATCH0,u75.cntr[0].latch, 16) },
    { HRDATA(LATCH1,u75.cntr[1].latch, 16) },
    { HRDATA(LATCH2,u75.cntr[2].latch, 16) },
    { HRDATA(DIV0,  u75.cntr[0].divider, 16),REG_HRO },
    { HRDATA(DIV1,  u75.cntr[1].divider, 16),REG_HRO },
    { HRDATA(DIV2,  u75.cntr[2].divider, 16),REG_HRO },
    { NULL }
};

static MTAB sagetimer1_mod[] = {
    { MTAB_XTD|MTAB_VDV,    0,  "IO", "IO", &set_iobase, &show_iobase, NULL },
    { 0 }
};

DEVICE sagetimer1_dev = {
    "TIMER1", &sagetimer1_unit, sagetimer1_reg, sagetimer1_mod,
    1, 16, 32, 2, 16, 16,
    NULL, NULL, &sagetimer1_reset,
    NULL, NULL, NULL,
    &u75, DEV_DEBUG, 0,
    i8253_dt, NULL, NULL
};

    static t_stat sagetimer1_reset(DEVICE* dptr) 
    {
        t_stat rc;

        if (dptr->flags & DEV_DIS)
            rc = del_iohandler(dptr->ctxt);
        else
            rc = add_iohandler(&sagetimer1_unit,dptr->ctxt,i8253_io);
        if (rc != SCPE_OK)
            return rc;
        return u75.reset(&u75);
    }

static t_stat timer1_svc(UNIT* uptr)
{
    int32 wait;
    I8253CNTR* t1c0 = &u75.cntr[0];
    I8253CNTR* t2c0 = &u74.cntr[0];
    
//  fprintf(sim_deb,"TIMER1: timer1_svc called T1C0=%d T2C0=%d\n",t1c0->count,t2c0->count);
    /* we call this service 64000 times a second to decrement counter T1C0.
     * When T1C0 reaches 0, it will decrement T2C0 */
    t1c0->count--;
    if (t1c0->count <= 0) {
        /* reload from divider */
        t1c0->count = t1c0->divider;
        /* decrement T2C0 counter and raise interrupt 6 if counter is zero */
        if (t2c0->count == 0) {
            sage_raiseint(TIMER2C0_PICINT);
//          printf("timer1 heartbeat\n");
            t2c0->count = 65536;
        }
        t2c0->count--;
    }

    /* adjust timing */
    wait = sim_rtcn_calb(64000,TMR_RTC1);
    sim_activate(u75.unit,wait); /* 64000 ticks per second */
    return SCPE_OK;
}

static t_stat u75_ckmode(I8253* chip,uint32 mode)
{
    /* @TODO check valid modes */
    return SCPE_OK;
}

static t_stat u75_call0(I8253* chip,int rw,uint32* value)
{
    if (rw==1) {
        I8253CNTR* cntr = &chip->cntr[0];
        if ((cntr->mode & I8253_BOTH) && (cntr->state & I8253_ST_MSBNEXT)) {
            sim_cancel(chip->unit);
            return SCPE_OK; /* not fully loaded yet */
        } else {
            /* start the CK0 clock at 64000Hz */
            sim_activate(chip->unit,sim_rtcn_init(64000,TMR_RTC1)); /* use timer1 C0 for this clock */
        }
    }
    return SCPE_OK;
}


/************************************************************************************
 *  timer 2
 ***********************************************************************************/

REG sagetimer2_reg[] = {
    { HRDATA(INIT,  u74.init, 8), REG_HRO },
    { HRDATA(STATE0,u74.cntr[0].state, 8),REG_HRO },
    { HRDATA(STATE1,u74.cntr[1].state, 8),REG_HRO },
    { HRDATA(STATE2,u74.cntr[2].state, 8),REG_HRO },
    { HRDATA(MODE0, u74.cntr[0].mode, 8) },
    { HRDATA(MODE1, u74.cntr[1].mode, 8) },
    { HRDATA(MODE2, u74.cntr[2].mode, 8) },
    { HRDATA(CNT0,  u74.cntr[0].count, 16) },
    { HRDATA(CNT1,  u74.cntr[1].count, 16) },
    { HRDATA(CNT2,  u74.cntr[2].count, 16) },
    { HRDATA(LATCH0,u74.cntr[0].latch, 16) },
    { HRDATA(LATCH1,u74.cntr[1].latch, 16) },
    { HRDATA(LATCH2,u74.cntr[2].latch, 16) },
    { HRDATA(DIV0,  u74.cntr[0].divider, 16),REG_HRO },
    { HRDATA(DIV1,  u74.cntr[1].divider, 16),REG_HRO },
    { HRDATA(DIV2,  u74.cntr[2].divider, 16),REG_HRO },
    { NULL }
};

static MTAB sagetimer2_mod[] = {
    { MTAB_XTD|MTAB_VDV,    0,  "IO", "IO", &set_iobase, &show_iobase, NULL },
    { 0 }
};

DEVICE sagetimer2_dev = {
    "TIMER2", &sagetimer2_unit, sagetimer2_reg, sagetimer2_mod,
    1, 16, 32, 2, 16, 16,
    NULL, NULL, &sagetimer2_reset,
    NULL, NULL, NULL,
    &u74, DEV_DEBUG, 0,
    i8253_dt, NULL, NULL
};

static t_stat sagetimer2_reset(DEVICE* dptr) 
{
    t_stat rc;
    if ((rc = (dptr->flags & DEV_DIS) ?
        del_iohandler(dptr->ctxt) :
        add_iohandler(&sagetimer2_unit,dptr->ctxt,i8253_io)) != SCPE_OK) return rc;
    return u74.reset(&u74);
}

static t_stat u74_ckmode(I8253* chip,uint32 mode)
{
    /* @TODO check valid modes */
    return SCPE_OK;
}

static t_stat u74_call1(I8253* chip,int rw,uint32* value)
{
    if (rw==1) {
        I8253CNTR* cntr = &chip->cntr[1];
        if ((cntr->mode & I8253_BOTH) && (cntr->state & I8253_ST_MSBNEXT)) {
            sim_cancel(chip->unit);
            return SCPE_OK; /* not fully loaded yet */
        } else {
            /* start the CK0 clock at 64000Hz */
            sim_activate(chip->unit,sim_rtcn_init(64000,TMR_RTC1)); /* use timer1 C0 for this clock */
        }
    }
    return SCPE_OK;
}

static t_stat timer2_svc(UNIT* uptr)
{
    int32 wait;
    I8253CNTR* t2c1 = &u74.cntr[1];
    I8253CNTR* t2c2 = &u74.cntr[2];

    /* we call this service 64000 times a second to decrement counter T2C1.
     * When T2C1 reaches 0, it will decrement T2C2 */
    t2c1->count--;
    if (t2c1->count <= 0) {
        /* reload from divider */
        t2c1->count = t2c1->divider;
        /* decrement T2C2 counter and raise interrupt 0 if counter is zero */
        if (t2c2->count == 0) {
//          printf("timer2 heartbeat\n");
            sage_raiseint(TIMER2C2_PICINT);
        }
        t2c2->count--;
    }

    /* adjust timing */
    wait = sim_rtcn_calb(64000,TMR_RTC1);
    sim_activate(u74.unit,wait); /* 64000 ticks per second */
    return SCPE_OK;
}
