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