diff options
-rw-r--r-- | core/time-utils.c | 17 | ||||
-rw-r--r-- | hw/Makefile.inc | 2 | ||||
-rw-r--r-- | hw/lpc-rtc.c | 239 | ||||
-rw-r--r-- | include/skiboot.h | 1 | ||||
-rw-r--r-- | include/time-utils.h | 18 | ||||
-rw-r--r-- | platforms/qemu/qemu.c | 5 |
6 files changed, 264 insertions, 18 deletions
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 9f5fa31..2ec3e7a 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) |