1213496Scognet/*- 2213496Scognet * Copyright (c) 2010 Greg Ansley. All rights reserved. 3213496Scognet * 4213496Scognet * Redistribution and use in source and binary forms, with or without 5213496Scognet * modification, are permitted provided that the following conditions 6213496Scognet * are met: 7213496Scognet * 1. Redistributions of source code must retain the above copyright 8213496Scognet * notice, this list of conditions and the following disclaimer. 9213496Scognet * 2. Redistributions in binary form must reproduce the above copyright 10213496Scognet * notice, this list of conditions and the following disclaimer in the 11213496Scognet * documentation and/or other materials provided with the distribution. 12213496Scognet * 13213496Scognet * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND 14213496Scognet * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15213496Scognet * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16213496Scognet * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE 17213496Scognet * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18213496Scognet * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 19213496Scognet * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20213496Scognet * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 21213496Scognet * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 22213496Scognet * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 23213496Scognet * SUCH DAMAGE. 24213496Scognet */ 25213496Scognet 26213496Scognet/* 27234281Smarius * The SAM9 watchdog hardware can be programed only once. So we set the 28234281Smarius * hardware watchdog to 16 s in wdt_attach and only reset it in the wdt_tick 29234281Smarius * handler. The watchdog is halted in processor debug mode. 30213496Scognet */ 31213496Scognet 32266196Sian#include "opt_platform.h" 33266196Sian 34213496Scognet#include <sys/cdefs.h> 35213496Scognet__FBSDID("$FreeBSD: releng/10.2/sys/arm/at91/at91_wdt.c 266196 2014-05-15 21:21:47Z ian $"); 36213496Scognet 37213496Scognet#include <sys/param.h> 38213496Scognet#include <sys/bus.h> 39213496Scognet#include <sys/kdb.h> 40213496Scognet#include <sys/kernel.h> 41213496Scognet#include <sys/module.h> 42213496Scognet#include <sys/systm.h> 43213496Scognet#include <sys/watchdog.h> 44213496Scognet 45213496Scognet#include <machine/bus.h> 46213496Scognet 47213496Scognet#include <arm/at91/at91var.h> 48213496Scognet#include <arm/at91/at91_wdtreg.h> 49213496Scognet 50266196Sian#ifdef FDT 51266196Sian#include <dev/fdt/fdt_common.h> 52266196Sian#include <dev/ofw/ofw_bus.h> 53266196Sian#include <dev/ofw/ofw_bus_subr.h> 54266196Sian#endif 55266196Sian 56213496Scognetstruct wdt_softc { 57213496Scognet struct mtx sc_mtx; 58213496Scognet device_t sc_dev; 59213496Scognet struct resource *mem_res; 60213496Scognet struct callout tick_ch; 61213496Scognet eventhandler_tag sc_wet; 62213496Scognet void *intrhand; 63234281Smarius u_int cmd; 64234281Smarius u_int interval; 65213496Scognet}; 66213496Scognet 67213496Scognetstatic inline uint32_t 68213496ScognetRD4(struct wdt_softc *sc, bus_size_t off) 69213496Scognet{ 70234281Smarius 71213496Scognet return (bus_read_4(sc->mem_res, off)); 72213496Scognet} 73213496Scognet 74213496Scognetstatic inline void 75213496ScognetWR4(struct wdt_softc *sc, bus_size_t off, uint32_t val) 76213496Scognet{ 77234281Smarius 78213496Scognet bus_write_4(sc->mem_res, off, val); 79213496Scognet} 80213496Scognet 81213496Scognetstatic int 82213496Scognetwdt_intr(void *argp) 83213496Scognet{ 84213496Scognet struct wdt_softc *sc = argp; 85213496Scognet 86213496Scognet 87213496Scognet if (RD4(sc, WDT_SR) & (WDT_WDUNF | WDT_WDERR)) { 88213496Scognet#if defined(KDB) && !defined(KDB_UNATTENDED) 89213496Scognet kdb_backtrace(); 90213496Scognet kdb_enter(KDB_WHY_WATCHDOG, "watchdog timeout"); 91213496Scognet#else 92213496Scognet panic("watchdog timeout"); 93213496Scognet#endif 94213496Scognet } 95213496Scognet return (FILTER_STRAY); 96213496Scognet} 97213496Scognet 98213496Scognet/* User interface, see watchdog(9) */ 99213496Scognetstatic void 100213496Scognetwdt_watchdog(void *argp, u_int cmd, int *error) 101213496Scognet{ 102213496Scognet struct wdt_softc *sc = argp; 103213496Scognet u_int interval; 104213496Scognet 105213496Scognet mtx_lock(&sc->sc_mtx); 106213496Scognet 107213496Scognet *error = 0; 108213496Scognet sc->cmd = 0; 109213496Scognet interval = cmd & WD_INTERVAL; 110213496Scognet if (interval > WD_TO_16SEC) 111213496Scognet *error = EOPNOTSUPP; 112213496Scognet else if (interval > 0) 113213496Scognet sc->cmd = interval | WD_ACTIVE; 114213496Scognet 115221025Scognet /* We cannot turn off our watchdog so if user 116213496Scognet * fails to turn us on go to passive mode. */ 117213496Scognet if ((sc->cmd & WD_ACTIVE) == 0) 118213496Scognet sc->cmd = WD_PASSIVE; 119213496Scognet 120213496Scognet mtx_unlock(&sc->sc_mtx); 121213496Scognet} 122213496Scognet 123213496Scognet/* This routine is called no matter what state the user sets the 124213496Scognet * watchdog mode to. Called at a rate that is slightly less than 125213496Scognet * half the hardware timeout. */ 126213496Scognetstatic void 127213496Scognetwdt_tick(void *argp) 128213496Scognet{ 129213496Scognet struct wdt_softc *sc = argp; 130213496Scognet 131213496Scognet mtx_assert(&sc->sc_mtx, MA_OWNED); 132213496Scognet if (sc->cmd & (WD_ACTIVE | WD_PASSIVE)) 133213496Scognet WR4(sc, WDT_CR, WDT_KEY|WDT_WDRSTT); 134213496Scognet 135213496Scognet sc->cmd &= WD_PASSIVE; 136213496Scognet callout_reset(&sc->tick_ch, sc->interval, wdt_tick, sc); 137213496Scognet} 138213496Scognet 139213496Scognetstatic int 140213496Scognetwdt_probe(device_t dev) 141213496Scognet{ 142266196Sian#ifdef FDT 143266196Sian if (!ofw_bus_is_compatible(dev, "atmel,at91sam9260-wdt")) 144266196Sian return (ENXIO); 145266196Sian#endif 146266196Sian device_set_desc(dev, "WDT"); 147266196Sian return (0); 148213496Scognet} 149213496Scognet 150213496Scognetstatic int 151213496Scognetwdt_attach(device_t dev) 152213496Scognet{ 153213496Scognet static struct wdt_softc *sc; 154213496Scognet struct resource *irq; 155213496Scognet uint32_t wdt_mr; 156213496Scognet int rid, err; 157213496Scognet 158213496Scognet sc = device_get_softc(dev); 159213496Scognet sc->cmd = WD_PASSIVE; 160213496Scognet sc->sc_dev = dev; 161213496Scognet 162213496Scognet mtx_init(&sc->sc_mtx, device_get_nameunit(dev), "at91_wdt", MTX_DEF); 163213496Scognet callout_init_mtx(&sc->tick_ch, &sc->sc_mtx, 0); 164213496Scognet 165213496Scognet rid = 0; 166213496Scognet sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, 167213496Scognet RF_ACTIVE); 168213496Scognet 169213496Scognet if (sc->mem_res == NULL) 170234281Smarius panic("couldn't allocate wdt register resources"); 171213496Scognet 172213496Scognet wdt_mr = RD4(sc, WDT_MR); 173213496Scognet if ((wdt_mr & WDT_WDRSTEN) == 0) 174213496Scognet device_printf(dev, "Watchdog disabled! (Boot ROM?)\n"); 175213496Scognet else { 176213496Scognet#ifdef WDT_RESET 177213496Scognet /* Rude, full reset of whole system on watch dog timeout */ 178213496Scognet WR4(sc, WDT_MR, WDT_WDDBGHLT | WDT_WDD(0xC00)| 179213496Scognet WDT_WDRSTEN| WDT_WDV(0xFFF)); 180213496Scognet#else 181213496Scognet /* Generate stack trace and panic on watchdog timeout*/ 182213496Scognet WR4(sc, WDT_MR, WDT_WDDBGHLT | WDT_WDD(0xC00)| 183213496Scognet WDT_WDFIEN| WDT_WDV(0xFFF)); 184213496Scognet#endif 185234281Smarius /* 186234281Smarius * This may have been set by Boot ROM so register value may 187234281Smarius * not be what we just requested since this is a write once 188234281Smarius * register. 189234281Smarius */ 190213496Scognet wdt_mr = RD4(sc, WDT_MR); 191213496Scognet if (wdt_mr & WDT_WDFIEN) { 192213496Scognet rid = 0; 193213496Scognet irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, 194213496Scognet RF_ACTIVE | RF_SHAREABLE); 195213496Scognet if (!irq) 196213496Scognet panic("could not allocate interrupt.\n"); 197213496Scognet 198213496Scognet err = bus_setup_intr(dev, irq, INTR_TYPE_CLK, wdt_intr, 199234281Smarius NULL, sc, &sc->intrhand); 200213496Scognet } 201213496Scognet 202213496Scognet /* interval * hz */ 203213496Scognet sc->interval = (((wdt_mr & WDT_WDV(~0)) + 1) * WDT_DIV) / 204234281Smarius (WDT_CLOCK/hz); 205213496Scognet 206213496Scognet device_printf(dev, "watchdog timeout: %d seconds\n", 207234281Smarius sc->interval / hz); 208213496Scognet 209213496Scognet /* Slightly less than 1/2 of watchdog hardware timeout */ 210213496Scognet sc->interval = (sc->interval/2) - (sc->interval/20); 211213496Scognet callout_reset(&sc->tick_ch, sc->interval, wdt_tick, sc); 212213496Scognet 213213496Scognet /* Register us as a watchdog */ 214213496Scognet sc->sc_wet = EVENTHANDLER_REGISTER(watchdog_list, 215213496Scognet wdt_watchdog, sc, 0); 216213496Scognet } 217213496Scognet return (0); 218213496Scognet} 219213496Scognet 220213496Scognetstatic device_method_t wdt_methods[] = { 221213496Scognet DEVMETHOD(device_probe, wdt_probe), 222213496Scognet DEVMETHOD(device_attach, wdt_attach), 223234281Smarius DEVMETHOD_END 224213496Scognet}; 225213496Scognet 226213496Scognetstatic driver_t wdt_driver = { 227213496Scognet "at91_wdt", 228213496Scognet wdt_methods, 229213496Scognet sizeof(struct wdt_softc), 230213496Scognet}; 231213496Scognet 232213496Scognetstatic devclass_t wdt_devclass; 233213496Scognet 234266196Sian#ifdef FDT 235266196SianDRIVER_MODULE(at91_wdt, simplebus, wdt_driver, wdt_devclass, NULL, NULL); 236266196Sian#else 237234281SmariusDRIVER_MODULE(at91_wdt, atmelarm, wdt_driver, wdt_devclass, NULL, NULL); 238266196Sian#endif 239