1 /* $NetBSD: sunxi_wdt.c,v 1.2 2017/10/07 16:44:24 jmcneill Exp $ */ 2 3 /*- 4 * Copyright (c) 2017 Jared McNeill <jmcneill@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.2 2017/10/07 16:44:24 jmcneill 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 static const int sunxi_periods[] = { 72 500, 1000, 2000, 3000, 73 4000, 5000, 6000, 8000, 74 10000, 12000, 14000, 16000, 75 -1 76 }; 77 78 enum sunxi_wdt_type { 79 WDT_SUN4I = 1, 80 WDT_SUN6I, 81 }; 82 83 static const struct of_compat_data compat_data[] = { 84 { "allwinner,sun4i-a10-wdt", WDT_SUN4I }, 85 { "allwinner,sun6i-a31-wdt", WDT_SUN6I }, 86 { NULL } 87 }; 88 89 struct sunxi_wdt_softc { 90 device_t sc_dev; 91 bus_space_tag_t sc_bst; 92 bus_space_handle_t sc_bsh; 93 94 const int *sc_periods; 95 96 struct sysmon_wdog sc_smw; 97 }; 98 99 #define WDT_READ(sc, reg) \ 100 bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg)) 101 #define WDT_WRITE(sc, reg, val) \ 102 bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val)) 103 104 static int 105 sunxi_wdt_map_period(struct sunxi_wdt_softc *sc, u_int period, 106 u_int *aperiod) 107 { 108 const int *p = sc->sc_periods; 109 int i; 110 111 if (period == 0) 112 return -1; 113 114 for (i = 0; *p != -1; i++, p++) 115 if (*p >= period * 1000) { 116 *aperiod = *p / 1000; 117 return i; 118 } 119 120 return -1; 121 } 122 123 static int 124 sun4i_wdt_setmode(struct sysmon_wdog *smw) 125 { 126 struct sunxi_wdt_softc * const sc = smw->smw_cookie; 127 uint32_t mode; 128 int intv; 129 130 if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) { 131 WDT_WRITE(sc, SUN4I_WDT_MODE_REG, 0); 132 } else { 133 if (smw->smw_period == WDOG_PERIOD_DEFAULT) 134 smw->smw_period = SUNXI_WDT_PERIOD_DEFAULT; 135 intv = sunxi_wdt_map_period(sc, smw->smw_period, 136 &sc->sc_smw.smw_period); 137 if (intv == -1) 138 return EINVAL; 139 140 mode = SUN4I_WDT_MODE_EN | SUN4I_WDT_MODE_RST_EN | 141 __SHIFTIN(intv, SUN4I_WDT_MODE_INTV); 142 143 WDT_WRITE(sc, SUN4I_WDT_MODE_REG, mode); 144 } 145 146 return 0; 147 } 148 149 static int 150 sun4i_wdt_tickle(struct sysmon_wdog *smw) 151 { 152 struct sunxi_wdt_softc * const sc = smw->smw_cookie; 153 const uint32_t ctrl = SUN4I_WDT_CTRL_RSTART | 154 __SHIFTIN(SUN4I_WDT_CTRL_KEY_FIELD_V, SUN4I_WDT_CTRL_KEY_FIELD); 155 156 WDT_WRITE(sc, SUN4I_WDT_CTRL_REG, ctrl); 157 158 return 0; 159 } 160 161 static int 162 sun6i_wdt_setmode(struct sysmon_wdog *smw) 163 { 164 struct sunxi_wdt_softc * const sc = smw->smw_cookie; 165 uint32_t cfg, mode; 166 int intv; 167 168 if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) { 169 WDT_WRITE(sc, SUN6I_WDT_MODE_REG, 0); 170 } else { 171 if (smw->smw_period == WDOG_PERIOD_DEFAULT) 172 smw->smw_period = SUNXI_WDT_PERIOD_DEFAULT; 173 intv = sunxi_wdt_map_period(sc, smw->smw_period, 174 &sc->sc_smw.smw_period); 175 if (intv == -1) 176 return EINVAL; 177 178 cfg = __SHIFTIN(SUN6I_WDT_CFG_CONFIG_SYS, SUN6I_WDT_CFG_CONFIG); 179 mode = SUN6I_WDT_MODE_EN | __SHIFTIN(intv, SUN6I_WDT_MODE_INTV); 180 181 WDT_WRITE(sc, SUN6I_WDT_CFG_REG, cfg); 182 WDT_WRITE(sc, SUN6I_WDT_MODE_REG, mode); 183 } 184 185 return 0; 186 } 187 188 static int 189 sun6i_wdt_tickle(struct sysmon_wdog *smw) 190 { 191 struct sunxi_wdt_softc * const sc = smw->smw_cookie; 192 const uint32_t ctrl = SUN6I_WDT_CTRL_RSTART | 193 __SHIFTIN(SUN6I_WDT_CTRL_KEY_FIELD_V, SUN6I_WDT_CTRL_KEY_FIELD); 194 195 WDT_WRITE(sc, SUN6I_WDT_CTRL_REG, ctrl); 196 197 return 0; 198 } 199 200 static int 201 sunxi_wdt_match(device_t parent, cfdata_t cf, void *aux) 202 { 203 struct fdt_attach_args * const faa = aux; 204 205 return of_match_compat_data(faa->faa_phandle, compat_data); 206 } 207 208 static void 209 sunxi_wdt_attach(device_t parent, device_t self, void *aux) 210 { 211 struct sunxi_wdt_softc * const sc = device_private(self); 212 struct fdt_attach_args * const faa = aux; 213 const int phandle = faa->faa_phandle; 214 enum sunxi_wdt_type type; 215 bus_addr_t addr; 216 bus_size_t size; 217 218 if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { 219 aprint_error(": couldn't get registers\n"); 220 return; 221 } 222 223 sc->sc_dev = self; 224 sc->sc_bst = faa->faa_bst; 225 if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) { 226 aprint_error(": couldn't map registers\n"); 227 return; 228 } 229 sc->sc_periods = sunxi_periods; 230 231 aprint_naive("\n"); 232 aprint_normal(": Watchdog\n"); 233 234 sc->sc_smw.smw_name = device_xname(self); 235 sc->sc_smw.smw_cookie = sc; 236 sc->sc_smw.smw_period = SUNXI_WDT_PERIOD_DEFAULT; 237 238 type = of_search_compatible(phandle, compat_data)->data; 239 switch (type) { 240 case WDT_SUN4I: 241 sc->sc_smw.smw_setmode = sun4i_wdt_setmode; 242 sc->sc_smw.smw_tickle = sun4i_wdt_tickle; 243 break; 244 case WDT_SUN6I: 245 sc->sc_smw.smw_setmode = sun6i_wdt_setmode; 246 sc->sc_smw.smw_tickle = sun6i_wdt_tickle; 247 248 /* Disable watchdog IRQs */ 249 WDT_WRITE(sc, SUN6I_WDT_IRQ_EN_REG, 0); 250 break; 251 } 252 253 aprint_normal_dev(self, 254 "default watchdog period is %u seconds\n", 255 sc->sc_smw.smw_period); 256 257 if (sysmon_wdog_register(&sc->sc_smw) != 0) { 258 aprint_error_dev(self, 259 "couldn't register with sysmon\n"); 260 } 261 } 262 263 CFATTACH_DECL_NEW(sunxi_wdt, sizeof(struct sunxi_wdt_softc), 264 sunxi_wdt_match, sunxi_wdt_attach, NULL, NULL); 265