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);