summaryrefslogtreecommitdiff
path: root/sys/dev/pci/berkwdt.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/pci/berkwdt.c')
-rw-r--r--sys/dev/pci/berkwdt.c244
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);
+}
+