ASoC: msm: Add incall recording support

Add two backend DAIs, and add two mixer controls to route
capture stream to the new backend DAIs. Start/stop record
commands will be sent to CVD if starting/stoping recording,
after voice call is established.

Change-Id: Iabff5390ca8c50ba05ae2dfa31fa3f1a8797f1db
Signed-off-by: Helen Zeng <xiaoyunz@codeaurora.org>
diff --git a/arch/arm/mach-msm/board-8960.c b/arch/arm/mach-msm/board-8960.c
index bd39579..e6ca8e0c 100644
--- a/arch/arm/mach-msm/board-8960.c
+++ b/arch/arm/mach-msm/board-8960.c
@@ -1772,6 +1772,8 @@
 	&msm_lpa_pcm,
 	&msm_compr_dsp,
 	&msm_cpudai_incall_music_rx,
+	&msm_cpudai_incall_record_rx,
+	&msm_cpudai_incall_record_tx,
 
 #if defined(CONFIG_CRYPTO_DEV_QCRYPTO) || \
 		defined(CONFIG_CRYPTO_DEV_QCRYPTO_MODULE)
@@ -1833,6 +1835,8 @@
 	&msm_pcm_afe,
 	&msm_compr_dsp,
 	&msm_cpudai_incall_music_rx,
+	&msm_cpudai_incall_record_rx,
+	&msm_cpudai_incall_record_tx,
 	&msm_pcm_hostless,
 	&msm_bus_apps_fabric,
 	&msm_bus_sys_fabric,
diff --git a/arch/arm/mach-msm/devices-8960.c b/arch/arm/mach-msm/devices-8960.c
index 0bb159a..2d06d97 100644
--- a/arch/arm/mach-msm/devices-8960.c
+++ b/arch/arm/mach-msm/devices-8960.c
@@ -1425,6 +1425,16 @@
 	.id     = 0x8005,
 };
 
+struct platform_device msm_cpudai_incall_record_rx = {
+	.name   = "msm-dai-q6",
+	.id     = 0x8004,
+};
+
+struct platform_device msm_cpudai_incall_record_tx = {
+	.name   = "msm-dai-q6",
+	.id     = 0x8003,
+};
+
 /*
  * Machine specific data for AUX PCM Interface
  * which the driver will  be unware of.
diff --git a/arch/arm/mach-msm/devices.h b/arch/arm/mach-msm/devices.h
index 384aced..0d6aa09 100644
--- a/arch/arm/mach-msm/devices.h
+++ b/arch/arm/mach-msm/devices.h
@@ -174,6 +174,8 @@
 extern struct platform_device msm_pcm_afe;
 extern struct platform_device msm_compr_dsp;
 extern struct platform_device msm_cpudai_incall_music_rx;
+extern struct platform_device msm_cpudai_incall_record_rx;
+extern struct platform_device msm_cpudai_incall_record_tx;
 
 extern struct platform_device msm_pil_q6v3;
 extern struct platform_device msm_8960_q6_lpass;
diff --git a/sound/soc/msm/msm-dai-q6.c b/sound/soc/msm/msm-dai-q6.c
index 00dc0b7..4af4f06 100644
--- a/sound/soc/msm/msm-dai-q6.c
+++ b/sound/soc/msm/msm-dai-q6.c
@@ -301,6 +301,8 @@
 		rc = msm_dai_q6_afe_rtproxy_hw_params(params, dai);
 		break;
 	case VOICE_PLAYBACK_TX:
+	case VOICE_RECORD_RX:
+	case VOICE_RECORD_TX:
 		rc = 0;
 		break;
 	default:
@@ -348,6 +350,8 @@
 	if (test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) {
 		switch (dai->id) {
 		case VOICE_PLAYBACK_TX:
+		case VOICE_RECORD_TX:
+		case VOICE_RECORD_RX:
 			pr_debug("%s, stop pseudo port:%d\n",
 						__func__,  dai->id);
 			rc = afe_stop_pseudo_port(dai->id);
@@ -492,6 +496,8 @@
 		if (!test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) {
 			switch (dai->id) {
 			case VOICE_PLAYBACK_TX:
+			case VOICE_RECORD_TX:
+			case VOICE_RECORD_RX:
 				afe_pseudo_port_start_nowait(dai->id);
 				break;
 			default:
@@ -509,6 +515,8 @@
 		if (test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) {
 			switch (dai->id) {
 			case VOICE_PLAYBACK_TX:
+			case VOICE_RECORD_TX:
+			case VOICE_RECORD_RX:
 				afe_pseudo_port_stop_nowait(dai->id);
 				break;
 			default:
@@ -614,6 +622,8 @@
 	if (test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) {
 		switch (dai->id) {
 		case VOICE_PLAYBACK_TX:
+		case VOICE_RECORD_TX:
+		case VOICE_RECORD_RX:
 			pr_debug("%s, stop pseudo port:%d\n",
 						__func__,  dai->id);
 			rc = afe_stop_pseudo_port(dai->id);
@@ -784,6 +794,21 @@
 	.remove = msm_dai_q6_dai_remove,
 };
 
+static struct snd_soc_dai_driver msm_dai_q6_incall_record_dai = {
+	.capture = {
+		.rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 |
+		SNDRV_PCM_RATE_16000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,
+		.channels_min = 1,
+		.channels_max = 2,
+		.rate_min =     8000,
+		.rate_max =     48000,
+	},
+	.ops = &msm_dai_q6_ops,
+	.probe = msm_dai_q6_dai_probe,
+	.remove = msm_dai_q6_dai_remove,
+};
+
 static struct snd_soc_dai_driver msm_dai_q6_bt_sco_rx_dai = {
 	.playback = {
 		.rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
@@ -925,6 +950,11 @@
 		rc = snd_soc_register_dai(&pdev->dev,
 					&msm_dai_q6_voice_playback_tx_dai);
 		break;
+	case VOICE_RECORD_RX:
+	case VOICE_RECORD_TX:
+		rc = snd_soc_register_dai(&pdev->dev,
+						&msm_dai_q6_incall_record_dai);
+		break;
 	default:
 		rc = -ENODEV;
 		break;
diff --git a/sound/soc/msm/msm-pcm-routing.c b/sound/soc/msm/msm-pcm-routing.c
index 933e4a8..453bce4 100644
--- a/sound/soc/msm/msm-pcm-routing.c
+++ b/sound/soc/msm/msm-pcm-routing.c
@@ -115,6 +115,8 @@
 	{ PCM_RX, 0, NULL, 0, 0},
 	{ PCM_TX, 0, NULL, 0, 0},
 	{ VOICE_PLAYBACK_TX, 0, NULL, 0, 0},
+	{ VOICE_RECORD_RX, 0, NULL, 0, 0},
+	{ VOICE_RECORD_TX, 0, NULL, 0, 0},
 };
 
 
@@ -286,6 +288,9 @@
 				fe_dai_map[val][session_type], path_type);
 		}
 	}
+	if ((msm_bedais[reg].port_id == VOICE_RECORD_RX)
+			|| (msm_bedais[reg].port_id == VOICE_RECORD_TX))
+		voc_start_record(msm_bedais[reg].port_id, set);
 
 	mutex_unlock(&routing_lock);
 }
@@ -780,6 +785,12 @@
 	SOC_SINGLE_EXT("AFE_PCM_TX", MSM_BACKEND_DAI_AFE_PCM_TX,
 		MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer,
 		msm_routing_put_audio_mixer),
+	SOC_SINGLE_EXT("VOC_REC_DL", MSM_BACKEND_DAI_INCALL_RECORD_RX,
+		MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer,
+		msm_routing_put_audio_mixer),
+	SOC_SINGLE_EXT("VOC_REC_UL", MSM_BACKEND_DAI_INCALL_RECORD_TX,
+		MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer,
+		msm_routing_put_audio_mixer),
 };
 
 static const struct snd_kcontrol_new mmul2_mixer_controls[] = {
@@ -1124,6 +1135,10 @@
 	/* incall */
 	SND_SOC_DAPM_AIF_OUT("VOICE_PLAYBACK_TX", "Voice Farend Playback",
 				0, 0, 0 , 0),
+	SND_SOC_DAPM_AIF_IN("INCALL_RECORD_TX", "Voice Uplink Capture",
+				0, 0, 0, 0),
+	SND_SOC_DAPM_AIF_IN("INCALL_RECORD_RX", "Voice Downlink Capture",
+				0, 0, 0, 0),
 	SND_SOC_DAPM_AIF_OUT("AUX_PCM_RX", "AUX PCM Playback", 0, 0, 0, 0),
 	SND_SOC_DAPM_AIF_IN("AUX_PCM_TX", "AUX PCM Capture", 0, 0, 0, 0),
 	/* Switch Definitions */
@@ -1210,6 +1225,10 @@
 	{"Incall_Music Audio Mixer", "MultiMedia1", "MM_DL1"},
 	{"Incall_Music Audio Mixer", "MultiMedia2", "MM_DL2"},
 	{"VOICE_PLAYBACK_TX", NULL, "Incall_Music Audio Mixer"},
+
+	{"MultiMedia1 Mixer", "VOC_REC_UL", "INCALL_RECORD_TX"},
+	{"MultiMedia1 Mixer", "VOC_REC_DL", "INCALL_RECORD_RX"},
+
 	{"MultiMedia1 Mixer", "PRI_TX", "PRI_I2S_TX"},
 	{"MultiMedia1 Mixer", "SLIM_0_TX", "SLIMBUS_0_TX"},
 	{"MultiMedia1 Mixer", "AUX_PCM_UL_TX", "AUX_PCM_TX"},
diff --git a/sound/soc/msm/msm-pcm-routing.h b/sound/soc/msm/msm-pcm-routing.h
index f45af05..2020939 100644
--- a/sound/soc/msm/msm-pcm-routing.h
+++ b/sound/soc/msm/msm-pcm-routing.h
@@ -27,6 +27,9 @@
 #define LPASS_BE_AUXPCM_RX "(Backend) AUX_PCM_RX"
 #define LPASS_BE_AUXPCM_TX "(Backend) AUX_PCM_TX"
 #define LPASS_BE_VOICE_PLAYBACK_TX "(Backend) VOICE_PLAYBACK_TX"
+#define LPASS_BE_INCALL_RECORD_RX "(Backend) INCALL_RECORD_TX"
+#define LPASS_BE_INCALL_RECORD_TX "(Backend) INCALL_RECORD_RX"
+
 
 /* For multimedia front-ends, asm session is allocated dynamically.
  * Hence, asm session/multimedia front-end mapping has to be maintained.
@@ -64,6 +67,8 @@
 	MSM_BACKEND_DAI_AUXPCM_RX,
 	MSM_BACKEND_DAI_AUXPCM_TX,
 	MSM_BACKEND_DAI_VOICE_PLAYBACK_TX,
+	MSM_BACKEND_DAI_INCALL_RECORD_RX,
+	MSM_BACKEND_DAI_INCALL_RECORD_TX,
 	MSM_BACKEND_DAI_MAX,
 };
 
diff --git a/sound/soc/msm/msm8960.c b/sound/soc/msm/msm8960.c
index 228047e..dd8224c 100644
--- a/sound/soc/msm/msm8960.c
+++ b/sound/soc/msm/msm8960.c
@@ -1074,6 +1074,32 @@
 		.be_id = MSM_BACKEND_DAI_VOICE_PLAYBACK_TX,
 		.be_hw_params_fixup = msm8960_be_hw_params_fixup,
 	},
+	/* Incall Record Uplink BACK END DAI Link */
+	{
+		.name = LPASS_BE_INCALL_RECORD_TX,
+		.stream_name = "Voice Uplink Capture",
+		.cpu_dai_name = "msm-dai-q6.32772",
+		.platform_name = "msm-pcm-routing",
+		.codec_name     = "msm-stub-codec.1",
+		.codec_dai_name = "msm-stub-tx",
+		.no_pcm = 1,
+		.no_codec = 1,
+		.be_id = MSM_BACKEND_DAI_INCALL_RECORD_TX,
+		.be_hw_params_fixup = msm8960_be_hw_params_fixup,
+	},
+	/* Incall Record Downlink BACK END DAI Link */
+	{
+		.name = LPASS_BE_INCALL_RECORD_RX,
+		.stream_name = "Voice Downlink Capture",
+		.cpu_dai_name = "msm-dai-q6.32771",
+		.platform_name = "msm-pcm-routing",
+		.codec_name     = "msm-stub-codec.1",
+		.codec_dai_name = "msm-stub-tx",
+		.no_pcm = 1,
+		.no_codec = 1,
+		.be_id = MSM_BACKEND_DAI_INCALL_RECORD_RX,
+		.be_hw_params_fixup = msm8960_be_hw_params_fixup,
+	},
 };
 
 struct snd_soc_card snd_soc_card_msm8960 = {
diff --git a/sound/soc/msm/qdsp6/q6voice.c b/sound/soc/msm/qdsp6/q6voice.c
index 6676d4d..de63fa0 100644
--- a/sound/soc/msm/qdsp6/q6voice.c
+++ b/sound/soc/msm/qdsp6/q6voice.c
@@ -56,6 +56,8 @@
 
 static int voice_cvs_stop_playback(struct voice_data *v);
 static int voice_cvs_start_playback(struct voice_data *v);
+static int voice_cvs_start_record(struct voice_data *v, uint32_t rec_mode);
+static int voice_cvs_stop_record(struct voice_data *v);
 
 static int32_t qdsp_mvm_callback(struct apr_client_data *data, void *priv);
 static int32_t qdsp_cvs_callback(struct apr_client_data *data, void *priv);
@@ -1921,10 +1923,14 @@
 	if (is_voip_session(v->session_id))
 		voice_send_netid_timing_cmd(v);
 
-	/* Start in-call music delivery if it's enabled */
+	/* Start in-call music delivery if this feature is enabled */
 	if (v->music_info.play_enable)
 		voice_cvs_start_playback(v);
 
+	/* Start in-call recording if this feature is enabled */
+	if (v->rec_info.rec_enable)
+		voice_cvs_start_record(v, v->rec_info.rec_mode);
+
 	rtac_add_voice(voice_get_cvs_handle(v),
 		voice_get_cvp_handle(v),
 		v->dev_rx.port_id, v->dev_tx.port_id,
@@ -2156,10 +2162,10 @@
 	mvm_handle = voice_get_mvm_handle(v);
 	cvp_handle = voice_get_cvp_handle(v);
 
-	/* stop playback */
+	/* stop playback or recording */
 	v->music_info.force = 1;
 	voice_cvs_stop_playback(v);
-
+	voice_cvs_stop_record(v);
 	/* send stop voice cmd */
 	voice_send_stop_voice_cmd(v);
 
@@ -2332,6 +2338,262 @@
 	return 0;
 }
 
+static int voice_cvs_start_record(struct voice_data *v, uint32_t rec_mode)
+{
+	int ret = 0;
+	void *apr_cvs;
+	u16 cvs_handle;
+
+	struct cvs_start_record_cmd cvs_start_record;
+
+	if (v == NULL) {
+		pr_err("%s: v is NULL\n", __func__);
+		return -EINVAL;
+	}
+	apr_cvs = common.apr_q6_cvs;
+
+	if (!apr_cvs) {
+		pr_err("%s: apr_cvs is NULL.\n", __func__);
+		return -EINVAL;
+	}
+
+	cvs_handle = voice_get_cvs_handle(v);
+
+	if (!v->rec_info.recording) {
+		cvs_start_record.hdr.hdr_field = APR_HDR_FIELD(
+					APR_MSG_TYPE_SEQ_CMD,
+					APR_HDR_LEN(APR_HDR_SIZE),
+					APR_PKT_VER);
+		cvs_start_record.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE,
+				  sizeof(cvs_start_record) - APR_HDR_SIZE);
+		cvs_start_record.hdr.src_port = v->session_id;
+		cvs_start_record.hdr.dest_port = cvs_handle;
+		cvs_start_record.hdr.token = 0;
+		cvs_start_record.hdr.opcode = VSS_ISTREAM_CMD_START_RECORD;
+
+		if (rec_mode == VOC_REC_UPLINK) {
+			cvs_start_record.rec_mode.rx_tap_point =
+						VSS_TAP_POINT_NONE;
+			cvs_start_record.rec_mode.tx_tap_point =
+						VSS_TAP_POINT_STREAM_END;
+		} else if (rec_mode == VOC_REC_DOWNLINK) {
+			cvs_start_record.rec_mode.rx_tap_point =
+						VSS_TAP_POINT_STREAM_END;
+			cvs_start_record.rec_mode.tx_tap_point =
+						VSS_TAP_POINT_NONE;
+		} else if (rec_mode == VOC_REC_BOTH) {
+			cvs_start_record.rec_mode.rx_tap_point =
+						VSS_TAP_POINT_STREAM_END;
+			cvs_start_record.rec_mode.tx_tap_point =
+						VSS_TAP_POINT_STREAM_END;
+		} else {
+			pr_err("%s: Invalid in-call rec_mode %d\n", __func__,
+				rec_mode);
+
+			ret = -EINVAL;
+			goto fail;
+		}
+
+		v->cvs_state = CMD_STATUS_FAIL;
+
+		ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_start_record);
+		if (ret < 0) {
+			pr_err("%s: Error %d sending START_RECORD\n", __func__,
+				ret);
+
+			goto fail;
+		}
+
+		ret = wait_event_timeout(v->cvs_wait,
+				 (v->cvs_state == CMD_STATUS_SUCCESS),
+				 msecs_to_jiffies(TIMEOUT_MS));
+
+		if (!ret) {
+			pr_err("%s: wait_event timeout\n", __func__);
+
+			goto fail;
+		}
+		v->rec_info.recording = 1;
+	} else {
+		pr_debug("%s: Start record already sent\n", __func__);
+	}
+
+	return 0;
+
+fail:
+	return ret;
+}
+
+static int voice_cvs_stop_record(struct voice_data *v)
+{
+	int ret = 0;
+	void *apr_cvs;
+	u16 cvs_handle;
+	struct apr_hdr cvs_stop_record;
+
+	if (v == NULL) {
+		pr_err("%s: v is NULL\n", __func__);
+		return -EINVAL;
+	}
+	apr_cvs = common.apr_q6_cvs;
+
+	if (!apr_cvs) {
+		pr_err("%s: apr_cvs is NULL.\n", __func__);
+		return -EINVAL;
+	}
+
+	cvs_handle = voice_get_cvs_handle(v);
+
+	if (v->rec_info.recording) {
+		cvs_stop_record.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
+				  APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER);
+		cvs_stop_record.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE,
+				  sizeof(cvs_stop_record) - APR_HDR_SIZE);
+		cvs_stop_record.src_port = v->session_id;
+		cvs_stop_record.dest_port = cvs_handle;
+		cvs_stop_record.token = 0;
+		cvs_stop_record.opcode = VSS_ISTREAM_CMD_STOP_RECORD;
+
+		v->cvs_state = CMD_STATUS_FAIL;
+
+		ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_stop_record);
+		if (ret < 0) {
+			pr_err("%s: Error %d sending STOP_RECORD\n",
+				__func__, ret);
+
+			goto fail;
+		}
+
+		ret = wait_event_timeout(v->cvs_wait,
+				 (v->cvs_state == CMD_STATUS_SUCCESS),
+				 msecs_to_jiffies(TIMEOUT_MS));
+		if (!ret) {
+			pr_err("%s: wait_event timeout\n", __func__);
+
+			goto fail;
+		}
+		v->rec_info.recording = 0;
+	} else {
+		pr_debug("%s: Stop record already sent\n", __func__);
+	}
+
+	return 0;
+
+fail:
+
+	return ret;
+}
+
+int voc_start_record(uint32_t port_id, uint32_t set)
+{
+	int ret = 0;
+	int rec_mode = 0;
+	u16 cvs_handle;
+	int i, rec_set = 0;
+
+	for (i = 0; i < MAX_VOC_SESSIONS; i++) {
+		struct voice_data *v = &common.voice[i];
+		pr_debug("%s: i:%d port_id: %d, set: %d\n",
+			__func__, i, port_id, set);
+
+		mutex_lock(&v->lock);
+		rec_mode = v->rec_info.rec_mode;
+		rec_set = set;
+		if (set) {
+			if ((v->rec_route_state.ul_flag != 0) &&
+				(v->rec_route_state.dl_flag != 0)) {
+				pr_debug("%s: i=%d, rec mode already set.\n",
+					__func__, i);
+				mutex_unlock(&v->lock);
+				if (i < MAX_VOC_SESSIONS)
+					continue;
+				else
+					return 0;
+			}
+
+			if (port_id == VOICE_RECORD_TX) {
+				if ((v->rec_route_state.ul_flag == 0)
+				&& (v->rec_route_state.dl_flag == 0)) {
+					rec_mode = VOC_REC_UPLINK;
+					v->rec_route_state.ul_flag = 1;
+				} else if ((v->rec_route_state.ul_flag == 0)
+					&& (v->rec_route_state.dl_flag != 0)) {
+					voice_cvs_stop_record(v);
+					rec_mode = VOC_REC_BOTH;
+					v->rec_route_state.ul_flag = 1;
+				}
+			} else if (port_id == VOICE_RECORD_RX) {
+				if ((v->rec_route_state.ul_flag == 0)
+					&& (v->rec_route_state.dl_flag == 0)) {
+					rec_mode = VOC_REC_DOWNLINK;
+					v->rec_route_state.dl_flag = 1;
+				} else if ((v->rec_route_state.ul_flag != 0)
+					&& (v->rec_route_state.dl_flag == 0)) {
+					voice_cvs_stop_record(v);
+					rec_mode = VOC_REC_BOTH;
+					v->rec_route_state.dl_flag = 1;
+				}
+			}
+			rec_set = 1;
+		} else {
+			if ((v->rec_route_state.ul_flag == 0) &&
+				(v->rec_route_state.dl_flag == 0)) {
+				pr_debug("%s: i=%d, rec already stops.\n",
+					__func__, i);
+				mutex_unlock(&v->lock);
+				if (i < MAX_VOC_SESSIONS)
+					continue;
+				else
+					return 0;
+			}
+
+			if (port_id == VOICE_RECORD_TX) {
+				if ((v->rec_route_state.ul_flag != 0)
+					&& (v->rec_route_state.dl_flag == 0)) {
+					v->rec_route_state.ul_flag = 0;
+					rec_set = 0;
+				} else if ((v->rec_route_state.ul_flag != 0)
+					&& (v->rec_route_state.dl_flag != 0)) {
+					voice_cvs_stop_record(v);
+					v->rec_route_state.ul_flag = 0;
+					rec_mode = VOC_REC_DOWNLINK;
+					rec_set = 1;
+				}
+			} else if (port_id == VOICE_RECORD_RX) {
+				if ((v->rec_route_state.ul_flag == 0)
+					&& (v->rec_route_state.dl_flag != 0)) {
+					v->rec_route_state.dl_flag = 0;
+					rec_set = 0;
+				} else if ((v->rec_route_state.ul_flag != 0)
+					&& (v->rec_route_state.dl_flag != 0)) {
+					voice_cvs_stop_record(v);
+					v->rec_route_state.dl_flag = 0;
+					rec_mode = VOC_REC_UPLINK;
+					rec_set = 1;
+				}
+			}
+		}
+		pr_debug("%s: i=%d, mode =%d, set =%d\n", __func__,
+			i, rec_mode, rec_set);
+		cvs_handle = voice_get_cvs_handle(v);
+
+		if (cvs_handle != 0) {
+			if (rec_set)
+				ret = voice_cvs_start_record(v, rec_mode);
+			else
+				ret = voice_cvs_stop_record(v);
+		}
+
+		/* Cache the value */
+		v->rec_info.rec_enable = rec_set;
+		v->rec_info.rec_mode = rec_mode;
+
+		mutex_unlock(&v->lock);
+	}
+
+	return ret;
+}
+
 static int voice_cvs_start_playback(struct voice_data *v)
 {
 	int ret = 0;
@@ -3110,6 +3372,8 @@
 			case VSS_ICOMMON_CMD_SET_UI_PROPERTY:
 			case VSS_ISTREAM_CMD_START_PLAYBACK:
 			case VSS_ISTREAM_CMD_STOP_PLAYBACK:
+			case VSS_ISTREAM_CMD_START_RECORD:
+			case VSS_ISTREAM_CMD_STOP_RECORD:
 				pr_debug("%s: cmd = 0x%x\n", __func__, ptr[0]);
 				v->cvs_state = CMD_STATUS_SUCCESS;
 				wake_up(&v->cvs_wait);
diff --git a/sound/soc/msm/qdsp6/q6voice.h b/sound/soc/msm/qdsp6/q6voice.h
index 03993f6..d330ada 100644
--- a/sound/soc/msm/qdsp6/q6voice.h
+++ b/sound/soc/msm/qdsp6/q6voice.h
@@ -17,6 +17,10 @@
 #define MAX_VOC_PKT_SIZE 642
 #define SESSION_NAME_LEN 20
 
+#define VOC_REC_UPLINK		0x00
+#define VOC_REC_DOWNLINK	0x01
+#define VOC_REC_BOTH		0x02
+
 struct voice_header {
 	uint32_t id;
 	uint32_t data_len;
@@ -43,6 +47,11 @@
 	u16 tx_route_flag;
 };
 
+struct voice_rec_route_state {
+	u16 ul_flag;
+	u16 dl_flag;
+};
+
 enum {
 	VOC_INIT = 0,
 	VOC_RUN,
@@ -324,6 +333,30 @@
 #define VSS_ISTREAM_CMD_STOP_PLAYBACK                   0x00011239
 /* Stop the in-call music delivery on the Tx voice path. */
 
+#define VSS_ISTREAM_CMD_START_RECORD                    0x00011236
+/* Start in-call conversation recording. */
+#define VSS_ISTREAM_CMD_STOP_RECORD                     0x00011237
+/* Stop in-call conversation recording. */
+
+#define VSS_TAP_POINT_NONE                              0x00010F78
+/* Indicates no tapping for specified path. */
+
+#define VSS_TAP_POINT_STREAM_END                        0x00010F79
+/* Indicates that specified path should be tapped at the end of the stream. */
+
+struct vss_istream_cmd_start_record_t {
+	uint32_t rx_tap_point;
+	/* Tap point to use on the Rx path. Supported values are:
+	 * VSS_TAP_POINT_NONE : Do not record Rx path.
+	 * VSS_TAP_POINT_STREAM_END : Rx tap point is at the end of the stream.
+	 */
+	uint32_t tx_tap_point;
+	/* Tap point to use on the Tx path. Supported values are:
+	 * VSS_TAP_POINT_NONE : Do not record tx path.
+	 * VSS_TAP_POINT_STREAM_END : Tx tap point is at the end of the stream.
+	 */
+} __packed;
+
 struct vss_istream_cmd_create_passive_control_session_t {
 	char name[SESSION_NAME_LEN];
 	/**<
@@ -544,6 +577,10 @@
 	struct apr_hdr hdr;
 	struct vss_icommon_cmd_set_ui_property_st_enable_t vss_set_st;
 } __packed;
+struct cvs_start_record_cmd {
+	struct apr_hdr hdr;
+	struct vss_istream_cmd_start_record_t rec_mode;
+} __packed;
 
 /* TO CVP commands */
 
@@ -753,8 +790,9 @@
 };
 
 struct incall_rec_info {
-	uint32_t pending;
+	uint32_t rec_enable;
 	uint32_t rec_mode;
+	uint32_t recording;
 };
 
 struct incall_music_info {
@@ -798,7 +836,12 @@
 	struct voice_dev_route_state voc_route_state;
 
 	u16 session_id;
+
+	struct incall_rec_info rec_info;
+
 	struct incall_music_info music_info;
+
+	struct voice_rec_route_state rec_route_state;
 };
 
 #define MAX_VOC_SESSIONS 2
@@ -866,4 +909,5 @@
 uint16_t voc_get_session_id(char *name);
 
 int voc_start_playback(uint32_t set);
+int voc_start_record(uint32_t port_id, uint32_t set);
 #endif