@@ -16,6 +16,7 @@
*/
#include "qemu/osdep.h"
+#include "exec/address-spaces.h"
#include "migration/vmstate.h"
#include "hw/sysbus.h"
#include "hw/irq.h"
@@ -871,6 +872,112 @@ static void via1_auxmode_update(MOS6522Q800VIA1State *v1s)
}
}
+/*
+ * Addresses and real values for TimeDBRA/TimeSCCB to allow timer calibration
+ * to succeed (NOTE: both values have been multiplied by 3 to cope with the
+ * speed of QEMU execution on a modern host
+ */
+#define MACOS_TIMEDBRA 0xd00
+#define MACOS_TIMESCCB 0xd02
+
+#define MACOS_TIMEDBRA_VALUE (0x2a00 * 3)
+#define MACOS_TIMESCCB_VALUE (0x079d * 3)
+
+static bool via1_is_toolbox_timer_calibrated(void)
+{
+ /*
+ * Indicate whether the MacOS toolbox has been calibrated by checking
+ * for the value of our magic constants
+ */
+ uint16_t timedbra = lduw_be_phys(&address_space_memory, MACOS_TIMEDBRA);
+ uint16_t timesccdb = lduw_be_phys(&address_space_memory, MACOS_TIMESCCB);
+
+ return (timedbra == MACOS_TIMEDBRA_VALUE &&
+ timesccdb == MACOS_TIMESCCB_VALUE);
+}
+
+static void via1_timer_calibration_hack(MOS6522Q800VIA1State *v1s, int addr,
+ uint64_t val, int size)
+{
+ /*
+ * Work around timer calibration to ensure we that we have non-zero and
+ * known good values for TIMEDRBA and TIMESCCDB.
+ *
+ * This works by attempting to detect the reset and calibration sequence
+ * of writes to VIA1
+ */
+ int old_timer_hack_state = v1s->timer_hack_state;
+
+ switch (v1s->timer_hack_state) {
+ case 0:
+ if (addr == VIA_REG_PCR && val == 0x22) {
+ /* VIA_REG_PCR: configure VIA1 edge triggering */
+ v1s->timer_hack_state = 1;
+ }
+ break;
+ case 1:
+ if (addr == VIA_REG_T2CL && val == 0xc) {
+ /* VIA_REG_T2CL: low byte of 1ms counter */
+ if (!via1_is_toolbox_timer_calibrated()) {
+ v1s->timer_hack_state = 2;
+ } else {
+ v1s->timer_hack_state = 0;
+ }
+ }
+ break;
+ case 2:
+ if (addr == VIA_REG_T2CH && val == 0x3) {
+ /*
+ * VIA_REG_T2CH: high byte of 1ms counter (very likely at the
+ * start of SETUPTIMEK)
+ */
+ if (!via1_is_toolbox_timer_calibrated()) {
+ v1s->timer_hack_state = 3;
+ } else {
+ v1s->timer_hack_state = 0;
+ }
+ }
+ break;
+ case 3:
+ if (addr == VIA_REG_IER && val == 0x20) {
+ /*
+ * VIA_REG_IER: update at end of SETUPTIMEK
+ *
+ * Timer calibration has finished: unfortunately the values in
+ * TIMEDBRA (0xd00) and TIMESCCDB (0xd02) are so far out they
+ * cause divide by zero errors.
+ *
+ * Update them with values obtained from a real Q800 but with
+ * a x3 scaling factor which seems to work well
+ */
+ stw_be_phys(&address_space_memory, MACOS_TIMEDBRA,
+ MACOS_TIMEDBRA_VALUE);
+ stw_be_phys(&address_space_memory, MACOS_TIMESCCB,
+ MACOS_TIMESCCB_VALUE);
+
+ v1s->timer_hack_state = 4;
+ }
+ break;
+ case 4:
+ /*
+ * This is the normal post-calibration timer state: we should
+ * generally remain here unless we detect the A/UX calibration
+ * loop, or a write to VIA_REG_PCR suggesting a reset
+ */
+ if (addr == VIA_REG_PCR && val == 0x22) {
+ /* Looks like there has been a reset? */
+ v1s->timer_hack_state = 1;
+ }
+ break;
+ default:
+ g_assert_not_reached();
+ }
+
+ if (old_timer_hack_state != v1s->timer_hack_state) {
+ trace_via1_timer_hack_state(v1s->timer_hack_state);
+ }
+}
+
static uint64_t mos6522_q800_via1_read(void *opaque, hwaddr addr, unsigned size)
{
MOS6522Q800VIA1State *s = MOS6522_Q800_VIA1(opaque);
@@ -896,6 +1003,9 @@ static void mos6522_q800_via1_write(void *opaque, hwaddr addr, uint64_t val,
MOS6522State *ms = MOS6522(v1s);
addr = (addr >> 9) & 0xf;
+
+ via1_timer_calibration_hack(v1s, addr, val, size);
+
mos6522_write(ms, addr, val, size);
switch (addr) {
@@ -1008,6 +1118,9 @@ static void mos6522_q800_via1_reset_hold(Object *obj)
adb_set_autopoll_enabled(adb_bus, true);
v1s->cmd = REG_EMPTY;
v1s->alt = REG_EMPTY;
+
+ /* Timer calibration hack */
+ v1s->timer_hack_state = 0;
}
static void mos6522_q800_via1_realize(DeviceState *dev, Error **errp)
@@ -1100,6 +1213,8 @@ static const VMStateDescription vmstate_q800_via1 = {
VMSTATE_INT64(next_second, MOS6522Q800VIA1State),
VMSTATE_TIMER_PTR(sixty_hz_timer, MOS6522Q800VIA1State),
VMSTATE_INT64(next_sixty_hz, MOS6522Q800VIA1State),
+ /* Timer hack */
+ VMSTATE_INT32(timer_hack_state, MOS6522Q800VIA1State),
VMSTATE_END_OF_LIST()
}
};
@@ -272,6 +272,7 @@ via1_adb_send(const char *state, uint8_t data, const char *vadbint) "state %s da
via1_adb_receive(const char *state, uint8_t data, const char *vadbint, int status, int index, int size) "state %s data=0x%02x vADBInt=%s status=0x%x index=%d size=%d"
via1_adb_poll(uint8_t data, const char *vadbint, int status, int index, int size) "data=0x%02x vADBInt=%s status=0x%x index=%d size=%d"
via1_auxmode(int mode) "setting auxmode to %d"
+via1_timer_hack_state(int state) "setting timer_hack_state to %d"
# grlib_ahb_apb_pnp.c
grlib_ahb_pnp_read(uint64_t addr, unsigned size, uint32_t value) "AHB PnP read addr:0x%03"PRIx64" size:%u data:0x%08x"
@@ -74,6 +74,9 @@ struct MOS6522Q800VIA1State {
int64_t next_second;
QEMUTimer *sixty_hz_timer;
int64_t next_sixty_hz;
+
+ /* SETUPTIMEK hack */
+ int timer_hack_state;
};