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