1228431Sfabient/*-
2228802Sfabient * Copyright (c) 2011 Fabien Thomas <fabient@FreeBSD.org>
3228431Sfabient * All rights reserved.
4228431Sfabient *
5228431Sfabient * Redistribution and use in source and binary forms, with or without
6228431Sfabient * modification, are permitted provided that the following conditions
7228431Sfabient * are met:
8228431Sfabient * 1. Redistributions of source code must retain the above copyright
9228431Sfabient *    notice, this list of conditions and the following disclaimer.
10228431Sfabient * 2. Redistributions in binary form must reproduce the above copyright
11228431Sfabient *    notice, this list of conditions and the following disclaimer in the
12228431Sfabient *    documentation and/or other materials provided with the distribution.
13228431Sfabient *
14228431Sfabient * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15228431Sfabient * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16228431Sfabient * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17228431Sfabient * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18228431Sfabient * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19228431Sfabient * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20228431Sfabient * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21228431Sfabient * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22228431Sfabient * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23228431Sfabient * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24228431Sfabient * SUCH DAMAGE.
25228431Sfabient */
26228431Sfabient
27228431Sfabient#include <sys/cdefs.h>
28228431Sfabient__FBSDID("$FreeBSD$");
29228431Sfabient
30228431Sfabient#include <sys/param.h>
31228431Sfabient#include <sys/kernel.h>
32228431Sfabient#include <sys/module.h>
33228431Sfabient#include <sys/systm.h>
34228431Sfabient#include <sys/bus.h>
35228431Sfabient#include <machine/bus.h>
36228431Sfabient#include <sys/rman.h>
37228431Sfabient#include <machine/resource.h>
38228431Sfabient#include <sys/watchdog.h>
39228431Sfabient
40228431Sfabient#include <isa/isavar.h>
41228431Sfabient#include <dev/pci/pcivar.h>
42228431Sfabient
43228431Sfabient#include "viawd.h"
44228431Sfabient
45228502Sfabient#define	viawd_read_4(sc, off)	bus_read_4((sc)->wd_res, (off))
46228502Sfabient#define	viawd_write_4(sc, off, val)	\
47228502Sfabient	bus_write_4((sc)->wd_res, (off), (val))
48228431Sfabient
49228431Sfabientstatic struct viawd_device viawd_devices[] = {
50228431Sfabient	{ DEVICEID_VT8251, "VIA VT8251 watchdog timer" },
51228431Sfabient	{ DEVICEID_CX700,  "VIA CX700 watchdog timer" },
52228431Sfabient	{ DEVICEID_VX800,  "VIA VX800 watchdog timer" },
53228431Sfabient	{ DEVICEID_VX855,  "VIA VX855 watchdog timer" },
54228431Sfabient	{ DEVICEID_VX900,  "VIA VX900 watchdog timer" },
55228431Sfabient	{ 0, NULL },
56228431Sfabient};
57228431Sfabient
58228431Sfabientstatic devclass_t viawd_devclass;
59228431Sfabient
60228431Sfabientstatic void
61228431Sfabientviawd_tmr_state(struct viawd_softc *sc, int enable)
62228431Sfabient{
63228431Sfabient	uint32_t reg;
64228431Sfabient
65228502Sfabient	reg = viawd_read_4(sc, VIAWD_MEM_CTRL);
66228431Sfabient	if (enable)
67228431Sfabient		reg |= VIAWD_MEM_CTRL_TRIGGER | VIAWD_MEM_CTRL_ENABLE;
68228431Sfabient	else
69228431Sfabient		reg &= ~VIAWD_MEM_CTRL_ENABLE;
70228502Sfabient	viawd_write_4(sc, VIAWD_MEM_CTRL, reg);
71228431Sfabient}
72228431Sfabient
73228431Sfabientstatic void
74228431Sfabientviawd_tmr_set(struct viawd_softc *sc, unsigned int timeout)
75228431Sfabient{
76228431Sfabient
77228431Sfabient	/* Keep value in range. */
78228431Sfabient	if (timeout < VIAWD_MEM_COUNT_MIN)
79228431Sfabient		timeout = VIAWD_MEM_COUNT_MIN;
80228431Sfabient	else if (timeout > VIAWD_MEM_COUNT_MAX)
81228431Sfabient		timeout = VIAWD_MEM_COUNT_MAX;
82228431Sfabient
83228502Sfabient	viawd_write_4(sc, VIAWD_MEM_COUNT, timeout);
84228431Sfabient	sc->timeout = timeout;
85228431Sfabient}
86228431Sfabient
87228431Sfabient/*
88228431Sfabient * Watchdog event handler - called by the framework to enable or disable
89228431Sfabient * the watchdog or change the initial timeout value.
90228431Sfabient */
91228431Sfabientstatic void
92228431Sfabientviawd_event(void *arg, unsigned int cmd, int *error)
93228431Sfabient{
94228431Sfabient	struct viawd_softc *sc = arg;
95228431Sfabient	unsigned int timeout;
96228431Sfabient
97228431Sfabient	/* Convert from power-of-two-ns to second. */
98228431Sfabient	cmd &= WD_INTERVAL;
99228431Sfabient	timeout = ((uint64_t)1 << cmd) / 1000000000;
100228431Sfabient	if (cmd) {
101228431Sfabient		if (timeout != sc->timeout)
102228431Sfabient			viawd_tmr_set(sc, timeout);
103228431Sfabient		viawd_tmr_state(sc, 1);
104228431Sfabient		*error = 0;
105228431Sfabient	} else
106228431Sfabient		viawd_tmr_state(sc, 0);
107228431Sfabient}
108228431Sfabient
109228502Sfabient/* Look for a supported VIA south bridge. */
110228502Sfabientstatic struct viawd_device *
111228502Sfabientviawd_find(device_t dev)
112228502Sfabient{
113228502Sfabient	struct viawd_device *id;
114228502Sfabient
115228502Sfabient	if (pci_get_vendor(dev) != VENDORID_VIA)
116228502Sfabient		return (NULL);
117228502Sfabient	for (id = viawd_devices; id->desc != NULL; id++)
118228502Sfabient		if (pci_get_device(dev) == id->device)
119228502Sfabient			return (id);
120228502Sfabient	return (NULL);
121228502Sfabient}
122228502Sfabient
123228431Sfabientstatic void
124228431Sfabientviawd_identify(driver_t *driver, device_t parent)
125228431Sfabient{
126228431Sfabient
127228502Sfabient	if (viawd_find(parent) == NULL)
128228431Sfabient		return;
129228431Sfabient
130228502Sfabient	if (device_find_child(parent, driver->name, -1) == NULL)
131228502Sfabient		BUS_ADD_CHILD(parent, 0, driver->name, 0);
132228431Sfabient}
133228431Sfabient
134228431Sfabientstatic int
135228431Sfabientviawd_probe(device_t dev)
136228431Sfabient{
137228502Sfabient	struct viawd_device *id;
138228431Sfabient
139228502Sfabient	id = viawd_find(device_get_parent(dev));
140228502Sfabient	KASSERT(id != NULL, ("parent should be a valid VIA SB"));
141228502Sfabient	device_set_desc(dev, id->desc);
142228502Sfabient	return (BUS_PROBE_GENERIC);
143228431Sfabient}
144228431Sfabient
145228431Sfabientstatic int
146228431Sfabientviawd_attach(device_t dev)
147228431Sfabient{
148228431Sfabient	device_t sb_dev;
149228431Sfabient	struct viawd_softc *sc;
150228431Sfabient	uint32_t pmbase, reg;
151228431Sfabient
152228431Sfabient	sc = device_get_softc(dev);
153228431Sfabient	sc->dev = dev;
154228431Sfabient
155228502Sfabient	sb_dev = device_get_parent(dev);
156228431Sfabient	if (sb_dev == NULL) {
157228431Sfabient		device_printf(dev, "Can not find watchdog device.\n");
158228431Sfabient		goto fail;
159228431Sfabient	}
160228431Sfabient	sc->sb_dev = sb_dev;
161228431Sfabient
162228431Sfabient	/* Get watchdog memory base. */
163228431Sfabient	pmbase = pci_read_config(sb_dev, VIAWD_CONFIG_BASE, 4);
164228431Sfabient	if (pmbase == 0) {
165228431Sfabient		device_printf(dev,
166228431Sfabient		    "Watchdog disabled in BIOS or hardware\n");
167228431Sfabient		goto fail;
168228431Sfabient	}
169228431Sfabient
170228431Sfabient	/* Allocate I/O register space. */
171237295Sfabient	sc->wd_rid = VIAWD_CONFIG_BASE;
172237295Sfabient	sc->wd_res = bus_alloc_resource_any(sb_dev, SYS_RES_MEMORY, &sc->wd_rid,
173228431Sfabient	    RF_ACTIVE | RF_SHAREABLE);
174228431Sfabient	if (sc->wd_res == NULL) {
175228431Sfabient		device_printf(dev, "Unable to map watchdog memory\n");
176228431Sfabient		goto fail;
177228431Sfabient	}
178237295Sfabient	if (rman_get_size(sc->wd_res) < VIAWD_MEM_LEN) {
179237295Sfabient		device_printf(dev, "Bad size for watchdog memory: %#x\n",
180237295Sfabient		    (unsigned)rman_get_size(sc->wd_res));
181237295Sfabient		goto fail;
182237295Sfabient	}
183228431Sfabient
184228431Sfabient	/* Check if watchdog fired last boot. */
185228502Sfabient	reg = viawd_read_4(sc, VIAWD_MEM_CTRL);
186228431Sfabient	if (reg & VIAWD_MEM_CTRL_FIRED) {
187228431Sfabient		device_printf(dev,
188228431Sfabient		    "ERROR: watchdog rebooted the system\n");
189228431Sfabient		/* Reset bit state. */
190228502Sfabient		viawd_write_4(sc, VIAWD_MEM_CTRL, reg);
191228431Sfabient	}
192228431Sfabient
193228431Sfabient	/* Register the watchdog event handler. */
194228431Sfabient	sc->ev_tag = EVENTHANDLER_REGISTER(watchdog_list, viawd_event, sc, 0);
195228431Sfabient
196228431Sfabient	return (0);
197228431Sfabientfail:
198228431Sfabient	if (sc->wd_res != NULL)
199237295Sfabient		bus_release_resource(sb_dev, SYS_RES_MEMORY,
200228431Sfabient		    sc->wd_rid, sc->wd_res);
201228431Sfabient	return (ENXIO);
202228431Sfabient}
203228431Sfabient
204228431Sfabientstatic int
205228431Sfabientviawd_detach(device_t dev)
206228431Sfabient{
207228431Sfabient	struct viawd_softc *sc;
208228431Sfabient	uint32_t reg;
209228431Sfabient
210228431Sfabient	sc = device_get_softc(dev);
211228431Sfabient
212228431Sfabient	/* Deregister event handler. */
213228431Sfabient	if (sc->ev_tag != NULL)
214228431Sfabient		EVENTHANDLER_DEREGISTER(watchdog_list, sc->ev_tag);
215228431Sfabient	sc->ev_tag = NULL;
216228431Sfabient
217228431Sfabient	/*
218228431Sfabient	 * Do not stop the watchdog on shutdown if active but bump the
219228431Sfabient	 * timer to avoid spurious reset.
220228431Sfabient	 */
221228502Sfabient	reg = viawd_read_4(sc, VIAWD_MEM_CTRL);
222228431Sfabient	if (reg & VIAWD_MEM_CTRL_ENABLE) {
223228431Sfabient		viawd_tmr_set(sc, VIAWD_TIMEOUT_SHUTDOWN);
224228431Sfabient		viawd_tmr_state(sc, 1);
225228431Sfabient		device_printf(dev,
226228431Sfabient		    "Keeping watchog alive during shutdown for %d seconds\n",
227228431Sfabient		    VIAWD_TIMEOUT_SHUTDOWN);
228228431Sfabient	}
229228431Sfabient
230228431Sfabient	if (sc->wd_res != NULL)
231237295Sfabient		bus_release_resource(sc->sb_dev, SYS_RES_MEMORY,
232228431Sfabient		    sc->wd_rid, sc->wd_res);
233228431Sfabient
234228431Sfabient	return (0);
235228431Sfabient}
236228431Sfabient
237228431Sfabientstatic device_method_t viawd_methods[] = {
238228431Sfabient	DEVMETHOD(device_identify, viawd_identify),
239228431Sfabient	DEVMETHOD(device_probe,	viawd_probe),
240228431Sfabient	DEVMETHOD(device_attach, viawd_attach),
241228431Sfabient	DEVMETHOD(device_detach, viawd_detach),
242228431Sfabient	DEVMETHOD(device_shutdown, viawd_detach),
243228431Sfabient	{0,0}
244228431Sfabient};
245228431Sfabient
246228431Sfabientstatic driver_t viawd_driver = {
247228431Sfabient	"viawd",
248228431Sfabient	viawd_methods,
249228431Sfabient	sizeof(struct viawd_softc),
250228431Sfabient};
251228431Sfabient
252228502SfabientDRIVER_MODULE(viawd, isab, viawd_driver, viawd_devclass, NULL, NULL);
253