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