> Date: Thu, 9 Dec 2021 11:31:25 +0100 > From: Christopher Zimmermann <chr...@openbsd.org> > > On Wed, Dec 08, 2021 at 05:20:39PM +0100, Mark Kettenis wrote: > >> Date: Wed, 8 Dec 2021 16:31:50 +0100 > >> From: Christopher Zimmermann <chr...@openbsd.org> > >> > >> >On Wed, Dec 08, 2021 at 10:47:45AM +0100, Christopher Zimmermann wrote: > >> >> Note that it attaches to "snps,dw-wdt" > >> > >> On Wed, Dec 08, 2021 at 08:57:53PM +1100, Jonathan Gray wrote: > >> >We already have a driver for that. > >> > >> Oh no. > >> > >> OK? > > > >What problem are you trying to solve? > > I want a watchdog.
Fair enough. > Here is a diff merging my driver with the current driver to support > kern.watchdog sysctl. Tested on RockPro64. OK? Your mail client is scrambling the diff. A bit of advice. Please read the style(9) man page, and take a look at other drivers in this directory. Try to keep your changes minimal. Making arbitrary changes and adding lots of debug code makes it difficult to review a diff. > Index: sys/dev/fdt/dwdog.c > =================================================================== > RCS file: /cvs/src/sys/dev/fdt/dwdog.c,v > retrieving revision 1.3 > diff -u -p -r1.3 dwdog.c > --- sys/dev/fdt/dwdog.c 24 Oct 2021 17:52:26 -0000 1.3 > +++ sys/dev/fdt/dwdog.c 9 Dec 2021 10:13:21 -0000 > @@ -19,42 +19,63 @@ > #include <sys/systm.h> > #include <sys/device.h> > > +#include <machine/intr.h> > #include <machine/bus.h> > #include <machine/fdt.h> > > #include <armv7/armv7/armv7_machdep.h> If we start using this driver on arm64, we should drop this include. Other watchdog drivers just use: extern void (*cpuresetfn)(void); > #include <dev/ofw/openfirm.h> > +#include <dev/ofw/ofw_clock.h> > #include <dev/ofw/fdt.h> > > +/* #define DEBUG */ > + > /* Registers */ > -#define WDT_CR 0x0000 > -#define WDT_CR_WDT_EN (1 << 0) > +#define WDT_CR 0x0000 /* control */ > +#define WDT_CR_ENABLE (1 << 0) /* enable counter */ > #define WDT_CR_RESP_MODE (1 << 1) > +/* > + * On some devices WDT_CR_ENABLE can only be set, but not be cleared. > + * > + * If WDT_CR_RESP_MODE is enabled the watchdog will count twice. After the > + * first count the interrupt is raised as a warning. After the second count > + * the reset happens only when the interrupt is not cleared. > + */ > +#define WDT_CR_RST_PULSE_LENGTH_MASK 0x001c > +#define WDT_CR_RST_PULSE_LENGTH_SHIFT 2 > + /* pulse length is 2^(n+1) clock cycles. Default is n=2 / 8 cycles. */ > + > #define WDT_TORR 0x0004 > -#define WDT_CCVR 0x0008 > -#define WDT_CRR 0x000c > -#define WDT_CRR_KICK 0x76 > -#define WDT_STAT 0x0010 > -#define WDT_EOI 0x0014 > +/* timeout range n={0..15}. Timeout is 2^(16+n)-1 ticks */ > + > +#define WDT_CCVR 0x0008 /* current counter value */ > +#define WDT_CRR 0x000c /* counter restart by writing magic > number */ > +#define WDT_CRR_MAGIC 0x76 > +#define WDT_STAT 0x0010 /* interrupt status */ > +#define WDT_EOI 0x0014 /* clear interrupt */ We typically don't annotate the register definitions this way, especially when documentation is publically available. I'd prefer to keep it that way. If something non-obvious is happening, it is better to document it in the code. > > #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)) > -#define HSET4(sc, reg, bits) \ > - HWRITE4((sc), (reg), HREAD4((sc), (reg)) | (bits)) > -#define HCLR4(sc, reg, bits) \ > - HWRITE4((sc), (reg), HREAD4((sc), (reg)) & ~(bits)) > > struct dwdog_softc { > struct device sc_dev; > bus_space_tag_t sc_iot; > bus_space_handle_t sc_ioh; > + > + uint32_t sc_freq; > + int sc_period; > + int sc_reset; > }; > > -int dwdog_match(struct device *, void *, void *); > -void dwdog_attach(struct device *, struct device *, void *); > +int dwdog_match(struct device *, void *, void *); > +void dwdog_attach(struct device *, struct device *, void *); > +static void dwdog_dump(struct dwdog_softc *, const char *); > +int dwdog_intr(void *); > +int dwdog_cb(void *, int); > +void dwdog_reset(void); > > const struct cfattach dwdog_ca = { > sizeof(struct dwdog_softc), dwdog_match, dwdog_attach > @@ -64,8 +85,6 @@ struct cfdriver dwdog_cd = { > NULL, "dwdog", DV_DULL > }; > > -void dwdog_reset(void); > - > int > dwdog_match(struct device *parent, void *match, void *aux) > { > @@ -86,33 +105,179 @@ dwdog_attach(struct device *parent, stru > } > > sc->sc_iot = faa->fa_iot; > + sc->sc_freq = 0; > > 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; > + } > + > + fdt_intr_establish(faa->fa_node, IPL_CLOCK, dwdog_intr, > + sc, sc->sc_dev.dv_xname); > + if (NULL == fdt_intr_establish(faa->fa_node, IPL_BIO, > + dwdog_intr, sc, sc->sc_dev.dv_xname)) { > + printf(": unable to establish interrupt\n"); > + return; > } This makes no sense. You're establishing the interrupt twice. There is no real reason to check the return value. > > printf("\n"); > > + dwdog_dump(sc, "initial dump"); Please remove all debug code from your diff. > + > +#ifndef SMALL_KERNEL > + wdog_register(dwdog_cb, sc); > +#endif Best the register the watchdog at the end of the function, after the hardware has been initialized properly. > + > + clock_set_assigned(faa->fa_node); > + clock_enable_all(faa->fa_node); > + > if (cpuresetfn == NULL) > cpuresetfn = dwdog_reset; > } > > +static void > +dwdog_dump(struct dwdog_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, WDT_CR), > + HREAD4(sc, WDT_TORR), > + HREAD4(sc, WDT_TORR), > + HREAD4(sc, WDT_CCVR), > + HREAD4(sc, WDT_STAT)); > +#endif > +} > + > +int > +dwdog_intr(void *self) { > + struct dwdog_softc *sc = self; > + > + dwdog_dump(sc, "interrupt"); > + > + /* clear all interrupts */ > + HREAD4(sc, WDT_EOI); > + > + return 1; > +} This interrupt handler doesn't really do anything. > + > +int > +dwdog_cb(void *self, int period) > +{ > + struct dwdog_softc *sc = self; > + unsigned tor = 0; > + uint32_t control; > + > + dwdog_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 (sc->sc_reset) > + return -1; > + > + if (period > 0 && period == sc->sc_period) { > +#ifdef DEBUG > + printf("%s: just tickling\n", sc->sc_dev.dv_xname); > +#endif > + HWRITE4(sc, WDT_CRR, WDT_CRR_MAGIC); > + return sc->sc_period; > + } > + > + control = HREAD4(sc, WDT_CR); > + > + if (period == 0) { > + /* in case counter cannot be disabled enable interrupt */ > + if ((control & WDT_CR_ENABLE) == 0) > + return 0; > + HWRITE4(sc, WDT_TORR, 15); > + control &= ~WDT_CR_ENABLE; > + control |= WDT_CR_RESP_MODE; > + HWRITE4(sc, WDT_CR, control); > + control = HREAD4(sc, WDT_CR); > + dwdog_dump(sc, "disabled"); > + > + if (control & WDT_CR_ENABLE) { > + printf("%s: Cannot disable watchdog timer. " > + "Please enable kern.watchdog.auto as a > workaround\n", > + sc->sc_dev.dv_xname); > + return (sc->sc_freq ? 0x7fffffff / sc->sc_freq : 1); > + } > + else > + return 0; > + } > + > + if (sc->sc_freq == 0) { > + uint32_t ticks, dt = 1000; /* us */ > + > + HWRITE4(sc, WDT_TORR, 15); > + control |= WDT_CR_ENABLE | WDT_CR_RESP_MODE; > + HWRITE4(sc, WDT_CR, control); > + HWRITE4(sc, WDT_CRR, WDT_CRR_MAGIC); > + > + ticks = HREAD4(sc, WDT_CCVR); > + delay(dt); > + ticks -= HREAD4(sc, WDT_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, WDT_TORR, tor); > + control = HREAD4(sc, WDT_CR); > + control &= ~WDT_CR_RESP_MODE; > + control |= WDT_CR_ENABLE; > + HWRITE4(sc, WDT_CR, control); > + HWRITE4(sc, WDT_CRR, WDT_CRR_MAGIC); > + > + dwdog_dump(sc, "started watchdog"); > + > + return sc->sc_period; > +} > + This all looks horribly complicated. Isn't the clock frequency provided by the device tree? I suspect you should be able to use clock_get_frequency() to get it. > void > dwdog_reset(void) > { > struct dwdog_softc *sc = dwdog_cd.cd_devs[0]; > + uint32_t control; > > /* > * Generate system reset when timer expires and select > * smallest timeout. > */ > - HCLR4(sc, WDT_CR, WDT_CR_RESP_MODE | WDT_CR_WDT_EN); > - HWRITE4(sc, WDT_TORR, 0); > - HSET4(sc, WDT_CR, WDT_CR_WDT_EN); > > - /* Kick the dog! */ > - HWRITE4(sc, WDT_CRR, WDT_CRR_KICK); > + sc->sc_reset = 1; > + > + control = HREAD4(sc, WDT_CR); > + control &= ~WDT_CR_RESP_MODE; > + control |= WDT_CR_ENABLE; > + HWRITE4(sc, WDT_TORR, 0); > + HWRITE4(sc, WDT_CR, control); > + HWRITE4(sc, WDT_CRR, WDT_CRR_MAGIC); > > delay(1000000); > } > Index: sys/arch/arm64/conf/GENERIC > =================================================================== > RCS file: /cvs/src/sys/arch/arm64/conf/GENERIC,v > retrieving revision 1.215 > diff -u -p -r1.215 GENERIC > --- sys/arch/arm64/conf/GENERIC 3 Dec 2021 19:16:29 -0000 1.215 > +++ sys/arch/arm64/conf/GENERIC 9 Dec 2021 10:13:21 -0000 > @@ -277,6 +277,7 @@ rkpwm* at fdt? > rkrng* at fdt? > rktemp* at fdt? > rkvop* at fdt? > +dwdog* at fdt? > rkdwusb* at fdt? > dwmmc* at fdt? > sdmmc* at dwmmc? >