Commit | Line | Data |
---|---|---|
2e62c498 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
853807fb AV |
2 | /* |
3 | * Watchdog driver for Atmel AT91RM9200 (Thunder) | |
4 | * | |
5 | * Copyright (C) 2003 SAN People (Pty) Ltd | |
6 | * | |
853807fb AV |
7 | */ |
8 | ||
27c766aa JP |
9 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
10 | ||
1977f032 | 11 | #include <linux/bitops.h> |
9ab45f3f | 12 | #include <linux/delay.h> |
853807fb AV |
13 | #include <linux/errno.h> |
14 | #include <linux/fs.h> | |
15 | #include <linux/init.h> | |
02e0746e | 16 | #include <linux/io.h> |
853807fb | 17 | #include <linux/kernel.h> |
8432f9e5 AB |
18 | #include <linux/mfd/syscon.h> |
19 | #include <linux/mfd/syscon/atmel-st.h> | |
853807fb AV |
20 | #include <linux/miscdevice.h> |
21 | #include <linux/module.h> | |
22 | #include <linux/moduleparam.h> | |
dfc7bd9c | 23 | #include <linux/platform_device.h> |
9ab45f3f | 24 | #include <linux/reboot.h> |
8432f9e5 | 25 | #include <linux/regmap.h> |
853807fb AV |
26 | #include <linux/types.h> |
27 | #include <linux/watchdog.h> | |
2760600d | 28 | #include <linux/uaccess.h> |
a6a1bcd3 JE |
29 | #include <linux/of.h> |
30 | #include <linux/of_device.h> | |
853807fb | 31 | |
dfc7bd9c AV |
32 | #define WDT_DEFAULT_TIME 5 /* seconds */ |
33 | #define WDT_MAX_TIME 256 /* seconds */ | |
853807fb AV |
34 | |
35 | static int wdt_time = WDT_DEFAULT_TIME; | |
86a1e189 | 36 | static bool nowayout = WATCHDOG_NOWAYOUT; |
8432f9e5 | 37 | static struct regmap *regmap_st; |
853807fb AV |
38 | |
39 | module_param(wdt_time, int, 0); | |
2760600d AC |
40 | MODULE_PARM_DESC(wdt_time, "Watchdog time in seconds. (default=" |
41 | __MODULE_STRING(WDT_DEFAULT_TIME) ")"); | |
853807fb | 42 | |
dfc7bd9c | 43 | #ifdef CONFIG_WATCHDOG_NOWAYOUT |
86a1e189 | 44 | module_param(nowayout, bool, 0); |
2760600d AC |
45 | MODULE_PARM_DESC(nowayout, |
46 | "Watchdog cannot be stopped once started (default=" | |
47 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | |
dfc7bd9c | 48 | #endif |
853807fb AV |
49 | |
50 | ||
51 | static unsigned long at91wdt_busy; | |
52 | ||
53 | /* ......................................................................... */ | |
54 | ||
9ab45f3f AB |
55 | static int at91rm9200_restart(struct notifier_block *this, |
56 | unsigned long mode, void *cmd) | |
57 | { | |
58 | /* | |
59 | * Perform a hardware reset with the use of the Watchdog timer. | |
60 | */ | |
61 | regmap_write(regmap_st, AT91_ST_WDMR, | |
62 | AT91_ST_RSTEN | AT91_ST_EXTEN | 1); | |
63 | regmap_write(regmap_st, AT91_ST_CR, AT91_ST_WDRST); | |
64 | ||
65 | mdelay(2000); | |
66 | ||
67 | pr_emerg("Unable to restart system\n"); | |
68 | return NOTIFY_DONE; | |
69 | } | |
70 | ||
71 | static struct notifier_block at91rm9200_restart_nb = { | |
72 | .notifier_call = at91rm9200_restart, | |
73 | .priority = 192, | |
74 | }; | |
75 | ||
853807fb AV |
76 | /* |
77 | * Disable the watchdog. | |
78 | */ | |
2760600d | 79 | static inline void at91_wdt_stop(void) |
853807fb | 80 | { |
8432f9e5 | 81 | regmap_write(regmap_st, AT91_ST_WDMR, AT91_ST_EXTEN); |
853807fb AV |
82 | } |
83 | ||
84 | /* | |
85 | * Enable and reset the watchdog. | |
86 | */ | |
2760600d | 87 | static inline void at91_wdt_start(void) |
853807fb | 88 | { |
8432f9e5 | 89 | regmap_write(regmap_st, AT91_ST_WDMR, AT91_ST_EXTEN | AT91_ST_RSTEN | |
2760600d | 90 | (((65536 * wdt_time) >> 8) & AT91_ST_WDV)); |
8432f9e5 | 91 | regmap_write(regmap_st, AT91_ST_CR, AT91_ST_WDRST); |
853807fb AV |
92 | } |
93 | ||
94 | /* | |
95 | * Reload the watchdog timer. (ie, pat the watchdog) | |
96 | */ | |
2760600d | 97 | static inline void at91_wdt_reload(void) |
853807fb | 98 | { |
8432f9e5 | 99 | regmap_write(regmap_st, AT91_ST_CR, AT91_ST_WDRST); |
853807fb AV |
100 | } |
101 | ||
102 | /* ......................................................................... */ | |
103 | ||
104 | /* | |
105 | * Watchdog device is opened, and watchdog starts running. | |
106 | */ | |
107 | static int at91_wdt_open(struct inode *inode, struct file *file) | |
108 | { | |
109 | if (test_and_set_bit(0, &at91wdt_busy)) | |
110 | return -EBUSY; | |
111 | ||
112 | at91_wdt_start(); | |
c5bf68fe | 113 | return stream_open(inode, file); |
853807fb AV |
114 | } |
115 | ||
116 | /* | |
117 | * Close the watchdog device. | |
118 | * If CONFIG_WATCHDOG_NOWAYOUT is NOT defined then the watchdog is also | |
119 | * disabled. | |
120 | */ | |
121 | static int at91_wdt_close(struct inode *inode, struct file *file) | |
122 | { | |
2760600d | 123 | /* Disable the watchdog when file is closed */ |
853807fb | 124 | if (!nowayout) |
2760600d | 125 | at91_wdt_stop(); |
853807fb AV |
126 | |
127 | clear_bit(0, &at91wdt_busy); | |
128 | return 0; | |
129 | } | |
130 | ||
131 | /* | |
132 | * Change the watchdog time interval. | |
133 | */ | |
134 | static int at91_wdt_settimeout(int new_time) | |
135 | { | |
136 | /* | |
2af29b78 | 137 | * All counting occurs at SLOW_CLOCK / 128 = 256 Hz |
853807fb AV |
138 | * |
139 | * Since WDV is a 16-bit counter, the maximum period is | |
2af29b78 | 140 | * 65536 / 256 = 256 seconds. |
853807fb AV |
141 | */ |
142 | if ((new_time <= 0) || (new_time > WDT_MAX_TIME)) | |
143 | return -EINVAL; | |
144 | ||
2760600d AC |
145 | /* Set new watchdog time. It will be used when |
146 | at91_wdt_start() is called. */ | |
853807fb AV |
147 | wdt_time = new_time; |
148 | return 0; | |
149 | } | |
150 | ||
42747d71 | 151 | static const struct watchdog_info at91_wdt_info = { |
853807fb AV |
152 | .identity = "at91 watchdog", |
153 | .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, | |
154 | }; | |
155 | ||
156 | /* | |
157 | * Handle commands from user-space. | |
158 | */ | |
3c4fafd6 | 159 | static long at91_wdt_ioctl(struct file *file, |
2760600d | 160 | unsigned int cmd, unsigned long arg) |
853807fb AV |
161 | { |
162 | void __user *argp = (void __user *)arg; | |
163 | int __user *p = argp; | |
164 | int new_value; | |
165 | ||
2760600d | 166 | switch (cmd) { |
2760600d AC |
167 | case WDIOC_GETSUPPORT: |
168 | return copy_to_user(argp, &at91_wdt_info, | |
169 | sizeof(at91_wdt_info)) ? -EFAULT : 0; | |
2760600d AC |
170 | case WDIOC_GETSTATUS: |
171 | case WDIOC_GETBOOTSTATUS: | |
172 | return put_user(0, p); | |
173 | case WDIOC_SETOPTIONS: | |
174 | if (get_user(new_value, p)) | |
175 | return -EFAULT; | |
176 | if (new_value & WDIOS_DISABLECARD) | |
177 | at91_wdt_stop(); | |
178 | if (new_value & WDIOS_ENABLECARD) | |
853807fb | 179 | at91_wdt_start(); |
2760600d | 180 | return 0; |
0c06090c WVS |
181 | case WDIOC_KEEPALIVE: |
182 | at91_wdt_reload(); /* pat the watchdog */ | |
183 | return 0; | |
184 | case WDIOC_SETTIMEOUT: | |
185 | if (get_user(new_value, p)) | |
186 | return -EFAULT; | |
187 | if (at91_wdt_settimeout(new_value)) | |
188 | return -EINVAL; | |
189 | /* Enable new time value */ | |
190 | at91_wdt_start(); | |
191 | /* Return current value */ | |
192 | return put_user(wdt_time, p); | |
193 | case WDIOC_GETTIMEOUT: | |
194 | return put_user(wdt_time, p); | |
2760600d AC |
195 | default: |
196 | return -ENOTTY; | |
853807fb AV |
197 | } |
198 | } | |
199 | ||
200 | /* | |
201 | * Pat the watchdog whenever device is written to. | |
202 | */ | |
2760600d AC |
203 | static ssize_t at91_wdt_write(struct file *file, const char *data, |
204 | size_t len, loff_t *ppos) | |
853807fb AV |
205 | { |
206 | at91_wdt_reload(); /* pat the watchdog */ | |
207 | return len; | |
208 | } | |
209 | ||
210 | /* ......................................................................... */ | |
211 | ||
62322d25 | 212 | static const struct file_operations at91wdt_fops = { |
853807fb AV |
213 | .owner = THIS_MODULE, |
214 | .llseek = no_llseek, | |
2760600d | 215 | .unlocked_ioctl = at91_wdt_ioctl, |
b6dfb247 | 216 | .compat_ioctl = compat_ptr_ioctl, |
853807fb AV |
217 | .open = at91_wdt_open, |
218 | .release = at91_wdt_close, | |
219 | .write = at91_wdt_write, | |
220 | }; | |
221 | ||
222 | static struct miscdevice at91wdt_miscdev = { | |
223 | .minor = WATCHDOG_MINOR, | |
224 | .name = "watchdog", | |
225 | .fops = &at91wdt_fops, | |
226 | }; | |
227 | ||
2d991a16 | 228 | static int at91wdt_probe(struct platform_device *pdev) |
853807fb | 229 | { |
8432f9e5 AB |
230 | struct device *dev = &pdev->dev; |
231 | struct device *parent; | |
853807fb AV |
232 | int res; |
233 | ||
e0b79e0b | 234 | if (at91wdt_miscdev.parent) |
dfc7bd9c | 235 | return -EBUSY; |
e0b79e0b | 236 | at91wdt_miscdev.parent = &pdev->dev; |
853807fb | 237 | |
8432f9e5 AB |
238 | parent = dev->parent; |
239 | if (!parent) { | |
240 | dev_err(dev, "no parent\n"); | |
241 | return -ENODEV; | |
242 | } | |
243 | ||
244 | regmap_st = syscon_node_to_regmap(parent->of_node); | |
bf5125d5 | 245 | if (IS_ERR(regmap_st)) |
8432f9e5 AB |
246 | return -ENODEV; |
247 | ||
853807fb AV |
248 | res = misc_register(&at91wdt_miscdev); |
249 | if (res) | |
250 | return res; | |
251 | ||
9ab45f3f AB |
252 | res = register_restart_handler(&at91rm9200_restart_nb); |
253 | if (res) | |
254 | dev_warn(dev, "failed to register restart handler\n"); | |
255 | ||
27c766aa JP |
256 | pr_info("AT91 Watchdog Timer enabled (%d seconds%s)\n", |
257 | wdt_time, nowayout ? ", nowayout" : ""); | |
853807fb AV |
258 | return 0; |
259 | } | |
260 | ||
4b12b896 | 261 | static int at91wdt_remove(struct platform_device *pdev) |
dfc7bd9c | 262 | { |
9ab45f3f | 263 | struct device *dev = &pdev->dev; |
dfc7bd9c AV |
264 | int res; |
265 | ||
9ab45f3f AB |
266 | res = unregister_restart_handler(&at91rm9200_restart_nb); |
267 | if (res) | |
268 | dev_warn(dev, "failed to unregister restart handler\n"); | |
269 | ||
f368ed60 GKH |
270 | misc_deregister(&at91wdt_miscdev); |
271 | at91wdt_miscdev.parent = NULL; | |
dfc7bd9c AV |
272 | |
273 | return res; | |
274 | } | |
275 | ||
276 | static void at91wdt_shutdown(struct platform_device *pdev) | |
277 | { | |
278 | at91_wdt_stop(); | |
279 | } | |
280 | ||
281 | #ifdef CONFIG_PM | |
282 | ||
283 | static int at91wdt_suspend(struct platform_device *pdev, pm_message_t message) | |
284 | { | |
285 | at91_wdt_stop(); | |
286 | return 0; | |
287 | } | |
288 | ||
289 | static int at91wdt_resume(struct platform_device *pdev) | |
290 | { | |
291 | if (at91wdt_busy) | |
292 | at91_wdt_start(); | |
95f62bdc | 293 | return 0; |
dfc7bd9c AV |
294 | } |
295 | ||
296 | #else | |
297 | #define at91wdt_suspend NULL | |
298 | #define at91wdt_resume NULL | |
299 | #endif | |
300 | ||
a6a1bcd3 JE |
301 | static const struct of_device_id at91_wdt_dt_ids[] = { |
302 | { .compatible = "atmel,at91rm9200-wdt" }, | |
303 | { /* sentinel */ } | |
304 | }; | |
305 | MODULE_DEVICE_TABLE(of, at91_wdt_dt_ids); | |
306 | ||
dfc7bd9c AV |
307 | static struct platform_driver at91wdt_driver = { |
308 | .probe = at91wdt_probe, | |
82268714 | 309 | .remove = at91wdt_remove, |
dfc7bd9c AV |
310 | .shutdown = at91wdt_shutdown, |
311 | .suspend = at91wdt_suspend, | |
312 | .resume = at91wdt_resume, | |
313 | .driver = { | |
8432f9e5 | 314 | .name = "atmel_st_watchdog", |
85eee819 | 315 | .of_match_table = at91_wdt_dt_ids, |
dfc7bd9c AV |
316 | }, |
317 | }; | |
318 | ||
319 | static int __init at91_wdt_init(void) | |
320 | { | |
2760600d AC |
321 | /* Check that the heartbeat value is within range; |
322 | if not reset to the default */ | |
dfc7bd9c AV |
323 | if (at91_wdt_settimeout(wdt_time)) { |
324 | at91_wdt_settimeout(WDT_DEFAULT_TIME); | |
27c766aa JP |
325 | pr_info("wdt_time value must be 1 <= wdt_time <= 256, using %d\n", |
326 | wdt_time); | |
dfc7bd9c AV |
327 | } |
328 | ||
329 | return platform_driver_register(&at91wdt_driver); | |
330 | } | |
331 | ||
853807fb AV |
332 | static void __exit at91_wdt_exit(void) |
333 | { | |
dfc7bd9c | 334 | platform_driver_unregister(&at91wdt_driver); |
853807fb AV |
335 | } |
336 | ||
337 | module_init(at91_wdt_init); | |
338 | module_exit(at91_wdt_exit); | |
339 | ||
340 | MODULE_AUTHOR("Andrew Victor"); | |
341 | MODULE_DESCRIPTION("Watchdog driver for Atmel AT91RM9200"); | |
342 | MODULE_LICENSE("GPL"); | |
8432f9e5 | 343 | MODULE_ALIAS("platform:atmel_st_watchdog"); |