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