xref: /freebsd/sys/arm/mv/armada/wdt.c (revision 315ee00f)
1 /*-
2  * Copyright (c) 2006 Benno Rice.
3  * Copyright (C) 2007-2008 MARVELL INTERNATIONAL LTD.
4  * All rights reserved.
5  *
6  * Adapted to Marvell SoC by Semihalf.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  *
28  * from: FreeBSD: //depot/projects/arm/src/sys/arm/xscale/pxa2x0/pxa2x0_timer.c, rev 1
29  */
30 
31 #include <sys/cdefs.h>
32 #include <sys/param.h>
33 #include <sys/systm.h>
34 #include <sys/bus.h>
35 #include <sys/eventhandler.h>
36 #include <sys/kernel.h>
37 #include <sys/module.h>
38 #include <sys/malloc.h>
39 #include <sys/rman.h>
40 #include <sys/kdb.h>
41 #include <sys/timeet.h>
42 #include <sys/timetc.h>
43 #include <sys/watchdog.h>
44 #include <machine/bus.h>
45 #include <machine/cpu.h>
46 
47 #include <arm/mv/mvreg.h>
48 #include <arm/mv/mvvar.h>
49 
50 #include <dev/ofw/ofw_bus.h>
51 #include <dev/ofw/ofw_bus_subr.h>
52 
53 #define INITIAL_TIMECOUNTER	(0xffffffff)
54 #define MAX_WATCHDOG_TICKS	(0xffffffff)
55 #define WD_RST_OUT_EN           0x00000002
56 
57 #define	MV_CLOCK_SRC_ARMV7	25000000	/* Timers' 25MHz mode */
58 
59 struct mv_wdt_config {
60 	enum soc_family wdt_soc;
61 	uint32_t wdt_timer;
62 	void (*wdt_enable)(void);
63 	void (*wdt_disable)(void);
64 	unsigned int wdt_clock_src;
65 };
66 
67 static void mv_wdt_enable_armv5(void);
68 static void mv_wdt_enable_armada_38x(void);
69 static void mv_wdt_enable_armada_xp(void);
70 static inline void mv_wdt_enable_armada_38x_xp_helper(void);
71 
72 static void mv_wdt_disable_armv5(void);
73 static void mv_wdt_disable_armada_38x(void);
74 static void mv_wdt_disable_armada_xp(void);
75 
76 static struct mv_wdt_config mv_wdt_armada_38x_config = {
77 	.wdt_soc = MV_SOC_ARMADA_38X,
78 	.wdt_timer = 4,
79 	.wdt_enable = &mv_wdt_enable_armada_38x,
80 	.wdt_disable = &mv_wdt_disable_armada_38x,
81 	.wdt_clock_src = MV_CLOCK_SRC_ARMV7,
82 };
83 
84 static struct mv_wdt_config mv_wdt_armada_xp_config = {
85 	.wdt_soc = MV_SOC_ARMADA_XP,
86 	.wdt_timer = 2,
87 	.wdt_enable = &mv_wdt_enable_armada_xp,
88 	.wdt_disable = &mv_wdt_disable_armada_xp,
89 	.wdt_clock_src = MV_CLOCK_SRC_ARMV7,
90 };
91 
92 static struct mv_wdt_config mv_wdt_armv5_config = {
93 	.wdt_soc = MV_SOC_ARMV5,
94 	.wdt_timer = 2,
95 	.wdt_enable = &mv_wdt_enable_armv5,
96 	.wdt_disable = &mv_wdt_disable_armv5,
97 	.wdt_clock_src = 0,
98 };
99 
100 struct mv_wdt_softc {
101 	struct resource	*	wdt_res;
102 	struct mtx		wdt_mtx;
103 	struct mv_wdt_config *	wdt_config;
104 };
105 
106 static struct resource_spec mv_wdt_spec[] = {
107 	{ SYS_RES_MEMORY,	0,	RF_ACTIVE },
108 	{ -1, 0 }
109 };
110 
111 static struct ofw_compat_data mv_wdt_compat[] = {
112 	{"marvell,armada-380-wdt",	(uintptr_t)&mv_wdt_armada_38x_config},
113 	{"marvell,armada-xp-wdt",	(uintptr_t)&mv_wdt_armada_xp_config},
114 	{"marvell,orion-wdt",		(uintptr_t)&mv_wdt_armv5_config},
115 	{NULL,				(uintptr_t)NULL}
116 };
117 
118 static struct mv_wdt_softc *wdt_softc = NULL;
119 int timers_initialized = 0;
120 
121 static int mv_wdt_probe(device_t);
122 static int mv_wdt_attach(device_t);
123 
124 static uint32_t	mv_get_timer_control(void);
125 static void mv_set_timer_control(uint32_t);
126 static void mv_set_timer(uint32_t, uint32_t);
127 
128 static void mv_watchdog_event(void *, unsigned int, int *);
129 
130 static device_method_t mv_wdt_methods[] = {
131 	DEVMETHOD(device_probe, mv_wdt_probe),
132 	DEVMETHOD(device_attach, mv_wdt_attach),
133 	{ 0, 0 }
134 };
135 
136 static driver_t mv_wdt_driver = {
137 	"wdt",
138 	mv_wdt_methods,
139 	sizeof(struct mv_wdt_softc),
140 };
141 
142 DRIVER_MODULE(wdt, simplebus, mv_wdt_driver, 0, 0);
143 
144 static int
145 mv_wdt_probe(device_t dev)
146 {
147 
148 	if (!ofw_bus_status_okay(dev))
149 		return (ENXIO);
150 
151 	if (!ofw_bus_search_compatible(dev, mv_wdt_compat)->ocd_data)
152 		return (ENXIO);
153 
154 	device_set_desc(dev, "Marvell Watchdog Timer");
155 	return (0);
156 }
157 
158 static int
159 mv_wdt_attach(device_t dev)
160 {
161 	struct mv_wdt_softc *sc;
162 	int error;
163 
164 	if (wdt_softc != NULL)
165 		return (ENXIO);
166 
167 	sc = device_get_softc(dev);
168 	wdt_softc = sc;
169 
170 	error = bus_alloc_resources(dev, mv_wdt_spec, &sc->wdt_res);
171 	if (error) {
172 		device_printf(dev, "could not allocate resources\n");
173 		return (ENXIO);
174 	}
175 
176 	mtx_init(&sc->wdt_mtx, "watchdog", NULL, MTX_DEF);
177 
178 	sc->wdt_config = (struct mv_wdt_config *)
179 	   ofw_bus_search_compatible(dev, mv_wdt_compat)->ocd_data;
180 
181 	if (sc->wdt_config->wdt_clock_src == 0)
182 		sc->wdt_config->wdt_clock_src = get_tclk();
183 
184 	if (wdt_softc->wdt_config->wdt_disable != NULL)
185 		wdt_softc->wdt_config->wdt_disable();
186 	EVENTHANDLER_REGISTER(watchdog_list, mv_watchdog_event, sc, 0);
187 
188 	return (0);
189 }
190 
191 static __inline uint32_t
192 mv_get_timer_control(void)
193 {
194 
195 	return (bus_read_4(wdt_softc->wdt_res, CPU_TIMER_CONTROL));
196 }
197 
198 static __inline void
199 mv_set_timer_control(uint32_t val)
200 {
201 
202 	bus_write_4(wdt_softc->wdt_res, CPU_TIMER_CONTROL, val);
203 }
204 
205 static __inline void
206 mv_set_timer(uint32_t timer, uint32_t val)
207 {
208 
209 	bus_write_4(wdt_softc->wdt_res, CPU_TIMER0 + timer * 0x8, val);
210 }
211 static void
212 mv_wdt_enable_armv5(void)
213 {
214 	uint32_t val, irq_cause, irq_mask;
215 
216 	irq_cause = read_cpu_ctrl(BRIDGE_IRQ_CAUSE);
217 	irq_cause &= IRQ_TIMER_WD_CLR;
218 	write_cpu_ctrl(BRIDGE_IRQ_CAUSE, irq_cause);
219 
220 	irq_mask = read_cpu_ctrl(BRIDGE_IRQ_MASK);
221 	irq_mask |= IRQ_TIMER_WD_MASK;
222 	write_cpu_ctrl(BRIDGE_IRQ_MASK, irq_mask);
223 
224 	val = read_cpu_ctrl(RSTOUTn_MASK);
225 	val |= WD_RST_OUT_EN;
226 	write_cpu_ctrl(RSTOUTn_MASK, val);
227 
228 	val = mv_get_timer_control();
229 	val |= CPU_TIMER2_EN | CPU_TIMER2_AUTO;
230 	mv_set_timer_control(val);
231 }
232 
233 static inline void
234 mv_wdt_enable_armada_38x_xp_helper(void)
235 {
236 	uint32_t val, irq_cause;
237 
238 	irq_cause = read_cpu_ctrl(BRIDGE_IRQ_CAUSE);
239 	irq_cause &= IRQ_TIMER_WD_CLR;
240 	write_cpu_ctrl(BRIDGE_IRQ_CAUSE, irq_cause);
241 
242 	val = read_cpu_mp_clocks(WD_RSTOUTn_MASK);
243 	val |= (WD_GLOBAL_MASK | WD_CPU0_MASK);
244 	write_cpu_mp_clocks(WD_RSTOUTn_MASK, val);
245 
246 	val = read_cpu_misc(RSTOUTn_MASK_ARMV7);
247 	val &= ~RSTOUTn_MASK_WD;
248 	write_cpu_misc(RSTOUTn_MASK_ARMV7, val);
249 }
250 
251 static void
252 mv_wdt_enable_armada_38x(void)
253 {
254 	uint32_t val, irq_cause;
255 
256 	irq_cause = read_cpu_ctrl(BRIDGE_IRQ_CAUSE);
257 	irq_cause &= IRQ_TIMER_WD_CLR;
258 	write_cpu_ctrl(BRIDGE_IRQ_CAUSE, irq_cause);
259 
260 	mv_wdt_enable_armada_38x_xp_helper();
261 
262 	val = mv_get_timer_control();
263 	val |= CPU_TIMER_WD_EN | CPU_TIMER_WD_AUTO | CPU_TIMER_WD_25MHZ_EN;
264 	mv_set_timer_control(val);
265 }
266 
267 static void
268 mv_wdt_enable_armada_xp(void)
269 {
270 	uint32_t val, irq_cause;
271 	irq_cause = read_cpu_ctrl(BRIDGE_IRQ_CAUSE_ARMADAXP);
272 	irq_cause &= IRQ_TIMER_WD_CLR_ARMADAXP;
273 	write_cpu_ctrl(BRIDGE_IRQ_CAUSE_ARMADAXP, irq_cause);
274 
275 	mv_wdt_enable_armada_38x_xp_helper();
276 
277 	val = mv_get_timer_control();
278 	val |= CPU_TIMER2_EN | CPU_TIMER2_AUTO | CPU_TIMER_WD_25MHZ_EN;
279 	mv_set_timer_control(val);
280 }
281 
282 static void
283 mv_wdt_disable_armv5(void)
284 {
285 	uint32_t val, irq_cause, irq_mask;
286 
287 	val = mv_get_timer_control();
288 	val &= ~(CPU_TIMER2_EN | CPU_TIMER2_AUTO);
289 	mv_set_timer_control(val);
290 
291 	val = read_cpu_ctrl(RSTOUTn_MASK);
292 	val &= ~WD_RST_OUT_EN;
293 	write_cpu_ctrl(RSTOUTn_MASK, val);
294 
295 	irq_mask = read_cpu_ctrl(BRIDGE_IRQ_MASK);
296 	irq_mask &= ~(IRQ_TIMER_WD_MASK);
297 	write_cpu_ctrl(BRIDGE_IRQ_MASK, irq_mask);
298 
299 	irq_cause = read_cpu_ctrl(BRIDGE_IRQ_CAUSE);
300 	irq_cause &= IRQ_TIMER_WD_CLR;
301 	write_cpu_ctrl(BRIDGE_IRQ_CAUSE, irq_cause);
302 }
303 
304 static __inline void
305 mv_wdt_disable_armada_38x_xp_helper(void)
306 {
307 	uint32_t val;
308 
309 	val = read_cpu_mp_clocks(WD_RSTOUTn_MASK);
310 	val &= ~(WD_GLOBAL_MASK | WD_CPU0_MASK);
311 	write_cpu_mp_clocks(WD_RSTOUTn_MASK, val);
312 
313 	val = read_cpu_misc(RSTOUTn_MASK_ARMV7);
314 	val |= RSTOUTn_MASK_WD;
315 	write_cpu_misc(RSTOUTn_MASK_ARMV7, RSTOUTn_MASK_WD);
316 }
317 
318 static void
319 mv_wdt_disable_armada_38x(void)
320 {
321 	uint32_t val;
322 
323 	val = mv_get_timer_control();
324 	val &= ~(CPU_TIMER_WD_EN | CPU_TIMER_WD_AUTO);
325 	mv_set_timer_control(val);
326 
327 	mv_wdt_disable_armada_38x_xp_helper();
328 }
329 
330 static void
331 mv_wdt_disable_armada_xp(void)
332 {
333 	uint32_t val;
334 
335 	val = mv_get_timer_control();
336 	val &= ~(CPU_TIMER2_EN | CPU_TIMER2_AUTO);
337 	mv_set_timer_control(val);
338 
339 	mv_wdt_disable_armada_38x_xp_helper();
340 }
341 
342 /*
343  * Watchdog event handler.
344  */
345 static void
346 mv_watchdog_event(void *arg, unsigned int cmd, int *error)
347 {
348 	struct mv_wdt_softc *sc;
349 	uint64_t ns;
350 	uint64_t ticks;
351 
352 	sc = arg;
353 	mtx_lock(&sc->wdt_mtx);
354 	if (cmd == 0) {
355 		if (wdt_softc->wdt_config->wdt_disable != NULL)
356 			wdt_softc->wdt_config->wdt_disable();
357 	} else {
358 		/*
359 		 * Watchdog timeout is in nanosecs, calculation according to
360 		 * watchdog(9)
361 		 */
362 		ns = (uint64_t)1 << (cmd & WD_INTERVAL);
363 		ticks = (uint64_t)(ns * sc->wdt_config->wdt_clock_src) / 1000000000;
364 		if (ticks > MAX_WATCHDOG_TICKS) {
365 			if (wdt_softc->wdt_config->wdt_disable != NULL)
366 				wdt_softc->wdt_config->wdt_disable();
367 		}
368 		else {
369 			mv_set_timer(wdt_softc->wdt_config->wdt_timer, ticks);
370 			if (wdt_softc->wdt_config->wdt_enable != NULL)
371 				wdt_softc->wdt_config->wdt_enable();
372 			*error = 0;
373 		}
374 	}
375 	mtx_unlock(&sc->wdt_mtx);
376 }
377