wcnss: Add 8974 WCNSS restart module
Add WCNSS restart module for MSM8974; this module monitors WCNSS
SMSM reset bit and WCNSS hardware watchdog interrupt line. And
depending on the restart level, it will restart WCNSS when a
fatal error occurs at WCNSS.
Change-Id: I6ed7ea8459522e8f3a70b94dc6a94a0645c59a47
Signed-off-by: Sameer Thalappil <sameert@codeaurora.org>
diff --git a/arch/arm/mach-msm/Kconfig b/arch/arm/mach-msm/Kconfig
index c9b786c..015cd7d 100644
--- a/arch/arm/mach-msm/Kconfig
+++ b/arch/arm/mach-msm/Kconfig
@@ -2052,6 +2052,15 @@
restarts the adsp or the 8974 when the adsp encounters a fatal error,
depending on the restart level selected in the subsystem restart driver.
+config MSM_WCNSS_SSR_8974
+ tristate "MSM 8974 WCNSS restart module"
+ depends on (ARCH_MSM8974)
+ help
+ This option enables the WCNSS restart module for MSM8974. It monitors
+ WCNSS SMSM status bits and WCNSS hardware watchdog interrupt line; and
+ depending on the restart level, it will restart WCNSS when a fatal error
+ occurs at WCNSS.
+
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/wcnss-ssr-8974.c b/arch/arm/mach-msm/wcnss-ssr-8974.c
new file mode 100644
index 0000000..d8745fc
--- /dev/null
+++ b/arch/arm/mach-msm/wcnss-ssr-8974.c
@@ -0,0 +1,163 @@
+/* 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>
+
+#define MODULE_NAME "wcnss_8974"
+#define MAX_SSR_REASON_LEN 0x51
+
+static int ss_restart_inprogress;
+static int wcnss_crash;
+static struct subsys_device *wcnss_ssr_dev;
+
+#define WCNSS_APSS_WDOG_BITE_RESET_RDY_IRQ 231
+
+static void log_wcnss_sfr(void)
+{
+ char *smem_reset_reason;
+ char buffer[MAX_SSR_REASON_LEN];
+ unsigned smem_reset_size;
+ unsigned size;
+
+ smem_reset_reason = smem_get_entry(SMEM_SSR_REASON_WCNSS0,
+ &smem_reset_size);
+
+ if (!smem_reset_reason || !smem_reset_size) {
+ pr_err("%s: wcnss subsystem failure reason: %s\n",
+ __func__, "(unknown, smem_get_entry failed)");
+ } else if (!smem_reset_reason[0]) {
+ pr_err("%s: wcnss subsystem failure reason: %s\n",
+ __func__, "(unknown, init string found)");
+ } else {
+ size = smem_reset_size < MAX_SSR_REASON_LEN ? smem_reset_size :
+ (MAX_SSR_REASON_LEN - 1);
+ memcpy(buffer, smem_reset_reason, size);
+ buffer[size] = '\0';
+ pr_err("%s: wcnss subsystem failure reason: %s\n",
+ __func__, buffer);
+ memset(smem_reset_reason, 0, smem_reset_size);
+ wmb();
+ }
+}
+
+static void restart_wcnss(void)
+{
+ log_wcnss_sfr();
+ subsystem_restart("wcnss");
+}
+
+static void smsm_state_cb_hdlr(void *data, uint32_t old_state,
+ uint32_t new_state)
+{
+ wcnss_crash = true;
+
+ pr_err("%s: smsm state changed\n", MODULE_NAME);
+
+ if (!(new_state & SMSM_RESET))
+ return;
+
+ if (ss_restart_inprogress) {
+ pr_err("%s: Ignoring smsm reset req, restart in progress\n",
+ MODULE_NAME);
+ return;
+ }
+
+ ss_restart_inprogress = true;
+ restart_wcnss();
+}
+
+
+static irqreturn_t wcnss_wdog_bite_irq_hdlr(int irq, void *dev_id)
+{
+ wcnss_crash = true;
+
+ if (ss_restart_inprogress) {
+ pr_err("%s: Ignoring wcnss bite irq, restart in progress\n",
+ MODULE_NAME);
+ return IRQ_HANDLED;
+ }
+
+ ss_restart_inprogress = true;
+ restart_wcnss();
+
+ return IRQ_HANDLED;
+}
+
+
+static int wcnss_shutdown(const struct subsys_desc *subsys)
+{
+ return 0;
+}
+
+static int wcnss_powerup(const struct subsys_desc *subsys)
+{
+ return 0;
+}
+
+/* wcnss crash handler */
+static void wcnss_crash_shutdown(const struct subsys_desc *subsys)
+{
+ pr_err("%s: crash shutdown : %d\n", MODULE_NAME, wcnss_crash);
+ if (wcnss_crash != true)
+ smsm_change_state(SMSM_APPS_STATE, SMSM_RESET, SMSM_RESET);
+}
+
+static int wcnss_ramdump(int enable,
+ const struct subsys_desc *crashed_subsys)
+{
+ return 0;
+}
+
+static struct subsys_desc wcnss_ssr = {
+ .name = "wcnss",
+ .shutdown = wcnss_shutdown,
+ .powerup = wcnss_powerup,
+ .ramdump = wcnss_ramdump,
+ .crash_shutdown = wcnss_crash_shutdown
+};
+
+static int __init wcnss_ssr_init(void)
+{
+ int ret;
+
+ ret = smsm_state_cb_register(SMSM_WCNSS_STATE, SMSM_RESET,
+ smsm_state_cb_hdlr, 0);
+ if (ret < 0) {
+ pr_err("%s: Unable to register smsm callback for wcnss Reset! %d\n",
+ MODULE_NAME, ret);
+ goto out;
+ }
+ ret = request_irq(WCNSS_APSS_WDOG_BITE_RESET_RDY_IRQ,
+ wcnss_wdog_bite_irq_hdlr, IRQF_TRIGGER_HIGH,
+ "wcnss_wdog", NULL);
+
+ if (ret < 0) {
+ pr_err("%s: Unable to register for wcnss bite interrupt (%d)\n",
+ MODULE_NAME, ret);
+ goto out;
+ }
+ wcnss_ssr_dev = subsys_register(&wcnss_ssr);
+ if (IS_ERR(wcnss_ssr_dev))
+ return PTR_ERR(wcnss_ssr_dev);
+
+ pr_info("%s: module initialized\n", MODULE_NAME);
+out:
+ return ret;
+}
+
+arch_initcall(wcnss_ssr_init);