diff options
author | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2015-07-03 13:49:22 +1000 |
---|---|---|
committer | Stewart Smith <stewart@linux.vnet.ibm.com> | 2015-07-03 17:09:44 +1000 |
commit | 642a2f8a27629ef0782b29d7a719cab0688456f8 (patch) | |
tree | fa37ea55b76dae1278ddd4f132f463366ea34286 /hw/lpc-rtc.c | |
parent | ef50b5f5ad2834996eabc7e10885659a1ef65196 (diff) | |
download | skiboot-642a2f8a27629ef0782b29d7a719cab0688456f8.zip skiboot-642a2f8a27629ef0782b29d7a719cab0688456f8.tar.gz skiboot-642a2f8a27629ef0782b29d7a719cab0688456f8.tar.bz2 |
plat/qemu: Add LPC based RTC support
This adds a driver for standard CMOS RTC chips and use it from
the QEMU platform.
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Signed-off-by: Stewart Smith <stewart@linux.vnet.ibm.com>
Diffstat (limited to 'hw/lpc-rtc.c')
-rw-r--r-- | hw/lpc-rtc.c | 239 |
1 files changed, 239 insertions, 0 deletions
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(); +} |