xref: /netbsd/sys/dev/pci/weasel_pci.c (revision 6550d01e)
1 /*	$NetBSD: weasel_pci.c,v 1.14 2009/03/18 16:00:19 cegger Exp $	*/
2 
3 /*-
4  * Copyright (c) 2001 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Herb Peyerl and Jason Thorpe.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 /*
33  * Device driver for the control space on the Middle Digital, Inc.
34  * PCI-Weasel serial console board.
35  *
36  * Since the other functions of the PCI-Weasel already appear in
37  * PCI configuration space, we just need to hook up the watchdog
38  * timer.
39  */
40 
41 #include <sys/cdefs.h>
42 __KERNEL_RCSID(0, "$NetBSD: weasel_pci.c,v 1.14 2009/03/18 16:00:19 cegger Exp $");
43 
44 #include <sys/param.h>
45 #include <sys/systm.h>
46 #include <sys/device.h>
47 #include <sys/wdog.h>
48 #include <sys/endian.h>
49 
50 #include <sys/bus.h>
51 
52 #include <dev/pci/pcireg.h>
53 #include <dev/pci/pcivar.h>
54 #include <dev/pci/pcidevs.h>
55 
56 #include <dev/pci/weaselreg.h>
57 
58 #include <dev/sysmon/sysmonvar.h>
59 
60 struct weasel_softc {
61 	device_t sc_dev;
62 	bus_space_tag_t sc_st;
63 	bus_space_handle_t sc_sh;
64 
65 	struct sysmon_wdog sc_smw;
66 
67 	int sc_wdog_armed;
68 	int sc_wdog_period;
69 };
70 
71 /* XXX */
72 extern int	sysmon_wdog_setmode(struct sysmon_wdog *, int, u_int);
73 
74 static int	weasel_pci_wdog_setmode(struct sysmon_wdog *);
75 static int	weasel_pci_wdog_tickle(struct sysmon_wdog *);
76 
77 static int	weasel_wait_response(struct weasel_softc *);
78 static int	weasel_issue_command(struct weasel_softc *, uint8_t cmd);
79 
80 static int	weasel_pci_wdog_arm(struct weasel_softc *);
81 static int	weasel_pci_wdog_disarm(struct weasel_softc *);
82 
83 static int	weasel_pci_wdog_query_state(struct weasel_softc *);
84 
85 static int
86 weasel_pci_match(device_t parent, cfdata_t cf, void *aux)
87 {
88 	struct pci_attach_args *pa = aux;
89 
90 	if (PCI_VENDOR(pa->pa_id) == PCI_VENDOR_MIDDLE_DIGITAL &&
91 	    PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_MIDDLE_DIGITAL_WEASEL_CONTROL)
92 		return (1);
93 
94 	return (0);
95 }
96 
97 static void
98 weasel_pci_attach(device_t parent, device_t self, void *aux)
99 {
100 	struct weasel_softc *sc = device_private(self);
101 	struct pci_attach_args *pa = aux;
102 	struct weasel_config_block cfg;
103 	const char *vers, *mode;
104 	uint8_t v, *cp;
105 	uint16_t cfg_size;
106 	uint8_t buf[8];
107 
108 	sc->sc_dev = self;
109 
110 	printf(": PCI-Weasel watchdog timer\n");
111 
112 	if (pci_mapreg_map(pa, PCI_MAPREG_START,
113 	    PCI_MAPREG_TYPE_MEM|PCI_MAPREG_MEM_TYPE_32BIT, 0,
114 	    &sc->sc_st, &sc->sc_sh, NULL, NULL) != 0) {
115 		aprint_error_dev(self, "unable to map device registers\n");
116 		return;
117 	}
118 
119 	/* Ping the Weasel to see if it's alive. */
120 	if (weasel_issue_command(sc, OS_CMD_PING)) {
121 		aprint_error_dev(self, "Weasel didn't respond to PING\n");
122 		return;
123 	}
124 	bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0);
125 	if ((v = bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_DATA_RD)) !=
126 	    OS_RET_PONG) {
127 		aprint_error_dev(self, "unexpected PING response from Weasel: 0x%02x\n", v);
128 		return;
129 	}
130 
131 	/* Read the config block. */
132 	if (weasel_issue_command(sc, OS_CMD_SHOW_CONFIG)) {
133 		aprint_error_dev(self, "Weasel didn't respond to SHOW_CONFIG\n");
134 		return;
135 	}
136 	cfg_size = bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_DATA_RD);
137 	bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0);
138 
139 	if (++cfg_size != sizeof(cfg)) {
140 		aprint_error_dev(self, "weird config block size from Weasel: 0x%03x\n", cfg_size);
141 		return;
142 	}
143 
144 	for (cp = (uint8_t *) &cfg; cfg_size != 0; cfg_size--) {
145 		if (weasel_wait_response(sc)) {
146 			aprint_error_dev(self, "Weasel stopped providing config block(%d)\n", cfg_size);
147 			return;
148 		}
149 		*cp++ = bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_DATA_RD);
150 		bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0);
151 	}
152 
153 	switch (cfg.cfg_version) {
154 	case CFG_VERSION_2:
155 		vers="2";
156 		switch (cfg.enable_duart_switching) {
157 		case 0:
158 			mode = "emulation";
159 			break;
160 		case 1:
161 			mode = "serial";
162 			break;
163 		case 2:
164 			mode = "autoswitch";
165 			break;
166 		default:
167 			mode = "unknown";
168 		}
169 		break;
170 
171 	default:
172 		vers = mode = NULL;
173 	}
174 
175 	if (vers != NULL)
176 		printf("%s: %s mode\n", device_xname(self), mode);
177 	else
178 		printf("%s: unknown config version 0x%02x\n", device_xname(self),
179 		    cfg.cfg_version);
180 
181 	/*
182 	 * Fetch sw version.
183 	 */
184 	if (weasel_issue_command(sc, OS_CMD_QUERY_SW_VER)) {
185 		aprint_error_dev(self, "didn't reply to software version query.\n");
186 	}
187 	else {
188 		v = bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_DATA_RD);
189 		bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0);
190 		if (v>7)
191 			printf("%s: weird length for version string(%d).\n",
192 			    device_xname(self), v);
193 		memset(buf, 0, sizeof(buf));
194 		for (cp = buf; v != 0; v--) {
195 			if (weasel_wait_response(sc)) {
196 				printf("%s: Weasel stopped providing version\n",
197 				    device_xname(self));
198 			}
199 			*cp++ = bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_DATA_RD);
200 			bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0);
201 		}
202 		printf("%s: sw: %s", device_xname(self), buf);
203 	}
204 	/*
205 	 * Fetch logic version.
206 	 */
207 	if (weasel_issue_command(sc, OS_CMD_QUERY_L_VER)) {
208 		aprint_normal("\n");
209 		aprint_error_dev(self, "didn't reply to logic version query.\n");
210 	}
211 	bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0);
212 	v = bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_DATA_RD);
213 	printf(" logic: %03d", v);
214 	/*
215 	 * Fetch vga bios version.
216 	 */
217 	if (weasel_issue_command(sc, OS_CMD_QUERY_VB_VER)) {
218 		aprint_normal("\n");
219 		aprint_error_dev(self, "didn't reply to vga bios version query.\n");
220 	}
221 	v = bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_DATA_RD);
222 	bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0);
223 	printf(" vga bios: %d.%d", (v>>4), (v&0x0f));
224 	/*
225 	 * Fetch hw version.
226 	 */
227 	if (weasel_issue_command(sc, OS_CMD_QUERY_HW_VER)) {
228 		aprint_normal("\n");
229 		aprint_error_dev(self, "didn't reply to hardware version query.\n");
230 	}
231 	v = bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_DATA_RD);
232 	bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0);
233 	printf(" hw: %d.%d", (v>>4), (v&0x0f));
234 
235 	printf("\n%s: break passthrough %s", device_xname(self),
236 	    cfg.break_passthru ? "enabled" : "disabled");
237 
238 	if ((sc->sc_wdog_armed = weasel_pci_wdog_query_state(sc)) == -1)
239 		sc->sc_wdog_armed = 0;
240 
241 	/* Weasel is big-endian */
242 	sc->sc_wdog_period = be16toh(cfg.wdt_msec) / 1000;
243 
244 	printf(", watchdog timer %d sec.\n", sc->sc_wdog_period);
245 	sc->sc_smw.smw_name = "weasel";
246 	sc->sc_smw.smw_cookie = sc;
247 	sc->sc_smw.smw_setmode = weasel_pci_wdog_setmode;
248 	sc->sc_smw.smw_tickle = weasel_pci_wdog_tickle;
249 	sc->sc_smw.smw_period = sc->sc_wdog_period;
250 
251 	if (sysmon_wdog_register(&sc->sc_smw) != 0)
252 		aprint_error_dev(self, "unable to register PC-Weasel watchdog "
253 		    "with sysmon\n");
254 }
255 
256 CFATTACH_DECL_NEW(weasel_pci, sizeof(struct weasel_softc),
257     weasel_pci_match, weasel_pci_attach, NULL, NULL);
258 
259 static int
260 weasel_wait_response(struct weasel_softc *sc)
261 {
262 	int i;
263 
264 	for (i = 10000; i ; i--) {
265 		delay(100);
266 		if (bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS) ==
267 		    OS_WS_HOST_READ)
268 			return(0);
269 	}
270 	return (1);
271 }
272 
273 static int
274 weasel_issue_command(struct weasel_softc *sc, uint8_t cmd)
275 {
276 	bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_DATA_WR, cmd);
277 	bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_HOST_STATUS, OS_HS_WEASEL_READ);
278 	bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0);
279 	return (weasel_wait_response(sc));
280 }
281 
282 static int
283 weasel_pci_wdog_setmode(struct sysmon_wdog *smw)
284 {
285 	struct weasel_softc *sc = smw->smw_cookie;
286 	int error = 0;
287 
288 	if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) {
289 		error = weasel_pci_wdog_disarm(sc);
290 	} else {
291 		if (smw->smw_period == WDOG_PERIOD_DEFAULT)
292 			smw->smw_period = sc->sc_wdog_period;
293 		else if (smw->smw_period != sc->sc_wdog_period) {
294 			/* Can't change the period on the Weasel. */
295 			return (EINVAL);
296 		}
297 		error = weasel_pci_wdog_arm(sc);
298 		weasel_pci_wdog_tickle(smw);
299 	}
300 
301 	return (error);
302 }
303 
304 static int
305 weasel_pci_wdog_tickle(struct sysmon_wdog *smw)
306 {
307 	struct weasel_softc *sc = smw->smw_cookie;
308 	u_int8_t reg;
309 	int x;
310 	int s;
311 	int error = 0;
312 
313 	s = splhigh();
314 	/*
315 	 * first we tickle the watchdog
316 	 */
317 	reg = bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_CHALLENGE);
318 	bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_RESPONSE, ~reg);
319 
320 	/*
321 	 * then we check to make sure the weasel is still armed. If someone
322 	 * has rebooted the weasel for whatever reason (firmware update),
323 	 * then the watchdog timer would no longer be armed and we'd be
324 	 * servicing nothing. Let the user know that the machine is no
325 	 * longer being monitored by the weasel.
326 	 */
327 	if((x = weasel_pci_wdog_query_state(sc)) == -1)
328 		error = EIO;
329 	if (x == 1) {
330 		error = 0;
331 	} else {
332 		printf("%s: Watchdog timer disabled on PC/Weasel! Disarming wdog.\n",
333 			device_xname(sc->sc_dev));
334 		sc->sc_wdog_armed = 0;
335 		sysmon_wdog_setmode(smw, WDOG_MODE_DISARMED, 0);
336 		error = 1;
337 	}
338 	splx(s);
339 
340 	return (error);
341 }
342 
343 static int
344 weasel_pci_wdog_arm(struct weasel_softc *sc)
345 {
346 	u_int8_t reg;
347 	int x;
348 	int s;
349 	int error = 0;
350 
351 	s = splhigh();
352 	if (weasel_issue_command(sc, OS_CMD_WDT_ENABLE)) {
353 		printf("%s: no reply to watchdog enable. Check Weasel \"Allow Watchdog\" setting.\n",
354 			device_xname(sc->sc_dev));
355 		error = EIO;
356 	}
357 	reg = bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_DATA_RD);
358 	bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0);
359 
360 	/*
361 	 * Ensure that the Weasel thinks it's in the same mode we want it to
362 	 * be in.   EIO if not.
363 	 */
364 	x = weasel_pci_wdog_query_state(sc);
365 	switch (x) {
366 		case -1:
367 			error = EIO;
368 			break;
369 		case 0:
370 			sc->sc_wdog_armed = 0;
371 			error = EIO;
372 			break;
373 		case 1:
374 			sc->sc_wdog_armed = 1;
375 			error = 0;
376 			break;
377 	}
378 
379 	splx(s);
380 	return(error);
381 }
382 
383 
384 static int
385 weasel_pci_wdog_disarm(struct weasel_softc *sc)
386 {
387 	u_int8_t reg;
388 	int x;
389 	int s;
390 	int error = 0;
391 
392 	s = splhigh();
393 
394 	if (weasel_issue_command(sc, OS_CMD_WDT_DISABLE)) {
395 		printf("%s: didn't reply to watchdog disable.\n",
396 			device_xname(sc->sc_dev));
397 		error = EIO;
398 	}
399 	reg = bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_DATA_RD);
400 	bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0);
401 
402 	/*
403 	 * Ensure that the Weasel thinks it's in the same mode we want it to
404 	 * be in.   EIO if not.
405 	 */
406 	x = weasel_pci_wdog_query_state(sc);
407 	switch (x) {
408 		case -1:
409 			error = EIO;
410 			break;
411 		case 0:
412 			sc->sc_wdog_armed = 0;
413 			error = 0;
414 			break;
415 		case 1:
416 			sc->sc_wdog_armed = 1;
417 			error = EIO;
418 			break;
419 	}
420 
421 	splx(s);
422 	return(error);
423 }
424 
425 static int
426 weasel_pci_wdog_query_state(struct weasel_softc *sc)
427 {
428 
429 	u_int8_t v;
430 
431 	if (weasel_issue_command(sc, OS_CMD_WDT_QUERY)) {
432 		printf("%s: didn't reply to watchdog state query.\n",
433 			device_xname(sc->sc_dev));
434 		bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0);
435 		return(-1);
436 	}
437 	v = bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_DATA_RD);
438 	bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0);
439 	return(v);
440 }
441