summaryrefslogtreecommitdiffstats
path: root/drivers/watchdog/imxwd.c
blob: 9f09f6ecd03291554f3e42d14c737903ce4f8e38 (plain) (blame)
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
/*
 * (c) 2012 Sascha Hauer <s.hauer@pengutronix.de>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <common.h>
#include <init.h>
#include <io.h>
#include <of.h>
#include <errno.h>
#include <malloc.h>
#include <watchdog.h>
#include <reset_source.h>

struct imx_wd;

struct imx_wd_ops {
	int (*set_timeout)(struct imx_wd *, int);
	int (*init)(struct imx_wd *);
};

struct imx_wd {
	struct watchdog wd;
	void __iomem *base;
	struct device_d *dev;
	const struct imx_wd_ops *ops;
};

#define to_imx_wd(h) container_of(h, struct imx_wd, wd)

#define IMX1_WDOG_WCR	0x00 /* Watchdog Control Register */
#define IMX1_WDOG_WSR	0x04 /* Watchdog Service Register */
#define IMX1_WDOG_WSTR	0x08 /* Watchdog Status Register  */
#define IMX1_WDOG_WCR_WDE	(1 << 0)
#define IMX1_WDOG_WCR_WHALT	(1 << 15)

#define IMX21_WDOG_WCR	0x00 /* Watchdog Control Register */
#define IMX21_WDOG_WSR	0x02 /* Watchdog Service Register */
#define IMX21_WDOG_WSTR	0x04 /* Watchdog Status Register  */
#define IMX21_WDOG_WMCR	0x08 /* Misc Register */
#define IMX21_WDOG_WCR_WDE	(1 << 2)
#define IMX21_WDOG_WCR_SRS	(1 << 4)
#define IMX21_WDOG_WCR_WDA	(1 << 5)

/* valid for i.MX25, i.MX27, i.MX31, i.MX35, i.MX51 */
#define WSTR_WARMSTART	(1 << 0)
/* valid for i.MX25, i.MX27, i.MX31, i.MX35, i.MX51 */
#define WSTR_WDOG	(1 << 1)
/* valid for i.MX27, i.MX31, always '0' on i.MX25, i.MX35, i.MX51 */
#define WSTR_HARDRESET	(1 << 3)
/* valid for i.MX27, i.MX31, always '0' on i.MX25, i.MX35, i.MX51 */
#define WSTR_COLDSTART	(1 << 4)

static int imx1_watchdog_set_timeout(struct imx_wd *priv, int timeout)
{
	u16 val;

	dev_dbg(priv->dev, "%s: %d\n", __func__, timeout);

	if (timeout > 64)
		return -EINVAL;

	if (!timeout) {
		writew(IMX1_WDOG_WCR_WHALT, priv->base + IMX1_WDOG_WCR);
		return 0;
	}

	if (timeout > 0)
		val = (timeout * 2 - 1) << 8;
	else
		val = 0;

	writew(val, priv->base + IMX1_WDOG_WCR);
	writew(IMX1_WDOG_WCR_WDE | val, priv->base + IMX1_WDOG_WCR);

	/* Write Service Sequence */
	writew(0x5555, priv->base + IMX1_WDOG_WSR);
	writew(0xaaaa, priv->base + IMX1_WDOG_WSR);

	return 0;
}

static int imx21_watchdog_set_timeout(struct imx_wd *priv, int timeout)
{
	u16 val;

	dev_dbg(priv->dev, "%s: %d\n", __func__, timeout);

	if (!timeout || timeout > 128)
		return -EINVAL;

	if (timeout > 0)
		val = ((timeout * 2 - 1) << 8) | IMX21_WDOG_WCR_SRS |
			IMX21_WDOG_WCR_WDA;
	else
		val = 0;

	writew(val, priv->base + IMX21_WDOG_WCR);
	writew(IMX21_WDOG_WCR_WDE | val, priv->base + IMX21_WDOG_WCR);

	/* Write Service Sequence */
	writew(0x5555, priv->base + IMX21_WDOG_WSR);
	writew(0xaaaa, priv->base + IMX21_WDOG_WSR);

	return 0;
}

static int imx_watchdog_set_timeout(struct watchdog *wd, unsigned timeout)
{
	struct imx_wd *priv = (struct imx_wd *)to_imx_wd(wd);

	return priv->ops->set_timeout(priv, timeout);
}

static struct imx_wd *reset_wd;

void __noreturn reset_cpu(unsigned long addr)
{
	if (reset_wd)
		reset_wd->ops->set_timeout(reset_wd, -1);

	mdelay(1000);

	hang();
}

static void imx_watchdog_detect_reset_source(struct imx_wd *priv)
{
	u16 val = readw(priv->base + IMX21_WDOG_WSTR);

	if (val & WSTR_COLDSTART) {
		reset_source_set(RESET_POR);
		return;
	}

	if (val & (WSTR_HARDRESET | WSTR_WARMSTART)) {
		reset_source_set(RESET_RST);
		return;
	}

	if (val & WSTR_WDOG) {
		reset_source_set(RESET_WDG);
		return;
	}

	/* else keep the default 'unknown' state */
}

static int imx21_wd_init(struct imx_wd *priv)
{
	imx_watchdog_detect_reset_source(priv);

	/*
	 * Disable watchdog powerdown counter
	 */
	writew(0x0, priv->base + IMX21_WDOG_WMCR);

	return 0;
}

static int imx_wd_probe(struct device_d *dev)
{
	struct imx_wd *priv;
	void *ops;
	int ret;

	ret = dev_get_drvdata(dev, (const void **)&ops);
	if (ret)
		return ret;

	priv = xzalloc(sizeof(struct imx_wd));
	priv->base = dev_request_mem_region(dev, 0);
	if (!priv->base) {
		dev_err(dev, "could not get memory region\n");
		return -ENODEV;
	}
	priv->ops = ops;
	priv->wd.set_timeout = imx_watchdog_set_timeout;
	priv->dev = dev;

	if (!reset_wd)
		reset_wd = priv;

	if (IS_ENABLED(CONFIG_WATCHDOG_IMX)) {
		ret = watchdog_register(&priv->wd);
		if (ret)
			goto on_error;
	}

	if (priv->ops->init) {
		ret = priv->ops->init(priv);
		if (ret) {
			dev_err(dev, "Failed to init watchdog device %d\n", ret);
			goto error_unregister;
		}
	}

	dev->priv = priv;

	return 0;

error_unregister:
	if (IS_ENABLED(CONFIG_WATCHDOG_IMX))
		watchdog_deregister(&priv->wd);
on_error:
	if (reset_wd && reset_wd != priv)
		free(priv);
	return ret;
}

static const struct imx_wd_ops imx21_wd_ops = {
	.set_timeout = imx21_watchdog_set_timeout,
	.init = imx21_wd_init,
};

static const struct imx_wd_ops imx1_wd_ops = {
	.set_timeout = imx1_watchdog_set_timeout,
};

static __maybe_unused struct of_device_id imx_wdt_dt_ids[] = {
	{
		.compatible = "fsl,imx1-wdt",
		.data = &imx1_wd_ops,
	}, {
		.compatible = "fsl,imx21-wdt",
		.data = &imx21_wd_ops,
	}, {
		/* sentinel */
	}
};

static struct platform_device_id imx_wdt_ids[] = {
	{
		.name = "imx1-wdt",
		.driver_data = (unsigned long)&imx1_wd_ops,
	}, {
		.name = "imx21-wdt",
		.driver_data = (unsigned long)&imx21_wd_ops,
	}, {
		/* sentinel */
	},
};

static struct driver_d imx_wd_driver = {
	.name   = "imx-watchdog",
	.probe  = imx_wd_probe,
	.of_compatible = DRV_OF_COMPAT(imx_wdt_dt_ids),
	.id_table = imx_wdt_ids,
};
device_platform_driver(imx_wd_driver);