From e5460c6bccde7fac0e111accda96076f5fd6929f Mon Sep 17 00:00:00 2001 From: Henrik Brix Andersen Date: Sun, 18 Nov 2018 16:57:40 +0100 Subject: [PATCH] drivers: watchdog: wdt_sam0: Convert Atmel SAM0 driver to the new API Convert the Atmel SAM0 watchdog driver to the new watchdog API and enable DTS support. This fixes #10914. Signed-off-by: Henrik Brix Andersen --- drivers/watchdog/Kconfig.sam0 | 3 +- drivers/watchdog/wdt_sam0.c | 215 +++++++++++++++++--------- soc/arm/atmel_sam0/samd20/dts_fixup.h | 1 + soc/arm/atmel_sam0/samd21/dts_fixup.h | 1 + 4 files changed, 144 insertions(+), 76 deletions(-) diff --git a/drivers/watchdog/Kconfig.sam0 b/drivers/watchdog/Kconfig.sam0 index abaeaf30d08..eba112dc4c9 100644 --- a/drivers/watchdog/Kconfig.sam0 +++ b/drivers/watchdog/Kconfig.sam0 @@ -3,9 +3,10 @@ # Copyright (c) 2017 Google LLC. # SPDX-License-Identifier: Apache-2.0 -menuconfig WDT_SAM0 +config WDT_SAM0 bool "Atmel SAM0 series Watchdog (WDT) Driver" depends on SOC_FAMILY_SAM0 + select HAS_DTS_WDT default y help Enable WDT driver for Atmel SAM0 MCUs. diff --git a/drivers/watchdog/wdt_sam0.c b/drivers/watchdog/wdt_sam0.c index 3501cf62dc0..9e483b5fd76 100644 --- a/drivers/watchdog/wdt_sam0.c +++ b/drivers/watchdog/wdt_sam0.c @@ -1,4 +1,5 @@ /* + * Copyright (c) 2018 Henrik Brix Andersen * Copyright (c) 2017 Google LLC. * * SPDX-License-Identifier: Apache-2.0 @@ -7,138 +8,202 @@ #include #include +#define LOG_LEVEL CONFIG_WDT_LOG_LEVEL +#include +LOG_MODULE_REGISTER(wdt_sam0); + #define WDT_REGS ((Wdt *)DT_WDT_SAM0_BASE_ADDRESS) struct wdt_sam0_dev_data { - void (*cb)(struct device *dev); + wdt_callback_t cb; + bool timeout_valid; }; static struct device DEVICE_NAME_GET(wdt_sam0); +static struct wdt_sam0_dev_data wdt_sam0_data = { 0 }; + static void wdt_sam0_wait_synchronization(void) { while (WDT_REGS->STATUS.bit.SYNCBUSY) { } } +static u32_t wdt_sam0_timeout_to_wdt_period(u32_t timeout_ms) +{ + u32_t next_pow2; + u32_t cycles; + + /* Calculate number of clock cycles @ 1.024 kHz input clock */ + cycles = (timeout_ms * 1024) / 1000; + + /* Minimum wdt period is 8 clock cycles (register value 0) */ + if (cycles <= 8) + return 0; + + /* Round up to next pow2 and calculate the register value */ + next_pow2 = (1ULL << 32) >> __builtin_clz(cycles - 1); + return find_msb_set(next_pow2 >> 4); +} + static void wdt_sam0_isr(struct device *dev) { struct wdt_sam0_dev_data *data = dev->driver_data; WDT_REGS->INTFLAG.reg = WDT_INTFLAG_EW; if (data->cb != NULL) { - data->cb(dev); + data->cb(dev, 0); } } -static void wdt_sam0_enable(struct device *dev) +static int wdt_sam0_setup(struct device *dev, u8_t options) { - WDT_REGS->CTRL.reg = WDT_CTRL_ENABLE; + struct wdt_sam0_dev_data *data = dev->driver_data; + + if (WDT_REGS->CTRL.reg == WDT_CTRL_ENABLE) { + LOG_ERR("Watchdog already setup"); + return -EBUSY; + } + + if (!data->timeout_valid) { + LOG_ERR("No valid timeout installed"); + return -EINVAL; + } + + if (options & WDT_OPT_PAUSE_IN_SLEEP) { + LOG_ERR("Pause in sleep not supported"); + return -ENOTSUP; + } + + if (options & WDT_OPT_PAUSE_HALTED_BY_DBG) { + LOG_ERR("Pause when halted by debugger not supported"); + return -ENOTSUP; + } + + /* Enable watchdog */ + WDT_REGS->CTRL.bit.ENABLE = 1; wdt_sam0_wait_synchronization(); + + return 0; } static int wdt_sam0_disable(struct device *dev) { - WDT_REGS->CTRL.reg = 0; + if (!WDT_REGS->CTRL.bit.ENABLE) { + LOG_ERR("Watchdog not enabled"); + return -EFAULT; + } + + WDT_REGS->CTRL.bit.ENABLE = 0; wdt_sam0_wait_synchronization(); return 0; } -static int wdt_sam0_set_config(struct device *dev, struct wdt_config *config) +static int wdt_sam0_install_timeout(struct device *dev, + const struct wdt_timeout_cfg *cfg) { struct wdt_sam0_dev_data *data = dev->driver_data; - WDT_CTRL_Type ctrl = WDT_REGS->CTRL; - int divisor; + u32_t window, per; - /* As per wdt_esp32.c, the Zephyr watchdog API is modeled - * after the Quark MCU where: - * - * timeout_ms = 2**(config->timeout + 11) / 1000 - * - * The SAM0 is also power-of-two based with a 1 kHz clock, so - * 2**14 / 1kHz ~= 2**29 / 32 MHz. - */ - divisor = config->timeout + WDT_CONFIG_PER_16K_Val - WDT_2_29_CYCLES; - - /* Limit to 16x so that 8x is available for early warning. */ - if (divisor < WDT_CONFIG_PER_16_Val) { - return -EINVAL; - } else if (divisor > WDT_CONFIG_PER_16K_Val) { - return -EINVAL; + /* CONFIG is enable protected, error out if already enabled */ + if (WDT_REGS->CTRL.bit.ENABLE) { + LOG_ERR("Watchdog already setup"); + return -EBUSY; } - /* Disable the WDT to change the config. */ - wdt_sam0_disable(dev); - - switch (config->mode) { - case WDT_MODE_RESET: - WDT_REGS->INTENCLR.reg = WDT_INTENCLR_EW; - wdt_sam0_wait_synchronization(); - break; - - case WDT_MODE_INTERRUPT_RESET: - /* Fire the early warning earlier. */ - WDT_REGS->EWCTRL.bit.EWOFFSET = divisor - 1; - wdt_sam0_wait_synchronization(); - - /* Clear the pending interrupt, if any. */ - WDT_REGS->INTFLAG.reg = WDT_INTFLAG_EW; - wdt_sam0_wait_synchronization(); - - WDT_REGS->INTENSET.reg = WDT_INTENSET_EW; - wdt_sam0_wait_synchronization(); - break; - - default: - return -EINVAL; + if (cfg->flags != WDT_FLAG_RESET_SOC) { + LOG_ERR("Only SoC reset supported"); + return -ENOTSUP; } - WDT_REGS->CONFIG.bit.PER = divisor; - wdt_sam0_wait_synchronization(); + per = wdt_sam0_timeout_to_wdt_period(cfg->window.max); + if (per > WDT_CONFIG_PER_16K_Val) { + LOG_ERR("Upper limit timeout out of range"); + goto timeout_invalid; + } - data->cb = config->interrupt_fn; - - WDT_REGS->CTRL = ctrl; - wdt_sam0_wait_synchronization(); - - return 0; -} - -static void wdt_sam0_get_config(struct device *dev, struct wdt_config *config) -{ - struct wdt_sam0_dev_data *data = dev->driver_data; - - if (WDT_REGS->INTENSET.bit.EW) { - config->mode = WDT_MODE_INTERRUPT_RESET; + if (cfg->window.min) { + /* Window mode */ + window = wdt_sam0_timeout_to_wdt_period(cfg->window.min); + if (window > WDT_CONFIG_PER_8K_Val) { + LOG_ERR("Lower limit timeout out of range"); + goto timeout_invalid; + } + if (per <= window) { + /* Ensure we have a window */ + per = window + 1; + } + WDT_REGS->CTRL.bit.WEN = 1; + wdt_sam0_wait_synchronization(); } else { - config->mode = WDT_MODE_RESET; + /* Normal mode */ + if (cfg->callback) { + if (per == WDT_CONFIG_PER_8_Val) { + /* Ensure we have time for the early warning */ + per += 1; + } + WDT_REGS->EWCTRL.bit.EWOFFSET = per - 1; + } + window = WDT_CONFIG_PER_8_Val; + WDT_REGS->CTRL.bit.WEN = 0; + wdt_sam0_wait_synchronization(); } - config->timeout = WDT_REGS->CONFIG.bit.PER - + WDT_2_29_CYCLES - WDT_CONFIG_PER_16K_Val; - config->interrupt_fn = data->cb; + WDT_REGS->CONFIG.reg = WDT_CONFIG_WINDOW(window) | WDT_CONFIG_PER(per); + wdt_sam0_wait_synchronization(); + + /* Only enable IRQ if a callback was provided */ + data->cb = cfg->callback; + if (data->cb) { + WDT_REGS->INTENSET.reg = WDT_INTENSET_EW; + } else { + WDT_REGS->INTENCLR.reg = WDT_INTENCLR_EW; + WDT_REGS->INTFLAG.reg = WDT_INTFLAG_EW; + } + + data->timeout_valid = true; + + return 0; + +timeout_invalid: + data->timeout_valid = false; + data->cb = NULL; + + return -EINVAL; } -static void wdt_sam0_reload(struct device *dev) +static int wdt_sam0_feed(struct device *dev, int channel_id) { - WDT_REGS->CLEAR.bit.CLEAR = WDT_CLEAR_CLEAR_KEY_Val; + struct wdt_sam0_dev_data *data = dev->driver_data; + + if (!data->timeout_valid) { + LOG_ERR("No valid timeout installed"); + return -EINVAL; + } + + WDT_REGS->CLEAR.reg = WDT_CLEAR_CLEAR_KEY_Val; + + return 0; } static const struct wdt_driver_api wdt_sam0_api = { - .enable = wdt_sam0_enable, + .setup = wdt_sam0_setup, .disable = wdt_sam0_disable, - .get_config = wdt_sam0_get_config, - .set_config = wdt_sam0_set_config, - .reload = wdt_sam0_reload, + .install_timeout = wdt_sam0_install_timeout, + .feed = wdt_sam0_feed, }; static int wdt_sam0_init(struct device *dev) { +#ifdef CONFIG_WDT_DISABLE_AT_BOOT + /* Ignore any errors */ + wdt_sam0_disable(dev); +#endif /* Enable APB clock */ PM->APBAMASK.bit.WDT_ = 1; - /* Connect to GCLK2 (~1 kHz) */ + /* Connect to GCLK2 (~1.024 kHz) */ GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID_WDT | GCLK_CLKCTRL_GEN_GCLK2 | GCLK_CLKCTRL_CLKEN; @@ -153,6 +218,6 @@ static int wdt_sam0_init(struct device *dev) static struct wdt_sam0_dev_data wdt_sam0_data; -DEVICE_AND_API_INIT(wdt_sam0, CONFIG_WDT_0_NAME, wdt_sam0_init, +DEVICE_AND_API_INIT(wdt_sam0, DT_WDT_SAM0_LABEL, wdt_sam0_init, &wdt_sam0_data, NULL, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &wdt_sam0_api); diff --git a/soc/arm/atmel_sam0/samd20/dts_fixup.h b/soc/arm/atmel_sam0/samd20/dts_fixup.h index fde2a0752f9..1a352e26bc2 100644 --- a/soc/arm/atmel_sam0/samd20/dts_fixup.h +++ b/soc/arm/atmel_sam0/samd20/dts_fixup.h @@ -91,6 +91,7 @@ #define DT_WDT_SAM0_IRQ_PRIORITY DT_ATMEL_SAM0_WATCHDOG_40001000_IRQ_0_PRIORITY #define DT_WDT_SAM0_LABEL DT_ATMEL_SAM0_WATCHDOG_40001000_LABEL #define DT_WDT_SAM0_BASE_ADDRESS DT_ATMEL_SAM0_WATCHDOG_40001000_BASE_ADDRESS +#define CONFIG_WDT_0_NAME DT_WDT_SAM0_LABEL #define DT_PINMUX_SAM0_A_BASE_ADDRESS DT_ATMEL_SAM0_PINMUX_41004400_BASE_ADDRESS #define DT_PINMUX_SAM0_A_LABEL DT_ATMEL_SAM0_PINMUX_41004400_LABEL diff --git a/soc/arm/atmel_sam0/samd21/dts_fixup.h b/soc/arm/atmel_sam0/samd21/dts_fixup.h index fe206c84b87..a506edee043 100644 --- a/soc/arm/atmel_sam0/samd21/dts_fixup.h +++ b/soc/arm/atmel_sam0/samd21/dts_fixup.h @@ -91,6 +91,7 @@ #define DT_WDT_SAM0_IRQ_PRIORITY DT_ATMEL_SAM0_WATCHDOG_40001000_IRQ_0_PRIORITY #define DT_WDT_SAM0_LABEL DT_ATMEL_SAM0_WATCHDOG_40001000_LABEL #define DT_WDT_SAM0_BASE_ADDRESS DT_ATMEL_SAM0_WATCHDOG_40001000_BASE_ADDRESS +#define CONFIG_WDT_0_NAME DT_WDT_SAM0_LABEL #define DT_PINMUX_SAM0_A_BASE_ADDRESS DT_ATMEL_SAM0_PINMUX_41004400_BASE_ADDRESS #define DT_PINMUX_SAM0_A_LABEL DT_ATMEL_SAM0_PINMUX_41004400_LABEL