audio: hal: enable ext FM usecase via audio patch

Support external FM Tuner usecase via device
to device audio patch configuration.

Change-Id: I876ca3f1b4aeb79593a75128ff640083803e07fe
diff --git a/hal/Android.mk b/hal/Android.mk
index 1a0c2e2..35b84b6 100644
--- a/hal/Android.mk
+++ b/hal/Android.mk
@@ -354,6 +354,10 @@
 LOCAL_SHARED_LIBRARIES += libbase libhidlbase libhwbinder libutils android.hardware.power@1.2 liblog
 LOCAL_SRC_FILES += audio_perf.cpp
 
+ifeq ($(strip $(AUDIO_FEATURE_ENABLED_FM_TUNER_EXT)),true)
+    LOCAL_CFLAGS += -DFM_TUNER_EXT_ENABLED
+endif
+
 LOCAL_MODULE := audio.primary.$(TARGET_BOARD_PLATFORM)
 
 LOCAL_MODULE_RELATIVE_PATH := hw
diff --git a/hal/audio_extn/auto_hal.c b/hal/audio_extn/auto_hal.c
index 7f2163d..c47bae8 100644
--- a/hal/audio_extn/auto_hal.c
+++ b/hal/audio_extn/auto_hal.c
@@ -65,6 +65,19 @@
     USECASE_AUDIO_PLAYBACK_PHONE,
 };
 
+static struct audio_patch_record *get_patch_from_list(struct audio_device *adev,
+                                                    audio_patch_handle_t patch_id)
+{
+    struct audio_patch_record *patch;
+    struct listnode *node;
+    list_for_each(node, &adev->audio_patch_record_list) {
+        patch = node_to_item(node, struct audio_patch_record, list);
+        if (patch->handle == patch_id)
+            return patch;
+    }
+    return NULL;
+}
+
 #define MAX_SOURCE_PORTS_PER_PATCH 1
 #define MAX_SINK_PORTS_PER_PATCH 1
 
@@ -80,6 +93,11 @@
     char *str = NULL;
     struct str_parms *parms = NULL;
     char *address = NULL;
+    struct audio_usecase *uc_info = NULL;
+    struct audio_patch_record *patch_record = NULL;
+    audio_usecase_t usecase = USECASE_INVALID;
+    audio_io_handle_t input_io_handle = AUDIO_IO_HANDLE_NONE;
+    audio_io_handle_t output_io_handle = AUDIO_IO_HANDLE_NONE;
 
     ALOGV("%s: enter", __func__);
 
@@ -132,6 +150,7 @@
         pthread_mutex_unlock(&adev->lock);
         if(ret)
             return ret;
+        input_io_handle = sinks->ext.mix.handle;
 
         if (strcmp(sources->ext.device.address, "") != 0) {
             address = audio_device_address_to_parameter(
@@ -144,7 +163,7 @@
         if (!parms) {
             ALOGE("%s: failed to allocate mem for parms", __func__);
             ret = -ENOMEM;
-            goto error;
+            goto exit;
         }
         str_parms_add_int(parms, AUDIO_PARAMETER_STREAM_ROUTING,
                         (int)sources->ext.device.type);
@@ -165,6 +184,7 @@
         pthread_mutex_unlock(&adev->lock);
         if(ret)
             return ret;
+        output_io_handle = sources->ext.mix.handle;
 
         if (strcmp(sinks->ext.device.address, "") != 0) {
             address = audio_device_address_to_parameter(
@@ -177,20 +197,89 @@
         if (!parms) {
             ALOGE("%s: failed to allocate mem for parms", __func__);
             ret = -ENOMEM;
-            goto error;
+            goto exit;
         }
         str_parms_add_int(parms, AUDIO_PARAMETER_STREAM_ROUTING,
                         (int)sinks->ext.device.type);
         str = str_parms_to_str(parms);
         out_ctxt->output->stream.common.set_parameters(
                         (struct audio_stream *)out_ctxt->output, str);
+    } else if ((sources->type == AUDIO_PORT_TYPE_DEVICE) &&
+            (sinks->type == AUDIO_PORT_TYPE_DEVICE)) {
+        /* allocate use case and call to plugin driver*/
+        uc_info = (struct audio_usecase *)calloc(1, sizeof(struct audio_usecase));
+        if (!uc_info) {
+            ALOGE("%s fail to allocate uc_info", __func__);
+            return -ENOMEM;
+        }
+        /* TODO - add sink type check and printout for non speaker sink */
+        switch(sources->ext.device.type) {
+#ifdef FM_TUNER_EXT_ENABLED
+            case AUDIO_DEVICE_IN_FM_TUNER:
+                ALOGV("Creating audio patch for external FM tuner");
+                uc_info->id = USECASE_AUDIO_FM_TUNER_EXT;
+                uc_info->type = PCM_PASSTHROUGH;
+                uc_info->devices = AUDIO_DEVICE_IN_FM_TUNER;
+                uc_info->in_snd_device = SND_DEVICE_IN_CAPTURE_FM;
+                uc_info->out_snd_device = SND_DEVICE_OUT_BUS_MEDIA;
+                break;
+#endif
+            default:
+                ALOGE("%s: Unsupported audio source type %x", __func__,
+                            sources->ext.device.type);
+                goto error;
+        }
+
+        ALOGD("%s: Starting ext hw plugin use case (%d) in_snd_device (%d) out_snd_device (%d)",
+              __func__, uc_info->id, uc_info->in_snd_device, uc_info->out_snd_device);
+
+        ret = audio_extn_ext_hw_plugin_usecase_start(adev->ext_hw_plugin, uc_info);
+        if (ret) {
+            ALOGE("%s: failed to start ext hw plugin use case (%d)",
+                __func__, uc_info->id);
+            goto error;
+        }
+        /* TODO: apply audio port gain to codec if applicable */
+        usecase = uc_info->id;
+        pthread_mutex_lock(&adev->lock);
+        list_add_tail(&adev->usecase_list, &uc_info->list);
+        pthread_mutex_unlock(&adev->lock);
     } else {
-        ALOGW("%s: create device -> device audio patch", __func__);
+        ALOGW("%s: audio patch not supported",__func__);
+        return -EINVAL;
     }
 
+    /* patch created success, add to patch record list */
+    patch_record = (struct audio_patch_record *)calloc(1,
+                    sizeof(struct audio_patch_record));
+    if (!patch_record) {
+        ALOGE("%s fail to allocate patch_record", __func__);
+        ret = -ENOMEM;
+        if (uc_info)
+            list_remove(&uc_info->list);
+        goto error;
+    }
+
+    pthread_mutex_lock(&adev->lock);
+    adev->audio_patch_index++;
+    patch_record->handle = adev->audio_patch_index;
+    patch_record->usecase = usecase;
+    patch_record->input_io_handle = input_io_handle;
+    patch_record->output_io_handle = output_io_handle;
+    list_add_tail(&adev->audio_patch_record_list, &patch_record->list);
+    pthread_mutex_unlock(&adev->lock);
+
+    *handle = patch_record->handle;
+    goto exit;
+
 error:
+    if(uc_info)
+        free(uc_info);
+exit:
     if (parms)
         str_parms_destroy(parms);
+    if (str)
+        free(str);
     if (address)
         free(address);
     ALOGV("%s: exit: handle 0x%x", __func__, *handle);
@@ -201,6 +290,13 @@
                                 audio_patch_handle_t handle)
 {
     int ret = 0;
+    struct audio_device *adev = (struct audio_device *)dev;
+    struct audio_usecase *uc_info = NULL;
+    struct audio_patch_record *patch_record = NULL;
+    streams_input_ctxt_t *in_ctxt = NULL;
+    streams_output_ctxt_t *out_ctxt = NULL;
+    char *str = NULL;
+    struct str_parms *parms = NULL;
 
     ALOGV("%s: enter: handle 0x%x", __func__, handle);
 
@@ -209,10 +305,92 @@
         return -EINVAL;
     }
 
-    if (handle != AUDIO_PATCH_HANDLE_NONE) {
-        ALOGW("%s: release device -> device audio patch", __func__);
+    if (handle == AUDIO_PATCH_HANDLE_NONE) {
+        ALOGW("%s: null audio patch handle", __func__);
+        return -EINVAL;
     }
 
+    /* get the patch record from handle */
+    pthread_mutex_lock(&adev->lock);
+    patch_record = get_patch_from_list(adev, handle);
+    if(!patch_record) {
+        ALOGE("%s: failed to find the patch record with handle (%d) in the list",
+                __func__, handle);
+        ret = -EINVAL;
+    }
+    pthread_mutex_unlock(&adev->lock);
+    if(ret)
+        goto exit;
+
+    if (patch_record->input_io_handle) {
+        pthread_mutex_lock(&adev->lock);
+        in_ctxt = in_get_stream(adev, patch_record->input_io_handle);
+        if (!in_ctxt) {
+            ALOGE("%s, Could not find input stream", __func__);
+            ret = -EINVAL;
+        }
+        pthread_mutex_unlock(&adev->lock);
+        if(ret)
+            goto exit;
+
+        parms = str_parms_create();
+        str_parms_add_int(parms, AUDIO_PARAMETER_STREAM_ROUTING, 0);
+        str = str_parms_to_str(parms);
+        in_ctxt->input->stream.common.set_parameters(
+                        (struct audio_stream *)in_ctxt->input, str);
+    }
+
+    if (patch_record->output_io_handle) {
+        pthread_mutex_lock(&adev->lock);
+        out_ctxt = out_get_stream(adev, patch_record->output_io_handle);
+        if (!out_ctxt) {
+            ALOGE("%s, Could not find output stream", __func__);
+            ret = -EINVAL;
+        }
+        pthread_mutex_unlock(&adev->lock);
+        if(ret)
+            goto exit;
+
+        parms = str_parms_create();
+        str_parms_add_int(parms, AUDIO_PARAMETER_STREAM_ROUTING, 0);
+        str = str_parms_to_str(parms);
+        out_ctxt->output->stream.common.set_parameters(
+                        (struct audio_stream *)out_ctxt->output, str);
+    }
+
+    if (patch_record->usecase != USECASE_INVALID) {
+        pthread_mutex_lock(&adev->lock);
+        uc_info = get_usecase_from_list(adev, patch_record->usecase);
+        if (!uc_info) {
+            ALOGE("%s: failed to find the usecase (%d)",
+                    __func__, patch_record->usecase);
+            ret = -EINVAL;
+        } else {
+            /* call to plugin to stop the usecase */
+            ret = audio_extn_ext_hw_plugin_usecase_stop(adev->ext_hw_plugin, uc_info);
+            if (ret) {
+                ALOGE("%s: failed to stop ext hw plugin use case (%d)",
+                        __func__, uc_info->id);
+            }
+
+            /* remove usecase from list and free it */
+            list_remove(&uc_info->list);
+            free(uc_info);
+        }
+        pthread_mutex_unlock(&adev->lock);
+    }
+
+    /* remove the patch record from list and free it */
+    pthread_mutex_lock(&adev->lock);
+    list_remove(&patch_record->list);
+    pthread_mutex_unlock(&adev->lock);
+    free(patch_record);
+    if (parms)
+        str_parms_destroy(parms);
+    if (str)
+        free(str);
+
+exit:
     ALOGV("%s: exit", __func__);
     return ret;
 }
diff --git a/hal/audio_extn/ext_hw_plugin.c b/hal/audio_extn/ext_hw_plugin.c
index 619ecfc..41faf03 100644
--- a/hal/audio_extn/ext_hw_plugin.c
+++ b/hal/audio_extn/ext_hw_plugin.c
@@ -205,6 +205,9 @@
     case USECASE_AUDIO_PLAYBACK_PHONE:
         *plugin_usecase = AUDIO_HAL_PLUGIN_USECASE_PHONE_PLAYBACK;
         break;
+    case USECASE_AUDIO_FM_TUNER_EXT:
+       *plugin_usecase = AUDIO_HAL_PLUGIN_USECASE_FM_TUNER;
+        break;
     default:
         ret = -EINVAL;
     }
@@ -247,7 +250,8 @@
         }
 
         if (((usecase->type == PCM_CAPTURE) || (usecase->type == VOICE_CALL) ||
-                (usecase->type == VOIP_CALL) || (usecase->type == PCM_HFP_CALL)) &&
+              (usecase->type == VOIP_CALL) || (usecase->type == PCM_HFP_CALL) ||
+              (usecase->type == PCM_PASSTHROUGH)) &&
             (usecase->in_snd_device != SND_DEVICE_NONE)) {
             codec_enable.snd_dev = usecase->in_snd_device;
             /* TODO - below should be related with in_snd_dev */
@@ -402,7 +406,8 @@
             my_plugin->out_snd_dev[codec_disable.usecase] = 0;
         }
         if (((usecase->type == PCM_CAPTURE) || (usecase->type == VOICE_CALL) ||
-                (usecase->type == VOIP_CALL) || (usecase->type == PCM_HFP_CALL)) &&
+             (usecase->type == VOIP_CALL) || (usecase->type == PCM_HFP_CALL) ||
+             (usecase->type == PCM_PASSTHROUGH)) &&
             (usecase->in_snd_device != SND_DEVICE_NONE)) {
             codec_disable.snd_dev = usecase->in_snd_device;
 
diff --git a/hal/audio_hw.c b/hal/audio_hw.c
index b759f4d..863e458 100644
--- a/hal/audio_hw.c
+++ b/hal/audio_hw.c
@@ -406,6 +406,7 @@
     [USECASE_AUDIO_PLAYBACK_SYS_NOTIFICATION] = "sys-notification-playback",
     [USECASE_AUDIO_PLAYBACK_NAV_GUIDANCE] = "nav-guidance-playback",
     [USECASE_AUDIO_PLAYBACK_PHONE] = "phone-playback",
+    [USECASE_AUDIO_FM_TUNER_EXT] = "fm-tuner-ext",
 };
 
 static const audio_usecase_t offload_usecases[] = {
@@ -1615,7 +1616,8 @@
               platform_get_snd_device_name(snd_device),
               platform_get_snd_device_name(usecase->out_snd_device),
               platform_check_backends_match(snd_device, usecase->out_snd_device));
-        if ((usecase->type != PCM_CAPTURE) && (usecase != uc_info)) {
+        if ((usecase->type != PCM_CAPTURE) && (usecase != uc_info) &&
+                (usecase->type != PCM_PASSTHROUGH)) {
             uc_derive_snd_device = derive_playback_snd_device(adev->platform,
                                                usecase, uc_info, snd_device);
             if (((uc_derive_snd_device != usecase->out_snd_device) || force_routing) &&
@@ -7155,6 +7157,15 @@
 
     *stream_out = NULL;
 
+    pthread_mutex_lock(&adev->lock);
+    if (out_get_stream(adev, handle) != NULL) {
+        ALOGW("%s, output stream already opened", __func__);
+        ret = -EEXIST;
+    }
+    pthread_mutex_unlock(&adev->lock);
+    if (ret)
+        return ret;
+
     out = (struct stream_out *)calloc(1, sizeof(struct stream_out));
 
     ALOGD("%s: enter: format(%#x) sample_rate(%d) channel_mask(%#x) devices(%#x) flags(%#x)\
@@ -8658,6 +8669,15 @@
             return -EINVAL;
     }
 
+    pthread_mutex_lock(&adev->lock);
+    if (in_get_stream(adev, handle) != NULL) {
+        ALOGW("%s, input stream already opened", __func__);
+        ret = -EEXIST;
+    }
+    pthread_mutex_unlock(&adev->lock);
+    if (ret)
+        return ret;
+
     in = (struct stream_in *)calloc(1, sizeof(struct stream_in));
 
     if (!in) {
@@ -9571,6 +9591,8 @@
     list_init(&adev->usecase_list);
     list_init(&adev->active_inputs_list);
     list_init(&adev->active_outputs_list);
+    list_init(&adev->audio_patch_record_list);
+    adev->audio_patch_index = 0;
     adev->cur_wfd_channels = 2;
     adev->offload_usecases_state = 0;
     adev->pcm_record_uc_state = 0;
diff --git a/hal/audio_hw.h b/hal/audio_hw.h
index 4810896..0a1569d 100644
--- a/hal/audio_hw.h
+++ b/hal/audio_hw.h
@@ -228,6 +228,8 @@
     USECASE_AUDIO_PLAYBACK_NAV_GUIDANCE,
     USECASE_AUDIO_PLAYBACK_PHONE,
 
+    /*Audio FM Tuner usecase*/
+    USECASE_AUDIO_FM_TUNER_EXT,
     AUDIO_USECASE_MAX
 };
 
@@ -494,6 +496,7 @@
     PCM_HFP_CALL,
     TRANSCODE_LOOPBACK_RX,
     TRANSCODE_LOOPBACK_TX,
+    PCM_PASSTHROUGH,
     USECASE_TYPE_MAX
 } usecase_type_t;
 
@@ -673,6 +676,16 @@
     bool use_old_pspd_mix_ctrl;
     int camera_orientation; /* CAMERA_BACK_LANDSCAPE ... CAMERA_FRONT_PORTRAIT */
     bool adm_routing_changed;
+    struct listnode audio_patch_record_list;
+    unsigned int audio_patch_index;
+};
+
+struct audio_patch_record {
+    struct listnode list;
+    audio_patch_handle_t handle;
+    audio_usecase_t usecase;
+    audio_io_handle_t input_io_handle;
+    audio_io_handle_t output_io_handle;
 };
 
 int select_devices(struct audio_device *adev,
diff --git a/hal/msm8974/platform.c b/hal/msm8974/platform.c
index 0321714..2c1fef1 100644
--- a/hal/msm8974/platform.c
+++ b/hal/msm8974/platform.c
@@ -480,7 +480,7 @@
                                              NAV_GUIDANCE_PCM_DEVICE},
     [USECASE_AUDIO_PLAYBACK_PHONE] = {PHONE_PCM_DEVICE,
                                       PHONE_PCM_DEVICE},
-
+    [USECASE_AUDIO_FM_TUNER_EXT] = {-1, -1},
 };
 
 /* Array to store sound devices */