| /* Copyright 2016 The ChromiumOS Authors |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| /* Clocks and power management settings */ |
| |
| #include "builtin/assert.h" |
| #include "chipset.h" |
| #include "clock.h" |
| #include "clock_chip.h" |
| #include "common.h" |
| #include "console.h" |
| #include "cpu.h" |
| #include "hooks.h" |
| #include "host_command.h" |
| #include "hwtimer.h" |
| #include "registers.h" |
| #include "rtc.h" |
| #include "system.h" |
| #include "task.h" |
| #include "timer.h" |
| #include "util.h" |
| |
| /* Console output macros */ |
| #define CPUTS(outstr) cputs(CC_CLOCK, outstr) |
| #define CPRINTS(format, args...) cprints(CC_CLOCK, format, ##args) |
| |
| /* Convert decimal to BCD */ |
| static uint8_t u8_to_bcd(uint8_t val) |
| { |
| /* Fast division by 10 (when lacking HW div) */ |
| uint32_t quot = ((uint32_t)val * 0xCCCD) >> 19; |
| uint32_t rem = val - quot * 10; |
| |
| return rem | (quot << 4); |
| } |
| |
| /* Convert between RTC regs in BCD and seconds */ |
| static uint32_t rtc_tr_to_sec(uint32_t rtc_tr) |
| { |
| uint32_t sec; |
| |
| /* convert the hours field */ |
| sec = (((rtc_tr & 0x300000) >> 20) * 10 + ((rtc_tr & 0xf0000) >> 16)) * |
| 3600; |
| /* convert the minutes field */ |
| sec += (((rtc_tr & 0x7000) >> 12) * 10 + ((rtc_tr & 0xf00) >> 8)) * 60; |
| /* convert the seconds field */ |
| sec += ((rtc_tr & 0x70) >> 4) * 10 + (rtc_tr & 0xf); |
| return sec; |
| } |
| |
| static uint32_t sec_to_rtc_tr(uint32_t sec) |
| { |
| uint32_t rtc_tr; |
| uint8_t hour; |
| uint8_t min; |
| |
| sec %= SECS_PER_DAY; |
| /* convert the hours field */ |
| hour = sec / 3600; |
| rtc_tr = u8_to_bcd(hour) << 16; |
| /* convert the minutes field */ |
| sec -= hour * 3600; |
| min = sec / 60; |
| rtc_tr |= u8_to_bcd(min) << 8; |
| /* convert the seconds field */ |
| sec -= min * 60; |
| rtc_tr |= u8_to_bcd(sec); |
| |
| return rtc_tr; |
| } |
| |
| /* Register setup before RTC alarm is allowed for update */ |
| static void pre_work_set_rtc_alarm(void) |
| { |
| rtc_unlock_regs(); |
| |
| /* Make sure alarm is disabled */ |
| STM32_RTC_CR &= ~STM32_RTC_CR_ALRAE; |
| while (!(STM32_RTC_ISR & STM32_RTC_ISR_ALRAWF)) |
| ; |
| STM32_RTC_ISR &= ~STM32_RTC_ISR_ALRAF; |
| } |
| |
| /* Register setup after RTC alarm is updated */ |
| static void post_work_set_rtc_alarm(void) |
| { |
| STM32_EXTI_PR = EXTI_RTC_ALR_EVENT; |
| |
| /* Enable alarm and alarm interrupt */ |
| STM32_EXTI_IMR |= EXTI_RTC_ALR_EVENT; |
| STM32_RTC_CR |= STM32_RTC_CR_ALRAE; |
| |
| rtc_lock_regs(); |
| } |
| |
| #ifdef CONFIG_HOSTCMD_RTC |
| static struct wake_time host_wake_time; |
| |
| bool is_host_wake_alarm_expired(timestamp_t ts) |
| { |
| return host_wake_time.ts.val && |
| timestamp_expired(host_wake_time.ts, &ts); |
| } |
| |
| void restore_host_wake_alarm(void) |
| { |
| if (!host_wake_time.ts.val) |
| return; |
| |
| pre_work_set_rtc_alarm(); |
| |
| /* Set alarm time */ |
| STM32_RTC_ALRMAR = host_wake_time.rtc_alrmar; |
| |
| post_work_set_rtc_alarm(); |
| } |
| |
| static uint32_t rtc_dr_to_sec(uint32_t rtc_dr) |
| { |
| struct calendar_date time; |
| uint32_t sec; |
| |
| time.year = |
| (((rtc_dr & 0xf00000) >> 20) * 10 + ((rtc_dr & 0xf0000) >> 16)); |
| time.month = (((rtc_dr & 0x1000) >> 12) * 10 + ((rtc_dr & 0xf00) >> 8)); |
| time.day = ((rtc_dr & 0x30) >> 4) * 10 + (rtc_dr & 0xf); |
| |
| sec = date_to_sec(time); |
| |
| return sec; |
| } |
| |
| static uint32_t sec_to_rtc_dr(uint32_t sec) |
| { |
| struct calendar_date time; |
| uint32_t rtc_dr; |
| |
| time = sec_to_date(sec); |
| |
| rtc_dr = u8_to_bcd(time.year) << 16; |
| rtc_dr |= u8_to_bcd(time.month) << 8; |
| rtc_dr |= u8_to_bcd(time.day); |
| |
| return rtc_dr; |
| } |
| #endif |
| |
| uint32_t rtc_to_sec(const struct rtc_time_reg *rtc) |
| { |
| uint32_t sec = 0; |
| #ifdef CONFIG_HOSTCMD_RTC |
| sec = rtc_dr_to_sec(rtc->rtc_dr); |
| #endif |
| return sec + (rtcss_to_us(rtc->rtc_ssr) / SECOND) + |
| rtc_tr_to_sec(rtc->rtc_tr); |
| } |
| |
| void sec_to_rtc(uint32_t sec, struct rtc_time_reg *rtc) |
| { |
| rtc->rtc_dr = 0; |
| #ifdef CONFIG_HOSTCMD_RTC |
| rtc->rtc_dr = sec_to_rtc_dr(sec); |
| #endif |
| rtc->rtc_tr = sec_to_rtc_tr(sec); |
| rtc->rtc_ssr = 0; |
| } |
| |
| /* Return sub-10-sec time diff between two rtc readings |
| * |
| * Note: this function assumes rtc0 was sampled before rtc1. |
| * Additionally, this function only looks at the difference mod 10 |
| * seconds. |
| */ |
| uint32_t get_rtc_diff(const struct rtc_time_reg *rtc0, |
| const struct rtc_time_reg *rtc1) |
| { |
| uint32_t rtc0_val, rtc1_val, diff; |
| |
| rtc0_val = (rtc0->rtc_tr & 0xF) * SECOND + rtcss_to_us(rtc0->rtc_ssr); |
| rtc1_val = (rtc1->rtc_tr & 0xF) * SECOND + rtcss_to_us(rtc1->rtc_ssr); |
| diff = rtc1_val; |
| if (rtc1_val < rtc0_val) { |
| /* rtc_ssr has wrapped, since we assume rtc0 < rtc1, add |
| * 10 seconds to get the correct value |
| */ |
| diff += 10 * SECOND; |
| } |
| diff -= rtc0_val; |
| return diff; |
| } |
| |
| void rtc_read(struct rtc_time_reg *rtc) |
| { |
| /* |
| * Read current time synchronously. Each register must be read |
| * twice with identical values because glitches may occur for reads |
| * close to the RTCCLK edge. |
| */ |
| do { |
| rtc->rtc_dr = STM32_RTC_DR; |
| |
| do { |
| rtc->rtc_tr = STM32_RTC_TR; |
| |
| do { |
| rtc->rtc_ssr = STM32_RTC_SSR; |
| } while (rtc->rtc_ssr != STM32_RTC_SSR); |
| |
| } while (rtc->rtc_tr != STM32_RTC_TR); |
| |
| } while (rtc->rtc_dr != STM32_RTC_DR); |
| } |
| |
| void set_rtc_alarm(uint32_t delay_s, uint32_t delay_us, |
| struct rtc_time_reg *rtc, uint8_t save_alarm) |
| { |
| uint32_t alarm_sec = 0; |
| uint32_t alarm_us = 0; |
| |
| if (delay_s == EC_RTC_ALARM_CLEAR && !delay_us) { |
| reset_rtc_alarm(rtc); |
| return; |
| } |
| |
| /* Alarm timeout must be within 1 day (86400 seconds) */ |
| ASSERT((delay_s + delay_us / SECOND) < SECS_PER_DAY); |
| |
| pre_work_set_rtc_alarm(); |
| rtc_read(rtc); |
| |
| /* Calculate alarm time */ |
| alarm_sec = rtc_tr_to_sec(rtc->rtc_tr) + delay_s; |
| |
| if (delay_us) { |
| alarm_us = rtcss_to_us(rtc->rtc_ssr) + delay_us; |
| alarm_sec = alarm_sec + alarm_us / SECOND; |
| alarm_us = alarm_us % SECOND; |
| } |
| |
| /* |
| * If seconds is greater than 1 day, subtract by 1 day to deal with |
| * 24-hour rollover. |
| */ |
| if (alarm_sec >= SECS_PER_DAY) |
| alarm_sec -= SECS_PER_DAY; |
| |
| /* |
| * Set alarm time in seconds and check for match on |
| * hours, minutes, and seconds. |
| */ |
| STM32_RTC_ALRMAR = sec_to_rtc_tr(alarm_sec) | 0xc0000000; |
| |
| /* |
| * Set alarm time in subseconds and check for match on subseconds. |
| * If the caller doesn't specify subsecond delay (e.g. host command), |
| * just align the alarm time to second. |
| */ |
| STM32_RTC_ALRMASSR = delay_us ? (us_to_rtcss(alarm_us) | 0x0f000000) : |
| 0; |
| |
| #ifdef CONFIG_HOSTCMD_RTC |
| /* |
| * If alarm is set by the host, preserve the wake time timestamp |
| * and alarm registers. |
| */ |
| if (save_alarm) { |
| host_wake_time.ts.val = delay_s * SECOND + get_time().val; |
| host_wake_time.rtc_alrmar = STM32_RTC_ALRMAR; |
| } |
| #endif |
| post_work_set_rtc_alarm(); |
| } |
| |
| uint32_t get_rtc_alarm(void) |
| { |
| struct rtc_time_reg now; |
| uint32_t now_sec; |
| uint32_t alarm_sec; |
| |
| if (!(STM32_RTC_CR & STM32_RTC_CR_ALRAE)) |
| return 0; |
| |
| rtc_read(&now); |
| |
| now_sec = rtc_tr_to_sec(now.rtc_tr); |
| alarm_sec = rtc_tr_to_sec(STM32_RTC_ALRMAR & 0x3fffff); |
| |
| return ((alarm_sec < now_sec) ? SECS_PER_DAY : 0) + |
| (alarm_sec - now_sec); |
| } |
| |
| void reset_rtc_alarm(struct rtc_time_reg *rtc) |
| { |
| rtc_unlock_regs(); |
| |
| /* Disable alarm */ |
| STM32_RTC_CR &= ~STM32_RTC_CR_ALRAE; |
| STM32_RTC_ISR &= ~STM32_RTC_ISR_ALRAF; |
| |
| /* Disable RTC alarm interrupt */ |
| STM32_EXTI_IMR &= ~EXTI_RTC_ALR_EVENT; |
| STM32_EXTI_PR = EXTI_RTC_ALR_EVENT; |
| |
| /* Clear the pending RTC alarm IRQ in NVIC */ |
| task_clear_pending_irq(STM32_IRQ_RTC_ALARM); |
| |
| /* Read current time */ |
| rtc_read(rtc); |
| |
| rtc_lock_regs(); |
| } |
| |
| #ifdef CONFIG_HOSTCMD_RTC |
| static void set_rtc_host_event(void) |
| { |
| host_set_single_event(EC_HOST_EVENT_RTC); |
| } |
| DECLARE_DEFERRED(set_rtc_host_event); |
| #endif |
| |
| test_mockable void rtc_alarm_irq(void) |
| { |
| struct rtc_time_reg rtc; |
| reset_rtc_alarm(&rtc); |
| |
| #ifdef CONFIG_HOSTCMD_RTC |
| /* Wake up the host if there is a saved rtc wake alarm. */ |
| if (host_wake_time.ts.val) { |
| host_wake_time.ts.val = 0; |
| hook_call_deferred(&set_rtc_host_event_data, 0); |
| } |
| #endif |
| } |
| |
| static void __rtc_alarm_irq(void) |
| { |
| rtc_alarm_irq(); |
| } |
| DECLARE_IRQ(STM32_IRQ_RTC_ALARM, __rtc_alarm_irq, 1); |
| |
| __attribute__((weak)) int clock_get_timer_freq(void) |
| { |
| return clock_get_freq(); |
| } |
| |
| void clock_init(void) |
| { |
| /* |
| * The initial state : |
| * SYSCLK from HSI (=8MHz), no divider on AHB, APB1, APB2 |
| * PLL unlocked, RTC enabled on LSE |
| */ |
| |
| /* |
| * put 1 Wait-State for flash access to ensure proper reads at 48Mhz |
| * and enable prefetch buffer. |
| */ |
| STM32_FLASH_ACR = STM32_FLASH_ACR_LATENCY | STM32_FLASH_ACR_PRFTEN; |
| |
| #ifdef CHIP_FAMILY_STM32F4 |
| /* Enable data and instruction cache. */ |
| STM32_FLASH_ACR |= STM32_FLASH_ACR_DCEN | STM32_FLASH_ACR_ICEN; |
| #endif |
| |
| config_hispeed_clock(); |
| |
| rtc_init(); |
| } |
| |
| #ifdef CHIP_FAMILY_STM32F4 |
| void reset_flash_cache(void) |
| { |
| /* Disable data and instruction cache. */ |
| STM32_FLASH_ACR &= ~(STM32_FLASH_ACR_DCEN | STM32_FLASH_ACR_ICEN); |
| |
| /* Reset data and instruction cache */ |
| STM32_FLASH_ACR |= STM32_FLASH_ACR_DCRST | STM32_FLASH_ACR_ICRST; |
| } |
| DECLARE_HOOK(HOOK_SYSJUMP, reset_flash_cache, HOOK_PRIO_DEFAULT); |
| #endif |
| |
| /*****************************************************************************/ |
| /* Console commands */ |
| |
| void print_system_rtc(enum console_channel ch) |
| { |
| uint32_t sec; |
| struct rtc_time_reg rtc; |
| |
| rtc_read(&rtc); |
| sec = rtc_to_sec(&rtc); |
| |
| cprintf(ch, "RTC: 0x%08x (%d.00 s)\n", sec, sec); |
| } |
| |
| #ifdef CONFIG_CMD_RTC |
| static int command_system_rtc(int argc, const char **argv) |
| { |
| char *e; |
| uint32_t t; |
| |
| if (argc == 3 && !strcasecmp(argv[1], "set")) { |
| t = strtoi(argv[2], &e, 0); |
| if (*e) |
| return EC_ERROR_PARAM2; |
| rtc_set(t); |
| } else if (argc > 1) |
| return EC_ERROR_INVAL; |
| |
| print_system_rtc(CC_COMMAND); |
| |
| return EC_SUCCESS; |
| } |
| DECLARE_CONSOLE_COMMAND(rtc, command_system_rtc, "[set <seconds>]", |
| "Get/set real-time clock"); |
| |
| #ifdef CONFIG_CMD_RTC_ALARM |
| static int command_rtc_alarm_test(int argc, const char **argv) |
| { |
| int s = 1, us = 0; |
| struct rtc_time_reg rtc; |
| char *e; |
| |
| ccprintf("Setting RTC alarm\n"); |
| |
| if (argc > 1) { |
| s = strtoi(argv[1], &e, 10); |
| if (*e) |
| return EC_ERROR_PARAM1; |
| } |
| if (argc > 2) { |
| us = strtoi(argv[2], &e, 10); |
| if (*e) |
| return EC_ERROR_PARAM2; |
| } |
| |
| set_rtc_alarm(s, us, &rtc, 0); |
| return EC_SUCCESS; |
| } |
| DECLARE_CONSOLE_COMMAND(rtc_alarm, command_rtc_alarm_test, |
| "[seconds [microseconds]]", "Test alarm"); |
| #endif /* CONFIG_CMD_RTC_ALARM */ |
| #endif /* CONFIG_CMD_RTC */ |
| |
| /*****************************************************************************/ |
| /* Host commands */ |
| |
| #ifdef CONFIG_HOSTCMD_RTC |
| static enum ec_status system_rtc_get_value(struct host_cmd_handler_args *args) |
| { |
| struct ec_response_rtc *r = args->response; |
| struct rtc_time_reg rtc; |
| |
| rtc_read(&rtc); |
| r->time = rtc_to_sec(&rtc); |
| args->response_size = sizeof(*r); |
| |
| return EC_RES_SUCCESS; |
| } |
| DECLARE_HOST_COMMAND(EC_CMD_RTC_GET_VALUE, system_rtc_get_value, |
| EC_VER_MASK(0)); |
| |
| static enum ec_status system_rtc_set_value(struct host_cmd_handler_args *args) |
| { |
| const struct ec_params_rtc *p = args->params; |
| |
| rtc_set(p->time); |
| return EC_RES_SUCCESS; |
| } |
| DECLARE_HOST_COMMAND(EC_CMD_RTC_SET_VALUE, system_rtc_set_value, |
| EC_VER_MASK(0)); |
| |
| static enum ec_status system_rtc_set_alarm(struct host_cmd_handler_args *args) |
| { |
| struct rtc_time_reg rtc; |
| const struct ec_params_rtc *p = args->params; |
| |
| /* Alarm timeout must be within 1 day (86400 seconds) */ |
| if (p->time >= SECS_PER_DAY) |
| return EC_RES_INVALID_PARAM; |
| |
| set_rtc_alarm(p->time, 0, &rtc, 1); |
| return EC_RES_SUCCESS; |
| } |
| DECLARE_HOST_COMMAND(EC_CMD_RTC_SET_ALARM, system_rtc_set_alarm, |
| EC_VER_MASK(0)); |
| |
| static enum ec_status system_rtc_get_alarm(struct host_cmd_handler_args *args) |
| { |
| struct ec_response_rtc *r = args->response; |
| |
| r->time = get_rtc_alarm(); |
| args->response_size = sizeof(*r); |
| |
| return EC_RES_SUCCESS; |
| } |
| DECLARE_HOST_COMMAND(EC_CMD_RTC_GET_ALARM, system_rtc_get_alarm, |
| EC_VER_MASK(0)); |
| |
| #endif /* CONFIG_HOSTCMD_RTC */ |