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
berkwdt_send_command(struct berkwdt_softc * sc,u_int8_t cmd,int * val)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
berkwdt_start(struct berkwdt_softc * sc)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
berkwdt_stop(struct berkwdt_softc * sc)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
berkwdt_reload(struct berkwdt_softc * sc)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
berkwdt_match(struct device * parent,void * match,void * aux)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
berkwdt_attach(struct device * parent,struct device * self,void * aux)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
berkwdt_activate(struct device * self,int act)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
berkwdt_set_timeout(void * self,int timeout)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