diff --git a/core/interrupts.c b/core/interrupts.c
index b91508f..32f43ef 100644
--- a/core/interrupts.c
+++ b/core/interrupts.c
@@ -296,9 +296,6 @@ static int64_t opal_handle_interrupt(uint32_t isn, uint64_t *outstanding_event_m
 	struct irq_source *is = irq_find_source(isn);
 	int64_t rc = OPAL_SUCCESS;
 
-	/* We run the timers first */
-	check_timers(true);
-
 	/* No source ? return */
 	if (!is || !is->ops->interrupt) {
 		rc = OPAL_PARAMETER;
@@ -308,6 +305,10 @@ static int64_t opal_handle_interrupt(uint32_t isn, uint64_t *outstanding_event_m
 	/* Run it */
 	is->ops->interrupt(is->data, isn);
 
+	/* Check timers if SLW timer isn't working */
+	if (!slw_timer_ok())
+		check_timers(true);
+
 	/* Update output events */
  bail:
 	if (outstanding_event_mask)
diff --git a/core/timer.c b/core/timer.c
index d5a3477..e97dc07 100644
--- a/core/timer.c
+++ b/core/timer.c
@@ -12,6 +12,9 @@
 #include <cpu.h>
 #endif
 
+/* Heartbeat requested from Linux */
+#define HEARTBEAT_DEFAULT_MS	2000
+
 static struct lock timer_lock = LOCK_UNLOCKED;
 static LIST_HEAD(timer_list);
 static LIST_HEAD(timer_poll_list);
@@ -72,28 +75,44 @@ void cancel_timer_async(struct timer *t)
 	unlock(&timer_lock);
 }
 
-void schedule_timer_at(struct timer *t, uint64_t when)
+static void __schedule_timer_at(struct timer *t, uint64_t when)
 {
 	struct timer *lt;
 
-	lock(&timer_lock);
+	/* If the timer is already scheduled, take it out */
 	if (t->link.next)
 		__remove_timer(t);
+
+	/* Update target */
 	t->target = when;
+
 	if (when == TIMER_POLL) {
+		/* It's a poller, add it to the poller list */
 		t->gen = timer_poll_gen;
 		list_add_tail(&timer_poll_list, &t->link);
 	} else {
+		/* It's a real timer, add it in the right spot in the
+		 * ordered timer list
+		 */
 		list_for_each(&timer_list, lt, link) {
-			if (when < lt->target) {
-				list_add_before(&timer_list, &t->link,
-						&lt->link);
-				unlock(&timer_lock);
-				return;
-			}
+			if (when >= lt->target)
+				continue;
+			list_add_before(&timer_list, &t->link, &lt->link);
+			goto bail;
 		}
 		list_add_tail(&timer_list, &t->link);
 	}
+ bail:
+	/* Pick up the next timer and upddate the SBE HW timer */
+	lt = list_top(&timer_list, struct timer, link);
+	if (lt)
+		slw_update_timer_expiry(lt->target);
+}
+
+void schedule_timer_at(struct timer *t, uint64_t when)
+{
+	lock(&timer_lock);
+	__schedule_timer_at(t, when);
 	unlock(&timer_lock);
 }
 
@@ -134,18 +153,19 @@ static void __check_poll_timers(uint64_t now)
 		t = list_top(&timer_poll_list, struct timer, link);
 
 		/* Top timer has a different generation than current ? Must
-		 * be older
+		 * be older, we are done.
 		 */
 		if (!t || t->gen == timer_poll_gen)
 			break;
 
-		/* Top of list still running, we have to delay handling
-		 * it. For now just skip until the next poll, when we have
-		 * SLW interrupts, we'll probably want to trip another one
-		 * ASAP
+		/* Top of list still running, we have to delay handling it,
+		 * let's reprogram the SLW with a small delay. We chose
+		 * arbitrarily 1us.
 		 */
-		if (t->running)
+		if (t->running) {
+			slw_update_timer_expiry(now + usecs_to_tb(1));
 			break;
+		}
 
 		/* Allright, first remove it and mark it running */
 		__remove_timer(t);
@@ -222,24 +242,25 @@ void check_timers(bool from_interrupt)
 }
 
 #ifndef __TEST__
+
 void late_init_timers(void)
 {
 	/* Add a property requesting the OS to call opal_poll_event() at
 	 * a specified interval in order for us to run our background
-	 * low priority poller.
+	 * low priority pollers.
 	 *
-	 * When we have a working SLW based HW timer, we'll be able to
-	 * reduce this or even remove it, for now however, we want to be
-	 * called at least every couple of seconds on FSP based machines
-	 * and a bit faster on BMC based machines where the LPC and i2c
-	 * interrupts might not be functional.
+	 * If we have an SLW timer facility, we run this 10 times slower,
+	 * we could possibly completely get rid of it.
 	 *
 	 * We use a value in milliseconds, we don't want this to ever be
 	 * faster than that.
 	 */
-	if (fsp_present())
-		dt_add_property_cells(opal_node, "ibm,heartbeat-ms", 2000);
-	else
-		dt_add_property_cells(opal_node, "ibm,heartbeat-ms", 250);
+	if (slw_timer_ok() || fsp_present()) {
+		dt_add_property_cells(opal_node, "ibm,heartbeat-ms",
+				      HEARTBEAT_DEFAULT_MS);
+	} else {
+		dt_add_property_cells(opal_node, "ibm,heartbeat-ms",
+				      HEARTBEAT_DEFAULT_MS / 10);
+	}
 }
 #endif
diff --git a/hw/occ.c b/hw/occ.c
index 4b74b99..68b7032 100644
--- a/hw/occ.c
+++ b/hw/occ.c
@@ -26,6 +26,7 @@
 #include <errorlog.h>
 #include <opal-api.h>
 #include <opal-msg.h>
+#include <timer.h>
 
 /* OCC Communication Area for PStates */
 
@@ -715,9 +716,11 @@ static struct fsp_client fsp_occ_client = {
 #define OCB_OCI_OCCMISC_OR	0x6a022
 #define OCB_OCI_OCIMISC_IRQ		PPC_BIT(0)
 #define OCB_OCI_OCIMISC_IRQ_TMGT	PPC_BIT(1)
+#define OCB_OCI_OCIMISC_IRQ_SLW_TMR	PPC_BIT(14)
 #define OCB_OCI_OCIMISC_IRQ_OPAL_DUMMY	PPC_BIT(15)
 #define OCB_OCI_OCIMISC_MASK		(OCB_OCI_OCIMISC_IRQ_TMGT | \
-					 OCB_OCI_OCIMISC_IRQ_OPAL_DUMMY )
+					 OCB_OCI_OCIMISC_IRQ_OPAL_DUMMY | \
+					 OCB_OCI_OCIMISC_IRQ_SLW_TMR)
 
 void occ_send_dummy_interrupt(void)
 {
@@ -765,6 +768,8 @@ void occ_interrupt(uint32_t chip_id)
 	/* Dispatch */
 	if (ireg & OCB_OCI_OCIMISC_IRQ_TMGT)
 		prd_tmgt_interrupt(chip_id);
+	if (ireg & OCB_OCI_OCIMISC_IRQ_SLW_TMR)
+		check_timers(true);
 
 	/* We may have masked-out OCB_OCI_OCIMISC_IRQ in the previous
 	 * OCCMISC_AND write. Check if there are any new source bits set,
diff --git a/hw/slw.c b/hw/slw.c
index 9aec41c..a009090 100644
--- a/hw/slw.c
+++ b/hw/slw.c
@@ -40,6 +40,14 @@ static uint32_t slw_saved_reset[MAX_RESET_PATCH_SIZE];
 static bool slw_current_le = false;
 #endif /* __HAVE_LIBPORE__ */
 
+/* SLW timer related stuff */
+static bool slw_has_timer;
+static uint64_t slw_timer_inc;
+static uint64_t slw_timer_target;
+static uint32_t slw_timer_chip;
+static uint64_t slw_last_gen;
+static uint64_t slw_last_gen_stamp;
+
 /* Assembly in head.S */
 extern void enter_rvwinkle(void);
 
@@ -897,7 +905,7 @@ static void slw_patch_regs(struct proc_chip *chip)
 
 static void slw_init_chip(struct proc_chip *chip)
 {
-	int rc __unused;
+	int64_t rc;
 	struct cpu_thread *c;
 
 	prlog(PR_DEBUG, "SLW: Init chip 0x%x\n", chip->id);
@@ -913,7 +921,7 @@ static void slw_init_chip(struct proc_chip *chip)
 				&chip->slw_image_size);
 	if (rc != 0) {
 		log_simple_error(&e_info(OPAL_RC_SLW_INIT),
-			"SLW: Error %d reading SLW image size\n", rc);
+			"SLW: Error %lld reading SLW image size\n", rc);
 		/* XXX Panic ? */
 		chip->slw_base = 0;
 		chip->slw_bar_size = 0;
@@ -939,19 +947,6 @@ static void slw_init_chip(struct proc_chip *chip)
 	}
 }
 
-void slw_init(void)
-{
-	struct proc_chip *chip;
-
-	if (proc_gen != proc_gen_p8)
-		return;
-
-	for_each_chip(chip)
-		slw_init_chip(chip);
-
-	add_cpu_idle_state_properties();
-}
-
 /* Workarounds while entering fast-sleep */
 
 static void fast_sleep_enter(void)
@@ -1085,3 +1080,161 @@ static int64_t opal_slw_set_reg(uint64_t cpu_pir, uint64_t sprn, uint64_t val)
 
 opal_call(OPAL_SLW_SET_REG, opal_slw_set_reg, 3);
 #endif /* __HAVE_LIBPORE__ */
+
+static void slw_dump_timer_ffdc(void)
+{
+	uint64_t i, val;
+	int64_t rc;
+
+	static const uint32_t dump_regs[] = {
+		0xe0000, 0xe0001, 0xe0002, 0xe0003,
+		0xe0004, 0xe0005, 0xe0006, 0xe0007,
+		0xe0008, 0xe0009, 0xe000a, 0xe000b,
+		0xe000c, 0xe000d, 0xe000e, 0xe000f,
+		0xe0010, 0xe0011, 0xe0012, 0xe0013,
+		0xe0014, 0xe0015, 0xe0016, 0xe0017,
+		0xe0018, 0xe0019,
+		0x5001c,
+		0x50038, 0x50039, 0x5003a, 0x5003b
+	};
+
+	prlog(PR_ERR, "SLW: Register state:\n");
+
+	for (i = 0; i < ARRAY_SIZE(dump_regs); i++) {
+		uint32_t reg = dump_regs[i];
+		rc = xscom_read(slw_timer_chip, reg, &val);
+		if (rc) {
+			prlog(PR_ERR, "SLW: XSCOM error %lld reading"
+			      " reg 0x%x\n", rc, reg);
+			break;
+		}
+		prlog(PR_ERR, "SLW:  %5x = %016llx\n", reg, val);
+	}
+}
+
+/* This is called with the timer lock held, so there is no
+ * issue with re-entrancy or concurrence
+ */
+void slw_update_timer_expiry(uint64_t new_target)
+{
+	uint64_t count, gen, gen2, req, now = mftb();
+	int64_t rc;
+
+	if (!slw_has_timer || new_target == slw_timer_target)
+		return;
+
+	slw_timer_target = new_target;
+
+	/* Calculate how many increments from now, rounded up */
+	if (now < new_target)
+		count = (new_target - now + slw_timer_inc - 1) / slw_timer_inc;
+	else
+		count = 1;
+
+	/* Max counter is 24-bit */
+	if (count > 0xffffff)
+		count = 0xffffff;
+	/* Fabricate update request */
+	req = (1ull << 63) | (count << 32);
+
+	prlog(PR_TRACE, "SLW: TMR expiry: 0x%llx, req: %016llx\n", count, req);
+
+	do {
+		/* Grab generation and spin if odd */
+		for (;;) {
+			rc = xscom_read(slw_timer_chip, 0xE0006, &gen);
+			if (rc) {
+				prerror("SLW: Error %lld reading tmr gen "
+					" count\n", rc);
+				return;
+			}
+			if (!(gen & 1))
+				break;
+			if (tb_compare(now + msecs_to_tb(1), mftb()) == TB_ABEFOREB) {
+				prerror("SLW: Stuck with odd generation !\n");
+				slw_has_timer = false;
+				slw_dump_timer_ffdc();
+				return;
+			}
+		}
+
+		rc = xscom_write(slw_timer_chip, 0x5003A, req);
+		if (rc) {
+			prerror("SLW: Error %lld writing tmr request\n", rc);
+			return;
+		}
+
+		/* Re-check gen count */
+		rc = xscom_read(slw_timer_chip, 0xE0006, &gen2);
+		if (rc) {
+			prerror("SLW: Error %lld re-reading tmr gen "
+				" count\n", rc);
+			return;
+		}
+	} while(gen != gen2);
+
+	/* Check if the timer is working. If at least 1ms has elapsed
+	 * since the last call to this function, check that the gen
+	 * count has changed
+	 */
+	if (tb_compare(slw_last_gen_stamp + msecs_to_tb(1), now)
+	    == TB_ABEFOREB) {
+		if (slw_last_gen == gen) {
+			prlog(PR_ERR,
+			      "SLW: Timer appears to not be running !\n");
+			slw_has_timer = false;
+			slw_dump_timer_ffdc();
+		}
+		slw_last_gen = gen;
+		slw_last_gen_stamp = mftb();
+	}
+
+	prlog(PR_TRACE, "SLW: gen: %llx\n", gen);
+}
+
+bool slw_timer_ok(void)
+{
+	return slw_has_timer;
+}
+
+static void slw_init_timer(void)
+{
+	struct dt_node *np;
+	int64_t rc;
+	uint32_t tick_us;
+
+	np = dt_find_compatible_node(dt_root, NULL, "ibm,power8-sbe-timer");
+	if (!np)
+		return;
+
+	slw_timer_chip = dt_get_chip_id(np);
+	tick_us = dt_prop_get_u32(np, "tick-time-us");
+	slw_timer_inc = usecs_to_tb(tick_us);
+	slw_timer_target = ~0ull;
+
+	rc = xscom_read(slw_timer_chip, 0xE0006, &slw_last_gen);
+	if (rc) {
+		prerror("SLW: Error %lld reading tmr gen count\n", rc);
+		return;
+	}
+	slw_last_gen_stamp = mftb();
+
+	prlog(PR_INFO, "SLW: Timer facility on chip %d, resolution %dus\n",
+	      slw_timer_chip, tick_us);
+	slw_has_timer = true;
+}
+
+void slw_init(void)
+{
+	struct proc_chip *chip;
+
+	if (proc_gen != proc_gen_p8)
+		return;
+
+	for_each_chip(chip)
+		slw_init_chip(chip);
+
+	add_cpu_idle_state_properties();
+
+	slw_init_timer();
+}
diff --git a/include/skiboot.h b/include/skiboot.h
index 2ec3e7a..30a94d4 100644
--- a/include/skiboot.h
+++ b/include/skiboot.h
@@ -263,6 +263,12 @@ extern void *create_dtb(const struct dt_node *root);
 /* SLW reinit function for switching core settings */
 extern int64_t slw_reinit(uint64_t flags);
 
+/* SLW update timer function */
+extern void slw_update_timer_expiry(uint64_t new_target);
+
+/* Is SLW timer available ? */
+extern bool slw_timer_ok(void);
+
 /* Fallback fake RTC */
 extern void fake_rtc_init(void);
 
