blob: 3c1ead3dda6ae6d278026056b961eb38a2099f8f [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2013 Lubomir Rintel <lkundrak@v3.sk>
* Copyright (C) 2023 Etienne Dublé (CNRS) <etienne.duble@imag.fr>
*
* This code is mostly derived from the linux driver.
*/
#include <dm.h>
#include <wdt.h>
#include <asm/io.h>
#include <linux/delay.h>
#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,
};