From 4fabf20dd61f1f4d71d93cbaa112a33bb40cbbb7 Mon Sep 17 00:00:00 2001 From: Xin Li Date: Mon, 19 Dec 2011 20:47:24 -0800 Subject: [PATCH 23/65] Winbond Watchdog. --- sys/conf/files.amd64 | 1 + sys/dev/winbondwd/winbondwd.c | 368 +++++++++++++++++++++++++++++++++++++++++ sys/dev/winbondwd/winbondwd.h | 47 ++++++ 3 files changed, 416 insertions(+), 0 deletions(-) create mode 100644 sys/dev/winbondwd/winbondwd.c create mode 100644 sys/dev/winbondwd/winbondwd.h diff --git a/sys/conf/files.amd64 b/sys/conf/files.amd64 index 82a5c14..94a8ab2 100644 --- a/sys/conf/files.amd64 +++ b/sys/conf/files.amd64 @@ -260,6 +260,7 @@ dev/tpm/tpm.c optional tpm dev/tpm/tpm_acpi.c optional tpm acpi dev/tpm/tpm_isa.c optional tpm isa dev/uart/uart_cpu_amd64.c optional uart +dev/winbondwd/winbondwd.c optional winbondwd dev/wpi/if_wpi.c optional wpi isa/syscons_isa.c optional sc isa/vga_isa.c optional vga diff --git a/sys/dev/winbondwd/winbondwd.c b/sys/dev/winbondwd/winbondwd.c new file mode 100644 index 0000000..fa53735 --- /dev/null +++ b/sys/dev/winbondwd/winbondwd.c @@ -0,0 +1,368 @@ +/*- + * 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 << 2))? "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 << 2); /* 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); + + /* + * I know this is ugly but I didn't found a way to do + * this in a cleaner manner. + */ + /* Get data from CR 0x20 (Device ID) */ + outb(baseport_candidates[i], 0x20); + val = inb(baseport_candidates[i]+1); + + if (bootverbose) + printf("winbondwd0: CR20 probing port 0x%x got 0x%x\n", baseport_candidates[i], val); + + 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); diff --git a/sys/dev/winbondwd/winbondwd.h b/sys/dev/winbondwd/winbondwd.h new file mode 100644 index 0000000..57a1a23 --- /dev/null +++ b/sys/dev/winbondwd/winbondwd.h @@ -0,0 +1,47 @@ +/*- + * 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. + * + * $FreeBSD$ + */ + +#ifndef _WINBONDWD_H_ +#define _WINBONDWD_H_ + +struct winbondwd_softc { + device_t device; + + int active; + unsigned int timeout; + + int wb_rid; + struct resource *wb_res; + bus_space_tag_t wb_bst; + bus_space_handle_t wb_bsh; + + eventhandler_tag ev_tag; +}; + +#endif /* _WINBONDWD_H_ */ -- 1.7.8.3