]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/dev/dwwdt/dwwdt.c
NgATM: deprecate
[FreeBSD/FreeBSD.git] / sys / dev / dwwdt / dwwdt.c
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
97 /* Setting this to true disables full restart mode. */
98 static bool dwwdt_prevent_restart = false;
99 SYSCTL_BOOL(_dev_dwwdt, OID_AUTO, prevent_restart, CTLFLAG_RW | CTLFLAG_MPSAFE,
100     &dwwdt_prevent_restart, 0, "Disable system reset on timeout");
101
102 static bool dwwdt_debug_enabled = false;
103 SYSCTL_BOOL(_dev_dwwdt, OID_AUTO, debug, CTLFLAG_RW | CTLFLAG_MPSAFE,
104     &dwwdt_debug_enabled, 0, "Enable debug mode");
105
106 static bool dwwdt_panic_first = true;
107 SYSCTL_BOOL(_dev_dwwdt, OID_AUTO, panic_first, CTLFLAG_RW | CTLFLAG_MPSAFE,
108     &dwwdt_panic_first, 0,
109     "Try to panic on timeout, reset on another timeout");
110
111 static int dwwdt_probe(device_t);
112 static int dwwdt_attach(device_t);
113 static int dwwdt_detach(device_t);
114 static int dwwdt_shutdown(device_t);
115
116 static void dwwdt_intr(void *);
117 static void dwwdt_event(void *, unsigned int, int *);
118
119 /* Helpers */
120 static inline void dwwdt_start(struct dwwdt_softc *sc);
121 static inline bool dwwdt_started(const struct dwwdt_softc *sc);
122 static inline void dwwdt_stop(struct dwwdt_softc *sc);
123 static inline void dwwdt_set_timeout(const struct dwwdt_softc *sc, int val);
124
125 static void dwwdt_debug(device_t);
126
127 static void
128 dwwdt_debug(device_t dev)
129 {
130         /*
131          * Reading from EOI may clear interrupt flag.
132          */
133         const struct dwwdt_softc *sc = device_get_softc(dev);
134
135         device_printf(dev, "Registers dump: \n");
136         device_printf(dev, "  CR:   %08x\n", DWWDT_READ4(sc, DWWDT_CR));
137         device_printf(dev, "  CCVR: %08x\n", DWWDT_READ4(sc, DWWDT_CCVR));
138         device_printf(dev, "  CRR:  %08x\n", DWWDT_READ4(sc, DWWDT_CRR));
139         device_printf(dev, "  STAT: %08x\n", DWWDT_READ4(sc, DWWDT_STAT));
140
141         device_printf(dev, "Clock: %s\n", clk_get_name(sc->sc_clk));
142         device_printf(dev, "  FREQ: %lu\n", sc->sc_clk_freq);
143 }
144
145 static inline bool
146 dwwdt_started(const struct dwwdt_softc *sc)
147 {
148
149         /* CR_WDT_E bit can be clear only by full CPU reset. */
150         return ((DWWDT_READ4(sc, DWWDT_CR) & DWWDT_CR_WDT_EN) != 0);
151 }
152
153 static void inline
154 dwwdt_start(struct dwwdt_softc *sc)
155 {
156         uint32_t val;
157
158         /* Enable watchdog */
159         val = DWWDT_READ4(sc, DWWDT_CR);
160         val |= DWWDT_CR_WDT_EN | DWWDT_CR_RESP_MODE;
161         DWWDT_WRITE4(sc, DWWDT_CR, val);
162         sc->sc_status = DWWDT_RUNNING;
163 }
164
165 static void inline
166 dwwdt_stop(struct dwwdt_softc *sc)
167 {
168
169         sc->sc_status = DWWDT_STOPPED;
170         dwwdt_set_timeout(sc, 0x0f);
171 }
172
173 static void inline
174 dwwdt_set_timeout(const struct dwwdt_softc *sc, int val)
175 {
176
177         DWWDT_WRITE4(sc, DWWDT_TORR, val);
178         DWWDT_WRITE4(sc, DWWDT_CRR, DWWDT_CRR_KICK);
179 }
180
181 static void
182 dwwdt_intr(void *arg)
183 {
184         struct dwwdt_softc *sc = arg;
185
186         KASSERT((DWWDT_READ4(sc, DWWDT_STAT) & DWWDT_STAT_STATUS) != 0,
187             ("Missing interrupt status bit?"));
188
189         if (dwwdt_prevent_restart || sc->sc_status == DWWDT_STOPPED) {
190                 /*
191                  * Confirm interrupt reception. Restart counter.
192                  * This also emulates stopping watchdog.
193                  */
194                 (void)DWWDT_READ4(sc, DWWDT_EOI);
195                 return;
196         }
197
198         if (dwwdt_panic_first)
199                 panic("dwwdt pre-timeout interrupt");
200 }
201
202 static void
203 dwwdt_event(void *arg, unsigned int cmd, int *error)
204 {
205         struct dwwdt_softc *sc = arg;
206         const int exponent = flsl(sc->sc_clk_freq);
207         int timeout;
208         int val;
209
210         timeout = cmd & WD_INTERVAL;
211         val = MAX(0, timeout + exponent - DWWDT_EXP_OFFSET + 1);
212
213         dwwdt_stop(sc);
214         if (cmd == 0 || val > 0x0f) {
215                 /*
216                  * Set maximum time between interrupts and Leave watchdog
217                  * disabled.
218                  */
219                 return;
220         }
221
222         dwwdt_set_timeout(sc, val);
223         dwwdt_start(sc);
224         *error = 0;
225
226         if (dwwdt_debug_enabled)
227                 dwwdt_debug(sc->sc_dev);
228 }
229
230 static int
231 dwwdt_probe(device_t dev)
232 {
233         if (!ofw_bus_status_okay(dev))
234                 return (ENXIO);
235
236         if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data)
237                 return (ENXIO);
238
239         device_set_desc(dev, "Synopsys Designware watchdog timer");
240         return (BUS_PROBE_DEFAULT);
241 }
242
243 static int
244 dwwdt_attach(device_t dev)
245 {
246         struct dwwdt_softc *sc;
247
248         sc = device_get_softc(dev);
249         sc->sc_dev = dev;
250
251         sc->sc_mem_rid = 0;
252         sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
253             &sc->sc_mem_rid, RF_ACTIVE);
254         if (sc->sc_mem_res == NULL) {
255                 device_printf(dev, "cannot allocate memory resource\n");
256                 goto err_no_mem;
257         }
258
259         sc->sc_irq_rid = 0;
260         sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ,
261             &sc->sc_irq_rid, RF_ACTIVE);
262         if (sc->sc_irq_res == NULL) {
263                 device_printf(dev, "cannot allocate ireq resource\n");
264                 goto err_no_irq;
265         }
266
267         sc->sc_intr_cookie = NULL;
268         if (bus_setup_intr(dev, sc->sc_irq_res, INTR_MPSAFE | INTR_TYPE_MISC,
269             NULL, dwwdt_intr, sc, &sc->sc_intr_cookie) != 0) {
270                 device_printf(dev, "cannot setup interrupt routine\n");
271                 goto err_no_intr;
272         }
273
274         if (clk_get_by_ofw_index(dev, 0, 0, &sc->sc_clk) != 0) {
275                 device_printf(dev, "cannot find clock\n");
276                 goto err_no_clock;
277         }
278
279         if (clk_enable(sc->sc_clk) != 0) {
280                 device_printf(dev, "cannot enable clock\n");
281                 goto err_no_freq;
282         }
283
284         if (clk_get_freq(sc->sc_clk, &sc->sc_clk_freq) != 0) {
285                 device_printf(dev, "cannot get clock frequency\n");
286                 goto err_no_freq;
287         }
288
289         if (sc->sc_clk_freq == 0UL)
290                 goto err_no_freq;
291
292         sc->sc_evtag = EVENTHANDLER_REGISTER(watchdog_list, dwwdt_event, sc, 0);
293         sc->sc_status = DWWDT_STOPPED;
294
295         return (bus_generic_attach(dev));
296
297 err_no_freq:
298         clk_release(sc->sc_clk);
299 err_no_clock:
300         bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_intr_cookie);
301 err_no_intr:
302         bus_release_resource(dev, SYS_RES_IRQ, sc->sc_irq_rid, sc->sc_irq_res);
303 err_no_irq:
304         bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_mem_rid,
305             sc->sc_mem_res);
306 err_no_mem:
307         return (ENXIO);
308 }
309
310 static int
311 dwwdt_detach(device_t dev)
312 {
313         struct dwwdt_softc *sc = device_get_softc(dev);
314
315         if (dwwdt_started(sc)) {
316                 /*
317                  * Once started it cannot be stopped. Prevent module unload
318                  * instead.
319                  */
320                 return (EBUSY);
321         }
322
323         EVENTHANDLER_DEREGISTER(watchdog_list, sc->sc_evtag);
324         sc->sc_evtag = NULL;
325
326         if (sc->sc_clk != NULL)
327                 clk_release(sc->sc_clk);
328
329         if (sc->sc_intr_cookie != NULL)
330                 bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_intr_cookie);
331
332         if (sc->sc_irq_res) {
333                 bus_release_resource(dev, SYS_RES_IRQ, sc->sc_irq_rid,
334                     sc->sc_irq_res);
335         }
336
337         if (sc->sc_mem_res) {
338                 bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_mem_rid,
339                     sc->sc_mem_res);
340         }
341
342         return (bus_generic_detach(dev));
343 }
344
345 static int
346 dwwdt_shutdown(device_t dev)
347 {
348         struct dwwdt_softc *sc;
349
350         sc = device_get_softc(dev);
351
352         /* Prevent restarts during shutdown. */
353         dwwdt_prevent_restart = true;
354         dwwdt_stop(sc);
355         return (bus_generic_shutdown(dev));
356 }
357
358 static device_method_t dwwdt_methods[] = {
359         DEVMETHOD(device_probe, dwwdt_probe),
360         DEVMETHOD(device_attach, dwwdt_attach),
361         DEVMETHOD(device_detach, dwwdt_detach),
362         DEVMETHOD(device_shutdown, dwwdt_shutdown),
363
364         {0, 0}
365 };
366
367 static driver_t dwwdt_driver = {
368         "dwwdt",
369         dwwdt_methods,
370         sizeof(struct dwwdt_softc),
371 };
372
373 DRIVER_MODULE(dwwdt, simplebus, dwwdt_driver, dwwdt_devclass, NULL, NULL);
374 MODULE_VERSION(dwwdt, 1);
375 OFWBUS_PNP_INFO(compat_data);