1 /* $OpenBSD: berkwdt.c,v 1.12 2024/05/24 06:02:53 jsg Exp $ */ 2 3 /* 4 * Copyright (c) 2009 Wim Van Sebroeck <wim@iguana.be> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 /* 20 * Berkshire PCI-PC Watchdog Card Driver 21 * http://www.pcwatchdog.com/ 22 */ 23 24 #include <sys/param.h> 25 #include <sys/device.h> 26 #include <sys/systm.h> 27 28 #include <machine/bus.h> 29 30 #include <dev/pci/pcivar.h> 31 #include <dev/pci/pcireg.h> 32 #include <dev/pci/pcidevs.h> 33 34 struct berkwdt_softc { 35 struct device sc_dev; 36 37 /* device access through bus space */ 38 bus_space_tag_t sc_iot; 39 bus_space_handle_t sc_ioh; 40 41 /* the timeout period */ 42 int sc_period; 43 }; 44 45 int berkwdt_match(struct device *, void *, void *); 46 void berkwdt_attach(struct device *, struct device *, void *); 47 int berkwdt_activate(struct device *, int); 48 49 void berkwdt_start(struct berkwdt_softc *sc); 50 void berkwdt_stop(struct berkwdt_softc *sc); 51 void berkwdt_reload(struct berkwdt_softc *sc); 52 int berkwdt_send_command(struct berkwdt_softc *sc, u_int8_t cmd, int *val); 53 54 int berkwdt_set_timeout(void *, int); 55 56 const struct cfattach berkwdt_ca = { 57 sizeof(struct berkwdt_softc), berkwdt_match, berkwdt_attach, 58 NULL, berkwdt_activate 59 }; 60 61 struct cfdriver berkwdt_cd = { 62 NULL, "berkwdt", DV_DULL 63 }; 64 65 const struct pci_matchid berkwdt_devices[] = { 66 { PCI_VENDOR_PIJNENBURG, PCI_PRODUCT_PIJNENBURG_PCWD_PCI } 67 }; 68 69 /* PCWD-PCI I/O Port definitions */ 70 #define PCWD_PCI_RELOAD 0x00 /* Re-trigger */ 71 #define PCWD_PCI_CS1 0x01 /* Control Status 1 */ 72 #define PCWD_PCI_CS2 0x02 /* Control Status 2 */ 73 #define PCWD_PCI_WDT_DIS 0x03 /* Watchdog Disable */ 74 #define PCWD_PCI_LSB 0x04 /* Command / Response */ 75 #define PCWD_PCI_MSB 0x05 /* Command/Response LSB */ 76 #define PCWD_PCI_CMD 0x06 /* Command/Response MSB */ 77 78 /* Port 1 : Control Status #1 */ 79 #define WD_PCI_WTRP 0x01 /* Watchdog Trip status */ 80 #define WD_PCI_TTRP 0x04 /* Temperature Trip status */ 81 #define WD_PCI_R2DS 0x40 /* Relay 2 Disable Temp-trip reset */ 82 83 /* Port 2 : Control Status #2 */ 84 #define WD_PCI_WDIS 0x10 /* Watchdog Disable */ 85 #define WD_PCI_WRSP 0x40 /* Watchdog wrote response */ 86 87 /* 88 * According to documentation max. time to process a command for the pci 89 * watchdog card is 100 ms, so we give it 150 ms to do its job. 90 */ 91 #define PCI_CMD_TIMEOUT 150 92 93 /* Watchdog's internal commands */ 94 #define CMD_WRITE_WD_TIMEOUT 0x19 95 96 int 97 berkwdt_send_command(struct berkwdt_softc *sc, u_int8_t cmd, int *val) 98 { 99 u_int8_t msb; 100 u_int8_t lsb; 101 u_int8_t got_response; 102 int count; 103 104 msb = *val / 256; 105 lsb = *val % 256; 106 107 /* Send command with data (data first!) */ 108 bus_space_write_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_LSB, lsb); 109 bus_space_write_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_MSB, msb); 110 bus_space_write_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_CMD, cmd); 111 112 got_response = bus_space_read_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_CS2); 113 got_response &= WD_PCI_WRSP; 114 for (count = 0; count < PCI_CMD_TIMEOUT && !got_response; count++) { 115 delay(1000); 116 got_response = bus_space_read_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_CS2); 117 got_response &= WD_PCI_WRSP; 118 } 119 120 if (got_response) { 121 /* read back response */ 122 lsb = bus_space_read_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_LSB); 123 msb = bus_space_read_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_MSB); 124 *val = (msb << 8) + lsb; 125 126 /* clear WRSP bit */ 127 bus_space_read_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_CMD); 128 return 1; 129 } 130 131 return 0; 132 } 133 134 void 135 berkwdt_start(struct berkwdt_softc *sc) 136 { 137 u_int8_t reg; 138 139 bus_space_write_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_WDT_DIS, 0x00); 140 delay(1000); 141 142 reg = bus_space_read_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_CS2); 143 if (reg & WD_PCI_WDIS) { 144 printf("%s: unable to enable\n", sc->sc_dev.dv_xname); 145 } 146 } 147 148 void 149 berkwdt_stop(struct berkwdt_softc *sc) 150 { 151 u_int8_t reg; 152 153 bus_space_write_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_WDT_DIS, 0xa5); 154 delay(1000); 155 bus_space_write_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_WDT_DIS, 0xa5); 156 delay(1000); 157 158 reg = bus_space_read_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_CS2); 159 if (!(reg & WD_PCI_WDIS)) { 160 printf("%s: unable to disable\n", sc->sc_dev.dv_xname); 161 } 162 } 163 164 void 165 berkwdt_reload(struct berkwdt_softc *sc) 166 { 167 bus_space_write_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_RELOAD, 0x42); 168 } 169 170 int 171 berkwdt_match(struct device *parent, void *match, void *aux) 172 { 173 return (pci_matchbyid((struct pci_attach_args *)aux, berkwdt_devices, 174 sizeof(berkwdt_devices) / sizeof(berkwdt_devices[0]))); 175 } 176 177 void 178 berkwdt_attach(struct device *parent, struct device *self, void *aux) 179 { 180 struct berkwdt_softc *sc = (struct berkwdt_softc *)self; 181 struct pci_attach_args *pa = (struct pci_attach_args *)aux; 182 bus_size_t iosize; 183 u_int8_t reg; 184 185 /* retrieve the I/O region (BAR 0) */ 186 if (pci_mapreg_map(pa, 0x10, PCI_MAPREG_TYPE_IO, 0, 187 &sc->sc_iot, &sc->sc_ioh, NULL, &iosize, 0) != 0) { 188 printf(": couldn't find PCI I/O region\n"); 189 return; 190 } 191 192 /* Check for reboot by the card */ 193 reg = bus_space_read_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_CS1); 194 if (reg & WD_PCI_WTRP) { 195 printf(", warning: watchdog triggered"); 196 197 if (reg & WD_PCI_TTRP) 198 printf(", overheat detected"); 199 200 /* clear trip status & LED and keep mode of relay 2 */ 201 reg &= WD_PCI_R2DS; 202 reg |= WD_PCI_WTRP; 203 bus_space_write_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_CS1, reg); 204 } 205 206 printf("\n"); 207 208 /* ensure that the watchdog is disabled */ 209 berkwdt_stop(sc); 210 sc->sc_period = 0; 211 212 /* register with the watchdog framework */ 213 wdog_register(berkwdt_set_timeout, sc); 214 } 215 216 int 217 berkwdt_activate(struct device *self, int act) 218 { 219 switch (act) { 220 case DVACT_POWERDOWN: 221 wdog_shutdown(self); 222 break; 223 } 224 225 return (0); 226 } 227 228 int 229 berkwdt_set_timeout(void *self, int timeout) 230 { 231 struct berkwdt_softc *sc = self; 232 int new_timeout = timeout; 233 234 if (timeout == 0) { 235 /* Disable watchdog */ 236 berkwdt_stop(sc); 237 } else { 238 if (sc->sc_period != timeout) { 239 /* Set new timeout */ 240 berkwdt_send_command(sc, CMD_WRITE_WD_TIMEOUT, 241 &new_timeout); 242 } 243 if (sc->sc_period == 0) { 244 /* Enable watchdog */ 245 berkwdt_start(sc); 246 berkwdt_reload(sc); 247 } else { 248 /* Reset timer */ 249 berkwdt_reload(sc); 250 } 251 } 252 sc->sc_period = timeout; 253 254 return (timeout); 255 } 256 257