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