xref: /netbsd-src/sys/arch/x86/pci/tco.c (revision 82d56013d7b633d116a93943de88e08335357a7c)
1 /*	$NetBSD: tco.c,v 1.2 2015/08/30 07:50:34 christos Exp $	*/
2 
3 /*-
4  * Copyright (c) 2015 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Minoura Makoto and Matthew R. Green.
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  * Intel I/O Controller Hub (ICHn) watchdog timer
34  */
35 
36 #include <sys/cdefs.h>
37 __KERNEL_RCSID(0, "$NetBSD: tco.c,v 1.2 2015/08/30 07:50:34 christos Exp $");
38 
39 #include <sys/types.h>
40 #include <sys/param.h>
41 #include <sys/systm.h>
42 #include <sys/device.h>
43 #include <sys/timetc.h>
44 #include <sys/module.h>
45 
46 #include <dev/pci/pcivar.h>
47 #include <dev/pci/pcireg.h>
48 #include <dev/ic/i82801lpcreg.h>
49 
50 #include <dev/sysmon/sysmonvar.h>
51 
52 #include <arch/x86/pci/tco.h>
53 
54 #include "pcibvar.h"
55 
56 struct tco_softc{
57 	struct sysmon_wdog	sc_smw;
58 	bus_space_tag_t		sc_iot;
59 	bus_space_handle_t	sc_ioh;
60 	bus_space_tag_t		sc_rcbat;
61 	bus_space_handle_t	sc_rcbah;
62 	struct pcib_softc *	sc_pcib;
63 	int			sc_armed;
64 	unsigned int		sc_min_t;
65 	unsigned int		sc_max_t;
66 	int			sc_has_rcba;
67 };
68 
69 static int tco_match(device_t, cfdata_t, void *);
70 static void tco_attach(device_t, device_t, void *);
71 static int tco_detach(device_t, int);
72 
73 static bool tco_suspend(device_t, const pmf_qual_t *);
74 
75 static int tcotimer_setmode(struct sysmon_wdog *);
76 static int tcotimer_tickle(struct sysmon_wdog *);
77 static void tcotimer_stop(struct tco_softc *);
78 static void tcotimer_start(struct tco_softc *);
79 static void tcotimer_status_reset(struct tco_softc *);
80 static int  tcotimer_disable_noreboot(device_t);
81 
82 CFATTACH_DECL3_NEW(tco, sizeof(struct tco_softc),
83     tco_match, tco_attach, tco_detach, NULL, NULL, NULL, 0);
84 
85 /*
86  * Autoconf callbacks.
87  */
88 static int
89 tco_match(device_t parent, cfdata_t match, void *aux)
90 {
91 	struct lpcib_tco_attach_args *ta = aux;
92 
93 	if (ta->ta_iot != 0)
94 		return 1;
95 
96 	return 0;
97 }
98 
99 static void
100 tco_attach(device_t parent, device_t self, void *aux)
101 {
102 	struct tco_softc *sc = device_private(self);
103 	struct lpcib_tco_attach_args *ta = aux;
104 	uint32_t ioreg;
105 
106 	/* Retrieve bus info shared with parent/siblings */
107 
108 	sc->sc_iot = ta->ta_iot;
109 	sc->sc_ioh = ta->ta_ioh;
110 	sc->sc_rcbat = ta->ta_rcbat;
111 	sc->sc_rcbah = ta->ta_rcbah;
112 	sc->sc_pcib = ta->ta_pcib;
113 	sc->sc_has_rcba = ta->ta_has_rcba;
114 
115 	aprint_normal(": TCO (watchdog) timer configured.\n");
116 	aprint_naive("\n");
117 
118 	/* Explicitly stop the TCO timer. */
119 	tcotimer_stop(sc);
120 
121 	/*
122 	 * Enable TCO timeout SMI only if the hardware reset does not
123 	 * work. We don't know what the SMBIOS does.
124 	 */
125 	ioreg = bus_space_read_4(sc->sc_iot, sc->sc_ioh, LPCIB_SMI_EN);
126 	ioreg &= ~LPCIB_SMI_EN_TCO_EN;
127 
128 	/*
129 	 * Clear the No Reboot (NR) bit. If this fails, enabling the TCO_EN bit
130 	 * in the SMI_EN register is the last chance.
131 	 */
132 	if (tcotimer_disable_noreboot(self)) {
133 		ioreg |= LPCIB_SMI_EN_TCO_EN;
134 	}
135 	if ((ioreg & LPCIB_SMI_EN_GBL_SMI_EN) != 0) {
136 		bus_space_write_4(sc->sc_iot, sc->sc_ioh, LPCIB_SMI_EN, ioreg);
137 	}
138 
139 	/* Reset the watchdog status registers. */
140 	tcotimer_status_reset(sc);
141 
142 	/*
143 	 * Register the driver with the sysmon watchdog framework.
144 	 */
145 	sc->sc_smw.smw_name = device_xname(self);
146 	sc->sc_smw.smw_cookie = sc;
147 	sc->sc_smw.smw_setmode = tcotimer_setmode;
148 	sc->sc_smw.smw_tickle = tcotimer_tickle;
149 
150 	/*
151 	 * ICH6 or newer are limited to 2ticks min and 613ticks max.
152 	 *                              1sec           367secs
153 	 *
154 	 * ICH5 or older are limited to 4ticks min and 39ticks max.
155 	 *                              2secs          23secs
156 	 */
157 	if (sc->sc_has_rcba) {
158 		sc->sc_max_t = LPCIB_TCOTIMER2_MAX_TICK;
159 		sc->sc_min_t = LPCIB_TCOTIMER2_MIN_TICK;
160 	} else {
161 		sc->sc_max_t = LPCIB_TCOTIMER_MAX_TICK;
162 		sc->sc_min_t = LPCIB_TCOTIMER_MIN_TICK;
163 	}
164 	sc->sc_smw.smw_period = lpcib_tcotimer_tick_to_second(sc->sc_max_t);
165 
166 	aprint_verbose_dev(self, "Min/Max interval %u/%u seconds\n",
167 		lpcib_tcotimer_tick_to_second(sc->sc_min_t),
168 		lpcib_tcotimer_tick_to_second(sc->sc_max_t));
169 
170 	if (sysmon_wdog_register(&sc->sc_smw))
171 		aprint_error_dev(self, "unable to register TCO timer"
172 		       "as a sysmon watchdog device.\n");
173 
174 	if (!pmf_device_register(self, tco_suspend, NULL))
175 		aprint_error_dev(self, "unable to register with pmf\n");
176 }
177 
178 static int
179 tco_detach(device_t self, int flags)
180 {
181 	struct tco_softc *sc = device_private(self);
182 	int rc;
183 
184 	if ((rc = sysmon_wdog_unregister(&sc->sc_smw)) != 0) {
185 		if (rc == ERESTART)
186 			rc = EINTR;
187 		return rc;
188 	}
189 
190 	/* Explicitly stop the TCO timer. */
191 	tcotimer_stop(sc);
192 
193 	/* XXX Set No Reboot? */
194 
195 	pmf_device_deregister(self);
196 
197 	return 0;
198 }
199 
200 static bool
201 tco_suspend(device_t self, const pmf_qual_t *quals)
202 {
203 	struct tco_softc *sc = device_private(self);
204 
205 	/* Allow suspend only if watchdog is not armed */
206 
207 	return ((sc->sc_smw.smw_mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED);
208 }
209 
210 /*
211  * Sysmon watchdog callbacks.
212  */
213 static int
214 tcotimer_setmode(struct sysmon_wdog *smw)
215 {
216 	struct tco_softc *sc = smw->smw_cookie;
217 	unsigned int period;
218 	uint16_t ich6period = 0;
219 	uint8_t ich5period = 0;
220 
221 	if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) {
222 		/* Stop the TCO timer. */
223 		tcotimer_stop(sc);
224 	} else {
225 		period = lpcib_tcotimer_second_to_tick(smw->smw_period);
226 		if (period < sc->sc_min_t || period > sc->sc_max_t)
227 			return EINVAL;
228 
229 		/* Stop the TCO timer, */
230 		tcotimer_stop(sc);
231 
232 		/* set the timeout, */
233 		if (sc->sc_has_rcba) {
234 			/* ICH6 or newer */
235 			ich6period = bus_space_read_2(sc->sc_iot, sc->sc_ioh,
236 						      LPCIB_TCO_TMR2);
237 			ich6period &= 0xfc00;
238 			bus_space_write_2(sc->sc_iot, sc->sc_ioh,
239 					  LPCIB_TCO_TMR2, ich6period | period);
240 		} else {
241 			/* ICH5 or older */
242 			ich5period = bus_space_read_1(sc->sc_iot, sc->sc_ioh,
243 						   LPCIB_TCO_TMR);
244 			ich5period &= 0xc0;
245 			bus_space_write_1(sc->sc_iot, sc->sc_ioh,
246 					  LPCIB_TCO_TMR, ich5period | period);
247 		}
248 
249 		/* and start/reload the timer. */
250 		tcotimer_start(sc);
251 		tcotimer_tickle(smw);
252 	}
253 
254 	return 0;
255 }
256 
257 static int
258 tcotimer_tickle(struct sysmon_wdog *smw)
259 {
260 	struct tco_softc *sc = smw->smw_cookie;
261 
262 	/* any value is allowed */
263 	if (sc->sc_has_rcba)
264 		bus_space_write_2(sc->sc_iot, sc->sc_ioh, LPCIB_TCO_RLD, 1);
265 	else
266 		bus_space_write_1(sc->sc_iot, sc->sc_ioh, LPCIB_TCO_RLD, 1);
267 
268 	return 0;
269 }
270 
271 static void
272 tcotimer_stop(struct tco_softc *sc)
273 {
274 	uint16_t ioreg;
275 
276 	ioreg = bus_space_read_2(sc->sc_iot, sc->sc_ioh, LPCIB_TCO1_CNT);
277 	ioreg |= LPCIB_TCO1_CNT_TCO_TMR_HLT;
278 	bus_space_write_2(sc->sc_iot, sc->sc_ioh, LPCIB_TCO1_CNT, ioreg);
279 }
280 
281 static void
282 tcotimer_start(struct tco_softc *sc)
283 {
284 	uint16_t ioreg;
285 
286 	ioreg = bus_space_read_2(sc->sc_iot, sc->sc_ioh, LPCIB_TCO1_CNT);
287 	ioreg &= ~LPCIB_TCO1_CNT_TCO_TMR_HLT;
288 	bus_space_write_2(sc->sc_iot, sc->sc_ioh, LPCIB_TCO1_CNT, ioreg);
289 }
290 
291 static void
292 tcotimer_status_reset(struct tco_softc *sc)
293 {
294 	bus_space_write_2(sc->sc_iot, sc->sc_ioh, LPCIB_TCO1_STS,
295 			  LPCIB_TCO1_STS_TIMEOUT);
296 	bus_space_write_2(sc->sc_iot, sc->sc_ioh, LPCIB_TCO2_STS,
297 			  LPCIB_TCO2_STS_BOOT_STS);
298 	bus_space_write_2(sc->sc_iot, sc->sc_ioh, LPCIB_TCO2_STS,
299 			  LPCIB_TCO2_STS_SECONDS_TO_STS);
300 }
301 
302 /*
303  * Clear the No Reboot (NR) bit, this enables reboots when the timer
304  * reaches the timeout for the second time.
305  */
306 static int
307 tcotimer_disable_noreboot(device_t self)
308 {
309 	struct tco_softc *sc = device_private(self);
310 
311 	if (sc->sc_has_rcba) {
312 		uint32_t status;
313 
314 		status = bus_space_read_4(sc->sc_rcbat, sc->sc_rcbah,
315 		    LPCIB_GCS_OFFSET);
316 		status &= ~LPCIB_GCS_NO_REBOOT;
317 		bus_space_write_4(sc->sc_rcbat, sc->sc_rcbah,
318 		    LPCIB_GCS_OFFSET, status);
319 		status = bus_space_read_4(sc->sc_rcbat, sc->sc_rcbah,
320 		    LPCIB_GCS_OFFSET);
321 		if (status & LPCIB_GCS_NO_REBOOT)
322 			goto error;
323 	} else {
324 		pcireg_t pcireg;
325 
326 		pcireg = pci_conf_read(sc->sc_pcib->sc_pc, sc->sc_pcib->sc_tag,
327 				       LPCIB_PCI_GEN_STA);
328 		if (pcireg & LPCIB_PCI_GEN_STA_NO_REBOOT) {
329 			/* TCO timeout reset is disabled; try to enable it */
330 			pcireg &= ~LPCIB_PCI_GEN_STA_NO_REBOOT;
331 			pci_conf_write(sc->sc_pcib->sc_pc, sc->sc_pcib->sc_tag,
332 				       LPCIB_PCI_GEN_STA, pcireg);
333 			if (pcireg & LPCIB_PCI_GEN_STA_NO_REBOOT)
334 				goto error;
335 		}
336 	}
337 
338 	return 0;
339 error:
340 	aprint_error_dev(self, "TCO timer reboot disabled by hardware; "
341 	    "hope SMBIOS properly handles it.\n");
342 	return EINVAL;
343 }
344 
345 MODULE(MODULE_CLASS_DRIVER, tco, "sysmon_wdog");
346 
347 #ifdef _MODULE
348 #include "ioconf.c"
349 #endif
350 
351 static int
352 tco_modcmd(modcmd_t cmd, void *arg)
353 {
354 	int ret = 0;
355 
356 	switch (cmd) {
357 	case MODULE_CMD_INIT:
358 #ifdef _MODULE
359 		ret = config_init_component(cfdriver_ioconf_tco,
360 					    cfattach_ioconf_tco,
361 					    cfdata_ioconf_tco);
362 #endif
363 		break;
364 	case MODULE_CMD_FINI:
365 #ifdef _MODULE
366 		ret = config_fini_component(cfdriver_ioconf_tco,
367 					    cfattach_ioconf_tco,
368 					    cfdata_ioconf_tco);
369 #endif
370 		break;
371 	default:
372 		ret = ENOTTY;
373 	}
374 
375 	return ret;
376 }
377