diff options
author | Angelos Mouzakitis <a.mouzakitis@virtualopensystems.com> | 2023-10-10 14:33:42 +0000 |
---|---|---|
committer | Angelos Mouzakitis <a.mouzakitis@virtualopensystems.com> | 2023-10-10 14:33:42 +0000 |
commit | af1a266670d040d2f4083ff309d732d648afba2a (patch) | |
tree | 2fc46203448ddcc6f81546d379abfaeb323575e9 /roms/skiboot/hw/lpc-rtc.c | |
parent | e02cda008591317b1625707ff8e115a4841aa889 (diff) |
Change-Id: Iaf8d18082d3991dec7c0ebbea540f092188eb4ec
Diffstat (limited to 'roms/skiboot/hw/lpc-rtc.c')
-rw-r--r-- | roms/skiboot/hw/lpc-rtc.c | 235 |
1 files changed, 235 insertions, 0 deletions
diff --git a/roms/skiboot/hw/lpc-rtc.c b/roms/skiboot/hw/lpc-rtc.c new file mode 100644 index 000000000..dc4a484b3 --- /dev/null +++ b/roms/skiboot/hw/lpc-rtc.c @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* + * Real Time Clock hanging off LPC + * + * Copyright 2015 IBM Corp. + */ + +#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; + + memset(&tm, 0, sizeof(tm)); + + 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(__be32 *__ymd, __be64 *__hmsm) +{ + uint8_t val; + int64_t rc = OPAL_SUCCESS; + struct tm tm; + uint32_t ymd; + uint64_t hmsm; + + if (!__ymd || !__hmsm) + 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, &ymd, &hmsm); + *__ymd = cpu_to_be32(ymd); + *__hmsm = cpu_to_be64(hmsm); + } + + 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(); +} |