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