Initial Contribution

msm-2.6.38: tag AU_LINUX_ANDROID_GINGERBREAD.02.03.04.00.142

Signed-off-by: Bryan Huntsman <bryanh@codeaurora.org>
diff --git a/arch/arm/mach-msm/msm_watchdog.c b/arch/arm/mach-msm/msm_watchdog.c
new file mode 100644
index 0000000..40fa37e
--- /dev/null
+++ b/arch/arm/mach-msm/msm_watchdog.c
@@ -0,0 +1,340 @@
+/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/workqueue.h>
+#include <linux/pm.h>
+#include <linux/mfd/pmic8058.h>
+#include <linux/jiffies.h>
+#include <linux/suspend.h>
+#include <linux/interrupt.h>
+#include <mach/msm_iomap.h>
+#include <asm/mach-types.h>
+#include <mach/scm-io.h>
+#include <mach/scm.h>
+#include "msm_watchdog.h"
+
+#define TCSR_WDT_CFG 0x30
+
+#define WDT0_RST	(MSM_TMR0_BASE + 0x38)
+#define WDT0_EN		(MSM_TMR0_BASE + 0x40)
+#define WDT0_BARK_TIME	(MSM_TMR0_BASE + 0x4C)
+#define WDT0_BITE_TIME	(MSM_TMR0_BASE + 0x5C)
+
+/* Watchdog pet interval in ms */
+#define PET_DELAY 3000
+static unsigned long delay_time;
+static unsigned long long last_pet;
+
+/*
+ * On the kernel command line specify
+ * msm_watchdog.enable=1 to enable the watchdog
+ * By default watchdog is turned on
+ */
+static int enable = 1;
+module_param(enable, int, 0);
+
+/*
+ * If the watchdog is enabled at bootup (enable=1),
+ * the runtime_disable sysfs node at
+ * /sys/module/msm_watchdog/runtime_disable
+ * can be used to deactivate the watchdog.
+ * This is a one-time setting. The watchdog
+ * cannot be re-enabled once it is disabled.
+ */
+static int runtime_disable;
+static DEFINE_MUTEX(disable_lock);
+static int wdog_enable_set(const char *val, struct kernel_param *kp);
+module_param_call(runtime_disable, wdog_enable_set, param_get_int,
+			&runtime_disable, 0644);
+
+/*
+ * On the kernel command line specify msm_watchdog.appsbark=1 to handle
+ * watchdog barks in Linux. By default barks are processed by the secure side.
+ */
+static int appsbark;
+module_param(appsbark, int, 0);
+
+/*
+ * Use /sys/module/msm_watchdog/parameters/print_all_stacks
+ * to control whether stacks of all running
+ * processes are printed when a wdog bark is received.
+ */
+static int print_all_stacks = 1;
+module_param(print_all_stacks, int,  S_IRUGO | S_IWUSR);
+
+/* Area for context dump in secure mode */
+static void *scm_regsave;
+
+static void pet_watchdog_work(struct work_struct *work);
+static void init_watchdog_work(struct work_struct *work);
+static DECLARE_DELAYED_WORK(dogwork_struct, pet_watchdog_work);
+static DECLARE_WORK(init_dogwork_struct, init_watchdog_work);
+
+static int msm_watchdog_suspend(void)
+{
+	__raw_writel(1, WDT0_RST);
+	__raw_writel(0, WDT0_EN);
+	mb();
+	return NOTIFY_DONE;
+}
+static int msm_watchdog_resume(void)
+{
+	__raw_writel(1, WDT0_EN);
+	__raw_writel(1, WDT0_RST);
+	return NOTIFY_DONE;
+}
+
+static int msm_watchdog_power_event(struct notifier_block *this,
+				unsigned long event, void *ptr)
+{
+	switch (event) {
+	case PM_POST_HIBERNATION:
+	case PM_POST_SUSPEND:
+		return msm_watchdog_resume();
+	case PM_HIBERNATION_PREPARE:
+	case PM_SUSPEND_PREPARE:
+		return msm_watchdog_suspend();
+	default:
+		return NOTIFY_DONE;
+	}
+}
+
+static int panic_wdog_handler(struct notifier_block *this,
+			      unsigned long event, void *ptr)
+{
+	if (panic_timeout == 0) {
+		__raw_writel(0, WDT0_EN);
+		mb();
+		secure_writel(0, MSM_TCSR_BASE + TCSR_WDT_CFG);
+	} else {
+		__raw_writel(32768 * (panic_timeout + 4), WDT0_BARK_TIME);
+		__raw_writel(32768 * (panic_timeout + 4), WDT0_BITE_TIME);
+		__raw_writel(1, WDT0_RST);
+	}
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block panic_blk = {
+	.notifier_call	= panic_wdog_handler,
+};
+
+static struct notifier_block msm_watchdog_power_notifier = {
+	.notifier_call = msm_watchdog_power_event,
+};
+
+static int wdog_enable_set(const char *val, struct kernel_param *kp)
+{
+	int ret = 0;
+	int old_val = runtime_disable;
+
+	mutex_lock(&disable_lock);
+
+	if (!enable) {
+		printk(KERN_INFO "MSM Watchdog is not active.\n");
+		ret = -EINVAL;
+		goto done;
+	}
+
+	ret = param_set_int(val, kp);
+
+	if (ret)
+		goto done;
+
+	switch (runtime_disable) {
+
+	case 1:
+		if (!old_val) {
+			__raw_writel(0, WDT0_EN);
+			unregister_pm_notifier(&msm_watchdog_power_notifier);
+
+			/* may be suspended after the first write above */
+			__raw_writel(0, WDT0_EN);
+			mb();
+			secure_writel(0, MSM_TCSR_BASE + TCSR_WDT_CFG);
+			free_irq(WDT0_ACCSCSSNBARK_INT, 0);
+			enable = 0;
+			atomic_notifier_chain_unregister(&panic_notifier_list,
+			       &panic_blk);
+			cancel_delayed_work(&dogwork_struct);
+			printk(KERN_INFO "MSM Watchdog deactivated.\n");
+		}
+	break;
+
+	default:
+		runtime_disable = old_val;
+		ret = -EINVAL;
+	break;
+
+	}
+
+done:
+	mutex_unlock(&disable_lock);
+	return ret;
+}
+
+void pet_watchdog(void)
+{
+	__raw_writel(1, WDT0_RST);
+	last_pet = sched_clock();
+}
+
+static void pet_watchdog_work(struct work_struct *work)
+{
+	pet_watchdog();
+
+	if (enable)
+		schedule_delayed_work_on(0, &dogwork_struct, delay_time);
+}
+
+static void __exit exit_watchdog(void)
+{
+	if (enable) {
+		__raw_writel(0, WDT0_EN);
+		unregister_pm_notifier(&msm_watchdog_power_notifier);
+		__raw_writel(0, WDT0_EN); /* In case we got suspended
+					   * mid-exit */
+		mb();
+		secure_writel(0, MSM_TCSR_BASE + TCSR_WDT_CFG);
+		free_irq(WDT0_ACCSCSSNBARK_INT, 0);
+		enable = 0;
+	}
+	printk(KERN_INFO "MSM Watchdog Exit - Deactivated\n");
+}
+
+static irqreturn_t wdog_bark_handler(int irq, void *dev_id)
+{
+	unsigned long nanosec_rem;
+	unsigned long long t = sched_clock();
+	struct task_struct *tsk;
+
+	nanosec_rem = do_div(t, 1000000000);
+	printk(KERN_INFO "Watchdog bark! Now = %lu.%06lu\n", (unsigned long) t,
+		nanosec_rem / 1000);
+
+	nanosec_rem = do_div(last_pet, 1000000000);
+	printk(KERN_INFO "Watchdog last pet at %lu.%06lu\n", (unsigned long)
+		last_pet, nanosec_rem / 1000);
+
+	if (print_all_stacks) {
+
+		/* Suspend wdog until all stacks are printed */
+		msm_watchdog_suspend();
+
+		printk(KERN_INFO "Stack trace dump:\n");
+
+		for_each_process(tsk) {
+			printk(KERN_INFO "\nPID: %d, Name: %s\n",
+				tsk->pid, tsk->comm);
+			show_stack(tsk, NULL);
+		}
+
+		msm_watchdog_resume();
+	}
+
+	panic("Apps watchdog bark received!");
+	return IRQ_HANDLED;
+}
+
+#define SCM_SET_REGSAVE_CMD 0x2
+
+static void init_watchdog_work(struct work_struct *work)
+{
+	int ret;
+	struct {
+		unsigned addr;
+		int len;
+	} cmd_buf;
+
+	if (!enable) {
+		printk(KERN_INFO "MSM Watchdog Not Initialized\n");
+		return;
+	}
+
+	/* Must request irq before sending scm command */
+	ret = request_irq(WDT0_ACCSCSSNBARK_INT, wdog_bark_handler, 0,
+			  "apps_wdog_bark", NULL);
+	if (ret)
+		return;
+
+#ifdef CONFIG_MSM_SCM
+	if (!appsbark) {
+		scm_regsave = (void *)__get_free_page(GFP_KERNEL);
+
+		if (scm_regsave) {
+			cmd_buf.addr = __pa(scm_regsave);
+			cmd_buf.len  = PAGE_SIZE;
+
+			ret = scm_call(SCM_SVC_UTIL, SCM_SET_REGSAVE_CMD,
+				       &cmd_buf, sizeof(cmd_buf), NULL, 0);
+			if (ret)
+				pr_err("Setting register save address failed.\n"
+				       "Registers won't be dumped on a dog "
+				       "bite\n");
+		} else
+			pr_err("Allocating register save space failed\n"
+			       "Registers won't be dumped on a dog bite\n");
+			/*
+			 * No need to bail if allocation fails. Simply don't
+			 * send the command, and the secure side will reset
+			 * without saving registers.
+			 */
+	}
+#endif
+	secure_writel(1, MSM_TCSR_BASE + TCSR_WDT_CFG);
+	delay_time = msecs_to_jiffies(PET_DELAY);
+
+	/* 32768 ticks = 1 second */
+	if (machine_is_msm8960_sim()) {
+		__raw_writel(32768*8, WDT0_BARK_TIME);
+		__raw_writel(32768*10, WDT0_BITE_TIME);
+	} else {
+		__raw_writel(32768*4, WDT0_BARK_TIME);
+		__raw_writel(32768*5, WDT0_BITE_TIME);
+	}
+
+	ret = register_pm_notifier(&msm_watchdog_power_notifier);
+	if (ret) {
+		free_irq(WDT0_ACCSCSSNBARK_INT, NULL);
+		return;
+	}
+
+	schedule_delayed_work_on(0, &dogwork_struct, delay_time);
+
+	atomic_notifier_chain_register(&panic_notifier_list,
+				       &panic_blk);
+
+	__raw_writel(1, WDT0_EN);
+	__raw_writel(1, WDT0_RST);
+	last_pet = sched_clock();
+
+	printk(KERN_INFO "MSM Watchdog Initialized\n");
+
+	return;
+}
+
+static int __init init_watchdog(void)
+{
+	schedule_work_on(0, &init_dogwork_struct);
+	return 0;
+}
+
+late_initcall(init_watchdog);
+module_exit(exit_watchdog);
+MODULE_DESCRIPTION("MSM Watchdog Driver");
+MODULE_VERSION("1.0");
+MODULE_LICENSE("GPL v2");