diff options
-rw-r--r-- | zephyr/drivers/cros_system/cros_system_npcx.c | 364 | ||||
-rw-r--r-- | zephyr/dts/bindings/gpio/gpio-enum-name.yaml | 1 | ||||
-rw-r--r-- | zephyr/dts/bindings/gpio/hibernate-wake-pins.yaml | 13 | ||||
-rw-r--r-- | zephyr/linker/CMakeLists.txt | 4 | ||||
-rw-r--r-- | zephyr/linker/npcx-lfw.ld | 10 | ||||
-rw-r--r-- | zephyr/projects/trogdor/lazor/gpio.dts | 1 | ||||
-rw-r--r-- | zephyr/shim/src/system.c | 10 |
7 files changed, 359 insertions, 44 deletions
diff --git a/zephyr/drivers/cros_system/cros_system_npcx.c b/zephyr/drivers/cros_system/cros_system_npcx.c index 82d95427af..07a7cf8c80 100644 --- a/zephyr/drivers/cros_system/cros_system_npcx.c +++ b/zephyr/drivers/cros_system/cros_system_npcx.c @@ -3,14 +3,61 @@ * found in the LICENSE file. */ +#include <arch/arm/aarch32/cortex_m/cmsis.h> #include <drivers/cros_system.h> +#include <drivers/gpio.h> #include <drivers/watchdog.h> #include <logging/log.h> #include <soc.h> +#include <soc/nuvoton_npcx/reg_def_cros.h> +#include <sys/util.h> + +#include "gpio.h" #include "rom_chip.h" +#include "soc_gpio.h" +#include "soc_miwu.h" +#include "system.h" LOG_MODULE_REGISTER(cros_system, LOG_LEVEL_ERR); +/** + * @brief Get a node from path '/hibernate_wakeup_pins' which has a property + * 'wakeup-pins' contains GPIO list for hibernate wake-up + * + * @return node identifier with that path. + */ +#define SYSTEM_DT_NODE_HIBERNATE_CONFIG DT_INST(0, cros_ec_hibernate_wake_pins) + +/** + * @brief Get the length of 'wakeup-pins' property + * + * @return length of 'wakeup-pins' prop which type is 'phandles' + */ +#define SYSTEM_DT_NODE_WAKEUP_PIN_LEN \ + DT_PROP_LEN(SYSTEM_DT_NODE_HIBERNATE_CONFIG, wakeup_pins) + +/** + * @brief Get a node identifier from a phandle in property 'wakeup-pins' at + * index i. + * + * @param i index of 'wakeup-pins' prop which type is 'phandles' + * @return node identifier with that path. + */ +#define SYSTEM_DT_NODE_WAKEUP_PIN_BY_IDX(i) \ + DT_PHANDLE_BY_IDX(SYSTEM_DT_NODE_HIBERNATE_CONFIG, wakeup_pins, i) + +/** + * @brief Get the enum using in chromium system by index i in 'wakeup-pins' + * list. + * + * @param i index of 'wakeup-pins' prop which type is 'phandles' + * @return GPIO enumeration + */ +#define SYSTEM_DT_WAKEUP_GPIO_ENUM_BY_IDX(i, _) \ + COND_CODE_1(DT_NODE_HAS_PROP(SYSTEM_DT_NODE_WAKEUP_PIN_BY_IDX(i), \ + enum_name), \ + (GPIO_SIGNAL(SYSTEM_DT_NODE_WAKEUP_PIN_BY_IDX(i)), ), ()) + /* Driver config */ struct cros_system_npcx_config { /* hardware module base address */ @@ -45,14 +92,245 @@ enum npcx_chip_id { DEVICE_ID_NPCX797W_C = 0x2C, }; +/* RAM block size in npcx family (Unit: bytes) */ +#define NPCX_RAM_BLOCK_SIZE (32 * 1024) +/* RAM block number in npcx7 series */ +#define NPCX7_RAM_BLOCK_NUM 12 +/* RAM block mask for power down in npcx7 series */ +#define NPCX7_RAM_BLOCK_PD_MASK (BIT(12) - 1) +/* Get saved reset flag address in battery-backed ram */ +#define BBRAM_SAVED_RESET_FLAG_ADDR \ + (DT_REG_ADDR(DT_INST(0, nuvoton_npcx_cros_bbram)) + \ + DT_PROP(DT_PATH(named_bbram_regions, saved_reset_flags), offset)) + +/* Soc specific system local functions */ +static int system_npcx_watchdog_stop(void) +{ + if (IS_ENABLED(CONFIG_WATCHDOG)) { + const struct device *wdt_dev = device_get_binding( + DT_LABEL(DT_INST(0, nuvoton_npcx_watchdog))); + + if (!wdt_dev) { + LOG_ERR("wdt_dev get binding failed"); + return -ENODEV; + } + + wdt_disable(wdt_dev); + } + + return 0; +} + +static void system_npcx_set_flash_pins_tri_state(const struct device *dev) +{ + struct scfg_reg *const inst_scfg = HAL_SCFG_INST(dev); + + inst_scfg->DEVCNT |= BIT(NPCX_DEVCNT_F_SPI_TRIS); +} + +static void system_npcx_init_watchdog_reset(const struct device *dev) +{ + struct twd_reg *const inst_twd = HAL_TWD_INST(dev); + + /* Enable early touch */ + inst_twd->T0CSR &= ~BIT(NPCX_T0CSR_TESDIS); + /* watchdog touched by writing 5Ch to WDSDM */ + inst_twd->TWCFG |= BIT(NPCX_TWCFG_WDSDME); +} + +static void system_npcx_turn_off_adc(void) +{ + struct adc_reg *const inst_adc = + (struct adc_reg *)(DT_REG_ADDR(DT_INST(0, nuvoton_npcx_adc))); + + inst_adc->ADCCNF = 0; + /* Wait for 1000 us to make sure conversion is completed. */ + k_busy_wait(1000); +} + +static void system_npcx_turn_off_kernel_timer(void) +{ + static struct itim32_reg *const evt_tmr = + (struct itim32_reg *)DT_REG_ADDR_BY_NAME( + DT_INST(0, nuvoton_npcx_itim_timer), evt_itim); + + evt_tmr->ITCTS32 &= ~BIT(NPCX_ITCTSXX_ITEN); +} + +static void system_npcx_disable_instant_wakeup(void) +{ + struct pmc_reg *const inst_pmc = (struct pmc_reg *)(DT_REG_ADDR_BY_NAME( + DT_INST(0, nuvoton_npcx_pcc), pmc)); + + inst_pmc->ENIDL_CTL &= ~BIT(NPCX_ENIDL_CTL_LP_WK_CTL); +} + +static void system_npcx_set_wakeup_gpios_before_hibernate(void) +{ + const uintptr_t miwu_base[] = { + DT_REG_ADDR(DT_INST(0, nuvoton_npcx_miwu)), + DT_REG_ADDR(DT_INST(1, nuvoton_npcx_miwu)), + DT_REG_ADDR(DT_INST(2, nuvoton_npcx_miwu)), + }; + + /* Disable all MIWU inputs before entering hibernate */ + for (int table = 0; table < ARRAY_SIZE(miwu_base); table++) { + for (int group = 0; group < NPCX_MIWU_GROUP_COUNT; group++) { + /* Disable all wake-ups */ + NPCX_WKEN(miwu_base[table], group) = 0x00; + /* Clear all pending bits of wake-ups */ + NPCX_WKPCL(miwu_base[table], group) = 0xFF; + /* + * Disable all inputs of wake-ups to prevent leakage + * caused by input floating. + */ + NPCX_WKINEN(miwu_base[table], group) = 0x00; + } + } + + static const int wakeup_pin_list[] = { +#if DT_NODE_EXISTS(SYSTEM_DT_NODE_HIBERNATE_CONFIG) + UTIL_LISTIFY(SYSTEM_DT_NODE_WAKEUP_PIN_LEN, + SYSTEM_DT_WAKEUP_GPIO_ENUM_BY_IDX, _) +#endif + }; + + /* Reconfigure wake-up GPIOs */ + for (int i = 0; i < ARRAY_SIZE(wakeup_pin_list); i++) { + gpio_reset(wakeup_pin_list[i]); + /* Re-enable interrupt for wake-up inputs */ + gpio_enable_interrupt(wakeup_pin_list[i]); + } +} + /* - * For cortex-m we cannot use irq_lock() for disabling all the interrupts - * because it leaves some (NMI and faults) still enabled. Use "cpsid i" to - * replace it. + * Hibernate function locates in the last 32K ram block in npcx7 series. + * Do not use global variables or call functions since we have turned off + * the other ram blocks. */ -static inline void interrupt_disable_all(void) +noreturn void __keep __attribute__((section(".lfw.hiber"))) +system_npcx_hibernate_by_lfw_in_last_ram(const struct device *dev, + uint32_t pd_ram_mask) +{ + /* Modules used for hibernating */ + struct twd_reg *const inst_twd = HAL_TWD_INST(dev); + struct mtc_reg *const inst_mtc = (struct mtc_reg *)(DT_REG_ADDR( + DT_INST(0, nuvoton_npcx_cros_mtc))); + struct pmc_reg *const inst_pmc = (struct pmc_reg *)(DT_REG_ADDR_BY_NAME( + DT_INST(0, nuvoton_npcx_pcc), pmc)); + uint32_t reset_flags; + volatile uint8_t *saved_reset_flags = + (volatile uint8_t *)BBRAM_SAVED_RESET_FLAG_ADDR; + + /* Turn off all blocks except last one for better power consumption */ + inst_pmc->RAM_PD[0] = (uint8_t)pd_ram_mask; + inst_pmc->RAM_PD[1] = (uint8_t)(pd_ram_mask >> 8); + + /* Set deep idle mode */ + inst_pmc->PMCSR = BIT(NPCX_PMCSR_IDLE) | BIT(NPCX_PMCSR_DHF); + + /* Enter system sleep mode */ + __asm__ volatile("wfi"); + + /* + * Mark wake-up reason for hibernate. Do not call bbram utilities + * directly since the other ram blocks are power down. + */ + if (IS_BIT_SET(inst_mtc->WTC, NPCX_WTC_PTO)) { + /* Save wake-up reason as RTC alarm. */ + reset_flags = EC_RESET_FLAG_RTC_ALARM; + } else { + /* Otherwise, we treat it as GPIOs wake-up */ + reset_flags = EC_RESET_FLAG_WAKE_PIN; + } + + saved_reset_flags[0] |= reset_flags; + saved_reset_flags[1] |= reset_flags >> 8; + saved_reset_flags[2] |= reset_flags >> 16; + saved_reset_flags[3] |= reset_flags >> 24; + + /* + * The trigger of a watchdog event by a "too early service" condition. + * When the watchdog is written more than once during three watchdog + * clock cycle. + */ + inst_twd->WDSDM = 0x5C; + inst_twd->WDSDM = 0x5C; + + /* Spin and wait for reboot; should never return */ + while (1) + continue; +} + +static inline int system_npcx_get_ram_blk_by_lfw_addr(char *address) { - __asm__("cpsid i"); + return NPCX7_RAM_BLOCK_NUM - + ceiling_fraction((uint32_t)address - + CONFIG_CROS_EC_PROGRAM_MEMORY_BASE, + NPCX_RAM_BLOCK_SIZE); +} + +static void system_npcx_hibernate_by_disable_ram(const struct device *dev, + uint32_t seconds, + uint32_t microseconds) +{ + /* Get 32kb ram block order of lfw function */ + extern char __lfw_text_start[], __lfw_text_end[]; + int lfw_block = system_npcx_get_ram_blk_by_lfw_addr(__lfw_text_start); + uint32_t pd_ram_mask = ~BIT(lfw_block) & NPCX7_RAM_BLOCK_PD_MASK; + + if (lfw_block != system_npcx_get_ram_blk_by_lfw_addr(__lfw_text_end)) { + LOG_ERR("LFW cannot cross ram blocks!"); + return; + } + + /* + * Set status of pins which connect to flash to tri-state in case + * the leakage current. + */ + system_npcx_set_flash_pins_tri_state(dev); + + /* Initialize watchdog for reset after wake-up from hibernating */ + system_npcx_init_watchdog_reset(dev); + + /* Disable ADC and wait for 1000 us to make sure conversion is done */ + if (IS_ENABLED(CONFIG_ADC)) + system_npcx_turn_off_adc(); + + /* Disable kernel timer */ + system_npcx_turn_off_kernel_timer(); + + /* Disable instant wake up mode for better power consumption */ + system_npcx_disable_instant_wakeup(); + + /* + * Set wake-up input GPIOs and turn off the other sources for better + * power consumption before entering hibernate mode. + */ + system_npcx_set_wakeup_gpios_before_hibernate(); + + /* + * Give the board a chance to do any late stage hibernation work. This + * is likely going to configure GPIOs for hibernation. On some boards, + * it's possible that this may not return at all. On those boards, + * power to the EC is likely being turn off entirely. + */ + if (board_hibernate_late) { + board_hibernate_late(); + } + + /* Setup a RTC alarm if needed */ + if (IS_ENABLED(CONFIG_RTC) && (seconds || microseconds)) { + system_set_rtc_alarm(seconds, microseconds); + } + + /* Clear all pending IRQs in case wake-up immediately after sleeping */ + for (int i = 0; i < CONFIG_NUM_IRQS; i++) { + NVIC_ClearPendingIRQ(i); + } + + /* Execute hibernate by lfw which locates in last 32K block ram */ + system_npcx_hibernate_by_lfw_in_last_ram(dev, pd_ram_mask); } static const char *cros_system_npcx_get_chip_vendor(const struct device *dev) @@ -122,6 +400,37 @@ static const char *cros_system_npcx_get_chip_revision(const struct device *dev) return rev; } +static void system_npcx_hibernate_by_psl(const struct device *dev, + uint32_t seconds, + uint32_t microseconds) +{ + ARG_UNUSED(dev); + /* + * TODO(b/178230662): RTC wake-up in PSL mode only support in npcx9 + * series. Nuvoton will introduce CLs for it later. + */ + ARG_UNUSED(seconds); + ARG_UNUSED(microseconds); + + /* + * Configure PSL input pads from "psl-in-pads" property in device tree + * file. + */ + npcx_pinctrl_psl_input_configure(); + + /* + * Give the board a chance to do any late stage hibernation work. This + * is likely going to configure GPIOs for hibernation. On some boards, + * it's possible that this may not return at all. On those boards, + * power to the EC is likely being turn off entirely. + */ + if (board_hibernate_late) + board_hibernate_late(); + + /* Turn off VCC1 to enter ultra-low-power mode for hibernating */ + npcx_pinctrl_psl_output_set_inactive(); +} + static int cros_system_npcx_get_reset_cause(const struct device *dev) { struct cros_system_npcx_data *data = DRV_DATA(dev); @@ -164,21 +473,10 @@ static int cros_system_npcx_soc_reset(const struct device *dev) */ /* Stop the watchdog */ - if (IS_ENABLED(CONFIG_WATCHDOG)) { - const struct device *wdt_dev = device_get_binding( - DT_LABEL(DT_INST(0, nuvoton_npcx_watchdog))); - - if (!wdt_dev) { - LOG_ERR("wdt_dev get binding failed"); - return -ENODEV; - } + system_npcx_watchdog_stop(); - wdt_disable(wdt_dev); - } - - /* Enable early touch */ - inst_twd->T0CSR &= ~BIT(NPCX_T0CSR_TESDIS); - inst_twd->TWCFG |= BIT(NPCX_TWCFG_WDSDME); + /* Initialize watchdog for reset */ + system_npcx_init_watchdog_reset(dev); /* * The trigger of a watchdog event by a "too early service" condition. @@ -200,32 +498,20 @@ static int cros_system_npcx_soc_reset(const struct device *dev) static int cros_system_npcx_hibernate(const struct device *dev, uint32_t seconds, uint32_t microseconds) { - ARG_UNUSED(seconds); - ARG_UNUSED(microseconds); - /* Disable interrupt first */ interrupt_disable_all(); - /* - * TODO(b:178230662): RTC wake-up in PSL mode only support in npcx9 - * series. Nuvoton will introduce CLs for it later. - */ - - if (IS_ENABLED(CONFIG_PLATFORM_EC_HIBERNATE_PSL)) { - /* - * Configure PSL input pads from "psl-in-pads" property in - * device tree file. - */ - npcx_pinctrl_psl_input_configure(); + /* Stop the watchdog */ + system_npcx_watchdog_stop(); - /* Turn off VCC1 and enter ultra-low-power mode */ - npcx_pinctrl_psl_output_set_inactive(); + /* Enter hibernate mode */ + if (IS_ENABLED(CONFIG_PLATFORM_EC_SYSTEM_HIBERNATE_PSL)) { + system_npcx_hibernate_by_psl(dev, seconds, microseconds); + } else { + system_npcx_hibernate_by_disable_ram(dev, seconds, + microseconds); } - /* - * TODO(b:183745774): implement Non-PSL hibernate mechanism if - * CONFIG_PLATFORM_EC_HIBERNATE_PSL is not enabled. - */ return 0; } diff --git a/zephyr/dts/bindings/gpio/gpio-enum-name.yaml b/zephyr/dts/bindings/gpio/gpio-enum-name.yaml index d30984184d..b63020d4bc 100644 --- a/zephyr/dts/bindings/gpio/gpio-enum-name.yaml +++ b/zephyr/dts/bindings/gpio/gpio-enum-name.yaml @@ -40,6 +40,7 @@ properties: - GPIO_EC_PCH_SYS_PWROK - GPIO_EC_PCH_WAKE_ODL - GPIO_EC_PROCHOT_IN_L + - GPIO_EC_RST_ODL - GPIO_EC_WP_L - GPIO_EN_A_RAILS - GPIO_EN_PP3300_A diff --git a/zephyr/dts/bindings/gpio/hibernate-wake-pins.yaml b/zephyr/dts/bindings/gpio/hibernate-wake-pins.yaml new file mode 100644 index 0000000000..64435f7b3b --- /dev/null +++ b/zephyr/dts/bindings/gpio/hibernate-wake-pins.yaml @@ -0,0 +1,13 @@ +# Copyright 2021 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. + +description: Hibernate Wake-up Pins Configurations + +compatible: "cros-ec,hibernate-wake-pins" + +properties: + wakeup-pins: + type: phandles + required: true + description: GPIO list for hibernate wake-up diff --git a/zephyr/linker/CMakeLists.txt b/zephyr/linker/CMakeLists.txt index 170055dba7..27b028d22c 100644 --- a/zephyr/linker/CMakeLists.txt +++ b/zephyr/linker/CMakeLists.txt @@ -10,3 +10,7 @@ zephyr_linker_sources(RWDATA SORT_KEY 1 iram_text.ld) # Compute the image size zephyr_linker_sources(RAM_SECTIONS image_size.ld) + +# Little FW with specific purposes used by NPCX EC +zephyr_linker_sources_ifdef(CONFIG_SOC_FAMILY_NPCX ROM_START SORT_KEY 1 + npcx-lfw.ld) diff --git a/zephyr/linker/npcx-lfw.ld b/zephyr/linker/npcx-lfw.ld new file mode 100644 index 0000000000..bdab9c3546 --- /dev/null +++ b/zephyr/linker/npcx-lfw.ld @@ -0,0 +1,10 @@ +/* Copyright 2021 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. + */ + +. = ALIGN(4); +__lfw_text_start = .; +*(.lfw.*) +. = ALIGN(4); +__lfw_text_end = .; diff --git a/zephyr/projects/trogdor/lazor/gpio.dts b/zephyr/projects/trogdor/lazor/gpio.dts index c5f95a1fd1..5e91c23d57 100644 --- a/zephyr/projects/trogdor/lazor/gpio.dts +++ b/zephyr/projects/trogdor/lazor/gpio.dts @@ -131,6 +131,7 @@ }; ec_rst_odl { gpios = <&gpio0 2 GPIO_INPUT>; + enum-name = "GPIO_EC_RST_ODL"; label = "EC_RST_ODL"; }; ec_entering_rw { diff --git a/zephyr/shim/src/system.c b/zephyr/shim/src/system.c index b1a443fbf4..c8b5c81341 100644 --- a/zephyr/shim/src/system.c +++ b/zephyr/shim/src/system.c @@ -176,8 +176,7 @@ static int check_reset_cause(void) /* * TODO(b/182876692): Implement CONFIG_POWER_BUTTON_INIT_IDLE & - * CONFIG_BOARD_FORCE_RESET_PIN. Also, check the hibernate flow if PSL - * merge into tot. + * CONFIG_BOARD_FORCE_RESET_PIN. */ switch (chip_reset_cause) { @@ -234,10 +233,11 @@ static int check_reset_cause(void) case WATCHDOG_RST: /* * Don't set EC_RESET_FLAG_WATCHDOG flag if watchdog is issued - * by system_reset in order to distinguish reset cause is panic - * reason or not. + * by system_reset or hibernate in order to distinguish reset + * cause is panic reason or not. */ - if (!(system_flags & (EC_RESET_FLAG_SOFT | EC_RESET_FLAG_HARD))) + if (!(system_flags & (EC_RESET_FLAG_SOFT | EC_RESET_FLAG_HARD | + EC_RESET_FLAG_HIBERNATE))) system_flags |= EC_RESET_FLAG_WATCHDOG; break; } |