| /* Copyright 2020 The ChromiumOS Authors |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| /* LCT (Long Countdown Timer) module for Chrome EC */ |
| #include "console.h" |
| #include "hooks.h" |
| #include "lct_chip.h" |
| #include "registers.h" |
| #include "rtc.h" |
| #include "task.h" |
| #include "timer.h" |
| #include "util.h" |
| |
| #define LCT_CLK_ENABLE_DELAY_USEC 150 |
| |
| #define CPRINTF(format, args...) cprintf(CC_CLOCK, format, ##args) |
| #define CPRINTS(format, args...) cprints(CC_CLOCK, format, ##args) |
| |
| void npcx_lct_sel_power_src(enum NPCX_LCT_PWR_SRC pwr_src) |
| { |
| if (IS_BIT_SET(NPCX_LCTCONT, NPCX_LCTCONT_EN)) { |
| CPRINTS("Don't set power source when LCT is enabled"); |
| return; |
| } |
| |
| if (pwr_src == NPCX_LCT_PWR_SRC_VSBY) |
| SET_BIT(NPCX_LCTCONT, NPCX_LCTCONT_VSBY_PWR); |
| else |
| CLEAR_BIT(NPCX_LCTCONT, NPCX_LCTCONT_VSBY_PWR); |
| } |
| |
| void npcx_lct_enable_clk(uint8_t enable) |
| { |
| if (IS_BIT_SET(NPCX_LCTCONT, NPCX_LCTCONT_EN)) { |
| CPRINTS("Don't set/unset clock when LCT is enabled"); |
| return; |
| } |
| |
| if (enable) { |
| SET_BIT(NPCX_LCTCONT, NPCX_LCTCONT_CLK_EN); |
| /* |
| * This bit must be set to 1 at least tLCTCKEN (150 us) |
| * before the LCT is enabled. |
| */ |
| udelay(LCT_CLK_ENABLE_DELAY_USEC); |
| } else { |
| CLEAR_BIT(NPCX_LCTCONT, NPCX_LCTCONT_CLK_EN); |
| } |
| } |
| |
| void npcx_lct_enable(uint8_t enable) |
| { |
| enable = !!enable; |
| SET_FIELD(NPCX_LCTCONT, NPCX_LCTCONT_EN_FIELD, enable); |
| /* Wait until the bit value equals to what is set */ |
| while (IS_BIT_SET(NPCX_LCTCONT, NPCX_LCTCONT_EN) != enable) |
| ; |
| } |
| |
| void npcx_lct_config(int seconds, int psl_ena, int int_ena) |
| { |
| if (IS_BIT_SET(NPCX_LCTCONT, NPCX_LCTCONT_EN)) { |
| CPRINTS("Don't config LCT when LCT is enabled"); |
| return; |
| } |
| |
| /* LCT can count max to (16 weeks - 1 second) */ |
| if (seconds > NPCX_LCT_MAX) { |
| CPRINTS("LCT time is out of range"); |
| return; |
| } |
| |
| /* Clear pending LCT event first */ |
| NPCX_LCTSTAT = BIT(NPCX_LCTSTAT_EVST); |
| |
| NPCX_LCTWEEK = seconds / SECS_PER_WEEK; |
| seconds %= SECS_PER_WEEK; |
| NPCX_LCTDAY = seconds / SECS_PER_DAY; |
| seconds %= SECS_PER_DAY; |
| NPCX_LCTHOUR = seconds / SECS_PER_HOUR; |
| seconds %= SECS_PER_HOUR; |
| NPCX_LCTMINUTE = seconds / SECS_PER_MINUTE; |
| NPCX_LCTSECOND = seconds % SECS_PER_MINUTE; |
| |
| if (psl_ena) { |
| if (IS_BIT_SET(NPCX_LCTCONT, NPCX_LCTCONT_VSBY_PWR)) |
| SET_BIT(NPCX_LCTCONT, NPCX_LCTCONT_PSL_EN); |
| else |
| CPRINTS("LCT must source VSBY to support PSL wakeup"); |
| } |
| |
| if (int_ena) |
| SET_BIT(NPCX_LCTCONT, NPCX_LCTCONT_EVEN); |
| } |
| |
| uint32_t npcx_lct_get_time(void) |
| { |
| uint32_t second; |
| uint8_t week, day, hour, minute; |
| |
| do { |
| week = NPCX_LCTWEEK; |
| day = NPCX_LCTDAY; |
| hour = NPCX_LCTHOUR; |
| minute = NPCX_LCTMINUTE; |
| second = NPCX_LCTSECOND; |
| } while (week != NPCX_LCTWEEK || day != NPCX_LCTDAY || |
| hour != NPCX_LCTHOUR || minute != NPCX_LCTMINUTE || |
| second != NPCX_LCTSECOND); |
| |
| second += minute * SECS_PER_MINUTE + hour * SECS_PER_HOUR + |
| day * SECS_PER_DAY + week * SECS_PER_WEEK; |
| |
| return second; |
| } |
| |
| void npcx_lct_clear_event(void) |
| { |
| NPCX_LCTSTAT = BIT(NPCX_LCTSTAT_EVST); |
| } |
| |
| int npcx_lct_is_event_set(void) |
| { |
| return IS_BIT_SET(NPCX_LCTSTAT, NPCX_LCTSTAT_EVST); |
| } |
| |
| static void npcx_lct_init(void) |
| { |
| /* Disable LCT */ |
| npcx_lct_enable(0); |
| /* Clear control and status registers */ |
| NPCX_LCTCONT = 0x0; |
| npcx_lct_clear_event(); |
| /* Clear all timer registers */ |
| NPCX_LCTSECOND = 0x0; |
| NPCX_LCTMINUTE = 0x0; |
| NPCX_LCTHOUR = 0x0; |
| NPCX_LCTDAY = 0x0; |
| NPCX_LCTWEEK = 0x0; |
| } |
| DECLARE_HOOK(HOOK_INIT, npcx_lct_init, HOOK_PRIO_DEFAULT); |
| |
| #ifdef CONFIG_CMD_RTC_ALARM |
| static int command_lctalarm(int argc, const char **argv) |
| { |
| char *e; |
| int seconds; |
| |
| seconds = strtoi(argv[1], &e, 0); |
| if (*e) |
| return EC_ERROR_PARAM2; |
| |
| npcx_lct_enable(0); |
| npcx_lct_sel_power_src(NPCX_LCT_PWR_SRC_VSBY); |
| npcx_lct_enable_clk(1); |
| /* Enable LCT event interrupt and MIWU */ |
| npcx_lct_config(seconds, 0, 1); |
| task_disable_irq(NPCX_IRQ_LCT_WKINTF_2); |
| /* Enable wake-up input sources & clear pending bit */ |
| NPCX_WKPCL(MIWU_TABLE_2, LCT_WUI_GROUP) |= LCT_WUI_MASK; |
| NPCX_WKINEN(MIWU_TABLE_2, LCT_WUI_GROUP) |= LCT_WUI_MASK; |
| NPCX_WKEN(MIWU_TABLE_2, LCT_WUI_GROUP) |= LCT_WUI_MASK; |
| task_enable_irq(NPCX_IRQ_LCT_WKINTF_2); |
| npcx_lct_enable(1); |
| |
| return 0; |
| } |
| DECLARE_CONSOLE_COMMAND(lctalarm, command_lctalarm, "", ""); |
| #endif |