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