diff options
Diffstat (limited to 'sys/dev/pci/berkwdt.c')
-rw-r--r-- | sys/dev/pci/berkwdt.c | 244 |
1 files changed, 244 insertions, 0 deletions
diff --git a/sys/dev/pci/berkwdt.c b/sys/dev/pci/berkwdt.c new file mode 100644 index 00000000000..8c84901e508 --- /dev/null +++ b/sys/dev/pci/berkwdt.c @@ -0,0 +1,244 @@ +/* $OpenBSD: berkwdt.c,v 1.1 2009/04/24 17:52:55 mk Exp $ */ + +/* + * Copyright (c) 2009 Wim Van Sebroeck <wim@iguana.be> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Berkshire PCI-PC Watchdog Card Driver + * http://www.pcwatchdog.com/ + */ + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/device.h> +#include <sys/kernel.h> +#include <sys/proc.h> +#include <sys/systm.h> + +#include <machine/bus.h> + +#include <dev/pci/pcivar.h> +#include <dev/pci/pcireg.h> +#include <dev/pci/pcidevs.h> + +struct berkwdt_softc { + /* wdt_dev must be the first item in the struct */ + struct device berkwdt_dev; + + /* the watchdog's heartbeat */ + int heartbeat; + + /* device access through bus space */ + bus_space_tag_t iot; + bus_space_handle_t ioh; +}; + +int berkwdt_match(struct device *, void *, void *); +void berkwdt_attach(struct device *, struct device *, void *); +int berkwdt_set_timeout(void *, int); + +struct cfattach berkwdt_ca = { + sizeof(struct berkwdt_softc), berkwdt_match, berkwdt_attach +}; + +struct cfdriver berkwdt_cd = { + NULL, "berkwdt", DV_DULL +}; + +const struct pci_matchid berkwdt_devices[] = { + { PCI_VENDOR_PIJNENBURG, PCI_PRODUCT_PIJNENBURG_PCWD_PCI } +}; + +/* PCWD-PCI I/O Port definitions */ +#define PCWD_PCI_RELOAD 0x00 /* Re-trigger */ +#define PCWD_PCI_CS1 0x01 /* Control Status 1 */ +#define PCWD_PCI_CS2 0x02 /* Control Status 2 */ +#define PCWD_PCI_WDT_DIS 0x03 /* Watchdog Disable */ +#define PCWD_PCI_LSB 0x04 /* Command / Response */ +#define PCWD_PCI_MSB 0x05 /* Command/Response LSB */ +#define PCWD_PCI_CMD 0x06 /* Command/Response MSB */ + +/* Port 1 : Control Status #1 */ +#define WD_PCI_WTRP 0x01 /* Watchdog Trip status */ +#define WD_PCI_TTRP 0x04 /* Temperature Trip status */ +#define WD_PCI_R2DS 0x40 /* Relay 2 Disable Temp-trip reset */ + +/* Port 2 : Control Status #2 */ +#define WD_PCI_WDIS 0x10 /* Watchdog Disable */ +#define WD_PCI_WRSP 0x40 /* Watchdog wrote response */ + +/* + * According to documentation max. time to process a command for the pci + * watchdog card is 100 ms, so we give it 150 ms to do its job. + */ +#define PCI_CMD_TIMEOUT 150 + +/* Watchdog's internal commands */ +#define CMD_WRITE_WD_TIMEOUT 0x19 + +static int +berkwdt_send_command(struct berkwdt_softc *sc, u_int8_t cmd, int *val) +{ + u_int8_t msb = *val / 256; + u_int8_t lsb = *val % 256; + u_int8_t got_response; + int count; + + /* Send command with data (data first!) */ + bus_space_write_1(sc->iot, sc->ioh, PCWD_PCI_LSB, lsb); + bus_space_write_1(sc->iot, sc->ioh, PCWD_PCI_MSB, msb); + bus_space_write_1(sc->iot, sc->ioh, PCWD_PCI_CMD, cmd); + + got_response = bus_space_read_1(sc->iot, sc->ioh, PCWD_PCI_CS2); + got_response &= WD_PCI_WRSP; + for (count = 0; (count < PCI_CMD_TIMEOUT) && (!got_response); count++) { + delay(1000); + got_response = bus_space_read_1(sc->iot, sc->ioh, PCWD_PCI_CS2); + got_response &= WD_PCI_WRSP; + } + + if (got_response) { + /* read back response */ + lsb = bus_space_read_1(sc->iot, sc->ioh, PCWD_PCI_LSB); + msb = bus_space_read_1(sc->iot, sc->ioh, PCWD_PCI_MSB); + *val = (msb << 8) + lsb; + + /* clear WRSP bit */ + bus_space_read_1(sc->iot, sc->ioh, PCWD_PCI_CMD); + return 1; + } + + return 0; +} + +void +berkwdt_start(struct berkwdt_softc *sc) +{ + u_int8_t reg; + + bus_space_write_1(sc->iot, sc->ioh, + PCWD_PCI_WDT_DIS, 0x00); + delay(1000); + + reg = bus_space_read_1(sc->iot, sc->ioh, PCWD_PCI_CS2); + if (reg & WD_PCI_WDIS) { + printf("%s: Card did not acknowledge enable attempt\n", + sc->berkwdt_dev.dv_xname); + } +} + +void +berkwdt_stop(struct berkwdt_softc *sc) +{ + u_int8_t reg; + + bus_space_write_1(sc->iot, sc->ioh, PCWD_PCI_WDT_DIS, 0xa5); + delay(1000); + bus_space_write_1(sc->iot, sc->ioh, PCWD_PCI_WDT_DIS, 0xa5); + delay(1000); + + reg = bus_space_read_1(sc->iot, sc->ioh, PCWD_PCI_CS2); + if (!(reg & WD_PCI_WDIS)) { + printf("%s: Card did not acknowledge disable attempt\n", + sc->berkwdt_dev.dv_xname); + } +} + +void +berkwdt_reload(struct berkwdt_softc *sc) +{ + bus_space_write_1(sc->iot, sc->ioh, PCWD_PCI_RELOAD, 0x42); +} + +int +berkwdt_match(struct device *parent, void *match, void *aux) +{ + return (pci_matchbyid((struct pci_attach_args *)aux, berkwdt_devices, + sizeof(berkwdt_devices) / sizeof(berkwdt_devices[0]))); +} + +void +berkwdt_attach(struct device *parent, struct device *self, void *aux) +{ + struct berkwdt_softc *sc = (struct berkwdt_softc *)self; + struct pci_attach_args *const pa = (struct pci_attach_args *)aux; + bus_size_t iosize; + u_int8_t reg; + + /* retrieve the I/O region (BAR 0) */ + if (pci_mapreg_map(pa, 0x10, PCI_MAPREG_TYPE_IO, 0, + &sc->iot, &sc->ioh, NULL, &iosize, 0) != 0) { + printf(": couldn't find PCI I/O region\n"); + return; + } + + /* Check for reboot by the card */ + reg = bus_space_read_1(sc->iot, sc->ioh, PCWD_PCI_CS1); + if (reg & WD_PCI_WTRP) { + printf(": Previous reset was caused by the Watchdog card"); + + if (reg & WD_PCI_TTRP) + printf("- Card sensed a CPU Overheat"); + + /* clear trip status & LED and keep mode of relay 2 */ + reg &= WD_PCI_R2DS; + reg |= WD_PCI_WTRP; + bus_space_write_1(sc->iot, sc->ioh, PCWD_PCI_CS1, reg); + } + + printf("\n"); + + /* + * ensure that the watchdog is disabled + */ + berkwdt_stop(sc); + sc->heartbeat = 0; + + /* + * register with the watchdog framework + */ + wdog_register(sc, berkwdt_set_timeout); +} + +int +berkwdt_set_timeout(void *self, int timeout) +{ + struct berkwdt_softc *sc = self; + int new_timeout = timeout; + + if (timeout == 0) { + /* Disable watchdog */ + berkwdt_stop(sc); + } else { + if (sc->heartbeat != timeout) { + /* Set new timeout */ + berkwdt_send_command(sc, CMD_WRITE_WD_TIMEOUT, + &new_timeout); + } + if (sc->heartbeat == 0) { + /* Enable watchdog */ + berkwdt_start(sc); + berkwdt_reload(sc); + } else { + /* Reset timer */ + berkwdt_reload(sc); + } + } + sc->heartbeat = timeout; + + return (timeout); +} + |