xref: /linux/drivers/watchdog/sama5d4_wdt.c (revision 76534860f108b812926a4fbfbdadbfa9cdec89d0)
1*76534860SWenyou Yang /*
2*76534860SWenyou Yang  * Driver for Atmel SAMA5D4 Watchdog Timer
3*76534860SWenyou Yang  *
4*76534860SWenyou Yang  * Copyright (C) 2015 Atmel Corporation
5*76534860SWenyou Yang  *
6*76534860SWenyou Yang  * Licensed under GPLv2.
7*76534860SWenyou Yang  */
8*76534860SWenyou Yang 
9*76534860SWenyou Yang #include <linux/interrupt.h>
10*76534860SWenyou Yang #include <linux/io.h>
11*76534860SWenyou Yang #include <linux/kernel.h>
12*76534860SWenyou Yang #include <linux/module.h>
13*76534860SWenyou Yang #include <linux/of.h>
14*76534860SWenyou Yang #include <linux/of_irq.h>
15*76534860SWenyou Yang #include <linux/platform_device.h>
16*76534860SWenyou Yang #include <linux/reboot.h>
17*76534860SWenyou Yang #include <linux/watchdog.h>
18*76534860SWenyou Yang 
19*76534860SWenyou Yang #include "at91sam9_wdt.h"
20*76534860SWenyou Yang 
21*76534860SWenyou Yang /* minimum and maximum watchdog timeout, in seconds */
22*76534860SWenyou Yang #define MIN_WDT_TIMEOUT		1
23*76534860SWenyou Yang #define MAX_WDT_TIMEOUT		16
24*76534860SWenyou Yang #define WDT_DEFAULT_TIMEOUT	MAX_WDT_TIMEOUT
25*76534860SWenyou Yang 
26*76534860SWenyou Yang #define WDT_SEC2TICKS(s)	((s) ? (((s) << 8) - 1) : 0)
27*76534860SWenyou Yang 
28*76534860SWenyou Yang struct sama5d4_wdt {
29*76534860SWenyou Yang 	struct watchdog_device	wdd;
30*76534860SWenyou Yang 	void __iomem		*reg_base;
31*76534860SWenyou Yang 	u32	config;
32*76534860SWenyou Yang };
33*76534860SWenyou Yang 
34*76534860SWenyou Yang static int wdt_timeout = WDT_DEFAULT_TIMEOUT;
35*76534860SWenyou Yang static bool nowayout = WATCHDOG_NOWAYOUT;
36*76534860SWenyou Yang 
37*76534860SWenyou Yang module_param(wdt_timeout, int, 0);
38*76534860SWenyou Yang MODULE_PARM_DESC(wdt_timeout,
39*76534860SWenyou Yang 	"Watchdog timeout in seconds. (default = "
40*76534860SWenyou Yang 	__MODULE_STRING(WDT_DEFAULT_TIMEOUT) ")");
41*76534860SWenyou Yang 
42*76534860SWenyou Yang module_param(nowayout, bool, 0);
43*76534860SWenyou Yang MODULE_PARM_DESC(nowayout,
44*76534860SWenyou Yang 	"Watchdog cannot be stopped once started (default="
45*76534860SWenyou Yang 	__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
46*76534860SWenyou Yang 
47*76534860SWenyou Yang #define wdt_read(wdt, field) \
48*76534860SWenyou Yang 	readl_relaxed((wdt)->reg_base + (field))
49*76534860SWenyou Yang 
50*76534860SWenyou Yang #define wdt_write(wtd, field, val) \
51*76534860SWenyou Yang 	writel_relaxed((val), (wdt)->reg_base + (field))
52*76534860SWenyou Yang 
53*76534860SWenyou Yang static int sama5d4_wdt_start(struct watchdog_device *wdd)
54*76534860SWenyou Yang {
55*76534860SWenyou Yang 	struct sama5d4_wdt *wdt = watchdog_get_drvdata(wdd);
56*76534860SWenyou Yang 	u32 reg;
57*76534860SWenyou Yang 
58*76534860SWenyou Yang 	reg = wdt_read(wdt, AT91_WDT_MR);
59*76534860SWenyou Yang 	reg &= ~AT91_WDT_WDDIS;
60*76534860SWenyou Yang 	wdt_write(wdt, AT91_WDT_MR, reg);
61*76534860SWenyou Yang 
62*76534860SWenyou Yang 	return 0;
63*76534860SWenyou Yang }
64*76534860SWenyou Yang 
65*76534860SWenyou Yang static int sama5d4_wdt_stop(struct watchdog_device *wdd)
66*76534860SWenyou Yang {
67*76534860SWenyou Yang 	struct sama5d4_wdt *wdt = watchdog_get_drvdata(wdd);
68*76534860SWenyou Yang 	u32 reg;
69*76534860SWenyou Yang 
70*76534860SWenyou Yang 	reg = wdt_read(wdt, AT91_WDT_MR);
71*76534860SWenyou Yang 	reg |= AT91_WDT_WDDIS;
72*76534860SWenyou Yang 	wdt_write(wdt, AT91_WDT_MR, reg);
73*76534860SWenyou Yang 
74*76534860SWenyou Yang 	return 0;
75*76534860SWenyou Yang }
76*76534860SWenyou Yang 
77*76534860SWenyou Yang static int sama5d4_wdt_ping(struct watchdog_device *wdd)
78*76534860SWenyou Yang {
79*76534860SWenyou Yang 	struct sama5d4_wdt *wdt = watchdog_get_drvdata(wdd);
80*76534860SWenyou Yang 
81*76534860SWenyou Yang 	wdt_write(wdt, AT91_WDT_CR, AT91_WDT_KEY | AT91_WDT_WDRSTT);
82*76534860SWenyou Yang 
83*76534860SWenyou Yang 	return 0;
84*76534860SWenyou Yang }
85*76534860SWenyou Yang 
86*76534860SWenyou Yang static int sama5d4_wdt_set_timeout(struct watchdog_device *wdd,
87*76534860SWenyou Yang 				 unsigned int timeout)
88*76534860SWenyou Yang {
89*76534860SWenyou Yang 	struct sama5d4_wdt *wdt = watchdog_get_drvdata(wdd);
90*76534860SWenyou Yang 	u32 value = WDT_SEC2TICKS(timeout);
91*76534860SWenyou Yang 	u32 reg;
92*76534860SWenyou Yang 
93*76534860SWenyou Yang 	reg = wdt_read(wdt, AT91_WDT_MR);
94*76534860SWenyou Yang 	reg &= ~AT91_WDT_WDV;
95*76534860SWenyou Yang 	reg &= ~AT91_WDT_WDD;
96*76534860SWenyou Yang 	reg |= AT91_WDT_SET_WDV(value);
97*76534860SWenyou Yang 	reg |= AT91_WDT_SET_WDD(value);
98*76534860SWenyou Yang 	wdt_write(wdt, AT91_WDT_MR, reg);
99*76534860SWenyou Yang 
100*76534860SWenyou Yang 	wdd->timeout = timeout;
101*76534860SWenyou Yang 
102*76534860SWenyou Yang 	return 0;
103*76534860SWenyou Yang }
104*76534860SWenyou Yang 
105*76534860SWenyou Yang static const struct watchdog_info sama5d4_wdt_info = {
106*76534860SWenyou Yang 	.options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING,
107*76534860SWenyou Yang 	.identity = "Atmel SAMA5D4 Watchdog",
108*76534860SWenyou Yang };
109*76534860SWenyou Yang 
110*76534860SWenyou Yang static struct watchdog_ops sama5d4_wdt_ops = {
111*76534860SWenyou Yang 	.owner = THIS_MODULE,
112*76534860SWenyou Yang 	.start = sama5d4_wdt_start,
113*76534860SWenyou Yang 	.stop = sama5d4_wdt_stop,
114*76534860SWenyou Yang 	.ping = sama5d4_wdt_ping,
115*76534860SWenyou Yang 	.set_timeout = sama5d4_wdt_set_timeout,
116*76534860SWenyou Yang };
117*76534860SWenyou Yang 
118*76534860SWenyou Yang static irqreturn_t sama5d4_wdt_irq_handler(int irq, void *dev_id)
119*76534860SWenyou Yang {
120*76534860SWenyou Yang 	struct sama5d4_wdt *wdt = platform_get_drvdata(dev_id);
121*76534860SWenyou Yang 
122*76534860SWenyou Yang 	if (wdt_read(wdt, AT91_WDT_SR)) {
123*76534860SWenyou Yang 		pr_crit("Atmel Watchdog Software Reset\n");
124*76534860SWenyou Yang 		emergency_restart();
125*76534860SWenyou Yang 		pr_crit("Reboot didn't succeed\n");
126*76534860SWenyou Yang 	}
127*76534860SWenyou Yang 
128*76534860SWenyou Yang 	return IRQ_HANDLED;
129*76534860SWenyou Yang }
130*76534860SWenyou Yang 
131*76534860SWenyou Yang static int of_sama5d4_wdt_init(struct device_node *np, struct sama5d4_wdt *wdt)
132*76534860SWenyou Yang {
133*76534860SWenyou Yang 	const char *tmp;
134*76534860SWenyou Yang 
135*76534860SWenyou Yang 	wdt->config = AT91_WDT_WDDIS;
136*76534860SWenyou Yang 
137*76534860SWenyou Yang 	if (!of_property_read_string(np, "atmel,watchdog-type", &tmp) &&
138*76534860SWenyou Yang 	    !strcmp(tmp, "software"))
139*76534860SWenyou Yang 		wdt->config |= AT91_WDT_WDFIEN;
140*76534860SWenyou Yang 	else
141*76534860SWenyou Yang 		wdt->config |= AT91_WDT_WDRSTEN;
142*76534860SWenyou Yang 
143*76534860SWenyou Yang 	if (of_property_read_bool(np, "atmel,idle-halt"))
144*76534860SWenyou Yang 		wdt->config |= AT91_WDT_WDIDLEHLT;
145*76534860SWenyou Yang 
146*76534860SWenyou Yang 	if (of_property_read_bool(np, "atmel,dbg-halt"))
147*76534860SWenyou Yang 		wdt->config |= AT91_WDT_WDDBGHLT;
148*76534860SWenyou Yang 
149*76534860SWenyou Yang 	return 0;
150*76534860SWenyou Yang }
151*76534860SWenyou Yang 
152*76534860SWenyou Yang static int sama5d4_wdt_init(struct sama5d4_wdt *wdt)
153*76534860SWenyou Yang {
154*76534860SWenyou Yang 	struct watchdog_device *wdd = &wdt->wdd;
155*76534860SWenyou Yang 	u32 value = WDT_SEC2TICKS(wdd->timeout);
156*76534860SWenyou Yang 	u32 reg;
157*76534860SWenyou Yang 
158*76534860SWenyou Yang 	/*
159*76534860SWenyou Yang 	 * Because the fields WDV and WDD must not be modified when the WDDIS
160*76534860SWenyou Yang 	 * bit is set, so clear the WDDIS bit before writing the WDT_MR.
161*76534860SWenyou Yang 	 */
162*76534860SWenyou Yang 	reg = wdt_read(wdt, AT91_WDT_MR);
163*76534860SWenyou Yang 	reg &= ~AT91_WDT_WDDIS;
164*76534860SWenyou Yang 	wdt_write(wdt, AT91_WDT_MR, reg);
165*76534860SWenyou Yang 
166*76534860SWenyou Yang 	reg = wdt->config;
167*76534860SWenyou Yang 	reg |= AT91_WDT_SET_WDD(value);
168*76534860SWenyou Yang 	reg |= AT91_WDT_SET_WDV(value);
169*76534860SWenyou Yang 
170*76534860SWenyou Yang 	wdt_write(wdt, AT91_WDT_MR, reg);
171*76534860SWenyou Yang 
172*76534860SWenyou Yang 	return 0;
173*76534860SWenyou Yang }
174*76534860SWenyou Yang 
175*76534860SWenyou Yang static int sama5d4_wdt_probe(struct platform_device *pdev)
176*76534860SWenyou Yang {
177*76534860SWenyou Yang 	struct watchdog_device *wdd;
178*76534860SWenyou Yang 	struct sama5d4_wdt *wdt;
179*76534860SWenyou Yang 	struct resource *res;
180*76534860SWenyou Yang 	void __iomem *regs;
181*76534860SWenyou Yang 	u32 irq = 0;
182*76534860SWenyou Yang 	int ret;
183*76534860SWenyou Yang 
184*76534860SWenyou Yang 	wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL);
185*76534860SWenyou Yang 	if (!wdt)
186*76534860SWenyou Yang 		return -ENOMEM;
187*76534860SWenyou Yang 
188*76534860SWenyou Yang 	wdd = &wdt->wdd;
189*76534860SWenyou Yang 	wdd->timeout = wdt_timeout;
190*76534860SWenyou Yang 	wdd->info = &sama5d4_wdt_info;
191*76534860SWenyou Yang 	wdd->ops = &sama5d4_wdt_ops;
192*76534860SWenyou Yang 	wdd->min_timeout = MIN_WDT_TIMEOUT;
193*76534860SWenyou Yang 	wdd->max_timeout = MAX_WDT_TIMEOUT;
194*76534860SWenyou Yang 
195*76534860SWenyou Yang 	watchdog_set_drvdata(wdd, wdt);
196*76534860SWenyou Yang 
197*76534860SWenyou Yang 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
198*76534860SWenyou Yang 	regs = devm_ioremap_resource(&pdev->dev, res);
199*76534860SWenyou Yang 	if (IS_ERR(regs))
200*76534860SWenyou Yang 		return PTR_ERR(regs);
201*76534860SWenyou Yang 
202*76534860SWenyou Yang 	wdt->reg_base = regs;
203*76534860SWenyou Yang 
204*76534860SWenyou Yang 	if (pdev->dev.of_node) {
205*76534860SWenyou Yang 		irq = irq_of_parse_and_map(pdev->dev.of_node, 0);
206*76534860SWenyou Yang 		if (!irq)
207*76534860SWenyou Yang 			dev_warn(&pdev->dev, "failed to get IRQ from DT\n");
208*76534860SWenyou Yang 
209*76534860SWenyou Yang 		ret = of_sama5d4_wdt_init(pdev->dev.of_node, wdt);
210*76534860SWenyou Yang 		if (ret)
211*76534860SWenyou Yang 			return ret;
212*76534860SWenyou Yang 	}
213*76534860SWenyou Yang 
214*76534860SWenyou Yang 	if ((wdt->config & AT91_WDT_WDFIEN) && irq) {
215*76534860SWenyou Yang 		ret = devm_request_irq(&pdev->dev, irq, sama5d4_wdt_irq_handler,
216*76534860SWenyou Yang 				       IRQF_SHARED | IRQF_IRQPOLL |
217*76534860SWenyou Yang 				       IRQF_NO_SUSPEND, pdev->name, pdev);
218*76534860SWenyou Yang 		if (ret) {
219*76534860SWenyou Yang 			dev_err(&pdev->dev,
220*76534860SWenyou Yang 				"cannot register interrupt handler\n");
221*76534860SWenyou Yang 			return ret;
222*76534860SWenyou Yang 		}
223*76534860SWenyou Yang 	}
224*76534860SWenyou Yang 
225*76534860SWenyou Yang 	ret = watchdog_init_timeout(wdd, wdt_timeout, &pdev->dev);
226*76534860SWenyou Yang 	if (ret) {
227*76534860SWenyou Yang 		dev_err(&pdev->dev, "unable to set timeout value\n");
228*76534860SWenyou Yang 		return ret;
229*76534860SWenyou Yang 	}
230*76534860SWenyou Yang 
231*76534860SWenyou Yang 	ret = sama5d4_wdt_init(wdt);
232*76534860SWenyou Yang 	if (ret)
233*76534860SWenyou Yang 		return ret;
234*76534860SWenyou Yang 
235*76534860SWenyou Yang 	watchdog_set_nowayout(wdd, nowayout);
236*76534860SWenyou Yang 
237*76534860SWenyou Yang 	ret = watchdog_register_device(wdd);
238*76534860SWenyou Yang 	if (ret) {
239*76534860SWenyou Yang 		dev_err(&pdev->dev, "failed to register watchdog device\n");
240*76534860SWenyou Yang 		return ret;
241*76534860SWenyou Yang 	}
242*76534860SWenyou Yang 
243*76534860SWenyou Yang 	platform_set_drvdata(pdev, wdt);
244*76534860SWenyou Yang 
245*76534860SWenyou Yang 	dev_info(&pdev->dev, "initialized (timeout = %d sec, nowayout = %d)\n",
246*76534860SWenyou Yang 		 wdt_timeout, nowayout);
247*76534860SWenyou Yang 
248*76534860SWenyou Yang 	return 0;
249*76534860SWenyou Yang }
250*76534860SWenyou Yang 
251*76534860SWenyou Yang static int sama5d4_wdt_remove(struct platform_device *pdev)
252*76534860SWenyou Yang {
253*76534860SWenyou Yang 	struct sama5d4_wdt *wdt = platform_get_drvdata(pdev);
254*76534860SWenyou Yang 
255*76534860SWenyou Yang 	sama5d4_wdt_stop(&wdt->wdd);
256*76534860SWenyou Yang 
257*76534860SWenyou Yang 	watchdog_unregister_device(&wdt->wdd);
258*76534860SWenyou Yang 
259*76534860SWenyou Yang 	return 0;
260*76534860SWenyou Yang }
261*76534860SWenyou Yang 
262*76534860SWenyou Yang static const struct of_device_id sama5d4_wdt_of_match[] = {
263*76534860SWenyou Yang 	{ .compatible = "atmel,sama5d4-wdt", },
264*76534860SWenyou Yang 	{ }
265*76534860SWenyou Yang };
266*76534860SWenyou Yang MODULE_DEVICE_TABLE(of, sama5d4_wdt_of_match);
267*76534860SWenyou Yang 
268*76534860SWenyou Yang static struct platform_driver sama5d4_wdt_driver = {
269*76534860SWenyou Yang 	.probe		= sama5d4_wdt_probe,
270*76534860SWenyou Yang 	.remove		= sama5d4_wdt_remove,
271*76534860SWenyou Yang 	.driver		= {
272*76534860SWenyou Yang 		.name	= "sama5d4_wdt",
273*76534860SWenyou Yang 		.of_match_table = sama5d4_wdt_of_match,
274*76534860SWenyou Yang 	}
275*76534860SWenyou Yang };
276*76534860SWenyou Yang module_platform_driver(sama5d4_wdt_driver);
277*76534860SWenyou Yang 
278*76534860SWenyou Yang MODULE_AUTHOR("Atmel Corporation");
279*76534860SWenyou Yang MODULE_DESCRIPTION("Atmel SAMA5D4 Watchdog Timer driver");
280*76534860SWenyou Yang MODULE_LICENSE("GPL v2");
281