| /* | 
 |  * Copyright (c) 2018 Henrik Brix Andersen <henrik@brixandersen.dk> | 
 |  * Copyright (c) 2017 Google LLC. | 
 |  * | 
 |  * SPDX-License-Identifier: Apache-2.0 | 
 |  */ | 
 |  | 
 | #define DT_DRV_COMPAT atmel_sam0_watchdog | 
 |  | 
 | #include <soc.h> | 
 | #include <zephyr/drivers/watchdog.h> | 
 |  | 
 | #define LOG_LEVEL CONFIG_WDT_LOG_LEVEL | 
 | #include <zephyr/logging/log.h> | 
 | LOG_MODULE_REGISTER(wdt_sam0); | 
 |  | 
 | #define WDT_REGS ((Wdt *)DT_INST_REG_ADDR(0)) | 
 |  | 
 | #ifndef WDT_CONFIG_PER_8_Val | 
 | #define WDT_CONFIG_PER_8_Val WDT_CONFIG_PER_CYC8_Val | 
 | #endif | 
 | #ifndef WDT_CONFIG_PER_8K_Val | 
 | #define WDT_CONFIG_PER_8K_Val WDT_CONFIG_PER_CYC8192_Val | 
 | #endif | 
 | #ifndef WDT_CONFIG_PER_16K_Val | 
 | #define WDT_CONFIG_PER_16K_Val WDT_CONFIG_PER_CYC16384_Val | 
 | #endif | 
 |  | 
 | /* syncbusy check is different for SAM D/E */ | 
 | #ifdef WDT_STATUS_SYNCBUSY | 
 | #define WDT_SYNCBUSY WDT_REGS->STATUS.bit.SYNCBUSY | 
 | #else | 
 | #define WDT_SYNCBUSY WDT_REGS->SYNCBUSY.reg | 
 | #endif | 
 |  | 
 | struct wdt_sam0_dev_data { | 
 | 	wdt_callback_t cb; | 
 | 	bool timeout_valid; | 
 | }; | 
 |  | 
 | static struct wdt_sam0_dev_data wdt_sam0_data = { 0 }; | 
 |  | 
 | static void wdt_sam0_wait_synchronization(void) | 
 | { | 
 | 	while (WDT_SYNCBUSY) { | 
 | 		/* wait for SYNCBUSY */ | 
 | 	} | 
 | } | 
 |  | 
 | static inline void wdt_sam0_set_enable(bool on) | 
 | { | 
 | #ifdef WDT_CTRLA_ENABLE | 
 | 	WDT_REGS->CTRLA.bit.ENABLE = on; | 
 | #else | 
 | 	WDT_REGS->CTRL.bit.ENABLE = on; | 
 | #endif | 
 | } | 
 |  | 
 | static inline bool wdt_sam0_is_enabled(void) | 
 | { | 
 | #ifdef WDT_CTRLA_ENABLE | 
 | 	return WDT_REGS->CTRLA.bit.ENABLE; | 
 | #else | 
 | 	return WDT_REGS->CTRL.bit.ENABLE; | 
 | #endif | 
 | } | 
 |  | 
 | static uint32_t wdt_sam0_timeout_to_wdt_period(uint32_t timeout_ms) | 
 | { | 
 | 	uint32_t next_pow2; | 
 | 	uint32_t cycles; | 
 |  | 
 | 	/* Calculate number of clock cycles @ 1.024 kHz input clock */ | 
 | 	cycles = (timeout_ms * 1024U) / 1000; | 
 |  | 
 | 	/* Minimum wdt period is 8 clock cycles (register value 0) */ | 
 | 	if (cycles <= 8U) { | 
 | 		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(const struct device *dev) | 
 | { | 
 | 	struct wdt_sam0_dev_data *data = dev->data; | 
 |  | 
 | 	WDT_REGS->INTFLAG.reg = WDT_INTFLAG_EW; | 
 |  | 
 | 	if (data->cb != NULL) { | 
 | 		data->cb(dev, 0); | 
 | 	} | 
 | } | 
 |  | 
 | static int wdt_sam0_setup(const struct device *dev, uint8_t options) | 
 | { | 
 | 	struct wdt_sam0_dev_data *data = dev->data; | 
 |  | 
 | 	if (wdt_sam0_is_enabled()) { | 
 | 		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_sam0_set_enable(1); | 
 | 	wdt_sam0_wait_synchronization(); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int wdt_sam0_disable(const struct device *dev) | 
 | { | 
 | 	if (!wdt_sam0_is_enabled()) { | 
 | 		return -EFAULT; | 
 | 	} | 
 |  | 
 | 	wdt_sam0_set_enable(0); | 
 | 	wdt_sam0_wait_synchronization(); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int wdt_sam0_install_timeout(const struct device *dev, | 
 | 				    const struct wdt_timeout_cfg *cfg) | 
 | { | 
 | 	struct wdt_sam0_dev_data *data = dev->data; | 
 | 	uint32_t window, per; | 
 |  | 
 | 	/* CONFIG is enable protected, error out if already enabled */ | 
 | 	if (wdt_sam0_is_enabled()) { | 
 | 		LOG_ERR("Watchdog already setup"); | 
 | 		return -EBUSY; | 
 | 	} | 
 |  | 
 | 	if (cfg->flags != WDT_FLAG_RESET_SOC) { | 
 | 		LOG_ERR("Only SoC reset supported"); | 
 | 		return -ENOTSUP; | 
 | 	} | 
 |  | 
 | 	if (cfg->window.max == 0) { | 
 | 		LOG_ERR("Upper limit timeout out of range"); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	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; | 
 | 	} | 
 |  | 
 | 	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; | 
 | 		} | 
 | #ifdef WDT_CTRLA_WEN | 
 | 		WDT_REGS->CTRLA.bit.WEN = 1; | 
 | #else | 
 | 		WDT_REGS->CTRL.bit.WEN = 1; | 
 | #endif | 
 | 		wdt_sam0_wait_synchronization(); | 
 | 	} else { | 
 | 		/* Normal mode */ | 
 | 		if (cfg->callback) { | 
 | 			if (per == WDT_CONFIG_PER_8_Val) { | 
 | 				/* Ensure we have time for the early warning */ | 
 | 				per += 1U; | 
 | 			} | 
 | 			WDT_REGS->EWCTRL.bit.EWOFFSET = per - 1U; | 
 | 		} | 
 | 		window = WDT_CONFIG_PER_8_Val; | 
 | #ifdef WDT_CTRLA_WEN | 
 | 		WDT_REGS->CTRLA.bit.WEN = 0; | 
 | #else | 
 | 		WDT_REGS->CTRL.bit.WEN = 0; | 
 | #endif | 
 | 		wdt_sam0_wait_synchronization(); | 
 | 	} | 
 |  | 
 | 	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 int wdt_sam0_feed(const struct device *dev, int channel_id) | 
 | { | 
 | 	struct wdt_sam0_dev_data *data = dev->data; | 
 |  | 
 | 	if (!data->timeout_valid) { | 
 | 		LOG_ERR("No valid timeout installed"); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	if (WDT_SYNCBUSY) { | 
 | 		return -EAGAIN; | 
 | 	} | 
 |  | 
 | 	WDT_REGS->CLEAR.reg = WDT_CLEAR_CLEAR_KEY_Val; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static const struct wdt_driver_api wdt_sam0_api = { | 
 | 	.setup = wdt_sam0_setup, | 
 | 	.disable = wdt_sam0_disable, | 
 | 	.install_timeout = wdt_sam0_install_timeout, | 
 | 	.feed = wdt_sam0_feed, | 
 | }; | 
 |  | 
 | static int wdt_sam0_init(const struct device *dev) | 
 | { | 
 | #ifdef CONFIG_WDT_DISABLE_AT_BOOT | 
 | 	/* Ignore any errors */ | 
 | 	wdt_sam0_disable(dev); | 
 | #endif | 
 | 	/* Enable APB clock */ | 
 | #ifdef MCLK | 
 | 	MCLK->APBAMASK.bit.WDT_ = 1; | 
 |  | 
 | 	/* watchdog clock is fed by OSCULP32K */ | 
 | #else | 
 | 	PM->APBAMASK.bit.WDT_ = 1; | 
 |  | 
 | 	/* Connect to GCLK2 (~1.024 kHz) */ | 
 | 	GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID_WDT | 
 | 		| GCLK_CLKCTRL_GEN_GCLK2 | 
 | 		| GCLK_CLKCTRL_CLKEN; | 
 | #endif | 
 |  | 
 | 	IRQ_CONNECT(DT_INST_IRQN(0), | 
 | 		    DT_INST_IRQ(0, priority), wdt_sam0_isr, | 
 | 		    DEVICE_DT_INST_GET(0), 0); | 
 | 	irq_enable(DT_INST_IRQN(0)); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static struct wdt_sam0_dev_data wdt_sam0_data; | 
 |  | 
 | DEVICE_DT_INST_DEFINE(0, wdt_sam0_init, NULL, | 
 | 		    &wdt_sam0_data, NULL, PRE_KERNEL_1, | 
 | 		    CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &wdt_sam0_api); |