1 /* $OpenBSD: berkwdt.c,v 1.8 2014/12/10 12:27:57 mikeb 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/types.h> 25 #include <sys/param.h> 26 #include <sys/device.h> 27 #include <sys/kernel.h> 28 #include <sys/systm.h> 29 30 #include <machine/bus.h> 31 32 #include <dev/pci/pcivar.h> 33 #include <dev/pci/pcireg.h> 34 #include <dev/pci/pcidevs.h> 35 36 struct berkwdt_softc { 37 struct device sc_dev; 38 39 /* device access through bus space */ 40 bus_space_tag_t sc_iot; 41 bus_space_handle_t sc_ioh; 42 43 /* the timeout period */ 44 int sc_period; 45 }; 46 47 int berkwdt_match(struct device *, void *, void *); 48 void berkwdt_attach(struct device *, struct device *, void *); 49 int berkwdt_activate(struct device *, int); 50 51 void berkwdt_start(struct berkwdt_softc *sc); 52 void berkwdt_stop(struct berkwdt_softc *sc); 53 void berkwdt_reload(struct berkwdt_softc *sc); 54 int berkwdt_send_command(struct berkwdt_softc *sc, u_int8_t cmd, int *val); 55 56 int berkwdt_set_timeout(void *, int); 57 58 struct cfattach berkwdt_ca = { 59 sizeof(struct berkwdt_softc), berkwdt_match, berkwdt_attach, 60 NULL, berkwdt_activate 61 }; 62 63 struct cfdriver berkwdt_cd = { 64 NULL, "berkwdt", DV_DULL 65 }; 66 67 const struct pci_matchid berkwdt_devices[] = { 68 { PCI_VENDOR_PIJNENBURG, PCI_PRODUCT_PIJNENBURG_PCWD_PCI } 69 }; 70 71 /* PCWD-PCI I/O Port definitions */ 72 #define PCWD_PCI_RELOAD 0x00 /* Re-trigger */ 73 #define PCWD_PCI_CS1 0x01 /* Control Status 1 */ 74 #define PCWD_PCI_CS2 0x02 /* Control Status 2 */ 75 #define PCWD_PCI_WDT_DIS 0x03 /* Watchdog Disable */ 76 #define PCWD_PCI_LSB 0x04 /* Command / Response */ 77 #define PCWD_PCI_MSB 0x05 /* Command/Response LSB */ 78 #define PCWD_PCI_CMD 0x06 /* Command/Response MSB */ 79 80 /* Port 1 : Control Status #1 */ 81 #define WD_PCI_WTRP 0x01 /* Watchdog Trip status */ 82 #define WD_PCI_TTRP 0x04 /* Temperature Trip status */ 83 #define WD_PCI_R2DS 0x40 /* Relay 2 Disable Temp-trip reset */ 84 85 /* Port 2 : Control Status #2 */ 86 #define WD_PCI_WDIS 0x10 /* Watchdog Disable */ 87 #define WD_PCI_WRSP 0x40 /* Watchdog wrote response */ 88 89 /* 90 * According to documentation max. time to process a command for the pci 91 * watchdog card is 100 ms, so we give it 150 ms to do its job. 92 */ 93 #define PCI_CMD_TIMEOUT 150 94 95 /* Watchdog's internal commands */ 96 #define CMD_WRITE_WD_TIMEOUT 0x19 97 98 int 99 berkwdt_send_command(struct berkwdt_softc *sc, u_int8_t cmd, int *val) 100 { 101 u_int8_t msb; 102 u_int8_t lsb; 103 u_int8_t got_response; 104 int count; 105 106 msb = *val / 256; 107 lsb = *val % 256; 108 109 /* Send command with data (data first!) */ 110 bus_space_write_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_LSB, lsb); 111 bus_space_write_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_MSB, msb); 112 bus_space_write_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_CMD, cmd); 113 114 got_response = bus_space_read_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_CS2); 115 got_response &= WD_PCI_WRSP; 116 for (count = 0; count < PCI_CMD_TIMEOUT && !got_response; count++) { 117 delay(1000); 118 got_response = bus_space_read_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_CS2); 119 got_response &= WD_PCI_WRSP; 120 } 121 122 if (got_response) { 123 /* read back response */ 124 lsb = bus_space_read_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_LSB); 125 msb = bus_space_read_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_MSB); 126 *val = (msb << 8) + lsb; 127 128 /* clear WRSP bit */ 129 bus_space_read_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_CMD); 130 return 1; 131 } 132 133 return 0; 134 } 135 136 void 137 berkwdt_start(struct berkwdt_softc *sc) 138 { 139 u_int8_t reg; 140 141 bus_space_write_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_WDT_DIS, 0x00); 142 delay(1000); 143 144 reg = bus_space_read_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_CS2); 145 if (reg & WD_PCI_WDIS) { 146 printf("%s: unable to enable\n", sc->sc_dev.dv_xname); 147 } 148 } 149 150 void 151 berkwdt_stop(struct berkwdt_softc *sc) 152 { 153 u_int8_t reg; 154 155 bus_space_write_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_WDT_DIS, 0xa5); 156 delay(1000); 157 bus_space_write_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_WDT_DIS, 0xa5); 158 delay(1000); 159 160 reg = bus_space_read_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_CS2); 161 if (!(reg & WD_PCI_WDIS)) { 162 printf("%s: unable to disable\n", sc->sc_dev.dv_xname); 163 } 164 } 165 166 void 167 berkwdt_reload(struct berkwdt_softc *sc) 168 { 169 bus_space_write_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_RELOAD, 0x42); 170 } 171 172 int 173 berkwdt_match(struct device *parent, void *match, void *aux) 174 { 175 return (pci_matchbyid((struct pci_attach_args *)aux, berkwdt_devices, 176 sizeof(berkwdt_devices) / sizeof(berkwdt_devices[0]))); 177 } 178 179 void 180 berkwdt_attach(struct device *parent, struct device *self, void *aux) 181 { 182 struct berkwdt_softc *sc = (struct berkwdt_softc *)self; 183 struct pci_attach_args *const pa = (struct pci_attach_args *)aux; 184 bus_size_t iosize; 185 u_int8_t reg; 186 187 /* retrieve the I/O region (BAR 0) */ 188 if (pci_mapreg_map(pa, 0x10, PCI_MAPREG_TYPE_IO, 0, 189 &sc->sc_iot, &sc->sc_ioh, NULL, &iosize, 0) != 0) { 190 printf(": couldn't find PCI I/O region\n"); 191 return; 192 } 193 194 /* Check for reboot by the card */ 195 reg = bus_space_read_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_CS1); 196 if (reg & WD_PCI_WTRP) { 197 printf(", warning: watchdog triggered"); 198 199 if (reg & WD_PCI_TTRP) 200 printf(", overheat detected"); 201 202 /* clear trip status & LED and keep mode of relay 2 */ 203 reg &= WD_PCI_R2DS; 204 reg |= WD_PCI_WTRP; 205 bus_space_write_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_CS1, reg); 206 } 207 208 printf("\n"); 209 210 /* ensure that the watchdog is disabled */ 211 berkwdt_stop(sc); 212 sc->sc_period = 0; 213 214 /* register with the watchdog framework */ 215 wdog_register(berkwdt_set_timeout, sc); 216 } 217 218 int 219 berkwdt_activate(struct device *self, int act) 220 { 221 switch (act) { 222 case DVACT_POWERDOWN: 223 wdog_shutdown(self); 224 break; 225 } 226 227 return (0); 228 } 229 230 int 231 berkwdt_set_timeout(void *self, int timeout) 232 { 233 struct berkwdt_softc *sc = self; 234 int new_timeout = timeout; 235 236 if (timeout == 0) { 237 /* Disable watchdog */ 238 berkwdt_stop(sc); 239 } else { 240 if (sc->sc_period != timeout) { 241 /* Set new timeout */ 242 berkwdt_send_command(sc, CMD_WRITE_WD_TIMEOUT, 243 &new_timeout); 244 } 245 if (sc->sc_period == 0) { 246 /* Enable watchdog */ 247 berkwdt_start(sc); 248 berkwdt_reload(sc); 249 } else { 250 /* Reset timer */ 251 berkwdt_reload(sc); 252 } 253 } 254 sc->sc_period = timeout; 255 256 return (timeout); 257 } 258 259