summaryrefslogtreecommitdiffstats
path: root/drivers/watchdog/bcm2835_wdt.c
blob: ece80837b53690c07da42e2382da221d7894ce06 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (C) 2017 Pengutronix, Lucas Stach <l.stach@pengutronix.de>
 *
 * Based on code from  Carlo Caione <carlo@carlocaione.org>
 */
#include <common.h>
#include <init.h>
#include <io.h>
#include <restart.h>
#include <watchdog.h>

#define PM_RSTC		0x1c
#define PM_RSTS		0x20
#define PM_WDOG		0x24

#define PM_WDOG_RESET				0000000000
#define PM_PASSWORD				0x5a000000
#define PM_WDOG_TIME_SET			0x000fffff
#define PM_RSTC_WRCFG_CLR			0xffffffcf
#define PM_RSTC_WRCFG_SET			0x00000030
#define PM_RSTC_WRCFG_FULL_RESET		0x00000020
#define PM_RSTC_RESET				0x00000102

#define PM_RSTS_HADPOR_SET			0x00001000
#define PM_RSTS_HADSRH_SET			0x00000400
#define PM_RSTS_HADSRF_SET			0x00000200
#define PM_RSTS_HADSRQ_SET			0x00000100
#define PM_RSTS_HADWRH_SET			0x00000040
#define PM_RSTS_HADWRF_SET			0x00000020
#define PM_RSTS_HADWRQ_SET			0x00000010
#define PM_RSTS_HADDRH_SET			0x00000004
#define PM_RSTS_HADDRF_SET			0x00000002
#define PM_RSTS_HADDRQ_SET			0x00000001

#define SECS_TO_WDOG_TICKS(x) ((x) << 16)

/* Largest value where SECS_TO_WDOG_TICKS doesn't overflow 20 bits
 * (PM_WDOG_TIME_SET) */
#define WDOG_SECS_MAX 15

struct bcm2835_wd {
	struct watchdog wd;
	void __iomem *base;
	struct device_d *dev;
	struct restart_handler restart;
};

#define RESET_TIMEOUT 10

static void __noreturn bcm2835_restart_soc(struct restart_handler *rst)
{
	struct bcm2835_wd *priv = container_of(rst, struct bcm2835_wd, restart);
	uint32_t rstc;

	rstc = readl(priv->base + PM_RSTC);
	rstc &= ~PM_RSTC_WRCFG_SET;
	rstc |= PM_RSTC_WRCFG_FULL_RESET;
	writel(PM_PASSWORD | RESET_TIMEOUT, priv->base +  PM_WDOG);
	writel(PM_PASSWORD | rstc, priv->base + PM_RSTC);

	hang();
}

static int bcm2835_wd_set_timeout(struct watchdog *wd, unsigned timeout)
{
	struct bcm2835_wd *priv = container_of(wd, struct bcm2835_wd, wd);
	u32 val;

	if (!timeout) {
		writel(PM_PASSWORD | PM_RSTC_RESET, priv->base + PM_RSTC);
		return 0;
	}

	writel(PM_PASSWORD | (SECS_TO_WDOG_TICKS(timeout) & PM_WDOG_TIME_SET),
	       priv->base + PM_WDOG);
	val = readl(priv->base + PM_RSTC);
	writel(PM_PASSWORD | (val & PM_RSTC_WRCFG_CLR) |
	       PM_RSTC_WRCFG_FULL_RESET, priv->base + PM_RSTC);

	return 0;
}

static int bcm2835_wd_probe(struct device_d *dev)
{
	struct resource *iores;
	struct bcm2835_wd *priv;
	int ret;

	priv = xzalloc(sizeof(*priv));
	iores = dev_request_mem_resource(dev, 0);
	if (IS_ERR(iores)) {
		dev_err(dev, "could not get memory region\n");
		return PTR_ERR(iores);
	}
	priv->base = IOMEM(iores->start);
	priv->wd.set_timeout = bcm2835_wd_set_timeout;
	priv->wd.timeout_max = WDOG_SECS_MAX;
	priv->wd.hwdev = dev;
	priv->dev = dev;

	if (IS_ENABLED(CONFIG_WATCHDOG_BCM2835)) {
		ret = watchdog_register(&priv->wd);
		if (ret) {
			free(priv);
			return ret;
		}
	}

	priv->restart.name = "bcm2835_wd";
	priv->restart.restart = bcm2835_restart_soc;

	restart_handler_register(&priv->restart);

	return 0;
}

static __maybe_unused struct of_device_id bcm2835_wd_dt_ids[] = {
	{
		.compatible = "brcm,bcm2835-pm-wdt",
	}, {
		/* sentinel */
	},
};

static struct driver_d bcm2835_wd_driver = {
	.name		= "bcm2835_wd",
	.of_compatible	= DRV_OF_COMPAT(bcm2835_wd_dt_ids),
	.probe		= bcm2835_wd_probe,
};

device_platform_driver(bcm2835_wd_driver);