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