dwcwdt_fdt.c revision 1.4
1/* $NetBSD: dwcwdt_fdt.c,v 1.4 2019/10/19 13:08:52 tnn Exp $ */
2
3/*-
4 * Copyright (c) 2018 Jared McNeill <jmcneill@invisible.ca>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29#include <sys/cdefs.h>
30__KERNEL_RCSID(0, "$NetBSD: dwcwdt_fdt.c,v 1.4 2019/10/19 13:08:52 tnn Exp $");
31
32#include <sys/param.h>
33#include <sys/bus.h>
34#include <sys/device.h>
35#include <sys/intr.h>
36#include <sys/systm.h>
37#include <sys/mutex.h>
38#include <sys/wdog.h>
39
40#include <dev/sysmon/sysmonvar.h>
41
42#include <dev/fdt/fdtvar.h>
43
44#define	WDT_CR				0x00
45#define	 WDT_CR_RST_PULSE_LENGTH	__BITS(4,2)
46#define	 WDT_CR_RESP_MODE		__BIT(1)
47#define	 WDT_CR_WDT_EN			__BIT(0)
48
49#define	WDT_TORR			0x04
50#define	 WDT_TORR_TIMEOUT_PERIOD	__BITS(3,0)
51
52#define	WDT_CCVR			0x08
53
54#define	WDT_CRR				0x0c
55#define	 WDT_CRR_CNT_RESTART		__BITS(7,0)
56#define	  WDT_CRR_CNT_RESTART_MAGIC	0x76
57
58#define	WDT_STAT			0x10
59#define	 WDT_STAT_WDT_STATUS		__BIT(0)
60
61#define	WDT_EOI				0x14
62#define	 WDT_EOI_WDT_INT_CLR		__BIT(0)
63
64static const uint32_t wdt_torr[] = {
65	0x0000ffff,
66	0x0001ffff,
67	0x0003ffff,
68	0x0007ffff,
69	0x000fffff,
70	0x001fffff,
71	0x003fffff,
72	0x007fffff,
73	0x00ffffff,
74	0x01ffffff,
75	0x03ffffff,
76	0x07ffffff,
77	0x0fffffff,
78	0x1fffffff,
79	0x3fffffff,
80	0x7fffffff,
81};
82
83#define	DWCWDT_PERIOD_DEFAULT		15
84
85static const char * const compatible[] = {
86	"snps,dw-wdt",
87	NULL
88};
89
90struct dwcwdt_softc {
91	device_t sc_dev;
92	bus_space_tag_t sc_bst;
93	bus_space_handle_t sc_bsh;
94	struct sysmon_wdog sc_smw;
95	u_int sc_clkrate;
96};
97
98#define RD4(sc, reg) \
99	bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
100#define WR4(sc, reg, val) \
101	bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
102
103static int
104dwcwdt_map_period(struct dwcwdt_softc *sc, u_int period,
105    u_int *aperiod)
106{
107	int i;
108
109	if (period == 0)
110		return -1;
111
112	for (i = 0; i < __arraycount(wdt_torr); i++) {
113		const u_int ms = (u_int)((((uint64_t)wdt_torr[i] + 1) * 1000) / sc->sc_clkrate);
114		if (ms >= period * 1000) {
115			*aperiod = ms / 1000;
116			return i;
117		}
118	}
119
120	return -1;
121}
122
123static int
124dwcwdt_tickle(struct sysmon_wdog *smw)
125{
126	struct dwcwdt_softc * const sc = smw->smw_cookie;
127	const uint32_t crr =
128	    __SHIFTIN(WDT_CRR_CNT_RESTART_MAGIC, WDT_CRR_CNT_RESTART);
129
130	WR4(sc, WDT_CRR, crr);
131
132	return 0;
133}
134
135static int
136dwcwdt_setmode(struct sysmon_wdog *smw)
137{
138	struct dwcwdt_softc * const sc = smw->smw_cookie;
139	uint32_t cr, torr;
140	int intv;
141
142	if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) {
143		/* Watchdog can only be disarmed by a reset */
144		return EIO;
145	}
146
147	if (smw->smw_period == WDOG_PERIOD_DEFAULT)
148		smw->smw_period = DWCWDT_PERIOD_DEFAULT;
149
150	intv = dwcwdt_map_period(sc, smw->smw_period,
151	    &sc->sc_smw.smw_period);
152	if (intv == -1)
153		return EINVAL;
154
155	torr = __SHIFTIN(intv, WDT_TORR_TIMEOUT_PERIOD);
156	WR4(sc, WDT_TORR, torr);
157	dwcwdt_tickle(smw);
158	cr = RD4(sc, WDT_CR);
159	cr &= ~WDT_CR_RESP_MODE;
160	cr |= WDT_CR_WDT_EN;
161	WR4(sc, WDT_CR, cr);
162
163	return 0;
164}
165
166static int
167dwcwdt_match(device_t parent, cfdata_t cf, void *aux)
168{
169	struct fdt_attach_args * const faa = aux;
170
171	return of_match_compatible(faa->faa_phandle, compatible);
172}
173
174static void
175dwcwdt_attach(device_t parent, device_t self, void *aux)
176{
177	struct dwcwdt_softc * const sc = device_private(self);
178	struct fdt_attach_args * const faa = aux;
179	const int phandle = faa->faa_phandle;
180	struct fdtbus_reset *rst;
181	struct clk *clk;
182	bus_addr_t addr;
183	bus_size_t size;
184
185	if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
186		aprint_error(": couldn't get registers\n");
187		return;
188	}
189
190	clk = fdtbus_clock_get_index(phandle, 0);
191	if (clk == NULL || clk_enable(clk) != 0) {
192		aprint_error(": couldn't enable clock\n");
193		return;
194	}
195	rst = fdtbus_reset_get_index(phandle, 0);
196	if (rst && fdtbus_reset_assert(rst) != 0) {
197		aprint_error(": couldn't assert reset\n");
198		return;
199	}
200	if (rst && fdtbus_reset_deassert(rst) != 0) {
201		aprint_error(": couldn't de-assert reset\n");
202		return;
203	}
204
205	sc->sc_dev = self;
206	sc->sc_bst = faa->faa_bst;
207	if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) {
208		aprint_error(": couldn't map registers\n");
209		return;
210	}
211	sc->sc_clkrate = clk_get_rate(clk);
212
213	aprint_naive("\n");
214	aprint_normal(": DesignWare Watchdog Timer\n");
215
216	sc->sc_smw.smw_name = device_xname(self);
217	sc->sc_smw.smw_cookie = sc;
218	sc->sc_smw.smw_period = DWCWDT_PERIOD_DEFAULT;
219	sc->sc_smw.smw_setmode = dwcwdt_setmode;
220	sc->sc_smw.smw_tickle = dwcwdt_tickle;
221
222	aprint_normal_dev(self,
223	    "default watchdog period is %u seconds\n",
224	    sc->sc_smw.smw_period);
225
226	if (sysmon_wdog_register(&sc->sc_smw) != 0)
227		aprint_error_dev(self, "couldn't register with sysmon\n");
228}
229
230CFATTACH_DECL_NEW(dwcwdt_fdt, sizeof(struct dwcwdt_softc),
231	dwcwdt_match, dwcwdt_attach, NULL, NULL);
232