MSM: DSPS: Add support for independent DSPS restart

These changes are added to restart the DSPS independent
of other processors, when the DSPS crashes

CRs-fixed: 361257, 361260, 364130
Change-Id: I4afc4ff41b0862165dca45aa8a30ecfcd47dcb73
Acked-by: Phong Duong <pduong@qualcomm.com>
Signed-off-by: Karthik Karuppasamy <kkaruppa@codeaurora.org>
diff --git a/arch/arm/mach-msm/msm_dsps.c b/arch/arm/mach-msm/msm_dsps.c
index 057665b..3b52a9f 100644
--- a/arch/arm/mach-msm/msm_dsps.c
+++ b/arch/arm/mach-msm/msm_dsps.c
@@ -15,6 +15,8 @@
  *
  */
 
+#include <asm/atomic.h>
+
 #include <linux/types.h>
 #include <linux/slab.h>
 #include <linux/module.h>
@@ -39,10 +41,11 @@
 #include <mach/subsystem_restart.h>
 #include <mach/subsystem_notif.h>
 
+#include "ramdump.h"
 #include "timer.h"
 
 #define DRV_NAME	"msm_dsps"
-#define DRV_VERSION	"3.02"
+#define DRV_VERSION	"4.00"
 
 #define PPSS_PAUSE_REG	0x1804
 
@@ -58,8 +61,15 @@
  *  @cdev - character device for user interface.
  *  @pdata - platform data.
  *  @pil - handle to DSPS Firmware loader.
+ *  @dspsfw_ramdump_dev - handle to ramdump device for DSPS
+ *  @dspsfw_ramdump_segments - Ramdump segment information for DSPS
+ *  @smem_ramdump_dev - handle to ramdump device for smem
+ *  @smem_ramdump_segments - Ramdump segment information for smem
  *  @is_on - DSPS is on.
  *  @ref_count - open/close reference count.
+ *  @wdog_irq - DSPS Watchdog IRQ
+ *  @wd_crash - Watchdog ISR fired
+ *  @crash_in_progress - 1 if crash recovery is in progress
  *  @ppss_base - ppss registers virtual base address.
  */
 struct dsps_drv {
@@ -73,9 +83,18 @@
 
 	void *pil;
 
+	void *dspsfw_ramdump_dev;
+	struct ramdump_segment dspsfw_ramdump_segments[4];
+
+	void *smem_ramdump_dev;
+	struct ramdump_segment smem_ramdump_segments[1];
+
 	int is_on;
 	int ref_count;
+	int wdog_irq;
 
+	atomic_t wd_crash;
+	atomic_t crash_in_progress;
 	void __iomem *ppss_base;
 };
 
@@ -89,8 +108,7 @@
  */
 static int dsps_crash_shutdown_g;
 
-
-static void dsps_fatal_handler(struct work_struct *work);
+static void dsps_restart_handler(struct work_struct *work);
 
 /**
  *  Load DSPS Firmware.
@@ -360,7 +378,7 @@
 	return 0;
 }
 
-static DECLARE_WORK(dsps_fatal_work, dsps_fatal_handler);
+static DECLARE_WORK(dsps_fatal_work, dsps_restart_handler);
 
 /**
  *  Watchdog interrupt handler
@@ -369,6 +387,7 @@
 static irqreturn_t dsps_wdog_bite_irq(int irq, void *dev_id)
 {
 	pr_debug("%s\n", __func__);
+	atomic_set(&drv->wd_crash, 1);
 	(void)schedule_work(&dsps_fatal_work);
 	disable_irq_nosync(irq);
 	return IRQ_HANDLED;
@@ -406,7 +425,7 @@
 		ret = put_user(val, (u32 __user *) arg);
 		break;
 	case DSPS_IOCTL_RESET:
-		dsps_fatal_handler(NULL);
+		dsps_restart_handler(NULL);
 		ret = 0;
 		break;
 	default:
@@ -498,21 +517,52 @@
 	ppss_wdog = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
 						"ppss_wdog");
 	if (ppss_wdog) {
-		ret = request_irq(ppss_wdog->start, dsps_wdog_bite_irq,
+		drv->wdog_irq = ppss_wdog->start;
+		ret = request_irq(drv->wdog_irq, dsps_wdog_bite_irq,
 				  IRQF_TRIGGER_RISING, "dsps_wdog", NULL);
 		if (ret) {
 			pr_err("%s: request_irq fail %d\n", __func__, ret);
 			goto request_irq_err;
 		}
 	} else {
+		drv->wdog_irq = -1;
 		pr_debug("%s: ppss_wdog not supported.\n", __func__);
 	}
 
+	drv->dspsfw_ramdump_segments[0].address = drv->pdata->tcm_code_start;
+	drv->dspsfw_ramdump_segments[0].size =  drv->pdata->tcm_code_size;
+	drv->dspsfw_ramdump_segments[1].address = drv->pdata->tcm_buf_start;
+	drv->dspsfw_ramdump_segments[1].size =  drv->pdata->tcm_buf_size;
+	drv->dspsfw_ramdump_segments[2].address = drv->pdata->pipe_start;
+	drv->dspsfw_ramdump_segments[2].size =  drv->pdata->pipe_size;
+	drv->dspsfw_ramdump_segments[3].address = drv->pdata->ddr_start;
+	drv->dspsfw_ramdump_segments[3].size =  drv->pdata->ddr_size;
+
+	drv->dspsfw_ramdump_dev = create_ramdump_device("dsps");
+	if (!drv->dspsfw_ramdump_dev) {
+		pr_err("%s: create_ramdump_device(\"dsps\") fail\n",
+			      __func__);
+		goto create_ramdump_err;
+	}
+
+	drv->smem_ramdump_segments[0].address = drv->pdata->smem_start;
+	drv->smem_ramdump_segments[0].size =  drv->pdata->smem_size;
+	drv->smem_ramdump_dev = create_ramdump_device("smem");
+	if (!drv->smem_ramdump_dev) {
+		pr_err("%s: create_ramdump_device(\"smem\") fail\n",
+		       __func__);
+		goto create_ramdump_err;
+	}
+
 	if (drv->pdata->init)
 		drv->pdata->init(drv->pdata);
 
 	return 0;
 
+create_ramdump_err:
+	disable_irq_nosync(drv->wdog_irq);
+	free_irq(drv->wdog_irq, NULL);
+
 request_irq_err:
 	iounmap(drv->ppss_base);
 
@@ -600,6 +650,8 @@
 		}
 	}
 
+	free_irq(drv->wdog_irq, NULL);
+
 	iounmap(drv->ppss_base);
 }
 
@@ -646,22 +698,42 @@
  *  Fatal error handler
  *  Resets DSPS.
  */
-static void dsps_fatal_handler(struct work_struct *work)
+static void dsps_restart_handler(struct work_struct *work)
 {
 	uint32_t dsps_state;
+	int restart_level;
+	char *smem_reset_reason;
+	unsigned smem_reset_size;
+	const char dflt_reason[] = "Died too early due to unknown reason";
 
 	dsps_state = smsm_get_state(SMSM_DSPS_STATE);
+	restart_level = get_restart_level();
 
-	pr_debug("%s: DSPS state 0x%x\n", __func__, dsps_state);
+	pr_debug("%s: DSPS state 0x%x. Restart lvl %d\n",
+		__func__, dsps_state, restart_level);
 
-	if (dsps_state & SMSM_RESET) {
-		pr_err("%s: DSPS fatal error detected. Resetting\n",
+	if ((dsps_state & SMSM_RESET) ||
+	    (atomic_read(&drv->wd_crash) == 1)) {
+		smem_reset_reason = smem_get_entry(SMEM_SSR_REASON_DSPS0,
+			&smem_reset_size);
+		if (smem_reset_reason != NULL && smem_reset_reason[0] != 0) {
+			smem_reset_reason[smem_reset_size-1] = 0;
+			pr_err("%s: DSPS failure: %s\nResetting DSPS\n",
+				__func__, smem_reset_reason);
+			memset(smem_reset_reason, 0, smem_reset_size);
+			wmb();
+		} else
+			pr_err("%s: DSPS failure: %s\nResetting DSPS\n",
+				__func__, dflt_reason);
+	} else
+		pr_err("%s: User-initiated DSPS reset.\nResetting DSPS\n",
 		       __func__);
-		panic("DSPS fatal error detected.");
+
+	if (atomic_add_return(1, &drv->crash_in_progress) > 1) {
+		pr_err("%s: DSPS already resetting. Count %d\n", __func__,
+		       atomic_read(&drv->crash_in_progress));
 	} else {
-		pr_debug("%s: User-initiated DSPS reset. Resetting\n",
-			 __func__);
-		panic("User-initiated DSPS reset.");
+		subsystem_restart("dsps");
 	}
 }
 
@@ -680,13 +752,8 @@
 		dsps_crash_shutdown_g = 0;
 		return;
 	}
-
-	if (new_state & SMSM_RESET) {
-		pr_err
-		    ("%s: SMSM_RESET state detected. restarting the DSPS\n",
-		     __func__);
-		panic("SMSM_RESET state detected.");
-	}
+	if (new_state & SMSM_RESET)
+		dsps_restart_handler(NULL);
 }
 
 /**
@@ -697,7 +764,9 @@
 static int dsps_shutdown(const struct subsys_data *subsys)
 {
 	pr_debug("%s\n", __func__);
-	dsps_unload();
+	dsps_suspend();
+	pil_force_shutdown(drv->pdata->pil_name);
+	dsps_power_off_handler();
 	return 0;
 }
 
@@ -709,11 +778,14 @@
 static int dsps_powerup(const struct subsys_data *subsys)
 {
 	pr_debug("%s\n", __func__);
-	if (dsps_load(drv->pdata->pil_name) != 0) {
-		pr_err("%s: fail to restart DSPS after reboot\n",
-		       __func__);
-		return 1;
+	dsps_power_on_handler();
+	pil_force_boot(drv->pdata->pil_name);
+	atomic_set(&drv->crash_in_progress, 0);
+	if (atomic_read(&drv->wd_crash) > 0) {
+		atomic_set(&drv->wd_crash, 0);
+		enable_irq(drv->wdog_irq);
 	}
+	dsps_resume();
 	return 0;
 }
 
@@ -736,8 +808,34 @@
  */
 static int dsps_ramdump(int enable, const struct subsys_data *subsys)
 {
+	int ret = 0;
 	pr_debug("%s\n", __func__);
-	return 0;
+
+	if (enable) {
+		if (drv->dspsfw_ramdump_dev != NULL) {
+			ret = do_ramdump(drv->dspsfw_ramdump_dev,
+				drv->dspsfw_ramdump_segments,
+				ARRAY_SIZE(drv->dspsfw_ramdump_segments));
+			if (ret < 0) {
+				pr_err("%s: Unable to dump DSPS memory (rc = %d).\n",
+				       __func__, ret);
+				goto dsps_ramdump_out;
+			}
+		}
+		if (drv->smem_ramdump_dev != NULL) {
+			ret = do_ramdump(drv->smem_ramdump_dev,
+				drv->smem_ramdump_segments,
+				ARRAY_SIZE(drv->smem_ramdump_segments));
+			if (ret < 0) {
+				pr_err("%s: Unable to dump smem memory (rc = %d).\n",
+				       __func__, ret);
+				goto dsps_ramdump_out;
+			}
+		}
+	}
+
+dsps_ramdump_out:
+	return ret;
 }
 
 static struct subsys_data dsps_ssrops = {
@@ -768,6 +866,9 @@
 		pr_err("%s: kzalloc fail.\n", __func__);
 		goto alloc_err;
 	}
+	atomic_set(&drv->wd_crash, 0);
+	atomic_set(&drv->crash_in_progress, 0);
+
 	drv->pdata = pdev->dev.platform_data;
 
 	drv->dev_class = class_create(THIS_MODULE, DRV_NAME);