1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Copyright (C) 2013 Lubomir Rintel <lkundrak@v3.sk>
4 * Copyright (C) 2023 Etienne Dubl�� (CNRS) <etienne.duble@imag.fr>
5 *
6 * This code is mostly derived from the linux driver.
7 */
8
9#include <dm.h>
10#include <wdt.h>
11#include <asm/io.h>
12#include <linux/delay.h>
13
14#define PM_RSTC					0x1c
15#define PM_WDOG					0x24
16
17#define PM_PASSWORD				0x5a000000
18
19/* The hardware supports a maximum timeout value of 0xfffff ticks
20 * (just below 16 seconds).
21 */
22#define PM_WDOG_MAX_TICKS			0x000fffff
23#define PM_RSTC_WRCFG_CLR			0xffffffcf
24#define PM_RSTC_WRCFG_FULL_RESET		0x00000020
25#define PM_RSTC_RESET				0x00000102
26
27#define MS_TO_WDOG_TICKS(x) (((x) << 16) / 1000)
28
29struct bcm2835_wdt_priv {
30	void __iomem *base;
31	u64 timeout_ticks;
32};
33
34static int bcm2835_wdt_start_ticks(struct udevice *dev,
35				   u64 timeout_ticks, ulong flags)
36{
37	struct bcm2835_wdt_priv *priv = dev_get_priv(dev);
38	void __iomem *base = priv->base;
39	u32 cur;
40
41	writel(PM_PASSWORD | timeout_ticks, base + PM_WDOG);
42	cur = readl(base + PM_RSTC);
43	writel(PM_PASSWORD | (cur & PM_RSTC_WRCFG_CLR) | PM_RSTC_WRCFG_FULL_RESET,
44	       base + PM_RSTC);
45
46	return 0;
47}
48
49static int bcm2835_wdt_start(struct udevice *dev, u64 timeout_ms, ulong flags)
50{
51	struct bcm2835_wdt_priv *priv = dev_get_priv(dev);
52
53	priv->timeout_ticks = MS_TO_WDOG_TICKS(timeout_ms);
54
55	if (priv->timeout_ticks > PM_WDOG_MAX_TICKS) {
56		printf("bcm2835_wdt: the timeout value is too high, using ~16s instead.\n");
57		priv->timeout_ticks = PM_WDOG_MAX_TICKS;
58	}
59
60	return bcm2835_wdt_start_ticks(dev, priv->timeout_ticks, flags);
61}
62
63static int bcm2835_wdt_reset(struct udevice *dev)
64{
65	struct bcm2835_wdt_priv *priv = dev_get_priv(dev);
66
67	/* restart the timer with the value of priv->timeout_ticks
68	 * saved from the last bcm2835_wdt_start() call.
69	 */
70	return bcm2835_wdt_start_ticks(dev, priv->timeout_ticks, 0);
71}
72
73static int bcm2835_wdt_stop(struct udevice *dev)
74{
75	struct bcm2835_wdt_priv *priv = dev_get_priv(dev);
76	void __iomem *base = priv->base;
77
78	writel(PM_PASSWORD | PM_RSTC_RESET, base + PM_RSTC);
79
80	return 0;
81}
82
83static int bcm2835_wdt_expire_now(struct udevice *dev, ulong flags)
84{
85	int ret;
86
87	/* use a timeout of 10 ticks (~150us) */
88	ret = bcm2835_wdt_start_ticks(dev, 10, flags);
89	if (ret)
90		return ret;
91
92	mdelay(500);
93
94	return 0;
95}
96
97static const struct wdt_ops bcm2835_wdt_ops = {
98	.reset		= bcm2835_wdt_reset,
99	.start		= bcm2835_wdt_start,
100	.stop		= bcm2835_wdt_stop,
101	.expire_now	= bcm2835_wdt_expire_now,
102};
103
104static const struct udevice_id bcm2835_wdt_ids[] = {
105	{ .compatible = "brcm,bcm2835-pm" },
106	{ .compatible = "brcm,bcm2835-pm-wdt" },
107	{ /* sentinel */ }
108};
109
110static int bcm2835_wdt_probe(struct udevice *dev)
111{
112	struct bcm2835_wdt_priv *priv = dev_get_priv(dev);
113
114	priv->base = dev_remap_addr(dev);
115	if (!priv->base)
116		return -EINVAL;
117
118	priv->timeout_ticks = PM_WDOG_MAX_TICKS;
119
120	bcm2835_wdt_stop(dev);
121
122	return 0;
123}
124
125U_BOOT_DRIVER(bcm2835_wdt) = {
126	.name		= "bcm2835_wdt",
127	.id		= UCLASS_WDT,
128	.of_match	= bcm2835_wdt_ids,
129	.probe		= bcm2835_wdt_probe,
130	.priv_auto	= sizeof(struct bcm2835_wdt_priv),
131	.ops		= &bcm2835_wdt_ops,
132};
133