xref: /freebsd/sys/dev/viawd/viawd.c (revision 61af1d13936ec56808f62d13dd8698f73b440dc1)
1 /*-
2  * Copyright (c) 2011 Fabien Thomas <fthomas@FreeBSD.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26 
27 #include <sys/cdefs.h>
28 __FBSDID("$FreeBSD$");
29 
30 #include <sys/param.h>
31 #include <sys/kernel.h>
32 #include <sys/module.h>
33 #include <sys/systm.h>
34 #include <sys/bus.h>
35 #include <machine/bus.h>
36 #include <sys/rman.h>
37 #include <machine/resource.h>
38 #include <sys/watchdog.h>
39 
40 #include <isa/isavar.h>
41 #include <dev/pci/pcivar.h>
42 
43 #include "viawd.h"
44 
45 #define viawd_read_wd_4(sc, off) \
46 	bus_space_read_4((sc)->wd_bst, (sc)->wd_bsh, (off))
47 #define viawd_write_wd_4(sc, off, val) \
48 	bus_space_write_4((sc)->wd_bst, (sc)->wd_bsh, (off), (val))
49 
50 static struct viawd_device viawd_devices[] = {
51 	{ DEVICEID_VT8251, "VIA VT8251 watchdog timer" },
52 	{ DEVICEID_CX700,  "VIA CX700 watchdog timer" },
53 	{ DEVICEID_VX800,  "VIA VX800 watchdog timer" },
54 	{ DEVICEID_VX855,  "VIA VX855 watchdog timer" },
55 	{ DEVICEID_VX900,  "VIA VX900 watchdog timer" },
56 	{ 0, NULL },
57 };
58 
59 static devclass_t viawd_devclass;
60 
61 static device_t
62 viawd_find(struct viawd_device **id_p)
63 {
64 	struct viawd_device *id;
65 	device_t sb_dev = NULL;
66 
67 	/* Look for a supported VIA south bridge. */
68 	for (id = viawd_devices; id->desc != NULL; ++id)
69 		if ((sb_dev = pci_find_device(VENDORID_VIA, id->device)) != NULL)
70 			break;
71 
72 	if (sb_dev == NULL)
73 		return (NULL);
74 
75 	if (id_p != NULL)
76 		*id_p = id;
77 
78 	return (sb_dev);
79 }
80 
81 static void
82 viawd_tmr_state(struct viawd_softc *sc, int enable)
83 {
84 	uint32_t reg;
85 
86 	reg = viawd_read_wd_4(sc, VIAWD_MEM_CTRL);
87 	if (enable)
88 		reg |= VIAWD_MEM_CTRL_TRIGGER | VIAWD_MEM_CTRL_ENABLE;
89 	else
90 		reg &= ~VIAWD_MEM_CTRL_ENABLE;
91 	viawd_write_wd_4(sc, VIAWD_MEM_CTRL, reg);
92 }
93 
94 static void
95 viawd_tmr_set(struct viawd_softc *sc, unsigned int timeout)
96 {
97 
98 	/* Keep value in range. */
99 	if (timeout < VIAWD_MEM_COUNT_MIN)
100 		timeout = VIAWD_MEM_COUNT_MIN;
101 	else if (timeout > VIAWD_MEM_COUNT_MAX)
102 		timeout = VIAWD_MEM_COUNT_MAX;
103 
104 	viawd_write_wd_4(sc, VIAWD_MEM_COUNT, timeout);
105 	sc->timeout = timeout;
106 }
107 
108 /*
109  * Watchdog event handler - called by the framework to enable or disable
110  * the watchdog or change the initial timeout value.
111  */
112 static void
113 viawd_event(void *arg, unsigned int cmd, int *error)
114 {
115 	struct viawd_softc *sc = arg;
116 	unsigned int timeout;
117 
118 	/* Convert from power-of-two-ns to second. */
119 	cmd &= WD_INTERVAL;
120 	timeout = ((uint64_t)1 << cmd) / 1000000000;
121 	if (cmd) {
122 		if (timeout != sc->timeout)
123 			viawd_tmr_set(sc, timeout);
124 		viawd_tmr_state(sc, 1);
125 		*error = 0;
126 	} else
127 		viawd_tmr_state(sc, 0);
128 }
129 
130 static void
131 viawd_identify(driver_t *driver, device_t parent)
132 {
133 	device_t dev;
134 	device_t sb_dev;
135 	struct viawd_device *id_p;
136 
137 	sb_dev = viawd_find(&id_p);
138 	if (sb_dev == NULL)
139 		return;
140 
141 	/* Good, add child to bus. */
142 	if ((dev = device_find_child(parent, driver->name, 0)) == NULL)
143 		dev = BUS_ADD_CHILD(parent, 0, driver->name, 0);
144 
145 	if (dev == NULL)
146 		return;
147 
148 	device_set_desc_copy(dev, id_p->desc);
149 }
150 
151 static int
152 viawd_probe(device_t dev)
153 {
154 
155 	/* Do not claim some ISA PnP device by accident. */
156 	if (isa_get_logicalid(dev) != 0)
157 		return (ENXIO);
158 	return (0);
159 }
160 
161 static int
162 viawd_attach(device_t dev)
163 {
164 	device_t sb_dev;
165 	struct viawd_softc *sc;
166 	struct viawd_device *id_p;
167 	uint32_t pmbase, reg;
168 
169 	sc = device_get_softc(dev);
170 	sc->dev = dev;
171 
172 	sb_dev = viawd_find(&id_p);
173 	if (sb_dev == NULL) {
174 		device_printf(dev, "Can not find watchdog device.\n");
175 		goto fail;
176 	}
177 	sc->sb_dev = sb_dev;
178 
179 	/* Get watchdog memory base. */
180 	pmbase = pci_read_config(sb_dev, VIAWD_CONFIG_BASE, 4);
181 	if (pmbase == 0) {
182 		device_printf(dev,
183 		    "Watchdog disabled in BIOS or hardware\n");
184 		goto fail;
185 	}
186 
187 	/* Allocate I/O register space. */
188 	sc->wd_rid = 0;
189 	sc->wd_res = bus_alloc_resource(dev, SYS_RES_MEMORY, &sc->wd_rid,
190 	    pmbase, pmbase + VIAWD_MEM_LEN - 1, VIAWD_MEM_LEN,
191 	    RF_ACTIVE | RF_SHAREABLE);
192 	if (sc->wd_res == NULL) {
193 		device_printf(dev, "Unable to map watchdog memory\n");
194 		goto fail;
195 	}
196 	sc->wd_bst = rman_get_bustag(sc->wd_res);
197 	sc->wd_bsh = rman_get_bushandle(sc->wd_res);
198 
199 	/* Check if watchdog fired last boot. */
200 	reg = viawd_read_wd_4(sc, VIAWD_MEM_CTRL);
201 	if (reg & VIAWD_MEM_CTRL_FIRED) {
202 		device_printf(dev,
203 		    "ERROR: watchdog rebooted the system\n");
204 		/* Reset bit state. */
205 		viawd_write_wd_4(sc, VIAWD_MEM_CTRL, reg);
206 	}
207 
208 	/* Register the watchdog event handler. */
209 	sc->ev_tag = EVENTHANDLER_REGISTER(watchdog_list, viawd_event, sc, 0);
210 
211 	return (0);
212 fail:
213 	if (sc->wd_res != NULL)
214 		bus_release_resource(dev, SYS_RES_MEMORY,
215 		    sc->wd_rid, sc->wd_res);
216 	return (ENXIO);
217 }
218 
219 static int
220 viawd_detach(device_t dev)
221 {
222 	struct viawd_softc *sc;
223 	uint32_t reg;
224 
225 	sc = device_get_softc(dev);
226 
227 	/* Deregister event handler. */
228 	if (sc->ev_tag != NULL)
229 		EVENTHANDLER_DEREGISTER(watchdog_list, sc->ev_tag);
230 	sc->ev_tag = NULL;
231 
232 	/*
233 	 * Do not stop the watchdog on shutdown if active but bump the
234 	 * timer to avoid spurious reset.
235 	 */
236 	reg = viawd_read_wd_4(sc, VIAWD_MEM_CTRL);
237 	if (reg & VIAWD_MEM_CTRL_ENABLE) {
238 		viawd_tmr_set(sc, VIAWD_TIMEOUT_SHUTDOWN);
239 		viawd_tmr_state(sc, 1);
240 		device_printf(dev,
241 		    "Keeping watchog alive during shutdown for %d seconds\n",
242 		    VIAWD_TIMEOUT_SHUTDOWN);
243 	}
244 
245 	if (sc->wd_res != NULL)
246 		bus_release_resource(sc->dev, SYS_RES_MEMORY,
247 		    sc->wd_rid, sc->wd_res);
248 
249 	return (0);
250 }
251 
252 static device_method_t viawd_methods[] = {
253 	DEVMETHOD(device_identify, viawd_identify),
254 	DEVMETHOD(device_probe,	viawd_probe),
255 	DEVMETHOD(device_attach, viawd_attach),
256 	DEVMETHOD(device_detach, viawd_detach),
257 	DEVMETHOD(device_shutdown, viawd_detach),
258 	{0,0}
259 };
260 
261 static driver_t viawd_driver = {
262 	"viawd",
263 	viawd_methods,
264 	sizeof(struct viawd_softc),
265 };
266 
267 static int
268 viawd_modevent(module_t mode, int type, void *data)
269 {
270 	int error = 0;
271 
272 	switch (type) {
273 	case MOD_LOAD:
274 		printf("viawd module loaded\n");
275 		break;
276 	case MOD_UNLOAD:
277 		printf("viawd module unloaded\n");
278 		break;
279 	case MOD_SHUTDOWN:
280 		printf("viawd module shutting down\n");
281 		break;
282 	}
283 	return (error);
284 }
285 
286 DRIVER_MODULE(viawd, isa, viawd_driver, viawd_devclass, viawd_modevent, NULL);
287