1/*- 2 * Copyright 2013-2015 John Wehle <john@feith.com> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 * 26 */ 27 28/* 29 * Amlogic aml8726 watchdog driver. 30 */ 31 32#include <sys/cdefs.h> 33__FBSDID("$FreeBSD$"); 34 35#include <sys/param.h> 36#include <sys/systm.h> 37#include <sys/bus.h> 38#include <sys/kernel.h> 39#include <sys/module.h> 40#include <sys/malloc.h> 41#include <sys/rman.h> 42 43#include <sys/watchdog.h> 44 45#include <machine/bus.h> 46 47#include <dev/ofw/ofw_bus.h> 48#include <dev/ofw/ofw_bus_subr.h> 49 50#include <arm/amlogic/aml8726/aml8726_soc.h> 51 52 53struct aml8726_wdt_softc { 54 device_t dev; 55 struct resource * res[2]; 56 struct mtx mtx; 57 void * ih_cookie; 58}; 59 60static struct resource_spec aml8726_wdt_spec[] = { 61 { SYS_RES_MEMORY, 0, RF_ACTIVE }, 62 { SYS_RES_IRQ, 0, RF_ACTIVE }, 63 { -1, 0 } 64}; 65 66static struct { 67 uint32_t ctrl_cpu_mask; 68 uint32_t ctrl_en; 69 uint32_t term_cnt_mask; 70 uint32_t reset_cnt_mask; 71} aml8726_wdt_soc_params; 72 73/* 74 * devclass_get_device / device_get_softc could be used 75 * to dynamically locate this, however the wdt is a 76 * required device which can't be unloaded so there's 77 * no need for the overhead. 78 */ 79static struct aml8726_wdt_softc *aml8726_wdt_sc = NULL; 80 81#define AML_WDT_LOCK(sc) mtx_lock_spin(&(sc)->mtx) 82#define AML_WDT_UNLOCK(sc) mtx_unlock_spin(&(sc)->mtx) 83#define AML_WDT_LOCK_INIT(sc) \ 84 mtx_init(&(sc)->mtx, device_get_nameunit((sc)->dev), \ 85 "wdt", MTX_SPIN) 86#define AML_WDT_LOCK_DESTROY(sc) mtx_destroy(&(sc)->mtx); 87 88#define AML_WDT_CTRL_REG 0 89#define AML_WDT_CTRL_CPU_WDRESET_MASK aml8726_wdt_soc_params.ctrl_cpu_mask 90#define AML_WDT_CTRL_CPU_WDRESET_SHIFT 24 91#define AML_WDT_CTRL_IRQ_EN (1 << 23) 92#define AML_WDT_CTRL_EN aml8726_wdt_soc_params.ctrl_en 93#define AML_WDT_CTRL_TERMINAL_CNT_MASK aml8726_wdt_soc_params.term_cnt_mask 94#define AML_WDT_CTRL_TERMINAL_CNT_SHIFT 0 95#define AML_WDT_RESET_REG 4 96#define AML_WDT_RESET_CNT_MASK aml8726_wdt_soc_params.reset_cnt_mask 97#define AML_WDT_RESET_CNT_SHIFT 0 98 99#define CSR_WRITE_4(sc, reg, val) bus_write_4((sc)->res[0], reg, (val)) 100#define CSR_READ_4(sc, reg) bus_read_4((sc)->res[0], reg) 101#define CSR_BARRIER(sc, reg) bus_barrier((sc)->res[0], reg, 4, \ 102 (BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE)) 103 104static void 105aml8726_wdt_watchdog(void *private, u_int cmd, int *error) 106{ 107 struct aml8726_wdt_softc *sc = (struct aml8726_wdt_softc *)private; 108 uint32_t wcr; 109 uint64_t tens_of_usec; 110 111 AML_WDT_LOCK(sc); 112 113 tens_of_usec = (((uint64_t)1 << (cmd & WD_INTERVAL)) + 9999) / 10000; 114 115 if (cmd != 0 && tens_of_usec <= (AML_WDT_CTRL_TERMINAL_CNT_MASK >> 116 AML_WDT_CTRL_TERMINAL_CNT_SHIFT)) { 117 118 wcr = AML_WDT_CTRL_CPU_WDRESET_MASK | 119 AML_WDT_CTRL_EN | ((uint32_t)tens_of_usec << 120 AML_WDT_CTRL_TERMINAL_CNT_SHIFT); 121 122 CSR_WRITE_4(sc, AML_WDT_RESET_REG, 0); 123 CSR_WRITE_4(sc, AML_WDT_CTRL_REG, wcr); 124 125 *error = 0; 126 } else 127 CSR_WRITE_4(sc, AML_WDT_CTRL_REG, 128 (CSR_READ_4(sc, AML_WDT_CTRL_REG) & 129 ~(AML_WDT_CTRL_IRQ_EN | AML_WDT_CTRL_EN))); 130 131 AML_WDT_UNLOCK(sc); 132} 133 134static int 135aml8726_wdt_intr(void *arg) 136{ 137 struct aml8726_wdt_softc *sc = (struct aml8726_wdt_softc *)arg; 138 139 /* 140 * Normally a timeout causes a hardware reset, however 141 * the watchdog timer can be configured to cause an 142 * interrupt instead by setting AML_WDT_CTRL_IRQ_EN 143 * and clearing AML_WDT_CTRL_CPU_WDRESET_MASK. 144 */ 145 146 AML_WDT_LOCK(sc); 147 148 CSR_WRITE_4(sc, AML_WDT_CTRL_REG, 149 (CSR_READ_4(sc, AML_WDT_CTRL_REG) & ~(AML_WDT_CTRL_IRQ_EN | 150 AML_WDT_CTRL_EN))); 151 152 CSR_BARRIER(sc, AML_WDT_CTRL_REG); 153 154 AML_WDT_UNLOCK(sc); 155 156 device_printf(sc->dev, "timeout expired\n"); 157 158 return (FILTER_HANDLED); 159} 160 161static int 162aml8726_wdt_probe(device_t dev) 163{ 164 165 if (!ofw_bus_status_okay(dev)) 166 return (ENXIO); 167 168 if (!ofw_bus_is_compatible(dev, "amlogic,meson6-wdt")) 169 return (ENXIO); 170 171 device_set_desc(dev, "Amlogic aml8726 WDT"); 172 173 return (BUS_PROBE_DEFAULT); 174} 175 176static int 177aml8726_wdt_attach(device_t dev) 178{ 179 struct aml8726_wdt_softc *sc = device_get_softc(dev); 180 181 /* There should be exactly one instance. */ 182 if (aml8726_wdt_sc != NULL) 183 return (ENXIO); 184 185 sc->dev = dev; 186 187 if (bus_alloc_resources(dev, aml8726_wdt_spec, sc->res)) { 188 device_printf(dev, "can not allocate resources for device\n"); 189 return (ENXIO); 190 } 191 192 /* 193 * Certain bitfields are dependent on the hardware revision. 194 */ 195 switch (aml8726_soc_hw_rev) { 196 case AML_SOC_HW_REV_M8: 197 aml8726_wdt_soc_params.ctrl_cpu_mask = 0xf << 198 AML_WDT_CTRL_CPU_WDRESET_SHIFT; 199 switch (aml8726_soc_metal_rev) { 200 case AML_SOC_M8_METAL_REV_M2_A: 201 aml8726_wdt_soc_params.ctrl_en = 1 << 19; 202 aml8726_wdt_soc_params.term_cnt_mask = 0x07ffff << 203 AML_WDT_CTRL_TERMINAL_CNT_SHIFT; 204 aml8726_wdt_soc_params.reset_cnt_mask = 0x07ffff << 205 AML_WDT_RESET_CNT_SHIFT; 206 break; 207 default: 208 aml8726_wdt_soc_params.ctrl_en = 1 << 22; 209 aml8726_wdt_soc_params.term_cnt_mask = 0x3fffff << 210 AML_WDT_CTRL_TERMINAL_CNT_SHIFT; 211 aml8726_wdt_soc_params.reset_cnt_mask = 0x3fffff << 212 AML_WDT_RESET_CNT_SHIFT; 213 break; 214 } 215 break; 216 case AML_SOC_HW_REV_M8B: 217 aml8726_wdt_soc_params.ctrl_cpu_mask = 0xf << 218 AML_WDT_CTRL_CPU_WDRESET_SHIFT; 219 aml8726_wdt_soc_params.ctrl_en = 1 << 19; 220 aml8726_wdt_soc_params.term_cnt_mask = 0x07ffff << 221 AML_WDT_CTRL_TERMINAL_CNT_SHIFT; 222 aml8726_wdt_soc_params.reset_cnt_mask = 0x07ffff << 223 AML_WDT_RESET_CNT_SHIFT; 224 break; 225 default: 226 aml8726_wdt_soc_params.ctrl_cpu_mask = 3 << 227 AML_WDT_CTRL_CPU_WDRESET_SHIFT; 228 aml8726_wdt_soc_params.ctrl_en = 1 << 22; 229 aml8726_wdt_soc_params.term_cnt_mask = 0x3fffff << 230 AML_WDT_CTRL_TERMINAL_CNT_SHIFT; 231 aml8726_wdt_soc_params.reset_cnt_mask = 0x3fffff << 232 AML_WDT_RESET_CNT_SHIFT; 233 break; 234 } 235 236 /* 237 * Disable the watchdog. 238 */ 239 CSR_WRITE_4(sc, AML_WDT_CTRL_REG, 240 (CSR_READ_4(sc, AML_WDT_CTRL_REG) & ~(AML_WDT_CTRL_IRQ_EN | 241 AML_WDT_CTRL_EN))); 242 243 /* 244 * Initialize the mutex prior to installing the interrupt handler 245 * in case of a spurious interrupt. 246 */ 247 AML_WDT_LOCK_INIT(sc); 248 249 if (bus_setup_intr(dev, sc->res[1], INTR_TYPE_MISC | INTR_MPSAFE, 250 aml8726_wdt_intr, NULL, sc, &sc->ih_cookie)) { 251 device_printf(dev, "could not setup interrupt handler\n"); 252 bus_release_resources(dev, aml8726_wdt_spec, sc->res); 253 AML_WDT_LOCK_DESTROY(sc); 254 return (ENXIO); 255 } 256 257 aml8726_wdt_sc = sc; 258 259 EVENTHANDLER_REGISTER(watchdog_list, aml8726_wdt_watchdog, sc, 0); 260 261 return (0); 262} 263 264static int 265aml8726_wdt_detach(device_t dev) 266{ 267 268 return (EBUSY); 269} 270 271static device_method_t aml8726_wdt_methods[] = { 272 /* Device interface */ 273 DEVMETHOD(device_probe, aml8726_wdt_probe), 274 DEVMETHOD(device_attach, aml8726_wdt_attach), 275 DEVMETHOD(device_detach, aml8726_wdt_detach), 276 277 DEVMETHOD_END 278}; 279 280static driver_t aml8726_wdt_driver = { 281 "wdt", 282 aml8726_wdt_methods, 283 sizeof(struct aml8726_wdt_softc), 284}; 285 286static devclass_t aml8726_wdt_devclass; 287 288EARLY_DRIVER_MODULE(wdt, simplebus, aml8726_wdt_driver, aml8726_wdt_devclass, 289 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LATE); 290 291void 292cpu_reset(void) 293{ 294 295 /* Watchdog has not yet been initialized */ 296 if (aml8726_wdt_sc == NULL) 297 printf("Reset hardware has not yet been initialized.\n"); 298 else { 299 CSR_WRITE_4(aml8726_wdt_sc, AML_WDT_RESET_REG, 0); 300 CSR_WRITE_4(aml8726_wdt_sc, AML_WDT_CTRL_REG, 301 (AML_WDT_CTRL_CPU_WDRESET_MASK | AML_WDT_CTRL_EN | 302 (10 << AML_WDT_CTRL_TERMINAL_CNT_SHIFT))); 303 } 304 305 while (1); 306} 307