1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2019 Andriy Gapon
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28#include <sys/param.h>
29#include <sys/kernel.h>
30#include <sys/bus.h>
31#include <sys/eventhandler.h>
32#include <sys/module.h>
33#include <sys/rman.h>
34#include <sys/systm.h>
35#include <sys/watchdog.h>
36
37#include <dev/superio/superio.h>
38
39#include <machine/bus.h>
40#include <machine/resource.h>
41
42
43struct itwd_softc {
44	eventhandler_tag	wd_ev;
45	void			*intr_handle;
46	struct resource		*intr_res;
47	int			intr_rid;
48};
49
50static void
51wd_func(void *priv, u_int cmd, int *error)
52{
53	device_t dev = priv;
54	uint64_t timeout;
55	uint8_t val;
56
57
58	if (cmd != 0) {
59		cmd &= WD_INTERVAL;
60
61		/*
62		 * Convert the requested timeout to seconds.
63		 * If the timeout is smaller than the minimal supported value
64		 * then set it to the minimum.
65		 * TODO This hardware actually supports 64ms resolution
66		 * when bit 5 of 0x72 is set.  Switch to that resolution when
67		 * needed.
68		 */
69		if (cmd >= WD_TO_1SEC)
70			timeout = (uint64_t)1 << (cmd - WD_TO_1SEC);
71		else
72			timeout = 1;
73
74		/* TODO If timeout is greater than maximum value
75		 * that can be specified in seconds, we should
76		 * switch the timer to minutes mode by clearing
77		 * bit 7 of 0x72 (ensure that bit 5 is also cleared).
78		 *
79		 * For now, just disable the timer to honor the
80		 * watchdog(9) protocol.
81		 *
82		 * XXX The timeout actually can be up to 65535 units
83		 * as it is set via two registers 0x73, LSB, and 0x74,
84		 * MSB.  But it is not clear what the protocol for writing
85		 * those two registers is.
86		 */
87		if (timeout <= UINT8_MAX) {
88			val = timeout;
89			*error = 0;
90		} else {
91			/* error left unchanged */
92			val = 0;
93		}
94	} else {
95		val = 0;
96	}
97#ifdef DIAGNOSTIC
98	if (bootverbose)
99		device_printf(dev, "setting timeout to %d\n", val);
100#endif
101	superio_write(dev, 0x73, val);
102	if (superio_read(dev, 0x73) != val)
103		superio_write(dev, 0x73, val);
104}
105
106static void
107itwd_intr(void *cookie)
108{
109	device_t dev = cookie;
110	uint8_t val;
111
112	val = superio_read(dev, 0x71);
113	if (bootverbose)
114		device_printf(dev, "got interrupt, wdt status = %d\n", val & 1);
115	superio_write(dev, 0x71, val & ~((uint8_t)0x01));
116}
117
118static int
119itwd_probe(device_t dev)
120{
121
122	if (superio_vendor(dev) != SUPERIO_VENDOR_ITE)
123		return (ENXIO);
124	if (superio_get_type(dev) != SUPERIO_DEV_WDT)
125		return (ENXIO);
126	device_set_desc(dev, "Watchdog Timer on ITE SuperIO");
127	return (BUS_PROBE_DEFAULT);
128}
129
130static int
131itwd_attach(device_t dev)
132{
133	struct itwd_softc *sc = device_get_softc(dev);
134	int irq = 0;
135	int nmi = 0;
136	int error;
137
138	/* First, reset the timeout, just in case. */
139	superio_write(dev, 0x74, 0);
140	superio_write(dev, 0x73, 0);
141
142	TUNABLE_INT_FETCH("dev.itwd.irq", &irq);
143	TUNABLE_INT_FETCH("dev.itwd.nmi", &nmi);
144	if (irq < 0 || irq > 15) {
145		device_printf(dev, "Ignoring invalid IRQ value %d\n", irq);
146		irq = 0;
147	}
148	if (irq == 0 && nmi) {
149		device_printf(dev, "Ignoring NMI mode if IRQ is not set\n");
150		nmi = 0;
151	}
152
153	/*
154	 * NB: if the interrupt has been configured for the NMI delivery,
155	 * then it is not available for the regular interrupt allocation.
156	 * Thus, we configure the hardware to generate the interrupt,
157	 * but do not attempt to allocate and setup it as a regular
158	 * interrupt.
159	 */
160	if (irq != 0 && !nmi) {
161		sc->intr_rid = 0;
162		bus_set_resource(dev, SYS_RES_IRQ, sc->intr_rid, irq, 1);
163
164		sc->intr_res = bus_alloc_resource_any(dev, SYS_RES_IRQ,
165		    &sc->intr_rid, RF_ACTIVE);
166		if (sc->intr_res == NULL) {
167			device_printf(dev, "unable to map interrupt\n");
168			return (ENXIO);
169		}
170		error = bus_setup_intr(dev, sc->intr_res,
171		    INTR_TYPE_MISC | INTR_MPSAFE, NULL, itwd_intr, dev,
172		    &sc->intr_handle);
173		if (error != 0) {
174			bus_release_resource(dev, SYS_RES_IRQ,
175			    sc->intr_rid, sc->intr_res);
176			device_printf(dev, "Unable to setup irq: error %d\n",
177			    error);
178			return (ENXIO);
179		}
180	}
181	if (irq != 0) {
182		device_printf(dev, "Using IRQ%d to signal timeout\n", irq);
183	} else {
184		/* System reset via KBRST. */
185		irq = 0x40;
186		device_printf(dev, "Configured for system reset on timeout\n");
187	}
188
189	superio_write(dev, 0x71, 0);
190	superio_write(dev, 0x72, 0x80 | (uint8_t)irq);
191
192	sc->wd_ev = EVENTHANDLER_REGISTER(watchdog_list, wd_func, dev, 0);
193	return (0);
194}
195
196static int
197itwd_detach(device_t dev)
198{
199	struct itwd_softc *sc = device_get_softc(dev);
200	int dummy;
201
202	if (sc->wd_ev != NULL)
203		EVENTHANDLER_DEREGISTER(watchdog_list, sc->wd_ev);
204	wd_func(dev, 0, &dummy);
205	if (sc->intr_handle)
206		bus_teardown_intr(dev, sc->intr_res, sc->intr_handle);
207	if (sc->intr_res)
208		bus_release_resource(dev, SYS_RES_IRQ, sc->intr_rid,
209		    sc->intr_res);
210	return (0);
211}
212
213static device_method_t itwd_methods[] = {
214	/* Methods from the device interface */
215	DEVMETHOD(device_probe,		itwd_probe),
216	DEVMETHOD(device_attach,	itwd_attach),
217	DEVMETHOD(device_detach,	itwd_detach),
218
219	/* Terminate method list */
220	{ 0, 0 }
221};
222
223static driver_t itwd_driver = {
224	"itwd",
225	itwd_methods,
226	sizeof (struct itwd_softc)
227};
228
229DRIVER_MODULE(itwd, superio, itwd_driver, NULL, NULL);
230MODULE_DEPEND(itwd, superio, 1, 1, 1);
231MODULE_VERSION(itwd, 1);
232