Parcourir la source

msm: camera: common: LDAR dump NRT devices information

When user space detects an error or does not receive
response for a request, Lets do a reset(LDAR) is triggered.
Before LDAR, user space sends flush command to the
kernel space.
In order to debug the cause for this situation and to dump
the information, user space sends a dump command to the
kernel space before sending flush.
As a part of this command, it passes the culprit request id
and the buffer into which the information can be dumped.
Kernel space traverses across the drivers and find the culprit hw
and dumps the relevant information in the buffer.
This data is written to a file for offline processing.
This commit dumps the information for NRT devices; JPEG,
LRME, FD and ICP.
For LRME, FD, JPEG context information is dumped.
FOR ICP, fw image is dumped.

Change-Id: I123e9b8289521a40d88156ba9bd0003ad9602f01
CRs-Fixed: 2602180
Signed-off-by: Gaurav Jindal <[email protected]>
Gaurav Jindal il y a 5 ans
Parent
commit
dbc2111c4f

+ 15 - 1
drivers/cam_fd/cam_fd_context.c

@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /*
- * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
  */
 
 #include <linux/module.h>
@@ -117,6 +117,19 @@ static int __cam_fd_ctx_release_dev_in_activated(struct cam_context *ctx,
 	return rc;
 }
 
+static int __cam_fd_ctx_dump_dev_in_activated(
+	struct cam_context *ctx,
+	struct cam_dump_req_cmd *cmd)
+{
+	int rc;
+
+	rc = cam_context_dump_dev_to_hw(ctx, cmd);
+	if (rc)
+		CAM_ERR(CAM_FD, "Failed to dump device, rc=%d", rc);
+
+	return rc;
+}
+
 static int __cam_fd_ctx_flush_dev_in_activated(struct cam_context *ctx,
 	struct cam_flush_dev_cmd *cmd)
 {
@@ -198,6 +211,7 @@ static struct cam_ctx_ops
 			.release_dev = __cam_fd_ctx_release_dev_in_activated,
 			.config_dev = __cam_fd_ctx_config_dev_in_activated,
 			.flush_dev = __cam_fd_ctx_flush_dev_in_activated,
+			.dump_dev = __cam_fd_ctx_dump_dev_in_activated,
 		},
 		.crm_ops = {},
 		.irq_ops = __cam_fd_ctx_handle_irq_in_activated,

+ 134 - 1
drivers/cam_fd/fd_hw_mgr/cam_fd_hw_mgr.c

@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /*
- * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
  */
 
 #include <linux/module.h>
@@ -883,6 +883,7 @@ static int cam_fd_mgr_util_submit_frame(void *priv, void *data)
 	hw_device->cur_hw_ctx = hw_ctx;
 	hw_device->req_id = frame_req->request_id;
 	mutex_unlock(&hw_device->lock);
+	frame_req->submit_timestamp = ktime_get();
 
 	rc = cam_fd_mgr_util_put_frame_req(
 		&hw_mgr->frame_processing_list, &frame_req);
@@ -1504,6 +1505,137 @@ static int cam_fd_mgr_hw_flush(void *hw_mgr_priv,
 	return rc;
 }
 
+static int cam_fd_mgr_hw_dump(
+	void *hw_mgr_priv,
+	void *hw_dump_args)
+{
+	int                              rc;
+	uint8_t                         *dst;
+	ktime_t                          cur_time;
+	size_t                           remain_len;
+	uint32_t                         min_len;
+	uint64_t                         diff;
+	uint64_t                        *addr, *start;
+	struct timespec64                cur_ts;
+	struct timespec64                req_ts;
+	struct cam_fd_hw_mgr            *hw_mgr;
+	struct cam_hw_dump_args         *dump_args;
+	struct cam_fd_hw_mgr_ctx        *hw_ctx;
+	struct cam_fd_device            *hw_device;
+	struct cam_fd_hw_dump_args       fd_dump_args;
+	struct cam_fd_hw_dump_header    *hdr;
+	struct cam_fd_mgr_frame_request *frame_req, *req_temp;
+
+	hw_mgr = (struct cam_fd_hw_mgr *)hw_mgr_priv;
+	dump_args = (struct cam_hw_dump_args *)hw_dump_args;
+	if (!hw_mgr || !dump_args) {
+		CAM_ERR(CAM_FD, "Invalid args %pK %pK",
+			hw_mgr, dump_args);
+		return -EINVAL;
+	}
+
+	hw_ctx = (struct cam_fd_hw_mgr_ctx *)dump_args->ctxt_to_hw_map;
+
+	if (!hw_ctx) {
+		CAM_ERR(CAM_FD, "Invalid ctx");
+		return -EINVAL;
+	}
+
+	rc = cam_fd_mgr_util_get_device(hw_mgr, hw_ctx, &hw_device);
+
+	if (rc) {
+		CAM_ERR(CAM_FD, "Error in getting device %d", rc);
+		return rc;
+	}
+
+	list_for_each_entry_safe(frame_req, req_temp,
+		&hw_mgr->frame_processing_list, list) {
+		if (frame_req->request_id == dump_args->request_id)
+			goto hw_dump;
+	}
+
+	CAM_DBG(CAM_FD, "fd dump cannot find req %llu",
+		dump_args->request_id);
+	return rc;
+hw_dump:
+	cur_time = ktime_get();
+	diff = ktime_us_delta(frame_req->submit_timestamp, cur_time);
+	cur_ts = ktime_to_timespec64(cur_time);
+	req_ts = ktime_to_timespec64(frame_req->submit_timestamp);
+	if (diff < CAM_FD_RESPONSE_TIME_THRESHOLD) {
+		CAM_INFO(CAM_FD, "No Error req %lld %ld:%06ld %ld:%06ld",
+			dump_args->request_id,
+			req_ts.tv_sec,
+			req_ts.tv_nsec/NSEC_PER_USEC,
+			cur_ts.tv_sec,
+			cur_ts.tv_nsec/NSEC_PER_USEC);
+		return 0;
+	}
+	CAM_INFO(CAM_FD, "Error req %lld %ld:%06ld %ld:%06ld",
+		dump_args->request_id,
+		req_ts.tv_sec,
+		req_ts.tv_nsec/NSEC_PER_USEC,
+		cur_ts.tv_sec,
+		cur_ts.tv_nsec/NSEC_PER_USEC);
+	rc  = cam_mem_get_cpu_buf(dump_args->buf_handle,
+		&fd_dump_args.cpu_addr, &fd_dump_args.buf_len);
+	if (rc) {
+		CAM_ERR(CAM_FD, "Invalid handle %u rc %d",
+			dump_args->buf_handle, rc);
+		return rc;
+	}
+	if (fd_dump_args.buf_len <= dump_args->offset) {
+		CAM_WARN(CAM_FD, "dump offset overshoot len %zu offset %zu",
+			fd_dump_args.buf_len, dump_args->offset);
+		return -ENOSPC;
+	}
+	remain_len = fd_dump_args.buf_len - dump_args->offset;
+	min_len =  sizeof(struct cam_fd_hw_dump_header) +
+		(CAM_FD_HW_DUMP_NUM_WORDS * sizeof(uint64_t));
+
+	if (remain_len < min_len) {
+		CAM_WARN(CAM_FD, "dump buffer exhaust remain %zu min %u",
+			remain_len, min_len);
+		return -ENOSPC;
+	}
+
+	dst = (uint8_t *)fd_dump_args.cpu_addr + dump_args->offset;
+	hdr = (struct cam_fd_hw_dump_header *)dst;
+	scnprintf(hdr->tag, CAM_FD_HW_DUMP_TAG_MAX_LEN,
+		"FD_REQ:");
+	hdr->word_size = sizeof(uint64_t);
+	addr = (uint64_t *)(dst + sizeof(struct cam_fd_hw_dump_header));
+	start = addr;
+	*addr++ = frame_req->request_id;
+	*addr++ = req_ts.tv_sec;
+	*addr++ = req_ts.tv_nsec/NSEC_PER_USEC;
+	*addr++ = cur_ts.tv_sec;
+	*addr++ = cur_ts.tv_nsec/NSEC_PER_USEC;
+	hdr->size = hdr->word_size * (addr - start);
+	dump_args->offset += hdr->size +
+		sizeof(struct cam_fd_hw_dump_header);
+
+	fd_dump_args.request_id = dump_args->request_id;
+	fd_dump_args.offset = dump_args->offset;
+	if (hw_device->hw_intf->hw_ops.process_cmd) {
+		rc = hw_device->hw_intf->hw_ops.process_cmd(
+			hw_device->hw_intf->hw_priv,
+			CAM_FD_HW_CMD_HW_DUMP,
+			&fd_dump_args,
+			sizeof(struct
+			cam_fd_hw_dump_args));
+		if (rc) {
+			CAM_ERR(CAM_FD, "Hw Dump cmd fails req %lld rc %d",
+				frame_req->request_id, rc);
+			return rc;
+		}
+	}
+	CAM_DBG(CAM_FD, "Offset before %zu after %zu",
+		dump_args->offset, fd_dump_args.offset);
+	dump_args->offset = fd_dump_args.offset;
+	return rc;
+}
+
 static int cam_fd_mgr_hw_stop(void *hw_mgr_priv, void *mgr_stop_args)
 {
 	struct cam_fd_hw_mgr *hw_mgr = (struct cam_fd_hw_mgr *)hw_mgr_priv;
@@ -1944,6 +2076,7 @@ int cam_fd_hw_mgr_init(struct device_node *of_node,
 	hw_mgr_intf->hw_write = NULL;
 	hw_mgr_intf->hw_close = NULL;
 	hw_mgr_intf->hw_flush = cam_fd_mgr_hw_flush;
+	hw_mgr_intf->hw_dump = cam_fd_mgr_hw_dump;
 
 	return rc;
 

+ 9 - 1
drivers/cam_fd/fd_hw_mgr/cam_fd_hw_mgr.h

@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0-only */
 /*
- * Copyright (c) 2017-2018, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
  */
 
 #ifndef _CAM_FD_HW_MGR_H_
@@ -21,6 +21,12 @@
 #define CAM_FD_HW_MAX            1
 #define CAM_FD_WORKQ_NUM_TASK    10
 
+/*
+ * Response time threshold in ms beyond which a request is not expected to be
+ * with FD hw
+ */
+#define CAM_FD_RESPONSE_TIME_THRESHOLD   100000
+
 struct cam_fd_hw_mgr;
 
 /**
@@ -100,6 +106,7 @@ struct cam_fd_device {
  * @hw_update_entries     : HW update entries corresponding to this request
  *                          which needs to be submitted to HW through CDM
  * @num_hw_update_entries : Number of HW update entries
+ * @submit_timestamp      : Time stamp for submit req with hw
  */
 struct cam_fd_mgr_frame_request {
 	struct list_head               list;
@@ -108,6 +115,7 @@ struct cam_fd_mgr_frame_request {
 	struct cam_fd_hw_req_private   hw_req_private;
 	struct cam_hw_update_entry     hw_update_entries[CAM_FD_MAX_HW_ENTRIES];
 	uint32_t                       num_hw_update_entries;
+	ktime_t                        submit_timestamp;
 };
 
 /**

+ 79 - 0
drivers/cam_fd/fd_hw_mgr/fd_hw/cam_fd_hw_core.c

@@ -516,6 +516,80 @@ static int cam_fd_hw_util_processcmd_frame_done(struct cam_hw_info *fd_hw,
 	return 0;
 }
 
+static int cam_fd_hw_util_processcmd_hw_dump(
+	struct cam_hw_info *fd_hw,
+	void               *args)
+{
+	int                            i, j;
+	uint8_t                       *dst;
+	uint32_t                      *addr, *start;
+	uint32_t                       num_reg, min_len;
+	uint64_t                       remain_len;
+	struct cam_hw_soc_info        *soc_info;
+	struct cam_fd_hw_dump_header  *hdr;
+	struct cam_fd_hw_dump_args    *dump_args;
+
+	if (!fd_hw || !args) {
+		CAM_ERR(CAM_FD, "Invalid args %pK %pK",
+			fd_hw, args);
+		return -EINVAL;
+	}
+
+	mutex_lock(&fd_hw->hw_mutex);
+
+	if (fd_hw->hw_state == CAM_HW_STATE_POWER_DOWN) {
+		CAM_INFO(CAM_FD, "power off state");
+		mutex_unlock(&fd_hw->hw_mutex);
+		return 0;
+	}
+
+	dump_args = (struct cam_fd_hw_dump_args *)args;
+	soc_info = &fd_hw->soc_info;
+
+	if (dump_args->buf_len <= dump_args->offset) {
+		CAM_WARN(CAM_FD, "dump offset overshoot len %zu offset %zu",
+			dump_args->buf_len, dump_args->offset);
+		mutex_unlock(&fd_hw->hw_mutex);
+		return -ENOSPC;
+	}
+
+	remain_len = dump_args->buf_len - dump_args->offset;
+	min_len =  sizeof(struct cam_fd_hw_dump_header) +
+		    soc_info->reg_map[0].size + sizeof(uint32_t);
+
+	if (remain_len < min_len) {
+		CAM_WARN(CAM_FD, "dump buffer exhaust remain %zu min %u",
+			remain_len, min_len);
+		mutex_unlock(&fd_hw->hw_mutex);
+		return -ENOSPC;
+	}
+
+	dst = (uint8_t *)dump_args->cpu_addr + dump_args->offset;
+	hdr = (struct cam_fd_hw_dump_header *)dst;
+	scnprintf(hdr->tag, CAM_FD_HW_DUMP_TAG_MAX_LEN,
+		"FD_REG:");
+	hdr->word_size = sizeof(uint32_t);
+	addr = (uint32_t *)(dst + sizeof(struct cam_fd_hw_dump_header));
+	start = addr;
+	*addr++ = soc_info->index;
+
+	for (j = 0; j < soc_info->num_reg_map; j++) {
+		num_reg = soc_info->reg_map[j].size/4;
+		for (i = 0; i < num_reg; i++) {
+			*addr++ = soc_info->mem_block[j]->start + i*4;
+			*addr++ = cam_io_r(soc_info->reg_map[j].mem_base +
+				(i*4));
+		}
+	}
+
+	mutex_unlock(&fd_hw->hw_mutex);
+	hdr->size = hdr->word_size * (addr - start);
+	dump_args->offset += hdr->size +
+		sizeof(struct cam_fd_hw_dump_header);
+	CAM_DBG(CAM_FD, "%zu", dump_args->offset);
+	return 0;
+}
+
 irqreturn_t cam_fd_hw_irq(int irq_num, void *data)
 {
 	struct cam_hw_info *fd_hw = (struct cam_hw_info *)data;
@@ -1159,6 +1233,11 @@ int cam_fd_hw_process_cmd(void *hw_priv, uint32_t cmd_type,
 			cmd_frame_results);
 		break;
 	}
+	case CAM_FD_HW_CMD_HW_DUMP: {
+		rc = cam_fd_hw_util_processcmd_hw_dump(fd_hw,
+			cmd_args);
+		break;
+	}
 	default:
 		break;
 	}

+ 34 - 1
drivers/cam_fd/fd_hw_mgr/fd_hw/cam_fd_hw_intf.h

@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0-only */
 /*
- * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
  */
 
 #ifndef _CAM_FD_HW_INTF_H_
@@ -24,6 +24,8 @@
 
 #define CAM_FD_MAX_IO_BUFFERS        5
 #define CAM_FD_MAX_HW_ENTRIES        5
+#define CAM_FD_HW_DUMP_TAG_MAX_LEN   32
+#define CAM_FD_HW_DUMP_NUM_WORDS     5
 
 /**
  * enum cam_fd_hw_type - Enum for FD HW type
@@ -81,12 +83,14 @@ enum cam_fd_hw_irq_type {
  * @CAM_FD_HW_CMD_UPDATE_SOC        : Command to process soc update
  * @CAM_FD_HW_CMD_REGISTER_CALLBACK : Command to set hw mgr callback
  * @CAM_FD_HW_CMD_MAX               : Indicates max cmd
+ * @CAM_FD_HW_CMD_HW_DUMP           : Command to dump fd hw information
  */
 enum cam_fd_hw_cmd_type {
 	CAM_FD_HW_CMD_PRESTART,
 	CAM_FD_HW_CMD_FRAME_DONE,
 	CAM_FD_HW_CMD_UPDATE_SOC,
 	CAM_FD_HW_CMD_REGISTER_CALLBACK,
+	CAM_FD_HW_CMD_HW_DUMP,
 	CAM_FD_HW_CMD_MAX,
 };
 
@@ -279,6 +283,34 @@ struct cam_fd_hw_cmd_set_irq_cb {
 	void *data;
 };
 
+/**
+ * struct cam_fd_hw_dump_args : Args for dump request
+ *
+ * @request_id   : Issue request id
+ * @offset       : offset of the buffer
+ * @buf_len      : Length of target buffer
+ * @cpu_addr     : start address of the target buffer
+ */
+struct cam_fd_hw_dump_args {
+	uint64_t  request_id;
+	size_t    offset;
+	size_t    buf_len;
+	uintptr_t cpu_addr;
+};
+
+/**
+ * struct cam_fd_hw_dump_header : fd hw dump header
+ *
+ * @tag       : fd hw dump header tag
+ * @size      : Size of data
+ * @word_size : size of each word
+ */
+struct cam_fd_hw_dump_header {
+	uint8_t  tag[CAM_FD_HW_DUMP_TAG_MAX_LEN];
+	uint64_t size;
+	uint32_t word_size;
+};
+
 /**
  * @brief : API to register FD Hw to platform framework.
  * @return struct platform_device pointer on on success, or ERR_PTR() on error.
@@ -291,3 +323,4 @@ int cam_fd_hw_init_module(void);
 void cam_fd_hw_exit_module(void);
 
 #endif /* _CAM_FD_HW_INTF_H_ */
+

+ 16 - 1
drivers/cam_icp/cam_icp_context.c

@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /*
- * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
  */
 
 #include <linux/debugfs.h>
@@ -107,6 +107,19 @@ static int __cam_icp_start_dev_in_acquired(struct cam_context *ctx,
 	return rc;
 }
 
+static int __cam_icp_dump_dev_in_ready(
+	struct cam_context      *ctx,
+	struct cam_dump_req_cmd *cmd)
+{
+	int rc;
+
+	rc = cam_context_dump_dev_to_hw(ctx, cmd);
+	if (rc)
+		CAM_ERR(CAM_ICP, "Failed to dump device");
+
+	return rc;
+}
+
 static int __cam_icp_flush_dev_in_ready(struct cam_context *ctx,
 	struct cam_flush_dev_cmd *cmd)
 {
@@ -230,6 +243,7 @@ static struct cam_ctx_ops
 			.start_dev = __cam_icp_start_dev_in_acquired,
 			.config_dev = __cam_icp_config_dev_in_ready,
 			.flush_dev = __cam_icp_flush_dev_in_ready,
+			.dump_dev = __cam_icp_dump_dev_in_ready,
 		},
 		.crm_ops = {},
 		.irq_ops = __cam_icp_handle_buf_done_in_ready,
@@ -242,6 +256,7 @@ static struct cam_ctx_ops
 			.release_dev = __cam_icp_release_dev_in_ready,
 			.config_dev = __cam_icp_config_dev_in_ready,
 			.flush_dev = __cam_icp_flush_dev_in_ready,
+			.dump_dev = __cam_icp_dump_dev_in_ready,
 		},
 		.crm_ops = {},
 		.irq_ops = __cam_icp_handle_buf_done_in_ready,

+ 54 - 1
drivers/cam_icp/icp_hw/a5_hw/a5_core.c

@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /*
- * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
  */
 
 #include <linux/slab.h>
@@ -235,6 +235,53 @@ fw_download_failed:
 	return rc;
 }
 
+static int cam_a5_fw_dump(
+	struct cam_icp_hw_dump_args    *dump_args,
+	struct cam_a5_device_core_info *core_info)
+{
+	u8                         *dest;
+	u8                         *src;
+	uint64_t                    size_required;
+	struct cam_icp_dump_header *hdr;
+
+	if (!core_info || !dump_args) {
+		CAM_ERR(CAM_ICP, "invalid params %pK %pK",
+		    core_info, dump_args);
+		return -EINVAL;
+	}
+	if (!core_info->fw_kva_addr || !dump_args->cpu_addr) {
+		CAM_ERR(CAM_ICP, "invalid params %pK, 0x%zx",
+		    core_info->fw_kva_addr, dump_args->cpu_addr);
+		return -EINVAL;
+	}
+
+	size_required = core_info->fw_buf_len +
+		sizeof(struct cam_icp_dump_header);
+
+	if (dump_args->buf_len <= dump_args->offset) {
+		CAM_WARN(CAM_ICP, "Dump offset overshoot len %zu offset %zu",
+			dump_args->buf_len, dump_args->offset);
+		return -ENOSPC;
+	}
+
+	if ((dump_args->buf_len - dump_args->offset) < size_required) {
+		CAM_WARN(CAM_ICP, "Dump buffer exhaust required %llu len %llu",
+			size_required, core_info->fw_buf_len);
+		return -ENOSPC;
+	}
+
+	dest = (u8 *)dump_args->cpu_addr + dump_args->offset;
+	hdr = (struct cam_icp_dump_header *)dest;
+	scnprintf(hdr->tag, CAM_ICP_DUMP_TAG_MAX_LEN, "ICP_FW:");
+	hdr->word_size = sizeof(u8);
+	hdr->size = core_info->fw_buf_len;
+	src = (u8 *)core_info->fw_kva_addr;
+	dest = (u8 *)dest + sizeof(struct cam_icp_dump_header);
+	memcpy_fromio(dest, src, core_info->fw_buf_len);
+	dump_args->offset += hdr->size + sizeof(struct cam_icp_dump_header);
+	return 0;
+}
+
 int cam_a5_init_hw(void *device_priv,
 	void *init_hw_args, uint32_t arg_size)
 {
@@ -543,6 +590,12 @@ int cam_a5_process_cmd(void *device_priv, uint32_t cmd_type,
 			core_info->cpas_handle, &ahb_vote);
 		break;
 	}
+	case CAM_ICP_A5_CMD_HW_DUMP: {
+		struct cam_icp_hw_dump_args *dump_args = cmd_args;
+
+		rc = cam_a5_fw_dump(dump_args, core_info);
+		break;
+	}
 	default:
 		break;
 	}

+ 111 - 0
drivers/cam_icp/icp_hw/icp_hw_mgr/cam_icp_hw_mgr.c

@@ -3930,6 +3930,7 @@ static int cam_icp_mgr_config_hw(void *hw_mgr_priv, void *config_hw_args)
 
 	cam_icp_mgr_ipe_bps_clk_update(hw_mgr, ctx_data, idx);
 	ctx_data->hfi_frame_process.fw_process_flag[idx] = true;
+	ctx_data->hfi_frame_process.submit_timestamp[idx] = ktime_get();
 
 	CAM_DBG(CAM_ICP, "req_id %llu, io config %llu", req_id,
 		frame_info->io_config);
@@ -5038,6 +5039,115 @@ static int cam_icp_mgr_enqueue_abort(
 	return 0;
 }
 
+static int cam_icp_mgr_hw_dump(void *hw_priv, void *hw_dump_args)
+{
+	int                              rc;
+	int                              i;
+	size_t                           remain_len;
+	uint8_t                         *dst;
+	uint32_t                         min_len;
+	uint64_t                         diff;
+	uint64_t                        *addr, *start;
+	struct timespec64                cur_ts;
+	struct timespec64                req_ts;
+	ktime_t                          cur_time;
+	struct cam_hw_intf              *a5_dev_intf;
+	struct cam_icp_hw_mgr           *hw_mgr;
+	struct cam_hw_dump_args         *dump_args;
+	struct cam_icp_hw_ctx_data      *ctx_data;
+	struct cam_icp_dump_header      *hdr;
+	struct cam_icp_hw_dump_args      icp_dump_args;
+	struct hfi_frame_process_info   *frm_process;
+
+	if ((!hw_priv) || (!hw_dump_args)) {
+		CAM_ERR(CAM_ICP, "Invalid params %pK %pK",
+			hw_priv, hw_dump_args);
+		return -EINVAL;
+	}
+
+	dump_args = (struct cam_hw_dump_args *)hw_dump_args;
+	hw_mgr = hw_priv;
+	ctx_data = dump_args->ctxt_to_hw_map;
+	CAM_DBG(CAM_ICP, "Req %lld", dump_args->request_id);
+	frm_process = &ctx_data->hfi_frame_process;
+	for (i = 0; i < CAM_FRAME_CMD_MAX; i++) {
+		if ((frm_process->request_id[i] ==
+			dump_args->request_id) &&
+			frm_process->fw_process_flag[i])
+			goto hw_dump;
+	}
+	return 0;
+hw_dump:
+	cur_time = ktime_get();
+	diff = ktime_us_delta(frm_process->submit_timestamp[i], cur_time);
+	cur_ts = ktime_to_timespec64(cur_time);
+	req_ts = ktime_to_timespec64(frm_process->submit_timestamp[i]);
+
+	if (diff < CAM_ICP_CTX_RESPONSE_TIME_THRESHOLD) {
+		CAM_INFO(CAM_ICP, "No Error req %lld %ld:%06ld %ld:%06ld",
+			dump_args->request_id,
+			req_ts.tv_sec,
+			req_ts.tv_nsec/NSEC_PER_USEC,
+			cur_ts.tv_sec,
+			cur_ts.tv_nsec/NSEC_PER_USEC);
+		return 0;
+	}
+
+	CAM_INFO(CAM_ICP, "Error req %lld %ld:%06ld %ld:%06ld",
+		dump_args->request_id,
+		req_ts.tv_sec,
+		req_ts.tv_nsec/NSEC_PER_USEC,
+		cur_ts.tv_sec,
+		cur_ts.tv_nsec/NSEC_PER_USEC);
+	rc  = cam_mem_get_cpu_buf(dump_args->buf_handle,
+		&icp_dump_args.cpu_addr, &icp_dump_args.buf_len);
+	if (rc) {
+		CAM_ERR(CAM_ICP, "Invalid addr %u rc %d",
+			dump_args->buf_handle, rc);
+		return rc;
+	}
+	if (icp_dump_args.buf_len <= dump_args->offset) {
+		CAM_WARN(CAM_ICP, "dump buffer overshoot len %zu offset %zu",
+			icp_dump_args.buf_len, dump_args->offset);
+		return -ENOSPC;
+	}
+
+	remain_len = icp_dump_args.buf_len - dump_args->offset;
+	min_len = sizeof(struct cam_icp_dump_header) +
+			(CAM_ICP_DUMP_NUM_WORDS * sizeof(uint64_t));
+
+	if (remain_len < min_len) {
+		CAM_WARN(CAM_ICP, "dump buffer exhaust remain %zu min %u",
+			remain_len, min_len);
+		return -ENOSPC;
+	}
+
+	dst = (uint8_t *)icp_dump_args.cpu_addr + dump_args->offset;
+	hdr = (struct cam_icp_dump_header *)dst;
+	scnprintf(hdr->tag, CAM_ICP_DUMP_TAG_MAX_LEN, "ICP_REQ:");
+	hdr->word_size = sizeof(uint64_t);
+	addr = (uint64_t *)(dst + sizeof(struct cam_icp_dump_header));
+	start = addr;
+	*addr++ = frm_process->request_id[i];
+	*addr++ = req_ts.tv_sec;
+	*addr++ = req_ts.tv_nsec/NSEC_PER_USEC;
+	*addr++ = cur_ts.tv_sec;
+	*addr++ = cur_ts.tv_nsec/NSEC_PER_USEC;
+	hdr->size = hdr->word_size * (addr - start);
+	dump_args->offset += (hdr->size + sizeof(struct cam_icp_dump_header));
+	/* Dumping the fw image*/
+	icp_dump_args.offset = dump_args->offset;
+	a5_dev_intf = hw_mgr->a5_dev_intf;
+	rc = a5_dev_intf->hw_ops.process_cmd(
+		a5_dev_intf->hw_priv,
+		CAM_ICP_A5_CMD_HW_DUMP, &icp_dump_args,
+		sizeof(struct cam_icp_hw_dump_args));
+	CAM_DBG(CAM_ICP, "Offset before %zu after %zu",
+		dump_args->offset, icp_dump_args.offset);
+	dump_args->offset = icp_dump_args.offset;
+	return rc;
+}
+
 static int cam_icp_mgr_hw_flush(void *hw_priv, void *hw_flush_args)
 {
 	struct cam_hw_flush_args *flush_args = hw_flush_args;
@@ -5850,6 +5960,7 @@ int cam_icp_hw_mgr_init(struct device_node *of_node, uint64_t *hw_mgr_hdl,
 	hw_mgr_intf->hw_close = cam_icp_mgr_hw_close_u;
 	hw_mgr_intf->hw_flush = cam_icp_mgr_hw_flush;
 	hw_mgr_intf->hw_cmd = cam_icp_mgr_cmd;
+	hw_mgr_intf->hw_dump = cam_icp_mgr_hw_dump;
 
 	icp_hw_mgr.secure_mode = CAM_SECURE_MODE_NON_SECURE;
 	mutex_init(&icp_hw_mgr.hw_mgr_mutex);

+ 9 - 1
drivers/cam_icp/icp_hw/icp_hw_mgr/cam_icp_hw_mgr.h

@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0-only */
 /*
- * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
  */
 
 #ifndef CAM_ICP_HW_MGR_H
@@ -67,6 +67,12 @@
 /* Current appliacble vote paths, based on number of UAPI definitions */
 #define CAM_ICP_MAX_PER_PATH_VOTES 6
 
+/*
+ * Response time threshold in ms beyond which a request is not expected
+ * to be with ICP hw
+ */
+#define CAM_ICP_CTX_RESPONSE_TIME_THRESHOLD   300000
+
 /**
  * struct icp_hfi_mem_info
  * @qtbl: Memory info of queue table
@@ -171,6 +177,7 @@ struct cam_icp_clk_bw_req_internal_v2 {
  * @clk_info: Clock information for a request
  * @clk_info_v2: Clock info for AXI bw voting v2
  * @frame_info: information needed to process request
+ * @submit_timestamp: Submit timestamp to hw
  */
 struct hfi_frame_process_info {
 	struct hfi_cmd_ipebps_async hfi_frame_cmd[CAM_FRAME_CMD_MAX];
@@ -186,6 +193,7 @@ struct hfi_frame_process_info {
 	struct cam_icp_clk_bw_request clk_info[CAM_FRAME_CMD_MAX];
 	struct cam_icp_clk_bw_req_internal_v2 clk_info_v2[CAM_FRAME_CMD_MAX];
 	struct icp_frame_info frame_info[CAM_FRAME_CMD_MAX];
+	ktime_t submit_timestamp[CAM_FRAME_CMD_MAX];
 };
 
 /**

+ 2 - 1
drivers/cam_icp/icp_hw/icp_hw_mgr/include/cam_a5_hw_intf.h

@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0-only */
 /*
- * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
  */
 
 #ifndef CAM_A5_HW_INTF_H
@@ -27,6 +27,7 @@ enum cam_icp_a5_cmd_type {
 	CAM_ICP_A5_CMD_UBWC_CFG,
 	CAM_ICP_A5_CMD_PC_PREP,
 	CAM_ICP_A5_CMD_CLK_UPDATE,
+	CAM_ICP_A5_CMD_HW_DUMP,
 	CAM_ICP_A5_CMD_MAX,
 };
 

+ 28 - 1
drivers/cam_icp/icp_hw/include/cam_icp_hw_mgr_intf.h

@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0-only */
 /*
- * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
  */
 
 #ifndef CAM_ICP_HW_MGR_INTF_H
@@ -27,6 +27,9 @@
 #define CAM_ICP_DEFAULT_AXI_PATH    CAM_AXI_PATH_DATA_ALL
 #define CAM_ICP_DEFAULT_AXI_TRANSAC CAM_AXI_TRANSACTION_READ
 
+#define CAM_ICP_DUMP_TAG_MAX_LEN 32
+#define CAM_ICP_DUMP_NUM_WORDS   5
+
 int cam_icp_hw_mgr_init(struct device_node *of_node,
 	uint64_t *hw_mgr_hdl, int *iommu_hdl);
 
@@ -44,4 +47,28 @@ struct cam_icp_cpas_vote {
 	uint32_t axi_vote_valid;
 };
 
+/**
+ * struct cam_icp_hw_dump_args
+ * @cpu_addr: kernel vaddr
+ * @buf_len:  buffer length
+ * @offset:   offset
+ */
+struct cam_icp_hw_dump_args {
+	uintptr_t  cpu_addr;
+	size_t     buf_len;
+	size_t     offset;
+};
+
+/**
+ * struct cam_icp_dump_header
+ * @tag:        tag of the packet
+ * @size:       size of data in packet
+ * @word_size:  size of each word in packet
+ */
+struct cam_icp_dump_header {
+	uint8_t    tag[CAM_ICP_DUMP_TAG_MAX_LEN];
+	uint64_t   size;
+	int32_t    word_size;
+};
+
 #endif /* CAM_ICP_HW_MGR_INTF_H */

+ 15 - 1
drivers/cam_jpeg/cam_jpeg_context.c

@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /*
- * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
  */
 
 #include <linux/debugfs.h>
@@ -83,6 +83,19 @@ static int __cam_jpeg_ctx_release_dev_in_acquired(struct cam_context *ctx,
 	return rc;
 }
 
+static int __cam_jpeg_ctx_dump_dev_in_acquired(
+	struct cam_context      *ctx,
+	struct cam_dump_req_cmd *cmd)
+{
+	int rc;
+
+	rc = cam_context_dump_dev_to_hw(ctx, cmd);
+	if (rc)
+		CAM_ERR(CAM_JPEG, "Failed to dump device, rc=%d", rc);
+
+	return rc;
+}
+
 static int __cam_jpeg_ctx_flush_dev_in_acquired(struct cam_context *ctx,
 	struct cam_flush_dev_cmd *cmd)
 {
@@ -145,6 +158,7 @@ static struct cam_ctx_ops
 			.config_dev = __cam_jpeg_ctx_config_dev_in_acquired,
 			.stop_dev = __cam_jpeg_ctx_stop_dev_in_acquired,
 			.flush_dev = __cam_jpeg_ctx_flush_dev_in_acquired,
+			.dump_dev = __cam_jpeg_ctx_dump_dev_in_acquired,
 		},
 		.crm_ops = { },
 		.irq_ops = __cam_jpeg_ctx_handle_buf_done_in_acquired,

+ 139 - 0
drivers/cam_jpeg/jpeg_hw/cam_jpeg_hw_mgr.c

@@ -488,6 +488,7 @@ static int cam_jpeg_mgr_process_cmd(void *priv, void *data)
 			rc);
 		goto end_callcb;
 	}
+	p_cfg_req->submit_timestamp = ktime_get();
 
 	mutex_unlock(&hw_mgr->hw_mgr_mutex);
 	return rc;
@@ -1491,6 +1492,143 @@ num_dev_failed:
 	return rc;
 }
 
+static int cam_jpeg_mgr_hw_dump(void *hw_mgr_priv, void *dump_hw_args)
+{
+	int                             rc;
+	uint8_t                        *dst;
+	ktime_t                         cur_time;
+	size_t                          remain_len;
+	uint32_t                        min_len;
+	uint32_t                        dev_type;
+	uint64_t                        diff;
+	uint64_t                       *addr, *start;
+	struct timespec64               cur_ts;
+	struct timespec64               req_ts;
+	struct cam_jpeg_hw_mgr         *hw_mgr;
+	struct cam_hw_dump_args        *dump_args;
+	struct cam_jpeg_hw_cfg_req     *p_cfg_req;
+	struct cam_jpeg_hw_ctx_data    *ctx_data;
+	struct cam_jpeg_hw_dump_args    jpeg_dump_args;
+	struct cam_jpeg_hw_dump_header *hdr;
+
+	if (!hw_mgr_priv || !dump_hw_args) {
+		CAM_ERR(CAM_JPEG, "Invalid args %pK %pK",
+			hw_mgr_priv, dump_hw_args);
+		return -EINVAL;
+	}
+
+	hw_mgr = hw_mgr_priv;
+	dump_args = (struct cam_hw_dump_args *)dump_hw_args;
+	ctx_data = (struct cam_jpeg_hw_ctx_data *)dump_args->ctxt_to_hw_map;
+
+	if (!ctx_data) {
+		CAM_ERR(CAM_JPEG, "Invalid context");
+		return -EINVAL;
+	}
+
+	mutex_lock(&hw_mgr->hw_mgr_mutex);
+
+	if (!ctx_data->in_use) {
+		CAM_ERR(CAM_JPEG, "ctx is not in use");
+		mutex_unlock(&hw_mgr->hw_mgr_mutex);
+		return -EINVAL;
+	}
+
+	dev_type = ctx_data->jpeg_dev_acquire_info.dev_type;
+
+	if (true == hw_mgr->device_in_use[dev_type][0]) {
+		p_cfg_req = hw_mgr->dev_hw_cfg_args[dev_type][0];
+		if (p_cfg_req  && p_cfg_req->req_id ==
+			    (uintptr_t)dump_args->request_id)
+			goto hw_dump;
+	}
+
+	mutex_unlock(&hw_mgr->hw_mgr_mutex);
+	return 0;
+
+hw_dump:
+	cur_time = ktime_get();
+	diff = ktime_us_delta(p_cfg_req->submit_timestamp, cur_time);
+	cur_ts = ktime_to_timespec64(cur_time);
+	req_ts = ktime_to_timespec64(p_cfg_req->submit_timestamp);
+
+	if (diff < CAM_JPEG_RESPONSE_TIME_THRESHOLD) {
+		CAM_INFO(CAM_JPEG,
+			"No error req %lld %ld:%06ld %ld:%06ld",
+			dump_args->request_id,
+			req_ts.tv_sec,
+			req_ts.tv_nsec/NSEC_PER_USEC,
+			cur_ts.tv_sec,
+			cur_ts.tv_nsec/NSEC_PER_USEC);
+		mutex_unlock(&hw_mgr->hw_mgr_mutex);
+		return 0;
+	}
+
+	CAM_INFO(CAM_JPEG,
+		"Error req %lld %ld:%06ld %ld:%06ld",
+		dump_args->request_id,
+		req_ts.tv_sec,
+		req_ts.tv_nsec/NSEC_PER_USEC,
+		cur_ts.tv_sec,
+		cur_ts.tv_nsec/NSEC_PER_USEC);
+	rc  = cam_mem_get_cpu_buf(dump_args->buf_handle,
+		&jpeg_dump_args.cpu_addr, &jpeg_dump_args.buf_len);
+	if (rc) {
+		CAM_ERR(CAM_JPEG, "Invalid handle %u rc %d",
+			dump_args->buf_handle, rc);
+		mutex_unlock(&hw_mgr->hw_mgr_mutex);
+		return -rc;
+	}
+
+	if (jpeg_dump_args.buf_len <= dump_args->offset) {
+		CAM_WARN(CAM_JPEG, "dump offset overshoot len %zu offset %zu",
+			jpeg_dump_args.buf_len, dump_args->offset);
+		mutex_unlock(&hw_mgr->hw_mgr_mutex);
+		return -ENOSPC;
+	}
+
+	remain_len = jpeg_dump_args.buf_len - dump_args->offset;
+	min_len = sizeof(struct cam_jpeg_hw_dump_header) +
+			(CAM_JPEG_HW_DUMP_NUM_WORDS * sizeof(uint64_t));
+	if (remain_len < min_len) {
+		CAM_WARN(CAM_JPEG, "dump buffer exhaust remain %zu min %u",
+			remain_len, min_len);
+		mutex_unlock(&hw_mgr->hw_mgr_mutex);
+		return -ENOSPC;
+	}
+
+	dst = (uint8_t *)jpeg_dump_args.cpu_addr + dump_args->offset;
+	hdr = (struct cam_jpeg_hw_dump_header *)dst;
+	scnprintf(hdr->tag, CAM_JPEG_HW_DUMP_TAG_MAX_LEN,
+		"JPEG_REQ:");
+	hdr->word_size = sizeof(uint64_t);
+	addr = (uint64_t *)(dst + sizeof(struct cam_jpeg_hw_dump_header));
+	start = addr;
+	*addr++ = dump_args->request_id;
+	*addr++ = req_ts.tv_sec;
+	*addr++ = req_ts.tv_nsec/NSEC_PER_USEC;
+	*addr++ = cur_ts.tv_sec;
+	*addr++ = cur_ts.tv_nsec/NSEC_PER_USEC;
+	hdr->size = hdr->word_size * (addr - start);
+	dump_args->offset += hdr->size +
+		sizeof(struct cam_jpeg_hw_dump_header);
+	jpeg_dump_args.request_id = dump_args->request_id;
+	jpeg_dump_args.offset = dump_args->offset;
+
+	if (hw_mgr->devices[dev_type][0]->hw_ops.process_cmd) {
+		rc = hw_mgr->devices[dev_type][0]->hw_ops.process_cmd(
+			hw_mgr->devices[dev_type][0]->hw_priv,
+			CAM_JPEG_CMD_HW_DUMP,
+			&jpeg_dump_args, sizeof(jpeg_dump_args));
+	}
+
+	mutex_unlock(&hw_mgr->hw_mgr_mutex);
+	CAM_DBG(CAM_JPEG, "Offset before %u after %u",
+		dump_args->offset, jpeg_dump_args.offset);
+	dump_args->offset = jpeg_dump_args.offset;
+	return rc;
+}
+
 static int cam_jpeg_mgr_cmd(void *hw_mgr_priv, void *cmd_args)
 {
 	int rc = 0;
@@ -1544,6 +1682,7 @@ int cam_jpeg_hw_mgr_init(struct device_node *of_node, uint64_t *hw_mgr_hdl,
 	hw_mgr_intf->hw_flush = cam_jpeg_mgr_hw_flush;
 	hw_mgr_intf->hw_stop = cam_jpeg_mgr_hw_stop;
 	hw_mgr_intf->hw_cmd = cam_jpeg_mgr_cmd;
+	hw_mgr_intf->hw_dump = cam_jpeg_mgr_hw_dump;
 
 	mutex_init(&g_jpeg_hw_mgr.hw_mgr_mutex);
 	spin_lock_init(&g_jpeg_hw_mgr.hw_mgr_lock);

+ 9 - 1
drivers/cam_jpeg/jpeg_hw/cam_jpeg_hw_mgr.h

@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0-only */
 /*
- * Copyright (c) 2017-2018, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
  */
 
 #ifndef CAM_JPEG_HW_MGR_H
@@ -21,6 +21,12 @@
 #define CAM_JPEG_WORKQ_TASK_MSG_TYPE 2
 #define CAM_JPEG_HW_CFG_Q_MAX        50
 
+/*
+ * Response time threshold in ms beyond which a request is not expected
+ * to be with JPEG hw
+ */
+#define CAM_JPEG_RESPONSE_TIME_THRESHOLD   100000
+
 /**
  * struct cam_jpeg_process_frame_work_data_t
  *
@@ -69,12 +75,14 @@ struct cam_jpeg_hw_cdm_info_t {
  * @hw_cfg_args: Hw config args
  * @dev_type: Dev type for cfg request
  * @req_id: Request Id
+ * @submit_timestamp: Timestamp of submitting request
  */
 struct cam_jpeg_hw_cfg_req {
 	struct list_head list;
 	struct cam_hw_config_args hw_cfg_args;
 	uint32_t dev_type;
 	uintptr_t req_id;
+	ktime_t    submit_timestamp;
 };
 
 /**

+ 18 - 1
drivers/cam_jpeg/jpeg_hw/include/cam_jpeg_hw_intf.h

@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0-only */
 /*
- * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
  */
 
 #ifndef CAM_JPEG_HW_INTF_H
@@ -15,6 +15,9 @@
 
 #define JPEG_VOTE                     640000000
 
+#define CAM_JPEG_HW_DUMP_TAG_MAX_LEN 32
+#define CAM_JPEG_HW_DUMP_NUM_WORDS   5
+
 enum cam_jpeg_hw_type {
 	CAM_JPEG_DEV_ENC,
 	CAM_JPEG_DEV_DMA,
@@ -27,9 +30,23 @@ struct cam_jpeg_set_irq_cb {
 	uint32_t b_set_cb;
 };
 
+struct cam_jpeg_hw_dump_args {
+	uint64_t  request_id;
+	uintptr_t cpu_addr;
+	size_t    offset;
+	size_t    buf_len;
+};
+
+struct cam_jpeg_hw_dump_header {
+	uint8_t     tag[CAM_JPEG_HW_DUMP_TAG_MAX_LEN];
+	uint64_t    size;
+	uint32_t    word_size;
+};
+
 enum cam_jpeg_cmd_type {
 	CAM_JPEG_CMD_CDM_CFG,
 	CAM_JPEG_CMD_SET_IRQ_CB,
+	CAM_JPEG_CMD_HW_DUMP,
 	CAM_JPEG_CMD_MAX,
 };
 

+ 5 - 1
drivers/cam_jpeg/jpeg_hw/jpeg_enc_hw/cam_jpeg_enc_hw_info_ver_4_2_0.h

@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0-only */
 /*
- * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
  */
 
 #ifndef CAM_JPEG_ENC_HW_INFO_TITAN170_H
@@ -66,6 +66,10 @@ static struct cam_jpeg_enc_device_hw_info cam_jpeg_enc_hw_info = {
 		.resetdone = CAM_JPEG_HW_MASK_COMP_RESET_ACK,
 		.iserror = CAM_JPEG_HW_MASK_COMP_ERR,
 		.stopdone = CAM_JPEG_HW_IRQ_STATUS_STOP_DONE_MASK,
+	},
+	.reg_dump = {
+		.start_offset = 0x0,
+		.end_offset = 0x33C,
 	}
 };
 

+ 82 - 1
drivers/cam_jpeg/jpeg_hw/jpeg_enc_hw/jpeg_enc_core.c

@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /*
- * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
  */
 
 #include <linux/of.h>
@@ -378,6 +378,81 @@ int cam_jpeg_enc_stop_hw(void *data,
 	return 0;
 }
 
+int cam_jpeg_enc_hw_dump(
+	struct cam_hw_info           *jpeg_enc_dev,
+	struct cam_jpeg_hw_dump_args *dump_args)
+{
+
+	int                                   i;
+	uint8_t                              *dst;
+	uint32_t                             *addr, *start;
+	uint32_t                              num_reg, min_len;
+	uint32_t                              reg_start_offset;
+	size_t                                remain_len;
+	struct cam_hw_soc_info               *soc_info;
+	struct cam_jpeg_hw_dump_header       *hdr;
+	struct cam_jpeg_enc_device_hw_info   *hw_info;
+	struct cam_jpeg_enc_device_core_info *core_info;
+
+	soc_info = &jpeg_enc_dev->soc_info;
+	core_info = (struct cam_jpeg_enc_device_core_info *)
+		jpeg_enc_dev->core_info;
+	hw_info = core_info->jpeg_enc_hw_info;
+	mutex_lock(&core_info->core_mutex);
+	spin_lock(&jpeg_enc_dev->hw_lock);
+
+	if (jpeg_enc_dev->hw_state == CAM_HW_STATE_POWER_DOWN) {
+		CAM_ERR(CAM_JPEG, "JPEG HW is in off state");
+		spin_unlock(&jpeg_enc_dev->hw_lock);
+		mutex_unlock(&core_info->core_mutex);
+		return -EINVAL;
+	}
+
+	spin_unlock(&jpeg_enc_dev->hw_lock);
+
+	if (dump_args->buf_len <= dump_args->offset) {
+		CAM_WARN(CAM_JPEG, "dump buffer overshoot %zu %zu",
+			dump_args->buf_len, dump_args->offset);
+		mutex_unlock(&core_info->core_mutex);
+		return -ENOSPC;
+	}
+
+	remain_len = dump_args->buf_len - dump_args->offset;
+	min_len =  sizeof(struct cam_jpeg_hw_dump_header) +
+		    soc_info->reg_map[0].size + sizeof(uint32_t);
+	if (remain_len < min_len) {
+		CAM_WARN(CAM_JPEG, "dump buffer exhaust %zu %u",
+			remain_len, min_len);
+		mutex_unlock(&core_info->core_mutex);
+		return -ENOSPC;
+	}
+
+	dst = (uint8_t *)dump_args->cpu_addr + dump_args->offset;
+	hdr = (struct cam_jpeg_hw_dump_header *)dst;
+	snprintf(hdr->tag, CAM_JPEG_HW_DUMP_TAG_MAX_LEN,
+		"JPEG_REG:");
+	hdr->word_size = sizeof(uint32_t);
+	addr = (uint32_t *)(dst + sizeof(struct cam_jpeg_hw_dump_header));
+	start = addr;
+	*addr++ = soc_info->index;
+	num_reg = (hw_info->reg_dump.end_offset -
+		hw_info->reg_dump.start_offset)/4;
+	reg_start_offset = hw_info->reg_dump.start_offset;
+	for (i = 0; i < num_reg; i++) {
+		*addr++ = soc_info->mem_block[0]->start +
+			reg_start_offset + i*4;
+		*addr++ = cam_io_r(soc_info->reg_map[0].mem_base + (i*4));
+	}
+
+	mutex_unlock(&core_info->core_mutex);
+	hdr->size = hdr->word_size * (addr - start);
+	dump_args->offset += hdr->size +
+		sizeof(struct cam_jpeg_hw_dump_header);
+	CAM_DBG(CAM_JPEG, "offset %zu", dump_args->offset);
+
+	return 0;
+}
+
 int cam_jpeg_enc_process_cmd(void *device_priv, uint32_t cmd_type,
 	void *cmd_args, uint32_t arg_size)
 {
@@ -418,6 +493,12 @@ int cam_jpeg_enc_process_cmd(void *device_priv, uint32_t cmd_type,
 		rc = 0;
 		break;
 	}
+	case CAM_JPEG_CMD_HW_DUMP:
+	{
+		rc = cam_jpeg_enc_hw_dump(jpeg_enc_dev,
+			cmd_args);
+		break;
+	}
 	default:
 		rc = -EINVAL;
 		break;

+ 7 - 1
drivers/cam_jpeg/jpeg_hw/jpeg_enc_hw/jpeg_enc_core.h

@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0-only */
 /*
- * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
  */
 
 #ifndef CAM_JPEG_ENC_CORE_H
@@ -39,10 +39,16 @@ struct cam_jpeg_enc_int_status {
 	uint32_t stopdone;
 };
 
+struct cam_jpeg_enc_reg_dump {
+	uint32_t start_offset;
+	uint32_t end_offset;
+};
+
 struct cam_jpeg_enc_device_hw_info {
 	struct cam_jpeg_enc_reg_offsets reg_offset;
 	struct cam_jpeg_enc_regval reg_val;
 	struct cam_jpeg_enc_int_status int_status;
+	struct cam_jpeg_enc_reg_dump reg_dump;
 };
 
 enum cam_jpeg_enc_core_state {

+ 17 - 1
drivers/cam_lrme/cam_lrme_context.c

@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /*
- * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
  */
 
 #include <linux/module.h>
@@ -86,6 +86,21 @@ static int __cam_lrme_ctx_config_dev_in_activated(struct cam_context *ctx,
 	return rc;
 }
 
+static int __cam_lrme_ctx_dump_dev_in_activated(
+	struct cam_context      *ctx,
+	struct cam_dump_req_cmd *cmd)
+{
+	int rc = 0;
+
+	CAM_DBG(CAM_LRME, "Enter ctx %d", ctx->ctx_id);
+
+	rc = cam_context_dump_dev_to_hw(ctx, cmd);
+	if (rc)
+		CAM_ERR(CAM_LRME, "Failed to dump device");
+
+	return rc;
+}
+
 static int __cam_lrme_ctx_flush_dev_in_activated(struct cam_context *ctx,
 	struct cam_flush_dev_cmd *cmd)
 {
@@ -199,6 +214,7 @@ static struct cam_ctx_ops
 			.release_dev = __cam_lrme_ctx_release_dev_in_activated,
 			.stop_dev = __cam_lrme_ctx_stop_dev_in_activated,
 			.flush_dev = __cam_lrme_ctx_flush_dev_in_activated,
+			.dump_dev = __cam_lrme_ctx_dump_dev_in_activated,
 		},
 		.crm_ops = {},
 		.irq_ops = __cam_lrme_ctx_handle_irq_in_activated,

+ 46 - 1
drivers/cam_lrme/lrme_hw_mgr/cam_lrme_hw_mgr.c

@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /*
- * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
  */
 
 #include <linux/module.h>
@@ -649,6 +649,50 @@ static int cam_lrme_mgr_hw_release(void *hw_mgr_priv, void *hw_release_args)
 	return rc;
 }
 
+static int cam_lrme_mgr_hw_dump(void *hw_mgr_priv, void *hw_dump_args)
+{
+	struct cam_hw_dump_args *dump_args = hw_dump_args;
+	struct cam_lrme_hw_mgr *hw_mgr = hw_mgr_priv;
+	struct cam_lrme_device *hw_device;
+	int rc = 0;
+	uint32_t device_index;
+	struct cam_lrme_hw_dump_args lrme_dump_args;
+
+	device_index = CAM_LRME_DECODE_DEVICE_INDEX(dump_args->ctxt_to_hw_map);
+	if (device_index >= hw_mgr->device_count) {
+		CAM_ERR(CAM_LRME, "Invalid device index %d", device_index);
+		return -EPERM;
+	}
+
+	CAM_DBG(CAM_LRME, "Start device index %d", device_index);
+
+	rc = cam_lrme_mgr_util_get_device(hw_mgr, device_index, &hw_device);
+	if (rc) {
+		CAM_ERR(CAM_LRME, "Failed to get hw device");
+		return rc;
+	}
+	rc  = cam_mem_get_cpu_buf(dump_args->buf_handle,
+		&lrme_dump_args.cpu_addr,
+		&lrme_dump_args.buf_len);
+	if (rc) {
+		CAM_ERR(CAM_LRME, "Invalid handle %u rc %d",
+			dump_args->buf_handle, rc);
+		return rc;
+	}
+	lrme_dump_args.offset =  dump_args->offset;
+	lrme_dump_args.request_id = dump_args->request_id;
+
+	rc = hw_device->hw_intf.hw_ops.process_cmd(
+		hw_device->hw_intf.hw_priv,
+		CAM_LRME_HW_CMD_DUMP,
+		&lrme_dump_args,
+		sizeof(struct cam_lrme_hw_dump_args));
+	CAM_DBG(CAM_LRME, "Offset before %zu after %zu",
+		dump_args->offset, lrme_dump_args.offset);
+	dump_args->offset = lrme_dump_args.offset;
+	return rc;
+}
+
 static int cam_lrme_mgr_hw_flush(void *hw_mgr_priv, void *hw_flush_args)
 {	int rc = 0, i;
 	struct cam_lrme_hw_mgr *hw_mgr = hw_mgr_priv;
@@ -1147,6 +1191,7 @@ int cam_lrme_hw_mgr_init(struct cam_hw_mgr_intf *hw_mgr_intf,
 	hw_mgr_intf->hw_flush = cam_lrme_mgr_hw_flush;
 
 	g_lrme_hw_mgr.event_cb = cam_lrme_dev_buf_done_cb;
+	hw_mgr_intf->hw_dump = cam_lrme_mgr_hw_dump;
 
 	cam_lrme_mgr_create_debugfs_entry();
 

+ 162 - 0
drivers/cam_lrme/lrme_hw_mgr/lrme_hw/cam_lrme_hw_core.c

@@ -3,6 +3,7 @@
  * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
  */
 
+#include <linux/timer.h>
 #include "cam_lrme_hw_core.h"
 #include "cam_lrme_hw_soc.h"
 #include "cam_smmu_api.h"
@@ -21,6 +22,159 @@ static void cam_lrme_dump_registers(void __iomem *base)
 	cam_io_dump(base, 0x900, (0x928 - 0x900) / 0x4);
 }
 
+static int  cam_lrme_dump_regs_to_buf(
+	struct cam_lrme_frame_request *req,
+	struct cam_hw_info *lrme_hw,
+	struct cam_lrme_hw_dump_args *dump_args)
+{
+	int                             i;
+	uint8_t                        *dst;
+	uint32_t                       *addr, *start;
+	uint32_t                        num_reg, min_len;
+	size_t                          remain_len;
+	struct cam_hw_soc_info         *soc_info;
+	struct cam_lrme_hw_dump_header *hdr;
+
+	if (!lrme_hw || !req || !dump_args) {
+		CAM_ERR(CAM_LRME, "Invalid params %pK, %pK, %pK",
+			lrme_hw, req, dump_args);
+		return -EINVAL;
+	}
+	soc_info = &lrme_hw->soc_info;
+	if (dump_args->buf_len <= dump_args->offset) {
+		CAM_WARN(CAM_LRME, "dump buffer overshoot len %zu offset %zu",
+			dump_args->buf_len, dump_args->offset);
+		return -ENOSPC;
+	}
+	remain_len = dump_args->buf_len - dump_args->offset;
+	min_len =  sizeof(struct cam_lrme_hw_dump_header) +
+		    soc_info->reg_map[0].size + sizeof(uint32_t);
+
+	if (remain_len < min_len) {
+		CAM_WARN(CAM_LRME, "dump buffer exhaust remain %zu min %u",
+			remain_len, min_len);
+		return -ENOSPC;
+	}
+	dst = (uint8_t *)dump_args->cpu_addr + dump_args->offset;
+	hdr = (struct cam_lrme_hw_dump_header *)dst;
+	scnprintf(hdr->tag, CAM_LRME_HW_DUMP_TAG_MAX_LEN,
+		"LRME_REG:");
+	hdr->word_size = sizeof(uint32_t);
+	addr = (uint32_t *)(dst + sizeof(struct cam_lrme_hw_dump_header));
+	start = addr;
+	*addr++ = soc_info->index;
+	num_reg = soc_info->reg_map[0].size/4;
+	for (i = 0; i < num_reg; i++) {
+		*addr++ = soc_info->mem_block[0]->start + (i*4);
+		*addr++ = cam_io_r(soc_info->reg_map[0].mem_base + (i*4));
+	}
+	hdr->size = hdr->word_size * (addr - start);
+	dump_args->offset += hdr->size +
+		sizeof(struct cam_lrme_hw_dump_header);
+	CAM_DBG(CAM_LRME, "offset %zu", dump_args->offset);
+	return 0;
+}
+
+static int cam_lrme_hw_dump(
+	struct cam_hw_info           *lrme_hw,
+	struct cam_lrme_hw_dump_args *dump_args)
+{
+	uint8_t                        *dst;
+	ktime_t                         cur_time;
+	size_t                          remain_len;
+	uint32_t                        min_len;
+	uint64_t                        diff;
+	uint64_t                       *addr, *start;
+	struct timespec64               cur_ts;
+	struct timespec64               req_ts;
+	struct cam_lrme_core           *lrme_core;
+	struct cam_lrme_frame_request  *req = NULL;
+	struct cam_lrme_hw_dump_header *hdr;
+
+	mutex_lock(&lrme_hw->hw_mutex);
+	if (lrme_hw->hw_state == CAM_HW_STATE_POWER_DOWN) {
+		CAM_DBG(CAM_LRME, "LRME HW is in off state");
+		mutex_unlock(&lrme_hw->hw_mutex);
+		return 0;
+	}
+
+	lrme_core = (struct cam_lrme_core *)lrme_hw->core_info;
+
+	if (lrme_core->req_submit &&
+		lrme_core->req_submit->req_id == dump_args->request_id)
+		req = lrme_core->req_submit;
+	else if (lrme_core->req_proc &&
+		lrme_core->req_proc->req_id == dump_args->request_id)
+		req = lrme_core->req_proc;
+
+	if (!req) {
+		CAM_DBG(CAM_LRME, "LRME req %lld not with hw",
+			dump_args->request_id);
+		mutex_unlock(&lrme_hw->hw_mutex);
+		return 0;
+	}
+
+	cur_time = ktime_get();
+	diff = ktime_us_delta(req->submit_timestamp, cur_time);
+	cur_ts = ktime_to_timespec64(cur_time);
+	req_ts = ktime_to_timespec64(req->submit_timestamp);
+
+	if (diff < CAM_LRME_RESPONSE_TIME_THRESHOLD) {
+		CAM_INFO(CAM_LRME, "No error req %lld %ld:%06ld %ld:%06ld",
+			dump_args->request_id,
+			req_ts.tv_sec,
+			req_ts.tv_nsec/NSEC_PER_USEC,
+			cur_ts.tv_sec,
+			cur_ts.tv_nsec/NSEC_PER_USEC);
+		mutex_unlock(&lrme_hw->hw_mutex);
+		return 0;
+	}
+
+	CAM_INFO(CAM_LRME, "Error req %lld %ld:%06ld %ld:%06ld",
+		dump_args->request_id,
+		req_ts.tv_sec,
+		req_ts.tv_nsec/NSEC_PER_USEC,
+		cur_ts.tv_sec,
+		cur_ts.tv_nsec/NSEC_PER_USEC);
+
+	if (dump_args->buf_len <= dump_args->offset) {
+		CAM_WARN(CAM_LRME, "dump buffer overshoot len %zu offset %zu",
+			dump_args->buf_len, dump_args->offset);
+		mutex_unlock(&lrme_hw->hw_mutex);
+		return 0;
+	}
+
+	remain_len = dump_args->buf_len - dump_args->offset;
+	min_len =  sizeof(struct cam_lrme_hw_dump_header) +
+		    (CAM_LRME_HW_DUMP_NUM_WORDS * sizeof(uint64_t));
+
+	if (remain_len < min_len) {
+		CAM_WARN(CAM_LRME, "dump buffer exhaust remain %zu min %u",
+			remain_len, min_len);
+		mutex_unlock(&lrme_hw->hw_mutex);
+		return 0;
+	}
+
+	dst = (uint8_t *)dump_args->cpu_addr + dump_args->offset;
+	hdr = (struct cam_lrme_hw_dump_header *)dst;
+	scnprintf(hdr->tag, CAM_LRME_HW_DUMP_TAG_MAX_LEN,
+		"LRME_REQ:");
+	hdr->word_size = sizeof(uint64_t);
+	addr = (uint64_t *)(dst + sizeof(struct cam_lrme_hw_dump_header));
+	start = addr;
+	*addr++ = req->req_id;
+	*addr++ = req_ts.tv_sec;
+	*addr++ = req_ts.tv_nsec/NSEC_PER_USEC;
+	*addr++ = cur_ts.tv_sec;
+	*addr++ = cur_ts.tv_nsec/NSEC_PER_USEC;
+	hdr->size = hdr->word_size * (addr - start);
+	dump_args->offset += hdr->size +
+		sizeof(struct cam_lrme_hw_dump_header);
+	cam_lrme_dump_regs_to_buf(req, lrme_hw, dump_args);
+	mutex_unlock(&lrme_hw->hw_mutex);
+	return 0;
+}
+
 static void cam_lrme_cdm_write_reg_val_pair(uint32_t *buffer,
 	uint32_t *index, uint32_t reg_offset, uint32_t reg_value)
 {
@@ -959,6 +1113,8 @@ int cam_lrme_hw_submit_req(void *hw_priv, void *hw_submit_args,
 		goto error;
 	}
 
+	frame_req->submit_timestamp = ktime_get();
+
 	switch (lrme_core->state) {
 	case CAM_LRME_CORE_STATE_PROCESSING:
 		lrme_core->state = CAM_LRME_CORE_STATE_REQ_PROC_PEND;
@@ -1268,6 +1424,12 @@ int cam_lrme_hw_process_cmd(void *hw_priv, uint32_t cmd_type,
 		break;
 	}
 
+	case CAM_LRME_HW_CMD_DUMP: {
+		struct cam_lrme_hw_dump_args *dump_args =
+		   (struct cam_lrme_hw_dump_args *)cmd_args;
+		rc = cam_lrme_hw_dump(lrme_hw, dump_args);
+		break;
+		}
 	default:
 		break;
 	}

+ 19 - 1
drivers/cam_lrme/lrme_hw_mgr/lrme_hw/cam_lrme_hw_core.h

@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0-only */
 /*
- * Copyright (c) 2017-2018, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
  */
 
 #ifndef _CAM_LRME_HW_CORE_H_
@@ -35,6 +35,10 @@
 
 #define CAM_LRME_MAX_REG_PAIR_NUM 60
 
+#define CAM_LRME_RESPONSE_TIME_THRESHOLD 100000
+#define CAM_LRME_HW_DUMP_TAG_MAX_LEN     32
+#define CAM_LRME_HW_DUMP_NUM_WORDS       5
+
 /**
  * enum cam_lrme_irq_set
  *
@@ -432,6 +436,20 @@ struct cam_lrme_hw_info {
 	struct cam_lrme_titan_reg titan_reg;
 };
 
+/**
+ * struct cam_lrme_hw_dump_header : LRME hw dump header
+ *
+ * @tag       : LRME hw dump header tag
+ * @size      : Size of data
+ * @word_size : size of each word
+ */
+
+struct cam_lrme_hw_dump_header {
+	uint8_t     tag[CAM_LRME_HW_DUMP_TAG_MAX_LEN];
+	uint64_t    size;
+	uint32_t    word_size;
+};
+
 int cam_lrme_hw_process_irq(void *priv, void *data);
 int cam_lrme_hw_submit_req(void *hw_priv, void *hw_submit_args,
 	uint32_t arg_size);

+ 22 - 1
drivers/cam_lrme/lrme_hw_mgr/lrme_hw/cam_lrme_hw_intf.h

@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0-only */
 /*
- * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
  */
 
 #ifndef _CAM_LRME_HW_INTF_H_
@@ -59,12 +59,14 @@ enum cam_lrme_cb_type {
  * @CAM_LRME_HW_CMD_REGISTER_CB       : register HW manager callback
  * @CAM_LRME_HW_CMD_SUBMIT            : Submit frame to HW
  * @CAM_LRME_HW_CMD_DUMP_REGISTER     : dump register values
+ * @CAM_LRME_HW_CMD_DUMP              : dump register values to buffer
  */
 enum cam_lrme_hw_cmd_type {
 	CAM_LRME_HW_CMD_PREPARE_HW_UPDATE,
 	CAM_LRME_HW_CMD_REGISTER_CB,
 	CAM_LRME_HW_CMD_SUBMIT,
 	CAM_LRME_HW_CMD_DUMP_REGISTER,
+	CAM_LRME_HW_CMD_DUMP,
 };
 
 /**
@@ -87,6 +89,7 @@ enum cam_lrme_hw_reset_type {
  * @hw_device             : Pointer to HW device
  * @hw_update_entries     : List of hw_update_entries
  * @num_hw_update_entries : number of hw_update_entries
+ * @submit_timestamp      : timestamp of submitting request with hw
  */
 struct cam_lrme_frame_request {
 	struct list_head           frame_list;
@@ -95,6 +98,7 @@ struct cam_lrme_frame_request {
 	struct cam_lrme_device    *hw_device;
 	struct cam_hw_update_entry hw_update_entries[CAM_LRME_MAX_HW_ENTRIES];
 	uint32_t                   num_hw_update_entries;
+	ktime_t                    submit_timestamp;
 };
 
 /**
@@ -192,6 +196,21 @@ struct cam_lrme_hw_submit_args {
 	struct cam_lrme_frame_request *frame_req;
 };
 
+/**
+ * struct cam_lrme_hw_dump_args : Args for dump request
+ *
+ * @request_id   : Issue request id
+ * @cpu_addr     : start address of the target buffer
+ * @offset       : offset of the buffer
+ * @buf_len      : Length of target buffer
+ */
+struct cam_lrme_hw_dump_args {
+	uint64_t  request_id;
+	uintptr_t cpu_addr;
+	size_t    offset;
+	size_t    buf_len;
+};
+
 /**
  * @brief : API to register LRME hw to platform framework.
  * @return struct platform_device pointer on on success, or ERR_PTR() on error.
@@ -202,4 +221,6 @@ int cam_lrme_hw_init_module(void);
  * @brief : API to remove LRME Hw from platform framework.
  */
 void cam_lrme_hw_exit_module(void);
+
 #endif /* _CAM_LRME_HW_INTF_H_ */
+