| /* |
| * Copyright (c) 2019 Interay Solutions B.V. |
| * Copyright (c) 2019 Oane Kingma |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <soc.h> |
| #include <drivers/watchdog.h> |
| #include <em_wdog.h> |
| #include <em_cmu.h> |
| |
| #include <logging/log.h> |
| LOG_MODULE_REGISTER(wdt_gecko, CONFIG_WDT_LOG_LEVEL); |
| |
| /* Defines maximum WDOG_CTRL.PERSEL value which is used by the watchdog module |
| * to select its timeout period. |
| */ |
| #define WDT_GECKO_MAX_PERIOD_SELECT_VALUE 15 |
| |
| /* Device constant configuration parameters */ |
| struct wdt_gecko_cfg { |
| WDOG_TypeDef *base; |
| void (*irq_cfg_func)(void); |
| }; |
| |
| struct wdt_gecko_data { |
| wdt_callback_t callback; |
| WDOG_Init_TypeDef wdog_config; |
| bool timeout_installed; |
| }; |
| |
| #define DEV_NAME(dev) ((dev)->config->name) |
| #define DEV_DATA(dev) \ |
| ((struct wdt_gecko_data *)(dev)->driver_data) |
| #define DEV_CFG(dev) \ |
| ((struct wdt_gecko_cfg *)(dev)->config->config_info) |
| |
| static u32_t wdt_gecko_get_timeout_from_persel(int perSel) |
| { |
| return (8 << perSel) + 1; |
| } |
| |
| /* Find the rounded up value of cycles for supplied timeout. When using ULFRCO |
| * (default), 1 cycle is 1 ms +/- 12%. |
| */ |
| static int wdt_gecko_get_persel_from_timeout(u32_t timeout) |
| { |
| int idx; |
| |
| for (idx = 0; idx < WDT_GECKO_MAX_PERIOD_SELECT_VALUE; idx++) { |
| if (wdt_gecko_get_timeout_from_persel(idx) >= timeout) { |
| break; |
| } |
| } |
| |
| return idx; |
| } |
| |
| static int wdt_gecko_convert_window(u32_t window, u32_t period) |
| { |
| int idx = 0; |
| u32_t incr_val, comp_val; |
| |
| incr_val = period / 8; |
| comp_val = 0; /* Initially 0, disable */ |
| |
| /* Valid window settings range from 12.5% of the calculated |
| * timeout period up to 87.5% (= 7 * 12.5%) |
| */ |
| while (idx < 7) { |
| if (window > comp_val) { |
| comp_val += incr_val; |
| idx++; |
| continue; |
| } |
| |
| break; |
| } |
| |
| return idx; |
| } |
| |
| static int wdt_gecko_setup(struct device *dev, u8_t options) |
| { |
| const struct wdt_gecko_cfg *config = DEV_CFG(dev); |
| struct wdt_gecko_data *data = DEV_DATA(dev); |
| WDOG_TypeDef *wdog = config->base; |
| |
| if (!data->timeout_installed) { |
| LOG_ERR("No valid timeouts installed"); |
| return -EINVAL; |
| } |
| |
| data->wdog_config.em2Run = |
| (options & WDT_OPT_PAUSE_IN_SLEEP) == 0U; |
| data->wdog_config.em3Run = |
| (options & WDT_OPT_PAUSE_IN_SLEEP) == 0U; |
| |
| data->wdog_config.debugRun = |
| (options & WDT_OPT_PAUSE_HALTED_BY_DBG) == 0U; |
| |
| if (data->callback != NULL) { |
| /* Interrupt mode for window */ |
| /* Clear possible lingering interrupts */ |
| WDOGn_IntClear(wdog, WDOG_IEN_TOUT); |
| /* Enable timeout interrupt */ |
| WDOGn_IntEnable(wdog, WDOG_IEN_TOUT); |
| } else { |
| /* Disable timeout interrupt */ |
| WDOGn_IntDisable(wdog, WDOG_IEN_TOUT); |
| } |
| |
| /* Watchdog is started after initialization */ |
| WDOGn_Init(wdog, &data->wdog_config); |
| LOG_DBG("Setup the watchdog"); |
| |
| return 0; |
| } |
| |
| static int wdt_gecko_disable(struct device *dev) |
| { |
| const struct wdt_gecko_cfg *config = DEV_CFG(dev); |
| struct wdt_gecko_data *data = DEV_DATA(dev); |
| WDOG_TypeDef *wdog = config->base; |
| |
| WDOGn_Enable(wdog, false); |
| data->timeout_installed = false; |
| LOG_DBG("Disabled the watchdog"); |
| |
| return 0; |
| } |
| |
| static int wdt_gecko_install_timeout(struct device *dev, |
| const struct wdt_timeout_cfg *cfg) |
| { |
| struct wdt_gecko_data *data = DEV_DATA(dev); |
| WDOG_Init_TypeDef init_defaults = WDOG_INIT_DEFAULT; |
| u32_t installed_timeout; |
| |
| if (data->timeout_installed) { |
| LOG_ERR("No more timeouts can be installed"); |
| return -ENOMEM; |
| } |
| |
| if ((cfg->window.max < wdt_gecko_get_timeout_from_persel(0)) || |
| (cfg->window.max > wdt_gecko_get_timeout_from_persel( |
| WDT_GECKO_MAX_PERIOD_SELECT_VALUE))) { |
| LOG_ERR("Upper limit timeout out of range"); |
| return -EINVAL; |
| } |
| |
| data->wdog_config = init_defaults; |
| |
| data->wdog_config.perSel = (WDOG_PeriodSel_TypeDef) |
| wdt_gecko_get_persel_from_timeout(cfg->window.max); |
| |
| installed_timeout = wdt_gecko_get_timeout_from_persel( |
| data->wdog_config.perSel); |
| LOG_INF("Installed timeout value: %u", installed_timeout); |
| |
| if (cfg->window.min > 0) { |
| /* Window mode. Use rounded up timeout value to |
| * calculate minimum window setting. |
| */ |
| data->wdog_config.winSel = (WDOG_WinSel_TypeDef) |
| wdt_gecko_convert_window(cfg->window.min, |
| installed_timeout); |
| |
| LOG_INF("Installed window value: %u", |
| (installed_timeout / 8) * data->wdog_config.winSel); |
| } else { |
| /* Normal mode */ |
| data->wdog_config.winSel = wdogIllegalWindowDisable; |
| } |
| |
| /* Set mode of watchdog and callback */ |
| switch (cfg->flags) { |
| case WDT_FLAG_RESET_SOC: |
| case WDT_FLAG_RESET_CPU_CORE: |
| if (cfg->callback != NULL) { |
| LOG_ERR("Reset mode with callback not supported\n"); |
| return -ENOTSUP; |
| } |
| data->wdog_config.resetDisable = false; |
| LOG_DBG("Configuring reset CPU/SoC mode\n"); |
| break; |
| |
| case WDT_FLAG_RESET_NONE: |
| data->wdog_config.resetDisable = true; |
| data->callback = cfg->callback; |
| LOG_DBG("Configuring non-reset mode\n"); |
| break; |
| |
| default: |
| LOG_ERR("Unsupported watchdog config flag"); |
| return -EINVAL; |
| } |
| |
| data->timeout_installed = true; |
| |
| return 0; |
| } |
| |
| static int wdt_gecko_feed(struct device *dev, int channel_id) |
| { |
| const struct wdt_gecko_cfg *config = DEV_CFG(dev); |
| WDOG_TypeDef *wdog = config->base; |
| |
| if (channel_id != 0) { |
| LOG_ERR("Invalid channel id"); |
| return -EINVAL; |
| } |
| |
| WDOGn_Feed(wdog); |
| LOG_DBG("Fed the watchdog"); |
| |
| return 0; |
| } |
| |
| static void wdt_gecko_isr(void *arg) |
| { |
| struct device *dev = (struct device *)arg; |
| const struct wdt_gecko_cfg *config = DEV_CFG(dev); |
| struct wdt_gecko_data *data = DEV_DATA(dev); |
| WDOG_TypeDef *wdog = config->base; |
| u32_t flags; |
| |
| /* Clear IRQ flags */ |
| flags = WDOGn_IntGet(wdog); |
| WDOGn_IntClear(wdog, flags); |
| |
| if (data->callback != NULL) { |
| data->callback(dev, 0); |
| } |
| } |
| |
| static int wdt_gecko_init(struct device *dev) |
| { |
| const struct wdt_gecko_cfg *config = DEV_CFG(dev); |
| |
| #ifdef CONFIG_WDT_DISABLE_AT_BOOT |
| /* Ignore any errors */ |
| wdt_gecko_disable(dev); |
| #endif |
| |
| /* Enable ULFRCO (1KHz) oscillator */ |
| CMU_OscillatorEnable(cmuOsc_ULFRCO, true, false); |
| |
| /* Ensure LE modules are clocked */ |
| CMU_ClockEnable(cmuClock_CORELE, true); |
| |
| /* Enable IRQs */ |
| config->irq_cfg_func(); |
| |
| LOG_INF("Device %s initialized", DEV_NAME(dev)); |
| |
| return 0; |
| } |
| |
| static const struct wdt_driver_api wdt_gecko_driver_api = { |
| .setup = wdt_gecko_setup, |
| .disable = wdt_gecko_disable, |
| .install_timeout = wdt_gecko_install_timeout, |
| .feed = wdt_gecko_feed, |
| }; |
| |
| #define GECKO_WDT_INIT(index) \ |
| \ |
| static void wdt_gecko_cfg_func_##index(void); \ |
| \ |
| static const struct wdt_gecko_cfg wdt_gecko_cfg_##index = { \ |
| .base = (WDOG_TypeDef *) \ |
| DT_INST_##index##_SILABS_GECKO_WDOG_BASE_ADDRESS,\ |
| .irq_cfg_func = wdt_gecko_cfg_func_##index, \ |
| }; \ |
| static struct wdt_gecko_data wdt_gecko_data_##index; \ |
| \ |
| DEVICE_AND_API_INIT(wdt_##index, \ |
| DT_INST_##index##_SILABS_GECKO_WDOG_LABEL,\ |
| &wdt_gecko_init, &wdt_gecko_data_##index,\ |
| &wdt_gecko_cfg_##index, POST_KERNEL, \ |
| CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \ |
| &wdt_gecko_driver_api); \ |
| \ |
| static void wdt_gecko_cfg_func_##index(void) \ |
| { \ |
| IRQ_CONNECT(DT_INST_##index##_SILABS_GECKO_WDOG_IRQ_0, \ |
| DT_INST_##index##_SILABS_GECKO_WDOG_IRQ_0_PRIORITY,\ |
| wdt_gecko_isr, DEVICE_GET(wdt_##index), 0); \ |
| irq_enable(DT_INST_##index##_SILABS_GECKO_WDOG_IRQ_0); \ |
| } |
| |
| #ifdef DT_INST_0_SILABS_GECKO_WDOG |
| GECKO_WDT_INIT(0) |
| #endif /* DT_INST_0_SILABS_GECKO_WDOG */ |
| |
| #ifdef DT_INST_1_SILABS_GECKO_WDOG |
| GECKO_WDT_INIT(1) |
| #endif /* DT_INST_1_SILABS_GECKO_WDOG */ |