/* 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; | |
} |