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