dwcwdt_fdt.c revision 1.2
1/* $NetBSD: dwcwdt_fdt.c,v 1.2 2018/10/14 18:28:55 aymeric 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.2 2018/10/14 18:28:55 aymeric 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) {
115			*aperiod = ms;
116			return i;
117		}
118	}
119
120	return -1;
121}
122
123static int
124dwcwdt_setmode(struct sysmon_wdog *smw)
125{
126	struct dwcwdt_softc * const sc = smw->smw_cookie;
127	uint32_t cr, torr;
128	int intv;
129
130	if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED)
131		return EIO;
132
133	if (smw->smw_period == WDOG_PERIOD_DEFAULT)
134		smw->smw_period = DWCWDT_PERIOD_DEFAULT;
135
136	intv = dwcwdt_map_period(sc, smw->smw_period,
137	    &sc->sc_smw.smw_period);
138	if (intv == -1)
139		return EINVAL;
140
141	torr = __SHIFTIN(intv, WDT_TORR_TIMEOUT_PERIOD);
142	WR4(sc, WDT_TORR, torr);
143
144	cr = RD4(sc, WDT_CR);
145	cr &= ~WDT_CR_RESP_MODE;
146	cr |= WDT_CR_WDT_EN;
147	WR4(sc, WDT_CR, cr);
148
149	return 0;
150}
151
152static int
153dwcwdt_tickle(struct sysmon_wdog *smw)
154{
155	struct dwcwdt_softc * const sc = smw->smw_cookie;
156	const uint32_t crr =
157	    __SHIFTIN(WDT_CRR_CNT_RESTART_MAGIC, WDT_CRR_CNT_RESTART);
158
159	WR4(sc, WDT_CRR, crr);
160
161	return 0;
162}
163
164static int
165dwcwdt_match(device_t parent, cfdata_t cf, void *aux)
166{
167	struct fdt_attach_args * const faa = aux;
168
169	return of_match_compatible(faa->faa_phandle, compatible);
170}
171
172static void
173dwcwdt_attach(device_t parent, device_t self, void *aux)
174{
175	struct dwcwdt_softc * const sc = device_private(self);
176	struct fdt_attach_args * const faa = aux;
177	const int phandle = faa->faa_phandle;
178	struct fdtbus_reset *rst;
179	struct clk *clk;
180	bus_addr_t addr;
181	bus_size_t size;
182
183	if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
184		aprint_error(": couldn't get registers\n");
185		return;
186	}
187
188	clk = fdtbus_clock_get_index(phandle, 0);
189	if (clk == NULL || clk_enable(clk) != 0) {
190		aprint_error(": couldn't enable clock\n");
191		return;
192	}
193	rst = fdtbus_reset_get_index(phandle, 0);
194	if (rst && fdtbus_reset_deassert(rst) != 0) {
195		aprint_error(": couldn't de-assert reset\n");
196		return;
197	}
198
199	sc->sc_dev = self;
200	sc->sc_bst = faa->faa_bst;
201	if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) {
202		aprint_error(": couldn't map registers\n");
203		return;
204	}
205	sc->sc_clkrate = clk_get_rate(clk);
206
207	aprint_naive("\n");
208	aprint_normal(": DesignWare Watchdog Timer\n");
209
210	sc->sc_smw.smw_name = device_xname(self);
211	sc->sc_smw.smw_cookie = sc;
212	sc->sc_smw.smw_period = DWCWDT_PERIOD_DEFAULT;
213	sc->sc_smw.smw_setmode = dwcwdt_setmode;
214	sc->sc_smw.smw_tickle = dwcwdt_tickle;
215
216	aprint_normal_dev(self,
217	    "default watchdog period is %u seconds\n",
218	    sc->sc_smw.smw_period);
219
220	if (sysmon_wdog_register(&sc->sc_smw) != 0)
221		aprint_error_dev(self, "couldn't register with sysmon\n");
222}
223
224CFATTACH_DECL_NEW(dwcwdt_fdt, sizeof(struct dwcwdt_softc),
225	dwcwdt_match, dwcwdt_attach, NULL, NULL);
226