/* 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 #include #include #include #include #include #include #include #include #include #include /* 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(); }