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