|
@@ -16,6 +16,7 @@
|
|
*/
|
|
*/
|
|
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "qemu/osdep.h"
|
|
|
|
+#include "exec/address-spaces.h"
|
|
#include "migration/vmstate.h"
|
|
#include "migration/vmstate.h"
|
|
#include "hw/sysbus.h"
|
|
#include "hw/sysbus.h"
|
|
#include "hw/irq.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)
|
|
static uint64_t mos6522_q800_via1_read(void *opaque, hwaddr addr, unsigned size)
|
|
{
|
|
{
|
|
MOS6522Q800VIA1State *s = MOS6522_Q800_VIA1(opaque);
|
|
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);
|
|
MOS6522State *ms = MOS6522(v1s);
|
|
|
|
|
|
addr = (addr >> 9) & 0xf;
|
|
addr = (addr >> 9) & 0xf;
|
|
|
|
+
|
|
|
|
+ via1_timer_calibration_hack(v1s, addr, val, size);
|
|
|
|
+
|
|
mos6522_write(ms, addr, val, size);
|
|
mos6522_write(ms, addr, val, size);
|
|
|
|
|
|
switch (addr) {
|
|
switch (addr) {
|
|
@@ -1008,6 +1118,9 @@ static void mos6522_q800_via1_reset_hold(Object *obj)
|
|
adb_set_autopoll_enabled(adb_bus, true);
|
|
adb_set_autopoll_enabled(adb_bus, true);
|
|
v1s->cmd = REG_EMPTY;
|
|
v1s->cmd = REG_EMPTY;
|
|
v1s->alt = REG_EMPTY;
|
|
v1s->alt = REG_EMPTY;
|
|
|
|
+
|
|
|
|
+ /* Timer calibration hack */
|
|
|
|
+ v1s->timer_hack_state = 0;
|
|
}
|
|
}
|
|
|
|
|
|
static void mos6522_q800_via1_realize(DeviceState *dev, Error **errp)
|
|
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_INT64(next_second, MOS6522Q800VIA1State),
|
|
VMSTATE_TIMER_PTR(sixty_hz_timer, MOS6522Q800VIA1State),
|
|
VMSTATE_TIMER_PTR(sixty_hz_timer, MOS6522Q800VIA1State),
|
|
VMSTATE_INT64(next_sixty_hz, MOS6522Q800VIA1State),
|
|
VMSTATE_INT64(next_sixty_hz, MOS6522Q800VIA1State),
|
|
|
|
+ /* Timer hack */
|
|
|
|
+ VMSTATE_INT32(timer_hack_state, MOS6522Q800VIA1State),
|
|
VMSTATE_END_OF_LIST()
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
}
|
|
};
|
|
};
|