Index: head/sys/arm/freescale/imx/imx_machdep.c =================================================================== --- head/sys/arm/freescale/imx/imx_machdep.c (revision 328441) +++ head/sys/arm/freescale/imx/imx_machdep.c (revision 328442) @@ -1,107 +1,132 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2013 Ian Lepore * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "opt_platform.h" #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include +#include #include #include #include SYSCTL_NODE(_hw, OID_AUTO, imx, CTLFLAG_RW, NULL, "i.MX container"); static int last_reset_status; SYSCTL_UINT(_hw_imx, OID_AUTO, last_reset_status, CTLFLAG_RD, &last_reset_status, 0, "Last reset status register"); SYSCTL_STRING(_hw_imx, OID_AUTO, last_reset_reason, CTLFLAG_RD, "unknown", 0, "Last reset reason"); /* * This code which manipulates the watchdog hardware is here to implement * cpu_reset() because the watchdog is the only way for software to reset the * chip. Why here and not in imx_wdog.c? Because there's no requirement that * the watchdog driver be compiled in, but it's nice to be able to reboot even * if it's not. */ void imx_wdog_cpu_reset(vm_offset_t wdcr_physaddr) { - volatile uint16_t * pcr; + volatile uint16_t cr, *pcr; + if ((pcr = devmap_ptov(wdcr_physaddr, sizeof(*pcr))) == NULL) { + printf("imx_wdog_cpu_reset(): " + "cannot find control register... locking up now."); + for (;;) + cpu_spinwait(); + } + cr = *pcr; + /* - * Trigger an immediate reset by clearing the SRS bit in the watchdog - * control register. The reset happens on the next cycle of the wdog - * 32KHz clock, so hang out in a spin loop until the reset takes effect. + * If the watchdog hardware has been set up to trigger an external reset + * signal on watchdog timeout, then we do software-requested rebooting + * the same way, by asserting the external reset signal. * + * Asserting external reset is supposed to result in some external + * component asserting the POR pin on the SoC, possibly after adjusting + * and stabilizing system voltages, or taking other system-wide reset + * actions. Just in case there is some kind of misconfiguration, we + * hang out and do nothing for a full second, then continue on into + * the code to assert a software reset as well. + */ + if (cr & WDOG_CR_WDT) { + cr &= ~WDOG_CR_WDA; /* Assert active-low ext reset bit. */ + *pcr = cr; + DELAY(1000000); + printf("imx_wdog_cpu_reset(): " + "External reset failed, trying internal cpu-reset\n"); + DELAY(10000); /* Time for printf to appear */ + } + + /* * Imx6 erratum ERR004346 says the SRS bit has to be cleared twice * within the same cycle of the 32khz clock to reliably trigger the * reset. Writing it 3 times in a row ensures at least 2 of the writes * happen in the same 32k clock cycle. */ - if ((pcr = devmap_ptov(wdcr_physaddr, sizeof(*pcr))) == NULL) { - printf("cpu_reset() can't find its control register... locking up now."); - } else { - *pcr &= ~WDOG_CR_SRS; - *pcr &= ~WDOG_CR_SRS; - *pcr &= ~WDOG_CR_SRS; - } + cr &= ~WDOG_CR_SRS; /* Assert active-low software reset bit. */ + *pcr = cr; + *pcr = cr; + *pcr = cr; + + /* Reset happens on the next tick of the 32khz clock, wait for it. */ for (;;) - continue; + cpu_spinwait(); } void imx_wdog_init_last_reset(vm_offset_t wdsr_phys) { volatile uint16_t * psr; if ((psr = devmap_ptov(wdsr_phys, sizeof(*psr))) == NULL) return; last_reset_status = *psr; if (last_reset_status & WDOG_RSR_SFTW) { sysctl___hw_imx_last_reset_reason.oid_arg1 = "SoftwareReset"; } else if (last_reset_status & WDOG_RSR_TOUT) { sysctl___hw_imx_last_reset_reason.oid_arg1 = "WatchdogTimeout"; } else if (last_reset_status & WDOG_RSR_POR) { sysctl___hw_imx_last_reset_reason.oid_arg1 = "PowerOnReset"; } } Index: head/sys/arm/freescale/imx/imx_wdog.c =================================================================== --- head/sys/arm/freescale/imx/imx_wdog.c (revision 328441) +++ head/sys/arm/freescale/imx/imx_wdog.c (revision 328442) @@ -1,180 +1,229 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2012, 2013 The FreeBSD Foundation * All rights reserved. * * This software was developed by Oleksandr Rybalko under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include struct imx_wdog_softc { struct mtx sc_mtx; device_t sc_dev; struct resource *sc_res[2]; + void *sc_ih; uint32_t sc_timeout; + bool sc_pde_enabled; }; static struct resource_spec imx_wdog_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE }, RESOURCE_SPEC_END }; #define MEMRES 0 #define IRQRES 1 static struct ofw_compat_data compat_data[] = { {"fsl,imx6sx-wdt", 1}, {"fsl,imx6sl-wdt", 1}, {"fsl,imx6q-wdt", 1}, {"fsl,imx53-wdt", 1}, {"fsl,imx51-wdt", 1}, {"fsl,imx50-wdt", 1}, {"fsl,imx35-wdt", 1}, {"fsl,imx27-wdt", 1}, {"fsl,imx25-wdt", 1}, {"fsl,imx21-wdt", 1}, {NULL, 0} }; static inline uint16_t RD2(struct imx_wdog_softc *sc, bus_size_t offs) { return (bus_read_2(sc->sc_res[MEMRES], offs)); } static inline void WR2(struct imx_wdog_softc *sc, bus_size_t offs, uint16_t val) { bus_write_2(sc->sc_res[MEMRES], offs, val); } static void imx_watchdog(void *arg, u_int cmd, int *error) { struct imx_wdog_softc *sc; uint16_t reg; u_int timeout; sc = arg; mtx_lock(&sc->sc_mtx); if (cmd == 0) { if (bootverbose) device_printf(sc->sc_dev, "Can not be disabled.\n"); *error = EOPNOTSUPP; } else { timeout = (u_int)((1ULL << (cmd & WD_INTERVAL)) / 1000000000U); if (timeout > 1 && timeout < 128) { if (timeout != sc->sc_timeout) { sc->sc_timeout = timeout; reg = RD2(sc, WDOG_CR_REG); reg &= ~WDOG_CR_WT_MASK; reg |= (timeout << (WDOG_CR_WT_SHIFT + 1)) & WDOG_CR_WT_MASK; WR2(sc, WDOG_CR_REG, reg | WDOG_CR_WDE); } /* Refresh counter */ WR2(sc, WDOG_SR_REG, WDOG_SR_STEP1); WR2(sc, WDOG_SR_REG, WDOG_SR_STEP2); + /* Watchdog active, can disable rom-boot watchdog. */ + if (sc->sc_pde_enabled) { + sc->sc_pde_enabled = false; + reg = RD2(sc, WDOG_MCR_REG); + WR2(sc, WDOG_MCR_REG, reg & ~WDOG_MCR_PDE); + } *error = 0; } } mtx_unlock(&sc->sc_mtx); } static int +imx_wdog_intr(void *arg) +{ + struct imx_wdog_softc *sc = arg; + + /* + * When configured for external reset, the actual reset is supposed to + * happen when some external device responds to the assertion of the + * WDOG_B signal by asserting the POR signal to the chip. This + * interrupt handler is a backstop mechanism; it is set up to fire + * simultaneously with WDOG_B, and if the external reset happens we'll + * never actually make it to here. If we do make it here, just trigger + * a software reset. That code will see that external reset is + * configured, and it will wait for 1 second for it to take effect, then + * it will do a software reset as a fallback. + */ + imx_wdog_cpu_reset(BUS_SPACE_PHYSADDR(sc->sc_res[MEMRES], WDOG_CR_REG)); + + return (FILTER_HANDLED); /* unreached */ +} + +static int imx_wdog_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "Freescale i.MX Watchdog"); return (0); } static int imx_wdog_attach(device_t dev) { struct imx_wdog_softc *sc; sc = device_get_softc(dev); sc->sc_dev = dev; if (bus_alloc_resources(dev, imx_wdog_spec, sc->sc_res)) { device_printf(dev, "could not allocate resources\n"); return (ENXIO); } mtx_init(&sc->sc_mtx, device_get_nameunit(dev), "imx_wdt", MTX_DEF); - /* TODO: handle interrupt */ + /* + * If we're configured to assert an external reset signal, set up the + * hardware to do so, and install an interrupt handler whose only + * purpose is to backstop the external reset. Don't worry if the + * interrupt setup fails, since it's only a backstop measure. + */ + if (ofw_bus_has_prop(sc->sc_dev, "fsl,ext-reset-output")) { + WR2(sc, WDOG_CR_REG, WDOG_CR_WDT | RD2(sc, WDOG_CR_REG)); + bus_setup_intr(sc->sc_dev, sc->sc_res[IRQRES], + INTR_TYPE_MISC | INTR_MPSAFE, imx_wdog_intr, NULL, sc, + &sc->sc_ih); + WR2(sc, WDOG_ICR_REG, WDOG_ICR_WIE); /* Enable, count is 0. */ + } + + /* + * Note whether the rom-boot so-called "power-down" watchdog is active, + * so we can disable it when the regular watchdog is first enabled. + */ + if (RD2(sc, WDOG_MCR_REG) & WDOG_MCR_PDE) + sc->sc_pde_enabled = true; EVENTHANDLER_REGISTER(watchdog_list, imx_watchdog, sc, 0); return (0); } static device_method_t imx_wdog_methods[] = { DEVMETHOD(device_probe, imx_wdog_probe), DEVMETHOD(device_attach, imx_wdog_attach), DEVMETHOD_END }; static driver_t imx_wdog_driver = { "imx_wdog", imx_wdog_methods, sizeof(struct imx_wdog_softc), }; static devclass_t imx_wdog_devclass; DRIVER_MODULE(imx_wdog, simplebus, imx_wdog_driver, imx_wdog_devclass, 0, 0);