msm: dsps: DSPS support for Sub System Restart (SSR)

Register handler for SMSM_RESET notifications and watchdog
interrupts from the DSPS. And add support for reset via
IOCTL from user-mode application.

Signed-off-by: Wentao Xu <wentaox@codeaurora.org>
diff --git a/arch/arm/mach-msm/devices-8960.c b/arch/arm/mach-msm/devices-8960.c
index 390608f..32243eb 100644
--- a/arch/arm/mach-msm/devices-8960.c
+++ b/arch/arm/mach-msm/devices-8960.c
@@ -2133,6 +2133,13 @@
 		.name  = "ppss_reg",
 		.flags = IORESOURCE_MEM,
 	},
+
+	{
+		.start = PPSS_WDOG_TIMER_IRQ,
+		.end   = PPSS_WDOG_TIMER_IRQ,
+		.name  = "ppss_wdog",
+		.flags = IORESOURCE_IRQ,
+	},
 };
 
 struct platform_device msm_dsps_device = {
diff --git a/arch/arm/mach-msm/msm_dsps.c b/arch/arm/mach-msm/msm_dsps.c
index 031f5e1..ca99b1a 100644
--- a/arch/arm/mach-msm/msm_dsps.c
+++ b/arch/arm/mach-msm/msm_dsps.c
@@ -31,12 +31,16 @@
 #include <linux/io.h>
 #include <linux/msm_dsps.h>
 
+#include <mach/irqs.h>
 #include <mach/peripheral-loader.h>
 #include <mach/msm_iomap.h>
+#include <mach/msm_smsm.h>
 #include <mach/msm_dsps.h>
+#include <mach/subsystem_restart.h>
+#include <mach/subsystem_notif.h>
 
 #define DRV_NAME	"msm_dsps"
-#define DRV_VERSION	"2.00"
+#define DRV_VERSION	"3.00"
 
 #define PPSS_PAUSE_REG	0x1804
 
@@ -79,6 +83,14 @@
 static struct dsps_drv *drv;
 
 /**
+ * self-initiated shutdown flag
+ */
+static int dsps_crash_shutdown_g;
+
+
+static void dsps_fatal_handler(struct work_struct *work);
+
+/**
  *  Load DSPS Firmware.
  */
 static int dsps_load(const char *name)
@@ -346,6 +358,20 @@
 	return 0;
 }
 
+static DECLARE_WORK(dsps_fatal_work, dsps_fatal_handler);
+
+/**
+ *  Watchdog interrupt handler
+ *
+ */
+static irqreturn_t dsps_wdog_bite_irq(int irq, void *dev_id)
+{
+	pr_debug("%s\n", __func__);
+	(void)schedule_work(&dsps_fatal_work);
+	disable_irq_nosync(irq);
+	return IRQ_HANDLED;
+}
+
 /**
  * IO Control - handle commands from client.
  *
@@ -377,6 +403,10 @@
 		val = dsps_read_fast_timer();
 		ret = put_user(val, (u32 __user *) arg);
 		break;
+	case DSPS_IOCTL_RESET:
+		dsps_fatal_handler(NULL);
+		ret = 0;
+		break;
 	default:
 		ret = -EINVAL;
 		break;
@@ -393,6 +423,7 @@
 {
 	int ret = -ENODEV;
 	struct resource *ppss_res;
+	struct resource *ppss_wdog;
 	int i;
 
 	pr_debug("%s.\n", __func__);
@@ -462,8 +493,23 @@
 	drv->ppss_base = ioremap(ppss_res->start,
 				 resource_size(ppss_res));
 
+	ppss_wdog = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
+						"ppss_wdog");
+	if (ppss_wdog) {
+		ret = request_irq(ppss_wdog->start, 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 {
+		pr_debug("%s: ppss_wdog not supported.\n", __func__);
+	}
 	return 0;
 
+request_irq_err:
+	iounmap(drv->ppss_base);
+
 reg_err:
 	for (i = 0; i < drv->pdata->regs_num; i++) {
 		if (drv->pdata->regs[i].reg) {
@@ -591,6 +637,111 @@
 };
 
 /**
+ *  Fatal error handler
+ *  Resets DSPS.
+ */
+static void dsps_fatal_handler(struct work_struct *work)
+{
+	uint32_t dsps_state;
+
+	dsps_state = smsm_get_state(SMSM_DSPS_STATE);
+
+	pr_debug("%s: DSPS state 0x%x\n", __func__, dsps_state);
+
+	if (dsps_state & SMSM_RESET) {
+		pr_err("%s: DSPS fatal error detected. Resetting\n",
+		       __func__);
+	} else {
+		pr_debug("%s: User-initiated DSPS reset. Resetting\n",
+			 __func__);
+	}
+	subsystem_restart("dsps");
+}
+
+
+/**
+ *  SMSM state change callback
+ *
+ */
+static void dsps_smsm_state_cb(void *data, uint32_t old_state,
+			       uint32_t new_state)
+{
+	pr_debug("%s\n", __func__);
+	if (dsps_crash_shutdown_g == 1) {
+		pr_debug("%s: SMSM_RESET state change ignored\n",
+			 __func__);
+		dsps_crash_shutdown_g = 0;
+		return;
+	}
+
+	if (new_state & SMSM_RESET) {
+		pr_err
+		    ("%s: SMSM_RESET state detected. restarting the DSPS\n",
+		     __func__);
+		subsystem_restart("dsps");
+	}
+}
+
+/**
+ *  Shutdown function
+ * called by the restart notifier
+ *
+ */
+static int dsps_shutdown(const struct subsys_data *subsys)
+{
+	pr_debug("%s\n", __func__);
+	dsps_unload();
+	return 0;
+}
+
+/**
+ *  Powerup function
+ * called by the restart notifier
+ *
+ */
+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;
+	}
+	return 0;
+}
+
+/**
+ *  Crash shutdown function
+ * called by the restart notifier
+ *
+ */
+static void dsps_crash_shutdown(const struct subsys_data *subsys)
+{
+	pr_debug("%s\n", __func__);
+	dsps_crash_shutdown_g = 1;
+	smsm_change_state(SMSM_DSPS_STATE, SMSM_RESET, SMSM_RESET);
+}
+
+/**
+ *  Ramdump function
+ * called by the restart notifier
+ *
+ */
+static int dsps_ramdump(int enable, const struct subsys_data *subsys)
+{
+	pr_debug("%s\n", __func__);
+	return 0;
+}
+
+static struct subsys_data dsps_ssrops = {
+	.name = "dsps",
+	.shutdown = dsps_shutdown,
+	.powerup = dsps_powerup,
+	.ramdump = dsps_ramdump,
+	.crash_shutdown = dsps_crash_shutdown
+};
+
+/**
  * platform driver
  *
  */
@@ -652,8 +803,30 @@
 		goto cdev_add_err;
 	}
 
+	ret =
+	    smsm_state_cb_register(SMSM_DSPS_STATE, SMSM_RESET,
+				   dsps_smsm_state_cb, 0);
+	if (ret) {
+		pr_err("%s: smsm_state_cb_register fail %d\n", __func__,
+		       ret);
+		goto smsm_register_err;
+	}
+
+	ret = ssr_register_subsystem(&dsps_ssrops);
+	if (ret) {
+		pr_err("%s: ssr_register_subsystem fail %d\n", __func__,
+		       ret);
+		goto ssr_register_err;
+	}
+
 	return 0;
 
+ssr_register_err:
+	smsm_state_cb_deregister(SMSM_DSPS_STATE, SMSM_RESET,
+				 dsps_smsm_state_cb,
+				 0);
+smsm_register_err:
+	cdev_del(drv->cdev);
 cdev_add_err:
 	kfree(drv->cdev);
 cdev_alloc_err:
diff --git a/include/linux/msm_dsps.h b/include/linux/msm_dsps.h
index 7f4d2dd..a5ac256 100644
--- a/include/linux/msm_dsps.h
+++ b/include/linux/msm_dsps.h
@@ -25,4 +25,6 @@
 #define DSPS_IOCTL_READ_SLOW_TIMER _IOR(DSPS_IOCTL_MAGIC, 3, unsigned int*)
 #define DSPS_IOCTL_READ_FAST_TIMER _IOR(DSPS_IOCTL_MAGIC, 4, unsigned int*)
 
+#define DSPS_IOCTL_RESET _IO(DSPS_IOCTL_MAGIC, 5)
+
 #endif	/* _DSPS_H_ */