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