jh7110_wdt.c revision 1.1 1 /* $NetBSD: jh7110_wdt.c,v 1.1 2026/08/25 20:46:17 riz Exp $ */
2
3 /*-
4 * Copyright (c) 2026 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Jeffrey C. Rizzo
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 #include <sys/cdefs.h>
33 __KERNEL_RCSID(0, "$NetBSD: jh7110_wdt.c,v 1.1 2026/08/25 20:46:17 riz Exp $");
34
35 #include <sys/param.h>
36 #include <sys/bus.h>
37 #include <sys/device.h>
38 #include <sys/systm.h>
39 #include <sys/wdog.h>
40
41 #include <dev/sysmon/sysmonvar.h>
42
43 #include <dev/fdt/fdtvar.h>
44
45 /*
46 * The JH7110 WDT appears to be a clone of the ARM SP805 watchdog module.
47 *
48 * Register definitions and operating principles were taken from the
49 * ARM Watchdog Module (SP805) Technical Reference Manual, retrieved from
50 * https://community.arm.com/cfs-file/__key/communityserver-discussions-components-files/468/DDI0270-_2800_4_2900_-2-_2800_1_2900_.pdf
51 *
52 */
53
54 #define JH7110_WDT_PERIOD_DEFAULT 15
55
56 #define JH7110_WDT_LOAD 0x00
57 #define JH7110_WDT_VALUE 0x04
58 #define JH7110_WDT_CONTROL 0x08
59 #define JH7110_WDT_CONTROL_INTEN __BIT(0)
60 #define JH7110_WDT_CONTROL_RESEN __BIT(1)
61
62 #define JH7110_WDT_INTCLR 0x0c
63 #define JH7110_WDT_MIS 0x14
64
65 #define JH7110_WDT_LOCK 0xc00
66 #define JH7110_WDT_UNLOCK_KEY 0x1ACCE551
67
68 #define JH7110_WDT_PERIPHID0 0xfe0
69 #define JH7110_WDT_PERIPHID1 0xfe4
70 #define JH7110_WDT_PERIPHID2 0xfe8
71 #define JH7110_WDT_PERIPHID3 0xfec
72 #define JH7110_WDT_PCELLID0 0xff0
73 #define JH7110_WDT_PCELLID1 0xff4
74 #define JH7110_WDT_PCELLID2 0xff8
75 #define JH7110_WDT_PCELLID3 0xffc
76
77 enum {
78 JH7110_WDT_CLK_APB = 0,
79 JH7110_WDT_CLK_CORE = 1,
80 };
81
82 static const struct device_compatible_entry compat_data[] = {
83 { .compat = "starfive,jh7110-wdt" },
84 DEVICE_COMPAT_EOL
85 };
86
87 struct jh7110_wdt_softc {
88 device_t sc_dev;
89 bus_space_tag_t sc_bst;
90 bus_space_handle_t sc_bsh;
91
92 struct sysmon_wdog sc_smw;
93 u_int sc_rate;
94 };
95
96 static inline uint32_t
97 jh7110_wdt_read(struct jh7110_wdt_softc *sc, bus_size_t reg)
98 {
99 return bus_space_read_4(sc->sc_bst, sc->sc_bsh, reg);
100 }
101
102 static inline void
103 jh7110_wdt_write(struct jh7110_wdt_softc *sc, bus_size_t reg, uint32_t val)
104 {
105 bus_space_write_4(sc->sc_bst, sc->sc_bsh, reg, val);
106 }
107
108 static void
109 jh7110_wdt_stop(struct jh7110_wdt_softc *sc)
110 {
111 jh7110_wdt_write(sc, JH7110_WDT_LOCK, JH7110_WDT_UNLOCK_KEY);
112
113 uint32_t val = jh7110_wdt_read(sc, JH7110_WDT_CONTROL);
114 jh7110_wdt_write(sc, JH7110_WDT_CONTROL, val & ~JH7110_WDT_CONTROL_INTEN);
115
116 jh7110_wdt_write(sc, JH7110_WDT_LOCK, 1);
117 }
118
119 static void
120 jh7110_wdt_start(struct jh7110_wdt_softc *sc, uint32_t count)
121 {
122 jh7110_wdt_write(sc, JH7110_WDT_LOCK, JH7110_WDT_UNLOCK_KEY);
123
124 uint32_t val = jh7110_wdt_read(sc, JH7110_WDT_CONTROL);
125 jh7110_wdt_write(sc, JH7110_WDT_CONTROL, val & ~JH7110_WDT_CONTROL_INTEN);
126 val = jh7110_wdt_read(sc, JH7110_WDT_CONTROL);
127 jh7110_wdt_write(sc, JH7110_WDT_CONTROL, val | JH7110_WDT_CONTROL_RESEN);
128 jh7110_wdt_write(sc, JH7110_WDT_INTCLR, 1);
129 jh7110_wdt_write(sc, JH7110_WDT_LOAD, count);
130 val = jh7110_wdt_read(sc, JH7110_WDT_CONTROL);
131 jh7110_wdt_write(sc, JH7110_WDT_CONTROL, val | JH7110_WDT_CONTROL_INTEN);
132
133 jh7110_wdt_write(sc, JH7110_WDT_LOCK, 1);
134 }
135
136 static int
137 jh7110_wdt_setmode(struct sysmon_wdog *smw)
138 {
139 struct jh7110_wdt_softc * const sc = smw->smw_cookie;
140
141 if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) {
142 jh7110_wdt_stop(sc);
143 return 0;
144 }
145
146 if (smw->smw_period == WDOG_PERIOD_DEFAULT)
147 sc->sc_smw.smw_period = JH7110_WDT_PERIOD_DEFAULT;
148 else
149 sc->sc_smw.smw_period = smw->smw_period;
150
151 if (sc->sc_smw.smw_period == 0) {
152 jh7110_wdt_stop(sc);
153 } else {
154 /* wdt counts down twice before resetting, so halve rate */
155 uint32_t counter = sc->sc_smw.smw_period * sc->sc_rate / 2;
156 jh7110_wdt_start(sc, (uint32_t)counter);
157 }
158
159 return 0;
160 }
161
162 static int
163 jh7110_wdt_tickle(struct sysmon_wdog *smw)
164 {
165 struct jh7110_wdt_softc * const sc = smw->smw_cookie;
166
167 jh7110_wdt_write(sc, JH7110_WDT_LOCK, JH7110_WDT_UNLOCK_KEY);
168
169 jh7110_wdt_write(sc, JH7110_WDT_INTCLR, 1);
170
171 jh7110_wdt_write(sc, JH7110_WDT_LOCK, 1);
172 return 0;
173 }
174
175 static int
176 jh7110_wdt_match(device_t parent, cfdata_t cf, void *aux)
177 {
178 struct fdt_attach_args * const faa = aux;
179
180 return of_compatible_match(faa->faa_phandle, compat_data);
181 }
182
183 static void
184 jh7110_wdt_attach(device_t parent, device_t self, void *aux)
185 {
186 struct jh7110_wdt_softc * const sc = device_private(self);
187 struct fdt_attach_args * const faa = aux;
188 const int phandle = faa->faa_phandle;
189 bus_addr_t addr;
190 bus_size_t size;
191 int error;
192
193 if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
194 aprint_error(": couldn't get registers\n");
195 return;
196 }
197
198 sc->sc_dev = self;
199 sc->sc_bst = faa->faa_bst;
200 error = bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh);
201 if (error) {
202 aprint_error(": couldn't map %#" PRIxBUSADDR ": %d", addr,
203 error);
204 return;
205 }
206
207 struct {
208 const char *jwc_name;
209 u_int jwc_rate;
210 } jh7110_wdt_clk[] = {
211 [JH7110_WDT_CLK_APB] = {
212 .jwc_name = "apb",
213 },
214 [JH7110_WDT_CLK_CORE] = {
215 .jwc_name = "core",
216 },
217 };
218 for (size_t i = 0; i < __arraycount(jh7110_wdt_clk); i++) {
219 const char *cr = jh7110_wdt_clk[i].jwc_name;
220
221 struct clk *clk = fdtbus_clock_get(phandle, cr);
222 if (clk == NULL) {
223 aprint_error(": couldn't get clock '%s'\n", cr);
224 return;
225 }
226 error = clk_enable(clk);
227 if (error) {
228 aprint_error(": couldn't enable clock '%s'\n", cr);
229 return;
230 }
231 u_int rate = clk_get_rate(clk);
232 if (rate == 0) {
233 aprint_error(": couldn't get rate for clock '%s'\n", cr);
234 return;
235 }
236 jh7110_wdt_clk[i].jwc_rate = rate;
237 }
238
239 /* de-assert resets */
240 struct fdtbus_reset *rst;
241 for (u_int n = 0; (rst = fdtbus_reset_get_index(phandle, n)) != NULL; n++) {
242 if (fdtbus_reset_deassert(rst) != 0) {
243 aprint_error(": couldn't de-assert reset #%d\n", n);
244 return;
245 }
246 }
247
248 sc->sc_rate = jh7110_wdt_clk[JH7110_WDT_CLK_CORE].jwc_rate;
249
250 aprint_naive("\n");
251 aprint_normal(": watchdog\n");
252
253 sc->sc_smw.smw_name = device_xname(self);
254 sc->sc_smw.smw_cookie = sc;
255 sc->sc_smw.smw_setmode = jh7110_wdt_setmode;
256 sc->sc_smw.smw_tickle = jh7110_wdt_tickle;
257 sc->sc_smw.smw_period = JH7110_WDT_PERIOD_DEFAULT;
258 sysmon_wdog_register(&sc->sc_smw);
259 }
260
261 CFATTACH_DECL_NEW(jh7110_wdt, sizeof(struct jh7110_wdt_softc),
262 jh7110_wdt_match, jh7110_wdt_attach, NULL, NULL);
263