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
34struct 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
45int berkwdt_match(struct device *, void *, void *);
46void berkwdt_attach(struct device *, struct device *, void *);
47int berkwdt_activate(struct device *, int);
48
49void berkwdt_start(struct berkwdt_softc *sc);
50void berkwdt_stop(struct berkwdt_softc *sc);
51void berkwdt_reload(struct berkwdt_softc *sc);
52int berkwdt_send_command(struct berkwdt_softc *sc, u_int8_t cmd, int *val);
53
54int berkwdt_set_timeout(void *, int);
55
56const struct cfattach berkwdt_ca = {
57	sizeof(struct berkwdt_softc), berkwdt_match, berkwdt_attach,
58	NULL, berkwdt_activate
59};
60
61struct cfdriver berkwdt_cd = {
62	NULL, "berkwdt", DV_DULL
63};
64
65const 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
96int
97berkwdt_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
134void
135berkwdt_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
148void
149berkwdt_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
164void
165berkwdt_reload(struct berkwdt_softc *sc)
166{
167	bus_space_write_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_RELOAD, 0x42);
168}
169
170int
171berkwdt_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
177void
178berkwdt_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
216int
217berkwdt_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
228int
229berkwdt_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