hal: Add device sidetone support for USB audio ADSP solution

Add device sidetone support for USB ADSP solution if the USB headset
has the sidetone capability.

CRs-Fixed: 1019158
Change-Id: Ie40c286eb9988aeb5093f32c7f81cdb142e88eb6
diff --git a/configs/msmcobalt/audio_platform_info.xml b/configs/msmcobalt/audio_platform_info.xml
index fc86a53..ae252bc 100644
--- a/configs/msmcobalt/audio_platform_info.xml
+++ b/configs/msmcobalt/audio_platform_info.xml
@@ -69,6 +69,8 @@
         <!-- followed by perf lock options                             -->
         <param key="perf_lock_opts" value="4, 0x101, 0x704, 0x20F, 0x1E01"/>
         <param key="input_mic_max_count" value="4"/>
+        <!-- In the below value string, the value indicates sidetone gain in dB -->
+        <param key="usb_sidetone_gain" value="35"/>
     </config_params>
     <backend_names>
         <device name="SND_DEVICE_OUT_HEADPHONES" backend="headphones" interface="SLIMBUS_6_RX"/>
diff --git a/hal/audio_extn/audio_extn.h b/hal/audio_extn/audio_extn.h
index 0ef1783..0420105 100644
--- a/hal/audio_extn/audio_extn.h
+++ b/hal/audio_extn/audio_extn.h
@@ -156,6 +156,8 @@
 #define audio_extn_usb_add_device(device, card)                        (0)
 #define audio_extn_usb_remove_device(device, card)                     (0)
 #define audio_extn_usb_is_config_supported(bit_width, sample_rate, ch) (0)
+#define audio_extn_usb_enable_sidetone(device, enable)                 (0)
+#define audio_extn_usb_set_sidetone_gain(parms, value, len)            (0)
 #else
 void audio_extn_usb_init(void *adev);
 void audio_extn_usb_deinit();
@@ -164,6 +166,9 @@
 bool audio_extn_usb_is_config_supported(unsigned int *bit_width,
                                         unsigned int *sample_rate,
                                         unsigned int ch);
+int audio_extn_usb_enable_sidetone(int device, bool enable);
+int audio_extn_usb_set_sidetone_gain(struct str_parms *parms,
+                                     char *value, int len);
 #endif
 
 #ifndef SSR_ENABLED
diff --git a/hal/audio_extn/usb.c b/hal/audio_extn/usb.c
index 141ef37..da80422 100644
--- a/hal/audio_extn/usb.c
+++ b/hal/audio_extn/usb.c
@@ -29,21 +29,22 @@
 #include <sys/ioctl.h>
 #include <fcntl.h>
 #include <sys/stat.h>
-
 #include <system/audio.h>
 #include <tinyalsa/asoundlib.h>
 #include <audio_hw.h>
 #include <cutils/properties.h>
+#include <ctype.h>
+#include <math.h>
 
 #ifdef USB_HEADSET_ENABLED
 #define USB_BUFF_SIZE           2048
 #define CHANNEL_NUMBER_STR      "Channels: "
 #define PLAYBACK_PROFILE_STR    "Playback:"
 #define CAPTURE_PROFILE_STR     "Capture:"
+#define USB_SIDETONE_GAIN_STR   "usb_sidetone_gain"
 #define ABS_SUB(A, B) (((A) > (B)) ? ((A) - (B)):((B) - (A)))
 #define SAMPLE_RATE_8000          8000
 #define SAMPLE_RATE_11025         11025
-
 // Supported sample rates for USB
 static uint32_t supported_sample_rates[] =
     {44100, 48000, 64000, 88200, 96000, 176400, 192000};
@@ -55,6 +56,12 @@
     USB_CAPTURE,
 };
 
+enum {
+    USB_SIDETONE_ENABLE_INDEX = 0,
+    USB_SIDETONE_VOLUME_INDEX,
+    USB_SIDETONE_MAX_INDEX,
+};
+
 struct usb_device_config {
     struct listnode list;
     unsigned int bit_width;
@@ -68,15 +75,120 @@
     audio_devices_t usb_device_type;
     int usb_card;
     struct listnode usb_device_conf_list;
+    struct mixer *usb_snd_mixer;
+    int usb_sidetone_index[USB_SIDETONE_MAX_INDEX];
+    int usb_sidetone_vol_min;
+    int usb_sidetone_vol_max;
 };
 
 struct usb_module {
     struct listnode usb_card_conf_list;
     struct audio_device *adev;
+    int sidetone_gain;
 };
 
 static struct usb_module *usbmod = NULL;
 static bool usb_audio_debug_enable = false;
+static int usb_sidetone_gain = 0;
+
+static const char * const usb_sidetone_enable_str[] = {
+    "Sidetone Playback Switch",
+    "Mic Playback Switchs",
+};
+
+static const char * const usb_sidetone_volume_str[] = {
+    "Sidetone Playback Volume",
+    "Mic Playback Volume",
+};
+
+static void usb_mixer_print_enum(struct mixer_ctl *ctl)
+{
+    unsigned int num_enums;
+    unsigned int i;
+    const char *string;
+
+    num_enums = mixer_ctl_get_num_enums(ctl);
+
+    for (i = 0; i < num_enums; i++) {
+        string = mixer_ctl_get_enum_string(ctl, i);
+        ALOGI("\t%s%s", mixer_ctl_get_value(ctl, 0) == (int)i ? ">" : "", string);
+    }
+}
+
+static void usb_soundcard_detail_control(struct mixer *mixer, const char *control)
+{
+    struct mixer_ctl *ctl;
+    enum mixer_ctl_type type;
+    unsigned int num_values;
+    unsigned int i;
+    int min, max;
+
+    if (isdigit(control[0]))
+        ctl = mixer_get_ctl(mixer, atoi(control));
+    else
+        ctl = mixer_get_ctl_by_name(mixer, control);
+
+    if (!ctl) {
+        fprintf(stderr, "Invalid mixer control\n");
+        return;
+    }
+
+    type = mixer_ctl_get_type(ctl);
+    num_values = mixer_ctl_get_num_values(ctl);
+
+    ALOGI("%s:", mixer_ctl_get_name(ctl));
+
+    for (i = 0; i < num_values; i++) {
+        switch (type) {
+            case MIXER_CTL_TYPE_INT:
+                ALOGI(" %d", mixer_ctl_get_value(ctl, i));
+                break;
+            case MIXER_CTL_TYPE_BOOL:
+                ALOGI(" %s", mixer_ctl_get_value(ctl, i) ? "On" : "Off");
+                break;
+            case MIXER_CTL_TYPE_ENUM:
+                usb_mixer_print_enum(ctl);
+                break;
+            case MIXER_CTL_TYPE_BYTE:
+                ALOGI(" 0x%02x", mixer_ctl_get_value(ctl, i));
+                break;
+            default:
+                ALOGI(" unknown");
+                break;
+        }
+    }
+
+    if (type == MIXER_CTL_TYPE_INT) {
+        min = mixer_ctl_get_range_min(ctl);
+        max = mixer_ctl_get_range_max(ctl);
+        ALOGI(" (range %d->%d)", min, max);
+    }
+}
+
+static void usb_soundcard_list_controls(struct mixer *mixer)
+{
+    struct mixer_ctl *ctl;
+    const char *name, *type;
+    unsigned int num_ctls, num_values;
+    unsigned int i;
+
+    num_ctls = mixer_get_num_ctls(mixer);
+
+    ALOGI("Number of controls: %d\n", num_ctls);
+
+    ALOGI("ctl\ttype\tnum\t%-40s value\n", "name");
+    for (i = 0; i < num_ctls; i++) {
+        ctl = mixer_get_ctl(mixer, i);
+        if (ctl != NULL) {
+            name = mixer_ctl_get_name(ctl);
+            type = mixer_ctl_get_type_string(ctl);
+            num_values = mixer_ctl_get_num_values(ctl);
+            ALOGI("%d\t%s\t%d\t%-40s", i, type, num_values, name);
+            if (name != NULL)
+                usb_soundcard_detail_control(mixer, name);
+        }
+    }
+}
 
 static int usb_set_channel_mixer_ctl(int channel,
                                      char *ch_mixer_ctl_name)
@@ -400,6 +512,46 @@
     return ret;
 }
 
+static void usb_get_sidetone_mixer(struct usb_card_config *usb_card_info)
+{
+    struct mixer_ctl *ctl;
+    unsigned int index;
+
+    for (index = 0; index < USB_SIDETONE_MAX_INDEX; index++)
+        usb_card_info->usb_sidetone_index[index] = -1;
+
+    usb_card_info->usb_snd_mixer = mixer_open(usb_card_info->usb_card);
+    for (index = 0;
+         index < sizeof(usb_sidetone_enable_str)/sizeof(usb_sidetone_enable_str[0]);
+         index++) {
+        ctl = mixer_get_ctl_by_name(usb_card_info->usb_snd_mixer,
+                                    usb_sidetone_enable_str[index]);
+        if (ctl) {
+            usb_card_info->usb_sidetone_index[USB_SIDETONE_ENABLE_INDEX] = index;
+            /* Disable device sidetone by default */
+            mixer_ctl_set_value(ctl, 0, false);
+            break;
+        }
+    }
+    for (index = 0;
+         index < sizeof(usb_sidetone_volume_str)/sizeof(usb_sidetone_volume_str[0]);
+         index++) {
+        ctl = mixer_get_ctl_by_name(usb_card_info->usb_snd_mixer,
+                                    usb_sidetone_volume_str[index]);
+        if (ctl) {
+            usb_card_info->usb_sidetone_index[USB_SIDETONE_VOLUME_INDEX] = index;
+            usb_card_info->usb_sidetone_vol_min = mixer_ctl_get_range_min(ctl);
+            usb_card_info->usb_sidetone_vol_max = mixer_ctl_get_range_max(ctl);
+            break;
+        }
+    }
+
+    if ((usb_card_info->usb_snd_mixer != NULL) && (usb_audio_debug_enable))
+        usb_soundcard_list_controls(usb_card_info->usb_snd_mixer);
+
+    return;
+}
+
 static bool usb_valid_device(int device)
 {
     bool is_usb_device = false;
@@ -631,7 +783,6 @@
     return true;
 }
 
-
 static bool usb_audio_backend_apply_policy(struct listnode *dev_list,
                                            unsigned int *bit_width,
                                            unsigned int *sample_rate,
@@ -666,6 +817,72 @@
     return is_usb_supported;
 }
 
+static int usb_get_sidetone_gain(struct usb_card_config *card_info)
+{
+    int gain = card_info->usb_sidetone_vol_min + usbmod->sidetone_gain;
+    if (gain > card_info->usb_sidetone_vol_max)
+        gain = card_info->usb_sidetone_vol_max;
+    return gain;
+}
+
+void audio_extn_usb_set_sidetone_gain(struct str_parms *parms,
+                                char *value, int len)
+{
+    int err;
+
+    err = str_parms_get_str(parms, USB_SIDETONE_GAIN_STR,
+                            value, len);
+    if (err >= 0) {
+        usb_sidetone_gain = pow(10.0, (float)(atoi(value))/10.0);
+        ALOGV("%s: sidetone gain(%s) decimal %d",
+              __func__, value, usb_sidetone_gain);
+        str_parms_del(parms, USB_SIDETONE_GAIN_STR);
+    }
+    return;
+}
+
+int audio_extn_usb_enable_sidetone(int device, bool enable)
+{
+    int ret = -ENODEV;
+    struct listnode *node_i;
+    struct usb_card_config *card_info;
+    int i;
+    ALOGV("%s: card_dev_type (0x%x), sidetone enable(%d)",
+           __func__,  device, enable);
+
+    list_for_each(node_i, &usbmod->usb_card_conf_list) {
+        card_info = node_to_item(node_i, struct usb_card_config, list);
+        ALOGV("%s: card_dev_type (0x%x), card_no(%d)",
+               __func__,  card_info->usb_device_type, card_info->usb_card);
+        if (card_info->usb_device_type == AUDIO_DEVICE_OUT_USB_DEVICE) {
+            if ((i = card_info->usb_sidetone_index[USB_SIDETONE_ENABLE_INDEX]) != -1) {
+                struct mixer_ctl *ctl = mixer_get_ctl_by_name(
+                                card_info->usb_snd_mixer,
+                                usb_sidetone_enable_str[i]);
+                if (ctl)
+                    mixer_ctl_set_value(ctl, 0, enable);
+                else
+                    break;
+
+                if ((i = card_info->usb_sidetone_index[USB_SIDETONE_VOLUME_INDEX]) != -1) {
+                    ctl = mixer_get_ctl_by_name(
+                                card_info->usb_snd_mixer,
+                                usb_sidetone_volume_str[i]);
+                    if (ctl == NULL)
+                        ALOGV("%s: sidetone gain mixer command is not found",
+                               __func__);
+                    else if (enable)
+                        mixer_ctl_set_value(ctl, 0,
+                                            usb_get_sidetone_gain(card_info));
+                }
+                ret = 0;
+                break;
+            }
+        }
+    }
+    return ret;
+}
+
 bool audio_extn_usb_is_config_supported(unsigned int *bit_width,
                                         unsigned int *sample_rate,
                                         unsigned int ch)
@@ -732,6 +949,7 @@
         if (!usb_get_device_pb_config(usb_card_info, card)){
             usb_card_info->usb_card = card;
             usb_card_info->usb_device_type = AUDIO_DEVICE_OUT_USB_DEVICE;
+            usb_get_sidetone_mixer(usb_card_info);
             list_add_tail(&usbmod->usb_card_conf_list, &usb_card_info->list);
             goto exit;
         }
@@ -810,6 +1028,7 @@
     }
     list_init(&usbmod->usb_card_conf_list);
     usbmod->adev = (struct audio_device*)adev;
+    usbmod->sidetone_gain = usb_sidetone_gain;
 exit:
     return;
 }
diff --git a/hal/msm8916/platform.c b/hal/msm8916/platform.c
index 0703b81..b18670e 100644
--- a/hal/msm8916/platform.c
+++ b/hal/msm8916/platform.c
@@ -5323,3 +5323,26 @@
              return snd_device;
     }
 }
+
+int platform_set_sidetone(struct audio_device *adev,
+                          snd_device_t out_snd_device,
+                          bool enable,
+                          char *str)
+{
+    int ret;
+    if (out_snd_device == SND_DEVICE_OUT_USB_HEADSET) {
+            ret = audio_extn_usb_enable_sidetone(out_snd_device, enable);
+            if (ret)
+                ALOGI("%s: usb device %d does not support device sidetone\n",
+                  __func__, out_snd_device);
+    } else {
+        ALOGV("%s: sidetone out device(%d) mixer cmd = %s\n",
+              __func__, out_snd_device, str);
+
+        if (enable)
+            audio_route_apply_and_update_path(adev->audio_route, str);
+        else
+            audio_route_reset_and_update_path(adev->audio_route, str);
+    }
+    return 0;
+}
diff --git a/hal/msm8960/platform.c b/hal/msm8960/platform.c
index 2c60f3b..b35e8e9 100644
--- a/hal/msm8960/platform.c
+++ b/hal/msm8960/platform.c
@@ -1234,3 +1234,26 @@
 {
     return true;
 }
+
+int platform_set_sidetone(struct audio_device *adev,
+                          snd_device_t out_snd_device,
+                          bool enable,
+                          char *str)
+{
+    int ret;
+    if (out_snd_device == SND_DEVICE_OUT_USB_HEADSET) {
+            ret = audio_extn_usb_enable_sidetone(out_snd_device, enable);
+            if (ret)
+                ALOGI("%s: usb device %d does not support device sidetone\n",
+                  __func__, out_snd_device);
+    } else {
+        ALOGV("%s: sidetone out device(%d) mixer cmd = %s\n",
+              __func__, out_snd_device, str);
+
+        if (enable)
+            audio_route_apply_and_update_path(adev->audio_route, str);
+        else
+            audio_route_reset_and_update_path(adev->audio_route, str);
+    }
+    return 0;
+}
diff --git a/hal/msm8974/platform.c b/hal/msm8974/platform.c
index b7d326c..d60d67c 100644
--- a/hal/msm8974/platform.c
+++ b/hal/msm8974/platform.c
@@ -3567,6 +3567,7 @@
     set_audiocal(platform, parms, value, len);
     native_audio_set_params(platform, parms, value, len);
     audio_extn_spkr_prot_set_parameters(parms, value, len);
+    audio_extn_usb_set_sidetone_gain(parms, value, len);
     perf_lock_set_params(platform, parms, value, len);
 done:
     ALOGV("%s: exit with code(%d)", __func__, ret);
@@ -5163,4 +5164,25 @@
     return ret;
 }
 
+int platform_set_sidetone(struct audio_device *adev,
+                          snd_device_t out_snd_device,
+                          bool enable,
+                          char *str)
+{
+    int ret;
+    if (out_snd_device == SND_DEVICE_OUT_USB_HEADSET) {
+            ret = audio_extn_usb_enable_sidetone(out_snd_device, enable);
+            if (ret)
+                ALOGI("%s: usb device %d does not support device sidetone\n",
+                  __func__, out_snd_device);
+    } else {
+        ALOGV("%s: sidetone out device(%d) mixer cmd = %s\n",
+              __func__, out_snd_device, str);
 
+        if (enable)
+            audio_route_apply_and_update_path(adev->audio_route, str);
+        else
+            audio_route_reset_and_update_path(adev->audio_route, str);
+    }
+    return 0;
+}
diff --git a/hal/platform_api.h b/hal/platform_api.h
index cb177b6..c6f3cb3 100644
--- a/hal/platform_api.h
+++ b/hal/platform_api.h
@@ -148,4 +148,8 @@
                                    snd_device_t *new_snd_devices);
 
 bool platform_check_backends_match(snd_device_t snd_device1, snd_device_t snd_device2);
+int platform_set_sidetone(struct audio_device *adev,
+                          snd_device_t out_snd_device,
+                          bool enable,
+                          char * str);
 #endif // AUDIO_PLATFORM_API_H
diff --git a/hal/voice.c b/hal/voice.c
index 5fef205..f86483e 100644
--- a/hal/voice.c
+++ b/hal/voice.c
@@ -72,6 +72,9 @@
         is_sidetone_dev = true;
         strlcpy(mixer_path, "sidetone-headphones", MIXER_PATH_MAX_LENGTH);
         break;
+    case SND_DEVICE_OUT_USB_HEADSET:
+        is_sidetone_dev = true;
+        break;
     default:
         is_sidetone_dev = false;
         break;
@@ -84,28 +87,11 @@
         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);
-
+    if (voice_is_sidetone_device(out_snd_device, mixer_path))
+        platform_set_sidetone(adev, out_snd_device, enable, mixer_path);
     return;
 }