msm: vidc: Add support to process HW Error

Add support to process Watchdog timeout and sys error
with SSR framework and reporting event bck to userspace.

Change-Id: Ie12a15044dd01cb0ceab82d4f29d612db2955b8d
Signed-off-by: Praneeth Paladugu <ppaladug@codeaurora.org>
diff --git a/drivers/media/video/msm_vidc/Makefile b/drivers/media/video/msm_vidc/Makefile
index 2a1f40f..9b3af9d 100644
--- a/drivers/media/video/msm_vidc/Makefile
+++ b/drivers/media/video/msm_vidc/Makefile
@@ -5,5 +5,6 @@
 				msm_venc.o \
 				msm_smem.o \
 				msm_vidc_debug.o \
+				msm_vidc_ssr.o \
 				vidc_hal.o \
 				vidc_hal_interrupt_handler.o \
diff --git a/drivers/media/video/msm_vidc/msm_v4l2_vidc.c b/drivers/media/video/msm_vidc/msm_v4l2_vidc.c
index a608e1ab..e1a0994 100644
--- a/drivers/media/video/msm_vidc/msm_v4l2_vidc.c
+++ b/drivers/media/video/msm_vidc/msm_v4l2_vidc.c
@@ -29,6 +29,7 @@
 #include "msm_vidc_debug.h"
 #include "vidc_hal_api.h"
 #include "msm_smem.h"
+#include "msm_vidc_ssr.h"
 
 #define BASE_DEVICE_NUMBER 32
 #define SHARED_QSIZE 0x1000000
@@ -584,7 +585,7 @@
 				buffer_info.m.planes[0].reserved[1],
 				buffer_info.m.planes[0].length);
 			rc = msm_vidc_release_buf(v4l2_inst->vidc_inst,
-				&buffer_info);
+					&buffer_info);
 			if (rc)
 				dprintk(VIDC_ERR,
 					"Failed Release buffer: %d, %d, %d\n",
@@ -1243,6 +1244,9 @@
 	core->debugfs_root = msm_vidc_debugfs_init_core(
 		core, vidc_driver->debugfs_root);
 	pdev->dev.platform_data = core;
+	rc = msm_vidc_ssr_init(core);
+	if (rc < 0)
+		dprintk(VIDC_ERR, "msm_vidc : Sub Systrem Restart failed\n");
 	return rc;
 
 err_cores_exceeded:
@@ -1271,6 +1275,7 @@
 	vidc_hal_delete_device(core->device);
 	video_unregister_device(&core->vdev[MSM_VIDC_ENCODER].vdev);
 	video_unregister_device(&core->vdev[MSM_VIDC_DECODER].vdev);
+	rc = msm_vidc_ssr_uninit(core);
 	v4l2_device_unregister(&core->v4l2_dev);
 	if (core->resources.ocmem.handle)
 		ocmem_notifier_unregister(core->resources.ocmem.handle,
diff --git a/drivers/media/video/msm_vidc/msm_vdec.c b/drivers/media/video/msm_vidc/msm_vdec.c
index 49b28ae..b615352 100644
--- a/drivers/media/video/msm_vidc/msm_vdec.c
+++ b/drivers/media/video/msm_vidc/msm_vdec.c
@@ -374,7 +374,14 @@
 	int rc = 0;
 	int i;
 	struct vidc_buffer_addr_info buffer_info;
-
+	struct msm_vidc_core *core = inst->core;
+	if (inst->state == MSM_VIDC_CORE_INVALID ||
+			core->state == VIDC_CORE_INVALID) {
+		dprintk(VIDC_ERR,
+			"Core %p in bad state, ignoring release output buf\n",
+				core);
+		goto exit;
+	}
 	switch (b->type) {
 	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
 		break;
@@ -392,7 +399,7 @@
 			buffer_info.extradata_addr =
 				inst->extradata_handle->device_addr;
 			rc = vidc_hal_session_release_buffers(
-				(void *)inst->session, &buffer_info);
+					(void *)inst->session, &buffer_info);
 			if (rc)
 				dprintk(VIDC_ERR,
 					"vidc_hal_session_release_buffers failed\n");
@@ -402,6 +409,7 @@
 		dprintk(VIDC_ERR, "Buffer type not recognized: %d\n", b->type);
 		break;
 	}
+exit:
 	return rc;
 }
 
@@ -842,6 +850,8 @@
 int msm_vdec_cmd(struct msm_vidc_inst *inst, struct v4l2_decoder_cmd *dec)
 {
 	int rc = 0;
+	struct v4l2_event dqevent = {0};
+	struct msm_vidc_core *core = inst->core;
 	switch (dec->cmd) {
 	case V4L2_DEC_QCOM_CMD_FLUSH:
 		rc = msm_comm_flush(inst, dec->flags);
@@ -853,6 +863,15 @@
 		rc = msm_comm_release_persist_buffers(inst);
 		if (rc)
 			pr_err("Failed to release persist buffers: %d\n", rc);
+		if (inst->state == MSM_VIDC_CORE_INVALID ||
+			core->state == VIDC_CORE_INVALID) {
+			dprintk(VIDC_ERR,
+				"Core %p in bad state, Sending CLOSE event\n",
+					core);
+			dqevent.type = V4L2_EVENT_MSM_VIDC_CLOSE_DONE;
+			v4l2_event_queue_fh(&inst->event_handler, &dqevent);
+			goto exit;
+		}
 		rc = msm_comm_try_state(inst, MSM_VIDC_CLOSE_DONE);
 		break;
 	default:
diff --git a/drivers/media/video/msm_vidc/msm_venc.c b/drivers/media/video/msm_vidc/msm_venc.c
index 4573018..0ea2a60 100644
--- a/drivers/media/video/msm_vidc/msm_venc.c
+++ b/drivers/media/video/msm_vidc/msm_venc.c
@@ -1260,11 +1260,20 @@
 int msm_venc_cmd(struct msm_vidc_inst *inst, struct v4l2_encoder_cmd *enc)
 {
 	int rc = 0;
+	struct v4l2_event dqevent = {0};
+	struct msm_vidc_core *core;
+	core = inst->core;
 	switch (enc->cmd) {
 	case V4L2_ENC_QCOM_CMD_FLUSH:
 		rc = msm_comm_flush(inst, enc->flags);
 		break;
 	case V4L2_ENC_CMD_STOP:
+		if (inst->state == MSM_VIDC_CORE_INVALID ||
+			core->state == VIDC_CORE_INVALID) {
+			dqevent.type = V4L2_EVENT_MSM_VIDC_CLOSE_DONE;
+			v4l2_event_queue_fh(&inst->event_handler, &dqevent);
+			return rc;
+		}
 		rc = msm_comm_try_state(inst, MSM_VIDC_CLOSE_DONE);
 		break;
 	}
diff --git a/drivers/media/video/msm_vidc/msm_vidc.c b/drivers/media/video/msm_vidc/msm_vidc.c
index 8ff7714..886113f 100644
--- a/drivers/media/video/msm_vidc/msm_vidc.c
+++ b/drivers/media/video/msm_vidc/msm_vidc.c
@@ -538,7 +538,9 @@
 			list_del(&inst->list);
 	}
 	mutex_unlock(&core->sync_lock);
-	rc = msm_comm_try_state(inst, MSM_VIDC_CORE_UNINIT);
+	if (inst->state != MSM_VIDC_CORE_INVALID &&
+		core->state != VIDC_CORE_INVALID)
+		rc = msm_comm_try_state(inst, MSM_VIDC_CORE_UNINIT);
 	if (rc)
 		dprintk(VIDC_ERR,
 			"Failed to move video instance to uninit state\n");
diff --git a/drivers/media/video/msm_vidc/msm_vidc_common.c b/drivers/media/video/msm_vidc/msm_vidc_common.c
index 4ff28d62..ed234b9 100644
--- a/drivers/media/video/msm_vidc/msm_vidc_common.c
+++ b/drivers/media/video/msm_vidc/msm_vidc_common.c
@@ -23,6 +23,7 @@
 #include "vidc_hal_api.h"
 #include "msm_smem.h"
 #include "msm_vidc_debug.h"
+#include "msm_vidc_ssr.h"
 
 #define HW_RESPONSE_TIMEOUT (5 * 60 * 1000)
 
@@ -312,7 +313,7 @@
 	complete(&core->completions[SYS_MSG_INDEX(cmd)]);
 }
 
-static inline void change_inst_state(struct msm_vidc_inst *inst,
+void change_inst_state(struct msm_vidc_inst *inst,
 	enum instance_state state)
 {
 	unsigned long flags;
@@ -508,6 +509,13 @@
 	}
 }
 
+static void handle_sys_watchdog_timeout(enum command_response cmd, void *data)
+{
+	subsystem_restart("msm_vidc");
+	dprintk(VIDC_ERR,
+		"msm_vidc: Sub System Restart initiated\n");
+}
+
 
 static void handle_session_close(enum command_response cmd, void *data)
 {
@@ -743,6 +751,9 @@
 	case SESSION_GET_SEQ_HDR_DONE:
 		handle_seq_hdr_done(cmd, data);
 		break;
+	case SYS_WATCHDOG_TIMEOUT:
+		handle_sys_watchdog_timeout(cmd, data);
+		break;
 	default:
 		dprintk(VIDC_ERR, "response unhandled\n");
 		break;
@@ -854,7 +865,7 @@
 	return rc;
 }
 
-static void msm_comm_unload_fw(struct msm_vidc_core *core)
+void msm_comm_unload_fw(struct msm_vidc_core *core)
 {
 	if (!core) {
 		dprintk(VIDC_ERR, "Invalid paramter: %p\n", core);
@@ -901,7 +912,7 @@
 	return rc;
 }
 
-static int msm_comm_unset_ocmem(struct msm_vidc_core *core)
+int msm_comm_unset_ocmem(struct msm_vidc_core *core)
 {
 	struct vidc_resource_hdr rhdr;
 	int rc = 0;
@@ -967,7 +978,7 @@
 	return rc;
 }
 
-static int msm_comm_free_ocmem(struct msm_vidc_core *core)
+int msm_comm_free_ocmem(struct msm_vidc_core *core)
 {
 	int rc = 0;
 	if (core->resources.ocmem.buf) {
@@ -1343,30 +1354,43 @@
 {
 	int rc = 0;
 	int flipped_state;
+	struct msm_vidc_core *core;
 	if (!inst) {
 		dprintk(VIDC_ERR,
-			"Invalid instance pointer = %p\n", inst);
+				"Invalid instance pointer = %p\n", inst);
 		return -EINVAL;
 	}
 	dprintk(VIDC_DBG,
-		"Trying to move inst: %p from: 0x%x to 0x%x\n",
-				inst, inst->state, state);
+			"Trying to move inst: %p from: 0x%x to 0x%x\n",
+			inst, inst->state, state);
+	core = inst->core;
+	if (!core) {
+		dprintk(VIDC_ERR,
+				"Invalid core pointer = %p\n", inst);
+		return -EINVAL;
+	}
 	mutex_lock(&inst->sync_lock);
+	if (inst->state == MSM_VIDC_CORE_INVALID ||
+			core->state == VIDC_CORE_INVALID) {
+		dprintk(VIDC_ERR,
+				"Core is in bad state can't change the state");
+		goto exit;
+	}
 	flipped_state = inst->state;
 	if (flipped_state < MSM_VIDC_STOP
-		&& state > MSM_VIDC_STOP) {
+			&& state > MSM_VIDC_STOP) {
 		flipped_state = MSM_VIDC_STOP + (MSM_VIDC_STOP - flipped_state);
 		flipped_state &= 0xFFFE;
 		flipped_state = flipped_state - 1;
 	} else if (flipped_state > MSM_VIDC_STOP
-		&& state < MSM_VIDC_STOP) {
+			&& state < MSM_VIDC_STOP) {
 		flipped_state = MSM_VIDC_STOP -
-				(flipped_state - MSM_VIDC_STOP + 1);
+			(flipped_state - MSM_VIDC_STOP + 1);
 		flipped_state &= 0xFFFE;
 		flipped_state = flipped_state - 1;
 	}
 	dprintk(VIDC_DBG,
-		"flipped_state = 0x%x\n", flipped_state);
+			"flipped_state = 0x%x\n", flipped_state);
 	switch (flipped_state) {
 	case MSM_VIDC_CORE_UNINIT_DONE:
 	case MSM_VIDC_CORE_INIT:
@@ -1421,14 +1445,14 @@
 		if (rc || state <= inst->state)
 			break;
 		dprintk(VIDC_DBG,
-			"Moving to release resources done state\n");
+				"Moving to release resources done state\n");
 	case MSM_VIDC_CLOSE:
 		rc = msm_comm_session_close(flipped_state, inst);
 		if (rc || state <= inst->state)
 			break;
 	case MSM_VIDC_CLOSE_DONE:
 		rc = wait_for_state(inst, flipped_state, MSM_VIDC_CLOSE_DONE,
-			SESSION_END_DONE);
+				SESSION_END_DONE);
 		if (rc || state <= inst->state)
 			break;
 	case MSM_VIDC_CORE_UNINIT:
@@ -1441,11 +1465,12 @@
 		rc = -EINVAL;
 		break;
 	}
+exit:
 	mutex_unlock(&inst->sync_lock);
 	if (rc)
 		dprintk(VIDC_ERR,
-			"Failed to move from state: %d to %d\n",
-			inst->state, state);
+				"Failed to move from state: %d to %d\n",
+				inst->state, state);
 	return rc;
 }
 
@@ -1456,13 +1481,25 @@
 	struct msm_vidc_inst *inst;
 	struct vb2_buf_entry *entry;
 	struct vidc_frame_data frame_data;
+	struct msm_vidc_core *core;
 	q = vb->vb2_queue;
 	inst = q->drv_priv;
-
 	if (!inst || !vb) {
 		dprintk(VIDC_ERR, "Invalid input: %p, %p\n", inst, vb);
 		return -EINVAL;
 	}
+	core = inst->core;
+	if (!core) {
+		dprintk(VIDC_ERR,
+			"Invalid input: %p, %p, %p\n", inst, core, vb);
+		return -EINVAL;
+	}
+
+	if (inst->state == MSM_VIDC_CORE_INVALID ||
+		core->state == VIDC_CORE_INVALID) {
+		dprintk(VIDC_ERR, "Core is in bad state. Can't Queue\n");
+		return -EINVAL;
+	}
 	if (inst->state != MSM_VIDC_START_DONE) {
 			entry = kzalloc(sizeof(*entry), GFP_KERNEL);
 			if (!entry) {
@@ -1580,7 +1617,6 @@
 	mutex_unlock(&inst->sync_lock);
 	return rc;
 }
-
 int msm_comm_release_scratch_buffers(struct msm_vidc_inst *inst)
 {
 	struct msm_smem *handle;
@@ -1589,6 +1625,18 @@
 	struct vidc_buffer_addr_info buffer_info;
 	int rc = 0;
 	unsigned long flags;
+	struct msm_vidc_core *core;
+	if (!inst) {
+		dprintk(VIDC_ERR,
+				"Invalid instance pointer = %p\n", inst);
+		return -EINVAL;
+	}
+	core = inst->core;
+	if (!core) {
+		dprintk(VIDC_ERR,
+				"Invalid core pointer = %p\n", core);
+		return -EINVAL;
+	}
 	spin_lock_irqsave(&inst->lock, flags);
 	if (!list_empty(&inst->internalbufs)) {
 		list_for_each_safe(ptr, next, &inst->internalbufs) {
@@ -1599,13 +1647,17 @@
 			buffer_info.buffer_type = HAL_BUFFER_INTERNAL_SCRATCH;
 			buffer_info.num_buffers = 1;
 			buffer_info.align_device_addr = handle->device_addr;
-			rc = vidc_hal_session_release_buffers(
-				(void *) inst->session,	&buffer_info);
-			if (rc)
-				dprintk(VIDC_WARN,
-					"Failed to release scratch buffer: 0x%x, %d",
-					buffer_info.align_device_addr,
-					buffer_info.buffer_size);
+			if (inst->state != MSM_VIDC_CORE_INVALID &&
+					core->state != VIDC_CORE_INVALID) {
+				rc = vidc_hal_session_release_buffers(
+						(void *) inst->session,
+							&buffer_info);
+				if (rc)
+					dprintk(VIDC_WARN,
+						"Rel scrtch buf fail:0x%x, %d",
+						buffer_info.align_device_addr,
+						buffer_info.buffer_size);
+			}
 			list_del(&buf->list);
 			spin_unlock_irqrestore(&inst->lock, flags);
 			msm_smem_free(inst->mem_client, buf->handle);
@@ -1625,23 +1677,39 @@
 	struct vidc_buffer_addr_info buffer_info;
 	int rc = 0;
 	unsigned long flags;
+	struct msm_vidc_core *core;
+	if (!inst) {
+		dprintk(VIDC_ERR,
+				"Invalid instance pointer = %p\n", inst);
+		return -EINVAL;
+	}
+	core = inst->core;
+	if (!core) {
+		dprintk(VIDC_ERR,
+				"Invalid core pointer = %p\n", core);
+		return -EINVAL;
+	}
 	spin_lock_irqsave(&inst->lock, flags);
 	if (!list_empty(&inst->persistbufs)) {
 		list_for_each_safe(ptr, next, &inst->persistbufs) {
 			buf = list_entry(ptr, struct internal_buf,
-				list);
+					list);
 			handle = buf->handle;
 			buffer_info.buffer_size = handle->size;
 			buffer_info.buffer_type = HAL_BUFFER_INTERNAL_PERSIST;
 			buffer_info.num_buffers = 1;
 			buffer_info.align_device_addr = handle->device_addr;
-			rc = vidc_hal_session_release_buffers(
-				(void *) inst->session,	&buffer_info);
-			if (rc)
-				dprintk(VIDC_WARN,
-					"Failed to release persist buffer 0x%x, %d\n",
-					buffer_info.align_device_addr,
-					buffer_info.buffer_size);
+			if (inst->state != MSM_VIDC_CORE_INVALID &&
+					core->state != VIDC_CORE_INVALID) {
+				rc = vidc_hal_session_release_buffers(
+						(void *) inst->session,
+							&buffer_info);
+				if (rc)
+					dprintk(VIDC_WARN,
+						"Rel prst buf fail:0x%x, %d",
+						buffer_info.align_device_addr,
+						buffer_info.buffer_size);
+			}
 			list_del(&buf->list);
 			spin_unlock_irqrestore(&inst->lock, flags);
 			msm_smem_free(inst->mem_client, buf->handle);
@@ -1779,6 +1847,50 @@
 	return rc;
 }
 
+static void msm_comm_flush_in_invalid_state(struct msm_vidc_inst *inst)
+{
+	struct v4l2_event dqevent = {0};
+	struct list_head *ptr, *next;
+	struct vb2_buffer *vb;
+	if (!list_empty(&inst->bufq[CAPTURE_PORT].
+				vb2_bufq.queued_list)) {
+		list_for_each_safe(ptr, next,
+				&inst->bufq[CAPTURE_PORT].
+				vb2_bufq.queued_list) {
+			vb = container_of(ptr,
+					struct vb2_buffer,
+					queued_entry);
+			if (vb) {
+				vb->v4l2_planes[0].bytesused = 0;
+				mutex_lock(&inst->bufq[CAPTURE_PORT].lock);
+				vb2_buffer_done(vb,
+						VB2_BUF_STATE_DONE);
+				mutex_unlock(&inst->bufq[CAPTURE_PORT].lock);
+			}
+		}
+	}
+	if (!list_empty(&inst->bufq[OUTPUT_PORT].
+				vb2_bufq.queued_list)) {
+		list_for_each_safe(ptr, next,
+				&inst->bufq[OUTPUT_PORT].
+				vb2_bufq.queued_list) {
+			vb = container_of(ptr,
+					struct vb2_buffer,
+					queued_entry);
+			if (vb) {
+				vb->v4l2_planes[0].bytesused = 0;
+				mutex_lock(&inst->bufq[OUTPUT_PORT].lock);
+				vb2_buffer_done(vb,
+						VB2_BUF_STATE_DONE);
+				mutex_unlock(&inst->bufq[OUTPUT_PORT].lock);
+			}
+		}
+	}
+	dqevent.type = V4L2_EVENT_MSM_VIDC_FLUSH_DONE;
+	dqevent.id = 0;
+	v4l2_event_queue_fh(&inst->event_handler, &dqevent);
+	return;
+}
 int msm_comm_flush(struct msm_vidc_inst *inst, u32 flags)
 {
 	int rc =  0;
@@ -1787,12 +1899,34 @@
 	struct list_head *ptr, *next;
 	struct vb2_buf_entry *temp;
 	struct mutex *lock;
+	struct msm_vidc_core *core;
+	if (!inst) {
+		dprintk(VIDC_ERR,
+				"Invalid instance pointer = %p\n", inst);
+		return -EINVAL;
+	}
+	core = inst->core;
+	if (!core) {
+		dprintk(VIDC_ERR,
+				"Invalid core pointer = %p\n", core);
+		return -EINVAL;
+	}
+
 	ip_flush = flags & V4L2_QCOM_CMD_FLUSH_OUTPUT;
 	op_flush = flags & V4L2_QCOM_CMD_FLUSH_CAPTURE;
+
 	if (ip_flush && !op_flush) {
 		dprintk(VIDC_WARN, "Input only flush not supported\n");
 		return 0;
 	}
+	if (inst->state == MSM_VIDC_CORE_INVALID ||
+			core->state == VIDC_CORE_INVALID) {
+		dprintk(VIDC_ERR,
+				"Core %p and inst %p are in bad state\n",
+					core, inst);
+		msm_comm_flush_in_invalid_state(inst);
+	}
+
 	mutex_lock(&inst->sync_lock);
 	if (inst->in_reconfig && !ip_flush && op_flush) {
 		if (!list_empty(&inst->pendingq)) {
diff --git a/drivers/media/video/msm_vidc/msm_vidc_common.h b/drivers/media/video/msm_vidc/msm_vidc_common.h
index 0708724..20b4bc2 100644
--- a/drivers/media/video/msm_vidc/msm_vidc_common.h
+++ b/drivers/media/video/msm_vidc/msm_vidc_common.h
@@ -34,6 +34,11 @@
 int msm_comm_flush(struct msm_vidc_inst *inst, u32 flags);
 int msm_comm_release_scratch_buffers(struct msm_vidc_inst *inst);
 int msm_comm_release_persist_buffers(struct msm_vidc_inst *inst);
+void msm_comm_unload_fw(struct msm_vidc_core *core);
+void change_inst_state(struct msm_vidc_inst *inst,
+	enum instance_state state);
+int msm_comm_unset_ocmem(struct msm_vidc_core *core);
+int msm_comm_free_ocmem(struct msm_vidc_core *core);
 #define IS_PRIV_CTRL(idx) (\
 		(V4L2_CTRL_ID2CLASS(idx) == V4L2_CTRL_CLASS_MPEG) && \
 		V4L2_CTRL_DRIVER_PRIV(idx))
diff --git a/drivers/media/video/msm_vidc/msm_vidc_internal.h b/drivers/media/video/msm_vidc/msm_vidc_internal.h
index 9806d771..0f6740f 100644
--- a/drivers/media/video/msm_vidc/msm_vidc_internal.h
+++ b/drivers/media/video/msm_vidc/msm_vidc_internal.h
@@ -84,6 +84,7 @@
 	VIDC_CORE_UNINIT = 0,
 	VIDC_CORE_INIT,
 	VIDC_CORE_INIT_DONE,
+	VIDC_CORE_INVALID
 };
 
 /*Donot change the enum values unless
@@ -105,6 +106,7 @@
 	MSM_VIDC_CLOSE,
 	MSM_VIDC_CLOSE_DONE,
 	MSM_VIDC_CORE_UNINIT,
+	MSM_VIDC_CORE_INVALID
 };
 
 struct buf_info {
@@ -224,6 +226,13 @@
 	int counter;
 };
 
+struct msm_vidc_ssr_info {
+	struct subsys_device *msm_vidc_dev;
+	struct subsys_desc *msm_vidc_subsys_desc;
+	void *msm_vidc_ramdump_dev;
+	bool ssr_in_progress;
+};
+
 struct msm_vidc_core {
 	struct list_head list;
 	struct mutex sync_lock;
@@ -241,6 +250,7 @@
 	enum vidc_core_state state;
 	struct msm_vidc_resources resources;
 	struct completion completions[SYS_MSG_END - SYS_MSG_START + 1];
+	struct msm_vidc_ssr_info ssr_info;
 };
 
 struct msm_vidc_inst {
diff --git a/drivers/media/video/msm_vidc/msm_vidc_ssr.c b/drivers/media/video/msm_vidc/msm_vidc_ssr.c
new file mode 100644
index 0000000..e8a6745
--- /dev/null
+++ b/drivers/media/video/msm_vidc/msm_vidc_ssr.c
@@ -0,0 +1,174 @@
+/* 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 <msm_vidc_ssr.h>
+
+static struct msm_vidc_core *get_vidc_core_from_dev(struct device *dev)
+{
+	struct video_device *vdev;
+	struct msm_video_device *videodev;
+	struct msm_vidc_core *core;
+	vdev = container_of(dev, struct video_device, dev);
+	videodev = container_of(vdev, struct msm_video_device, vdev);
+	core = container_of(videodev, struct msm_vidc_core,
+		vdev[MSM_VIDC_DECODER]);
+	return core;
+}
+int msm_vidc_shutdown(const struct subsys_desc *subsys)
+{
+	struct msm_vidc_inst *inst;
+	struct msm_vidc_core *core = NULL;
+	struct v4l2_event dqevent;
+	struct device *dev;
+	unsigned long flags;
+	int rc = 0;
+	if (!subsys) {
+		dprintk(VIDC_ERR, "Invalid subsys: %p\n", subsys);
+		rc = -EINVAL;
+		goto exit;
+	}
+	dev = subsys->dev;
+	if (dev)
+		core = get_vidc_core_from_dev(dev);
+	if (!core) {
+		dprintk(VIDC_ERR, "Invalid core: %p\n", core);
+		rc = -EINVAL;
+		goto exit;
+	}
+	core->ssr_info.ssr_in_progress = true;
+	spin_lock_irqsave(&core->lock, flags);
+	core->state = VIDC_CORE_INVALID;
+	spin_unlock_irqrestore(&core->lock, flags);
+	dqevent.type = V4L2_EVENT_MSM_VIDC_SYS_ERROR;
+	dqevent.id = 0;
+	list_for_each_entry(inst, &core->instances, list) {
+		if (inst) {
+			v4l2_event_queue_fh(&inst->event_handler, &dqevent);
+			spin_lock_irqsave(&inst->lock, flags);
+			inst->state = MSM_VIDC_CORE_INVALID;
+			spin_unlock_irqrestore(&inst->lock, flags);
+		}
+	}
+exit:
+	return rc;
+}
+int msm_vidc_ramdump(int enable, const struct subsys_desc *subsys)
+{
+	struct ramdump_segment memory_segments[] = {{0x0f500000, 0xFF000} };
+	struct msm_vidc_core *core = NULL;
+	void *dump_addr = NULL;
+	int rc = 0;
+	struct device *dev;
+	if (!subsys) {
+		dprintk(VIDC_ERR, "Invalid subsys: %p\n", subsys);
+		rc = -EINVAL;
+		goto exit;
+	}
+	dev = subsys->dev;
+	if (dev)
+		core = get_vidc_core_from_dev(dev);
+	if (!core) {
+		dprintk(VIDC_ERR, "Invalid core: %p\n", core);
+		rc = -EINVAL;
+		goto exit;
+	}
+	if (enable) {
+		rc = do_ramdump(core->ssr_info.msm_vidc_ramdump_dev,
+			memory_segments,
+			ARRAY_SIZE(memory_segments));
+		if (rc < 0)
+			dprintk(VIDC_DBG, "Failed : FW image memory dump\n");
+		dump_addr = kzalloc(core->resources.ocmem.buf->len, GFP_KERNEL);
+		if (dump_addr)
+			rc = ocmem_dump(OCMEM_VIDEO, core->resources.ocmem.buf,
+				(unsigned long)dump_addr);
+		if (rc < 0) {
+			dprintk(VIDC_DBG, "Failed : OCMEM copy\n");
+		} else	{
+			memory_segments[0].address = (unsigned long)dump_addr;
+			memory_segments[0].size =
+				(unsigned long)core->resources.ocmem.buf->len;
+			rc = do_ramdump(core->ssr_info.msm_vidc_ramdump_dev,
+				memory_segments,
+				ARRAY_SIZE(memory_segments));
+			if (rc < 0)
+				dprintk(VIDC_DBG, "Failed : OCMEM dump\n");
+		}
+		kfree(dump_addr);
+	}
+exit:
+	return rc;
+}
+int msm_vidc_powerup(const struct subsys_desc *subsys)
+{
+	unsigned long flags;
+	struct msm_vidc_core *core = NULL;
+	int rc = 0;
+	struct device *dev;
+	if (!subsys) {
+		dprintk(VIDC_ERR, "Invalid subsys: %p\n", subsys);
+		rc = -EINVAL;
+		goto exit;
+	}
+	dev = subsys->dev;
+	if (dev)
+		core = get_vidc_core_from_dev(dev);
+	if (!core) {
+		dprintk(VIDC_ERR, "Invalid core: %p\n", core);
+		rc = -EINVAL;
+		goto exit;
+	}
+	msm_comm_free_ocmem(core);
+	vidc_hal_core_release(core->device);
+	spin_lock_irqsave(&core->lock, flags);
+	core->state = VIDC_CORE_UNINIT;
+	spin_unlock_irqrestore(&core->lock, flags);
+	msm_comm_unload_fw(core);
+exit:
+	return rc;
+}
+void msm_vidc_crash_shutdown(const struct subsys_desc *subsys)
+{
+	dprintk(VIDC_DBG, "Nothing implemented in crash shutdown\n");
+}
+static struct subsys_desc msm_vidc_subsystem = {
+	.name = "msm_vidc",
+	.dev = NULL,
+	.shutdown = msm_vidc_shutdown,
+	.powerup = msm_vidc_powerup,
+	.ramdump = msm_vidc_ramdump,
+	.crash_shutdown = msm_vidc_crash_shutdown
+};
+int msm_vidc_ssr_init(struct msm_vidc_core *core)
+{
+	int rc = 0;
+	msm_vidc_subsystem.dev = &core->vdev[MSM_VIDC_DECODER].vdev.dev;
+	core->ssr_info.msm_vidc_dev = subsys_register(&msm_vidc_subsystem);
+	if (IS_ERR_OR_NULL(core->ssr_info.msm_vidc_dev)) {
+		dprintk(VIDC_ERR, "msm_vidc Sub System registration failed\n");
+		rc = -ENODEV;
+	}
+	core->ssr_info.msm_vidc_ramdump_dev = create_ramdump_device("msm_vidc");
+	if (!core->ssr_info.msm_vidc_ramdump_dev) {
+		dprintk(VIDC_ERR, "Unable to create msm_vidc ramdump device\n");
+		rc = -ENODEV;
+	}
+	core->ssr_info.ssr_in_progress = false;
+	return rc;
+}
+
+int msm_vidc_ssr_uninit(struct msm_vidc_core *core)
+{
+	subsys_unregister(core->ssr_info.msm_vidc_dev);
+	destroy_ramdump_device(core->ssr_info.msm_vidc_ramdump_dev);
+	return 0;
+}
diff --git a/drivers/media/video/msm_vidc/msm_vidc_ssr.h b/drivers/media/video/msm_vidc/msm_vidc_ssr.h
new file mode 100644
index 0000000..90f7380
--- /dev/null
+++ b/drivers/media/video/msm_vidc/msm_vidc_ssr.h
@@ -0,0 +1,28 @@
+/* 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.
+ *
+ */
+
+#ifndef __MSM_VIDC_SSR__
+#define __MSM_VIDC_SSR__
+
+#include <../ramdump.h>
+#include <mach/subsystem_restart.h>
+#include <mach/subsystem_notif.h>
+#include <media/msm_vidc.h>
+#include "msm_vidc_internal.h"
+#include "msm_vidc_common.h"
+#include "msm_vidc_debug.h"
+#include "vidc_hal_api.h"
+int msm_vidc_ssr_init(struct msm_vidc_core *core);
+int msm_vidc_ssr_uninit(struct msm_vidc_core *core);
+
+#endif
diff --git a/drivers/media/video/msm_vidc/vidc_hal_api.h b/drivers/media/video/msm_vidc/vidc_hal_api.h
index 879418d..9def3e3 100644
--- a/drivers/media/video/msm_vidc/vidc_hal_api.h
+++ b/drivers/media/video/msm_vidc/vidc_hal_api.h
@@ -812,6 +812,7 @@
 	PC_PREP_DONE,
 	SYS_IDLE,
 	SYS_DEBUG,
+	SYS_WATCHDOG_TIMEOUT,
 /* SESSION COMMANDS_DONE */
 	SESSION_LOAD_RESOURCE_DONE,
 	SESSION_INIT_DONE,
diff --git a/drivers/media/video/msm_vidc/vidc_hal_interrupt_handler.c b/drivers/media/video/msm_vidc/vidc_hal_interrupt_handler.c
index 043ed73..7eb0ae1 100644
--- a/drivers/media/video/msm_vidc/vidc_hal_interrupt_handler.c
+++ b/drivers/media/video/msm_vidc/vidc_hal_interrupt_handler.c
@@ -13,7 +13,9 @@
 
 #include <linux/slab.h>
 #include <linux/list.h>
+#include <linux/interrupt.h>
 #include "vidc_hal.h"
+#include "vidc_hal_io.h"
 #include "msm_vidc_debug.h"
 
 static enum vidc_status vidc_map_hal_err_status(int hfi_err)
@@ -136,7 +138,14 @@
 	cmd_done.data = &event_notify;
 	device->callback(VIDC_EVENT_CHANGE, &cmd_done);
 }
-
+static void hal_process_sys_watchdog_timeout(struct hal_device *device)
+{
+	struct msm_vidc_cb_cmd_done cmd_done;
+	disable_irq_nosync(device->hal_data->irq);
+	memset(&cmd_done, 0, sizeof(struct msm_vidc_cb_cmd_done));
+	cmd_done.device_id = device->device_id;
+	device->callback(SYS_WATCHDOG_TIMEOUT, &cmd_done);
+}
 static void hal_process_event_notify(struct hal_device *device,
 	struct hfi_msg_event_notify_packet *pkt)
 {
@@ -151,6 +160,7 @@
 	switch (pkt->event_id) {
 	case HFI_EVENT_SYS_ERROR:
 		dprintk(VIDC_INFO, "HFI_EVENT_SYS_ERROR");
+		hal_process_sys_watchdog_timeout(device);
 		break;
 	case HFI_EVENT_SESSION_ERROR:
 		dprintk(VIDC_INFO, "HFI_EVENT_SESSION_ERROR");
@@ -167,7 +177,6 @@
 		break;
 	}
 }
-
 static void hal_process_sys_init_done(struct hal_device *device,
 		struct hfi_msg_sys_init_done_packet *pkt)
 {
@@ -764,6 +773,11 @@
 	}
 
 	dprintk(VIDC_INFO, "Received: 0x%x in ", msg_hdr->packet);
+	if ((device->intr_status & VIDC_WRAPPER_INTR_CLEAR_A2HWD_BMSK)) {
+		dprintk(VIDC_ERR, "Received: Watchdog timeout %s", __func__);
+		hal_process_sys_watchdog_timeout(device);
+	}
+
 	switch (msg_hdr->packet) {
 	case HFI_MSG_EVENT_NOTIFY:
 		hal_process_event_notify(device,
diff --git a/include/linux/videodev2.h b/include/linux/videodev2.h
index 07beb50..cc390d0 100644
--- a/include/linux/videodev2.h
+++ b/include/linux/videodev2.h
@@ -2366,6 +2366,7 @@
 #define V4L2_EVENT_MSM_VIDC_PORT_SETTINGS_CHANGED_INSUFFICIENT	\
 		(V4L2_EVENT_MSM_VIDC_START + 3)
 #define V4L2_EVENT_MSM_VIDC_CLOSE_DONE	(V4L2_EVENT_MSM_VIDC_START + 4)
+#define V4L2_EVENT_MSM_VIDC_SYS_ERROR	(V4L2_EVENT_MSM_VIDC_START + 5)
 
 
 /* Payload for V4L2_EVENT_VSYNC */