watch dog: change module laod level from module_init to subsys_initcall
[firefly-linux-kernel-4.4.55.git] / drivers / watchdog / rk29_wdt.c
1 /* linux/drivers/watchdog/rk29_wdt.c\r
2  *\r
3  * Copyright (C) 2011 ROCKCHIP, Inc.\r
4  *      hhb@rock-chips.com\r
5  *\r
6  * This program is free software; you can redistribute it and/or modify\r
7  * it under the terms of the GNU General Public License as published by\r
8  * the Free Software Foundation; either version 2 of the License, or\r
9  * (at your option) any later version.\r
10  *\r
11  * This program is distributed in the hope that it will be useful,\r
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
14  * GNU General Public License for more details.\r
15  *\r
16  * You should have received a copy of the GNU General Public License\r
17  * along with this program; if not, write to the Free Software\r
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\r
19 */\r
20 \r
21 \r
22 #include <linux/module.h>\r
23 #include <linux/moduleparam.h>\r
24 #include <linux/types.h>\r
25 #include <linux/timer.h>\r
26 #include <linux/miscdevice.h>\r
27 #include <linux/watchdog.h>\r
28 #include <linux/fs.h>\r
29 #include <linux/init.h>\r
30 #include <linux/platform_device.h>\r
31 #include <linux/interrupt.h>\r
32 #include <linux/clk.h>\r
33 #include <linux/uaccess.h>\r
34 #include <linux/io.h>\r
35 \r
36 #include <asm/mach/map.h>\r
37 \r
38 \r
39 /* RK29 registers define */\r
40 #define RK29_WDT_CR     0X00\r
41 #define RK29_WDT_TORR   0X04\r
42 #define RK29_WDT_CCVR   0X08\r
43 #define RK29_WDT_CRR    0X0C\r
44 #define RK29_WDT_STAT   0X10\r
45 #define RK29_WDT_EOI    0X14\r
46 \r
47 #define RK29_WDT_EN     1\r
48 #define RK29_RESPONSE_MODE      1\r
49 #define RK29_RESET_PULSE    4\r
50 \r
51 //THAT wdt's clock is 24MHZ\r
52 #define RK29_WDT_2730US         0\r
53 #define RK29_WDT_5460US         1\r
54 #define RK29_WDT_10920US        2\r
55 #define RK29_WDT_21840US        3\r
56 #define RK29_WDT_43680US        4\r
57 #define RK29_WDT_87360US        5\r
58 #define RK29_WDT_174720US       6\r
59 #define RK29_WDT_349440US       7\r
60 #define RK29_WDT_698880US       8\r
61 #define RK29_WDT_1397760US      9\r
62 #define RK29_WDT_2795520US      10\r
63 #define RK29_WDT_5591040US      11\r
64 #define RK29_WDT_11182080US     12\r
65 #define RK29_WDT_22364160US     13\r
66 #define RK29_WDT_44728320US     14\r
67 #define RK29_WDT_89456640US     15\r
68 \r
69 /*\r
70 #define CONFIG_RK29_WATCHDOG_ATBOOT             (1)\r
71 #define CONFIG_RK29_WATCHDOG_DEFAULT_TIME       10      //unit second\r
72 #define CONFIG_RK29_WATCHDOG_DEBUG      1\r
73 */\r
74 \r
75 static int nowayout     = WATCHDOG_NOWAYOUT;\r
76 static int tmr_margin   = CONFIG_RK29_WATCHDOG_DEFAULT_TIME;\r
77 #ifdef CONFIG_RK29_WATCHDOG_ATBOOT\r
78 static int tmr_atboot   = 1;\r
79 #else\r
80 static int tmr_atboot   = 0;\r
81 #endif\r
82 \r
83 static int soft_noboot;\r
84 \r
85 #ifdef CONFIG_RK29_WATCHDOG_DEBUG\r
86 static int debug        = 1;\r
87 #else\r
88 static int debug        = 0;\r
89 #endif\r
90 \r
91 module_param(tmr_margin,  int, 0);\r
92 module_param(tmr_atboot,  int, 0);\r
93 module_param(nowayout,    int, 0);\r
94 module_param(soft_noboot, int, 0);\r
95 module_param(debug,       int, 0);\r
96 \r
97 MODULE_PARM_DESC(tmr_margin, "Watchdog tmr_margin in seconds. default="\r
98                 __MODULE_STRING(CONFIG_RK29_WATCHDOG_DEFAULT_TIME) ")");\r
99 MODULE_PARM_DESC(tmr_atboot,\r
100                 "Watchdog is started at boot time if set to 1, default="\r
101                         __MODULE_STRING(CONFIG_RK29_WATCHDOG_ATBOOT));\r
102 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="\r
103                         __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");\r
104 MODULE_PARM_DESC(soft_noboot, "Watchdog action, set to 1 to ignore reboots, "\r
105                         "0 to reboot (default depends on ONLY_TESTING)");\r
106 MODULE_PARM_DESC(debug, "Watchdog debug, set to >1 for debug, (default 0)");\r
107 \r
108 \r
109 static unsigned long open_lock;\r
110 static struct device    *wdt_dev;       /* platform device attached to */\r
111 static struct resource  *wdt_mem;\r
112 static struct resource  *wdt_irq;\r
113 static struct clk       *wdt_clock;\r
114 static void __iomem     *wdt_base;\r
115 static char              expect_close;\r
116 \r
117 \r
118 /* watchdog control routines */\r
119 \r
120 #define DBG(msg...) do { \\r
121         if (debug) \\r
122                 printk(KERN_INFO msg); \\r
123         } while (0)\r
124 \r
125 #define wdt_writel(v, offset) do { writel_relaxed(v, wdt_base + offset); dsb(); } while (0)\r
126 \r
127 /* functions */\r
128 void rk29_wdt_keepalive(void)\r
129 {\r
130         if (wdt_base)\r
131                 wdt_writel(0x76, RK29_WDT_CRR);\r
132 }\r
133 \r
134 static void __rk29_wdt_stop(void)\r
135 {\r
136         rk29_wdt_keepalive();    //feed dog\r
137         wdt_writel(0x0a, RK29_WDT_CR);\r
138 }\r
139 \r
140 static void rk29_wdt_stop(void)\r
141 {\r
142         __rk29_wdt_stop();\r
143         clk_disable(wdt_clock);\r
144 }\r
145 \r
146 /* timeout unit us */\r
147 static int rk29_wdt_set_heartbeat(int timeout)\r
148 {\r
149         unsigned int freq = clk_get_rate(wdt_clock);\r
150         unsigned int count;\r
151         unsigned int torr = 0;\r
152         unsigned int acc = 1;\r
153 \r
154         if (timeout < 1)\r
155                 return -EINVAL;\r
156 \r
157 //      freq /= 1000000;\r
158         count = timeout * freq;\r
159         count /= 0x10000;\r
160 \r
161         while(acc < count){\r
162                 acc *= 2;\r
163                 torr++;\r
164         }\r
165         if(torr > 15){\r
166                 torr = 15;\r
167         }\r
168         DBG("%s:%d\n", __func__, torr);\r
169         wdt_writel(torr, RK29_WDT_TORR);\r
170         return 0;\r
171 }\r
172 \r
173 static void rk29_wdt_start(void)\r
174 {\r
175         unsigned long wtcon;\r
176         clk_enable(wdt_clock);\r
177         rk29_wdt_set_heartbeat(tmr_margin);\r
178         wtcon = (RK29_WDT_EN << 0) | (RK29_RESPONSE_MODE << 1) | (RK29_RESET_PULSE << 2);\r
179         wdt_writel(wtcon, RK29_WDT_CR);\r
180 }\r
181 \r
182 /*\r
183  *      /dev/watchdog handling\r
184  */\r
185 \r
186 static int rk29_wdt_open(struct inode *inode, struct file *file)\r
187 {\r
188         DBG("%s\n", __func__);\r
189         if (test_and_set_bit(0, &open_lock))\r
190                 return -EBUSY;\r
191 \r
192         if (nowayout)\r
193                 __module_get(THIS_MODULE);\r
194 \r
195         expect_close = 0;\r
196 \r
197         /* start the timer */\r
198         rk29_wdt_start();\r
199         return nonseekable_open(inode, file);\r
200 }\r
201 \r
202 static int rk29_wdt_release(struct inode *inode, struct file *file)\r
203 {\r
204         /*\r
205          *      Shut off the timer.\r
206          *      Lock it in if it's a module and we set nowayout\r
207          */\r
208         DBG("%s\n", __func__);\r
209         if (expect_close == 42)\r
210                 rk29_wdt_stop();\r
211         else {\r
212                 dev_err(wdt_dev, "Unexpected close, not stopping watchdog\n");\r
213                 rk29_wdt_keepalive();\r
214         }\r
215         expect_close = 0;\r
216         clear_bit(0, &open_lock);\r
217         return 0;\r
218 }\r
219 \r
220 static ssize_t rk29_wdt_write(struct file *file, const char __user *data,\r
221                                 size_t len, loff_t *ppos)\r
222 {\r
223         /*\r
224          *      Refresh the timer.\r
225          */\r
226         DBG("%s\n", __func__);\r
227         if (len) {\r
228                 if (!nowayout) {\r
229                         size_t i;\r
230 \r
231                         /* In case it was set long ago */\r
232                         expect_close = 0;\r
233 \r
234                         for (i = 0; i != len; i++) {\r
235                                 char c;\r
236 \r
237                                 if (get_user(c, data + i))\r
238                                         return -EFAULT;\r
239                                 if (c == 'V')\r
240                                         expect_close = 42;\r
241                         }\r
242                 }\r
243                 rk29_wdt_keepalive();\r
244         }\r
245         return len;\r
246 }\r
247 \r
248 #define OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE)\r
249 \r
250 static const struct watchdog_info rk29_wdt_ident = {\r
251         .options          =     OPTIONS,\r
252         .firmware_version =     0,\r
253         .identity         =     "rk29 Watchdog",\r
254 };\r
255 \r
256 \r
257 static long rk29_wdt_ioctl(struct file *file,   unsigned int cmd,\r
258                                                         unsigned long arg)\r
259 {\r
260         void __user *argp = (void __user *)arg;\r
261         int __user *p = argp;\r
262         int new_margin;\r
263         DBG("%s\n", __func__);\r
264         switch (cmd) {\r
265         case WDIOC_GETSUPPORT:\r
266                 return copy_to_user(argp, &rk29_wdt_ident,\r
267                         sizeof(rk29_wdt_ident)) ? -EFAULT : 0;\r
268         case WDIOC_GETSTATUS:\r
269         case WDIOC_GETBOOTSTATUS:\r
270                 return put_user(0, p);\r
271         case WDIOC_KEEPALIVE:\r
272                 DBG("%s:rk29_wdt_keepalive\n", __func__);\r
273                 rk29_wdt_keepalive();\r
274                 return 0;\r
275         case WDIOC_SETTIMEOUT:\r
276                 if (get_user(new_margin, p))\r
277                         return -EFAULT;\r
278                 if (rk29_wdt_set_heartbeat(new_margin))\r
279                         return -EINVAL;\r
280                 rk29_wdt_keepalive();\r
281                 return put_user(tmr_margin, p);\r
282         case WDIOC_GETTIMEOUT:\r
283                 return put_user(tmr_margin, p);\r
284         default:\r
285                 return -ENOTTY;\r
286         }\r
287 }\r
288 \r
289 \r
290 \r
291 /* kernel interface */\r
292 \r
293 static const struct file_operations rk29_wdt_fops = {\r
294         .owner          = THIS_MODULE,\r
295         .llseek         = no_llseek,\r
296         .write          = rk29_wdt_write,\r
297         .unlocked_ioctl = rk29_wdt_ioctl,\r
298         .open           = rk29_wdt_open,\r
299         .release        = rk29_wdt_release,\r
300 };\r
301 \r
302 static struct miscdevice rk29_wdt_miscdev = {\r
303         .minor          = WATCHDOG_MINOR,\r
304         .name           = "watchdog",\r
305         .fops           = &rk29_wdt_fops,\r
306 };\r
307 \r
308 \r
309 /* interrupt handler code */\r
310 \r
311 static irqreturn_t rk29_wdt_irq_handler(int irqno, void *param)\r
312 {\r
313         DBG("RK29_wdt:watchdog timer expired (irq)\n");\r
314         rk29_wdt_keepalive();\r
315         return IRQ_HANDLED;\r
316 }\r
317 \r
318 \r
319 /* device interface */\r
320 \r
321 static int __devinit rk29_wdt_probe(struct platform_device *pdev)\r
322 {\r
323         struct resource *res;\r
324         struct device *dev;\r
325         int started = 0;\r
326         int ret;\r
327         int size;\r
328 \r
329         dev = &pdev->dev;\r
330         wdt_dev = &pdev->dev;\r
331 \r
332         /* get the memory region for the watchdog timer */\r
333 \r
334         res = platform_get_resource(pdev, IORESOURCE_MEM, 0);\r
335         if (res == NULL) {\r
336                 dev_err(dev, "no memory resource specified\n");\r
337                 return -ENOENT;\r
338         }\r
339 \r
340         size = (res->end - res->start) + 1;\r
341         wdt_mem = request_mem_region(res->start, size, pdev->name);\r
342         if (wdt_mem == NULL) {\r
343                 dev_err(dev, "failed to get memory region\n");\r
344                 ret = -ENOENT;\r
345                 goto err_req;\r
346         }\r
347 \r
348         wdt_base = ioremap(res->start, size);\r
349         if (wdt_base == NULL) {\r
350                 dev_err(dev, "failed to ioremap() region\n");\r
351                 ret = -EINVAL;\r
352                 goto err_req;\r
353         }\r
354 \r
355         DBG("probe: mapped wdt_base=%p\n", wdt_base);\r
356 \r
357 \r
358 #ifdef  CONFIG_RK29_FEED_DOG_BY_INTE\r
359 \r
360         wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);\r
361         if (wdt_irq == NULL) {\r
362                 dev_err(dev, "no irq resource specified\n");\r
363                 ret = -ENOENT;\r
364                 goto err_map;\r
365         }\r
366 \r
367         ret = request_irq(wdt_irq->start, rk29_wdt_irq_handler, 0, pdev->name, pdev);\r
368 \r
369         if (ret != 0) {\r
370                 dev_err(dev, "failed to install irq (%d)\n", ret);\r
371                 goto err_map;\r
372         }\r
373 \r
374 #endif\r
375 \r
376         wdt_clock = clk_get(&pdev->dev, "wdt");\r
377         if (IS_ERR(wdt_clock)) {\r
378                 dev_err(dev, "failed to find watchdog clock source\n");\r
379                 ret = PTR_ERR(wdt_clock);\r
380                 goto err_irq;\r
381         }\r
382 \r
383         ret = misc_register(&rk29_wdt_miscdev);\r
384         if (ret) {\r
385                 dev_err(dev, "cannot register miscdev on minor=%d (%d)\n",\r
386                         WATCHDOG_MINOR, ret);\r
387                 goto err_clk;\r
388         }\r
389         if (tmr_atboot && started == 0) {\r
390                 dev_info(dev, "starting watchdog timer\n");\r
391                 rk29_wdt_start();\r
392         } else if (!tmr_atboot) {\r
393                 /* if we're not enabling the watchdog, then ensure it is\r
394                  * disabled if it has been left running from the bootloader\r
395                  * or other source */\r
396 \r
397                 rk29_wdt_stop();\r
398         }\r
399 \r
400         return 0;\r
401 \r
402  err_clk:\r
403         clk_disable(wdt_clock);\r
404         clk_put(wdt_clock);\r
405 \r
406  err_irq:\r
407         free_irq(wdt_irq->start, pdev);\r
408 \r
409  err_map:\r
410         iounmap(wdt_base);\r
411 \r
412  err_req:\r
413         release_resource(wdt_mem);\r
414         kfree(wdt_mem);\r
415 \r
416         return ret;\r
417 }\r
418 \r
419 static int __devexit rk29_wdt_remove(struct platform_device *dev)\r
420 {\r
421         release_resource(wdt_mem);\r
422         kfree(wdt_mem);\r
423         wdt_mem = NULL;\r
424 \r
425         free_irq(wdt_irq->start, dev);\r
426         wdt_irq = NULL;\r
427 \r
428         clk_disable(wdt_clock);\r
429         clk_put(wdt_clock);\r
430         wdt_clock = NULL;\r
431 \r
432         iounmap(wdt_base);\r
433         misc_deregister(&rk29_wdt_miscdev);\r
434 \r
435         return 0;\r
436 }\r
437 \r
438 static void rk29_wdt_shutdown(struct platform_device *dev)\r
439 {\r
440         rk29_wdt_stop();\r
441 }\r
442 \r
443 #ifdef CONFIG_PM\r
444 \r
445 static int rk29_wdt_suspend(struct platform_device *dev, pm_message_t state)\r
446 {\r
447         rk29_wdt_stop();\r
448         return 0;\r
449 }\r
450 \r
451 static int rk29_wdt_resume(struct platform_device *dev)\r
452 {\r
453         rk29_wdt_start();\r
454         return 0;\r
455 }\r
456 \r
457 #else\r
458 #define rk29_wdt_suspend NULL\r
459 #define rk29_wdt_resume  NULL\r
460 #endif /* CONFIG_PM */\r
461 \r
462 \r
463 static struct platform_driver rk29_wdt_driver = {\r
464         .probe          = rk29_wdt_probe,\r
465         .remove         = __devexit_p(rk29_wdt_remove),\r
466         .shutdown       = rk29_wdt_shutdown,\r
467         .suspend        = rk29_wdt_suspend,\r
468         .resume         = rk29_wdt_resume,\r
469         .driver         = {\r
470                 .owner  = THIS_MODULE,\r
471                 .name   = "rk29-wdt",\r
472         },\r
473 };\r
474 \r
475 \r
476 static char banner[] __initdata =\r
477         KERN_INFO "RK29 Watchdog Timer, (c) 2011 Rockchip Electronics\n";\r
478 \r
479 static int __init watchdog_init(void)\r
480 {\r
481         printk(banner);\r
482         return platform_driver_register(&rk29_wdt_driver);\r
483 }\r
484 \r
485 static void __exit watchdog_exit(void)\r
486 {\r
487         platform_driver_unregister(&rk29_wdt_driver);\r
488 }\r
489 \r
490 subsys_initcall(watchdog_init);\r
491 module_exit(watchdog_exit);\r
492 \r
493 MODULE_AUTHOR("hhb@rock-chips.com");\r
494 MODULE_DESCRIPTION("RK29 Watchdog Device Driver");\r
495 MODULE_LICENSE("GPL");\r
496 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);\r
497 MODULE_ALIAS("platform:rk29-wdt");\r