1 /*
2  * Copyright (c) 2022 TOKITA Hiroshi
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT gd_gd32_wwdgt
8 
9 #include <zephyr/drivers/clock_control.h>
10 #include <zephyr/drivers/clock_control/gd32.h>
11 #include <zephyr/drivers/reset.h>
12 #include <zephyr/drivers/watchdog.h>
13 #include <zephyr/irq.h>
14 #include <zephyr/logging/log.h>
15 #include <zephyr/sys_clock.h>
16 
17 #include <gd32_wwdgt.h>
18 
19 LOG_MODULE_REGISTER(wdt_wwdgt_gd32, CONFIG_WDT_LOG_LEVEL);
20 
21 #define WWDGT_PRESCALER_EXP_MAX (3U)
22 #define WWDGT_COUNTER_MIN	(0x40U)
23 #define WWDGT_COUNTER_MAX	(0x7fU)
24 #define WWDGT_INTERNAL_DIVIDER	(4096ULL)
25 
26 struct gd32_wwdgt_config {
27 	uint16_t clkid;
28 	struct reset_dt_spec reset;
29 };
30 
31 /* mutable driver data */
32 struct gd32_wwdgt_data {
33 	/* counter update value*/
34 	uint8_t counter;
35 	/* user defined callback */
36 	wdt_callback_t callback;
37 };
38 
39 static void gd32_wwdgt_irq_config(const struct device *dev);
40 
41 /**
42  * @param timeout Timeout value in milliseconds.
43  * @param exp exponent part of prescaler
44  *
45  * @return ticks count calculated by this formula.
46  *
47  *  timeout = pclk * INTERNAL_DIVIDER * (2^prescaler_exp) * (count + 1)
48  *  transform as
49  *  count = (timeout * pclk / INTERNAL_DIVIDER * (2^prescaler_exp) ) - 1
50  *
51  *  and add WWDGT_COUNTER_MIN to this as a offset value.
52  */
gd32_wwdgt_calc_ticks(const struct device * dev,uint32_t timeout,uint32_t exp)53 static inline uint32_t gd32_wwdgt_calc_ticks(const struct device *dev,
54 					    uint32_t timeout, uint32_t exp)
55 {
56 	const struct gd32_wwdgt_config *config = dev->config;
57 	uint32_t pclk;
58 
59 	(void)clock_control_get_rate(GD32_CLOCK_CONTROLLER,
60 				       (clock_control_subsys_t)&config->clkid,
61 				       &pclk);
62 
63 	return ((timeout * pclk)
64 		/ (WWDGT_INTERNAL_DIVIDER * (1 << exp) * MSEC_PER_SEC) - 1)
65 		+ WWDGT_COUNTER_MIN;
66 }
67 
68 /**
69  * @brief Calculates WWDGT config value from timeout window.
70  *
71  * @param win Pointer to timeout window struct.
72  * @param counter Pointer to the storage of counter value.
73  * @param wval Pointer to the storage of window value.
74  * @param prescaler Pointer to the storage of prescaler value.
75  *
76  * @return 0 on success, -EINVAL if the window-max is out of range
77  */
gd32_wwdgt_calc_window(const struct device * dev,const struct wdt_window * win,uint32_t * counter,uint32_t * wval,uint32_t * prescaler)78 static int gd32_wwdgt_calc_window(const struct device *dev,
79 				  const struct wdt_window *win,
80 				  uint32_t *counter, uint32_t *wval,
81 				  uint32_t *prescaler)
82 {
83 	for (uint32_t shift = 0U; shift <= WWDGT_PRESCALER_EXP_MAX; shift++) {
84 		uint32_t max_count = gd32_wwdgt_calc_ticks(dev, win->max, shift);
85 
86 		if (max_count <= WWDGT_COUNTER_MAX) {
87 			*counter = max_count;
88 			*prescaler = CFG_PSC(shift);
89 			if (win->min == 0U) {
90 				*wval = max_count;
91 			} else {
92 				*wval = gd32_wwdgt_calc_ticks(dev, win->min, shift);
93 			}
94 
95 			return 0;
96 		}
97 	}
98 
99 	return -EINVAL;
100 }
101 
gd32_wwdgt_setup(const struct device * dev,uint8_t options)102 static int gd32_wwdgt_setup(const struct device *dev, uint8_t options)
103 {
104 	ARG_UNUSED(dev);
105 
106 	if (options & WDT_OPT_PAUSE_HALTED_BY_DBG) {
107 #if CONFIG_GD32_DBG_SUPPORT
108 		dbg_periph_enable(DBG_WWDGT_HOLD);
109 #else
110 		LOG_ERR("Debug support not enabled");
111 		return -ENOTSUP;
112 #endif
113 	}
114 
115 	if (options & WDT_OPT_PAUSE_IN_SLEEP) {
116 		LOG_ERR("WDT_OPT_PAUSE_IN_SLEEP not supported");
117 		return -ENOTSUP;
118 	}
119 
120 	wwdgt_enable();
121 	wwdgt_flag_clear();
122 	wwdgt_interrupt_enable();
123 
124 	return 0;
125 }
126 
gd32_wwdgt_disable(const struct device * dev)127 static int gd32_wwdgt_disable(const struct device *dev)
128 {
129 	/* watchdog cannot be stopped once started */
130 	ARG_UNUSED(dev);
131 
132 	return -EPERM;
133 }
134 
gd32_wwdgt_install_timeout(const struct device * dev,const struct wdt_timeout_cfg * config)135 static int gd32_wwdgt_install_timeout(const struct device *dev,
136 				      const struct wdt_timeout_cfg *config)
137 {
138 	uint32_t prescaler = 0U;
139 	uint32_t counter = 0U;
140 	uint32_t window = 0U;
141 	struct gd32_wwdgt_data *data = dev->data;
142 
143 	if (config->window.max == 0U) {
144 		LOG_ERR("window.max must be non-zero");
145 		return -EINVAL;
146 	}
147 
148 	if (gd32_wwdgt_calc_window(dev, &config->window, &counter, &window,
149 				   &prescaler) != 0) {
150 		LOG_ERR("window.max in out of range");
151 		return -EINVAL;
152 	}
153 
154 	data->callback = config->callback;
155 	data->counter = counter;
156 
157 	wwdgt_config(counter, window, prescaler);
158 
159 	return 0;
160 }
161 
gd32_wwdgt_feed(const struct device * dev,int channel_id)162 static int gd32_wwdgt_feed(const struct device *dev, int channel_id)
163 {
164 	struct gd32_wwdgt_data *data = dev->data;
165 
166 	ARG_UNUSED(channel_id);
167 
168 	wwdgt_counter_update(data->counter);
169 
170 	return 0;
171 }
172 
gd32_wwdgt_isr(const struct device * dev)173 static void gd32_wwdgt_isr(const struct device *dev)
174 {
175 	struct gd32_wwdgt_data *data = dev->data;
176 
177 	if (wwdgt_flag_get() != 0) {
178 		wwdgt_flag_clear();
179 
180 		if (data->callback != NULL) {
181 			data->callback(dev, 0);
182 		}
183 	}
184 }
185 
gd32_wwdgt_irq_config(const struct device * dev)186 static void gd32_wwdgt_irq_config(const struct device *dev)
187 {
188 	ARG_UNUSED(dev);
189 
190 	IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), gd32_wwdgt_isr,
191 		    DEVICE_DT_INST_GET(0), 0);
192 	irq_enable(DT_INST_IRQN(0));
193 }
194 
195 static DEVICE_API(wdt, wwdgt_gd32_api) = {
196 	.setup = gd32_wwdgt_setup,
197 	.disable = gd32_wwdgt_disable,
198 	.install_timeout = gd32_wwdgt_install_timeout,
199 	.feed = gd32_wwdgt_feed,
200 };
201 
gd32_wwdgt_init(const struct device * dev)202 static int gd32_wwdgt_init(const struct device *dev)
203 {
204 	const struct gd32_wwdgt_config *config = dev->config;
205 
206 	(void)clock_control_on(GD32_CLOCK_CONTROLLER,
207 			       (clock_control_subsys_t)&config->clkid);
208 	(void)reset_line_toggle_dt(&config->reset);
209 	gd32_wwdgt_irq_config(dev);
210 
211 	return 0;
212 }
213 
214 static const struct gd32_wwdgt_config wwdgt_cfg = {
215 	.clkid = DT_INST_CLOCKS_CELL(0, id),
216 	.reset = RESET_DT_SPEC_INST_GET(0),
217 };
218 
219 static struct gd32_wwdgt_data wwdgt_data = {
220 	.counter = WWDGT_COUNTER_MIN,
221 	.callback = NULL
222 };
223 
224 DEVICE_DT_INST_DEFINE(0, gd32_wwdgt_init, NULL, &wwdgt_data, &wwdgt_cfg, POST_KERNEL,
225 		      CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &wwdgt_gd32_api);
226