Hi,

this is a driver for the watchdog found in RockChip SoCs (at least RockChip 3399). Note that it attaches to "snps,dw-wdt", so it may be found elsewhere and maybe another name may be more appropriate.

I have no idea how to figure out the frequency of the driving clock so the driver measures the frequency on first use.

The timeout can be programmed only to about powers of two (1,2,5,10,21s) on by RockPro64, where the watchdog is driven by a ridiculously fast clock of 100 MHz (100 KHz would be more adequate).

On my RockPro64 the watchdog cannot be disabled once it has been enabled. To work around this I (ab)use the interrupt mechanism of the watchdog and would recommend to simlpy use kern.watchdog.auto=1 to "disable" the watchdog. This won't save ddb(4) though.

While tracing the driver I noticed that interrupts are delivered twice.

I'm looking for reviews and OKs. Thanks!


Christopher


Index: sys/dev/fdt/files.fdt
===================================================================
RCS file: /cvs/src/sys/dev/fdt/files.fdt,v
retrieving revision 1.160
diff -u -p -r1.160 files.fdt
--- sys/dev/fdt/files.fdt       21 Nov 2021 11:02:21 -0000      1.160
+++ sys/dev/fdt/files.fdt       8 Dec 2021 09:37:23 -0000
@@ -383,6 +383,10 @@ device     rkvop
 attach rkvop at fdt
 file   dev/fdt/rkvop.c                 rkvop
+device rkdog
+attach rkdog at fdt
+file   dev/fdt/rkdog.c                 rkdog
+
 device dwmmc: sdmmcbus
 attach dwmmc at fdt
 file   dev/fdt/dwmmc.c                 dwmmc
Index: sys/dev/fdt/rkdog.c
===================================================================
RCS file: sys/dev/fdt/rkdog.c
diff -N sys/dev/fdt/rkdog.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ sys/dev/fdt/rkdog.c 8 Dec 2021 09:37:23 -0000
@@ -0,0 +1,263 @@
+/*     $OpenBSD: rkdog.c,v 1.3 2020/05/29 04:42:25 deraadt Exp $       */
+/*
+ * Copyright (c) 2021 Christopher Zimmermann <chr...@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/device.h>
+#include <sys/timeout.h>
+
+#include <machine/intr.h>
+#include <machine/bus.h>
+#include <machine/fdt.h>
+
+#include <dev/ofw/openfirm.h>
+#include <dev/ofw/ofw_clock.h>
+#include <dev/ofw/fdt.h>
+
+/* Registers */
+#define RKDOG_CR               0x0000 /* control */
+#define  RKDOG_CR_ENABLE       0x0001 /* enable counter */
+#define  RKDOG_CR_INTERRUPT    0x0002
+/*
+ * On some devices RKDOG_CR_ENABLE can only be set, but not be cleared.
+ *
+ * If RKDOG_CR_INTERRUPT is enabled the watchdog will count twice. After the
+ * first count the interrupt is raised as a warning. After the second count
+ * reset happens only when the interrupt is not cleared.
+ */
+#define  RKDOG_CR_RST_PULSE_LENGTH_MASK                0x001c
+#define  RKDOG_CR_RST_PULSE_LENGTH_SHIFT       2
+/*
+ * 000: 2 pclk cycles
+ * 001: 4 pclk cycles
+ * 010: 8 pclk cycles  DEFAULT
+ * 011: 16 pclk cycles
+ * 100: 32 pclk cycles
+ * 101: 64 pclk cycles
+ * 110: 128 pclk cycles
+ * 111: 256 pclk cycles
+ */
+/* timeout range n={0..15}. Timeout is 2^(16+n)-1 ticks */
+#define RKDOG_TORR             0x0004
+#define RKDOG_CCVR             0x0008 /* current counter value */
+#define RKDOG_CRR              0x000c /* counter restart by writing code 
number */
+#define  RKDOG_CRR_MAGIC       0x76
+#define RKDOG_STAT             0x0010 /* interrupt status */
+#define RKDOG_EOI              0x0014 /* clear interrupt */
+
+#define HREAD4(sc, reg)                                                        
\
+       (bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg)))
+#define HWRITE4(sc, reg, val)                                          \
+       bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val))
+
+struct rkdog_softc {
+       struct device           sc_dev;
+       bus_space_tag_t         sc_iot;
+       bus_space_handle_t      sc_ioh;
+
+       struct timeout          sc_to;
+       uint32_t                sc_freq;
+       int                     sc_period;
+};
+
+int            rkdog_match(struct device *, void *, void *);
+void           rkdog_attach(struct device *, struct device *, void *);
+static void    rkdog_dump(struct rkdog_softc *, const char *);
+int            rkdog_intr(void *);
+int            rkdog_cb(void *, int);
+
+struct cfattach        rkdog_ca = {
+       sizeof (struct rkdog_softc), rkdog_match, rkdog_attach
+};
+
+struct cfdriver rkdog_cd = {
+       NULL, "rkdog", DV_DULL
+};
+
+int
+rkdog_match(struct device *parent, void *match, void *aux)
+{
+       struct fdt_attach_args *faa = aux;
+
+       return OF_is_compatible(faa->fa_node, "snps,dw-wdt");
+}
+
+void
+rkdog_attach(struct device *parent, struct device *self, void *aux)
+{
+       struct rkdog_softc *sc = (struct rkdog_softc *)self;
+       struct fdt_attach_args *faa = aux;
+
+       if (faa->fa_nreg < 1) {
+               printf(": no registers\n");
+               return;
+       }
+
+       sc->sc_iot = faa->fa_iot;
+       if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
+           faa->fa_reg[0].size, 0, &sc->sc_ioh)) {
+               printf(": can't map registers\n");
+               return;
+       }
+
+       if (HREAD4(sc, RKDOG_CR) != 0x0a) {
+               printf(" unexpected (expected 0xa)\n");
+               return;
+       }
+
+       fdt_intr_establish(faa->fa_node, IPL_CLOCK, rkdog_intr,
+           sc, sc->sc_dev.dv_xname);
+       if (NULL == fdt_intr_establish(faa->fa_node, IPL_BIO,
+           rkdog_intr, sc, sc->sc_dev.dv_xname)) {
+               printf(": unable to establish interrupt\n");
+               return;
+       }
+
+       printf("\n");
+
+       sc->sc_freq = 0;
+
+       rkdog_dump(sc, "initial dump");
+
+#ifndef SMALL_KERNEL
+       wdog_register(rkdog_cb, sc);
+#endif
+
+       clock_set_assigned(faa->fa_node);
+       clock_enable_all(faa->fa_node);
+}
+
+static void
+rkdog_dump(struct rkdog_softc *sc, const char *msg) {
+#ifdef DEBUG
+       printf("%s: %s\n  CR=%#x TORR=%#x(%u) CCVR=%#x STAT=%#x\n",
+           sc->sc_dev.dv_xname, msg,
+           HREAD4(sc, RKDOG_CR),
+           HREAD4(sc, RKDOG_TORR),
+           HREAD4(sc, RKDOG_TORR),
+           HREAD4(sc, RKDOG_CCVR),
+           HREAD4(sc, RKDOG_STAT));
+#endif
+}
+
+int
+rkdog_intr(void *self) {
+       struct rkdog_softc *sc = self;
+
+       rkdog_dump(sc, "interrupt");
+
+       /* clear all interrupts */
+       HREAD4(sc, RKDOG_EOI);
+
+       return 1;
+}
+
+int
+rkdog_cb(void *self, int period)
+{
+       struct rkdog_softc *sc = self;
+       unsigned tor = 0;
+       uint32_t control;
+
+       rkdog_dump(sc, "watchdog callback");
+
+       /*
+        * to enable set RC_ENABLE and clear RC_INTERRUPT
+        * to disable clear RC_ENABLE and set RC_INTERRUPT
+        * Even when RC_ENABLE cannot be cleared this prevents a reset because
+        * we acknowledge all interrupts.
+        */
+
+       if (period == 0) {
+               /* in case counter cannot be disabled enable interrupt */
+               control = HREAD4(sc, RKDOG_CR);
+               if ((control & RKDOG_CR_ENABLE) == 0)
+                       return 0;
+               HWRITE4(sc, RKDOG_TORR, 15);
+               control &= ~RKDOG_CR_ENABLE;
+               control |= RKDOG_CR_INTERRUPT;
+               HWRITE4(sc, RKDOG_CR, control);
+               control = HREAD4(sc, RKDOG_CR);
+               
+               if (control & RKDOG_CR_ENABLE) {
+                       /* we don't enable without measuring freq */
+                       printf("%s: Cannot disable watchdog timer. "
+                           "Please enable kern.watchdog.auto as a 
workaround\n",
+                           sc->sc_dev.dv_xname);
+                       return (0x7fffffff / sc->sc_freq);
+               }
+               else
+                       return 0;
+       }
+
+       if (period == sc->sc_period) {
+#ifdef DEBUG
+               printf("%s: just tickling\n", sc->sc_dev.dv_xname);
+#endif
+               HWRITE4(sc, RKDOG_CRR, RKDOG_CRR_MAGIC);
+               return sc->sc_period;
+       }
+
+       control = HREAD4(sc, RKDOG_CR);
+
+       if (sc->sc_freq == 0) {
+               uint32_t ticks, dt = 1000; /* us */
+
+               HWRITE4(sc, RKDOG_TORR, 15);
+               control |= RKDOG_CR_ENABLE | RKDOG_CR_INTERRUPT;
+               HWRITE4(sc, RKDOG_CR, control);
+               HWRITE4(sc, RKDOG_CRR, RKDOG_CRR_MAGIC);
+
+               ticks = HREAD4(sc, RKDOG_CCVR);
+               delay(dt);
+               ticks -= HREAD4(sc, RKDOG_CCVR);
+               sc->sc_freq = ticks * (1000000 / dt);
+#ifdef DEBUG
+               printf("%s: %d / %u us => frequency %u\n",
+                   sc->sc_dev.dv_xname, ticks, dt, sc->sc_freq);
+#endif
+       }
+
+       while (tor < 15 &&
+           1 << tor < (sc->sc_freq >> 16) * period)
+               tor++;
+
+#ifdef DEBUG
+       printf("%s: target period=%ds ticks_wanted=%u tor=%u ticks=%#x(%u / 
%us)\n",
+                       sc->sc_dev.dv_xname,
+                       period,
+                       sc->sc_freq * period - 1,
+                       tor,
+                       (1 << (16+tor)) - 1,
+                       (1 << (16+tor)) - 1,
+                       ((1 << (16+tor)) - 1) / sc->sc_freq);
+#endif
+
+       assert((1 << (16+tor)) - 1 >= sc->sc_freq);
+       sc->sc_period = ((1 << (16+tor)) - 1) / sc->sc_freq;
+
+       HWRITE4(sc, RKDOG_TORR, tor);
+       control = HREAD4(sc, RKDOG_CR);
+       control |= RKDOG_CR_ENABLE;
+       control &= ~RKDOG_CR_INTERRUPT;
+       HWRITE4(sc, RKDOG_CR, control);
+       HWRITE4(sc, RKDOG_CRR, RKDOG_CRR_MAGIC);
+
+       rkdog_dump(sc, "started watchdog");
+
+       return sc->sc_period;
+}
Index: share/man/man4/rkdog.4
===================================================================
RCS file: share/man/man4/rkdog.4
diff -N share/man/man4/rkdog.4
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ share/man/man4/rkdog.4      8 Dec 2021 09:37:23 -0000
@@ -0,0 +1,38 @@
+.\"       $OpenBSD: rkdog.4,v 1.1 2019/10/16 22:24:45 kettenis Exp $
+.\"
+.\" Copyright (c) 2021 Christopher Zimmermann <chr...@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: December 7 2021 $
+.Dt rkdog 4
+.Os
+.Sh NAME
+.Nm rkdog
+.Nd watchdog timer device used on RockChip SoCs
+.Sh SYNOPSIS
+.Cd "rkdog* at fdt?"
+.Sh DESCRIPTION
+The
+.Nm
+driver provides support for the watchdog timer device integrated in RockChip 
SoCs.
+The watchdog timeout can only be programmed on a log2 scale.
+The driver will round up the timeout to the next longer possible setting.
+The actually selected timeout can and should be checked by
+.Xr sysctl 4 to make sure the watchdog gets tickled in time in case the
+requested period was longer than supported.
+.Sh HISTORY
+The
+.Nm
+device driver first appeared in
+.Ox 7.0 .
Index: sys/arch/arm64/conf/GENERIC
===================================================================
RCS file: /cvs/src/sys/arch/arm64/conf/GENERIC,v
retrieving revision 1.214
diff -u -p -r1.214 GENERIC
--- sys/arch/arm64/conf/GENERIC 22 Nov 2021 20:25:50 -0000      1.214
+++ sys/arch/arm64/conf/GENERIC 8 Dec 2021 09:37:23 -0000
@@ -277,6 +277,7 @@ rkpwm*              at fdt?
 rkrng*         at fdt?
 rktemp*                at fdt?
 rkvop*         at fdt?
+rkdog*         at fdt?
 rkdwusb*       at fdt?
 dwmmc*         at fdt?
 sdmmc*         at dwmmc?

Reply via email to