msm: camera: add stats stream support

Stats is the statistics collected from ISP hardware. The purpose of
stats is to provide data to 3A algorithm to decide how to adjust the
3A gains. To start to collect the stats, we need to configure buffers
to save the stats so that it can be parsed to 3A to use. Besides, we
need to configure each stats stream info into AXI HW so the ISP HW
can start to collect the stats through AXI.

Change-Id: Ice730cd0b467da957d10f45dff4509e14508ce88
Signed-off-by: Mingcheng Zhu <mingchen@codeaurora.org>
diff --git a/drivers/media/video/msmb/isp/msm_buf_mgr.h b/drivers/media/video/msmb/isp/msm_buf_mgr.h
index a44b5ec..b96d9fe 100644
--- a/drivers/media/video/msmb/isp/msm_buf_mgr.h
+++ b/drivers/media/video/msmb/isp/msm_buf_mgr.h
@@ -17,9 +17,8 @@
 #include <mach/iommu_domains.h>
 #include "msm_sd.h"
 
-#define BUF_SRC_SHIFT 16
 /*Buffer source can be from userspace / HAL*/
-#define BUF_SRC(id) (id >> BUF_SRC_SHIFT)
+#define BUF_SRC(id) (id & ISP_NATIVE_BUF_BIT)
 
 struct msm_isp_buf_mgr;
 
diff --git a/drivers/media/video/msmb/isp/msm_isp.h b/drivers/media/video/msmb/isp/msm_isp.h
index c320a1a..1c82f45 100644
--- a/drivers/media/video/msmb/isp/msm_isp.h
+++ b/drivers/media/video/msmb/isp/msm_isp.h
@@ -132,6 +132,10 @@
 	int (*get_platform_data) (struct vfe_device *vfe_dev);
 };
 struct msm_vfe_stats_ops {
+	void (*cfg_framedrop) (struct vfe_device *vfe_dev,
+		struct msm_vfe_stats_stream *stream_info);
+	void (*clear_framedrop) (struct vfe_device *vfe_dev,
+		struct msm_vfe_stats_stream *stream_info);
 	void (*cfg_comp_mask) (struct vfe_device *vfe_dev,
 		struct msm_vfe_stats_stream *stream_info);
 	void (*clear_comp_mask) (struct vfe_device *vfe_dev,
@@ -142,7 +146,7 @@
 		struct msm_vfe_stats_stream *stream_info);
 
 	void (*cfg_wm_reg) (struct vfe_device *vfe_dev,
-		struct msm_vfe_stats_stream_request_cmd *stream_cfg_cmd);
+		struct msm_vfe_stats_stream *stream_info);
 	void (*clear_wm_reg) (struct vfe_device *vfe_dev,
 		struct msm_vfe_stats_stream *stream_info);
 
@@ -158,6 +162,10 @@
 	uint32_t (*get_frame_id) (struct vfe_device *vfe_dev);
 	uint32_t (*get_wm_mask) (uint32_t irq_status0, uint32_t irq_status1);
 	uint32_t (*get_comp_mask) (uint32_t irq_status0, uint32_t irq_status1);
+	uint32_t (*get_pingpong_status) (struct vfe_device *vfe_dev);
+	uint32_t (*get_active_pingpong_idx) (uint32_t pingpong_status,
+				enum msm_isp_stats_type stats_type);
+
 };
 
 struct msm_vfe_ops {
@@ -180,6 +188,7 @@
 	uint8_t num_rdi_master;
 	uint8_t num_comp_mask;
 	uint32_t min_wm_ub;
+	uint8_t num_stats_comp_mask;
 };
 
 enum msm_vfe_axi_state {
@@ -241,23 +250,33 @@
 	enum msm_vfe_inputmux input_mux;
 };
 
-
+enum msm_vfe_stats_state {
+	STATE_AVALIABLE,
+	STATE_INACTIVE,
+	STATE_ACTIVE,
+	STATE_START_PENDING,
+	STATE_STOP_PENDING,
+	STATE_STOPPING,
+};
 struct msm_vfe_stats_stream {
 	uint32_t frame_id;
 	uint8_t enable;
 	enum msm_isp_stats_type stats_type;
-	uint8_t comp_mask_index;
+	int8_t comp_mask_index;
 	struct msm_isp_buffer *buf[2];
 	uint32_t session_id;
 	uint32_t stream_id;
 	uint32_t bufq_handle;
 	uint32_t stream_handle;
 	uint32_t framedrop_pattern;
+	uint8_t comp_flag;
+	uint8_t comp_idx;
+	enum msm_vfe_stats_state state;
 };
 
 struct msm_vfe_stats_composite_info {
-	uint32_t stream_handle;
-	uint32_t stream_composite_mask;
+	uint32_t stats_mask;
+	uint8_t comp_flag;
 };
 
 enum msm_wm_ub_cfg_type {
@@ -288,7 +307,7 @@
 struct msm_vfe_stats_shared_data {
 	struct msm_vfe_stats_stream stream_info[MSM_ISP_STATS_MAX];
 	struct msm_vfe_stats_composite_info
-		comp_info[MAX_NUM_STATS_COMP_MASK];
+		composite_info[MAX_NUM_STATS_COMP_MASK];
 	uint8_t num_active_stream;
 	uint8_t num_used_composite_mask;
 	uint16_t stream_handle_cnt;
diff --git a/drivers/media/video/msmb/isp/msm_isp40.c b/drivers/media/video/msmb/isp/msm_isp40.c
index b9788eb..02ec0d9 100644
--- a/drivers/media/video/msmb/isp/msm_isp40.c
+++ b/drivers/media/video/msmb/isp/msm_isp40.c
@@ -937,7 +937,7 @@
 
 static void msm_vfe40_stats_cfg_wm_reg(
 	struct vfe_device *vfe_dev,
-	struct msm_vfe_stats_stream_request_cmd *stream_cfg_cmd)
+	struct msm_vfe_stats_stream *stream_info)
 {
 
 }
@@ -984,6 +984,56 @@
 {
 	return vfe_dev->axi_data.src_info[VFE_PIX_0].frame_id;
 }
+
+static uint32_t msm_vfe40_stats_get_active_pingpong_idx(
+	uint32_t pingpong_status,
+	enum msm_isp_stats_type stats_type)
+{
+	int pingpong_bit = 0;
+	switch (stats_type) {
+	case MSM_ISP_STATS_AWB:
+		if (pingpong_status & (1 << 11))
+			pingpong_bit = 1; /* pong is active */
+			break;
+	case MSM_ISP_STATS_RS:
+		if (pingpong_status & (1 << 12))
+			pingpong_bit = 1; /* pong is active */
+			break;
+	case MSM_ISP_STATS_CS:
+		if (pingpong_status & (1 << 13))
+			pingpong_bit = 1; /* pong is active */
+			break;
+	case MSM_ISP_STATS_IHIST:
+		if (pingpong_status & (1 << 14))
+			pingpong_bit = 1; /* pong is active */
+			break;
+	case MSM_ISP_STATS_BG:
+		if (pingpong_status & (1 << 9))
+			pingpong_bit = 1; /* pong is active */
+			break;
+	case MSM_ISP_STATS_BF:
+		if (pingpong_status & (1 << 10))
+			pingpong_bit = 1; /* pong is active */
+			break;
+	case MSM_ISP_STATS_BE:
+		if (pingpong_status & (1 << 8))
+			pingpong_bit = 1; /* pong is active */
+			break;
+	case MSM_ISP_STATS_BHIST:
+		if (pingpong_status & (1 << 15))
+			pingpong_bit = 1; /* pong is active */
+			break;
+	case MSM_ISP_STATS_SKIN:
+	case MSM_ISP_STATS_AEC:
+	case MSM_ISP_STATS_AF:
+	default:
+		pr_err("%s: not supported stats type = %d\n",
+			__func__, stats_type);
+		return -EINVAL;
+	}
+	return pingpong_bit;
+}
+
 static int msm_vfe40_get_platform_data(struct vfe_device *vfe_dev)
 {
 	int rc = 0;
@@ -1041,6 +1091,7 @@
 	.num_rdi = 3,
 	.num_rdi_master = 3,
 	.min_wm_ub = 64,
+	.num_stats_comp_mask = 2,
 };
 
 static struct v4l2_subdev_core_ops msm_vfe40_subdev_core_ops = {
@@ -1118,6 +1169,9 @@
 			.get_comp_mask = msm_vfe40_stats_get_comp_mask,
 			.get_wm_mask = msm_vfe40_stats_get_wm_mask,
 			.get_frame_id = msm_vfe40_stats_get_frame_id,
+			.get_pingpong_status = msm_vfe40_get_pingpong_status,
+			.get_active_pingpong_idx =
+				msm_vfe40_stats_get_active_pingpong_idx,
 		},
 	},
 	.axi_hw_info = &msm_vfe40_axi_hw_info,
diff --git a/drivers/media/video/msmb/isp/msm_isp_axi_util.c b/drivers/media/video/msmb/isp/msm_isp_axi_util.c
index fe55b40..7e62071 100644
--- a/drivers/media/video/msmb/isp/msm_isp_axi_util.c
+++ b/drivers/media/video/msmb/isp/msm_isp_axi_util.c
@@ -637,7 +637,6 @@
 		complete(&vfe_dev->stream_config_complete);
 	}
 }
-
 #define VFE_PING_FLAG 0xFFFFFFFF
 #define VFE_PONG_FLAG 0x0
 
@@ -687,7 +686,7 @@
 		vfe_dev, stream_info->wm[i],
 		pingpong_status, buf->mapped_info[i].paddr);
 
-	if (stream_info->buf[pingpong_bit]) {
+	if (stream_info->buf[pingpong_bit] && tv) {
 		if (stream_info->buf_divert) {
 			buf_event.frame_id = stream_info->frame_id;
 			buf_event.timestamp = *tv;
diff --git a/drivers/media/video/msmb/isp/msm_isp_stats_util.c b/drivers/media/video/msmb/isp/msm_isp_stats_util.c
index 15f4c23..86a4a3d 100644
--- a/drivers/media/video/msmb/isp/msm_isp_stats_util.c
+++ b/drivers/media/video/msmb/isp/msm_isp_stats_util.c
@@ -13,6 +13,41 @@
 #include <media/v4l2-subdev.h>
 #include "msm_isp_util.h"
 #include "msm_isp_stats_util.h"
+#define VFE_PING_ACTIVE_FLAG 0xFFFFFFFF
+#define VFE_PONG_ACTIVE_FLAG 0x0
+
+int msm_isp_stats_cfg_ping_pong_address(struct vfe_device *vfe_dev,
+	struct msm_vfe_stats_stream *stream_info, uint32_t pingpong_status,
+	struct msm_isp_buffer *out_buf)
+{
+	int rc = -1;
+	struct msm_isp_buffer *buf;
+	int active_bit = 0;
+	int buf_idx;
+	uint32_t bufq_handle = stream_info->bufq_handle;
+
+	out_buf = NULL;
+	active_bit = vfe_dev->hw_info->vfe_ops.stats_ops.
+		get_active_pingpong_idx(pingpong_status,
+			stream_info->stats_type);
+	buf_idx = active_bit^0x1;
+
+	rc = vfe_dev->buf_mgr->ops->get_buf(
+		vfe_dev->buf_mgr, bufq_handle, &buf);
+	if (rc < 0) {
+		pr_err("%s: No free buffer, stats_type = %d\n",
+			__func__, stream_info->stats_type);
+		return rc;
+	}
+	vfe_dev->hw_info->vfe_ops.stats_ops.update_ping_pong_addr(
+		vfe_dev, stream_info->stats_type,
+		pingpong_status, buf->mapped_info[buf_idx].paddr);
+
+	if (stream_info->buf[buf_idx])
+		out_buf = stream_info->buf[buf_idx];
+	stream_info->buf[buf_idx] = buf;
+	return 0;
+}
 
 void msm_isp_process_stats_irq(struct vfe_device *vfe_dev,
 	uint32_t irq_status0, uint32_t irq_status1,
@@ -22,7 +57,7 @@
 	uint32_t stats_comp_mask = 0, stats_mask = 0;
 	ISP_DBG("%s: status: 0x%x\n", __func__, irq_status0);
 	stats_comp_mask = vfe_dev->hw_info->vfe_ops.stats_ops.
-	get_comp_mask(irq_status0, irq_status1);
+		get_comp_mask(irq_status0, irq_status1);
 	stats_mask = vfe_dev->hw_info->vfe_ops.stats_ops.
 		get_wm_mask(irq_status0, irq_status1);
 	if (!(stats_comp_mask || stats_mask))
@@ -31,10 +66,100 @@
 	/* TD: process comp/non comp stats */
 }
 
+static int msm_isp_stats_reserve_comp_mask(
+	struct vfe_device *vfe_dev,
+	struct msm_vfe_stats_shared_data *stats_data,
+	struct msm_vfe_stats_stream *stream_info)
+{
+	int i;
+	uint8_t num_stats_comp;
+	int8_t comp_idx = -1;
+	if (stream_info->comp_flag == 0)
+		return 0;
+
+	num_stats_comp = vfe_dev->axi_data.hw_info->num_stats_comp_mask;
+	for (i = 0; i < num_stats_comp; i++) {
+		if (!stats_data->composite_info[i].stats_mask == 0 &&
+				comp_idx < 0)
+			comp_idx = i;
+		if (stats_data->composite_info[i].comp_flag ==
+				stream_info->comp_flag) {
+			comp_idx = i;
+			break;
+		}
+	}
+	if (comp_idx < 0) {
+		pr_err("%s: no more stats comp idx\n", __func__);
+		return -EACCES;
+	}
+	stats_data->composite_info[comp_idx].stats_mask |=
+		(1 << stream_info->stats_type);
+	if (stats_data->composite_info[comp_idx].comp_flag == 0)
+		stats_data->composite_info[comp_idx].comp_flag =
+			stream_info->comp_flag;
+	stream_info->comp_idx = comp_idx;
+	return 0;
+}
+
+static int msm_isp_stats_unreserve_comp_mask(
+	struct vfe_device *vfe_dev,
+	struct msm_vfe_stats_shared_data *stats_data,
+	struct msm_vfe_stats_stream *stream_info)
+{
+	uint8_t comp_idx = stream_info->comp_idx;
+
+	if (stream_info->comp_flag == 0)
+		return 0;
+	stats_data->composite_info[comp_idx].stats_mask &=
+		~(1 << stream_info->stats_type);
+	if (stats_data->composite_info[comp_idx].stats_mask == 0)
+		memset(&stats_data->composite_info[comp_idx], 0,
+			sizeof(struct msm_vfe_stats_composite_info));
+	return 0;
+}
+
 int msm_isp_request_stats_stream(struct vfe_device *vfe_dev, void *arg)
 {
 	int rc = 0;
-	/*To Do*/
+	struct msm_vfe_stats_stream_request_cmd *stream_cfg_cmd = arg;
+	struct msm_vfe_stats_stream *stream_info = NULL;
+	struct msm_vfe_stats_shared_data *stats_data = &vfe_dev->stats_data;
+
+	if (stream_cfg_cmd->stats_type < MSM_ISP_STATS_AEC ||
+			stream_cfg_cmd->stats_type >= MSM_ISP_STATS_MAX) {
+		pr_err("%s: invalid stats type %d received\n",
+		__func__, stream_cfg_cmd->stats_type);
+		return -EINVAL;
+	}
+	stream_info = &stats_data->stream_info[stream_cfg_cmd->stats_type];
+	if (stream_info->stream_handle != 0) {
+		pr_err("%s: stats type %d has been used already\n",
+			__func__, stream_cfg_cmd->stats_type);
+			return -EBUSY;
+	}
+
+	stats_data->stream_handle_cnt++;
+	if (stats_data->stream_handle_cnt == 0)
+		stats_data->stream_handle_cnt++;
+	stream_info->stream_handle =
+		stats_data->stream_handle_cnt << 16 |
+			stream_cfg_cmd->stats_type;
+	stream_info->enable = 0;
+	stream_info->stats_type = stream_cfg_cmd->stats_type;
+	stream_info->comp_flag = stream_cfg_cmd->comp_flag;
+	stream_info->session_id = stream_cfg_cmd->session_id;
+	stream_info->stream_id = stream_cfg_cmd->stream_id;
+	stream_info->framedrop_pattern = stream_cfg_cmd->framedrop_pattern;
+	stream_cfg_cmd->stream_handle =	stream_info->stream_handle;
+	msm_isp_stats_reserve_comp_mask(vfe_dev, stats_data, stream_info);
+	if (stream_info->comp_flag)
+		vfe_dev->hw_info->vfe_ops.stats_ops.
+			cfg_comp_mask(vfe_dev, stream_info);
+	else
+		vfe_dev->hw_info->vfe_ops.stats_ops.
+			cfg_wm_irq_mask(vfe_dev, stream_info);
+	vfe_dev->hw_info->vfe_ops.stats_ops.
+		cfg_wm_reg(vfe_dev, stream_info);
 	return rc;
 }
 
@@ -45,25 +170,94 @@
 	struct msm_vfe_stats_shared_data *stats_data = &vfe_dev->stats_data;
 	struct msm_vfe_stats_stream *stream_info =
 		&stats_data->stream_info[
-			(stream_release_cmd->stream_handle & 0xFF)];
+		(stream_release_cmd->stream_handle & 0xFF)];
 
-	if (stream_info == NULL)
-		rc = -1;
+	if (stream_info == NULL ||
+			stream_info->stream_handle !=
+				stream_release_cmd->stream_handle) {
+		pr_err("%s: handle mismatch(0x%x, 0x%x\n",
+			__func__, stream_info->stream_handle,
+			stream_release_cmd->stream_handle);
+		rc = -EINVAL;
+	}
+	if (stream_info->enable) {
+		struct msm_vfe_stats_stream_cfg_cmd stop_cmd;
+		memset(&stop_cmd, 0, sizeof(stop_cmd));
+		stop_cmd.num_streams = 1;
+		stop_cmd.stream_handle[0] = stream_release_cmd->stream_handle;
+		stop_cmd.enable = 0;
+		rc = msm_isp_cfg_stats_stream(vfe_dev, (void *)&stop_cmd);
+		if (rc < 0) {
+			pr_err("%s: cannot stop stats type %d\n",
+				__func__, stream_info->stats_type);
+			return -EPERM;
+		}
+	}
+	if (stream_info->bufq_handle) {
+		vfe_dev->buf_mgr->ops->release_buf(vfe_dev->buf_mgr,
+			stream_info->bufq_handle);
+		stream_info->bufq_handle = 0;
+	}
+	vfe_dev->hw_info->vfe_ops.stats_ops.
+		clear_wm_reg(vfe_dev, stream_info);
+	if (stream_info->comp_flag) {
+		msm_isp_stats_unreserve_comp_mask(vfe_dev,
+			stats_data, stream_info);
+		vfe_dev->hw_info->vfe_ops.stats_ops.
+		clear_comp_mask(vfe_dev, stream_info);
+	} else {
+		vfe_dev->hw_info->vfe_ops.stats_ops.
+		clear_wm_irq_mask(vfe_dev, stream_info);
+	}
+	vfe_dev->hw_info->vfe_ops.stats_ops.
+		clear_framedrop(vfe_dev, stream_info);
+	memset(stream_info, 0, sizeof(struct msm_vfe_stats_stream));
 	return rc;
 }
 
 int msm_isp_cfg_stats_stream(struct vfe_device *vfe_dev, void *arg)
 {
-	int rc = 0;
-	uint32_t stats_mask = 0;
-	uint8_t enable = 0;
+	int i, rc = 0;
 	uint32_t pingpong_status = 0;
 	struct msm_isp_buffer *buf = NULL;
-	enum msm_isp_stats_type stats_type = MSM_ISP_STATS_BE;
+	struct msm_vfe_stats_stream_cfg_cmd *stream_cfg_cmd = arg;
+	struct msm_vfe_stats_stream *stream_info;
+	struct msm_vfe_stats_shared_data *stats_data = &vfe_dev->stats_data;
+	int idx;
+	uint32_t stats_mask = 0;
+	uint8_t enable = stream_cfg_cmd->enable;
+
+	for (i = 0; i < stream_cfg_cmd->num_streams; i++) {
+		idx = stream_cfg_cmd->stream_handle[i] & 0xF;
+		stream_info = &stats_data->stream_info[idx];
+		if (stream_info->stream_handle !=
+				stream_cfg_cmd->stream_handle[i]) {
+			pr_err("%s: invalid stream handle -0x%x received\n",
+				__func__, stream_cfg_cmd->stream_handle[i]);
+			continue;
+		}
+		if (enable) {
+			stream_info->bufq_handle =
+				vfe_dev->buf_mgr->ops->get_bufq_handle(
+				vfe_dev->buf_mgr, stream_info->session_id,
+				stream_info->stream_id);
+				if (stream_info->bufq_handle == 0) {
+					pr_err("%s: no buf configured for stats type = %d\n",
+						__func__,
+						stream_info->stats_type);
+					return -EINVAL;
+				}
+		}
+		/* config ping address */
+		pingpong_status = VFE_PONG_ACTIVE_FLAG;
+		stats_mask |= (1 << stream_info->stats_type);
+		msm_isp_stats_cfg_ping_pong_address(vfe_dev,
+			stream_info, pingpong_status, buf);
+		pingpong_status = VFE_PING_ACTIVE_FLAG;
+		msm_isp_stats_cfg_ping_pong_address(vfe_dev,
+			stream_info, pingpong_status, buf);
+	}
 	vfe_dev->hw_info->vfe_ops.stats_ops.
-	stats_enable(vfe_dev, stats_mask, enable);
-	vfe_dev->hw_info->vfe_ops.stats_ops.
-	update_ping_pong_addr(vfe_dev, stats_type,
-		pingpong_status, buf->mapped_info[0].paddr);
+		stats_enable(vfe_dev, stats_mask, enable);
 	return rc;
 }
diff --git a/include/media/msmb_isp.h b/include/media/msmb_isp.h
index 01276bd..4e92f70 100644
--- a/include/media/msmb_isp.h
+++ b/include/media/msmb_isp.h
@@ -8,7 +8,8 @@
 
 #define ISP_VERSION_40        40
 #define ISP_VERSION_32        32
-
+#define ISP_NATIVE_BUF_BIT    0x10000
+#define ISP_STATS_STREAM_BIT  0x80000000
 
 enum ISP_START_PIXEL_PATTERN {
 	ISP_BAYER_RGRGRG,
@@ -160,6 +161,8 @@
 	uint32_t session_id;
 	uint32_t stream_id;
 	enum msm_isp_stats_type stats_type;
+	uint8_t comp_flag;
+	uint32_t framedrop_pattern;
 	uint32_t stream_handle;
 };
 struct msm_vfe_stats_stream_release_cmd {