/* * Copyright (C) 2017 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ /* Include esp-idf headers first to avoid redefining BIT() macro */ #include #include #include #include #include #include enum wdt_mode { WDT_MODE_RESET = 0, WDT_MODE_INTERRUPT_RESET }; struct wdt_esp32_data { u32_t timeout; enum wdt_mode mode; wdt_callback_t callback; struct device *dev; }; static struct wdt_esp32_data shared_data; /* ESP32 ignores writes to any register if WDTWPROTECT doesn't contain the * magic value of TIMG_WDT_WKEY_VALUE. The datasheet recommends unsealing, * making modifications, and sealing for every watchdog modification. */ static inline void wdt_esp32_seal(void) { volatile u32_t *reg = (u32_t *)TIMG_WDTWPROTECT_REG(1); *reg = 0U; } static inline void wdt_esp32_unseal(void) { volatile u32_t *reg = (u32_t *)TIMG_WDTWPROTECT_REG(1); *reg = TIMG_WDT_WKEY_VALUE; } static void wdt_esp32_enable(void) { volatile u32_t *reg = (u32_t *)TIMG_WDTCONFIG0_REG(1); wdt_esp32_unseal(); *reg |= BIT(TIMG_WDT_EN_S); wdt_esp32_seal(); } static int wdt_esp32_disable(struct device *dev) { volatile u32_t *reg = (u32_t *)TIMG_WDTCONFIG0_REG(1); ARG_UNUSED(dev); wdt_esp32_unseal(); *reg &= ~BIT(TIMG_WDT_EN_S); wdt_esp32_seal(); return 0; } static void adjust_timeout(u32_t timeout) { volatile u32_t *reg; u32_t ticks = timeout; reg = (u32_t *)TIMG_WDTCONFIG1_REG(1); /* MWDT ticks every 12.5ns. Set the prescaler to 40000, so the * counter for each watchdog stage is decremented every 0.5ms. */ *reg = 40000U; reg = (u32_t *)TIMG_WDTCONFIG2_REG(1); *reg = ticks; reg = (u32_t *)TIMG_WDTCONFIG3_REG(1); *reg = ticks; } static void wdt_esp32_isr(void *param); static int wdt_esp32_reload(struct device *dev, int channel_id) { volatile u32_t *reg = (u32_t *)TIMG_WDTFEED_REG(1); ARG_UNUSED(dev); wdt_esp32_unseal(); *reg = 0xABAD1DEA; /* Writing any value to WDTFEED will reload it. */ wdt_esp32_seal(); return 0; } static void set_interrupt_enabled(bool setting) { volatile u32_t *intr_enable_reg = (u32_t *)TIMG_INT_ENA_TIMERS_REG(1); volatile u32_t *intr_clear_timers = (u32_t *)TIMG_INT_CLR_TIMERS_REG(1); *intr_clear_timers |= TIMG_WDT_INT_CLR; if (setting) { *intr_enable_reg |= TIMG_WDT_INT_ENA; IRQ_CONNECT(CONFIG_WDT_ESP32_IRQ, 4, wdt_esp32_isr, &shared_data, 0); irq_enable(CONFIG_WDT_ESP32_IRQ); } else { *intr_enable_reg &= ~TIMG_WDT_INT_ENA; irq_disable(CONFIG_WDT_ESP32_IRQ); } } static int wdt_esp32_set_config(struct device *dev, u8_t options) { struct wdt_esp32_data *data = dev->driver_data; volatile u32_t *reg = (u32_t *)TIMG_WDTCONFIG0_REG(1); u32_t v; if (!data) { return -EINVAL; } v = *reg; /* Stages 3 and 4 are not used: disable them. */ v |= TIMG_WDT_STG_SEL_OFF<mode == WDT_MODE_RESET) { /* Warm reset on timeout */ v |= TIMG_WDT_STG_SEL_RESET_SYSTEM<mode == WDT_MODE_INTERRUPT_RESET) { /* Interrupt first, and warm reset if not reloaded */ v |= TIMG_WDT_STG_SEL_INT<timeout); set_interrupt_enabled(data->mode == WDT_MODE_INTERRUPT_RESET); wdt_esp32_seal(); wdt_esp32_reload(dev, 0); return 0; } static int wdt_esp32_install_timeout(struct device *dev, const struct wdt_timeout_cfg *cfg) { struct wdt_esp32_data *data = dev->driver_data; ARG_UNUSED(dev); if (cfg->flags != WDT_FLAG_RESET_SOC) { return -ENOTSUP; } if (cfg->window.min != 0U || cfg->window.max == 0U) { return -EINVAL; } data->dev = dev; data->timeout = cfg->window.max; data->mode = (cfg->callback == NULL) ? WDT_MODE_RESET : WDT_MODE_INTERRUPT_RESET; data->callback = cfg->callback; return 0; } static int wdt_esp32_init(struct device *dev) { #ifdef CONFIG_WDT_DISABLE_AT_BOOT wdt_esp32_disable(dev); #endif /* This is a level 4 interrupt, which is handled by _Level4Vector, * located in xtensa_vectors.S. */ irq_disable(CONFIG_WDT_ESP32_IRQ); esp32_rom_intr_matrix_set(0, ETS_TG1_WDT_LEVEL_INTR_SOURCE, CONFIG_WDT_ESP32_IRQ); wdt_esp32_enable(); return 0; } static const struct wdt_driver_api wdt_api = { .setup = wdt_esp32_set_config, .disable = wdt_esp32_disable, .install_timeout = wdt_esp32_install_timeout, .feed = wdt_esp32_reload }; DEVICE_AND_API_INIT(wdt_esp32, CONFIG_WDT_0_NAME, wdt_esp32_init, &shared_data, NULL, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &wdt_api); static void wdt_esp32_isr(void *param) { struct wdt_esp32_data *data = param; volatile u32_t *reg = (u32_t *)TIMG_INT_CLR_TIMERS_REG(1); if (shared_data.callback) { shared_data.callback(data->dev, 0); } *reg |= TIMG_WDT_INT_CLR; }