xref: /freebsd/sys/dev/dwwdt/dwwdt.c (revision cfd6422a5217410fbd66f7a7a8a64d9d85e61229)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2020 BusyTech
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #include <sys/param.h>
29 #include <sys/eventhandler.h>
30 #include <sys/kernel.h>
31 #include <sys/types.h>
32 #include <sys/bus.h>
33 #include <sys/module.h>
34 #include <sys/systm.h>
35 #include <sys/sysctl.h>
36 #include <sys/rman.h>
37 #include <sys/resource.h>
38 #include <sys/watchdog.h>
39 
40 #include <machine/bus.h>
41 #include <machine/resource.h>
42 
43 #include <dev/extres/clk/clk.h>
44 #include <dev/fdt/fdt_common.h>
45 #include <dev/ofw/openfirm.h>
46 #include <dev/ofw/ofw_bus.h>
47 #include <dev/ofw/ofw_bus_subr.h>
48 
49 /* Registers */
50 #define DWWDT_CR		0x00
51 #define DWWDT_CR_WDT_EN		(1 << 0)
52 #define DWWDT_CR_RESP_MODE	(1 << 1)
53 #define DWWDT_TORR		0x04
54 #define DWWDT_CCVR		0x08
55 #define DWWDT_CRR		0x0C
56 #define DWWDT_CRR_KICK		0x76
57 #define DWWDT_STAT		0x10
58 #define DWWDT_STAT_STATUS	0x01
59 #define DWWDT_EOI		0x14
60 
61 #define DWWDT_READ4(sc, reg) 		bus_read_4((sc)->sc_mem_res, (reg))
62 #define DWWDT_WRITE4(sc, reg, val)	\
63 	bus_write_4((sc)->sc_mem_res, (reg), (val))
64 
65 /*
66  * 47 = 16 (timeout shift of dwwdt) + 30 (1s ~= 2 ** 30ns) + 1
67  * (pre-restart delay)
68  */
69 #define DWWDT_EXP_OFFSET	47
70 
71 struct dwwdt_softc {
72 	device_t		 sc_dev;
73 	struct resource		*sc_mem_res;
74 	struct resource		*sc_irq_res;
75 	void			*sc_intr_cookie;
76 	clk_t			 sc_clk;
77 	uint64_t		 sc_clk_freq;
78 	eventhandler_tag	 sc_evtag;
79 	int 			 sc_mem_rid;
80 	int			 sc_irq_rid;
81 	enum {
82 		DWWDT_STOPPED,
83 		DWWDT_RUNNING,
84 	}			 sc_status;
85 };
86 
87 static devclass_t dwwdt_devclass;
88 
89 static struct ofw_compat_data compat_data[] = {
90 	{ "snps,dw-wdt",		1 },
91 	{ NULL,				0 }
92 };
93 
94 SYSCTL_NODE(_dev, OID_AUTO, dwwdt, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
95     "Synopsys Designware watchdog timer");
96 /* Setting this to 0 enables full restart mode. */
97 static uint32_t dwwdt_prevent_restart = 1;
98 SYSCTL_UINT(_dev_dwwdt, OID_AUTO, prevent_restart, CTLFLAG_RW | CTLFLAG_MPSAFE,
99     &dwwdt_prevent_restart, 1,
100     "Prevent system restart (0 - Disabled; 1 - Enabled)");
101 
102 static uint32_t dwwdt_debug_enabled = 0;
103 SYSCTL_UINT(_dev_dwwdt, OID_AUTO, debug, CTLFLAG_RW | CTLFLAG_MPSAFE,
104     &dwwdt_debug_enabled, 1, "Debug mode (0 - Disabled; 1 - Enabled)");
105 
106 static int dwwdt_probe(device_t);
107 static int dwwdt_attach(device_t);
108 static int dwwdt_detach(device_t);
109 static int dwwdt_shutdown(device_t);
110 
111 static void dwwdt_intr(void *);
112 static void dwwdt_event(void *, unsigned int, int *);
113 
114 /* Helpers */
115 static inline void dwwdt_start(struct dwwdt_softc *sc);
116 static inline bool dwwdt_started(const struct dwwdt_softc *sc);
117 static inline void dwwdt_stop(struct dwwdt_softc *sc);
118 static inline void dwwdt_set_timeout(const struct dwwdt_softc *sc, int val);
119 
120 static void dwwdt_debug(device_t);
121 
122 static void
123 dwwdt_debug(device_t dev)
124 {
125 	/*
126 	 * Reading from EOI may clear interrupt flag.
127 	 */
128 	const struct dwwdt_softc *sc = device_get_softc(dev);
129 
130 	device_printf(dev, "Registers dump: \n");
131 	device_printf(dev, "  CR:   %08x\n", DWWDT_READ4(sc, DWWDT_CR));
132 	device_printf(dev, "  CCVR: %08x\n", DWWDT_READ4(sc, DWWDT_CCVR));
133 	device_printf(dev, "  CRR:  %08x\n", DWWDT_READ4(sc, DWWDT_CRR));
134 	device_printf(dev, "  STAT: %08x\n", DWWDT_READ4(sc, DWWDT_STAT));
135 
136 	device_printf(dev, "Clock: %s\n", clk_get_name(sc->sc_clk));
137 	device_printf(dev, "  FREQ: %lu\n", sc->sc_clk_freq);
138 }
139 
140 static inline bool
141 dwwdt_started(const struct dwwdt_softc *sc)
142 {
143 
144 	/* CR_WDT_E bit can be clear only by full CPU reset. */
145 	return ((DWWDT_READ4(sc, DWWDT_CR) & DWWDT_CR_WDT_EN) != 0);
146 }
147 
148 static void inline
149 dwwdt_start(struct dwwdt_softc *sc)
150 {
151 	uint32_t val;
152 
153 	/* Enable watchdog */
154 	val = DWWDT_READ4(sc, DWWDT_CR);
155 	val |= DWWDT_CR_WDT_EN | DWWDT_CR_RESP_MODE;
156 	DWWDT_WRITE4(sc, DWWDT_CR, val);
157 	sc->sc_status = DWWDT_RUNNING;
158 }
159 
160 static void inline
161 dwwdt_stop(struct dwwdt_softc *sc)
162 {
163 
164 	sc->sc_status = DWWDT_STOPPED;
165 	dwwdt_set_timeout(sc, 0x0f);
166 }
167 
168 static void inline
169 dwwdt_set_timeout(const struct dwwdt_softc *sc, int val)
170 {
171 
172 	DWWDT_WRITE4(sc, DWWDT_TORR, val);
173 	DWWDT_WRITE4(sc, DWWDT_CRR, DWWDT_CRR_KICK);
174 }
175 
176 static void
177 dwwdt_intr(void *arg)
178 {
179 	struct dwwdt_softc *sc = arg;
180 
181 	KASSERT((DWWDT_READ4(sc, DWWDT_STAT) & DWWDT_STAT_STATUS) != 0,
182 	    ("Missing interrupt status bit?"));
183 
184 	if (dwwdt_prevent_restart != 0 || sc->sc_status == DWWDT_STOPPED) {
185 		/*
186 		 * Confirm interrupt reception. Restart counter.
187 		 * This also emulates stopping watchdog.
188 		 */
189 		(void)DWWDT_READ4(sc, DWWDT_EOI);
190 	}
191 }
192 
193 static void
194 dwwdt_event(void *arg, unsigned int cmd, int *error)
195 {
196 	struct dwwdt_softc *sc = arg;
197 	const int exponent = flsl(sc->sc_clk_freq);
198 	int timeout;
199 	int val;
200 
201 	timeout = cmd & WD_INTERVAL;
202 	val = MAX(0, timeout + exponent - DWWDT_EXP_OFFSET);
203 
204 	dwwdt_stop(sc);
205 	if (cmd == 0 || val > 0x0f) {
206 		/*
207 		 * Set maximum time between interrupts and Leave watchdog
208 		 * disabled.
209 		 */
210 		return;
211 	}
212 
213 	dwwdt_set_timeout(sc, val);
214 	dwwdt_start(sc);
215 	*error = 0;
216 
217 	if (dwwdt_debug_enabled)
218 		dwwdt_debug(sc->sc_dev);
219 }
220 
221 static int
222 dwwdt_probe(device_t dev)
223 {
224 	if (!ofw_bus_status_okay(dev))
225 		return (ENXIO);
226 
227 	if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data)
228 		return (ENXIO);
229 
230 	device_set_desc(dev, "Synopsys Designware watchdog timer");
231 	return (BUS_PROBE_DEFAULT);
232 }
233 
234 static int
235 dwwdt_attach(device_t dev)
236 {
237 	struct dwwdt_softc *sc;
238 
239 	sc = device_get_softc(dev);
240 	sc->sc_dev = dev;
241 
242 	sc->sc_mem_rid = 0;
243 	sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
244 	    &sc->sc_mem_rid, RF_ACTIVE);
245 	if (sc->sc_mem_res == NULL) {
246 		device_printf(dev, "cannot allocate memory resource\n");
247 		goto err_no_mem;
248 	}
249 
250 	sc->sc_irq_rid = 0;
251 	sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ,
252 	    &sc->sc_irq_rid, RF_ACTIVE);
253 	if (sc->sc_irq_res == NULL) {
254 		device_printf(dev, "cannot allocate ireq resource\n");
255 		goto err_no_irq;
256 	}
257 
258 	sc->sc_intr_cookie = NULL;
259 	if (bus_setup_intr(dev, sc->sc_irq_res, INTR_MPSAFE | INTR_TYPE_MISC,
260 	    NULL, dwwdt_intr, sc, &sc->sc_intr_cookie) != 0) {
261 		device_printf(dev, "cannot setup interrupt routine\n");
262 		goto err_no_intr;
263 	}
264 
265 	if (clk_get_by_ofw_index(dev, 0, 0, &sc->sc_clk) != 0) {
266 		device_printf(dev, "cannot find clock\n");
267 		goto err_no_clock;
268 	}
269 
270 	if (clk_enable(sc->sc_clk) != 0) {
271 		device_printf(dev, "cannot enable clock\n");
272 		goto err_no_freq;
273 	}
274 
275 	if (clk_get_freq(sc->sc_clk, &sc->sc_clk_freq) != 0) {
276 		device_printf(dev, "cannot get clock frequency\n");
277 		goto err_no_freq;
278 	}
279 
280 	if (sc->sc_clk_freq == 0UL)
281 		goto err_no_freq;
282 
283 	sc->sc_evtag = EVENTHANDLER_REGISTER(watchdog_list, dwwdt_event, sc, 0);
284 	sc->sc_status = DWWDT_STOPPED;
285 
286 	return (bus_generic_attach(dev));
287 
288 err_no_freq:
289 	clk_release(sc->sc_clk);
290 err_no_clock:
291 	bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_intr_cookie);
292 err_no_intr:
293 	bus_release_resource(dev, SYS_RES_IRQ, sc->sc_irq_rid, sc->sc_irq_res);
294 err_no_irq:
295 	bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_mem_rid,
296 	    sc->sc_mem_res);
297 err_no_mem:
298 	return (ENXIO);
299 }
300 
301 static int
302 dwwdt_detach(device_t dev)
303 {
304 	struct dwwdt_softc *sc = device_get_softc(dev);
305 
306 	if (dwwdt_started(sc)) {
307 		/*
308 		 * Once started it cannot be stopped. Prevent module unload
309 		 * instead.
310 		 */
311 		return (EBUSY);
312 	}
313 
314 	EVENTHANDLER_DEREGISTER(watchdog_list, sc->sc_evtag);
315 	sc->sc_evtag = NULL;
316 
317 	if (sc->sc_clk != NULL)
318 		clk_release(sc->sc_clk);
319 
320 	if (sc->sc_intr_cookie != NULL)
321 		bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_intr_cookie);
322 
323 	if (sc->sc_irq_res) {
324 		bus_release_resource(dev, SYS_RES_IRQ, sc->sc_irq_rid,
325 		    sc->sc_irq_res);
326 	}
327 
328 	if (sc->sc_mem_res) {
329 		bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_mem_rid,
330 		    sc->sc_mem_res);
331 	}
332 
333 	return (bus_generic_detach(dev));
334 }
335 
336 static int
337 dwwdt_shutdown(device_t dev)
338 {
339 	struct dwwdt_softc *sc;
340 
341 	sc = device_get_softc(dev);
342 
343 	/* Prevent restarts during shutdown. */
344 	dwwdt_prevent_restart = 1;
345 	dwwdt_stop(sc);
346 	return (bus_generic_shutdown(dev));
347 }
348 
349 static device_method_t dwwdt_methods[] = {
350 	DEVMETHOD(device_probe, dwwdt_probe),
351 	DEVMETHOD(device_attach, dwwdt_attach),
352 	DEVMETHOD(device_detach, dwwdt_detach),
353 	DEVMETHOD(device_shutdown, dwwdt_shutdown),
354 
355 	{0, 0}
356 };
357 
358 static driver_t dwwdt_driver = {
359 	"dwwdt",
360 	dwwdt_methods,
361 	sizeof(struct dwwdt_softc),
362 };
363 
364 DRIVER_MODULE(dwwdt, simplebus, dwwdt_driver, dwwdt_devclass, NULL, NULL);
365 MODULE_VERSION(dwwdt, 1);
366 OFWBUS_PNP_INFO(compat_data);
367