blob: dc4a484b3ecf93848040845cda97f07a4ec62149 [file] [log] [blame]
// 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();
}