| /* |
| * 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 <watchdog.h> |
| #include <device.h> |
| |
| struct wdt_esp32_data { |
| struct wdt_config config; |
| }; |
| |
| 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 = 0; |
| } |
| |
| 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(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(); |
| } |
| |
| static void 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(); |
| } |
| |
| static void adjust_timeout(u32_t timeout) |
| { |
| volatile u32_t *reg; |
| enum wdt_clock_timeout_cycles cycles = |
| (enum wdt_clock_timeout_cycles)timeout; |
| u32_t ticks; |
| |
| /* The watchdog API in Zephyr was modeled after the QMSI drivers, |
| * and those were modeled after the Quark MCUs. The possible |
| * values of enum wdt_clock_timeout_cycles maps 1:1 to what the |
| * Quark D2000 expects. At 32MHz, the timeout value in ms is given |
| * by the following formula, according to the D2000 datasheet: |
| * |
| * 2^(cycles + 11) |
| * timeout_ms = --------------- |
| * 1000 |
| * |
| * (e.g. 2.048ms for 2^16 cycles, or the WDT_2_16_CYCLES value.) |
| * |
| * While this is sort of backwards (this should be given units of |
| * time and converted to what the hardware expects), try to map this |
| * value to what the ESP32 expects. Use the same timeout value for |
| * stages 0 and 1, regardless of the configuration mode, in order to |
| * simplify things. |
| */ |
| |
| /* MWDT ticks every 12.5ns. Set the prescaler to 40000, so the |
| * counter for each watchdog stage is decremented every 0.5ms. |
| */ |
| reg = (u32_t *)TIMG_WDTCONFIG1_REG(1); |
| *reg = 40000; |
| |
| ticks = 1<<(cycles + 2); |
| |
| /* Correct the value: this is an integer-only approximation of |
| * 0.114074 * exp(0.67822 * cycles) |
| * Which calculates the difference in ticks from the D2000 values to |
| * the value calculated by the previous expression. |
| */ |
| ticks += (1<<cycles) / 10; |
| |
| 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 void wdt_esp32_reload(struct device *dev) |
| { |
| 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(); |
| } |
| |
| 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, struct wdt_config *config) |
| { |
| struct wdt_esp32_data *data = dev->driver_data; |
| volatile u32_t *reg = (u32_t *)TIMG_WDTCONFIG0_REG(1); |
| u32_t v; |
| |
| if (!config) { |
| return -EINVAL; |
| } |
| |
| v = *reg; |
| |
| /* 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 (config->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 (config->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(); |
| *reg = v; |
| adjust_timeout(config->timeout & WDT_TIMEOUT_MASK); |
| set_interrupt_enabled(config->mode == WDT_MODE_INTERRUPT_RESET); |
| wdt_esp32_seal(); |
| |
| wdt_esp32_reload(dev); |
| |
| memcpy(&data->config, config, sizeof(*config)); |
| return 0; |
| } |
| |
| static void wdt_esp32_get_config(struct device *dev, struct wdt_config *config) |
| { |
| struct wdt_esp32_data *data = dev->driver_data; |
| |
| memcpy(config, &data->config, sizeof(*config)); |
| } |
| |
| static int wdt_esp32_init(struct device *dev) |
| { |
| struct wdt_esp32_data *data = dev->driver_data; |
| |
| memset(&data->config, 0, sizeof(data->config)); |
| |
| #ifdef CONFIG_ESP32_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); |
| |
| return 0; |
| } |
| |
| static const struct wdt_driver_api wdt_api = { |
| .enable = wdt_esp32_enable, |
| .disable = wdt_esp32_disable, |
| .get_config = wdt_esp32_get_config, |
| .set_config = wdt_esp32_set_config, |
| .reload = wdt_esp32_reload |
| }; |
| |
| DEVICE_AND_API_INIT(wdt_esp32, CONFIG_WDT_ESP32_DEVICE_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 (data->config.interrupt_fn) { |
| data->config.interrupt_fn(DEVICE_GET(wdt_esp32)); |
| } |
| |
| *reg |= TIMG_WDT_INT_CLR; |
| } |