1 /* $NetBSD: sunxi_wdt.c,v 1.7 2026/03/18 06:37:09 skrll Exp $ */ 2 3 /*- 4 * Copyright (c) 2017 Jared McNeill <jmcneill (at) invisible.ca> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29 #include <sys/cdefs.h> 30 __KERNEL_RCSID(0, "$NetBSD: sunxi_wdt.c,v 1.7 2026/03/18 06:37:09 skrll Exp $"); 31 32 #include <sys/param.h> 33 #include <sys/bus.h> 34 #include <sys/device.h> 35 #include <sys/intr.h> 36 #include <sys/systm.h> 37 #include <sys/mutex.h> 38 #include <sys/wdog.h> 39 40 #include <dev/sysmon/sysmonvar.h> 41 42 #include <dev/fdt/fdtvar.h> 43 44 #define SUNXI_WDT_PERIOD_DEFAULT 16 45 46 #define SUN4I_WDT_CTRL_REG 0x00 47 #define SUN4I_WDT_CTRL_KEY_FIELD __BITS(12,1) 48 #define SUN4I_WDT_CTRL_KEY_FIELD_V 0xa57 49 #define SUN4I_WDT_CTRL_RSTART __BIT(0) 50 #define SUN4I_WDT_MODE_REG 0x04 51 #define SUN4I_WDT_MODE_INTV __BITS(6,3) 52 #define SUN4I_WDT_MODE_RST_EN __BIT(1) 53 #define SUN4I_WDT_MODE_EN __BIT(0) 54 55 #define SUN6I_WDT_IRQ_EN_REG 0x00 56 #define SUN6I_WDT_IRQ_EN_EN __BIT(0) 57 #define SUN6I_WDT_IRQ_STA_REG 0x04 58 #define SUN6I_WDT_IRQ_STA_PEND __BIT(0) 59 #define SUN6I_WDT_CTRL_REG 0x10 60 #define SUN6I_WDT_CTRL_KEY_FIELD __BITS(12,1) 61 #define SUN6I_WDT_CTRL_KEY_FIELD_V 0xa57 62 #define SUN6I_WDT_CTRL_RSTART __BIT(0) 63 #define SUN6I_WDT_CFG_REG 0x14 64 #define SUN6I_WDT_CFG_CONFIG __BITS(1,0) 65 #define SUN6I_WDT_CFG_CONFIG_SYS 1 66 #define SUN6I_WDT_CFG_CONFIG_IRQ 2 67 #define SUN6I_WDT_MODE_REG 0x18 68 #define SUN6I_WDT_MODE_INTV __BITS(7,4) 69 #define SUN6I_WDT_MODE_EN __BIT(0) 70 71 #define SUNXI_WDT_KEY_FIELD __BITS(31,16) 72 #define SUNXI_WDT_KEY_FIELD_V 0x16aa 73 74 static const int sunxi_periods[] = { 75 500, 1000, 2000, 3000, 76 4000, 5000, 6000, 8000, 77 10000, 12000, 14000, 16000, 78 -1 79 }; 80 81 enum sunxi_wdt_type { 82 WDT_SUN4I = 1, 83 WDT_SUN6I, 84 WDT_SUN20I, 85 }; 86 87 static const struct device_compatible_entry compat_data[] = { 88 { .compat = "allwinner,sun4i-a10-wdt", .value = WDT_SUN4I }, 89 { .compat = "allwinner,sun6i-a31-wdt", .value = WDT_SUN6I }, 90 { .compat = "allwinner,sun20i-d1-wdt", .value = WDT_SUN20I }, 91 DEVICE_COMPAT_EOL 92 }; 93 94 struct sunxi_wdt_softc { 95 device_t sc_dev; 96 bus_space_tag_t sc_bst; 97 bus_space_handle_t sc_bsh; 98 99 const int *sc_periods; 100 101 struct sysmon_wdog sc_smw; 102 }; 103 104 #define WDT_READ(sc, reg) \ 105 bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg)) 106 #define WDT_WRITE(sc, reg, val) \ 107 bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val)) 108 109 static int 110 sunxi_wdt_map_period(struct sunxi_wdt_softc *sc, u_int period, 111 u_int *aperiod) 112 { 113 const int *p = sc->sc_periods; 114 int i; 115 116 if (period == 0) 117 return -1; 118 119 for (i = 0; *p != -1; i++, p++) 120 if (*p >= period * 1000) { 121 *aperiod = *p / 1000; 122 return i; 123 } 124 125 return -1; 126 } 127 128 static int 129 sun4i_wdt_setmode(struct sysmon_wdog *smw) 130 { 131 struct sunxi_wdt_softc * const sc = smw->smw_cookie; 132 uint32_t mode; 133 int intv; 134 135 if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) { 136 WDT_WRITE(sc, SUN4I_WDT_MODE_REG, 0); 137 } else { 138 if (smw->smw_period == WDOG_PERIOD_DEFAULT) 139 smw->smw_period = SUNXI_WDT_PERIOD_DEFAULT; 140 intv = sunxi_wdt_map_period(sc, smw->smw_period, 141 &sc->sc_smw.smw_period); 142 if (intv == -1) 143 return EINVAL; 144 145 mode = SUN4I_WDT_MODE_EN | SUN4I_WDT_MODE_RST_EN | 146 __SHIFTIN(intv, SUN4I_WDT_MODE_INTV); 147 148 WDT_WRITE(sc, SUN4I_WDT_MODE_REG, mode); 149 } 150 151 return 0; 152 } 153 154 static int 155 sun4i_wdt_tickle(struct sysmon_wdog *smw) 156 { 157 struct sunxi_wdt_softc * const sc = smw->smw_cookie; 158 const uint32_t ctrl = SUN4I_WDT_CTRL_RSTART | 159 __SHIFTIN(SUN4I_WDT_CTRL_KEY_FIELD_V, SUN4I_WDT_CTRL_KEY_FIELD); 160 161 WDT_WRITE(sc, SUN4I_WDT_CTRL_REG, ctrl); 162 163 return 0; 164 } 165 166 static int 167 sun6i_wdt_setmode(struct sysmon_wdog *smw) 168 { 169 struct sunxi_wdt_softc * const sc = smw->smw_cookie; 170 uint32_t cfg, mode; 171 int intv; 172 173 if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) { 174 WDT_WRITE(sc, SUN6I_WDT_MODE_REG, 0); 175 } else { 176 if (smw->smw_period == WDOG_PERIOD_DEFAULT) 177 smw->smw_period = SUNXI_WDT_PERIOD_DEFAULT; 178 intv = sunxi_wdt_map_period(sc, smw->smw_period, 179 &sc->sc_smw.smw_period); 180 if (intv == -1) 181 return EINVAL; 182 183 cfg = __SHIFTIN(SUN6I_WDT_CFG_CONFIG_SYS, SUN6I_WDT_CFG_CONFIG); 184 mode = SUN6I_WDT_MODE_EN | __SHIFTIN(intv, SUN6I_WDT_MODE_INTV); 185 186 WDT_WRITE(sc, SUN6I_WDT_CFG_REG, cfg); 187 WDT_WRITE(sc, SUN6I_WDT_MODE_REG, mode); 188 } 189 190 return 0; 191 } 192 193 static int 194 sun6i_wdt_tickle(struct sysmon_wdog *smw) 195 { 196 struct sunxi_wdt_softc * const sc = smw->smw_cookie; 197 const uint32_t ctrl = SUN6I_WDT_CTRL_RSTART | 198 __SHIFTIN(SUN6I_WDT_CTRL_KEY_FIELD_V, SUN6I_WDT_CTRL_KEY_FIELD); 199 200 WDT_WRITE(sc, SUN6I_WDT_CTRL_REG, ctrl); 201 202 return 0; 203 } 204 205 static int 206 sun20i_wdt_setmode(struct sysmon_wdog *smw) 207 { 208 struct sunxi_wdt_softc * const sc = smw->smw_cookie; 209 uint32_t cfg, mode; 210 int intv; 211 212 if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) { 213 mode = __SHIFTIN(SUNXI_WDT_KEY_FIELD_V, SUNXI_WDT_KEY_FIELD); 214 WDT_WRITE(sc, SUN6I_WDT_MODE_REG, mode); 215 } else { 216 if (smw->smw_period == WDOG_PERIOD_DEFAULT) 217 smw->smw_period = SUNXI_WDT_PERIOD_DEFAULT; 218 intv = sunxi_wdt_map_period(sc, smw->smw_period, 219 &sc->sc_smw.smw_period); 220 if (intv == -1) 221 return EINVAL; 222 223 cfg = __SHIFTIN(SUN6I_WDT_CFG_CONFIG_SYS, SUN6I_WDT_CFG_CONFIG); 224 cfg |= __SHIFTIN(SUNXI_WDT_KEY_FIELD_V, SUNXI_WDT_KEY_FIELD); 225 mode = SUN6I_WDT_MODE_EN | __SHIFTIN(intv, SUN6I_WDT_MODE_INTV); 226 mode |= __SHIFTIN(SUNXI_WDT_KEY_FIELD_V, SUNXI_WDT_KEY_FIELD); 227 228 WDT_WRITE(sc, SUN6I_WDT_CFG_REG, cfg); 229 WDT_WRITE(sc, SUN6I_WDT_MODE_REG, mode); 230 } 231 232 return 0; 233 } 234 235 static int 236 sunxi_wdt_match(device_t parent, cfdata_t cf, void *aux) 237 { 238 struct fdt_attach_args * const faa = aux; 239 240 return of_compatible_match(faa->faa_phandle, compat_data); 241 } 242 243 static void 244 sunxi_wdt_attach(device_t parent, device_t self, void *aux) 245 { 246 struct sunxi_wdt_softc * const sc = device_private(self); 247 struct fdt_attach_args * const faa = aux; 248 const int phandle = faa->faa_phandle; 249 enum sunxi_wdt_type type; 250 bus_addr_t addr; 251 bus_size_t size; 252 uint32_t mode; 253 254 if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { 255 aprint_error(": couldn't get registers\n"); 256 return; 257 } 258 259 sc->sc_dev = self; 260 sc->sc_bst = faa->faa_bst; 261 if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) { 262 aprint_error(": couldn't map registers\n"); 263 return; 264 } 265 sc->sc_periods = sunxi_periods; 266 267 aprint_naive("\n"); 268 aprint_normal(": Watchdog\n"); 269 270 sc->sc_smw.smw_name = device_xname(self); 271 sc->sc_smw.smw_cookie = sc; 272 sc->sc_smw.smw_period = SUNXI_WDT_PERIOD_DEFAULT; 273 274 type = of_compatible_lookup(phandle, compat_data)->value; 275 switch (type) { 276 case WDT_SUN4I: 277 sc->sc_smw.smw_setmode = sun4i_wdt_setmode; 278 sc->sc_smw.smw_tickle = sun4i_wdt_tickle; 279 break; 280 case WDT_SUN6I: 281 sc->sc_smw.smw_setmode = sun6i_wdt_setmode; 282 sc->sc_smw.smw_tickle = sun6i_wdt_tickle; 283 284 /* Disable watchdog IRQs */ 285 WDT_WRITE(sc, SUN6I_WDT_IRQ_EN_REG, 0); 286 break; 287 case WDT_SUN20I: 288 sc->sc_smw.smw_setmode = sun20i_wdt_setmode; 289 sc->sc_smw.smw_tickle = sun6i_wdt_tickle; 290 291 /* Disable watchdog */ 292 mode = __SHIFTIN(SUNXI_WDT_KEY_FIELD_V, SUNXI_WDT_KEY_FIELD); 293 WDT_WRITE(sc, SUN6I_WDT_MODE_REG, mode); 294 295 /* Disable watchdog IRQs */ 296 WDT_WRITE(sc, SUN6I_WDT_IRQ_EN_REG, 0); 297 break; 298 } 299 300 aprint_normal_dev(self, 301 "default watchdog period is %u seconds\n", 302 sc->sc_smw.smw_period); 303 304 if (sysmon_wdog_register(&sc->sc_smw) != 0) { 305 aprint_error_dev(self, 306 "couldn't register with sysmon\n"); 307 } 308 } 309 310 CFATTACH_DECL_NEW(sunxi_wdt, sizeof(struct sunxi_wdt_softc), 311 sunxi_wdt_match, sunxi_wdt_attach, NULL, NULL); 312