diff --git a/core/time-utils.c b/core/time-utils.c
index 10a4da4..573e7f1 100644
--- a/core/time-utils.c
+++ b/core/time-utils.c
@@ -16,23 +16,6 @@
 
 #include <time-utils.h>
 
-/* MSB is byte 3, LSB is byte 0 */
-static unsigned int bcd_byte(uint32_t bcd, int byteno)
-{
-	bcd >>= byteno * 8;
-	return (bcd >> 4 & 0xf) * 10 + (bcd & 0xf);
-}
-
-static uint32_t int_to_bcd2(unsigned int x)
-{
-	return (((x / 10) << 4) & 0xf0) | (x % 10);
-}
-
-static uint32_t int_to_bcd4(unsigned int x)
-{
-	return int_to_bcd2(x / 100) << 8 | int_to_bcd2(x % 100);
-}
-
 /*
  * Converts an OPAL formated datetime into a struct tm. We ignore microseconds
  * as Linux doesn't use them anyway.
diff --git a/hw/Makefile.inc b/hw/Makefile.inc
index 8a67d75..034947c 100644
--- a/hw/Makefile.inc
+++ b/hw/Makefile.inc
@@ -6,7 +6,7 @@ HW_OBJS += homer.o slw.o occ.o fsi-master.o centaur.o
 HW_OBJS += nx.o nx-rng.o nx-crypto.o nx-842.o
 HW_OBJS += p7ioc.o p7ioc-inits.o p7ioc-phb.o p5ioc2.o p5ioc2-phb.o
 HW_OBJS += phb3.o sfc-ctrl.o fake-rtc.o bt.o p8-i2c.o prd.o
-HW_OBJS += dts.o
+HW_OBJS += dts.o lpc-rtc.o
 HW=hw/built-in.o
 
 include $(SRC)/hw/fsp/Makefile.inc
diff --git a/hw/lpc-rtc.c b/hw/lpc-rtc.c
new file mode 100644
index 0000000..63124df
--- /dev/null
+++ b/hw/lpc-rtc.c
@@ -0,0 +1,239 @@
+/* Copyright 2015 IBM Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 	http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <ipmi.h>
+#include <time.h>
+#include <time-utils.h>
+#include <device.h>
+#include <opal.h>
+#include <rtc.h>
+#include <lpc.h>
+#include <lock.h>
+#include <timebase.h>
+
+/* Legacy RTC registers */
+#define RTC_REG_SECONDS		0
+#define RTC_REG_MINUTES		2
+#define RTC_REG_HOURS		4
+#define RTC_REG_DAY_OF_WEEK	6
+#define RTC_REG_DAY_OF_MONTH	7
+#define RTC_REG_MONTH		8
+#define RTC_REG_YEAR		9
+#define RTC_REG_A		10
+#define   RTC_REG_A_UIP			0x80
+#define RTC_REG_B		11
+#define   RTC_REG_B_DIS_UPD		0x80
+#define   RTC_REG_B_PIE			0x40
+#define   RTC_REG_B_AIE			0x20
+#define   RTC_REG_B_UIE			0x10
+#define   RTC_REG_B_SQWE		0x08
+#define   RTC_REG_B_DM_BINARY		0x04
+#define   RTC_REG_B_24H			0x02
+#define   RTC_REG_B_DST_EN		0x01
+#define RTC_REG_C		12
+#define RTC_REG_D		13
+#define   RTC_REG_D_VALID		0x80
+
+/* Init value is no interrupts, 24H mode, updates enabled */
+#define RTC_REG_B_INIT		(RTC_REG_B_24H)
+
+static u32 rtc_port;
+static struct lock rtc_lock = LOCK_UNLOCKED;
+
+static uint8_t rtc_read(uint8_t reg)
+{
+	lpc_outb(reg, rtc_port);
+	return lpc_inb(rtc_port + 1);
+}
+
+static void rtc_write(uint8_t reg, uint8_t val)
+{
+	lpc_outb(reg, rtc_port);
+	lpc_outb(val, rtc_port + 1);
+}
+
+static bool lpc_rtc_read_tm(struct tm *tm)
+{
+	struct tm tm2;
+	unsigned int loops = 0;
+
+	/* Read until two series provide identical values, this
+	 * should deal with update races in all practical cases
+	 */
+	for (;;) {
+		tm2 = *tm;
+		tm->tm_sec = rtc_read(RTC_REG_SECONDS);
+		tm->tm_min = rtc_read(RTC_REG_MINUTES);
+		tm->tm_hour = rtc_read(RTC_REG_HOURS);
+		tm->tm_mday = rtc_read(RTC_REG_DAY_OF_MONTH);
+		tm->tm_mon = rtc_read(RTC_REG_MONTH);
+		tm->tm_year = rtc_read(RTC_REG_YEAR);
+		if (loops > 0 && memcmp(&tm2, tm, sizeof(struct tm)) == 0)
+			break;
+		loops++;
+		if (loops > 10) {
+			prerror("RTC: Failed to obtain stable values\n");
+			return false;
+		}
+	}
+	tm->tm_sec = bcd_byte(tm->tm_sec, 0);
+	tm->tm_min = bcd_byte(tm->tm_min, 0);
+	tm->tm_hour = bcd_byte(tm->tm_hour, 0);
+	tm->tm_mday = bcd_byte(tm->tm_mday, 0);
+	tm->tm_mon = bcd_byte(tm->tm_mon, 0) - 1;
+	tm->tm_year = bcd_byte(tm->tm_year, 0);
+
+	/* 2000 wrap */
+	if (tm->tm_year < 69)
+		tm->tm_year += 100;
+
+	/* Base */
+	tm->tm_year += 1900;
+
+	return true;
+}
+
+static void lpc_rtc_write_tm(struct tm *tm __unused)
+{
+	/* XXX */
+}
+
+static void lpc_init_time(void)
+{
+	uint8_t val;
+	struct tm tm;
+	bool valid;
+
+	lock(&rtc_lock);
+
+	/* If update is in progress, wait a bit */
+	val = rtc_read(RTC_REG_A);
+	if (val & RTC_REG_A_UIP)
+		time_wait_ms(10);
+
+	/* Read from RTC */
+	valid = lpc_rtc_read_tm(&tm);
+
+	unlock(&rtc_lock);
+
+	/* Update cache */
+	if (valid)
+		rtc_cache_update(&tm);
+}
+
+static void lpc_init_hw(void)
+{
+	lock(&rtc_lock);
+
+	/* Set REG B to a suitable default */
+	rtc_write(RTC_REG_B, RTC_REG_B_INIT);
+
+	unlock(&rtc_lock);
+}
+
+static int64_t lpc_opal_rtc_read(uint32_t *y_m_d,
+				 uint64_t *h_m_s_m)
+{
+	uint8_t val;
+	int64_t rc = OPAL_SUCCESS;
+	struct tm tm;
+
+	if (!y_m_d || !h_m_s_m)
+		return OPAL_PARAMETER;
+
+	/* Return busy if updating. This is somewhat racy, but will
+	 * do for now, most RTCs nowadays are smart enough to atomically
+	 * update. Alternatively we could just read from the cache...
+	 */
+	lock(&rtc_lock);
+	val = rtc_read(RTC_REG_A);
+	if (val & RTC_REG_A_UIP) {
+		unlock(&rtc_lock);
+		return OPAL_BUSY_EVENT;
+	}
+
+	/* Read from RTC */
+	if (lpc_rtc_read_tm(&tm))
+		rc = OPAL_SUCCESS;
+	else
+		rc = OPAL_HARDWARE;
+	unlock(&rtc_lock);
+
+	if (rc == OPAL_SUCCESS) {
+		/* Update cache */
+		rtc_cache_update(&tm);
+
+		/* Convert to OPAL time */
+		tm_to_datetime(&tm, y_m_d, h_m_s_m);
+	}
+
+	return rc;
+}
+
+static int64_t lpc_opal_rtc_write(uint32_t year_month_day,
+				  uint64_t hour_minute_second_millisecond)
+{
+	struct tm tm;
+
+	/* Convert to struct tm */
+	datetime_to_tm(year_month_day, hour_minute_second_millisecond, &tm);
+
+	/* Write it out */
+	lock(&rtc_lock);
+	lpc_rtc_write_tm(&tm);
+	unlock(&rtc_lock);
+
+	return OPAL_SUCCESS;
+}
+
+void lpc_rtc_init(void)
+{
+	struct dt_node *rtc_node, *np;
+
+	if (!lpc_present())
+		return;
+
+	/* We support only one */
+	rtc_node = dt_find_compatible_node(dt_root, NULL, "pnpPNP,b00");
+	if (!rtc_node)
+		return;
+
+	/* Get IO base */
+	rtc_port = dt_prop_get_cell_def(rtc_node, "reg", 1, 0);
+	if (!rtc_port) {
+		prerror("RTC: Can't find reg property\n");
+		return;
+	}
+	if (dt_prop_get_cell_def(rtc_node, "reg", 0, 0) != OPAL_LPC_IO) {
+		prerror("RTC: Unsupported address type\n");
+		return;
+	}
+
+	/* Init the HW */
+	lpc_init_hw();
+
+	/* Create OPAL API node and register OPAL calls */
+	np = dt_new(opal_node, "rtc");
+	dt_add_property_strings(np, "compatible", "ibm,opal-rtc");
+
+	opal_register(OPAL_RTC_READ, lpc_opal_rtc_read, 2);
+	opal_register(OPAL_RTC_WRITE, lpc_opal_rtc_write, 2);
+
+	/* Initialise the rtc cache */
+	lpc_init_time();
+}
diff --git a/include/skiboot.h b/include/skiboot.h
index bb0bc6f..30a94d4 100644
--- a/include/skiboot.h
+++ b/include/skiboot.h
@@ -216,6 +216,7 @@ extern void homer_init(void);
 extern void occ_pstates_init(void);
 extern void slw_init(void);
 extern void occ_fsp_init(void);
+extern void lpc_rtc_init(void);
 
 /* flash support */
 struct flash_chip;
diff --git a/include/time-utils.h b/include/time-utils.h
index 721b6f7..e0a9a98 100644
--- a/include/time-utils.h
+++ b/include/time-utils.h
@@ -20,6 +20,24 @@
 #include <stdint.h>
 #include <time.h>
 
+/* BCD conversion utilities. MSB is byte 3, LSB is byte 0 */
+
+static inline unsigned int bcd_byte(uint32_t bcd, int byteno)
+{
+	bcd >>= byteno * 8;
+	return (bcd >> 4 & 0xf) * 10 + (bcd & 0xf);
+}
+
+static inline uint32_t int_to_bcd2(unsigned int x)
+{
+	return (((x / 10) << 4) & 0xf0) | (x % 10);
+}
+
+static inline uint32_t int_to_bcd4(unsigned int x)
+{
+	return int_to_bcd2(x / 100) << 8 | int_to_bcd2(x % 100);
+}
+
 void tm_to_datetime(struct tm *tm, uint32_t *y_m_d, uint64_t *h_m_s_m);
 void datetime_to_tm(uint32_t y_m_d, uint64_t h_m_s_m, struct tm *tm);
 
diff --git a/platforms/qemu/qemu.c b/platforms/qemu/qemu.c
index 43e1221..66a6aa3 100644
--- a/platforms/qemu/qemu.c
+++ b/platforms/qemu/qemu.c
@@ -27,6 +27,11 @@ static void qemu_init(void)
 	/* Setup UART console for use by Linux via OPAL API */
 	if (!dummy_console_enabled())
 		uart_setup_opal_console();
+
+	/* Setup LPC RTC and use it as time source. Call after
+	 * chiptod_init()
+	 */
+	lpc_rtc_init();
 }
 
 static void qemu_dt_fixup_uart(struct dt_node *lpc)
