1 /*
2 * Copyright (C) 2020 Katsuhiro Suzuki
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 /**
8 * @brief Watchdog (WDT) Driver for SiFive Freedom
9 */
10
11 #define DT_DRV_COMPAT sifive_wdt
12
13 #include <zephyr/kernel.h>
14 #include <soc.h>
15 #include <zephyr/drivers/watchdog.h>
16
17 #define LOG_LEVEL CONFIG_WDT_LOG_LEVEL
18 #include <zephyr/logging/log.h>
19 #include <zephyr/irq.h>
20 LOG_MODULE_REGISTER(wdt_sifive);
21
22 #define WDOGCFG_SCALE_MAX 0xf
23 #define WDOGCFG_SCALE_SHIFT 0
24 #define WDOGCFG_SCALE_MASK (WDOGCFG_SCALE_MAX << WDOGCFG_SCALE_SHIFT)
25 #define WDOGCFG_RSTEN BIT(8)
26 #define WDOGCFG_ZEROCMP BIT(9)
27 #define WDOGCFG_ENALWAYS BIT(12)
28 #define WDOGCFG_COREAWAKE BIT(13)
29 #define WDOGCFG_IP0 BIT(28)
30
31 #define WDOGCMP_MAX 0xffff
32
33 #define WDOG_KEY 0x51f15e
34 #define WDOG_FEED 0xd09f00d
35
36 #define WDOG_CLK 32768
37
38 struct wdt_sifive_reg {
39 /* offset: 0x000 */
40 uint32_t wdogcfg;
41 uint32_t dummy0;
42 uint32_t wdogcount;
43 uint32_t dummy1;
44 /* offset: 0x010 */
45 uint32_t wdogs;
46 uint32_t dummy2;
47 uint32_t wdogfeed;
48 uint32_t wdogkey;
49 /* offset: 0x020 */
50 uint32_t wdogcmp0;
51 };
52
53 struct wdt_sifive_device_config {
54 uintptr_t regs;
55 };
56
57 struct wdt_sifive_dev_data {
58 wdt_callback_t cb;
59 bool enable_cb;
60 bool timeout_valid;
61 };
62
63 #define DEV_REG(dev) \
64 ((struct wdt_sifive_reg *) \
65 ((const struct wdt_sifive_device_config *const)(dev)->config)->regs)
66
67 /**
68 * @brief Set maximum length of timeout to watchdog
69 *
70 * @param dev Watchdog device struct
71 */
wdt_sifive_set_max_timeout(const struct device * dev)72 static void wdt_sifive_set_max_timeout(const struct device *dev)
73 {
74 volatile struct wdt_sifive_reg *wdt = DEV_REG(dev);
75 uint32_t t;
76
77 t = wdt->wdogcfg;
78 t |= WDOGCFG_SCALE_MASK;
79
80 wdt->wdogkey = WDOG_KEY;
81 wdt->wdogcfg = t;
82 wdt->wdogkey = WDOG_KEY;
83 wdt->wdogcmp0 = WDOGCMP_MAX;
84 }
85
wdt_sifive_isr(const struct device * dev)86 static void wdt_sifive_isr(const struct device *dev)
87 {
88 volatile struct wdt_sifive_reg *wdt = DEV_REG(dev);
89 struct wdt_sifive_dev_data *data = dev->data;
90 uint32_t t;
91
92 wdt_sifive_set_max_timeout(dev);
93
94 t = wdt->wdogcfg;
95 t &= ~WDOGCFG_IP0;
96
97 wdt->wdogkey = WDOG_KEY;
98 wdt->wdogcfg = t;
99
100 if (data->enable_cb && data->cb) {
101 data->enable_cb = false;
102 data->cb(dev, 0);
103 }
104 }
105
wdt_sifive_disable(const struct device * dev)106 static int wdt_sifive_disable(const struct device *dev)
107 {
108 struct wdt_sifive_dev_data *data = dev->data;
109
110 wdt_sifive_set_max_timeout(dev);
111
112 data->enable_cb = false;
113
114 return 0;
115 }
116
wdt_sifive_setup(const struct device * dev,uint8_t options)117 static int wdt_sifive_setup(const struct device *dev, uint8_t options)
118 {
119 volatile struct wdt_sifive_reg *wdt = DEV_REG(dev);
120 struct wdt_sifive_dev_data *data = dev->data;
121 uint32_t t, mode;
122
123 if (!data->timeout_valid) {
124 LOG_ERR("No valid timeouts installed");
125 return -EINVAL;
126 }
127
128 mode = WDOGCFG_ENALWAYS;
129 if ((options & WDT_OPT_PAUSE_IN_SLEEP) ==
130 WDT_OPT_PAUSE_IN_SLEEP) {
131 mode = WDOGCFG_COREAWAKE;
132 }
133 if ((options & WDT_OPT_PAUSE_HALTED_BY_DBG) ==
134 WDT_OPT_PAUSE_HALTED_BY_DBG) {
135 mode = WDOGCFG_COREAWAKE;
136 }
137
138 t = wdt->wdogcfg;
139 t &= ~(WDOGCFG_ENALWAYS | WDOGCFG_COREAWAKE);
140 t |= mode;
141
142 wdt->wdogkey = WDOG_KEY;
143 wdt->wdogcfg = t;
144
145 return 0;
146 }
147
148 /**
149 * @brief Calculates the watchdog counter value (wdogcmp0) and
150 * scaler (wdogscale) to be installed in the watchdog timer
151 *
152 * @param timeout Timeout value in milliseconds.
153 * @param clk Clock of watchdog in Hz.
154 * @param scaler Pointer to return scaler power of 2
155 *
156 * @return Watchdog counter value
157 */
wdt_sifive_convtime(uint32_t timeout,int clk,int * scaler)158 static int wdt_sifive_convtime(uint32_t timeout, int clk, int *scaler)
159 {
160 uint64_t cnt;
161 int i;
162
163 cnt = (uint64_t)timeout * clk / 1000;
164 for (i = 0; i < 16; i++) {
165 if (cnt <= WDOGCMP_MAX) {
166 break;
167 }
168
169 cnt >>= 1;
170 }
171
172 if (i == 16) {
173 /* Maximum counter and scaler */
174 LOG_ERR("Invalid timeout value allowed range");
175
176 *scaler = WDOGCFG_SCALE_MAX;
177 return WDOGCMP_MAX;
178 }
179
180 *scaler = i;
181
182 return cnt;
183 }
184
wdt_sifive_install_timeout(const struct device * dev,const struct wdt_timeout_cfg * cfg)185 static int wdt_sifive_install_timeout(const struct device *dev,
186 const struct wdt_timeout_cfg *cfg)
187 {
188 volatile struct wdt_sifive_reg *wdt = DEV_REG(dev);
189 struct wdt_sifive_dev_data *data = dev->data;
190 uint32_t mode = 0, t;
191 int cmp, scaler;
192
193 if (data->timeout_valid) {
194 LOG_ERR("No more timeouts can be installed");
195 return -ENOMEM;
196 }
197 if (cfg->window.min != 0U || cfg->window.max == 0U) {
198 return -EINVAL;
199 }
200
201 /*
202 * Freedom watchdog does not support window timeout config.
203 * So use max field of window.
204 */
205 cmp = wdt_sifive_convtime(cfg->window.max, WDOG_CLK, &scaler);
206 if (cmp < 0 || WDOGCMP_MAX < cmp) {
207 LOG_ERR("Unsupported watchdog timeout\n");
208 return -EINVAL;
209 }
210
211 switch (cfg->flags) {
212 case WDT_FLAG_RESET_SOC:
213 /* WDT supports global SoC reset but cannot callback. */
214 mode = WDOGCFG_RSTEN | WDOGCFG_ZEROCMP;
215 break;
216 case WDT_FLAG_RESET_NONE:
217 /* No reset */
218 mode = WDOGCFG_ZEROCMP;
219 break;
220 case WDT_FLAG_RESET_CPU_CORE:
221 default:
222 LOG_ERR("Unsupported watchdog config flags\n");
223
224 wdt_sifive_disable(dev);
225 return -ENOTSUP;
226 }
227
228 t = wdt->wdogcfg;
229 t &= ~(WDOGCFG_RSTEN | WDOGCFG_ZEROCMP | WDOGCFG_SCALE_MASK);
230 t |= mode | scaler;
231
232 wdt->wdogkey = WDOG_KEY;
233 wdt->wdogcfg = t;
234 wdt->wdogkey = WDOG_KEY;
235 wdt->wdogcmp0 = cmp;
236
237 data->cb = cfg->callback;
238 data->enable_cb = true;
239 data->timeout_valid = true;
240
241 return 0;
242 }
243
wdt_sifive_feed(const struct device * dev,int channel_id)244 static int wdt_sifive_feed(const struct device *dev, int channel_id)
245 {
246 volatile struct wdt_sifive_reg *wdt = DEV_REG(dev);
247
248 wdt->wdogkey = WDOG_KEY;
249 wdt->wdogfeed = WDOG_FEED;
250
251 return 0;
252 }
253
254 static DEVICE_API(wdt, wdt_sifive_api) = {
255 .setup = wdt_sifive_setup,
256 .disable = wdt_sifive_disable,
257 .install_timeout = wdt_sifive_install_timeout,
258 .feed = wdt_sifive_feed,
259 };
260
wdt_sifive_irq_config(void)261 static void wdt_sifive_irq_config(void)
262 {
263 IRQ_CONNECT(DT_INST_IRQN(0),
264 DT_INST_IRQ(0, priority), wdt_sifive_isr,
265 DEVICE_DT_INST_GET(0), 0);
266 irq_enable(DT_INST_IRQN(0));
267 }
268
wdt_sifive_init(const struct device * dev)269 static int wdt_sifive_init(const struct device *dev)
270 {
271 #ifdef CONFIG_WDT_DISABLE_AT_BOOT
272 wdt_sifive_disable(dev);
273 #endif
274 wdt_sifive_irq_config();
275
276 return 0;
277 }
278
279 static struct wdt_sifive_dev_data wdt_sifive_data;
280
281 static const struct wdt_sifive_device_config wdt_sifive_cfg = {
282 .regs = DT_INST_REG_ADDR(0),
283 };
284
285 DEVICE_DT_INST_DEFINE(0, wdt_sifive_init, NULL,
286 &wdt_sifive_data, &wdt_sifive_cfg, PRE_KERNEL_1,
287 CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &wdt_sifive_api);
288