msm: modem-ssr-8974: Introduce a modem SSR driver for the 8974
The modem on the 8974 notifies the krait subsystem of a
fatal error via an SMSM interrupt or a watchdog-triggered
interrupt. Introduce a driver that monitors the SMSM
state and the modem watchdog bite interrupt line and
restarts the modem or the target when they are triggered.
Change-Id: I4291be11d33f99839b98da484e105e0a59a53e21
Signed-off-by: Vikram Mulukutla <markivx@codeaurora.org>
diff --git a/arch/arm/mach-msm/Kconfig b/arch/arm/mach-msm/Kconfig
index 6f0a7e7..e14eb5a 100644
--- a/arch/arm/mach-msm/Kconfig
+++ b/arch/arm/mach-msm/Kconfig
@@ -2010,6 +2010,15 @@
gss hardware watchdog interrupt lines and plugs into the subsystem
restart and PIL drivers.
+config MSM_MODEM_SSR_8974
+ bool "MSM 8974 Modem restart driver"
+ depends on (ARCH_MSM8974)
+ help
+ This option enables the modem subsystem restart driver for the MSM8974.
+ It monitors the modem SMSM status bits and the modem watchdog line and
+ restarts the modem or the 8974 when the modem encounters a fatal error,
+ depending on the restart level selected in the subsystem restart driver.
+
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 524ec89..29bef02 100644
--- a/arch/arm/mach-msm/Makefile
+++ b/arch/arm/mach-msm/Makefile
@@ -210,6 +210,7 @@
endif
obj-$(CONFIG_MSM_SYSMON_COMM) += sysmon.o
obj-$(CONFIG_MSM_MODEM_8960) += modem-8960.o
+obj-$(CONFIG_MSM_MODEM_SSR_8974) += modem-ssr-8974.o
obj-$(CONFIG_MSM_LPASS_8960) += lpass-8960.o
obj-$(CONFIG_MSM_WCNSS_SSR_8960) += wcnss-ssr-8960.o
obj-$(CONFIG_MSM_GSS_SSR_8064) += gss-8064.o
diff --git a/arch/arm/mach-msm/modem-ssr-8974.c b/arch/arm/mach-msm/modem-ssr-8974.c
new file mode 100644
index 0000000..fec578f
--- /dev/null
+++ b/arch/arm/mach-msm/modem-ssr-8974.c
@@ -0,0 +1,141 @@
+/* Copyright (c) 2012, The Linux Foundation. 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/module.h>
+#include <linux/err.h>
+
+#include <mach/subsystem_restart.h>
+#include <mach/msm_smsm.h>
+
+static int crash_shutdown;
+static struct subsys_device *modem_ssr_dev;
+
+#define MAX_SSR_REASON_LEN 81U
+#define Q6SS_WDOG_ENABLE 0xFC802004
+#define MSS_Q6SS_WDOG_EXP_IRQ 56
+
+static void log_modem_sfr(void)
+{
+ u32 size;
+ char *smem_reason, reason[MAX_SSR_REASON_LEN];
+
+ smem_reason = smem_get_entry(SMEM_SSR_REASON_MSS0, &size);
+ if (!smem_reason || !size) {
+ pr_err("modem subsystem failure reason: (unknown, smem_get_entry failed).\n");
+ return;
+ }
+ if (!smem_reason[0]) {
+ pr_err("modem subsystem failure reason: (unknown, empty string found).\n");
+ return;
+ }
+
+ strlcpy(reason, smem_reason, min(size, sizeof(reason)));
+ pr_err("modem subsystem failure reason: %s.\n", reason);
+
+ smem_reason[0] = '\0';
+ wmb();
+}
+
+static void restart_modem(void)
+{
+ log_modem_sfr();
+ subsystem_restart("modem");
+}
+
+static void smsm_state_cb(void *data, uint32_t old_state, uint32_t new_state)
+{
+ /* Ignore if we're the one that set SMSM_RESET */
+ if (crash_shutdown)
+ return;
+
+ if (new_state & SMSM_RESET) {
+ pr_err("Probable fatal error on the modem.\n");
+ restart_modem();
+ }
+}
+
+static int modem_shutdown(const struct subsys_desc *subsys)
+{
+ return 0;
+}
+
+static int modem_powerup(const struct subsys_desc *subsys)
+{
+ return 0;
+}
+
+void modem_crash_shutdown(const struct subsys_desc *subsys)
+{
+ crash_shutdown = 1;
+ smsm_reset_modem(SMSM_RESET);
+}
+
+static int modem_ramdump(int enable,
+ const struct subsys_desc *crashed_subsys)
+{
+ return 0;
+}
+
+static irqreturn_t modem_wdog_bite_irq(int irq, void *dev_id)
+{
+ pr_err("Watchdog bite received from modem software!\n");
+ restart_modem();
+ return IRQ_HANDLED;
+}
+
+static struct subsys_desc modem_8974 = {
+ .name = "modem",
+ .shutdown = modem_shutdown,
+ .powerup = modem_powerup,
+ .ramdump = modem_ramdump,
+ .crash_shutdown = modem_crash_shutdown
+};
+
+static int __init modem_8974_init(void)
+{
+ int ret;
+
+ ret = smsm_state_cb_register(SMSM_MODEM_STATE, SMSM_RESET,
+ smsm_state_cb, 0);
+
+ if (ret < 0) {
+ pr_err("%s: Unable to register SMSM callback! (%d)\n",
+ __func__, ret);
+ goto out;
+ }
+
+ ret = request_irq(MSS_Q6SS_WDOG_EXP_IRQ, modem_wdog_bite_irq,
+ IRQF_TRIGGER_RISING, "modem_wdog_sw", NULL);
+
+ if (ret < 0) {
+ pr_err("%s: Unable to request q6sw watchdog IRQ. (%d)\n",
+ __func__, ret);
+ goto out;
+ }
+
+ modem_ssr_dev = subsys_register(&modem_8974);
+
+ if (IS_ERR_OR_NULL(modem_ssr_dev)) {
+ pr_err("%s: Unable to reg with subsystem restart. (%ld)\n",
+ __func__, PTR_ERR(modem_ssr_dev));
+ ret = PTR_ERR(modem_ssr_dev);
+ goto out;
+ }
+
+ pr_info("%s: modem subsystem restart driver init'ed.\n", __func__);
+out:
+ return ret;
+}
+
+arch_initcall(modem_8974_init);