summaryrefslogtreecommitdiff
path: root/drivers/watchdog/stm32mp_wdt.c
blob: 0712524b4a8d504a55a6ba6eac428fcf59a45908 (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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause
/*
 * Copyright (C) 2019, STMicroelectronics - All Rights Reserved
 */

#define LOG_CATEGORY UCLASS_WDT

#include <clk.h>
#include <dm.h>
#include <log.h>
#include <syscon.h>
#include <wdt.h>
#include <asm/io.h>
#include <dm/device_compat.h>
#include <linux/bitops.h>
#include <linux/iopoll.h>
#include <linux/printk.h>

/* IWDG registers */
#define IWDG_KR		0x00	/* Key register */
#define IWDG_PR		0x04	/* Prescaler Register */
#define IWDG_RLR	0x08	/* ReLoad Register */
#define IWDG_SR		0x0C	/* Status Register */
#define IWDG_VERR	0x3F4	/* Version Register */

/* IWDG_KR register bit mask */
#define KR_KEY_RELOAD	0xAAAA	/* Reload counter enable */
#define KR_KEY_ENABLE	0xCCCC	/* Peripheral enable */
#define KR_KEY_EWA	0x5555	/* Write access enable */
#define KR_KEY_DWA	0x0000	/* Write access disable*/

/* IWDG_PR register bit values */
#define PR_256		0x06	/* Prescaler set to 256 */

/* IWDG_RLR register values */
#define RLR_MAX		0xFFF	/* Max value supported by reload register */

/* IWDG_SR register bit values */
#define SR_PVU		BIT(0)	/* Watchdog prescaler value update */
#define SR_RVU		BIT(1)	/* Watchdog counter reload value update */
#define SR_ONF		BIT(8)	/* Watchdog enable status bit */

/* IWDG Compatibility */
#define ONF_MIN_VER	0x31

#define TIMEOUT_US	10000

struct stm32mp_wdt_priv {
	fdt_addr_t base;		/* registers addr in physical memory */
	unsigned long wdt_clk_rate;	/* Watchdog dedicated clock rate */
	unsigned int hw_version;	/* Peripheral version */
};

static int stm32mp_wdt_reset(struct udevice *dev)
{
	struct stm32mp_wdt_priv *priv = dev_get_priv(dev);

	writel(KR_KEY_RELOAD, priv->base + IWDG_KR);

	return 0;
}

static int stm32mp_wdt_start(struct udevice *dev, u64 timeout_ms, ulong flags)
{
	struct stm32mp_wdt_priv *priv = dev_get_priv(dev);
	int reload;
	u32 val;
	int ret;

	/* Prescaler fixed to 256 */
	reload = timeout_ms * priv->wdt_clk_rate / 256;
	if (reload > RLR_MAX + 1)
		/* Force to max watchdog counter reload value */
		reload = RLR_MAX + 1;
	else if (!reload)
		/* Force to min watchdog counter reload value */
		reload = priv->wdt_clk_rate / 256;

	/* Set prescaler & reload registers */
	writel(KR_KEY_EWA, priv->base + IWDG_KR);
	writel(PR_256, priv->base + IWDG_PR);
	writel(reload - 1, priv->base + IWDG_RLR);

	/* Enable watchdog */
	writel(KR_KEY_ENABLE, priv->base + IWDG_KR);

	/* Wait for the registers to be updated */
	ret = readl_poll_timeout(priv->base + IWDG_SR, val,
				 val & (SR_PVU | SR_RVU), CONFIG_SYS_HZ);

	if (ret < 0) {
		dev_err(dev, "Updating IWDG registers timeout");
		return -ETIMEDOUT;
	}

	return 0;
}

static int stm32mp_wdt_probe(struct udevice *dev)
{
	struct stm32mp_wdt_priv *priv = dev_get_priv(dev);
	u32 rlr, sr;
	struct clk clk;
	int ret;

	dev_dbg(dev, "IWDG init\n");

	priv->base = dev_read_addr(dev);
	if (priv->base == FDT_ADDR_T_NONE)
		return -EINVAL;

	/* Enable clock */
	ret = clk_get_by_name(dev, "pclk", &clk);
	if (ret)
		return ret;

	ret = clk_enable(&clk);
	if (ret)
		return ret;

	/* Get LSI clock */
	ret = clk_get_by_name(dev, "lsi", &clk);
	if (ret)
		return ret;

	priv->wdt_clk_rate = clk_get_rate(&clk);

	priv->hw_version = readl(priv->base + IWDG_VERR);

	if (priv->hw_version >= ONF_MIN_VER) {
		if (readl(priv->base + IWDG_SR) & SR_ONF)
			wdt_set_force_autostart(dev);
	} else {
		/*
		 * Workaround for old versions without IWDG_SR_ONF bit:
		 * - write in IWDG_RLR_OFFSET
		 * - wait for sync
		 * - if sync succeeds, then iwdg is running
		 */
		writel(KR_KEY_EWA, priv->base + IWDG_KR);
		rlr = readl(priv->base + IWDG_RLR);
		writel(rlr, priv->base + IWDG_RLR);
		ret = readl_poll_timeout(priv->base + IWDG_SR, sr, sr & SR_RVU,
					 TIMEOUT_US);
		if (!ret)
			wdt_set_force_autostart(dev);

		writel(KR_KEY_DWA, priv->base + IWDG_KR);
	}

	dev_dbg(dev, "IWDG init done\n");

	return 0;
}

static const struct wdt_ops stm32mp_wdt_ops = {
	.start = stm32mp_wdt_start,
	.reset = stm32mp_wdt_reset,
};

static const struct udevice_id stm32mp_wdt_match[] = {
	{ .compatible = "st,stm32mp1-iwdg" },
	{ /* sentinel */ }
};

U_BOOT_DRIVER(stm32mp_wdt) = {
	.name = "stm32mp-wdt",
	.id = UCLASS_WDT,
	.of_match = stm32mp_wdt_match,
	.priv_auto	= sizeof(struct stm32mp_wdt_priv),
	.probe = stm32mp_wdt_probe,
	.ops = &stm32mp_wdt_ops,
};