hal: Fix sidetone sequence during voice call

When sidetone is enabled during voice call, the RX and TX path mixer
controls are issued back to back, this causes the codec path to be
enabled from TX to RX as the internal loopback for sidetone exists. Due
to enabling this path, pop noise is observed on headphones. This issue
is fixed by treating sidetone as special case and changing the enable
and disable sequence of sidetone mixer controls.

Based on CAF commit : I7aea55af963cf939286e5b349ff25fd783314679

BUG=23135950

Change-Id: Ib5759e08d9b5948a76545b032d882231410cfa2a
diff --git a/hal/audio_hw.c b/hal/audio_hw.c
index 7c017fd..b307f6c 100644
--- a/hal/audio_hw.c
+++ b/hal/audio_hw.c
@@ -683,6 +683,9 @@
         (usecase->in_snd_device != SND_DEVICE_NONE) &&
         (usecase->out_snd_device != SND_DEVICE_NONE)) {
         status = platform_switch_voice_call_device_pre(adev->platform);
+        /* Disable sidetone only if voice call already exists */
+        if (voice_is_call_state_active(adev))
+            voice_set_sidetone(adev, usecase->out_snd_device, false);
     }
 
     /* Disable current sound devices */
@@ -734,10 +737,14 @@
      * Enable device command should be sent to modem only after
      * enabling voice call mixer controls
      */
-    if (usecase->type == VOICE_CALL)
+    if (usecase->type == VOICE_CALL) {
         status = platform_switch_voice_call_usecase_route_post(adev->platform,
                                                                out_snd_device,
                                                                in_snd_device);
+         /* Enable sidetone only if voice call already exists */
+        if (voice_is_call_state_active(adev))
+            voice_set_sidetone(adev, out_snd_device, true);
+    }
 
     return status;
 }
diff --git a/hal/msm8974/platform.h b/hal/msm8974/platform.h
index b166e2f..1c06726 100644
--- a/hal/msm8974/platform.h
+++ b/hal/msm8974/platform.h
@@ -148,6 +148,7 @@
 #define ALL_SESSION_VSID                0xFFFFFFFF
 #define DEFAULT_MUTE_RAMP_DURATION_MS   20
 #define DEFAULT_VOLUME_RAMP_DURATION_MS 20
+#define MIXER_PATH_MAX_LENGTH 100
 
 #define ACDB_ID_VOICE_SPEAKER 15
 #define ACDB_ID_VOICE_HANDSET 7
diff --git a/hal/voice.c b/hal/voice.c
index 1f36b36..a33305d 100644
--- a/hal/voice.c
+++ b/hal/voice.c
@@ -52,6 +52,58 @@
     return session;
 }
 
+static bool voice_is_sidetone_device(snd_device_t out_device,
+            char *mixer_path)
+{
+    bool is_sidetone_dev = true;
+
+    switch (out_device) {
+    case SND_DEVICE_OUT_VOICE_HAC_HANDSET:
+        strlcpy(mixer_path, "sidetone-hac-handset", MIXER_PATH_MAX_LENGTH);
+        break;
+    case SND_DEVICE_OUT_VOICE_HANDSET:
+        strlcpy(mixer_path, "sidetone-handset", MIXER_PATH_MAX_LENGTH);
+        break;
+    case SND_DEVICE_OUT_VOICE_HEADPHONES:
+        strlcpy(mixer_path, "sidetone-headphones", MIXER_PATH_MAX_LENGTH);
+        break;
+    default:
+        is_sidetone_dev = false;
+        break;
+    }
+
+    return is_sidetone_dev;
+}
+
+void voice_set_sidetone(struct audio_device *adev,
+        snd_device_t out_snd_device, bool enable)
+{
+    char mixer_path[MIXER_PATH_MAX_LENGTH];
+    bool is_sidetone_dev;
+
+    ALOGD("%s: %s, out_snd_device: %d\n",
+          __func__, (enable ? "enable" : "disable"),
+          out_snd_device);
+
+    is_sidetone_dev = voice_is_sidetone_device(out_snd_device, mixer_path);
+
+    if (!is_sidetone_dev) {
+        ALOGD("%s: device %d does not support sidetone\n",
+              __func__, out_snd_device);
+        return;
+    }
+
+    ALOGD("%s: sidetone out device = %s\n",
+          __func__, mixer_path);
+
+    if (enable)
+        audio_route_apply_and_update_path(adev->audio_route, mixer_path);
+    else
+        audio_route_reset_and_update_path(adev->audio_route, mixer_path);
+
+    return;
+}
+
 int voice_stop_usecase(struct audio_device *adev, audio_usecase_t usecase_id)
 {
     int i, ret = 0;
@@ -61,8 +113,20 @@
     ALOGD("%s: enter usecase:%s", __func__, use_case_table[usecase_id]);
 
     session = (struct voice_session *)voice_get_session_from_use_case(adev, usecase_id);
+
+    uc_info = get_usecase_from_list(adev, usecase_id);
+    if (uc_info == NULL) {
+        ALOGE("%s: Could not find the usecase (%d) in the list",
+              __func__, usecase_id);
+        return -EINVAL;
+    }
+
     session->state.current = CALL_INACTIVE;
 
+    /* Disable sidetone only when no calls are active */
+    if (!voice_is_call_state_active(adev))
+        voice_set_sidetone(adev, uc_info->out_snd_device, false);
+
     ret = platform_stop_voice_call(adev->platform, session->vsid);
 
     /* 1. Close the PCM devices */
@@ -75,13 +139,6 @@
         session->pcm_tx = NULL;
     }
 
-    uc_info = get_usecase_from_list(adev, usecase_id);
-    if (uc_info == NULL) {
-        ALOGE("%s: Could not find the usecase (%d) in the list",
-              __func__, usecase_id);
-        return -EINVAL;
-    }
-
     /* 2. Get and set stream specific mixer controls */
     disable_audio_route(adev, uc_info);
 
@@ -129,6 +186,17 @@
         goto error_start_voice;
     }
 
+    ALOGV("%s: Opening PCM capture device card_id(%d) device_id(%d)",
+          __func__, adev->snd_card, pcm_dev_tx_id);
+    session->pcm_tx = pcm_open(adev->snd_card,
+                               pcm_dev_tx_id,
+                               PCM_IN, &voice_config);
+    if (session->pcm_tx && !pcm_is_ready(session->pcm_tx)) {
+        ALOGE("%s: %s", __func__, pcm_get_error(session->pcm_tx));
+        ret = -EIO;
+        goto error_start_voice;
+    }
+
     ALOGV("%s: Opening PCM playback device card_id(%d) device_id(%d)",
           __func__, adev->snd_card, pcm_dev_rx_id);
     session->pcm_rx = pcm_open(adev->snd_card,
@@ -140,18 +208,12 @@
         goto error_start_voice;
     }
 
-    ALOGV("%s: Opening PCM capture device card_id(%d) device_id(%d)",
-          __func__, adev->snd_card, pcm_dev_tx_id);
-    session->pcm_tx = pcm_open(adev->snd_card,
-                               pcm_dev_tx_id,
-                               PCM_IN, &voice_config);
-    if (session->pcm_tx && !pcm_is_ready(session->pcm_tx)) {
-        ALOGE("%s: %s", __func__, pcm_get_error(session->pcm_tx));
-        ret = -EIO;
-        goto error_start_voice;
-    }
-    pcm_start(session->pcm_rx);
     pcm_start(session->pcm_tx);
+    pcm_start(session->pcm_rx);
+
+    /* Enable sidetone only when no calls are already active */
+    if (!voice_is_call_state_active(adev))
+        voice_set_sidetone(adev, uc_info->out_snd_device, true);
 
     voice_set_volume(adev, adev->voice.volume);
 
diff --git a/hal/voice.h b/hal/voice.h
index 76f9d0d..23b9ee3 100644
--- a/hal/voice.h
+++ b/hal/voice.h
@@ -40,6 +40,7 @@
 struct stream_in;
 struct stream_out;
 typedef int audio_usecase_t;
+typedef int snd_device_t;
 
 struct call_state {
     int current;
@@ -90,4 +91,8 @@
 int voice_check_and_stop_incall_rec_usecase(struct audio_device *adev,
                                             struct stream_in *in);
 void voice_update_devices_for_all_voice_usecases(struct audio_device *adev);
+void voice_set_sidetone(struct audio_device *adev,
+                       snd_device_t out_snd_device,
+                       bool enable);
+bool voice_is_call_state_active(struct audio_device *adev);
 #endif //VOICE_H