// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2013 Lubomir Rintel * Copyright (C) 2023 Etienne Dublé (CNRS) * * This code is mostly derived from the linux driver. */ #include #include #include #include #define PM_RSTC 0x1c #define PM_WDOG 0x24 #define PM_PASSWORD 0x5a000000 /* The hardware supports a maximum timeout value of 0xfffff ticks * (just below 16 seconds). */ #define PM_WDOG_MAX_TICKS 0x000fffff #define PM_RSTC_WRCFG_CLR 0xffffffcf #define PM_RSTC_WRCFG_FULL_RESET 0x00000020 #define PM_RSTC_RESET 0x00000102 #define MS_TO_WDOG_TICKS(x) (((x) << 16) / 1000) struct bcm2835_wdt_priv { void __iomem *base; u64 timeout_ticks; }; static int bcm2835_wdt_start_ticks(struct udevice *dev, u64 timeout_ticks, ulong flags) { struct bcm2835_wdt_priv *priv = dev_get_priv(dev); void __iomem *base = priv->base; u32 cur; writel(PM_PASSWORD | timeout_ticks, base + PM_WDOG); cur = readl(base + PM_RSTC); writel(PM_PASSWORD | (cur & PM_RSTC_WRCFG_CLR) | PM_RSTC_WRCFG_FULL_RESET, base + PM_RSTC); return 0; } static int bcm2835_wdt_start(struct udevice *dev, u64 timeout_ms, ulong flags) { struct bcm2835_wdt_priv *priv = dev_get_priv(dev); priv->timeout_ticks = MS_TO_WDOG_TICKS(timeout_ms); if (priv->timeout_ticks > PM_WDOG_MAX_TICKS) { printf("bcm2835_wdt: the timeout value is too high, using ~16s instead.\n"); priv->timeout_ticks = PM_WDOG_MAX_TICKS; } return bcm2835_wdt_start_ticks(dev, priv->timeout_ticks, flags); } static int bcm2835_wdt_reset(struct udevice *dev) { struct bcm2835_wdt_priv *priv = dev_get_priv(dev); /* restart the timer with the value of priv->timeout_ticks * saved from the last bcm2835_wdt_start() call. */ return bcm2835_wdt_start_ticks(dev, priv->timeout_ticks, 0); } static int bcm2835_wdt_stop(struct udevice *dev) { struct bcm2835_wdt_priv *priv = dev_get_priv(dev); void __iomem *base = priv->base; writel(PM_PASSWORD | PM_RSTC_RESET, base + PM_RSTC); return 0; } static int bcm2835_wdt_expire_now(struct udevice *dev, ulong flags) { int ret; /* use a timeout of 10 ticks (~150us) */ ret = bcm2835_wdt_start_ticks(dev, 10, flags); if (ret) return ret; mdelay(500); return 0; } static const struct wdt_ops bcm2835_wdt_ops = { .reset = bcm2835_wdt_reset, .start = bcm2835_wdt_start, .stop = bcm2835_wdt_stop, .expire_now = bcm2835_wdt_expire_now, }; static const struct udevice_id bcm2835_wdt_ids[] = { { .compatible = "brcm,bcm2835-pm" }, { .compatible = "brcm,bcm2835-pm-wdt" }, { /* sentinel */ } }; static int bcm2835_wdt_probe(struct udevice *dev) { struct bcm2835_wdt_priv *priv = dev_get_priv(dev); priv->base = dev_remap_addr(dev); if (!priv->base) return -EINVAL; priv->timeout_ticks = PM_WDOG_MAX_TICKS; bcm2835_wdt_stop(dev); return 0; } U_BOOT_DRIVER(bcm2835_wdt) = { .name = "bcm2835_wdt", .id = UCLASS_WDT, .of_match = bcm2835_wdt_ids, .probe = bcm2835_wdt_probe, .priv_auto = sizeof(struct bcm2835_wdt_priv), .ops = &bcm2835_wdt_ops, };