From owner-dev-commits-src-all@freebsd.org Wed Jan 13 17:44:04 2021 Return-Path: Delivered-To: dev-commits-src-all@mailman.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mailman.nyi.freebsd.org (Postfix) with ESMTP id 5C9CD4E3814; Wed, 13 Jan 2021 17:44:04 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "Let's Encrypt Authority X3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4DGFDh2Cg9z3GcD; Wed, 13 Jan 2021 17:44:04 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id 3EA731B5FD; Wed, 13 Jan 2021 17:44:04 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org ([127.0.1.44]) by gitrepo.freebsd.org (8.16.1/8.16.1) with ESMTP id 10DHi4K7033570; Wed, 13 Jan 2021 17:44:04 GMT (envelope-from git@gitrepo.freebsd.org) Received: (from git@localhost) by gitrepo.freebsd.org (8.16.1/8.16.1/Submit) id 10DHi43M033569; Wed, 13 Jan 2021 17:44:04 GMT (envelope-from git) Date: Wed, 13 Jan 2021 17:44:04 GMT Message-Id: <202101131744.10DHi43M033569@gitrepo.freebsd.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Emmanuel Vadot Subject: git: 0a05676b44ae - main - Add driver for Synopsys Designware Watchdog timer. MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: manu X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 0a05676b44ae5575141ff731079a8405e111f965 Auto-Submitted: auto-generated X-BeenThere: dev-commits-src-all@freebsd.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Commit messages for all branches of the src repository List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Wed, 13 Jan 2021 17:44:04 -0000 The branch main has been updated by manu: URL: https://cgit.FreeBSD.org/src/commit/?id=0a05676b44ae5575141ff731079a8405e111f965 commit 0a05676b44ae5575141ff731079a8405e111f965 Author: Emmanuel Vadot AuthorDate: 2021-01-13 17:23:51 +0000 Commit: Emmanuel Vadot CommitDate: 2021-01-13 17:43:47 +0000 Add driver for Synopsys Designware Watchdog timer. This driver supports some arm and arm64 boards equipped with "snps,dw-wdt"-compatible watchdog device. Tested on RK3399-based board (RockPro64). Once started watchdog device cannot be stopped. Interrupt handler has mode to kick watchdog even when software does not do it properly. This can be controlled via sysctl: dev.dwwdt.prevent_restart. Also - driver handles system shutdown and prevents from restart when system is asked to reboot. Submitted by: kjopek@gmail.com Differential Revision: https://reviews.freebsd.org/D26761 --- sys/dev/dwwdt/dwwdt.c | 360 +++++++++++++++++++++++++++++++++++++++++++++ sys/modules/Makefile | 2 + sys/modules/dwwdt/Makefile | 7 + 3 files changed, 369 insertions(+) diff --git a/sys/dev/dwwdt/dwwdt.c b/sys/dev/dwwdt/dwwdt.c new file mode 100644 index 000000000000..c08927f44c60 --- /dev/null +++ b/sys/dev/dwwdt/dwwdt.c @@ -0,0 +1,360 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2020 BusyTech + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +/* Registers */ +#define DWWDT_CR 0x00 +#define DWWDT_CR_WDT_EN (1 << 0) +#define DWWDT_CR_RESP_MODE (1 << 1) +#define DWWDT_TORR 0x04 +#define DWWDT_CCVR 0x08 +#define DWWDT_CRR 0x0C +#define DWWDT_CRR_KICK 0x76 +#define DWWDT_STAT 0x10 +#define DWWDT_STAT_STATUS 0x01 +#define DWWDT_EOI 0x14 + +#define DWWDT_READ4(sc, reg) bus_read_4((sc)->sc_mem_res, (reg)) +#define DWWDT_WRITE4(sc, reg, val) \ + bus_write_4((sc)->sc_mem_res, (reg), (val)) + +/* + * 47 = 16 (timeout shift of dwwdt) + 30 (1s ~= 2 ** 30ns) + 1 + * (pre-restart delay) + */ +#define DWWDT_EXP_OFFSET 47 + +struct dwwdt_softc { + device_t sc_dev; + struct resource *sc_mem_res; + struct resource *sc_irq_res; + void *sc_intr_cookie; + clk_t sc_clk; + uint64_t sc_clk_freq; + eventhandler_tag sc_evtag; + int sc_mem_rid; + int sc_irq_rid; + enum { + DWWDT_STOPPED, + DWWDT_RUNNING, + } sc_status; +}; + +static devclass_t dwwdt_devclass; + +SYSCTL_NODE(_dev, OID_AUTO, dwwdt, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, + "Synopsys Designware watchdog timer"); +/* Setting this to 0 enables full restart mode. */ +static uint32_t dwwdt_prevent_restart = 1; +SYSCTL_UINT(_dev_dwwdt, OID_AUTO, prevent_restart, CTLFLAG_RW | CTLFLAG_MPSAFE, + &dwwdt_prevent_restart, 1, + "Prevent system restart (0 - Disabled; 1 - Enabled)"); + +static uint32_t dwwdt_debug_enabled = 0; +SYSCTL_UINT(_dev_dwwdt, OID_AUTO, debug, CTLFLAG_RW | CTLFLAG_MPSAFE, + &dwwdt_debug_enabled, 1, "Debug mode (0 - Disabled; 1 - Enabled)"); + +static int dwwdt_probe(device_t); +static int dwwdt_attach(device_t); +static int dwwdt_detach(device_t); +static int dwwdt_shutdown(device_t); + +static void dwwdt_intr(void *); +static void dwwdt_event(void *, unsigned int, int *); + +/* Helpers */ +static inline void dwwdt_start(struct dwwdt_softc *sc); +static inline bool dwwdt_started(const struct dwwdt_softc *sc); +static inline void dwwdt_stop(struct dwwdt_softc *sc); +static inline void dwwdt_set_timeout(const struct dwwdt_softc *sc, int val); + +static void dwwdt_debug(device_t); + +static void +dwwdt_debug(device_t dev) +{ + /* + * Reading from EOI may clear interrupt flag. + */ + const struct dwwdt_softc *sc = device_get_softc(dev); + + device_printf(dev, "Registers dump: \n"); + device_printf(dev, " CR: %08x\n", DWWDT_READ4(sc, DWWDT_CR)); + device_printf(dev, " CCVR: %08x\n", DWWDT_READ4(sc, DWWDT_CCVR)); + device_printf(dev, " CRR: %08x\n", DWWDT_READ4(sc, DWWDT_CRR)); + device_printf(dev, " STAT: %08x\n", DWWDT_READ4(sc, DWWDT_STAT)); + + device_printf(dev, "Clock: %s\n", clk_get_name(sc->sc_clk)); + device_printf(dev, " FREQ: %lu\n", sc->sc_clk_freq); +} + +static inline bool +dwwdt_started(const struct dwwdt_softc *sc) +{ + + /* CR_WDT_E bit can be clear only by full CPU reset. */ + return ((DWWDT_READ4(sc, DWWDT_CR) & DWWDT_CR_WDT_EN) != 0); +} + +static void inline +dwwdt_start(struct dwwdt_softc *sc) +{ + uint32_t val; + + /* Enable watchdog */ + val = DWWDT_READ4(sc, DWWDT_CR); + val |= DWWDT_CR_WDT_EN | DWWDT_CR_RESP_MODE; + DWWDT_WRITE4(sc, DWWDT_CR, val); + sc->sc_status = DWWDT_RUNNING; +} + +static void inline +dwwdt_stop(struct dwwdt_softc *sc) +{ + + sc->sc_status = DWWDT_STOPPED; + dwwdt_set_timeout(sc, 0x0f); +} + +static void inline +dwwdt_set_timeout(const struct dwwdt_softc *sc, int val) +{ + + DWWDT_WRITE4(sc, DWWDT_TORR, val); + DWWDT_WRITE4(sc, DWWDT_CRR, DWWDT_CRR_KICK); +} + +static void +dwwdt_intr(void *arg) +{ + struct dwwdt_softc *sc = arg; + + KASSERT((DWWDT_READ4(sc, DWWDT_STAT) & DWWDT_STAT_STATUS) != 0, + ("Missing interrupt status bit?")); + + if (dwwdt_prevent_restart != 0 || sc->sc_status == DWWDT_STOPPED) { + /* + * Confirm interrupt reception. Restart counter. + * This also emulates stopping watchdog. + */ + (void)DWWDT_READ4(sc, DWWDT_EOI); + } +} + +static void +dwwdt_event(void *arg, unsigned int cmd, int *error) +{ + struct dwwdt_softc *sc = arg; + const int exponent = flsl(sc->sc_clk_freq); + int timeout; + int val; + + timeout = cmd & WD_INTERVAL; + val = MAX(0, timeout + exponent - DWWDT_EXP_OFFSET); + + dwwdt_stop(sc); + if (cmd == 0 || val > 0x0f) { + /* + * Set maximum time between interrupts and Leave watchdog + * disabled. + */ + return; + } + + dwwdt_set_timeout(sc, val); + dwwdt_start(sc); + *error = 0; + + if (dwwdt_debug_enabled) + dwwdt_debug(sc->sc_dev); +} + +static int +dwwdt_probe(device_t dev) +{ + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + if (!ofw_bus_is_compatible(dev, "snps,dw-wdt")) + return (ENXIO); + + device_set_desc(dev, "Synopsys Designware watchdog timer"); + return (BUS_PROBE_DEFAULT); +} + +static int +dwwdt_attach(device_t dev) +{ + struct dwwdt_softc *sc; + + sc = device_get_softc(dev); + sc->sc_dev = dev; + + sc->sc_mem_rid = 0; + sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, + &sc->sc_mem_rid, RF_ACTIVE); + if (sc->sc_mem_res == NULL) { + device_printf(dev, "cannot allocate memory resource\n"); + goto err_no_mem; + } + + sc->sc_irq_rid = 0; + sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, + &sc->sc_irq_rid, RF_ACTIVE); + if (sc->sc_irq_res == NULL) { + device_printf(dev, "cannot allocate ireq resource\n"); + goto err_no_irq; + } + + sc->sc_intr_cookie = NULL; + if (bus_setup_intr(dev, sc->sc_irq_res, INTR_MPSAFE | INTR_TYPE_MISC, + NULL, dwwdt_intr, sc, &sc->sc_intr_cookie) != 0) { + device_printf(dev, "cannot setup interrupt routine\n"); + goto err_no_intr; + } + + if (clk_get_by_ofw_index(dev, 0, 0, &sc->sc_clk) != 0) { + device_printf(dev, "cannot find clock\n"); + goto err_no_clock; + } + + if (clk_enable(sc->sc_clk) != 0) { + device_printf(dev, "cannot enable clock\n"); + goto err_no_freq; + } + + if (clk_get_freq(sc->sc_clk, &sc->sc_clk_freq) != 0) { + device_printf(dev, "cannot get clock frequency\n"); + goto err_no_freq; + } + + if (sc->sc_clk_freq == 0UL) + goto err_no_freq; + + sc->sc_evtag = EVENTHANDLER_REGISTER(watchdog_list, dwwdt_event, sc, 0); + sc->sc_status = DWWDT_STOPPED; + + return (bus_generic_attach(dev)); + +err_no_freq: + clk_release(sc->sc_clk); +err_no_clock: + bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_intr_cookie); +err_no_intr: + bus_release_resource(dev, SYS_RES_IRQ, sc->sc_irq_rid, sc->sc_irq_res); +err_no_irq: + bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_mem_rid, + sc->sc_mem_res); +err_no_mem: + return (ENXIO); +} + +static int +dwwdt_detach(device_t dev) +{ + struct dwwdt_softc *sc = device_get_softc(dev); + + if (dwwdt_started(sc)) { + /* + * Once started it cannot be stopped. Prevent module unload + * instead. + */ + return (EBUSY); + } + + EVENTHANDLER_DEREGISTER(watchdog_list, sc->sc_evtag); + sc->sc_evtag = NULL; + + if (sc->sc_clk != NULL) + clk_release(sc->sc_clk); + + if (sc->sc_intr_cookie != NULL) + bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_intr_cookie); + + if (sc->sc_irq_res) { + bus_release_resource(dev, SYS_RES_IRQ, sc->sc_irq_rid, + sc->sc_irq_res); + } + + if (sc->sc_mem_res) { + bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_mem_rid, + sc->sc_mem_res); + } + + return (bus_generic_detach(dev)); +} + +static int +dwwdt_shutdown(device_t dev) +{ + struct dwwdt_softc *sc; + + sc = device_get_softc(dev); + + /* Prevent restarts during shutdown. */ + dwwdt_prevent_restart = 1; + dwwdt_stop(sc); + return (bus_generic_shutdown(dev)); +} + +static device_method_t dwwdt_methods[] = { + DEVMETHOD(device_probe, dwwdt_probe), + DEVMETHOD(device_attach, dwwdt_attach), + DEVMETHOD(device_detach, dwwdt_detach), + DEVMETHOD(device_shutdown, dwwdt_shutdown), + + {0, 0} +}; + +static driver_t dwwdt_driver = { + "dwwdt", + dwwdt_methods, + sizeof(struct dwwdt_softc), +}; + +DRIVER_MODULE(dwwdt, simplebus, dwwdt_driver, dwwdt_devclass, NULL, NULL); +MODULE_VERSION(dwwdt, 1); diff --git a/sys/modules/Makefile b/sys/modules/Makefile index d417309cdee3..320f4002f817 100644 --- a/sys/modules/Makefile +++ b/sys/modules/Makefile @@ -108,6 +108,7 @@ SUBDIR= \ ${_dpdk_lpm6} \ ${_dpms} \ dummynet \ + ${_dwwdt} \ ${_efirt} \ ${_em} \ ${_ena} \ @@ -611,6 +612,7 @@ _cxgb= cxgb .if ${MACHINE_CPUARCH} == "aarch64" _allwinner= allwinner _armv8crypto= armv8crypto +_dwwdt= dwwdt _em= em _rockchip= rockchip .endif diff --git a/sys/modules/dwwdt/Makefile b/sys/modules/dwwdt/Makefile new file mode 100644 index 000000000000..e486705998d5 --- /dev/null +++ b/sys/modules/dwwdt/Makefile @@ -0,0 +1,7 @@ +.PATH: ${SRCTOP}/sys/dev/dwwdt + +KMOD = dwwdt +SRCS = dwwdt.c \ + device_if.h bus_if.h ofw_bus_if.h + +.include