2 * Watchdog driver for the SC8830
4 * Copyright (C) 2013 Spreadtrum
6 * based on sa1100_wdt.c and sc8830 arch reset implementation.
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.
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.
19 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
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>
27 #include <linux/of_device.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>
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>
50 #define KERNEL_ONLY_CHIP_DOG 0
51 #define KERNEL_WATCHDOG_FEEDER 1
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 */
60 static int chip_margin = 15; /* (secs) Default is 15 sec */
62 static unsigned long wdt_enabled;
64 #define wdt_feed_all() FEED_ALL_WDG(chip_margin, ap_margin, ca7_margin, ca7_irq_margin)
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)
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);
75 pr_info("Current PID %d is %s\n", task_pid_nr(current), current->comm);
76 panic("watchdog timeout interrupt happen\n");
80 static void ca7_wdg_fiq(struct fiq_glue_handler *h, void *regs, void *svc_sp)
83 outer_disable(); /* l2x0_disable */
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);
90 static struct fiq_glue_handler ca7_wdg_fiq_glue_handler = {
95 #define sprd_wdt_shutdown NULL
97 static void sprd_wdt_resume(void)
99 pr_debug("%s\n", __FUNCTION__);
107 static int sprd_wdt_suspend(void)
109 pr_debug("%s\n", __FUNCTION__);
116 static struct syscore_ops sprd_wdt_syscore_ops = {
117 .shutdown = sprd_wdt_shutdown,
118 .suspend = sprd_wdt_suspend,
119 .resume = sprd_wdt_resume,
122 #if KERNEL_WATCHDOG_FEEDER
123 #define check_first_enter() \
125 static bool first = 1; \
131 #define check_first_enter()
134 static inline int wdt_init(void)
137 if (in_calibration()) {
138 pr_info("calibration mode, quit...\n");
144 /* 0xf0 coressponding to wachdog reboot mode of bootloader */
145 sci_adi_raw_write(ANA_RST_STATUS, 0xf0);
147 register_syscore_ops(&sprd_wdt_syscore_ops);
151 static inline int wdt_exit(void)
153 if (in_calibration()) {
154 pr_info("calibration mode, quit...\n");
158 unregister_syscore_ops(&sprd_wdt_syscore_ops);
162 #if KERNEL_WATCHDOG_FEEDER
163 #define KERNEL_WATCHDOG_FEEDER_BIT 0
164 static unsigned long enabled;
165 static struct task_struct *feed_task;
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
172 static int watchdog_feeder(void *data)
175 if (kthread_should_stop())
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))
181 set_current_state(TASK_INTERRUPTIBLE);
182 schedule_timeout(feed_period * HZ);
185 pr_info(KERN_ERR "watchdog_feeder exit\n");
189 static void wdt_start(void)
192 pr_info("watchdog enable\n");
193 watchdog_start(chip_margin, ap_margin, ca7_margin, ca7_irq_margin);
195 pr_info("watchdog disable\n");
200 static int __init sci_wdt_kfeeder_init(void)
205 feed_task = kthread_create(watchdog_feeder, NULL, "watchdog_feeder");
209 feed_task = kthread_create_on_node(watchdog_feeder, NULL, cpu_to_node(cpu), "watchdog_feeder/%d", cpu);
210 kthread_bind(feed_task, cpu);
213 if (feed_task == 0) {
214 pr_err("Can't crate watchdog_feeder thread!\n");
217 set_bit(KERNEL_WATCHDOG_FEEDER_BIT, &wdt_enabled);
221 wake_up_process(feed_task);
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);
232 static void __exit sci_wdt_kfeeder_exit(void)
235 kthread_stop(feed_task);
239 int param_set_enabled(const char *val, struct kernel_param *kp)
243 ret = param_set_ulong(val, kp);
247 set_bit(KERNEL_WATCHDOG_FEEDER_BIT, &wdt_enabled);
249 clear_bit(KERNEL_WATCHDOG_FEEDER_BIT, &wdt_enabled);
251 wake_up_process(feed_task);
255 int param_set_feed_period(const char *val, struct kernel_param *kp)
259 ret = param_set_int(val, kp);
262 wake_up_process(feed_task);
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)");
274 #define SPRD_WATCHDOG_MINOR (WATCHDOG_MINOR - 1)
275 static int boot_status = 0;
277 static int sci_open(struct inode *inode, struct file *file)
279 if (test_and_set_bit(1, &wdt_enabled))
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);
285 watchdog_start(chip_margin, ap_margin, ca7_margin, ca7_irq_margin);
287 return nonseekable_open(inode, file);
290 static int sci_release(struct inode *inode, struct file *file)
292 clear_bit(1, &wdt_enabled);
297 static ssize_t sci_write(struct file *file, const char __user *data, size_t len,
305 static const struct watchdog_info ident = {
306 .options = WDIOF_CARDRESET | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
307 .identity = "SC8830 Watchdog",
308 .firmware_version = 1,
311 static long sci_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
315 void __user *argp = (void __user *) arg;
316 int __user *p = argp;
319 case WDIOC_GETSUPPORT:
320 ret = copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
323 case WDIOC_GETSTATUS:
324 ret = put_user(0, p);
327 case WDIOC_GETBOOTSTATUS:
328 ret = put_user(boot_status, p);
331 case WDIOC_KEEPALIVE:
336 case WDIOC_SETTIMEOUT:
337 ret = get_user(time, p);
341 if (time <= 0 || (WDG_CLK * (long long) time >= 0xffffffff)) {
346 ca7_irq_margin = time;
349 case WDIOC_GETTIMEOUT:
350 ret = put_user(ca7_irq_margin, p);
356 static const struct file_operations sci_fops =
358 .owner = THIS_MODULE,
361 .unlocked_ioctl = sci_ioctl,
363 .release = sci_release,
366 static struct miscdevice sci_miscdev =
368 .minor = SPRD_WATCHDOG_MINOR,
369 .name = "sprd-watchdog",
373 static int sprd_wdt_probe(struct platform_device *pdev)
375 struct resource *ap_res,*a7_res;
379 ret = misc_register(&sci_miscdev);
382 misc_deregister(&sci_miscdev);
385 ap_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
388 dev_err(&pdev->dev, "sprd_wdt get base resource failed!\n");
391 ap_wdt_addr = (unsigned long)devm_ioremap_nocache(&pdev->dev, ap_res->start,resource_size(ap_res));
394 dev_err(&pdev->dev, "sprd_wdt get base address failed!\n");
398 a7_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
400 dev_err(&pdev->dev, "sprd_wdt get io resource failed!\n");
403 a7_wdt_addr = (unsigned long)devm_ioremap_nocache(&pdev->dev, a7_res->start,resource_size(a7_res));
406 dev_err(&pdev->dev, "sprd_wdt get base address failed!\n");
410 irq_num = platform_get_irq(pdev, 0);
412 dev_err(&pdev->dev, "no IRQ resource info\n");
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);
419 pr_info("sprd wdg isr register failed");
423 ret = fiq_glue_register_handler(&ca7_wdg_fiq_glue_handler);
427 pr_err("<%s> fiq_glue_register_handler failed %d!\n", __func__, ret);
430 sci_wdt_kfeeder_init();
437 static int __exit sprd_wdt_remove(struct platform_device *pdev)
439 misc_deregister(&sci_miscdev);
443 static const struct of_device_id sprd_wdt_match_table[] = {
444 { .compatible = "sprd,sprd-wdt", },
448 static struct platform_driver sprd_wdt_driver = {
449 .probe = sprd_wdt_probe,
450 .remove = __exit_p(sprd_wdt_remove),
453 .owner = THIS_MODULE,
454 .of_match_table = of_match_ptr(sprd_wdt_match_table),
459 static int __init sci_wdt_init(void)
467 pr_info("SC8830 Watchdog: userspace watchdog feeder\n");
469 platform_driver_register(&sprd_wdt_driver);
477 static void __exit sci_wdt_exit(void)
480 platform_driver_unregister(&sprd_wdt_driver);
483 module_init(sci_wdt_init);
484 module_exit(sci_wdt_exit);
486 MODULE_ALIAS_MISCDEV(SPRD_WATCHDOG_MINOR);
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)");
497 MODULE_DESCRIPTION("SPRD Watchdog");
498 MODULE_LICENSE("GPL");