msm: 8960: Add the lpass driver for the 8960
This driver monitors lpass hardware watchdog interrupt
lines and also plugs in to the subsystem restart and PIL
drivers to support the subsystem restart feature.
Signed-off-by: Bharath Ramachandramurthy <bramacha@codeaurora.org>
diff --git a/arch/arm/mach-msm/Kconfig b/arch/arm/mach-msm/Kconfig
index 5e49225..653da93 100644
--- a/arch/arm/mach-msm/Kconfig
+++ b/arch/arm/mach-msm/Kconfig
@@ -1390,6 +1390,14 @@
modem hardware watchdog interrupt lines and plugs into the subsystem
restart and PIL drivers.
+config MSM_LPASS_8960
+ tristate "MSM 8960 Lpass driver"
+ depends on (ARCH_MSM8960)
+ help
+ This option enables the lpass driver for the MSM8960, which monitors
+ lpass hardware watchdog interrupt lines and plugs into the subsystem
+ restart and PIL drivers.
+
config SCORPION_Uni_45nm_BUG
bool "Scorpion Uni 45nm(SC45U): Workaround for ICIMVAU and BPIMVA"
depends on ARCH_MSM7X30 || (ARCH_QSD8X50 && MSM_SOC_REV_A)
diff --git a/arch/arm/mach-msm/Makefile b/arch/arm/mach-msm/Makefile
index f64f526..2d38e1e 100644
--- a/arch/arm/mach-msm/Makefile
+++ b/arch/arm/mach-msm/Makefile
@@ -154,6 +154,7 @@
obj-$(CONFIG_ARCH_MSM8X60) += subsystem-fatal-8x60.o
endif
obj-$(CONFIG_MSM_MODEM_8960) += modem-8960.o
+obj-$(CONFIG_MSM_LPASS_8960) += lpass-8960.o
ifdef CONFIG_CPU_IDLE
obj-$(CONFIG_ARCH_MSM8960) += cpuidle.o
diff --git a/arch/arm/mach-msm/lpass-8960.c b/arch/arm/mach-msm/lpass-8960.c
new file mode 100644
index 0000000..df7beb2
--- /dev/null
+++ b/arch/arm/mach-msm/lpass-8960.c
@@ -0,0 +1,170 @@
+/* Copyright (c) 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/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/reboot.h>
+#include <linux/workqueue.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+
+#include <mach/irqs.h>
+#include <mach/scm.h>
+#include <mach/peripheral-loader.h>
+#include <mach/subsystem_restart.h>
+#include <mach/subsystem_notif.h>
+
+#include "smd_private.h"
+#include "ramdump.h"
+
+#define SCM_Q6_NMI_CMD 0x1
+#define MODULE_NAME "lpass_8960"
+#define Q6SS_SOFT_INTR_WAKEUP 0x28800024
+
+/* Subsystem restart: QDSP6 data, functions */
+static void lpass_fatal_fn(struct work_struct *);
+static DECLARE_WORK(lpass_fatal_work, lpass_fatal_fn);
+struct lpass_ssr {
+ void *lpass_ramdump_dev;
+} lpass_ssr;
+
+static struct lpass_ssr lpass_ssr_8960;
+
+static void lpass_fatal_fn(struct work_struct *work)
+{
+ pr_err("%s: Watchdog bite received from Q6!\n", MODULE_NAME);
+ subsystem_restart("lpass");
+}
+
+static void send_q6_nmi(void)
+{
+ /* Send NMI to QDSP6 via an SCM call. */
+ uint32_t cmd = 0x1;
+ void __iomem *q6_wakeup_intr;
+
+ scm_call(SCM_SVC_UTIL, SCM_Q6_NMI_CMD,
+ &cmd, sizeof(cmd), NULL, 0);
+
+ /* Wakeup the Q6 */
+ q6_wakeup_intr = ioremap_nocache(Q6SS_SOFT_INTR_WAKEUP, 8);
+ if (!q6_wakeup_intr) {
+ pr_err("%s: Unable to ioremap\n", __func__);
+ return;
+ }
+ writel_relaxed(0x01, q6_wakeup_intr);
+ iounmap(q6_wakeup_intr);
+ mb();
+
+ /* Q6 requires worstcase 100ms to dump caches etc.*/
+ msleep(100);
+ pr_debug("%s: Q6 NMI was sent.\n", __func__);
+}
+
+static int lpass_shutdown(const struct subsys_data *subsys)
+{
+ send_q6_nmi();
+ pil_force_shutdown("q6");
+ disable_irq_nosync(LPASS_Q6SS_WDOG_EXPIRED);
+
+ return 0;
+}
+
+static int lpass_powerup(const struct subsys_data *subsys)
+{
+ int ret = pil_force_boot("q6");
+ enable_irq(LPASS_Q6SS_WDOG_EXPIRED);
+ return ret;
+}
+/* RAM segments - address and size for 8960 */
+static struct ramdump_segment q6_segments[] = { {0x8da00000, 0x8f200000 -
+ 0x8da00000}, {0x28400000, 0x20000} };
+static int lpass_ramdump(int enable, const struct subsys_data *subsys)
+{
+ pr_debug("%s: enable[%d]\n", __func__, enable);
+ if (enable)
+ return do_ramdump(lpass_ssr_8960.lpass_ramdump_dev,
+ q6_segments,
+ ARRAY_SIZE(q6_segments));
+ else
+ return 0;
+}
+
+static void lpass_crash_shutdown(const struct subsys_data *subsys)
+{
+ send_q6_nmi();
+}
+
+static irqreturn_t lpass_wdog_bite_irq(int irq, void *dev_id)
+{
+ int ret;
+
+ pr_debug("%s: rxed irq[0x%x]", __func__, irq);
+ disable_irq_nosync(LPASS_Q6SS_WDOG_EXPIRED);
+ ret = schedule_work(&lpass_fatal_work);
+
+ return IRQ_HANDLED;
+}
+
+static struct subsys_data lpass_8960 = {
+ .name = "lpass",
+ .shutdown = lpass_shutdown,
+ .powerup = lpass_powerup,
+ .ramdump = lpass_ramdump,
+ .crash_shutdown = lpass_crash_shutdown
+};
+
+static int __init lpass_restart_init(void)
+{
+ return ssr_register_subsystem(&lpass_8960);
+}
+
+static int __init lpass_fatal_init(void)
+{
+ int ret;
+
+ ret = request_irq(LPASS_Q6SS_WDOG_EXPIRED, lpass_wdog_bite_irq,
+ IRQF_TRIGGER_RISING, "q6_wdog", NULL);
+
+ if (ret < 0) {
+ pr_err("%s: Unable to request LPASS_Q6SS_WDOG_EXPIRED irq.",
+ __func__);
+ goto out;
+ }
+ ret = lpass_restart_init();
+ if (ret < 0) {
+ pr_err("%s: Unable to reg with lpass ssr. (%d)\n",
+ __func__, ret);
+ goto out;
+ }
+ lpass_ssr_8960.lpass_ramdump_dev = create_ramdump_device("lpass");
+
+ if (!lpass_ssr_8960.lpass_ramdump_dev) {
+ pr_err("%s: Unable to create ramdump device.\n",
+ __func__);
+ ret = -ENOMEM;
+ goto out;
+ }
+ pr_info("%s: 8960 lpass SSR driver init'ed.\n", __func__);
+out:
+ return ret;
+}
+
+static void __exit lpass_fatal_exit(void)
+{
+ free_irq(LPASS_Q6SS_WDOG_EXPIRED, NULL);
+}
+
+module_init(lpass_fatal_init);
+module_exit(lpass_fatal_exit);
+
+MODULE_LICENSE("GPL v2");