xref: /netbsd-src/sys/arch/arm/sunxi/sunxi_wdt.c (revision 6e54367a22fbc89a1139d033e95bec0c0cf0975b)
1*6e54367aSthorpej /* $NetBSD: sunxi_wdt.c,v 1.6 2021/01/27 03:10:20 thorpej Exp $ */
2fa2ad6afSjmcneill 
3fa2ad6afSjmcneill /*-
4fa2ad6afSjmcneill  * Copyright (c) 2017 Jared McNeill <jmcneill@invisible.ca>
5fa2ad6afSjmcneill  * All rights reserved.
6fa2ad6afSjmcneill  *
7fa2ad6afSjmcneill  * Redistribution and use in source and binary forms, with or without
8fa2ad6afSjmcneill  * modification, are permitted provided that the following conditions
9fa2ad6afSjmcneill  * are met:
10fa2ad6afSjmcneill  * 1. Redistributions of source code must retain the above copyright
11fa2ad6afSjmcneill  *    notice, this list of conditions and the following disclaimer.
12fa2ad6afSjmcneill  * 2. Redistributions in binary form must reproduce the above copyright
13fa2ad6afSjmcneill  *    notice, this list of conditions and the following disclaimer in the
14fa2ad6afSjmcneill  *    documentation and/or other materials provided with the distribution.
15fa2ad6afSjmcneill  *
16fa2ad6afSjmcneill  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17fa2ad6afSjmcneill  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18fa2ad6afSjmcneill  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19fa2ad6afSjmcneill  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20fa2ad6afSjmcneill  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21fa2ad6afSjmcneill  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22fa2ad6afSjmcneill  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23fa2ad6afSjmcneill  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24fa2ad6afSjmcneill  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25fa2ad6afSjmcneill  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26fa2ad6afSjmcneill  * SUCH DAMAGE.
27fa2ad6afSjmcneill  */
28fa2ad6afSjmcneill 
29fa2ad6afSjmcneill #include <sys/cdefs.h>
30*6e54367aSthorpej __KERNEL_RCSID(0, "$NetBSD: sunxi_wdt.c,v 1.6 2021/01/27 03:10:20 thorpej Exp $");
31fa2ad6afSjmcneill 
32fa2ad6afSjmcneill #include <sys/param.h>
33fa2ad6afSjmcneill #include <sys/bus.h>
34fa2ad6afSjmcneill #include <sys/device.h>
35fa2ad6afSjmcneill #include <sys/intr.h>
36fa2ad6afSjmcneill #include <sys/systm.h>
37fa2ad6afSjmcneill #include <sys/mutex.h>
38fa2ad6afSjmcneill #include <sys/wdog.h>
39fa2ad6afSjmcneill 
40fa2ad6afSjmcneill #include <dev/sysmon/sysmonvar.h>
41fa2ad6afSjmcneill 
42fa2ad6afSjmcneill #include <dev/fdt/fdtvar.h>
43fa2ad6afSjmcneill 
44fa2ad6afSjmcneill #define	SUNXI_WDT_PERIOD_DEFAULT	16
45fa2ad6afSjmcneill 
469f3abf71Sjmcneill #define	SUN4I_WDT_CTRL_REG		0x00
479f3abf71Sjmcneill #define	 SUN4I_WDT_CTRL_KEY_FIELD	__BITS(12,1)
489f3abf71Sjmcneill #define	  SUN4I_WDT_CTRL_KEY_FIELD_V	0xa57
499f3abf71Sjmcneill #define	 SUN4I_WDT_CTRL_RSTART		__BIT(0)
509f3abf71Sjmcneill #define	SUN4I_WDT_MODE_REG		0x04
519f3abf71Sjmcneill #define	 SUN4I_WDT_MODE_INTV		__BITS(6,3)
529f3abf71Sjmcneill #define	 SUN4I_WDT_MODE_RST_EN		__BIT(1)
539f3abf71Sjmcneill #define	 SUN4I_WDT_MODE_EN		__BIT(0)
54fa2ad6afSjmcneill 
559f3abf71Sjmcneill #define	SUN6I_WDT_IRQ_EN_REG		0x00
569f3abf71Sjmcneill #define	 SUN6I_WDT_IRQ_EN_EN		__BIT(0)
579f3abf71Sjmcneill #define	SUN6I_WDT_IRQ_STA_REG		0x04
589f3abf71Sjmcneill #define	 SUN6I_WDT_IRQ_STA_PEND		__BIT(0)
599f3abf71Sjmcneill #define	SUN6I_WDT_CTRL_REG		0x10
609f3abf71Sjmcneill #define	 SUN6I_WDT_CTRL_KEY_FIELD	__BITS(12,1)
619f3abf71Sjmcneill #define	  SUN6I_WDT_CTRL_KEY_FIELD_V	0xa57
629f3abf71Sjmcneill #define	 SUN6I_WDT_CTRL_RSTART		__BIT(0)
639f3abf71Sjmcneill #define	SUN6I_WDT_CFG_REG		0x14
649f3abf71Sjmcneill #define	 SUN6I_WDT_CFG_CONFIG		__BITS(1,0)
659f3abf71Sjmcneill #define	  SUN6I_WDT_CFG_CONFIG_SYS	1
669f3abf71Sjmcneill #define	  SUN6I_WDT_CFG_CONFIG_IRQ	2
679f3abf71Sjmcneill #define	SUN6I_WDT_MODE_REG		0x18
689f3abf71Sjmcneill #define	 SUN6I_WDT_MODE_INTV		__BITS(7,4)
699f3abf71Sjmcneill #define	 SUN6I_WDT_MODE_EN		__BIT(0)
709f3abf71Sjmcneill 
719f3abf71Sjmcneill static const int sunxi_periods[] = {
72fa2ad6afSjmcneill 	500, 1000, 2000, 3000,
73fa2ad6afSjmcneill 	4000, 5000, 6000, 8000,
74fa2ad6afSjmcneill 	10000, 12000, 14000, 16000,
75fa2ad6afSjmcneill 	-1
76fa2ad6afSjmcneill };
77fa2ad6afSjmcneill 
789f3abf71Sjmcneill enum sunxi_wdt_type {
799f3abf71Sjmcneill 	WDT_SUN4I = 1,
809f3abf71Sjmcneill 	WDT_SUN6I,
819f3abf71Sjmcneill };
829f3abf71Sjmcneill 
83646c0f59Sthorpej static const struct device_compatible_entry compat_data[] = {
84646c0f59Sthorpej 	{ .compat = "allwinner,sun4i-a10-wdt",	.value = WDT_SUN4I },
85646c0f59Sthorpej 	{ .compat = "allwinner,sun6i-a31-wdt",	.value = WDT_SUN6I },
86ec189949Sthorpej 	DEVICE_COMPAT_EOL
87fa2ad6afSjmcneill };
88fa2ad6afSjmcneill 
89fa2ad6afSjmcneill struct sunxi_wdt_softc {
90fa2ad6afSjmcneill 	device_t sc_dev;
91fa2ad6afSjmcneill 	bus_space_tag_t sc_bst;
92fa2ad6afSjmcneill 	bus_space_handle_t sc_bsh;
93fa2ad6afSjmcneill 
94fa2ad6afSjmcneill 	const int *sc_periods;
95fa2ad6afSjmcneill 
96fa2ad6afSjmcneill 	struct sysmon_wdog sc_smw;
97fa2ad6afSjmcneill };
98fa2ad6afSjmcneill 
99fa2ad6afSjmcneill #define WDT_READ(sc, reg) \
100fa2ad6afSjmcneill     bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
101fa2ad6afSjmcneill #define WDT_WRITE(sc, reg, val) \
102fa2ad6afSjmcneill     bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
103fa2ad6afSjmcneill 
104fa2ad6afSjmcneill static int
sunxi_wdt_map_period(struct sunxi_wdt_softc * sc,u_int period,u_int * aperiod)105fa2ad6afSjmcneill sunxi_wdt_map_period(struct sunxi_wdt_softc *sc, u_int period,
106fa2ad6afSjmcneill     u_int *aperiod)
107fa2ad6afSjmcneill {
108fa2ad6afSjmcneill 	const int *p = sc->sc_periods;
109fa2ad6afSjmcneill 	int i;
110fa2ad6afSjmcneill 
111fa2ad6afSjmcneill 	if (period == 0)
112fa2ad6afSjmcneill 		return -1;
113fa2ad6afSjmcneill 
114fa2ad6afSjmcneill 	for (i = 0; *p != -1; i++, p++)
115fa2ad6afSjmcneill 		if (*p >= period * 1000) {
116fa2ad6afSjmcneill 			*aperiod = *p / 1000;
117fa2ad6afSjmcneill 			return i;
118fa2ad6afSjmcneill 		}
119fa2ad6afSjmcneill 
120fa2ad6afSjmcneill 	return -1;
121fa2ad6afSjmcneill }
122fa2ad6afSjmcneill 
123fa2ad6afSjmcneill static int
sun4i_wdt_setmode(struct sysmon_wdog * smw)1249f3abf71Sjmcneill sun4i_wdt_setmode(struct sysmon_wdog *smw)
125fa2ad6afSjmcneill {
126fa2ad6afSjmcneill 	struct sunxi_wdt_softc * const sc = smw->smw_cookie;
1279f3abf71Sjmcneill 	uint32_t mode;
128fa2ad6afSjmcneill 	int intv;
129fa2ad6afSjmcneill 
130fa2ad6afSjmcneill 	if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) {
1319f3abf71Sjmcneill 		WDT_WRITE(sc, SUN4I_WDT_MODE_REG, 0);
132fa2ad6afSjmcneill 	} else {
133fa2ad6afSjmcneill 		if (smw->smw_period == WDOG_PERIOD_DEFAULT)
134fa2ad6afSjmcneill 			smw->smw_period = SUNXI_WDT_PERIOD_DEFAULT;
135fa2ad6afSjmcneill 		intv = sunxi_wdt_map_period(sc, smw->smw_period,
136fa2ad6afSjmcneill 		    &sc->sc_smw.smw_period);
137fa2ad6afSjmcneill 		if (intv == -1)
138fa2ad6afSjmcneill 			return EINVAL;
139fa2ad6afSjmcneill 
1409f3abf71Sjmcneill 		mode = SUN4I_WDT_MODE_EN | SUN4I_WDT_MODE_RST_EN |
1419f3abf71Sjmcneill 		    __SHIFTIN(intv, SUN4I_WDT_MODE_INTV);
142fa2ad6afSjmcneill 
1439f3abf71Sjmcneill 		WDT_WRITE(sc, SUN4I_WDT_MODE_REG, mode);
144fa2ad6afSjmcneill 	}
145fa2ad6afSjmcneill 
146fa2ad6afSjmcneill 	return 0;
147fa2ad6afSjmcneill }
148fa2ad6afSjmcneill 
149fa2ad6afSjmcneill static int
sun4i_wdt_tickle(struct sysmon_wdog * smw)1509f3abf71Sjmcneill sun4i_wdt_tickle(struct sysmon_wdog *smw)
151fa2ad6afSjmcneill {
152fa2ad6afSjmcneill 	struct sunxi_wdt_softc * const sc = smw->smw_cookie;
1539f3abf71Sjmcneill 	const uint32_t ctrl = SUN4I_WDT_CTRL_RSTART |
1549f3abf71Sjmcneill 	    __SHIFTIN(SUN4I_WDT_CTRL_KEY_FIELD_V, SUN4I_WDT_CTRL_KEY_FIELD);
155fa2ad6afSjmcneill 
1569f3abf71Sjmcneill 	WDT_WRITE(sc, SUN4I_WDT_CTRL_REG, ctrl);
1579f3abf71Sjmcneill 
1589f3abf71Sjmcneill 	return 0;
1599f3abf71Sjmcneill }
1609f3abf71Sjmcneill 
1619f3abf71Sjmcneill static int
sun6i_wdt_setmode(struct sysmon_wdog * smw)1629f3abf71Sjmcneill sun6i_wdt_setmode(struct sysmon_wdog *smw)
1639f3abf71Sjmcneill {
1649f3abf71Sjmcneill 	struct sunxi_wdt_softc * const sc = smw->smw_cookie;
1659f3abf71Sjmcneill 	uint32_t cfg, mode;
1669f3abf71Sjmcneill 	int intv;
1679f3abf71Sjmcneill 
1689f3abf71Sjmcneill 	if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) {
1699f3abf71Sjmcneill 		WDT_WRITE(sc, SUN6I_WDT_MODE_REG, 0);
1709f3abf71Sjmcneill 	} else {
1719f3abf71Sjmcneill 		if (smw->smw_period == WDOG_PERIOD_DEFAULT)
1729f3abf71Sjmcneill 			smw->smw_period = SUNXI_WDT_PERIOD_DEFAULT;
1739f3abf71Sjmcneill 		intv = sunxi_wdt_map_period(sc, smw->smw_period,
1749f3abf71Sjmcneill 		    &sc->sc_smw.smw_period);
1759f3abf71Sjmcneill 		if (intv == -1)
1769f3abf71Sjmcneill 			return EINVAL;
1779f3abf71Sjmcneill 
1789f3abf71Sjmcneill 		cfg = __SHIFTIN(SUN6I_WDT_CFG_CONFIG_SYS, SUN6I_WDT_CFG_CONFIG);
1799f3abf71Sjmcneill 		mode = SUN6I_WDT_MODE_EN | __SHIFTIN(intv, SUN6I_WDT_MODE_INTV);
1809f3abf71Sjmcneill 
1819f3abf71Sjmcneill 		WDT_WRITE(sc, SUN6I_WDT_CFG_REG, cfg);
1829f3abf71Sjmcneill 		WDT_WRITE(sc, SUN6I_WDT_MODE_REG, mode);
1839f3abf71Sjmcneill 	}
1849f3abf71Sjmcneill 
1859f3abf71Sjmcneill 	return 0;
1869f3abf71Sjmcneill }
1879f3abf71Sjmcneill 
1889f3abf71Sjmcneill static int
sun6i_wdt_tickle(struct sysmon_wdog * smw)1899f3abf71Sjmcneill sun6i_wdt_tickle(struct sysmon_wdog *smw)
1909f3abf71Sjmcneill {
1919f3abf71Sjmcneill 	struct sunxi_wdt_softc * const sc = smw->smw_cookie;
1929f3abf71Sjmcneill 	const uint32_t ctrl = SUN6I_WDT_CTRL_RSTART |
1939f3abf71Sjmcneill 	    __SHIFTIN(SUN6I_WDT_CTRL_KEY_FIELD_V, SUN6I_WDT_CTRL_KEY_FIELD);
1949f3abf71Sjmcneill 
1959f3abf71Sjmcneill 	WDT_WRITE(sc, SUN6I_WDT_CTRL_REG, ctrl);
196fa2ad6afSjmcneill 
197fa2ad6afSjmcneill 	return 0;
198fa2ad6afSjmcneill }
199fa2ad6afSjmcneill 
200fa2ad6afSjmcneill static int
sunxi_wdt_match(device_t parent,cfdata_t cf,void * aux)201fa2ad6afSjmcneill sunxi_wdt_match(device_t parent, cfdata_t cf, void *aux)
202fa2ad6afSjmcneill {
203fa2ad6afSjmcneill 	struct fdt_attach_args * const faa = aux;
204fa2ad6afSjmcneill 
205*6e54367aSthorpej 	return of_compatible_match(faa->faa_phandle, compat_data);
206fa2ad6afSjmcneill }
207fa2ad6afSjmcneill 
208fa2ad6afSjmcneill static void
sunxi_wdt_attach(device_t parent,device_t self,void * aux)209fa2ad6afSjmcneill sunxi_wdt_attach(device_t parent, device_t self, void *aux)
210fa2ad6afSjmcneill {
211fa2ad6afSjmcneill 	struct sunxi_wdt_softc * const sc = device_private(self);
212fa2ad6afSjmcneill 	struct fdt_attach_args * const faa = aux;
213fa2ad6afSjmcneill 	const int phandle = faa->faa_phandle;
2149f3abf71Sjmcneill 	enum sunxi_wdt_type type;
215fa2ad6afSjmcneill 	bus_addr_t addr;
216fa2ad6afSjmcneill 	bus_size_t size;
217fa2ad6afSjmcneill 
218fa2ad6afSjmcneill 	if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
219fa2ad6afSjmcneill 		aprint_error(": couldn't get registers\n");
220fa2ad6afSjmcneill 		return;
221fa2ad6afSjmcneill 	}
222fa2ad6afSjmcneill 
223fa2ad6afSjmcneill 	sc->sc_dev = self;
224fa2ad6afSjmcneill 	sc->sc_bst = faa->faa_bst;
225fa2ad6afSjmcneill 	if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) {
226fa2ad6afSjmcneill 		aprint_error(": couldn't map registers\n");
227fa2ad6afSjmcneill 		return;
228fa2ad6afSjmcneill 	}
2299f3abf71Sjmcneill 	sc->sc_periods = sunxi_periods;
230fa2ad6afSjmcneill 
231fa2ad6afSjmcneill 	aprint_naive("\n");
232fa2ad6afSjmcneill 	aprint_normal(": Watchdog\n");
233fa2ad6afSjmcneill 
234fa2ad6afSjmcneill 	sc->sc_smw.smw_name = device_xname(self);
235fa2ad6afSjmcneill 	sc->sc_smw.smw_cookie = sc;
236fa2ad6afSjmcneill 	sc->sc_smw.smw_period = SUNXI_WDT_PERIOD_DEFAULT;
237fa2ad6afSjmcneill 
238*6e54367aSthorpej 	type = of_compatible_lookup(phandle, compat_data)->value;
2399f3abf71Sjmcneill 	switch (type) {
2409f3abf71Sjmcneill 	case WDT_SUN4I:
2419f3abf71Sjmcneill 		sc->sc_smw.smw_setmode = sun4i_wdt_setmode;
2429f3abf71Sjmcneill 		sc->sc_smw.smw_tickle = sun4i_wdt_tickle;
2439f3abf71Sjmcneill 		break;
2449f3abf71Sjmcneill 	case WDT_SUN6I:
2459f3abf71Sjmcneill 		sc->sc_smw.smw_setmode = sun6i_wdt_setmode;
2469f3abf71Sjmcneill 		sc->sc_smw.smw_tickle = sun6i_wdt_tickle;
2479f3abf71Sjmcneill 
2489f3abf71Sjmcneill 		/* Disable watchdog IRQs */
2499f3abf71Sjmcneill 		WDT_WRITE(sc, SUN6I_WDT_IRQ_EN_REG, 0);
2509f3abf71Sjmcneill 		break;
2519f3abf71Sjmcneill 	}
2529f3abf71Sjmcneill 
253fa2ad6afSjmcneill 	aprint_normal_dev(self,
254fa2ad6afSjmcneill 	    "default watchdog period is %u seconds\n",
255fa2ad6afSjmcneill 	    sc->sc_smw.smw_period);
256fa2ad6afSjmcneill 
257fa2ad6afSjmcneill 	if (sysmon_wdog_register(&sc->sc_smw) != 0) {
258fa2ad6afSjmcneill 		aprint_error_dev(self,
259fa2ad6afSjmcneill 		    "couldn't register with sysmon\n");
260fa2ad6afSjmcneill 	}
261fa2ad6afSjmcneill }
262fa2ad6afSjmcneill 
263fa2ad6afSjmcneill CFATTACH_DECL_NEW(sunxi_wdt, sizeof(struct sunxi_wdt_softc),
264fa2ad6afSjmcneill 	sunxi_wdt_match, sunxi_wdt_attach, NULL, NULL);
265