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: releng/10.3/sys/dev/viawd/viawd.c 237295 2012-06-20 09:01:44Z fabient $"); 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