Linux Audio

Check our new training course

Loading...
v6.8
  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");
v4.10.11
 
  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
 50enum asm9260_wdt_mode {
 51	HW_RESET,
 52	SW_RESET,
 53	DEBUG,
 54};
 55
 56struct 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
 70static 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
 80static 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
 90static 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
102static 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
119static 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
130static 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
138static 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
156static 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
175static 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
186static const struct watchdog_info asm9260_wdt_ident = {
187	.options          =     WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING
188				| WDIOF_MAGICCLOSE,
189	.identity         =	"Alphascale asm9260 Watchdog",
190};
191
192static 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
 
 
 
 
 
201static 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
253static 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
277static 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
354clk_off:
355	clk_disable_unprepare(priv->clk);
356	clk_disable_unprepare(priv->clk_ahb);
357	return ret;
358}
359
360static 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
367static 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
383static const struct of_device_id asm9260_wdt_of_match[] = {
384	{ .compatible = "alphascale,asm9260-wdt"},
385	{},
386};
387MODULE_DEVICE_TABLE(of, asm9260_wdt_of_match);
388
389static 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};
398module_platform_driver(asm9260_wdt_driver);
399
400MODULE_DESCRIPTION("asm9260 WatchDog Timer Driver");
401MODULE_AUTHOR("Oleksij Rempel <linux@rempel-privat.de>");
402MODULE_LICENSE("GPL");