cbafbd3b6033a867754edca17b0c5d215b1e224f
[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 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 second */\r
147 int rk29_wdt_set_heartbeat(int timeout)\r
148 {\r
149         unsigned int freq = clk_get_rate(wdt_clock);\r
150         unsigned int long 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         count = timeout * freq;\r
158         count /= 0x10000;\r
159 \r
160         while(acc < count){\r
161                 acc *= 2;\r
162                 torr++;\r
163         }\r
164         if(torr > 15){\r
165                 torr = 15;\r
166         }\r
167         DBG("%s:%d\n", __func__, torr);\r
168         wdt_writel(torr, RK29_WDT_TORR);\r
169         return 0;\r
170 }\r
171 \r
172 void rk29_wdt_start(void)\r
173 {\r
174         unsigned long wtcon;\r
175         clk_enable(wdt_clock);\r
176         rk29_wdt_set_heartbeat(tmr_margin);\r
177         wtcon = (RK29_WDT_EN << 0) | (RK29_RESPONSE_MODE << 1) | (RK29_RESET_PULSE << 2);\r
178         wdt_writel(wtcon, RK29_WDT_CR);\r
179 }\r
180 \r
181 /*\r
182  *      /dev/watchdog handling\r
183  */\r
184 \r
185 static int rk29_wdt_open(struct inode *inode, struct file *file)\r
186 {\r
187         DBG("%s\n", __func__);\r
188         if (test_and_set_bit(0, &open_lock))\r
189                 return -EBUSY;\r
190 \r
191         if (nowayout)\r
192                 __module_get(THIS_MODULE);\r
193 \r
194         expect_close = 0;\r
195 \r
196         /* start the timer */\r
197         rk29_wdt_start();\r
198         return nonseekable_open(inode, file);\r
199 }\r
200 \r
201 static int rk29_wdt_release(struct inode *inode, struct file *file)\r
202 {\r
203         /*\r
204          *      Shut off the timer.\r
205          *      Lock it in if it's a module and we set nowayout\r
206          */\r
207         DBG("%s\n", __func__);\r
208         if (expect_close == 42)\r
209                 rk29_wdt_stop();\r
210         else {\r
211                 dev_err(wdt_dev, "Unexpected close, not stopping watchdog\n");\r
212                 rk29_wdt_keepalive();\r
213         }\r
214         expect_close = 0;\r
215         clear_bit(0, &open_lock);\r
216         return 0;\r
217 }\r
218 \r
219 static ssize_t rk29_wdt_write(struct file *file, const char __user *data,\r
220                                 size_t len, loff_t *ppos)\r
221 {\r
222         /*\r
223          *      Refresh the timer.\r
224          */\r
225         DBG("%s\n", __func__);\r
226         if (len) {\r
227                 if (!nowayout) {\r
228                         size_t i;\r
229 \r
230                         /* In case it was set long ago */\r
231                         expect_close = 0;\r
232 \r
233                         for (i = 0; i != len; i++) {\r
234                                 char c;\r
235 \r
236                                 if (get_user(c, data + i))\r
237                                         return -EFAULT;\r
238                                 if (c == 'V')\r
239                                         expect_close = 42;\r
240                         }\r
241                 }\r
242                 rk29_wdt_keepalive();\r
243         }\r
244         return len;\r
245 }\r
246 \r
247 #define OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE)\r
248 \r
249 static const struct watchdog_info rk29_wdt_ident = {\r
250         .options          =     OPTIONS,\r
251         .firmware_version =     0,\r
252         .identity         =     "rk29 Watchdog",\r
253 };\r
254 \r
255 \r
256 static long rk29_wdt_ioctl(struct file *file,   unsigned int cmd,\r
257                                                         unsigned long arg)\r
258 {\r
259         void __user *argp = (void __user *)arg;\r
260         int __user *p = argp;\r
261         int new_margin;\r
262         DBG("%s\n", __func__);\r
263         switch (cmd) {\r
264         case WDIOC_GETSUPPORT:\r
265                 return copy_to_user(argp, &rk29_wdt_ident,\r
266                         sizeof(rk29_wdt_ident)) ? -EFAULT : 0;\r
267         case WDIOC_GETSTATUS:\r
268         case WDIOC_GETBOOTSTATUS:\r
269                 return put_user(0, p);\r
270         case WDIOC_KEEPALIVE:\r
271                 DBG("%s:rk29_wdt_keepalive\n", __func__);\r
272                 rk29_wdt_keepalive();\r
273                 return 0;\r
274         case WDIOC_SETTIMEOUT:\r
275                 if (get_user(new_margin, p))\r
276                         return -EFAULT;\r
277                 if (rk29_wdt_set_heartbeat(new_margin))\r
278                         return -EINVAL;\r
279                 rk29_wdt_keepalive();\r
280                 return put_user(tmr_margin, p);\r
281         case WDIOC_GETTIMEOUT:\r
282                 return put_user(tmr_margin, p);\r
283         default:\r
284                 return -ENOTTY;\r
285         }\r
286 }\r
287 \r
288 \r
289 \r
290 /* kernel interface */\r
291 \r
292 static const struct file_operations rk29_wdt_fops = {\r
293         .owner          = THIS_MODULE,\r
294         .llseek         = no_llseek,\r
295         .write          = rk29_wdt_write,\r
296         .unlocked_ioctl = rk29_wdt_ioctl,\r
297         .open           = rk29_wdt_open,\r
298         .release        = rk29_wdt_release,\r
299 };\r
300 \r
301 static struct miscdevice rk29_wdt_miscdev = {\r
302         .minor          = WATCHDOG_MINOR,\r
303         .name           = "watchdog",\r
304         .fops           = &rk29_wdt_fops,\r
305 };\r
306 \r
307 \r
308 /* interrupt handler code */\r
309 \r
310 static irqreturn_t rk29_wdt_irq_handler(int irqno, void *param)\r
311 {\r
312         DBG("RK29_wdt:watchdog timer expired (irq)\n");\r
313         rk29_wdt_keepalive();\r
314         return IRQ_HANDLED;\r
315 }\r
316 \r
317 \r
318 /* device interface */\r
319 \r
320 static int __devinit rk29_wdt_probe(struct platform_device *pdev)\r
321 {\r
322         struct resource *res;\r
323         struct device *dev;\r
324         int started = 0;\r
325         int ret;\r
326         int size;\r
327 \r
328         dev = &pdev->dev;\r
329         wdt_dev = &pdev->dev;\r
330 \r
331         /* get the memory region for the watchdog timer */\r
332 \r
333         res = platform_get_resource(pdev, IORESOURCE_MEM, 0);\r
334         if (res == NULL) {\r
335                 dev_err(dev, "no memory resource specified\n");\r
336                 return -ENOENT;\r
337         }\r
338 \r
339         size = (res->end - res->start) + 1;\r
340         wdt_mem = request_mem_region(res->start, size, pdev->name);\r
341         if (wdt_mem == NULL) {\r
342                 dev_err(dev, "failed to get memory region\n");\r
343                 ret = -ENOENT;\r
344                 goto err_req;\r
345         }\r
346 \r
347         wdt_base = ioremap(res->start, size);\r
348         if (wdt_base == NULL) {\r
349                 dev_err(dev, "failed to ioremap() region\n");\r
350                 ret = -EINVAL;\r
351                 goto err_req;\r
352         }\r
353 \r
354         DBG("probe: mapped wdt_base=%p\n", wdt_base);\r
355 \r
356 \r
357 #ifdef  CONFIG_RK29_FEED_DOG_BY_INTE\r
358 \r
359         wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);\r
360         if (wdt_irq == NULL) {\r
361                 dev_err(dev, "no irq resource specified\n");\r
362                 ret = -ENOENT;\r
363                 goto err_map;\r
364         }\r
365 \r
366         ret = request_irq(wdt_irq->start, rk29_wdt_irq_handler, 0, pdev->name, pdev);\r
367 \r
368         if (ret != 0) {\r
369                 dev_err(dev, "failed to install irq (%d)\n", ret);\r
370                 goto err_map;\r
371         }\r
372 \r
373 #endif\r
374 \r
375         wdt_clock = clk_get(&pdev->dev, "wdt");\r
376         if (IS_ERR(wdt_clock)) {\r
377                 wdt_clock = clk_get(&pdev->dev, "pclk_wdt");\r
378         }\r
379         if (IS_ERR(wdt_clock)) {\r
380                 dev_err(dev, "failed to find watchdog clock source\n");\r
381                 ret = PTR_ERR(wdt_clock);\r
382                 goto err_irq;\r
383         }\r
384 \r
385         ret = misc_register(&rk29_wdt_miscdev);\r
386         if (ret) {\r
387                 dev_err(dev, "cannot register miscdev on minor=%d (%d)\n",\r
388                         WATCHDOG_MINOR, ret);\r
389                 goto err_clk;\r
390         }\r
391         if (tmr_atboot && started == 0) {\r
392                 dev_info(dev, "starting watchdog timer\n");\r
393                 rk29_wdt_start();\r
394         } else if (!tmr_atboot) {\r
395                 /* if we're not enabling the watchdog, then ensure it is\r
396                  * disabled if it has been left running from the bootloader\r
397                  * or other source */\r
398 \r
399                 rk29_wdt_stop();\r
400         }\r
401 \r
402         return 0;\r
403 \r
404  err_clk:\r
405         clk_disable(wdt_clock);\r
406         clk_put(wdt_clock);\r
407 \r
408  err_irq:\r
409         free_irq(wdt_irq->start, pdev);\r
410 \r
411  err_map:\r
412         iounmap(wdt_base);\r
413 \r
414  err_req:\r
415         release_resource(wdt_mem);\r
416         kfree(wdt_mem);\r
417 \r
418         return ret;\r
419 }\r
420 \r
421 static int __devexit rk29_wdt_remove(struct platform_device *dev)\r
422 {\r
423         release_resource(wdt_mem);\r
424         kfree(wdt_mem);\r
425         wdt_mem = NULL;\r
426 \r
427         free_irq(wdt_irq->start, dev);\r
428         wdt_irq = NULL;\r
429 \r
430         clk_disable(wdt_clock);\r
431         clk_put(wdt_clock);\r
432         wdt_clock = NULL;\r
433 \r
434         iounmap(wdt_base);\r
435         misc_deregister(&rk29_wdt_miscdev);\r
436 \r
437         return 0;\r
438 }\r
439 \r
440 static void rk29_wdt_shutdown(struct platform_device *dev)\r
441 {\r
442         rk29_wdt_stop();\r
443 }\r
444 \r
445 #ifdef CONFIG_PM\r
446 \r
447 static int rk29_wdt_suspend(struct platform_device *dev, pm_message_t state)\r
448 {\r
449         rk29_wdt_stop();\r
450         return 0;\r
451 }\r
452 \r
453 static int rk29_wdt_resume(struct platform_device *dev)\r
454 {\r
455         rk29_wdt_start();\r
456         return 0;\r
457 }\r
458 \r
459 #else\r
460 #define rk29_wdt_suspend NULL\r
461 #define rk29_wdt_resume  NULL\r
462 #endif /* CONFIG_PM */\r
463 \r
464 \r
465 static struct platform_driver rk29_wdt_driver = {\r
466         .probe          = rk29_wdt_probe,\r
467         .remove         = __devexit_p(rk29_wdt_remove),\r
468         .shutdown       = rk29_wdt_shutdown,\r
469         .suspend        = rk29_wdt_suspend,\r
470         .resume         = rk29_wdt_resume,\r
471         .driver         = {\r
472                 .owner  = THIS_MODULE,\r
473                 .name   = "rk29-wdt",\r
474         },\r
475 };\r
476 \r
477 \r
478 static char banner[] __initdata =\r
479         KERN_INFO "RK29 Watchdog Timer, (c) 2011 Rockchip Electronics\n";\r
480 \r
481 static int __init watchdog_init(void)\r
482 {\r
483         printk(banner);\r
484         return platform_driver_register(&rk29_wdt_driver);\r
485 }\r
486 \r
487 static void __exit watchdog_exit(void)\r
488 {\r
489         platform_driver_unregister(&rk29_wdt_driver);\r
490 }\r
491 \r
492 subsys_initcall(watchdog_init);\r
493 module_exit(watchdog_exit);\r
494 \r
495 MODULE_AUTHOR("hhb@rock-chips.com");\r
496 MODULE_DESCRIPTION("RK29 Watchdog Device Driver");\r
497 MODULE_LICENSE("GPL");\r
498 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);\r
499 MODULE_ALIAS("platform:rk29-wdt");\r