| /* | 
 |  * Copyright (c) 2022 TOKITA Hiroshi | 
 |  * | 
 |  * SPDX-License-Identifier: Apache-2.0 | 
 |  */ | 
 |  | 
 | #define DT_DRV_COMPAT gd_gd32_wwdgt | 
 |  | 
 | #include <zephyr/drivers/clock_control.h> | 
 | #include <zephyr/drivers/clock_control/gd32.h> | 
 | #include <zephyr/drivers/reset.h> | 
 | #include <zephyr/drivers/watchdog.h> | 
 | #include <zephyr/irq.h> | 
 | #include <zephyr/logging/log.h> | 
 | #include <zephyr/sys_clock.h> | 
 |  | 
 | #include <gd32_wwdgt.h> | 
 |  | 
 | LOG_MODULE_REGISTER(wdt_wwdgt_gd32, CONFIG_WDT_LOG_LEVEL); | 
 |  | 
 | #define WWDGT_PRESCALER_EXP_MAX (3U) | 
 | #define WWDGT_COUNTER_MIN	(0x40U) | 
 | #define WWDGT_COUNTER_MAX	(0x7fU) | 
 | #define WWDGT_INTERNAL_DIVIDER	(4096ULL) | 
 |  | 
 | struct gd32_wwdgt_config { | 
 | 	uint16_t clkid; | 
 | 	struct reset_dt_spec reset; | 
 | }; | 
 |  | 
 | /* mutable driver data */ | 
 | struct gd32_wwdgt_data { | 
 | 	/* counter update value*/ | 
 | 	uint8_t counter; | 
 | 	/* user defined callback */ | 
 | 	wdt_callback_t callback; | 
 | }; | 
 |  | 
 | static void gd32_wwdgt_irq_config(const struct device *dev); | 
 |  | 
 | /** | 
 |  * @param timeout Timeout value in milliseconds. | 
 |  * @param exp exponent part of prescaler | 
 |  * | 
 |  * @return ticks count calculated by this formula. | 
 |  * | 
 |  *  timeout = pclk * INTERNAL_DIVIDER * (2^prescaler_exp) * (count + 1) | 
 |  *  transform as | 
 |  *  count = (timeout * pclk / INTERNAL_DIVIDER * (2^prescaler_exp) ) - 1 | 
 |  * | 
 |  *  and add WWDGT_COUNTER_MIN to this as a offset value. | 
 |  */ | 
 | static inline uint32_t gd32_wwdgt_calc_ticks(const struct device *dev, | 
 | 					    uint32_t timeout, uint32_t exp) | 
 | { | 
 | 	const struct gd32_wwdgt_config *config = dev->config; | 
 | 	uint32_t pclk; | 
 |  | 
 | 	(void)clock_control_get_rate(GD32_CLOCK_CONTROLLER, | 
 | 				       (clock_control_subsys_t)&config->clkid, | 
 | 				       &pclk); | 
 |  | 
 | 	return ((timeout * pclk) | 
 | 		/ (WWDGT_INTERNAL_DIVIDER * (1 << exp) * MSEC_PER_SEC) - 1) | 
 | 		+ WWDGT_COUNTER_MIN; | 
 | } | 
 |  | 
 | /** | 
 |  * @brief Calculates WWDGT config value from timeout window. | 
 |  * | 
 |  * @param win Pointer to timeout window struct. | 
 |  * @param counter Pointer to the storage of counter value. | 
 |  * @param wval Pointer to the storage of window value. | 
 |  * @param prescaler Pointer to the storage of prescaler value. | 
 |  * | 
 |  * @return 0 on success, -EINVAL if the window-max is out of range | 
 |  */ | 
 | static int gd32_wwdgt_calc_window(const struct device *dev, | 
 | 				  const struct wdt_window *win, | 
 | 				  uint32_t *counter, uint32_t *wval, | 
 | 				  uint32_t *prescaler) | 
 | { | 
 | 	for (uint32_t shift = 0U; shift <= WWDGT_PRESCALER_EXP_MAX; shift++) { | 
 | 		uint32_t max_count = gd32_wwdgt_calc_ticks(dev, win->max, shift); | 
 |  | 
 | 		if (max_count <= WWDGT_COUNTER_MAX) { | 
 | 			*counter = max_count; | 
 | 			*prescaler = CFG_PSC(shift); | 
 | 			if (win->min == 0U) { | 
 | 				*wval = max_count; | 
 | 			} else { | 
 | 				*wval = gd32_wwdgt_calc_ticks(dev, win->min, shift); | 
 | 			} | 
 |  | 
 | 			return 0; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	return -EINVAL; | 
 | } | 
 |  | 
 | static int gd32_wwdgt_setup(const struct device *dev, uint8_t options) | 
 | { | 
 | 	ARG_UNUSED(dev); | 
 |  | 
 | 	if (options & WDT_OPT_PAUSE_HALTED_BY_DBG) { | 
 | #if CONFIG_GD32_DBG_SUPPORT | 
 | 		dbg_periph_enable(DBG_WWDGT_HOLD); | 
 | #else | 
 | 		LOG_ERR("Debug support not enabled"); | 
 | 		return -ENOTSUP; | 
 | #endif | 
 | 	} | 
 |  | 
 | 	if (options & WDT_OPT_PAUSE_IN_SLEEP) { | 
 | 		LOG_ERR("WDT_OPT_PAUSE_IN_SLEEP not supported"); | 
 | 		return -ENOTSUP; | 
 | 	} | 
 |  | 
 | 	wwdgt_enable(); | 
 | 	wwdgt_flag_clear(); | 
 | 	wwdgt_interrupt_enable(); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int gd32_wwdgt_disable(const struct device *dev) | 
 | { | 
 | 	/* watchdog cannot be stopped once started */ | 
 | 	ARG_UNUSED(dev); | 
 |  | 
 | 	return -EPERM; | 
 | } | 
 |  | 
 | static int gd32_wwdgt_install_timeout(const struct device *dev, | 
 | 				      const struct wdt_timeout_cfg *config) | 
 | { | 
 | 	uint32_t prescaler = 0U; | 
 | 	uint32_t counter = 0U; | 
 | 	uint32_t window = 0U; | 
 | 	struct gd32_wwdgt_data *data = dev->data; | 
 |  | 
 | 	if (config->window.max == 0U) { | 
 | 		LOG_ERR("window.max must be non-zero"); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	if (gd32_wwdgt_calc_window(dev, &config->window, &counter, &window, | 
 | 				   &prescaler) != 0) { | 
 | 		LOG_ERR("window.max in out of range"); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	data->callback = config->callback; | 
 | 	data->counter = counter; | 
 |  | 
 | 	wwdgt_config(counter, window, prescaler); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int gd32_wwdgt_feed(const struct device *dev, int channel_id) | 
 | { | 
 | 	struct gd32_wwdgt_data *data = dev->data; | 
 |  | 
 | 	ARG_UNUSED(channel_id); | 
 |  | 
 | 	wwdgt_counter_update(data->counter); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static void gd32_wwdgt_isr(const struct device *dev) | 
 | { | 
 | 	struct gd32_wwdgt_data *data = dev->data; | 
 |  | 
 | 	if (wwdgt_flag_get() != 0) { | 
 | 		wwdgt_flag_clear(); | 
 |  | 
 | 		if (data->callback != NULL) { | 
 | 			data->callback(dev, 0); | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 | static void gd32_wwdgt_irq_config(const struct device *dev) | 
 | { | 
 | 	ARG_UNUSED(dev); | 
 |  | 
 | 	IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), gd32_wwdgt_isr, | 
 | 		    DEVICE_DT_INST_GET(0), 0); | 
 | 	irq_enable(DT_INST_IRQN(0)); | 
 | } | 
 |  | 
 | static DEVICE_API(wdt, wwdgt_gd32_api) = { | 
 | 	.setup = gd32_wwdgt_setup, | 
 | 	.disable = gd32_wwdgt_disable, | 
 | 	.install_timeout = gd32_wwdgt_install_timeout, | 
 | 	.feed = gd32_wwdgt_feed, | 
 | }; | 
 |  | 
 | static int gd32_wwdgt_init(const struct device *dev) | 
 | { | 
 | 	const struct gd32_wwdgt_config *config = dev->config; | 
 |  | 
 | 	(void)clock_control_on(GD32_CLOCK_CONTROLLER, | 
 | 			       (clock_control_subsys_t)&config->clkid); | 
 | 	(void)reset_line_toggle_dt(&config->reset); | 
 | 	gd32_wwdgt_irq_config(dev); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static const struct gd32_wwdgt_config wwdgt_cfg = { | 
 | 	.clkid = DT_INST_CLOCKS_CELL(0, id), | 
 | 	.reset = RESET_DT_SPEC_INST_GET(0), | 
 | }; | 
 |  | 
 | static struct gd32_wwdgt_data wwdgt_data = { | 
 | 	.counter = WWDGT_COUNTER_MIN, | 
 | 	.callback = NULL | 
 | }; | 
 |  | 
 | DEVICE_DT_INST_DEFINE(0, gd32_wwdgt_init, NULL, &wwdgt_data, &wwdgt_cfg, POST_KERNEL, | 
 | 		      CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &wwdgt_gd32_api); |