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 | |