tizen 2.4 release
[profile/mobile/platform/kernel/linux-3.10-sc7730.git] / drivers / watchdog / sprd_wdt_sys.c
1 /*
2  * Watchdog driver for the SC8830
3  *
4  * Copyright (C) 2013 Spreadtrum
5  *
6  * based on sa1100_wdt.c and sc8830 arch reset implementation.
7  *
8  * This software is licensed under the terms of the GNU General Public
9  * License version 2, as published by the Free Software Foundation, and
10  * may be copied, distributed, and modified under those terms.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  */
18
19 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
20
21 #include <linux/module.h>
22 #include <linux/moduleparam.h>
23 #include <linux/types.h>
24 #include <linux/kernel.h>
25 #include <linux/module.h>
26 #include <linux/of.h>
27 #include <linux/of_device.h>
28 #include <linux/fs.h>
29 #include <linux/device.h>
30 #include <linux/miscdevice.h>
31 #include <linux/watchdog.h>
32 #include <linux/init.h>
33 #include <linux/uaccess.h>
34 #include <linux/timex.h>
35 #include <linux/syscore_ops.h>
36 #include <linux/kthread.h>
37 #include <linux/interrupt.h>
38 #include <linux/sysrq.h>
39 #include <linux/delay.h>
40 #include <linux/irq.h>
41 #include <soc/sprd/sys_reset.h>
42 #include <linux/io.h>
43 #include <asm/cacheflush.h>
44 #include <linux/platform_device.h>
45 #include "sprd_wdt_sys.h"
46 #ifdef CONFIG_SPRD_WATCHDOG_SYS_FIQ
47 #include <asm/fiq_glue.h>
48 #include <asm/fiq.h>
49 #endif
50 #define KERNEL_ONLY_CHIP_DOG 0
51 #define KERNEL_WATCHDOG_FEEDER 1
52
53 static int feed_period = 3; /* (secs) Default is 3 sec */
54 static int ca7_irq_margin = 9; /* (secs) Default is 9 sec */
55 static int ca7_margin = 10; /* (secs) Default is 10 sec */
56 static int ap_margin = 12; /* (secs) Default is 12 sec */
57 #if KERNEL_ONLY_CHIP_DOG
58 static int chip_margin = 8; /* (secs) Default is 8 sec */
59 #else
60 static int chip_margin = 15; /* (secs) Default is 15 sec */
61 #endif
62 static unsigned long wdt_enabled;
63
64 #define wdt_feed_all() FEED_ALL_WDG(chip_margin, ap_margin, ca7_margin, ca7_irq_margin)
65
66 extern int in_calibration(void);
67 #ifndef CONFIG_SPRD_WATCHDOG_SYS_FIQ
68 static irqreturn_t ca7_wdg_isr(int irq, void *dev_id)
69 {
70         pr_debug("%s\n", __func__);
71         pr_info("watchdog timeout interrupt happen\n");
72         sci_glb_set(AP_WDG_INT_CLR, WDG_INT_CLEAR_BIT);
73         handle_sysrq('m');
74         handle_sysrq('w');
75         pr_info("Current PID %d is %s\n", task_pid_nr(current), current->comm);
76         panic("watchdog timeout interrupt happen\n");
77         return IRQ_HANDLED;
78 }
79 #else
80 static void ca7_wdg_fiq(struct fiq_glue_handler *h, void *regs, void *svc_sp)
81 {
82         flush_cache_all();
83         outer_disable(); /* l2x0_disable */
84         oops_in_progress = 1;
85         mdelay(50); /* wait for other application processor finish printk */
86         arch_trigger_all_cpu_backtrace();
87         panic("Hardware Watchdog interrupt barks on <cpu%d>\n", smp_processor_id());
88         __raw_writel(WDG_INT_CLEAR_BIT, CA7_WDG_INT_CLR);
89 }
90 static struct fiq_glue_handler ca7_wdg_fiq_glue_handler = {
91         .fiq = ca7_wdg_fiq,
92 };
93 #endif
94
95 #define sprd_wdt_shutdown NULL
96
97 static void sprd_wdt_resume(void)
98 {
99         pr_debug("%s\n", __FUNCTION__);
100         if (!wdt_enabled)
101                 return;
102         wdt_feed_all();
103         ENABLE_ALL_WDG();
104         return;
105 }
106
107 static int sprd_wdt_suspend(void)
108 {
109         pr_debug("%s\n", __FUNCTION__);
110         if (!wdt_enabled)
111                 return 0;
112         DISABLE_ALL_WDG();
113         return 0;
114 }
115
116 static struct syscore_ops sprd_wdt_syscore_ops = {
117         .shutdown   = sprd_wdt_shutdown,
118         .suspend    = sprd_wdt_suspend,
119         .resume     = sprd_wdt_resume,
120 };
121
122 #if KERNEL_WATCHDOG_FEEDER
123 #define check_first_enter() \
124 do { \
125         static bool first = 1; \
126         if (first == 0) \
127                 return 0; \
128         first = 0; \
129 } while (0)
130 #else
131 #define check_first_enter()
132 #endif
133
134 static inline int wdt_init(void)
135 {
136         int ret;
137         if (in_calibration()) {
138                 pr_info("calibration mode, quit...\n");
139                 return -1;
140         }
141
142         check_first_enter();
143
144         /* 0xf0 coressponding to wachdog reboot mode of bootloader */
145         sci_adi_raw_write(ANA_RST_STATUS, 0xf0);
146
147         register_syscore_ops(&sprd_wdt_syscore_ops);
148         return ret;
149 }
150
151 static inline int wdt_exit(void)
152 {
153         if (in_calibration()) {
154                 pr_info("calibration mode, quit...\n");
155                 return 0;
156         }
157         check_first_enter();
158         unregister_syscore_ops(&sprd_wdt_syscore_ops);
159         return 0;
160 }
161
162 #if KERNEL_WATCHDOG_FEEDER
163 #define KERNEL_WATCHDOG_FEEDER_BIT 0
164 static unsigned long enabled;
165 static struct task_struct *feed_task;
166 /*
167  * This is a hw watchdog starts after init, and fed by a thread started
168  * by the watchdog driver itself. It can also be fed from userland.
169  * The watchdog will be stopped in system suspend, and restarted in
170  * system resume.
171  */
172 static int watchdog_feeder(void *data)
173 {
174         do {
175                 if (kthread_should_stop())
176                         break;
177                 pr_debug("%s, ca7_margin=%d, ap_margain=%d, chip_margin=%d, feed_period=%d, wdt_enabled=%ld\n",
178                         __FUNCTION__, ca7_margin, ap_margin, chip_margin, feed_period, wdt_enabled);
179                 if (wdt_enabled & (1 << KERNEL_WATCHDOG_FEEDER_BIT))
180                         wdt_feed_all();
181                 set_current_state(TASK_INTERRUPTIBLE);
182                 schedule_timeout(feed_period * HZ);
183         } while(1);
184
185         pr_info(KERN_ERR "watchdog_feeder exit\n");
186         return 0;
187 }
188
189 static void wdt_start(void)
190 {
191         if (wdt_enabled) {
192                 pr_info("watchdog enable\n");
193                 watchdog_start(chip_margin, ap_margin, ca7_margin, ca7_irq_margin);
194         } else {
195                 pr_info("watchdog disable\n");
196                 DISABLE_ALL_WDG();
197         }
198 }
199
200 static int __init sci_wdt_kfeeder_init(void)
201 {
202
203
204 #if 0
205         feed_task = kthread_create(watchdog_feeder, NULL, "watchdog_feeder");
206 #else
207         do {
208                 int cpu = 0;
209                 feed_task = kthread_create_on_node(watchdog_feeder, NULL, cpu_to_node(cpu), "watchdog_feeder/%d", cpu);
210                 kthread_bind(feed_task, cpu);
211         } while (0);
212 #endif
213         if (feed_task == 0) {
214                 pr_err("Can't crate watchdog_feeder thread!\n");
215         } else {
216                 #if 1
217                 set_bit(KERNEL_WATCHDOG_FEEDER_BIT, &wdt_enabled);
218                 enabled = 1;
219                 #endif
220                 wdt_start();
221                 wake_up_process(feed_task);
222         }
223
224         pr_info("SC8830 Watchdog: chip margin %d sec\n", chip_margin);
225         pr_info("SC8830 Watchdog: ap margin %d sec\n", ap_margin);
226         pr_info("SC8830 Watchdog: ca7 margin %d sec\n", ca7_margin);
227         pr_info("SC8830 Watchdog: ca7 irq margin %d sec\n", ca7_irq_margin);
228
229         return 0;
230 }
231
232 static void __exit sci_wdt_kfeeder_exit(void)
233 {
234         wdt_exit();
235         kthread_stop(feed_task);
236 }
237
238
239 int param_set_enabled(const char *val, struct kernel_param *kp)
240 {
241         int ret;
242
243         ret = param_set_ulong(val, kp);
244         if (ret < 0)
245                 return ret;
246         if (enabled)
247                 set_bit(KERNEL_WATCHDOG_FEEDER_BIT, &wdt_enabled);
248         else
249                 clear_bit(KERNEL_WATCHDOG_FEEDER_BIT, &wdt_enabled);
250         wdt_start();
251         wake_up_process(feed_task);
252         return ret;
253 }
254
255 int param_set_feed_period(const char *val, struct kernel_param *kp)
256 {
257         int ret;
258
259         ret = param_set_int(val, kp);
260         if (ret < 0)
261                 return ret;
262         wake_up_process(feed_task);
263         return ret;
264 }
265
266 module_param_call(enabled, param_set_enabled, param_get_ulong,
267                                         &enabled, S_IWUSR | S_IRUGO);
268 MODULE_PARM_DESC(enabled, "Enable kernel watchdog task to feed dog");
269 module_param_call(feed_period, param_set_feed_period, param_get_int,
270                                         &feed_period, S_IWUSR | S_IRUGO);
271 MODULE_PARM_DESC(feed_period, "Watchdog feed period in seconds (default 3s)");
272 #endif
273
274 #define SPRD_WATCHDOG_MINOR (WATCHDOG_MINOR - 1)
275 static int boot_status = 0;
276
277 static int sci_open(struct inode *inode, struct file *file)
278 {
279         if (test_and_set_bit(1, &wdt_enabled))
280                 return -EBUSY;
281
282         pr_debug("%s, ca7_margin=%d, ca7_irq_margin=%d, ap_margain=%d, chip_margin=%d, feed_period=%d\n", __FUNCTION__,
283                         ca7_margin,     ca7_irq_margin, ap_margin, chip_margin, feed_period);
284
285         watchdog_start(chip_margin, ap_margin, ca7_margin, ca7_irq_margin);
286
287         return nonseekable_open(inode, file);
288 }
289
290 static int sci_release(struct inode *inode, struct file *file)
291 {
292         clear_bit(1, &wdt_enabled);
293         DISABLE_ALL_WDG();
294         return 0;
295 }
296
297 static ssize_t sci_write(struct file *file, const char __user *data, size_t len,
298                 loff_t *ppos)
299 {
300         if (len)
301                 wdt_feed_all();
302         return len;
303 }
304
305 static const struct watchdog_info ident = {
306         .options = WDIOF_CARDRESET | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
307         .identity = "SC8830 Watchdog",
308         .firmware_version = 1,
309 };
310
311 static long sci_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
312 {
313         int ret = -ENOTTY;
314         int time;
315         void __user *argp = (void __user *) arg;
316         int __user *p = argp;
317
318         switch (cmd) {
319         case WDIOC_GETSUPPORT:
320                 ret = copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
321                 break;
322
323         case WDIOC_GETSTATUS:
324                 ret = put_user(0, p);
325                 break;
326
327         case WDIOC_GETBOOTSTATUS:
328                 ret = put_user(boot_status, p);
329                 break;
330
331         case WDIOC_KEEPALIVE:
332                 wdt_feed_all();
333                 ret = 0;
334                 break;
335
336         case WDIOC_SETTIMEOUT:
337                 ret = get_user(time, p);
338                 if (ret)
339                         break;
340
341                 if (time <= 0 || (WDG_CLK * (long long) time >= 0xffffffff)) {
342                         ret = -EINVAL;
343                         break;
344                 }
345
346                 ca7_irq_margin = time;
347                 wdt_feed_all();
348                 /*fall through*/
349         case WDIOC_GETTIMEOUT:
350                 ret = put_user(ca7_irq_margin, p);
351                 break;
352         }
353         return ret;
354 }
355
356 static const struct file_operations sci_fops =
357 {
358         .owner = THIS_MODULE,
359         .llseek = no_llseek,
360         .write = sci_write,
361         .unlocked_ioctl = sci_ioctl,
362         .open = sci_open,
363         .release = sci_release,
364 };
365
366 static struct miscdevice sci_miscdev =
367 {
368         .minor = SPRD_WATCHDOG_MINOR,
369         .name = "sprd-watchdog",
370         .fops = &sci_fops,
371 };
372
373 static int sprd_wdt_probe(struct platform_device *pdev)
374 {
375         struct resource *ap_res,*a7_res;
376         int ret = 0;
377         int irq_num = 0;
378
379         ret = misc_register(&sci_miscdev);
380         if (ret != 0)
381         {
382                 misc_deregister(&sci_miscdev);
383                 return -ENODEV;
384         }
385         ap_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
386         if (!ap_res)
387         {
388                 dev_err(&pdev->dev, "sprd_wdt get base resource failed!\n");
389                 return -ENODEV;
390         }
391         ap_wdt_addr = (unsigned long)devm_ioremap_nocache(&pdev->dev, ap_res->start,resource_size(ap_res));
392         if (!ap_wdt_addr)
393         {
394                 dev_err(&pdev->dev, "sprd_wdt get base address failed!\n");
395                 return -EBUSY;
396         }
397
398         a7_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
399         if (!a7_res) {
400                 dev_err(&pdev->dev, "sprd_wdt get io resource failed!\n");
401                 return -ENODEV;
402         }
403         a7_wdt_addr = (unsigned long)devm_ioremap_nocache(&pdev->dev, a7_res->start,resource_size(a7_res));
404         if (!a7_wdt_addr)
405         {
406                 dev_err(&pdev->dev, "sprd_wdt get base address failed!\n");
407                 return -EBUSY;
408         }
409
410         irq_num = platform_get_irq(pdev, 0);
411         if (irq_num < 0) {
412                 dev_err(&pdev->dev, "no IRQ resource info\n");
413                 return -EIO;
414         }
415 #ifndef CONFIG_SPRD_WATCHDOG_SYS_FIQ
416         ret = devm_request_irq(&pdev->dev, irq_num, ca7_wdg_isr, IRQF_NO_SUSPEND , "sprd_wdg", NULL);
417         if (ret)
418         {
419                 pr_info("sprd wdg isr register failed");
420                 BUG();
421         }
422 #else
423         ret = fiq_glue_register_handler(&ca7_wdg_fiq_glue_handler);
424         if (ret == 0)
425                 enable_fiq(irq_num);
426         else
427                 pr_err("<%s> fiq_glue_register_handler failed %d!\n", __func__, ret);
428 #endif
429
430         sci_wdt_kfeeder_init();
431
432         return ret;
433 }
434
435
436
437 static int __exit sprd_wdt_remove(struct platform_device *pdev)
438 {
439         misc_deregister(&sci_miscdev);
440         return 0;
441 }
442
443 static const struct of_device_id sprd_wdt_match_table[] = {
444         { .compatible = "sprd,sprd-wdt", },
445         { /* sentinel */ }
446 };
447
448 static struct platform_driver sprd_wdt_driver = {
449         .probe          = sprd_wdt_probe,
450         .remove                 = __exit_p(sprd_wdt_remove),
451         .driver         = {
452                 .name   = "sprd-wdt",
453                 .owner  = THIS_MODULE,
454                 .of_match_table = of_match_ptr(sprd_wdt_match_table),
455         },
456 };
457
458
459 static int __init sci_wdt_init(void)
460 {
461         int ret = 0;
462
463         if (wdt_init())
464                 goto wdt_out;
465
466         boot_status = 0;
467         pr_info("SC8830 Watchdog: userspace watchdog feeder\n");
468
469          platform_driver_register(&sprd_wdt_driver);
470
471         return 0;
472 wdt_out:
473         wdt_exit();
474         return ret;
475 }
476
477 static void __exit sci_wdt_exit(void)
478 {
479         wdt_exit();
480         platform_driver_unregister(&sprd_wdt_driver);
481 }
482
483 module_init(sci_wdt_init);
484 module_exit(sci_wdt_exit);
485
486 MODULE_ALIAS_MISCDEV(SPRD_WATCHDOG_MINOR);
487
488 module_param(chip_margin, int, S_IWUSR | S_IRUGO);
489 MODULE_PARM_DESC(chip_margin, "Chip Watchdog margin in seconds (default 15s)");
490 module_param(ca7_margin, int, S_IWUSR | S_IRUGO);
491 MODULE_PARM_DESC(ca7_margin, "CA7 Watchdog margin in seconds (default 10s)");
492 module_param(ca7_irq_margin, int, S_IWUSR | S_IRUGO);
493 MODULE_PARM_DESC(ca7_irq_margin, "CA7 Watchdog irq margin in seconds (default 9s)");
494 module_param(ap_margin, int, S_IWUSR | S_IRUGO);
495 MODULE_PARM_DESC(ap_margin, "AP Watchdog margin in seconds (default 12s)");
496
497 MODULE_DESCRIPTION("SPRD Watchdog");
498 MODULE_LICENSE("GPL");