xref: /openbsd/sys/dev/pci/berkwdt.c (revision 8529ddd3)
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