/*- * Copyright (c) 2010 iXsystems, Inc. * All rights reserved. * Written by: Xin LI * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Winbond Watchdog Timer (WDT) driver */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include static devclass_t winbondwd_devclass; #define winbondwd_read_1(sc, off) \ bus_space_read_1((sc)->wb_bst, (sc)->wb_bsh, (off)) #define winbondwd_write_1(sc, off, val) \ bus_space_write_1((sc)->wb_bst, (sc)->wb_bsh, (off), (val)) /* * Enter Winbond Extended Functions State */ static __inline void winbondwd_config_enter(struct winbondwd_softc *sc) { winbondwd_write_1(sc, 0, 0x87); winbondwd_write_1(sc, 0, 0x87); } /* * Leave Winbond Extended Functions State */ static __inline void winbondwd_config_leave(struct winbondwd_softc *sc) { winbondwd_write_1(sc, 0, 0xaa); } static __inline unsigned char winbondwd_read_reg(struct winbondwd_softc *sc, unsigned char reg) { winbondwd_write_1(sc, 0, reg); return (winbondwd_read_1(sc, 1)); } /* * Write specified extended function register */ static __inline void winbondwd_write_reg(struct winbondwd_softc *sc, unsigned char reg, unsigned char val) { winbondwd_write_1(sc, 0, reg); winbondwd_write_1(sc, 1, val); } /* * Select the watchdog device (GPIO Port 2, Logical device 8) */ static void winbondwd_select(struct winbondwd_softc *sc) { winbondwd_config_enter(sc); winbondwd_write_reg(sc, /* LDN bit 7:1 */ 0x7, /* GPIO Port 2 */ 0x8); winbondwd_write_reg(sc, /* CR30 */ 0x30, /* Activate */ 0x1); } /* * Deselect the watchdog device (only a config_leave is needed) */ static void winbondwd_deselect(struct winbondwd_softc *sc) { winbondwd_config_leave(sc); } /* * Show timeout */ static void winbondwd_show_timeout(struct winbondwd_softc *sc) { const char *mode_str; unsigned char timeout, mode; winbondwd_select(sc); timeout = winbondwd_read_reg(sc, 0xf6 /* Timeout CR */); if (timeout == 0) { sc->active = 0; if (bootverbose) device_printf(sc->device, "Winbond watchdog not running\n"); } else { sc->active = 1; mode = winbondwd_read_reg(sc, 0xf5 /* Bit 3: count mode */); mode_str = (mode & (1 << 3))? "minute" : "second"; device_printf(sc->device, "Winbond watchdog will timeout after %hhu %s%s\n", timeout, mode_str, ((timeout > 1)? "s" : "")); } winbondwd_deselect(sc); } /* * Set timeout in seconds; 0 = disable */ static void winbondwd_set_timeout(struct winbondwd_softc *sc, unsigned char timeout) { unsigned char mode; /* Don't bother to disable if the watchdog is not running */ if (sc->active == 0 && timeout == 0) return; winbondwd_select(sc); mode = winbondwd_read_reg(sc, 0xf5 /* Bit 3: count mode */); mode &= ~(1 << 3); /* Choose seconds mode */ winbondwd_write_reg(sc, 0xf5, mode); winbondwd_write_reg(sc, 0xf6 /* Timeout CR */, timeout); winbondwd_deselect(sc); if (bootverbose) { if (timeout == 0) device_printf(sc->device, "Winbond watchdog disabled.\n"); else device_printf(sc->device, "Winbond watchdog timeout after %hhu seconds.\n", timeout); } sc->active = (timeout == 0) ? 0 : 1; } /* * Watchdog event handler - called by the framework to enable or disable * the watchdog or change the initial timeout value. */ static void winbondwd_event(void *arg, unsigned int cmd, int *error) { struct winbondwd_softc *sc = arg; unsigned char rtimeout; uint64_t timeout; if (cmd == 0) winbondwd_set_timeout(sc, 0); else { timeout = (uint64_t)1 << (cmd & WD_INTERVAL); if (timeout < (uint64_t)0xff * 1000 * 1000 * 1000) { rtimeout = timeout / (1000 * 1000 * 1000); if (rtimeout == 0) rtimeout = 0xff; winbondwd_set_timeout(sc, rtimeout); } else { device_printf(sc->device, "Value %u too big, disabling\n", cmd & WD_INTERVAL); /* Proposed timeout can not be satisified */ winbondwd_set_timeout(sc, 0); } } } /* * A hack to probe Winbond chip's base port. */ static unsigned int winbondwd_baseport_probe(void) { unsigned char val; int i; const unsigned int baseport_candidates[] = { 0x2e, 0x4e, 0 }; for (i = 0; baseport_candidates[i] != 0; i++) { /* * Enter config mode. * * Output magic number twice to the index register */ outb(baseport_candidates[i], 0x87); outb(baseport_candidates[i], 0x87); /* Get data from CR 0x20 (Device ID) */ outb(baseport_candidates[i], 0x20); val = inb(baseport_candidates[i]+1); /* * This is ugly but I didn't found a way to do this * in a very clean manner. */ if (val != 0xff) { outb(baseport_candidates[i], 0xaa); return baseport_candidates[i]; } } return (unsigned int)(-1); } /* * Look for Winbond device. */ static void winbondwd_identify(driver_t *driver, device_t parent) { unsigned int baseport; device_t dev; if ((dev = device_find_child(parent, driver->name, 0)) == NULL) { if (resource_int_value("winbondwd", 0, "baseport", &baseport) != 0) { baseport = winbondwd_baseport_probe(); if (baseport == (unsigned int)(-1)) { printf("winbondwd0: Compatible Winbond Super I/O not found.\n"); return; } } dev = BUS_ADD_CHILD(parent, 0, driver->name, 0); bus_set_resource(dev, SYS_RES_IOPORT, 0, baseport, 2); } if (dev == NULL) return; } static int winbondwd_probe(device_t dev) { /* Do not claim some ISA PnP device by accident. */ if (isa_get_logicalid(dev) != 0) return (ENXIO); return (0); } static int winbondwd_attach(device_t dev) { struct winbondwd_softc *sc; unsigned int baseport; sc = device_get_softc(dev); sc->device = dev; if (resource_int_value("winbondwd", 0, "baseport", &baseport) != 0) { baseport = winbondwd_baseport_probe(); if (baseport == (unsigned int)(-1)) { device_printf(dev, "No compatible Winbond Super I/O found.\n"); return (ENXIO); } } /* allocate I/O register space */ sc->wb_rid = 0; sc->wb_res = bus_alloc_resource(dev, SYS_RES_IOPORT, &sc->wb_rid, baseport, baseport + 1, 2, RF_ACTIVE | RF_SHAREABLE); if (sc->wb_res == NULL) { device_printf(dev, "Unable to reserve Extended Function Registers\n"); goto fail; } sc->wb_bst = rman_get_bustag(sc->wb_res); sc->wb_bsh = rman_get_bushandle(sc->wb_res); /* Display the device status */ winbondwd_show_timeout(sc); /* register the watchdog event handler */ sc->ev_tag = EVENTHANDLER_REGISTER(watchdog_list, winbondwd_event, sc, 0); return (0); fail: if (sc->wb_res != NULL) bus_release_resource(dev, SYS_RES_IOPORT, sc->wb_rid, sc->wb_res); return (ENXIO); } static int winbondwd_detach(device_t dev) { struct winbondwd_softc *sc; sc = device_get_softc(dev); /* deregister event handler */ if (sc->ev_tag != NULL) EVENTHANDLER_DEREGISTER(watchdog_list, sc->ev_tag); sc->ev_tag = NULL; /* Disable the watchdog */ if (sc->active) winbondwd_set_timeout(sc, 0); /* deallocate I/O register space */ bus_release_resource(dev, SYS_RES_IOPORT, sc->wb_rid, sc->wb_res); return (0); } static device_method_t winbondwd_methods[] = { DEVMETHOD(device_identify, winbondwd_identify), DEVMETHOD(device_probe, winbondwd_probe), DEVMETHOD(device_attach, winbondwd_attach), DEVMETHOD(device_detach, winbondwd_detach), DEVMETHOD(device_shutdown, winbondwd_detach), {0,0} }; static driver_t winbondwd_driver = { "winbondwd", winbondwd_methods, sizeof(struct winbondwd_softc), }; DRIVER_MODULE(winbondwd, isa, winbondwd_driver, winbondwd_devclass, NULL, NULL);