/* Copyright 2020 The Chromium OS Authors. All rights reserved. * 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 "lct_chip.h" #include "console.h" #include "hooks.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, 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