drivers: watchdog: Implement ESP32 watchdog driver
Zephyr's watchdog API is badly designed in the sense that it's a 1:1 abstraction on top of whatever Quark D2000 expects for its watchdog, instead of expecting a generic timeout value. This implementation tries as much as possible to calculate the watchdog timeout in a way that's compatible with a Quark D2000 running at 32MHz; a comment in adjust_timeout() explains this in more detail. Jira: ZEP-2296 Signed-off-by: Leandro Pereira <leandro.pereira@intel.com>
This commit is contained in:
parent
c0c79a8041
commit
d691045592
4 changed files with 277 additions and 0 deletions
|
@ -26,4 +26,6 @@ source "drivers/watchdog/Kconfig.cmsdk_apb"
|
|||
|
||||
source "drivers/watchdog/Kconfig.sam"
|
||||
|
||||
source "drivers/watchdog/Kconfig.esp32"
|
||||
|
||||
endif
|
||||
|
|
35
drivers/watchdog/Kconfig.esp32
Normal file
35
drivers/watchdog/Kconfig.esp32
Normal file
|
@ -0,0 +1,35 @@
|
|||
# Kconfig - ESP32 WDT configuration
|
||||
#
|
||||
# Copyright (C) 2017 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
menuconfig WDT_ESP32
|
||||
bool "ESP32 Watchdog (WDT) Driver"
|
||||
depends on SOC_ESP32
|
||||
default y
|
||||
help
|
||||
Enable WDT driver for ESP32.
|
||||
|
||||
config WDT_ESP32_DISABLE_AT_BOOT
|
||||
bool "Disable WDT during boot"
|
||||
depends on WDT_ESP32
|
||||
default y
|
||||
help
|
||||
Select this option to disable the WDT during boot.
|
||||
|
||||
config WDT_ESP32_DEVICE_NAME
|
||||
string "Device name for Watchdog (WDT)"
|
||||
depends on WDT_ESP32
|
||||
default "WATCHDOG_0"
|
||||
help
|
||||
Set the name used by WDT device during registration.
|
||||
|
||||
config WDT_ESP32_IRQ
|
||||
int "IRQ line for watchdog interrupt"
|
||||
depends on WDT_ESP32
|
||||
default 24
|
||||
help
|
||||
Set the IRQ line used by the WDT device. Very few lines can be
|
||||
chosen here, as it must be a level 4 interrupt.
|
|
@ -2,3 +2,4 @@ obj-$(CONFIG_WDT_QMSI) += wdt_qmsi.o
|
|||
obj-$(CONFIG_IWDG_STM32) += iwdg_stm32.o
|
||||
obj-$(CONFIG_WDOG_CMSDK_APB) += wdog_cmsdk_apb.o
|
||||
obj-$(CONFIG_WDT_SAM) += wdt_sam.o
|
||||
obj-$(CONFIG_WDT_ESP32) += wdt_esp32.o
|
||||
|
|
239
drivers/watchdog/wdt_esp32.c
Normal file
239
drivers/watchdog/wdt_esp32.c
Normal file
|
@ -0,0 +1,239 @@
|
|||
/*
|
||||
* Copyright (C) 2017 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <watchdog.h>
|
||||
#include <device.h>
|
||||
|
||||
#include <rom/ets_sys.h>
|
||||
#include <soc/rtc_cntl_reg.h>
|
||||
#include <soc/timer_group_reg.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(0);
|
||||
|
||||
*reg = 0;
|
||||
}
|
||||
|
||||
static inline void wdt_esp32_unseal(void)
|
||||
{
|
||||
volatile u32_t *reg = (u32_t *)TIMG_WDTWPROTECT_REG(0);
|
||||
|
||||
*reg = TIMG_WDT_WKEY_VALUE;
|
||||
}
|
||||
|
||||
static void wdt_esp32_enable(struct device *dev)
|
||||
{
|
||||
volatile u32_t *reg = (u32_t *)TIMG_WDTCONFIG0_REG(0);
|
||||
|
||||
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(0);
|
||||
|
||||
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(0);
|
||||
*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(0);
|
||||
*reg = ticks;
|
||||
|
||||
reg = (u32_t *)TIMG_WDTCONFIG3_REG(0);
|
||||
*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(0);
|
||||
|
||||
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(0);
|
||||
|
||||
wdt_esp32_unseal();
|
||||
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);
|
||||
}
|
||||
wdt_esp32_seal();
|
||||
}
|
||||
|
||||
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(0);
|
||||
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_CPU<<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;
|
||||
|
||||
set_interrupt_enabled(false);
|
||||
} 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_CPU<<TIMG_WDT_STG1_S;
|
||||
|
||||
/* Use level-triggered interrupts. */
|
||||
v |= TIMG_WDT_LEVEL_INT_EN;
|
||||
|
||||
set_interrupt_enabled(true);
|
||||
} else {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
wdt_esp32_unseal();
|
||||
*reg = v;
|
||||
adjust_timeout(config->timeout & WDT_TIMEOUT_MASK);
|
||||
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);
|
||||
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;
|
||||
|
||||
if (data->config.interrupt_fn) {
|
||||
data->config.interrupt_fn(DEVICE_GET(wdt_esp32));
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue