Index: sys/arm/arm/generic_timer.c
===================================================================
--- sys/arm/arm/generic_timer.c	(revision 336112)
+++ sys/arm/arm/generic_timer.c	(working copy)
@@ -145,8 +145,8 @@ get_freq(void)
 static uint64_t
 get_cntxct_a64_unstable(bool physical)
 {
-	uint64_t val
-;
+	uint64_t val;
+
 	isb();
 	if (physical) {
 		do {
@@ -204,6 +204,16 @@ set_tval(uint32_t val, bool physical)
 	return (0);
 }
 
+static void
+set_cval(uint64_t val, bool physical)
+{
+
+	if (physical)
+		set_el0(cntp_cval, val);
+	else
+		set_el0(cntv_cval, val);
+}
+
 static int
 get_ctrl(bool physical)
 {
@@ -275,6 +285,41 @@ arm_tmr_start(struct eventtimer *et, sbintime_t fi
 
 }
 
+static int
+arm_tmr_start_a64_unstable(struct eventtimer *et, sbintime_t first,
+    sbintime_t period __unused)
+{
+	struct arm_tmr_softc *sc;
+	uint32_t ctrl;
+	uint64_t cntreg, counts;
+
+	sc = (struct arm_tmr_softc *)et->et_priv;
+
+	if (first == 0)
+		return (EINVAL);
+
+	/*
+	 * The hardware apparently suffers internally from the same bug software
+	 * sees when reading the count register: during a rollover the read
+	 * returns an indeterminate value for the low 11 bits (all-zeroes or
+	 * all-ones).  A write to the tval register is handled internally by
+	 * reading the count register, adding the tval, and storing the result
+	 * to the compare register; the rollover glitch can result in the
+	 * compare register being set almost 2^32 ticks too far into the future.
+	 * The Allwinner workaround in their linux BSP is to set tval, then
+	 * calculate what cval should be, read cval back, and see if the
+	 * hardware calculated it wrong.  If we're going to do the math
+	 * ourselves, we might as well just set cval directly ourselves.
+	 */
+	ctrl = (get_ctrl(sc->physical) & ~GT_CTRL_INT_MASK) | GT_CTRL_ENABLE;
+	counts = ((uint32_t)et->et_frequency * first) >> 32;
+	cntreg = get_cntxct_a64_unstable(sc->physical);
+	set_cval(cntreg + counts, sc->physical);
+	set_ctrl(ctrl, sc->physical);
+
+	return (0);
+}
+
 static void
 arm_tmr_disable(bool physical)
 {
@@ -397,7 +442,9 @@ arm_tmr_attach(device_t dev)
 #endif
 	int error;
 	int i;
+	bool a64_unstable;
 
+	a64_unstable = false;
 	sc = device_get_softc(dev);
 	if (arm_tmr_sc)
 		return (ENXIO);
@@ -413,6 +460,7 @@ arm_tmr_attach(device_t dev)
 			sc->clkfreq = clock;
 
 		if (OF_hasprop(node, "allwinner,sun50i-a64-unstable-timer")) {
+			a64_unstable = true;
 			sc->get_cntxct = &get_cntxct_a64_unstable;
 			if (bootverbose)
 				device_printf(dev,
@@ -475,7 +523,7 @@ arm_tmr_attach(device_t dev)
 	sc->et.et_frequency = sc->clkfreq;
 	sc->et.et_min_period = (0x00000010LLU << 32) / sc->et.et_frequency;
 	sc->et.et_max_period = (0xfffffffeLLU << 32) / sc->et.et_frequency;
-	sc->et.et_start = arm_tmr_start;
+	sc->et.et_start = (a64_unstable) ? arm_tmr_start_a64_unstable : arm_tmr_start;
 	sc->et.et_stop = arm_tmr_stop;
 	sc->et.et_priv = sc;
 	et_register(&sc->et);