| /* | 
 |  * Copyright (c) 2022 Florin Stancu <niflostancu@gmail.com> | 
 |  * | 
 |  * SPDX-License-Identifier: Apache-2.0 | 
 |  */ | 
 |  | 
 | #define DT_DRV_COMPAT ti_cc13xx_cc26xx_watchdog | 
 |  | 
 | #include <zephyr/drivers/watchdog.h> | 
 | #include <zephyr/irq.h> | 
 | #include <soc.h> | 
 | #include <errno.h> | 
 |  | 
 | #define LOG_LEVEL CONFIG_WDT_LOG_LEVEL | 
 | #include <zephyr/logging/log.h> | 
 | LOG_MODULE_REGISTER(wdt_cc13xx_cc26xx); | 
 |  | 
 | /* Driverlib includes */ | 
 | #include <driverlib/watchdog.h> | 
 |  | 
 |  | 
 | /* | 
 |  * TI CC13xx/CC26xx watchdog is a 32-bit timer that runs on the MCU clock | 
 |  * with a fixed 32 divider. | 
 |  * | 
 |  * For the default MCU frequency of 48MHz: | 
 |  * 1ms = (48e6 / 32 / 1000) = 1500 ticks | 
 |  * Max. value = 2^32 / 1500 ~= 2863311 ms | 
 |  * | 
 |  * The watchdog will issue reset only on second in turn time-out (if the timer | 
 |  * or the interrupt aren't reset after the first time-out). By default, regular | 
 |  * interrupt is generated but platform supports also NMI (can be enabled by | 
 |  * setting the `interrupt-nmi` boolean DT property). | 
 |  */ | 
 |  | 
 | #define CPU_FREQ DT_PROP(DT_PATH(cpus, cpu_0), clock_frequency) | 
 | #define WATCHDOG_DIV_RATIO	 32 | 
 | #define WATCHDOG_MS_RATIO	 (CPU_FREQ / WATCHDOG_DIV_RATIO / 1000) | 
 | #define WATCHDOG_MAX_RELOAD_MS	 (0xFFFFFFFFu / WATCHDOG_MS_RATIO) | 
 | #define WATCHDOG_MS_TO_TICKS(_ms) ((_ms) * WATCHDOG_MS_RATIO) | 
 |  | 
 | struct wdt_cc13xx_cc26xx_data { | 
 | 	uint8_t enabled; | 
 | 	uint32_t reload; | 
 | 	wdt_callback_t cb; | 
 | 	uint8_t flags; | 
 | }; | 
 |  | 
 | struct wdt_cc13xx_cc26xx_cfg { | 
 | 	uint32_t reg; | 
 | 	uint8_t irq_nmi; | 
 | 	void (*irq_cfg_func)(void); | 
 | }; | 
 |  | 
 | static int wdt_cc13xx_cc26xx_install_timeout(const struct device *dev, | 
 | 				      const struct wdt_timeout_cfg *cfg) | 
 | { | 
 | 	struct wdt_cc13xx_cc26xx_data *data = dev->data; | 
 |  | 
 | 	/* window watchdog not supported */ | 
 | 	if (cfg->window.min != 0U || cfg->window.max == 0U) { | 
 | 		return -EINVAL; | 
 | 	} | 
 | 	/* | 
 | 	 * Note: since this SoC doesn't define CONFIG_WDT_MULTISTAGE, we don't need to | 
 | 	 * specifically check for it and return ENOTSUP | 
 | 	 */ | 
 |  | 
 | 	if (cfg->window.max > WATCHDOG_MAX_RELOAD_MS) { | 
 | 		return -EINVAL; | 
 | 	} | 
 | 	data->reload = WATCHDOG_MS_TO_TICKS(cfg->window.max); | 
 | 	data->cb = cfg->callback; | 
 | 	data->flags = cfg->flags; | 
 | 	LOG_DBG("raw reload value: %d", data->reload); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int wdt_cc13xx_cc26xx_setup(const struct device *dev, uint8_t options) | 
 | { | 
 | 	const struct wdt_cc13xx_cc26xx_cfg *config = dev->config; | 
 | 	struct wdt_cc13xx_cc26xx_data *data = dev->data; | 
 |  | 
 | 	/* | 
 | 	 * Note: don't check if watchdog is already enabled, an application might | 
 | 	 * want to dynamically re-configure its options (e.g., decrease the reload | 
 | 	 * value for critical sections). | 
 | 	 */ | 
 |  | 
 | 	WatchdogUnlock(); | 
 |  | 
 | 	/* clear any previous interrupt flags */ | 
 | 	WatchdogIntClear(); | 
 |  | 
 | 	/* Stall the WDT counter when halted by debugger */ | 
 | 	if (options & WDT_OPT_PAUSE_HALTED_BY_DBG) { | 
 | 		WatchdogStallEnable(); | 
 | 	} else { | 
 | 		WatchdogStallDisable(); | 
 | 	} | 
 | 	/* | 
 | 	 * According to TI's datasheets, the WDT is paused in STANDBY mode, | 
 | 	 * so we simply continue with the setup => don't do this check: | 
 | 	 * > if (options & WDT_OPT_PAUSE_IN_SLEEP) { | 
 | 	 * >	return -ENOTSUP; | 
 | 	 * > } | 
 | 	 */ | 
 |  | 
 | 	/* raw reload value was computed by `_install_timeout()` */ | 
 | 	WatchdogReloadSet(data->reload); | 
 |  | 
 | 	/* use the Device Tree-configured interrupt type */ | 
 | 	if (config->irq_nmi) { | 
 | 		LOG_DBG("NMI enabled"); | 
 | 		WatchdogIntTypeSet(WATCHDOG_INT_TYPE_NMI); | 
 | 	} else { | 
 | 		WatchdogIntTypeSet(WATCHDOG_INT_TYPE_INT); | 
 | 	} | 
 |  | 
 | 	switch ((data->flags & WDT_FLAG_RESET_MASK)) { | 
 | 	case WDT_FLAG_RESET_NONE: | 
 | 		LOG_DBG("reset disabled"); | 
 | 		WatchdogResetDisable(); | 
 | 		break; | 
 | 	case WDT_FLAG_RESET_SOC: | 
 | 		LOG_DBG("reset enabled"); | 
 | 		WatchdogResetEnable(); | 
 | 		break; | 
 | 	default: | 
 | 		WatchdogLock(); | 
 | 		return -ENOTSUP; | 
 | 	} | 
 |  | 
 | 	data->enabled = 1; | 
 | 	WatchdogEnable(); | 
 | 	WatchdogLock(); | 
 |  | 
 | 	LOG_DBG("done"); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int wdt_cc13xx_cc26xx_disable(const struct device *dev) | 
 | { | 
 | 	struct wdt_cc13xx_cc26xx_data *data = dev->data; | 
 |  | 
 | 	if (!WatchdogRunning()) { | 
 | 		return -EFAULT; | 
 | 	} | 
 |  | 
 | 	/* | 
 | 	 * Node: once started, the watchdog timer cannot be stopped! | 
 | 	 * All we can do is disable the timeout reset, but the interrupt | 
 | 	 * will be triggered if it was enabled (though it won't trigger the | 
 | 	 * user callback due to `enabled` being unsed)! | 
 | 	 */ | 
 | 	data->enabled = 0; | 
 | 	WatchdogUnlock(); | 
 | 	WatchdogResetDisable(); | 
 | 	WatchdogLock(); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int wdt_cc13xx_cc26xx_feed(const struct device *dev, int channel_id) | 
 | { | 
 | 	struct wdt_cc13xx_cc26xx_data *data = dev->data; | 
 |  | 
 | 	WatchdogUnlock(); | 
 | 	WatchdogIntClear(); | 
 | 	WatchdogReloadSet(data->reload); | 
 | 	WatchdogLock(); | 
 | 	LOG_DBG("feed %i", data->reload); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static void wdt_cc13xx_cc26xx_isr(const struct device *dev) | 
 | { | 
 | 	struct wdt_cc13xx_cc26xx_data *data = dev->data; | 
 |  | 
 | 	/* Simulate the watchdog being disabled: don't call the handler. */ | 
 | 	if (!data->enabled) { | 
 | 		return; | 
 | 	} | 
 |  | 
 | 	/* | 
 | 	 * Note: don't clear the interrupt here, leave it for the callback | 
 | 	 * to decide (by calling `_feed()`) | 
 | 	 */ | 
 |  | 
 | 	LOG_DBG("ISR"); | 
 | 	if (data->cb) { | 
 | 		data->cb(dev, 0); | 
 | 	} | 
 | } | 
 |  | 
 | static int wdt_cc13xx_cc26xx_init(const struct device *dev) | 
 | { | 
 | 	const struct wdt_cc13xx_cc26xx_cfg *config = dev->config; | 
 | 	uint8_t options = 0; | 
 |  | 
 | 	LOG_DBG("init"); | 
 | 	config->irq_cfg_func(); | 
 |  | 
 | 	if (IS_ENABLED(CONFIG_WDT_DISABLE_AT_BOOT)) { | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | #ifdef CONFIG_DEBUG | 
 | 	/* when CONFIG_DEBUG is enabled, pause the WDT during debugging */ | 
 | 	options = WDT_OPT_PAUSE_HALTED_BY_DBG; | 
 | #endif /* CONFIG_DEBUG */ | 
 |  | 
 | 	return wdt_cc13xx_cc26xx_setup(dev, options); | 
 | } | 
 |  | 
 | static const struct wdt_driver_api wdt_cc13xx_cc26xx_api = { | 
 | 	.setup = wdt_cc13xx_cc26xx_setup, | 
 | 	.disable = wdt_cc13xx_cc26xx_disable, | 
 | 	.install_timeout = wdt_cc13xx_cc26xx_install_timeout, | 
 | 	.feed = wdt_cc13xx_cc26xx_feed, | 
 | }; | 
 |  | 
 | #define CC13XX_CC26XX_WDT_INIT(index)						 \ | 
 | 	static void wdt_cc13xx_cc26xx_irq_cfg_##index(void)			 \ | 
 | 	{									 \ | 
 | 		if (DT_INST_PROP(index, interrupt_nmi)) {			 \ | 
 | 			return; /* NMI interrupt is used */			 \ | 
 | 		}								 \ | 
 | 		IRQ_CONNECT(DT_INST_IRQN(index),				 \ | 
 | 			DT_INST_IRQ(index, priority),				 \ | 
 | 			wdt_cc13xx_cc26xx_isr, DEVICE_DT_INST_GET(index), 0);	 \ | 
 | 		irq_enable(DT_INST_IRQN(index));				 \ | 
 | 	}									 \ | 
 | 	static struct wdt_cc13xx_cc26xx_data wdt_cc13xx_cc26xx_data_##index = {	 \ | 
 | 		.reload = WATCHDOG_MS_TO_TICKS(					 \ | 
 | 			CONFIG_WDT_CC13XX_CC26XX_INITIAL_TIMEOUT),		 \ | 
 | 		.cb = NULL,							 \ | 
 | 		.flags = 0,							 \ | 
 | 	};									 \ | 
 | 	static struct wdt_cc13xx_cc26xx_cfg wdt_cc13xx_cc26xx_cfg_##index = {	 \ | 
 | 		.reg = DT_INST_REG_ADDR(index),					 \ | 
 | 		.irq_nmi = DT_INST_PROP(index, interrupt_nmi),			 \ | 
 | 		.irq_cfg_func = wdt_cc13xx_cc26xx_irq_cfg_##index,		 \ | 
 | 	};									 \ | 
 | 	DEVICE_DT_INST_DEFINE(index,						 \ | 
 | 		wdt_cc13xx_cc26xx_init, NULL,					 \ | 
 | 		&wdt_cc13xx_cc26xx_data_##index,				 \ | 
 | 		&wdt_cc13xx_cc26xx_cfg_##index,					 \ | 
 | 		POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,		 \ | 
 | 		&wdt_cc13xx_cc26xx_api); | 
 |  | 
 | DT_INST_FOREACH_STATUS_OKAY(CC13XX_CC26XX_WDT_INIT) |