exynos_wdt.c revision 1.5
1/*	$NetBSD: exynos_wdt.c,v 1.5 2014/09/29 14:47:52 reinoud Exp $	*/
2
3/*-
4 * Copyright (c) 2012 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Matt Thomas
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32#include "exynos_wdt.h"
33
34#include <sys/cdefs.h>
35__KERNEL_RCSID(0, "$NetBSD: exynos_wdt.c,v 1.5 2014/09/29 14:47:52 reinoud Exp $");
36
37#include <sys/param.h>
38#include <sys/bus.h>
39#include <sys/cpu.h>
40#include <sys/device.h>
41#include <sys/wdog.h>
42
43#include <prop/proplib.h>
44
45#include <dev/sysmon/sysmonvar.h>
46
47#include <arm/samsung/exynos_io.h>
48#include <arm/samsung/exynos_reg.h>
49#include <arm/samsung/exynos_var.h>
50
51
52#if NEXYNOS_WDT > 0
53static int exynos_wdt_match(device_t, cfdata_t, void *);
54static void exynos_wdt_attach(device_t, device_t, void *);
55
56struct exynos_wdt_softc {
57	struct sysmon_wdog sc_smw;
58	device_t sc_dev;
59	bus_space_tag_t sc_bst;
60	bus_space_handle_t sc_wdog_bsh;
61	u_int sc_wdog_period;
62	u_int sc_wdog_clock_select;
63	u_int sc_wdog_prescaler;
64	uint32_t sc_freq;
65	uint32_t sc_wdog_wtdat;
66	uint32_t sc_wdog_wtcon;
67	bool sc_wdog_armed;
68};
69
70#ifndef EXYNOS_WDT_PERIOD_DEFAULT
71#define	EXYNOS_WDT_PERIOD_DEFAULT	12
72#endif
73
74CFATTACH_DECL_NEW(exynos_wdt, sizeof(struct exynos_wdt_softc),
75    exynos_wdt_match, exynos_wdt_attach, NULL, NULL);
76
77static inline uint32_t
78exynos_wdt_wdog_read(struct exynos_wdt_softc *sc, bus_size_t o)
79{
80	return bus_space_read_4(sc->sc_bst, sc->sc_wdog_bsh, o);
81}
82
83static inline void
84exynos_wdt_wdog_write(struct exynos_wdt_softc *sc, bus_size_t o, uint32_t v)
85{
86	bus_space_write_4(sc->sc_bst, sc->sc_wdog_bsh, o, v);
87}
88
89/* ARGSUSED */
90static int
91exynos_wdt_match(device_t parent, cfdata_t cf, void *aux)
92{
93	return 1;
94}
95
96static int
97exynos_wdt_tickle(struct sysmon_wdog *smw)
98{
99	struct exynos_wdt_softc * const sc = smw->smw_cookie;
100
101	/*
102	 * Cause the WDOG to restart counting.
103	 */
104	exynos_wdt_wdog_write(sc, EXYNOS_WDT_WTCNT, sc->sc_wdog_wtdat);
105	aprint_debug_dev(sc->sc_dev, "tickle\n");
106	return 0;
107}
108
109static int
110exynos_wdt_setmode(struct sysmon_wdog *smw)
111{
112	struct exynos_wdt_softc * const sc = smw->smw_cookie;
113
114	if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) {
115		/*
116		 * Emit magic sequence to turn off WDOG
117		 */
118		sc->sc_wdog_wtcon &= ~(WTCON_ENABLE|WTCON_RESET_ENABLE);
119		exynos_wdt_wdog_write(sc, EXYNOS_WDT_WTCON, sc->sc_wdog_wtcon);
120		delay(1);
121		aprint_debug_dev(sc->sc_dev, "setmode disable\n");
122		return 0;
123	}
124
125	/*
126	 * If no changes, just tickle it and return.
127	 */
128	if (sc->sc_wdog_armed && smw->smw_period == sc->sc_wdog_period) {
129		sc->sc_wdog_wtdat = sc->sc_freq * sc->sc_wdog_period - 1;
130		sc->sc_wdog_wtcon = WTCON_ENABLE | WTCON_RESET_ENABLE
131		    | __SHIFTIN(sc->sc_wdog_clock_select, WTCON_CLOCK_SELECT)
132		    | __SHIFTIN(sc->sc_wdog_prescaler - 1, WTCON_PRESCALER);
133
134		exynos_wdt_wdog_write(sc, EXYNOS_WDT_WTCNT, sc->sc_wdog_wtdat);
135		exynos_wdt_wdog_write(sc, EXYNOS_WDT_WTDAT, sc->sc_wdog_wtdat);
136		exynos_wdt_wdog_write(sc, EXYNOS_WDT_WTCON, sc->sc_wdog_wtcon);
137		aprint_debug_dev(sc->sc_dev, "setmode refresh\n");
138		return 0;
139	}
140
141	if (smw->smw_period == WDOG_PERIOD_DEFAULT) {
142		sc->sc_wdog_period = EXYNOS_WDT_PERIOD_DEFAULT;
143		smw->smw_period = EXYNOS_WDT_PERIOD_DEFAULT;
144	}
145
146	/*
147	 * Make sure we don't overflow the counter.
148	 */
149	if (smw->smw_period * sc->sc_freq >= UINT16_MAX) {
150		return EINVAL;
151	}
152
153	sc->sc_wdog_wtdat = sc->sc_freq * sc->sc_wdog_period - 1;
154	sc->sc_wdog_wtcon = WTCON_ENABLE | WTCON_RESET_ENABLE
155	    | __SHIFTIN(sc->sc_wdog_clock_select, WTCON_CLOCK_SELECT)
156	    | __SHIFTIN(sc->sc_wdog_prescaler - 1, WTCON_PRESCALER);
157
158	/*
159	 * Have to disable to be able to write WTDAT
160	 */
161	exynos_wdt_wdog_write(sc, EXYNOS_WDT_WTCON,
162	    sc->sc_wdog_wtcon & ~(WTCON_ENABLE | WTCON_RESET_ENABLE));
163	exynos_wdt_wdog_write(sc, EXYNOS_WDT_WTCNT, sc->sc_wdog_wtdat);
164	exynos_wdt_wdog_write(sc, EXYNOS_WDT_WTDAT, sc->sc_wdog_wtdat);
165	exynos_wdt_wdog_write(sc, EXYNOS_WDT_WTCON, sc->sc_wdog_wtcon);
166
167	aprint_debug_dev(sc->sc_dev, "setmode enable\n");
168	return 0;
169}
170
171
172static void
173exynos_wdt_attach(device_t parent, device_t self, void *aux)
174{
175        struct exynos_wdt_softc * const sc = device_private(self);
176	struct exyo_attach_args * const exyo = aux;
177	prop_dictionary_t dict = device_properties(self);
178
179	sc->sc_dev = self;
180	sc->sc_bst = exyo->exyo_core_bst;
181
182	if (bus_space_subregion(sc->sc_bst, exyo->exyo_core_bsh,
183	    exyo->exyo_loc.loc_offset, exyo->exyo_loc.loc_size, &sc->sc_wdog_bsh)) {
184		aprint_error(": failed to map registers\n");
185		return;
186	}
187
188	/*
189	 * This runs at the Exynos Pclk.
190	 */
191	prop_dictionary_get_uint32(dict, "frequency", &sc->sc_freq);
192
193	sc->sc_wdog_wtcon = exynos_wdt_wdog_read(sc, EXYNOS_WDT_WTCON);
194	sc->sc_wdog_armed = (sc->sc_wdog_wtcon & WTCON_ENABLE)
195	    && (sc->sc_wdog_wtcon & WTCON_RESET_ENABLE);
196	if (sc->sc_wdog_armed) {
197		sc->sc_wdog_prescaler =
198		    __SHIFTOUT(sc->sc_wdog_wtcon, WTCON_PRESCALER) + 1;
199		sc->sc_wdog_clock_select =
200		    __SHIFTOUT(sc->sc_wdog_wtcon, WTCON_CLOCK_SELECT);
201		sc->sc_freq /= sc->sc_wdog_prescaler;
202		sc->sc_freq >>= 4 + sc->sc_wdog_clock_select;
203		sc->sc_wdog_wtdat = exynos_wdt_wdog_read(sc, EXYNOS_WDT_WTDAT);
204		sc->sc_wdog_period = (sc->sc_wdog_wtdat + 1) / sc->sc_freq;
205	} else {
206		sc->sc_wdog_period = EXYNOS_WDT_PERIOD_DEFAULT;
207		sc->sc_wdog_prescaler = 1;
208		/*
209		 * Let's see what clock select we should use.
210		 */
211		u_int n = __builtin_ffs(sc->sc_freq) - 1;
212		if (n > 7) {
213			sc->sc_wdog_clock_select = WTCON_CLOCK_SELECT_128;
214			sc->sc_freq >>= 7;
215		} else if (n >= 4) {
216			sc->sc_wdog_clock_select = n - 4;
217			sc->sc_freq >>= n;
218		}
219		/*
220		 * Let's hope the timer frequency isn't prime.  If it is, find
221		 * the highest divisor which gives us the least remainder.
222		 */
223		sc->sc_wdog_prescaler = 0;
224		u_int best_remainder = 256;
225		u_int max_period = 2 * EXYNOS_WDT_PERIOD_DEFAULT * sc->sc_freq;
226		for (size_t div = 256; UINT16_MAX > div * max_period; div++) {
227			u_int remainder = sc->sc_freq % div;
228			if (remainder == 0) {
229				sc->sc_wdog_prescaler = div;
230				break;
231			}
232			if (remainder < best_remainder) {
233				sc->sc_wdog_prescaler = div;
234				best_remainder = remainder;
235			}
236		}
237		KASSERT(sc->sc_wdog_prescaler != 0);
238		sc->sc_freq /= sc->sc_wdog_prescaler;
239	}
240
241	/*
242	 * Does the config file tell us to turn on the watchdog?
243	 */
244	if (device_cfdata(self)->cf_flags & 1)
245		sc->sc_wdog_armed = true;
246
247	aprint_naive("\n");
248	aprint_normal(": Exynos Watchdog Timer, default period is %u seconds%s\n",
249	    sc->sc_wdog_period,
250	    sc->sc_wdog_armed ? " (armed)" : "");
251
252	sc->sc_smw.smw_name = device_xname(self);
253	sc->sc_smw.smw_cookie = sc;
254	sc->sc_smw.smw_setmode = exynos_wdt_setmode;
255	sc->sc_smw.smw_tickle = exynos_wdt_tickle;
256	sc->sc_smw.smw_period = sc->sc_wdog_period;
257
258	if (sc->sc_wdog_armed) {
259		int error = sysmon_wdog_setmode(&sc->sc_smw, WDOG_MODE_KTICKLE,
260		    sc->sc_wdog_period);
261		if (error)
262			aprint_error_dev(self,
263			    "failed to start kernel tickler: %d\n", error);
264 	}
265}
266#endif /* NEXYNOS_WDOG > 0 */
267
268void
269exynos_wdt_reset(void)
270{
271	bus_space_tag_t bst = &exynos_bs_tag;
272	bus_space_handle_t bsh = exynos_wdt_bsh;
273
274	(void) splhigh();
275	bus_space_write_4(bst, bsh, EXYNOS_WDT_WTCON, 0);
276	bus_space_write_4(bst, bsh, EXYNOS_WDT_WTCNT, 1);
277	bus_space_write_4(bst, bsh, EXYNOS_WDT_WTCON,
278	   WTCON_ENABLE | WTCON_RESET_ENABLE);
279}
280
281