| /* |
| * Copyright (C) 2017 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| /* Include esp-idf headers first to avoid redefining BIT() macro */ |
| #include <soc/rtc_cntl_reg.h> |
| #include <soc/timer_group_reg.h> |
| |
| #include <soc.h> |
| #include <string.h> |
| #include <drivers/watchdog.h> |
| #include <device.h> |
| |
| /* FIXME: This struct shall be removed from here, when esp32 timer driver got |
| * implemented. |
| * That's why the type name starts with `timer` not `wdt` |
| */ |
| struct timer_esp32_irq_regs_t { |
| u32_t *timer_int_ena; |
| u32_t *timer_int_clr; |
| }; |
| |
| struct wdt_esp32_regs_t { |
| u32_t config0; |
| u32_t config1; |
| u32_t config2; |
| u32_t config3; |
| u32_t config4; |
| u32_t config5; |
| u32_t feed; |
| u32_t wprotect; |
| }; |
| |
| 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 wdt_esp32_config { |
| void (*connect_irq)(void); |
| const struct wdt_esp32_regs_t *base; |
| const struct timer_esp32_irq_regs_t irq_regs; |
| |
| const struct { |
| int source; |
| int line; |
| } irq; |
| }; |
| |
| #define DEV_CFG(dev) \ |
| ((const struct wdt_esp32_config *const)(dev)->config->config_info) |
| #define DEV_DATA(dev) \ |
| ((struct wdt_esp32_data *)(dev)->driver_data) |
| #define DEV_BASE(dev) \ |
| ((volatile struct wdt_esp32_regs_t *)(DEV_CFG(dev))->base) |
| |
| /* 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(struct device *dev) |
| { |
| DEV_BASE(dev)->wprotect = 0U; |
| |
| } |
| |
| static inline void wdt_esp32_unseal(struct device *dev) |
| { |
| DEV_BASE(dev)->wprotect = TIMG_WDT_WKEY_VALUE; |
| } |
| |
| static void wdt_esp32_enable(struct device *dev) |
| { |
| wdt_esp32_unseal(dev); |
| DEV_BASE(dev)->config0 |= BIT(TIMG_WDT_EN_S); |
| wdt_esp32_seal(dev); |
| |
| } |
| |
| static int wdt_esp32_disable(struct device *dev) |
| { |
| wdt_esp32_unseal(dev); |
| DEV_BASE(dev)->config0 &= ~BIT(TIMG_WDT_EN_S); |
| wdt_esp32_seal(dev); |
| |
| return 0; |
| } |
| |
| static void adjust_timeout(struct device *dev, u32_t timeout) |
| { |
| /* MWDT ticks every 12.5ns. Set the prescaler to 40000, so the |
| * counter for each watchdog stage is decremented every 0.5ms. |
| */ |
| DEV_BASE(dev)->config1 = 40000U; |
| DEV_BASE(dev)->config2 = timeout; |
| DEV_BASE(dev)->config3 = timeout; |
| } |
| |
| static void wdt_esp32_isr(struct device *dev); |
| |
| static int wdt_esp32_feed(struct device *dev, int channel_id) |
| { |
| wdt_esp32_unseal(dev); |
| DEV_BASE(dev)->feed = 0xABAD1DEA; /* Writing any value to WDTFEED will reload it. */ |
| wdt_esp32_seal(dev); |
| |
| return 0; |
| } |
| |
| static void set_interrupt_enabled(struct device *dev, bool setting) |
| { |
| *DEV_CFG(dev)->irq_regs.timer_int_clr |= TIMG_WDT_INT_CLR; |
| |
| if (setting) { |
| *DEV_CFG(dev)->irq_regs.timer_int_ena |= TIMG_WDT_INT_ENA; |
| irq_enable(DEV_CFG(dev)->irq.line); |
| } else { |
| *DEV_CFG(dev)->irq_regs.timer_int_ena &= ~TIMG_WDT_INT_ENA; |
| irq_disable(DEV_CFG(dev)->irq.line); |
| } |
| } |
| |
| static int wdt_esp32_set_config(struct device *dev, u8_t options) |
| { |
| struct wdt_esp32_data *data = DEV_DATA(dev); |
| u32_t v = DEV_BASE(dev)->config0; |
| |
| if (!data) { |
| return -EINVAL; |
| } |
| |
| /* Stages 3 and 4 are not used: disable them. */ |
| v |= TIMG_WDT_STG_SEL_OFF << TIMG_WDT_STG2_S; |
| v |= TIMG_WDT_STG_SEL_OFF << TIMG_WDT_STG3_S; |
| |
| /* Wait for 3.2us before booting again. */ |
| v |= 7 << TIMG_WDT_SYS_RESET_LENGTH_S; |
| v |= 7 << TIMG_WDT_CPU_RESET_LENGTH_S; |
| |
| if (data->mode == WDT_MODE_RESET) { |
| /* Warm reset on timeout */ |
| v |= TIMG_WDT_STG_SEL_RESET_SYSTEM << TIMG_WDT_STG0_S; |
| v |= TIMG_WDT_STG_SEL_OFF << TIMG_WDT_STG1_S; |
| |
| /* Disable interrupts for this mode. */ |
| v &= ~(TIMG_WDT_LEVEL_INT_EN | TIMG_WDT_EDGE_INT_EN); |
| } else if (data->mode == WDT_MODE_INTERRUPT_RESET) { |
| /* Interrupt first, and warm reset if not reloaded */ |
| v |= TIMG_WDT_STG_SEL_INT << TIMG_WDT_STG0_S; |
| v |= TIMG_WDT_STG_SEL_RESET_SYSTEM << TIMG_WDT_STG1_S; |
| |
| /* Use level-triggered interrupts. */ |
| v |= TIMG_WDT_LEVEL_INT_EN; |
| v &= ~TIMG_WDT_EDGE_INT_EN; |
| } else { |
| return -EINVAL; |
| } |
| |
| wdt_esp32_unseal(dev); |
| DEV_BASE(dev)->config0 = v; |
| adjust_timeout(dev, data->timeout); |
| set_interrupt_enabled(dev, data->mode == WDT_MODE_INTERRUPT_RESET); |
| wdt_esp32_seal(dev); |
| |
| wdt_esp32_feed(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_DATA(dev); |
| |
| if (cfg->flags != WDT_FLAG_RESET_SOC) { |
| return -ENOTSUP; |
| } |
| |
| if (cfg->window.min != 0U || cfg->window.max == 0U) { |
| return -EINVAL; |
| } |
| |
| 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(DEV_CFG(dev)->irq.line); |
| DEV_CFG(dev)->connect_irq(); |
| |
| wdt_esp32_enable(dev); |
| |
| 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_feed |
| }; |
| |
| #define ESP32_WDT_INIT(idx) \ |
| DEVICE_DECLARE(wdt_esp32_##idx); \ |
| static void wdt_esp32_connect_irq_func##idx(void) \ |
| { \ |
| esp32_rom_intr_matrix_set(0, ETS_TG##idx##_WDT_LEVEL_INTR_SOURCE, \ |
| CONFIG_WDT##idx##_ESP32_IRQ); \ |
| IRQ_CONNECT(CONFIG_WDT##idx##_ESP32_IRQ, \ |
| 4, \ |
| wdt_esp32_isr, \ |
| DEVICE_GET(wdt_esp32_##idx), \ |
| 0); \ |
| } \ |
| \ |
| static struct wdt_esp32_data wdt##idx##_data; \ |
| static struct wdt_esp32_config wdt_esp32_config##idx = { \ |
| .base = (struct wdt_esp32_regs_t *) DT_INST_##idx##_ESPRESSIF_ESP32_WATCHDOG_BASE_ADDRESS, \ |
| .irq_regs = { \ |
| .timer_int_ena = (u32_t *)TIMG_INT_ENA_TIMERS_REG(idx), \ |
| .timer_int_clr = (u32_t *)TIMG_INT_CLR_TIMERS_REG(idx), \ |
| }, \ |
| .irq = { \ |
| .source = ETS_TG##idx##_WDT_LEVEL_INTR_SOURCE, \ |
| .line = CONFIG_WDT##idx##_ESP32_IRQ, \ |
| }, \ |
| .connect_irq = wdt_esp32_connect_irq_func##idx \ |
| }; \ |
| \ |
| DEVICE_AND_API_INIT(wdt_esp32_##idx, DT_INST_##idx##_ESPRESSIF_ESP32_WATCHDOG_LABEL, \ |
| wdt_esp32_init, \ |
| &wdt##idx##_data, \ |
| &wdt_esp32_config##idx, \ |
| PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \ |
| &wdt_api) |
| |
| static void wdt_esp32_isr(struct device *dev) |
| { |
| struct wdt_esp32_data *data = DEV_DATA(dev); |
| |
| if (data->callback) { |
| data->callback(dev, 0); |
| } |
| |
| *DEV_CFG(dev)->irq_regs.timer_int_clr |= TIMG_WDT_INT_CLR; |
| } |
| |
| |
| #ifdef DT_INST_0_ESPRESSIF_ESP32_WATCHDOG |
| ESP32_WDT_INIT(0); |
| #endif |
| |
| #ifdef DT_INST_1_ESPRESSIF_ESP32_WATCHDOG |
| ESP32_WDT_INIT(1); |
| #endif |