1 /*
2  * Copyright (c) 2022 TOKITA Hiroshi
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT gd_gd32_fwdgt
8 
9 #include <zephyr/drivers/watchdog.h>
10 #include <zephyr/logging/log.h>
11 #include <zephyr/sys_clock.h>
12 
13 #include <gd32_fwdgt.h>
14 
15 LOG_MODULE_REGISTER(wdt_fwdgt_gd32, CONFIG_WDT_LOG_LEVEL);
16 
17 #define FWDGT_RELOAD_MAX    (0xFFFU)
18 #define FWDGT_PRESCALER_MAX (256U)
19 
20 #if defined(CONFIG_GD32_HAS_IRC_32K)
21 #define RCU_IRC_LOW_SPEED RCU_IRC32K
22 #elif defined(CONFIG_GD32_HAS_IRC_40K)
23 #define RCU_IRC_LOW_SPEED RCU_IRC40K
24 #else
25 #error IRC frequency was not configured
26 #endif
27 
28 #define IS_VALID_FWDGT_PRESCALER(psc)                                          \
29 	(((psc) == FWDGT_PSC_DIV4) || ((psc) == FWDGT_PSC_DIV8) ||             \
30 	 ((psc) == FWDGT_PSC_DIV16) || ((psc) == FWDGT_PSC_DIV32) ||           \
31 	 ((psc) == FWDGT_PSC_DIV64) || ((psc) == FWDGT_PSC_DIV128) ||          \
32 	 ((psc) == FWDGT_PSC_DIV256))
33 
34 #define FWDGT_INITIAL_TIMEOUT DT_INST_PROP(0, initial_timeout_ms)
35 
36 #if (FWDGT_INITIAL_TIMEOUT <= 0)
37 #error Must be initial-timeout > 0
38 #elif (FWDGT_INITIAL_TIMEOUT >                                                 \
39 	(FWDGT_PRESCALER_MAX * FWDGT_RELOAD_MAX * MSEC_PER_SEC /               \
40 	CONFIG_GD32_LOW_SPEED_IRC_FREQUENCY))
41 #error Must be initial-timeout <= (256 * 4095 * 1000 / GD32_LOW_SPEED_IRC_FREQUENCY)
42 #endif
43 
44 /**
45  * @brief Calculates FWDGT config value from timeout.
46  *
47  * @param timeout Timeout value in milliseconds.
48  * @param prescaler Pointer to the storage of prescaler value.
49  * @param reload Pointer to the storage of reload value.
50  *
51  * @return 0 on success, -EINVAL if the timeout is out of range
52  */
gd32_fwdgt_calc_timeout(uint32_t timeout,uint32_t * prescaler,uint32_t * reload)53 static int gd32_fwdgt_calc_timeout(uint32_t timeout, uint32_t *prescaler,
54 				   uint32_t *reload)
55 {
56 	uint16_t divider = 4U;
57 	uint8_t shift = 0U;
58 	uint32_t ticks = (uint64_t)CONFIG_GD32_LOW_SPEED_IRC_FREQUENCY *
59 			 timeout / MSEC_PER_SEC;
60 
61 	while ((ticks / divider) > FWDGT_RELOAD_MAX) {
62 		shift++;
63 		divider = 4U << shift;
64 	}
65 
66 	if (!IS_VALID_FWDGT_PRESCALER(PSC_PSC(shift)) || timeout == 0U) {
67 		return -EINVAL;
68 	}
69 
70 	/* convert the 'shift' to prescaler value */
71 	*prescaler = PSC_PSC(shift);
72 	*reload = (ticks / divider) - 1U;
73 
74 	return 0;
75 }
76 
gd32_fwdgt_setup(const struct device * dev,uint8_t options)77 static int gd32_fwdgt_setup(const struct device *dev, uint8_t options)
78 {
79 	ARG_UNUSED(dev);
80 
81 	if ((options & WDT_OPT_PAUSE_HALTED_BY_DBG) != 0U) {
82 #if CONFIG_GD32_DBG_SUPPORT
83 		dbg_periph_enable(DBG_FWDGT_HOLD);
84 #else
85 		LOG_ERR("Debug support not enabled");
86 		return -ENOTSUP;
87 #endif
88 	}
89 
90 	if ((options & WDT_OPT_PAUSE_IN_SLEEP) != 0U) {
91 		LOG_ERR("WDT_OPT_PAUSE_IN_SLEEP not supported");
92 		return -ENOTSUP;
93 	}
94 
95 	fwdgt_enable();
96 
97 	return 0;
98 }
99 
gd32_fwdgt_disable(const struct device * dev)100 static int gd32_fwdgt_disable(const struct device *dev)
101 {
102 	/* watchdog cannot be stopped once started */
103 	ARG_UNUSED(dev);
104 
105 	return -EPERM;
106 }
107 
gd32_fwdgt_install_timeout(const struct device * dev,const struct wdt_timeout_cfg * config)108 static int gd32_fwdgt_install_timeout(const struct device *dev,
109 				      const struct wdt_timeout_cfg *config)
110 {
111 	uint32_t prescaler = 0U;
112 	uint32_t reload = 0U;
113 	ErrStatus errstat = ERROR;
114 
115 	/* Callback is not supported by FWDGT */
116 	if (config->callback != NULL) {
117 		LOG_ERR("callback not supported by FWDGT");
118 		return -ENOTSUP;
119 	}
120 
121 	/* Calculate prescaler and reload value from timeout value */
122 	if (gd32_fwdgt_calc_timeout(config->window.max, &prescaler,
123 				    &reload) != 0) {
124 		LOG_ERR("window max is out of range");
125 		return -EINVAL;
126 	}
127 
128 	/* Configure and run FWDGT */
129 	fwdgt_write_enable();
130 	errstat = fwdgt_config(reload, prescaler);
131 	if (errstat != SUCCESS) {
132 		LOG_ERR("fwdgt_config() failed: %d", errstat);
133 		return -EINVAL;
134 	}
135 	fwdgt_write_disable();
136 
137 	return 0;
138 }
139 
gd32_fwdgt_feed(const struct device * dev,int channel_id)140 static int gd32_fwdgt_feed(const struct device *dev, int channel_id)
141 {
142 	ARG_UNUSED(channel_id);
143 
144 	fwdgt_counter_reload();
145 
146 	return 0;
147 }
148 
149 static const struct wdt_driver_api fwdgt_gd32_api = {
150 	.setup = gd32_fwdgt_setup,
151 	.disable = gd32_fwdgt_disable,
152 	.install_timeout = gd32_fwdgt_install_timeout,
153 	.feed = gd32_fwdgt_feed,
154 };
155 
gd32_fwdgt_init(const struct device * dev)156 static int gd32_fwdgt_init(const struct device *dev)
157 {
158 	int ret = 0;
159 
160 	/* Turn on and wait stabilize system clock oscillator. */
161 	rcu_osci_on(RCU_IRC_LOW_SPEED);
162 	while (!rcu_osci_stab_wait(RCU_IRC_LOW_SPEED)) {
163 	}
164 
165 #if !defined(CONFIG_WDT_DISABLE_AT_BOOT)
166 	const struct wdt_timeout_cfg config = {
167 		.window.max = FWDGT_INITIAL_TIMEOUT
168 	};
169 
170 	ret = gd32_fwdgt_install_timeout(dev, &config);
171 #endif
172 
173 	return ret;
174 }
175 
176 DEVICE_DT_INST_DEFINE(0, gd32_fwdgt_init, NULL, NULL, NULL, POST_KERNEL,
177 		      CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &fwdgt_gd32_api);
178