241 lines
5.3 KiB
C
241 lines
5.3 KiB
C
/* 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;
|
|
|
|
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(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();
|
|
}
|