1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Watchdog driver for Alphascale ASM9260.
4 *
5 * Copyright (c) 2014 Oleksij Rempel <linux@rempel-privat.de>
6 */
7
8#include <linux/bitops.h>
9#include <linux/clk.h>
10#include <linux/delay.h>
11#include <linux/interrupt.h>
12#include <linux/io.h>
13#include <linux/module.h>
14#include <linux/of.h>
15#include <linux/platform_device.h>
16#include <linux/reset.h>
17#include <linux/watchdog.h>
18
19#define CLOCK_FREQ	1000000
20
21/* Watchdog Mode register */
22#define HW_WDMOD			0x00
23/* Wake interrupt. Set by HW, can't be cleared. */
24#define BM_MOD_WDINT			BIT(3)
25/* This bit set if timeout reached. Cleared by SW. */
26#define BM_MOD_WDTOF			BIT(2)
27/* HW Reset on timeout */
28#define BM_MOD_WDRESET			BIT(1)
29/* WD enable */
30#define BM_MOD_WDEN			BIT(0)
31
32/*
33 * Watchdog Timer Constant register
34 * Minimal value is 0xff, the meaning of this value
35 * depends on used clock: T = WDCLK * (0xff + 1) * 4
36 */
37#define HW_WDTC				0x04
38#define BM_WDTC_MAX(freq)		(0x7fffffff / (freq))
39
40/* Watchdog Feed register */
41#define HW_WDFEED			0x08
42
43/* Watchdog Timer Value register */
44#define HW_WDTV				0x0c
45
46#define ASM9260_WDT_DEFAULT_TIMEOUT	30
47
48enum asm9260_wdt_mode {
49	HW_RESET,
50	SW_RESET,
51	DEBUG,
52};
53
54struct asm9260_wdt_priv {
55	struct device		*dev;
56	struct watchdog_device	wdd;
57	struct clk		*clk;
58	struct clk		*clk_ahb;
59	struct reset_control	*rst;
60
61	void __iomem		*iobase;
62	int			irq;
63	unsigned long		wdt_freq;
64	enum asm9260_wdt_mode	mode;
65};
66
67static int asm9260_wdt_feed(struct watchdog_device *wdd)
68{
69	struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
70
71	iowrite32(0xaa, priv->iobase + HW_WDFEED);
72	iowrite32(0x55, priv->iobase + HW_WDFEED);
73
74	return 0;
75}
76
77static unsigned int asm9260_wdt_gettimeleft(struct watchdog_device *wdd)
78{
79	struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
80	u32 counter;
81
82	counter = ioread32(priv->iobase + HW_WDTV);
83
84	return counter / priv->wdt_freq;
85}
86
87static int asm9260_wdt_updatetimeout(struct watchdog_device *wdd)
88{
89	struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
90	u32 counter;
91
92	counter = wdd->timeout * priv->wdt_freq;
93
94	iowrite32(counter, priv->iobase + HW_WDTC);
95
96	return 0;
97}
98
99static int asm9260_wdt_enable(struct watchdog_device *wdd)
100{
101	struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
102	u32 mode = 0;
103
104	if (priv->mode == HW_RESET)
105		mode = BM_MOD_WDRESET;
106
107	iowrite32(BM_MOD_WDEN | mode, priv->iobase + HW_WDMOD);
108
109	asm9260_wdt_updatetimeout(wdd);
110
111	asm9260_wdt_feed(wdd);
112
113	return 0;
114}
115
116static int asm9260_wdt_disable(struct watchdog_device *wdd)
117{
118	struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
119
120	/* The only way to disable WD is to reset it. */
121	reset_control_assert(priv->rst);
122	reset_control_deassert(priv->rst);
123
124	return 0;
125}
126
127static int asm9260_wdt_settimeout(struct watchdog_device *wdd, unsigned int to)
128{
129	wdd->timeout = to;
130	asm9260_wdt_updatetimeout(wdd);
131
132	return 0;
133}
134
135static void asm9260_wdt_sys_reset(struct asm9260_wdt_priv *priv)
136{
137	/* init WD if it was not started */
138
139	iowrite32(BM_MOD_WDEN | BM_MOD_WDRESET, priv->iobase + HW_WDMOD);
140
141	iowrite32(0xff, priv->iobase + HW_WDTC);
142	/* first pass correct sequence */
143	asm9260_wdt_feed(&priv->wdd);
144	/*
145	 * Then write wrong pattern to the feed to trigger reset
146	 * ASAP.
147	 */
148	iowrite32(0xff, priv->iobase + HW_WDFEED);
149
150	mdelay(1000);
151}
152
153static irqreturn_t asm9260_wdt_irq(int irq, void *devid)
154{
155	struct asm9260_wdt_priv *priv = devid;
156	u32 stat;
157
158	stat = ioread32(priv->iobase + HW_WDMOD);
159	if (!(stat & BM_MOD_WDINT))
160		return IRQ_NONE;
161
162	if (priv->mode == DEBUG) {
163		dev_info(priv->dev, "Watchdog Timeout. Do nothing.\n");
164	} else {
165		dev_info(priv->dev, "Watchdog Timeout. Doing SW Reset.\n");
166		asm9260_wdt_sys_reset(priv);
167	}
168
169	return IRQ_HANDLED;
170}
171
172static int asm9260_restart(struct watchdog_device *wdd, unsigned long action,
173			   void *data)
174{
175	struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
176
177	asm9260_wdt_sys_reset(priv);
178
179	return 0;
180}
181
182static const struct watchdog_info asm9260_wdt_ident = {
183	.options          =     WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING
184				| WDIOF_MAGICCLOSE,
185	.identity         =	"Alphascale asm9260 Watchdog",
186};
187
188static const struct watchdog_ops asm9260_wdt_ops = {
189	.owner		= THIS_MODULE,
190	.start		= asm9260_wdt_enable,
191	.stop		= asm9260_wdt_disable,
192	.get_timeleft	= asm9260_wdt_gettimeleft,
193	.ping		= asm9260_wdt_feed,
194	.set_timeout	= asm9260_wdt_settimeout,
195	.restart	= asm9260_restart,
196};
197
198static void asm9260_clk_disable_unprepare(void *data)
199{
200	clk_disable_unprepare(data);
201}
202
203static int asm9260_wdt_get_dt_clks(struct asm9260_wdt_priv *priv)
204{
205	int err;
206	unsigned long clk;
207
208	priv->clk = devm_clk_get(priv->dev, "mod");
209	if (IS_ERR(priv->clk)) {
210		dev_err(priv->dev, "Failed to get \"mod\" clk\n");
211		return PTR_ERR(priv->clk);
212	}
213
214	/* configure AHB clock */
215	priv->clk_ahb = devm_clk_get(priv->dev, "ahb");
216	if (IS_ERR(priv->clk_ahb)) {
217		dev_err(priv->dev, "Failed to get \"ahb\" clk\n");
218		return PTR_ERR(priv->clk_ahb);
219	}
220
221	err = clk_prepare_enable(priv->clk_ahb);
222	if (err) {
223		dev_err(priv->dev, "Failed to enable ahb_clk!\n");
224		return err;
225	}
226	err = devm_add_action_or_reset(priv->dev,
227				       asm9260_clk_disable_unprepare,
228				       priv->clk_ahb);
229	if (err)
230		return err;
231
232	err = clk_set_rate(priv->clk, CLOCK_FREQ);
233	if (err) {
234		dev_err(priv->dev, "Failed to set rate!\n");
235		return err;
236	}
237
238	err = clk_prepare_enable(priv->clk);
239	if (err) {
240		dev_err(priv->dev, "Failed to enable clk!\n");
241		return err;
242	}
243	err = devm_add_action_or_reset(priv->dev,
244				       asm9260_clk_disable_unprepare,
245				       priv->clk);
246	if (err)
247		return err;
248
249	/* wdt has internal divider */
250	clk = clk_get_rate(priv->clk);
251	if (!clk) {
252		dev_err(priv->dev, "Failed, clk is 0!\n");
253		return -EINVAL;
254	}
255
256	priv->wdt_freq = clk / 2;
257
258	return 0;
259}
260
261static void asm9260_wdt_get_dt_mode(struct asm9260_wdt_priv *priv)
262{
263	const char *tmp;
264	int ret;
265
266	/* default mode */
267	priv->mode = HW_RESET;
268
269	ret = of_property_read_string(priv->dev->of_node,
270				      "alphascale,mode", &tmp);
271	if (ret < 0)
272		return;
273
274	if (!strcmp(tmp, "hw"))
275		priv->mode = HW_RESET;
276	else if (!strcmp(tmp, "sw"))
277		priv->mode = SW_RESET;
278	else if (!strcmp(tmp, "debug"))
279		priv->mode = DEBUG;
280	else
281		dev_warn(priv->dev, "unknown reset-type: %s. Using default \"hw\" mode.",
282			 tmp);
283}
284
285static int asm9260_wdt_probe(struct platform_device *pdev)
286{
287	struct device *dev = &pdev->dev;
288	struct asm9260_wdt_priv *priv;
289	struct watchdog_device *wdd;
290	int ret;
291	static const char * const mode_name[] = { "hw", "sw", "debug", };
292
293	priv = devm_kzalloc(dev, sizeof(struct asm9260_wdt_priv), GFP_KERNEL);
294	if (!priv)
295		return -ENOMEM;
296
297	priv->dev = dev;
298
299	priv->iobase = devm_platform_ioremap_resource(pdev, 0);
300	if (IS_ERR(priv->iobase))
301		return PTR_ERR(priv->iobase);
302
303	priv->rst = devm_reset_control_get_exclusive(dev, "wdt_rst");
304	if (IS_ERR(priv->rst))
305		return PTR_ERR(priv->rst);
306
307	ret = asm9260_wdt_get_dt_clks(priv);
308	if (ret)
309		return ret;
310
311	wdd = &priv->wdd;
312	wdd->info = &asm9260_wdt_ident;
313	wdd->ops = &asm9260_wdt_ops;
314	wdd->min_timeout = 1;
315	wdd->max_timeout = BM_WDTC_MAX(priv->wdt_freq);
316	wdd->parent = dev;
317
318	watchdog_set_drvdata(wdd, priv);
319
320	/*
321	 * If 'timeout-sec' unspecified in devicetree, assume a 30 second
322	 * default, unless the max timeout is less than 30 seconds, then use
323	 * the max instead.
324	 */
325	wdd->timeout = ASM9260_WDT_DEFAULT_TIMEOUT;
326	watchdog_init_timeout(wdd, 0, dev);
327
328	asm9260_wdt_get_dt_mode(priv);
329
330	if (priv->mode != HW_RESET)
331		priv->irq = platform_get_irq(pdev, 0);
332
333	if (priv->irq > 0) {
334		/*
335		 * Not all supported platforms specify an interrupt for the
336		 * watchdog, so let's make it optional.
337		 */
338		ret = devm_request_irq(dev, priv->irq, asm9260_wdt_irq, 0,
339				       pdev->name, priv);
340		if (ret < 0)
341			dev_warn(dev, "failed to request IRQ\n");
342	}
343
344	watchdog_set_restart_priority(wdd, 128);
345
346	watchdog_stop_on_reboot(wdd);
347	watchdog_stop_on_unregister(wdd);
348	ret = devm_watchdog_register_device(dev, wdd);
349	if (ret)
350		return ret;
351
352	platform_set_drvdata(pdev, priv);
353
354	dev_info(dev, "Watchdog enabled (timeout: %d sec, mode: %s)\n",
355		 wdd->timeout, mode_name[priv->mode]);
356	return 0;
357}
358
359static const struct of_device_id asm9260_wdt_of_match[] = {
360	{ .compatible = "alphascale,asm9260-wdt"},
361	{},
362};
363MODULE_DEVICE_TABLE(of, asm9260_wdt_of_match);
364
365static struct platform_driver asm9260_wdt_driver = {
366	.driver = {
367		.name = "asm9260-wdt",
368		.of_match_table	= asm9260_wdt_of_match,
369	},
370	.probe = asm9260_wdt_probe,
371};
372module_platform_driver(asm9260_wdt_driver);
373
374MODULE_DESCRIPTION("asm9260 WatchDog Timer Driver");
375MODULE_AUTHOR("Oleksij Rempel <linux@rempel-privat.de>");
376MODULE_LICENSE("GPL");
377