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