Blame view

drivers/watchdog/at91rm9200_wdt.c 7.66 KB
2e62c4988   Marcus Folkesson   watchdog: add SPD...
1
  // SPDX-License-Identifier: GPL-2.0+
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
2
3
4
5
6
  /*
   * Watchdog driver for Atmel AT91RM9200 (Thunder)
   *
   *  Copyright (C) 2003 SAN People (Pty) Ltd
   *
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
7
   */
27c766aaa   Joe Perches   watchdog: Use pr_...
8
  #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
1977f0327   Jiri Slaby   remove asm/bitops...
9
  #include <linux/bitops.h>
9ab45f3fb   Alexandre Belloni   watchdog: at91rm9...
10
  #include <linux/delay.h>
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
11
12
13
  #include <linux/errno.h>
  #include <linux/fs.h>
  #include <linux/init.h>
02e0746ec   Jean-Christop PLAGNIOL-VILLARD   [ARM] 5370/1: at9...
14
  #include <linux/io.h>
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
15
  #include <linux/kernel.h>
8432f9e5e   Alexandre Belloni   watchdog: at91rm9...
16
17
  #include <linux/mfd/syscon.h>
  #include <linux/mfd/syscon/atmel-st.h>
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
18
19
20
  #include <linux/miscdevice.h>
  #include <linux/module.h>
  #include <linux/moduleparam.h>
dfc7bd9c3   Andrew Victor   [WATCHDOG] conver...
21
  #include <linux/platform_device.h>
9ab45f3fb   Alexandre Belloni   watchdog: at91rm9...
22
  #include <linux/reboot.h>
8432f9e5e   Alexandre Belloni   watchdog: at91rm9...
23
  #include <linux/regmap.h>
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
24
25
  #include <linux/types.h>
  #include <linux/watchdog.h>
2760600da   Alan Cox   [WATCHDOG 06/57] ...
26
  #include <linux/uaccess.h>
a6a1bcd37   Joachim Eastwood   watchdog: at91rm9...
27
28
  #include <linux/of.h>
  #include <linux/of_device.h>
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
29

dfc7bd9c3   Andrew Victor   [WATCHDOG] conver...
30
31
  #define WDT_DEFAULT_TIME	5	/* seconds */
  #define WDT_MAX_TIME		256	/* seconds */
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
32
33
  
  static int wdt_time = WDT_DEFAULT_TIME;
86a1e1896   Wim Van Sebroeck   watchdog: nowayou...
34
  static bool nowayout = WATCHDOG_NOWAYOUT;
8432f9e5e   Alexandre Belloni   watchdog: at91rm9...
35
  static struct regmap *regmap_st;
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
36
37
  
  module_param(wdt_time, int, 0);
2760600da   Alan Cox   [WATCHDOG 06/57] ...
38
39
  MODULE_PARM_DESC(wdt_time, "Watchdog time in seconds. (default="
  				__MODULE_STRING(WDT_DEFAULT_TIME) ")");
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
40

dfc7bd9c3   Andrew Victor   [WATCHDOG] conver...
41
  #ifdef CONFIG_WATCHDOG_NOWAYOUT
86a1e1896   Wim Van Sebroeck   watchdog: nowayou...
42
  module_param(nowayout, bool, 0);
2760600da   Alan Cox   [WATCHDOG 06/57] ...
43
44
45
  MODULE_PARM_DESC(nowayout,
  		"Watchdog cannot be stopped once started (default="
  				__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
dfc7bd9c3   Andrew Victor   [WATCHDOG] conver...
46
  #endif
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
47
48
49
50
51
  
  
  static unsigned long at91wdt_busy;
  
  /* ......................................................................... */
9ab45f3fb   Alexandre Belloni   watchdog: at91rm9...
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
  static int at91rm9200_restart(struct notifier_block *this,
  					unsigned long mode, void *cmd)
  {
  	/*
  	 * Perform a hardware reset with the use of the Watchdog timer.
  	 */
  	regmap_write(regmap_st, AT91_ST_WDMR,
  		     AT91_ST_RSTEN | AT91_ST_EXTEN | 1);
  	regmap_write(regmap_st, AT91_ST_CR, AT91_ST_WDRST);
  
  	mdelay(2000);
  
  	pr_emerg("Unable to restart system
  ");
  	return NOTIFY_DONE;
  }
  
  static struct notifier_block at91rm9200_restart_nb = {
  	.notifier_call = at91rm9200_restart,
  	.priority = 192,
  };
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
73
74
75
  /*
   * Disable the watchdog.
   */
2760600da   Alan Cox   [WATCHDOG 06/57] ...
76
  static inline void at91_wdt_stop(void)
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
77
  {
8432f9e5e   Alexandre Belloni   watchdog: at91rm9...
78
  	regmap_write(regmap_st, AT91_ST_WDMR, AT91_ST_EXTEN);
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
79
80
81
82
83
  }
  
  /*
   * Enable and reset the watchdog.
   */
2760600da   Alan Cox   [WATCHDOG 06/57] ...
84
  static inline void at91_wdt_start(void)
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
85
  {
8432f9e5e   Alexandre Belloni   watchdog: at91rm9...
86
  	regmap_write(regmap_st, AT91_ST_WDMR, AT91_ST_EXTEN | AT91_ST_RSTEN |
2760600da   Alan Cox   [WATCHDOG 06/57] ...
87
  				(((65536 * wdt_time) >> 8) & AT91_ST_WDV));
8432f9e5e   Alexandre Belloni   watchdog: at91rm9...
88
  	regmap_write(regmap_st, AT91_ST_CR, AT91_ST_WDRST);
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
89
90
91
92
93
  }
  
  /*
   * Reload the watchdog timer.  (ie, pat the watchdog)
   */
2760600da   Alan Cox   [WATCHDOG 06/57] ...
94
  static inline void at91_wdt_reload(void)
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
95
  {
8432f9e5e   Alexandre Belloni   watchdog: at91rm9...
96
  	regmap_write(regmap_st, AT91_ST_CR, AT91_ST_WDRST);
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
97
98
99
100
101
102
103
104
105
106
107
108
109
  }
  
  /* ......................................................................... */
  
  /*
   * Watchdog device is opened, and watchdog starts running.
   */
  static int at91_wdt_open(struct inode *inode, struct file *file)
  {
  	if (test_and_set_bit(0, &at91wdt_busy))
  		return -EBUSY;
  
  	at91_wdt_start();
c5bf68fe0   Kirill Smelkov   *: convert stream...
110
  	return stream_open(inode, file);
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
111
112
113
114
115
116
117
118
119
  }
  
  /*
   * Close the watchdog device.
   * If CONFIG_WATCHDOG_NOWAYOUT is NOT defined then the watchdog is also
   *  disabled.
   */
  static int at91_wdt_close(struct inode *inode, struct file *file)
  {
2760600da   Alan Cox   [WATCHDOG 06/57] ...
120
  	/* Disable the watchdog when file is closed */
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
121
  	if (!nowayout)
2760600da   Alan Cox   [WATCHDOG 06/57] ...
122
  		at91_wdt_stop();
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
123
124
125
126
127
128
129
130
131
132
133
  
  	clear_bit(0, &at91wdt_busy);
  	return 0;
  }
  
  /*
   * Change the watchdog time interval.
   */
  static int at91_wdt_settimeout(int new_time)
  {
  	/*
2af29b786   Andrew Victor   [ARM] 5390/1: AT9...
134
  	 * All counting occurs at SLOW_CLOCK / 128 = 256 Hz
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
135
136
  	 *
  	 * Since WDV is a 16-bit counter, the maximum period is
2af29b786   Andrew Victor   [ARM] 5390/1: AT9...
137
  	 * 65536 / 256 = 256 seconds.
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
138
139
140
  	 */
  	if ((new_time <= 0) || (new_time > WDT_MAX_TIME))
  		return -EINVAL;
2760600da   Alan Cox   [WATCHDOG 06/57] ...
141
142
  	/* Set new watchdog time. It will be used when
  	   at91_wdt_start() is called. */
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
143
144
145
  	wdt_time = new_time;
  	return 0;
  }
42747d712   Wim Van Sebroeck   [WATCHDOG] watchd...
146
  static const struct watchdog_info at91_wdt_info = {
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
147
148
149
150
151
152
153
  	.identity	= "at91 watchdog",
  	.options	= WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
  };
  
  /*
   * Handle commands from user-space.
   */
3c4fafd65   Adrian Bunk   [WATCHDOG] fix wa...
154
  static long at91_wdt_ioctl(struct file *file,
2760600da   Alan Cox   [WATCHDOG 06/57] ...
155
  					unsigned int cmd, unsigned long arg)
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
156
157
158
159
  {
  	void __user *argp = (void __user *)arg;
  	int __user *p = argp;
  	int new_value;
2760600da   Alan Cox   [WATCHDOG 06/57] ...
160
  	switch (cmd) {
2760600da   Alan Cox   [WATCHDOG 06/57] ...
161
162
163
  	case WDIOC_GETSUPPORT:
  		return copy_to_user(argp, &at91_wdt_info,
  				sizeof(at91_wdt_info)) ? -EFAULT : 0;
2760600da   Alan Cox   [WATCHDOG 06/57] ...
164
165
166
167
168
169
170
171
172
  	case WDIOC_GETSTATUS:
  	case WDIOC_GETBOOTSTATUS:
  		return put_user(0, p);
  	case WDIOC_SETOPTIONS:
  		if (get_user(new_value, p))
  			return -EFAULT;
  		if (new_value & WDIOS_DISABLECARD)
  			at91_wdt_stop();
  		if (new_value & WDIOS_ENABLECARD)
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
173
  			at91_wdt_start();
2760600da   Alan Cox   [WATCHDOG 06/57] ...
174
  		return 0;
0c06090c9   Wim Van Sebroeck   [WATCHDOG] Coding...
175
176
177
178
179
180
181
182
183
184
185
186
187
188
  	case WDIOC_KEEPALIVE:
  		at91_wdt_reload();	/* pat the watchdog */
  		return 0;
  	case WDIOC_SETTIMEOUT:
  		if (get_user(new_value, p))
  			return -EFAULT;
  		if (at91_wdt_settimeout(new_value))
  			return -EINVAL;
  		/* Enable new time value */
  		at91_wdt_start();
  		/* Return current value */
  		return put_user(wdt_time, p);
  	case WDIOC_GETTIMEOUT:
  		return put_user(wdt_time, p);
2760600da   Alan Cox   [WATCHDOG 06/57] ...
189
190
  	default:
  		return -ENOTTY;
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
191
192
193
194
195
196
  	}
  }
  
  /*
   * Pat the watchdog whenever device is written to.
   */
2760600da   Alan Cox   [WATCHDOG 06/57] ...
197
198
  static ssize_t at91_wdt_write(struct file *file, const char *data,
  						size_t len, loff_t *ppos)
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
199
200
201
202
203
204
  {
  	at91_wdt_reload();		/* pat the watchdog */
  	return len;
  }
  
  /* ......................................................................... */
62322d255   Arjan van de Ven   [PATCH] make more...
205
  static const struct file_operations at91wdt_fops = {
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
206
207
  	.owner		= THIS_MODULE,
  	.llseek		= no_llseek,
2760600da   Alan Cox   [WATCHDOG 06/57] ...
208
  	.unlocked_ioctl	= at91_wdt_ioctl,
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
209
210
211
212
213
214
215
216
217
218
  	.open		= at91_wdt_open,
  	.release	= at91_wdt_close,
  	.write		= at91_wdt_write,
  };
  
  static struct miscdevice at91wdt_miscdev = {
  	.minor		= WATCHDOG_MINOR,
  	.name		= "watchdog",
  	.fops		= &at91wdt_fops,
  };
2d991a164   Bill Pemberton   watchdog: remove ...
219
  static int at91wdt_probe(struct platform_device *pdev)
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
220
  {
8432f9e5e   Alexandre Belloni   watchdog: at91rm9...
221
222
  	struct device *dev = &pdev->dev;
  	struct device *parent;
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
223
  	int res;
e0b79e0bc   Andrew Victor   [WATCHDOG] watchd...
224
  	if (at91wdt_miscdev.parent)
dfc7bd9c3   Andrew Victor   [WATCHDOG] conver...
225
  		return -EBUSY;
e0b79e0bc   Andrew Victor   [WATCHDOG] watchd...
226
  	at91wdt_miscdev.parent = &pdev->dev;
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
227

8432f9e5e   Alexandre Belloni   watchdog: at91rm9...
228
229
230
231
232
233
234
235
  	parent = dev->parent;
  	if (!parent) {
  		dev_err(dev, "no parent
  ");
  		return -ENODEV;
  	}
  
  	regmap_st = syscon_node_to_regmap(parent->of_node);
bf5125d5e   Bjorn Andersson   watchdog: at91rm9...
236
  	if (IS_ERR(regmap_st))
8432f9e5e   Alexandre Belloni   watchdog: at91rm9...
237
  		return -ENODEV;
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
238
239
240
  	res = misc_register(&at91wdt_miscdev);
  	if (res)
  		return res;
9ab45f3fb   Alexandre Belloni   watchdog: at91rm9...
241
242
243
244
  	res = register_restart_handler(&at91rm9200_restart_nb);
  	if (res)
  		dev_warn(dev, "failed to register restart handler
  ");
27c766aaa   Joe Perches   watchdog: Use pr_...
245
246
247
  	pr_info("AT91 Watchdog Timer enabled (%d seconds%s)
  ",
  		wdt_time, nowayout ? ", nowayout" : "");
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
248
249
  	return 0;
  }
4b12b896c   Bill Pemberton   watchdog: remove ...
250
  static int at91wdt_remove(struct platform_device *pdev)
dfc7bd9c3   Andrew Victor   [WATCHDOG] conver...
251
  {
9ab45f3fb   Alexandre Belloni   watchdog: at91rm9...
252
  	struct device *dev = &pdev->dev;
dfc7bd9c3   Andrew Victor   [WATCHDOG] conver...
253
  	int res;
9ab45f3fb   Alexandre Belloni   watchdog: at91rm9...
254
255
256
257
  	res = unregister_restart_handler(&at91rm9200_restart_nb);
  	if (res)
  		dev_warn(dev, "failed to unregister restart handler
  ");
f368ed608   Greg Kroah-Hartman   char: make misc_d...
258
259
  	misc_deregister(&at91wdt_miscdev);
  	at91wdt_miscdev.parent = NULL;
dfc7bd9c3   Andrew Victor   [WATCHDOG] conver...
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
  
  	return res;
  }
  
  static void at91wdt_shutdown(struct platform_device *pdev)
  {
  	at91_wdt_stop();
  }
  
  #ifdef CONFIG_PM
  
  static int at91wdt_suspend(struct platform_device *pdev, pm_message_t message)
  {
  	at91_wdt_stop();
  	return 0;
  }
  
  static int at91wdt_resume(struct platform_device *pdev)
  {
  	if (at91wdt_busy)
  		at91_wdt_start();
95f62bdc5   Ilpo Jarvinen   [WATCHDOG] at91rm...
281
  	return 0;
dfc7bd9c3   Andrew Victor   [WATCHDOG] conver...
282
283
284
285
286
287
  }
  
  #else
  #define at91wdt_suspend NULL
  #define at91wdt_resume	NULL
  #endif
a6a1bcd37   Joachim Eastwood   watchdog: at91rm9...
288
289
290
291
292
  static const struct of_device_id at91_wdt_dt_ids[] = {
  	{ .compatible = "atmel,at91rm9200-wdt" },
  	{ /* sentinel */ }
  };
  MODULE_DEVICE_TABLE(of, at91_wdt_dt_ids);
dfc7bd9c3   Andrew Victor   [WATCHDOG] conver...
293
294
  static struct platform_driver at91wdt_driver = {
  	.probe		= at91wdt_probe,
82268714b   Bill Pemberton   watchdog: remove ...
295
  	.remove		= at91wdt_remove,
dfc7bd9c3   Andrew Victor   [WATCHDOG] conver...
296
297
298
299
  	.shutdown	= at91wdt_shutdown,
  	.suspend	= at91wdt_suspend,
  	.resume		= at91wdt_resume,
  	.driver		= {
8432f9e5e   Alexandre Belloni   watchdog: at91rm9...
300
  		.name	= "atmel_st_watchdog",
85eee8192   Sachin Kamat   watchdog: Remove ...
301
  		.of_match_table = at91_wdt_dt_ids,
dfc7bd9c3   Andrew Victor   [WATCHDOG] conver...
302
303
304
305
306
  	},
  };
  
  static int __init at91_wdt_init(void)
  {
2760600da   Alan Cox   [WATCHDOG 06/57] ...
307
308
  	/* Check that the heartbeat value is within range;
  	   if not reset to the default */
dfc7bd9c3   Andrew Victor   [WATCHDOG] conver...
309
310
  	if (at91_wdt_settimeout(wdt_time)) {
  		at91_wdt_settimeout(WDT_DEFAULT_TIME);
27c766aaa   Joe Perches   watchdog: Use pr_...
311
312
313
  		pr_info("wdt_time value must be 1 <= wdt_time <= 256, using %d
  ",
  			wdt_time);
dfc7bd9c3   Andrew Victor   [WATCHDOG] conver...
314
315
316
317
  	}
  
  	return platform_driver_register(&at91wdt_driver);
  }
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
318
319
  static void __exit at91_wdt_exit(void)
  {
dfc7bd9c3   Andrew Victor   [WATCHDOG] conver...
320
  	platform_driver_unregister(&at91wdt_driver);
853807fb5   Andrew Victor   [WATCHDOG] at91_w...
321
322
323
324
325
326
327
328
  }
  
  module_init(at91_wdt_init);
  module_exit(at91_wdt_exit);
  
  MODULE_AUTHOR("Andrew Victor");
  MODULE_DESCRIPTION("Watchdog driver for Atmel AT91RM9200");
  MODULE_LICENSE("GPL");
8432f9e5e   Alexandre Belloni   watchdog: at91rm9...
329
  MODULE_ALIAS("platform:atmel_st_watchdog");