/*
* Copyright (c) 2017, The Linux Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above
*       copyright notice, this list of conditions and the following
*       disclaimer in the documentation and/or other materials provided
*       with the distribution.
*     * Neither the name of The Linux Foundation nor the names of its
*       contributors may be used to endorse or promote products derived
*       from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
* ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#define LOG_TAG "audio_hw_loopback"
/*#define LOG_NDEBUG 0*/
/*#define VERY_VERY_VERBOSE_LOGGING*/
#ifdef VERY_VERY_VERBOSE_LOGGING
#define ALOGVV ALOGV
#else
#define ALOGVV(a...) do { } while(0)
#endif

#define MAX_NUM_PATCHES 1
#define MAX_NUM_HW_LOOPBACK_PATCHES 1
#define PATCH_HANDLE_INVALID 0xFFFF
#define MAX_SOURCE_PORTS_PER_PATCH 1
#define MAX_SINK_PORTS_PER_PATCH 1
#define HW_LOOPBACK_RX_VOLUME     "Trans Loopback RX Volume"
#define HW_LOOPBACK_RX_UNITY_GAIN 0x2000

#include <math.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
#include <dlfcn.h>
#include <sys/resource.h>
#include <sys/prctl.h>
#include <cutils/properties.h>
#include <cutils/str_parms.h>
#include <cutils/log.h>
#include <cutils/atomic.h>
#include "audio_utils/primitives.h"
#include "audio_hw.h"
#include "platform_api.h"
#include <platform.h>
#include <system/thread_defs.h>
#include <cutils/sched_policy.h>
#include "audio_extn.h"
#include <sound/compress_params.h>
#include <sound/compress_offload.h>
#include <system/audio.h>

/*
* Unique patch handle ID = (unique_patch_handle_type << 8 | patch_handle_num)
* Eg : HDMI_IN_SPKR_OUT handles can be 0x1000, 0x1001 and so on..
*/
typedef enum patch_handle_type {
    AUDIO_PATCH_HDMI_IN_SPKR_OUT=0x10,
    AUDIO_PATCH_SPDIF_IN_SPKR_OUT,
    AUDIO_PATCH_MIC_IN_SPKR_OUT,
    AUDIO_PATCH_MIC_IN_HDMI_OUT
} patch_handle_type_t;

typedef enum patch_state {
    PATCH_INACTIVE,// Patch is not created yet
    PATCH_CREATED, // Patch created but not in running state yet, probably due
                   // to lack of proper port config
    PATCH_RUNNING, // Patch in running state, moves to this state when patch
                   // created and proper port config is available
} patch_state_t;

typedef struct loopback_patch {
    audio_patch_handle_t patch_handle_id;            /* patch unique ID */
    struct audio_port_config loopback_source;        /* Source port config */
    struct audio_port_config loopback_sink;          /* Source port config */
    struct compress *source_stream;                  /* Source stream */
    struct compress *sink_stream;                    /* Source stream */
    struct stream_inout patch_stream;                /* InOut type stream */
    patch_state_t patch_state;                       /* Patch operation state */
} loopback_patch_t;

typedef struct patch_db_struct {
    int32_t num_patches;
    loopback_patch_t loopback_patch[MAX_NUM_PATCHES];
} patch_db_t;

typedef struct audio_loopback {
    struct audio_device *adev;
    patch_db_t patch_db;
    audio_usecase_t uc_id;
    usecase_type_t  uc_type;
    pthread_mutex_t lock;
} audio_loopback_t;

typedef struct port_info {
    audio_port_handle_t      id;                /* port unique ID */
    audio_port_role_t        role;              /* sink or source */
    audio_port_type_t        type;              /* device, mix ... */
} port_info_t;

/* Audio loopback module struct */
static audio_loopback_t *audio_loopback_mod = NULL;

uint32_t format_to_bitwidth(audio_format_t format)
{
    switch (format) {
        case AUDIO_FORMAT_PCM_16_BIT:
            return 16;
        case AUDIO_FORMAT_PCM_8_BIT:
            return 8;
        case AUDIO_FORMAT_PCM_32_BIT:
            return 32;
        case AUDIO_FORMAT_PCM_8_24_BIT:
            return 32;
        case AUDIO_FORMAT_PCM_24_BIT_PACKED:
            return 24;
        default:
            return 16;
    }
}

/* Set loopback volume : for mute implementation */
static int hw_loopback_set_volume(struct audio_device *adev, int value)
{
    int32_t ret = 0;
    struct mixer_ctl *ctl;
    char mixer_ctl_name[MAX_LENGTH_MIXER_CONTROL_IN_INT];
    snprintf(mixer_ctl_name, sizeof(mixer_ctl_name),
            "Transcode Loopback Rx Volume");

    ALOGD("%s: (%d)\n", __func__, value);

    ALOGD("%s: Setting HW loopback volume to %d \n", __func__, value);
    ctl = mixer_get_ctl_by_name(adev->mixer, mixer_ctl_name);
    if (!ctl) {
        ALOGE("%s: Could not get ctl for mixer cmd - %s",
              __func__, mixer_ctl_name);
        return -EINVAL;
    }

    if(mixer_ctl_set_value(ctl, 0, value) < 0) {
        ALOGE("%s: Couldn't set HW Loopback Volume: [%d]", __func__, value);
        return -EINVAL;
    }

    ALOGV("%s: exit", __func__);
    return ret;
}

/* Initialize patch database */
int init_patch_database(patch_db_t* patch_db)
{
    int patch_init_rc = 0, patch_num=0;
    patch_db->num_patches = 0;
    for (patch_num=0;patch_num < MAX_NUM_PATCHES;patch_num++) {
        patch_db->loopback_patch[patch_num].patch_handle_id = (int32_t)
        PATCH_HANDLE_INVALID;
    }
    return patch_init_rc;
}

bool is_supported_sink_device(audio_devices_t sink_device_mask)
{
    if((sink_device_mask & AUDIO_DEVICE_OUT_SPEAKER) ||
       (sink_device_mask & AUDIO_DEVICE_OUT_WIRED_HEADSET) ||
       (sink_device_mask & AUDIO_DEVICE_OUT_WIRED_HEADPHONE) ||
       (sink_device_mask & AUDIO_DEVICE_OUT_LINE)) {
           return true;
       }
    return false;
}

/* Get patch type based on source and sink ports configuration */
/* Only ports of type 'DEVICE' are supported */
patch_handle_type_t get_loopback_patch_type(loopback_patch_t*  loopback_patch)
{
    bool is_source_hdmi=false, is_sink_supported=false;
    if (loopback_patch->patch_handle_id != PATCH_HANDLE_INVALID) {
        ALOGE("%s, Patch handle already exists", __func__);
        return loopback_patch->patch_handle_id;
    }

    if (loopback_patch->loopback_source.role == AUDIO_PORT_ROLE_SOURCE) {
        switch (loopback_patch->loopback_source.type) {
            case AUDIO_PORT_TYPE_DEVICE :
                if ((loopback_patch->loopback_source.config_mask &
                   AUDIO_PORT_CONFIG_FORMAT) && (loopback_patch->loopback_source.ext.device.type & AUDIO_DEVICE_IN_HDMI)) {
                       switch (loopback_patch->loopback_source.format) {
                           case AUDIO_FORMAT_PCM:
                           case AUDIO_FORMAT_PCM_16_BIT:
                           case AUDIO_FORMAT_PCM_8_24_BIT:
                           case AUDIO_FORMAT_PCM_24_BIT_PACKED:
                           case AUDIO_FORMAT_IEC61937:
                           case AUDIO_FORMAT_AC3:
                           case AUDIO_FORMAT_E_AC3:
                              is_source_hdmi = true;
                           break;
                }
            }
            break;
            default :
            break;
            //Unsupported as of now, need to extend for other source types
        }
    }
    if (loopback_patch->loopback_sink.role == AUDIO_PORT_ROLE_SINK) {
        switch (loopback_patch->loopback_sink.type) {
        case AUDIO_PORT_TYPE_DEVICE :
            if ((loopback_patch->loopback_sink.config_mask &
                AUDIO_PORT_CONFIG_FORMAT) &&
                (is_supported_sink_device(loopback_patch->loopback_sink.ext.device.type))) {
                    switch (loopback_patch->loopback_sink.format) {
                    case AUDIO_FORMAT_PCM:
                    case AUDIO_FORMAT_PCM_16_BIT:
                    case AUDIO_FORMAT_PCM_32_BIT:
                    case AUDIO_FORMAT_PCM_8_24_BIT:
                    case AUDIO_FORMAT_PCM_24_BIT_PACKED:
                        is_sink_supported = true;
                        break;
                    default:
                        break;
                    }
            } else {
                ALOGE("%s, Unsupported sink port device %d", __func__,loopback_patch->loopback_sink.ext.device.type);
            }
            break;
        default :
            break;
            //Unsupported as of now, need to extend for other sink types
        }
    }
    if (is_source_hdmi && is_sink_supported) {
        return AUDIO_PATCH_HDMI_IN_SPKR_OUT;
    }
    ALOGE("%s, Unsupported source or sink port config", __func__);
    return loopback_patch->patch_handle_id;
}

/* Releases an existing loopback session */
/* Conditions : Session setup goes bad or actual session teardown */
int32_t release_loopback_session(loopback_patch_t *active_loopback_patch)
{
    int32_t ret = 0;
    struct audio_usecase *uc_info;
    struct audio_device *adev = audio_loopback_mod->adev;
    struct stream_inout *inout =  &active_loopback_patch->patch_stream;
    struct audio_port_config *source_patch_config = &active_loopback_patch->
                                                    loopback_source;

    /* 1. Close the PCM devices */
    if (active_loopback_patch->source_stream) {
        compress_close(active_loopback_patch->source_stream);
        active_loopback_patch->source_stream = NULL;
    } else {
        ALOGE("%s: Failed to close loopback stream in capture path",
            __func__);
    }
    if (active_loopback_patch->sink_stream) {
        compress_close(active_loopback_patch->sink_stream);
        active_loopback_patch->sink_stream = NULL;
    } else {
        ALOGE("%s: Failed to close loopback stream in playback path",
            __func__);
    }

    uc_info = get_usecase_from_list(adev, audio_loopback_mod->uc_id);
    if (uc_info == NULL) {
        ALOGE("%s: Could not find the loopback usecase (%d) in the list",
            __func__, active_loopback_patch->patch_handle_id);
        return -EINVAL;
    }

    active_loopback_patch->patch_state = PATCH_INACTIVE;

    /* 2. Get and set stream specific mixer controls */
    disable_audio_route(adev, uc_info);

    /* 3. Disable the rx and tx devices */
    disable_snd_device(adev, uc_info->out_snd_device);
    disable_snd_device(adev, uc_info->in_snd_device);

    /* 4. Reset backend device to default state */
    platform_invalidate_backend_config(adev->platform,uc_info->in_snd_device);

    list_remove(&uc_info->list);
    free(uc_info);

    adev->active_input = get_next_active_input(adev);

    if (inout->ip_hdlr_handle) {
        ret = audio_extn_ip_hdlr_intf_close(inout->ip_hdlr_handle, true, inout);
        if (ret < 0)
            ALOGE("%s: audio_extn_ip_hdlr_intf_close failed %d",__func__, ret);
    }

    /* close adsp hdrl session before standby */
    if (inout->adsp_hdlr_stream_handle) {
        ret = audio_extn_adsp_hdlr_stream_close(inout->adsp_hdlr_stream_handle);
        if (ret)
            ALOGE("%s: adsp_hdlr_stream_close failed %d",__func__, ret);
        inout->adsp_hdlr_stream_handle = NULL;
    }

    if (inout->ip_hdlr_handle) {
        audio_extn_ip_hdlr_intf_deinit(inout->ip_hdlr_handle);
        inout->ip_hdlr_handle = NULL;
    }

    ALOGD("%s: Release loopback session exit: status(%d)", __func__, ret);
    return ret;
}

/* Callback funtion called in the case of failures */
int loopback_stream_cb(stream_callback_event_t event, void *param, void *cookie)
{
    if (event == AUDIO_EXTN_STREAM_CBK_EVENT_ERROR) {
        pthread_mutex_lock(&audio_loopback_mod->lock);
        release_loopback_session(cookie);
        audio_loopback_mod->patch_db.num_patches--;
        pthread_mutex_unlock(&audio_loopback_mod->lock);
    }
    return 0;
}

#if defined SNDRV_COMPRESS_LATENCY_MODE
static void transcode_loopback_util_set_latency_mode(
                             loopback_patch_t *active_loopback_patch,
                             uint32_t latency_mode)
{
    struct snd_compr_metadata metadata;

    metadata.key = SNDRV_COMPRESS_LATENCY_MODE;
    metadata.value[0] = latency_mode;
    ALOGV("%s: Setting latency mode %d",__func__, latency_mode);
    compress_set_metadata(active_loopback_patch->source_stream,&metadata);
}
#else
static void transcode_loopback_util_set_latency_mode(
                            loopback_patch_t *active_loopback_patch __unused,
                            uint32_t latency_mode __unused)
{
    ALOGD("%s:: Latency mode configuration not supported", __func__);
}
#endif

/* Create a loopback session based on active loopback patch selected */
int create_loopback_session(loopback_patch_t *active_loopback_patch)
{
    int32_t ret = 0, bits_per_sample;
    struct audio_usecase *uc_info;
    int32_t pcm_dev_asm_rx_id, pcm_dev_asm_tx_id;
    char dummy_write_buf[64];
    struct audio_device *adev = audio_loopback_mod->adev;
    struct compr_config source_config, sink_config;
    struct snd_codec codec;
    struct audio_port_config *source_patch_config = &active_loopback_patch->
                                                    loopback_source;
    struct audio_port_config *sink_patch_config = &active_loopback_patch->
                                                    loopback_sink;
    struct stream_inout *inout =  &active_loopback_patch->patch_stream;
    struct adsp_hdlr_stream_cfg hdlr_stream_cfg;
    struct stream_in loopback_source_stream;
    char prop_value[PROPERTY_VALUE_MAX] = {0};

    ALOGD("%s: Create loopback session begin", __func__);

    uc_info = (struct audio_usecase *)calloc(1, sizeof(struct audio_usecase));

    if (!uc_info) {
        ALOGE("%s: Failure to open loopback session", __func__);
        return -ENOMEM;
    }

    uc_info->id = USECASE_AUDIO_TRANSCODE_LOOPBACK;
    uc_info->type = audio_loopback_mod->uc_type;
    uc_info->stream.inout = &active_loopback_patch->patch_stream;
    uc_info->devices = active_loopback_patch->patch_stream.out_config.devices;
    uc_info->in_snd_device = SND_DEVICE_NONE;
    uc_info->out_snd_device = SND_DEVICE_NONE;

    list_add_tail(&adev->usecase_list, &uc_info->list);

    loopback_source_stream.source = AUDIO_SOURCE_UNPROCESSED;
    loopback_source_stream.device = inout->in_config.devices;
    loopback_source_stream.channel_mask = inout->in_config.channel_mask;
    loopback_source_stream.bit_width = inout->in_config.bit_width;
    loopback_source_stream.sample_rate = inout->in_config.sample_rate;
    loopback_source_stream.format = inout->in_config.format;

    memcpy(&loopback_source_stream.usecase, uc_info,
           sizeof(struct audio_usecase));
    adev->active_input = &loopback_source_stream;
    select_devices(adev, uc_info->id);

    pcm_dev_asm_rx_id = platform_get_pcm_device_id(uc_info->id, PCM_PLAYBACK);
    pcm_dev_asm_tx_id = platform_get_pcm_device_id(uc_info->id, PCM_CAPTURE);

    if (pcm_dev_asm_rx_id < 0 || pcm_dev_asm_tx_id < 0 ) {
        ALOGE("%s: Invalid PCM devices (asm: rx %d tx %d) for the usecase(%d)",
            __func__, pcm_dev_asm_rx_id, pcm_dev_asm_tx_id, uc_info->id);
        ret = -EIO;
        goto exit;
    }

    ALOGD("%s: LOOPBACK PCM devices (rx: %d tx: %d) usecase(%d)",
        __func__, pcm_dev_asm_rx_id, pcm_dev_asm_tx_id, uc_info->id);

    /* setup a channel for client <--> adsp communication for stream events */
    inout->dev = adev;
    inout->client_callback = loopback_stream_cb;
    inout->client_cookie = active_loopback_patch;
    hdlr_stream_cfg.pcm_device_id = pcm_dev_asm_rx_id;
    hdlr_stream_cfg.flags = 0;
    hdlr_stream_cfg.type = PCM_PLAYBACK;
    ret = audio_extn_adsp_hdlr_stream_open(&inout->adsp_hdlr_stream_handle,
            &hdlr_stream_cfg);
    if (ret) {
        ALOGE("%s: adsp_hdlr_stream_open failed %d", __func__, ret);
        inout->adsp_hdlr_stream_handle = NULL;
        goto exit;
    }
    if (audio_extn_ip_hdlr_intf_supported(source_patch_config->format,false, true)) {
        ret = audio_extn_ip_hdlr_intf_init(&inout->ip_hdlr_handle, NULL, NULL, adev,
                                           USECASE_AUDIO_TRANSCODE_LOOPBACK);
        if (ret < 0) {
            ALOGE("%s: audio_extn_ip_hdlr_intf_init failed %d", __func__, ret);
            inout->ip_hdlr_handle = NULL;
            goto exit;
        }
    }

    if (source_patch_config->format == AUDIO_FORMAT_IEC61937) {
        // This is needed to set a known format to DSP and handle
        // any format change via ADSP event
        codec.id = AUDIO_FORMAT_AC3;
    }

    /* Set config for compress stream open in capture path */
    codec.id = get_snd_codec_id(source_patch_config->format);
    codec.ch_in = audio_channel_count_from_out_mask(source_patch_config->
                                                    channel_mask);
    codec.ch_out = 2; // Irrelevant for loopback case in this direction
    codec.sample_rate = source_patch_config->sample_rate;
    codec.format = hal_format_to_alsa(source_patch_config->format);
    source_config.fragment_size = 1024;
    source_config.fragments = 1;
    source_config.codec = &codec;

    /* Open compress stream in capture path */
    active_loopback_patch->source_stream = compress_open(adev->snd_card,
                        pcm_dev_asm_tx_id, COMPRESS_OUT, &source_config);
    if (active_loopback_patch->source_stream && !is_compress_ready(
        active_loopback_patch->source_stream)) {
        ALOGE("%s: %s", __func__, compress_get_error(active_loopback_patch->
        source_stream));
        active_loopback_patch->source_stream = NULL;
        ret = -EIO;
        goto exit;
    } else if (active_loopback_patch->source_stream == NULL) {
        ALOGE("%s: Failure to open loopback stream in capture path", __func__);
        ret = -EINVAL;
        goto exit;
    }

    /* Set config for compress stream open in playback path */
    codec.id = get_snd_codec_id(sink_patch_config->format);
    codec.ch_in = 2; // Irrelevant for loopback case in this direction
    codec.ch_out = audio_channel_count_from_out_mask(sink_patch_config->
                                                     channel_mask);
    codec.sample_rate = sink_patch_config->sample_rate;
    codec.format = hal_format_to_alsa(sink_patch_config->format);
    sink_config.fragment_size = 1024;
    sink_config.fragments = 1;
    sink_config.codec = &codec;

    /* Do not alter the location of sending latency mode property */
    /* Mode set on any stream but before both streams are open */
    if(property_get("audio.transcode.latency.mode", prop_value, "")) {
        uint32_t latency_mode = atoi(prop_value);
        transcode_loopback_util_set_latency_mode(active_loopback_patch,
                                                 latency_mode);
    }

    /* Open compress stream in playback path */
    active_loopback_patch->sink_stream = compress_open(adev->snd_card,
                         pcm_dev_asm_rx_id, COMPRESS_IN, &sink_config);
    if (active_loopback_patch->sink_stream && !is_compress_ready(
        active_loopback_patch->sink_stream)) {
        ALOGE("%s: %s", __func__, compress_get_error(active_loopback_patch->
                sink_stream));
        active_loopback_patch->sink_stream = NULL;
        ret = -EIO;
        goto exit;
    } else if (active_loopback_patch->sink_stream == NULL) {
        ALOGE("%s: Failure to open loopback stream in playback path", __func__);
        ret = -EINVAL;
        goto exit;
    }

    active_loopback_patch->patch_state = PATCH_CREATED;

    if (compress_start(active_loopback_patch->source_stream) < 0) {
        ALOGE("%s: Failure to start loopback stream in capture path",
        __func__);
        ret = -EINVAL;
        goto exit;
    }

    /* Dummy compress_write to ensure compress_start does not fail */
    compress_write(active_loopback_patch->sink_stream, dummy_write_buf, 64);
    if (compress_start(active_loopback_patch->sink_stream) < 0) {
        ALOGE("%s: Cannot start loopback stream in playback path",
                __func__);
        ret = -EINVAL;
        goto exit;
    }
    if (inout->ip_hdlr_handle) {
        ret = audio_extn_ip_hdlr_intf_open(inout->ip_hdlr_handle, true, inout,
                                           USECASE_AUDIO_TRANSCODE_LOOPBACK);
        if (ret < 0) {
            ALOGE("%s: audio_extn_ip_hdlr_intf_open failed %d",__func__, ret);
            goto exit;
        }
    }

    /* Move patch state to running, now that session is set up */
    active_loopback_patch->patch_state = PATCH_RUNNING;
    ALOGD("%s: Create loopback session end: status(%d)", __func__, ret);
    return ret;

exit:
    ALOGE("%s: Problem in Loopback session creation: \
            status(%d), releasing session ", __func__, ret);
    release_loopback_session(active_loopback_patch);
    return ret;
}

void update_patch_stream_config(struct stream_config *stream_cfg ,
                                struct audio_port_config *port_cfg)
{
    stream_cfg->sample_rate = port_cfg->sample_rate;
    stream_cfg->channel_mask = port_cfg->channel_mask;
    stream_cfg->format = port_cfg->format;
    stream_cfg->devices = port_cfg->ext.device.type;
    stream_cfg->bit_width = format_to_bitwidth(port_cfg->format);
}
/* API to create audio patch */
int audio_extn_hw_loopback_create_audio_patch(struct audio_hw_device *dev,
                                     unsigned int num_sources,
                                     const struct audio_port_config *sources,
                                     unsigned int num_sinks,
                                     const struct audio_port_config *sinks,
                                     audio_patch_handle_t *handle)
{
    int status = 0;
    patch_handle_type_t loopback_patch_type=0x0;
    loopback_patch_t loopback_patch, *active_loopback_patch = NULL;

    ALOGV("%s : Create audio patch begin", __func__);

    if ((audio_loopback_mod == NULL) || (dev == NULL)) {
        ALOGE("%s, Loopback module not initialized orInvalid device", __func__);
        status = -EINVAL;
        return status;
    }

    pthread_mutex_lock(&audio_loopback_mod->lock);
    if (audio_loopback_mod->patch_db.num_patches >= MAX_NUM_PATCHES ) {
        ALOGE("%s, Exhausted maximum possible patches per device", __func__);
        status = -EINVAL;
        goto exit_create_patch;
    }

    /* Port configuration check & validation */
    if (num_sources > MAX_SOURCE_PORTS_PER_PATCH ||
        num_sinks > MAX_SINK_PORTS_PER_PATCH) {
        ALOGE("%s, Unsupported patch configuration, sources %d sinks %d ",
                __func__, num_sources, num_sources);
        status = -EINVAL;
        goto exit_create_patch;
    }

    /* Use an empty patch from patch database and initialze */
    active_loopback_patch = &(audio_loopback_mod->patch_db.loopback_patch[
                                audio_loopback_mod->patch_db.num_patches]);
    active_loopback_patch->patch_handle_id = PATCH_HANDLE_INVALID;
    active_loopback_patch->patch_state = PATCH_INACTIVE;
    active_loopback_patch->patch_stream.ip_hdlr_handle = NULL;
    active_loopback_patch->patch_stream.adsp_hdlr_stream_handle = NULL;
    memcpy(&active_loopback_patch->loopback_source, &sources[0], sizeof(struct
    audio_port_config));
    memcpy(&active_loopback_patch->loopback_sink, &sinks[0], sizeof(struct
    audio_port_config));

    /* Get loopback patch type based on source and sink ports configuration */
    loopback_patch_type = get_loopback_patch_type(active_loopback_patch);

    if (loopback_patch_type == PATCH_HANDLE_INVALID) {
        ALOGE("%s, Unsupported patch type", __func__);
        status = -EINVAL;
        goto exit_create_patch;
    }

    update_patch_stream_config(&active_loopback_patch->patch_stream.in_config,
                                &active_loopback_patch->loopback_source);
    update_patch_stream_config(&active_loopback_patch->patch_stream.out_config,
                                &active_loopback_patch->loopback_sink);
    // Lock patch database, create patch handle and add patch handle to the list

    active_loopback_patch->patch_handle_id = (loopback_patch_type << 8 |
                                audio_loopback_mod->patch_db.num_patches);

    /* Is usecase transcode loopback? If yes, invoke loopback driver */
    if ((active_loopback_patch->loopback_source.type == AUDIO_PORT_TYPE_DEVICE)
       &&
       (active_loopback_patch->loopback_sink.type == AUDIO_PORT_TYPE_DEVICE)) {
        status = create_loopback_session(active_loopback_patch);
        if (status != 0)
            goto exit_create_patch;
    }

    // Create callback thread to listen to events from HW data path

    /* Fill unique handle ID generated based on active loopback patch */
    *handle = audio_loopback_mod->patch_db.loopback_patch[audio_loopback_mod->
                                        patch_db.num_patches].patch_handle_id;
    audio_loopback_mod->patch_db.num_patches++;

exit_create_patch :
    ALOGV("%s : Create audio patch end, status(%d)", __func__, status);
    pthread_mutex_unlock(&audio_loopback_mod->lock);
    return status;
}

/* API to release audio patch */
int audio_extn_hw_loopback_release_audio_patch(struct audio_hw_device *dev,
                                             audio_patch_handle_t handle)
{
    int status = 0, n=0, patch_index=-1;
    bool patch_found = false;
    loopback_patch_t *active_loopback_patch = NULL;
    ALOGV("%s audio_extn_hw_loopback_release_audio_patch begin %d", __func__, __LINE__);

    if ((audio_loopback_mod == NULL) || (dev == NULL)) {
        ALOGE("%s, Invalid device", __func__);
        status = -1;
        return status;
    }

    pthread_mutex_lock(&audio_loopback_mod->lock);

    for (n=0;n < MAX_NUM_PATCHES;n++) {
        if (audio_loopback_mod->patch_db.loopback_patch[n].patch_handle_id ==
           handle) {
            patch_found = true;
            patch_index = n;
            break;
        }
    }

    if (patch_found && (audio_loopback_mod->patch_db.num_patches > 0)) {
        active_loopback_patch = &(audio_loopback_mod->patch_db.loopback_patch[
                                patch_index]);
        status = release_loopback_session(active_loopback_patch);
        audio_loopback_mod->patch_db.num_patches--;
    } else {
        ALOGE("%s, Requested Patch handle does not exist", __func__);
        status = -1;
    }
    pthread_mutex_unlock(&audio_loopback_mod->lock);

    ALOGV("%s audio_extn_hw_loopback_release_audio_patch done, status(%d)", __func__,
    status);
    return status;
}

/* Find port config from patch database based on port info */
struct audio_port_config* get_port_from_patch_db(port_info_t *port,
                               patch_db_t *audio_patch_db, int *patch_num)
{
    int n=0, patch_index=-1;
    struct audio_port_config *cur_port=NULL;

    if (port->role == AUDIO_PORT_ROLE_SOURCE) {
        for (n=0;n < audio_patch_db->num_patches;n++) {
            cur_port = &(audio_patch_db->loopback_patch[n].loopback_source);
            if ((cur_port->id == port->id) && (cur_port->type == port->type) && (
               cur_port->role == port->role)) {
                patch_index = n;
                break;
            }
        }
    } else if (port->role == AUDIO_PORT_ROLE_SINK) {
        for (n=0;n < audio_patch_db->num_patches;n++) {
            cur_port = &(audio_patch_db->loopback_patch[n].loopback_sink);
            if ((cur_port->id == port->id) && (cur_port->type == port->type) && (
               cur_port->role == port->role)) {
                patch_index = n;
                break;
            }
        }
    }
    *patch_num = patch_index;
    return cur_port;
}

/* API to get port config based on port unique ID */
int audio_extn_hw_loopback_get_audio_port(struct audio_hw_device *dev,
                                    struct audio_port *port_in)
{
    int status = 0, n=0, patch_num=-1;
    port_info_t port_info;
    struct audio_port_config *port_out=NULL;
    ALOGV("%s %d", __func__, __LINE__);

    if ((audio_loopback_mod == NULL) || (dev == NULL)) {
        ALOGE("%s, Invalid device", __func__);
        status = -1;
        return status;
    }

    pthread_mutex_lock(&audio_loopback_mod->lock);

    port_info.id = port_in->id;
    port_info.role = port_in->role;              /* sink or source */
    port_info.type = port_in->type;              /* device, mix ... */
    port_out = get_port_from_patch_db(&port_info, &audio_loopback_mod->patch_db,
                                      &patch_num);
    if (port_out == NULL) {
        ALOGE("%s, Unable to find a valid matching port in patch \
        database,exiting", __func__);
        status = -EINVAL;
        return status;
    }

    /* Fill port output properties before returning the port */
    memcpy(&port_in->active_config,port_out, sizeof(struct audio_port_config));

    /* Multiple fields are not valid for loopback extension usecases, TODO :
    enhance for all patch handler cases. */
    port_in->num_sample_rates = 1;
    port_in->sample_rates[0] = port_out->sample_rate;
    port_in->num_channel_masks = 1;
    port_in->channel_masks[0] = port_out->channel_mask;
    port_in->num_formats = 1;
    port_in->formats[0] = port_out->format;
    port_in->num_gains = 1;

    pthread_mutex_unlock(&audio_loopback_mod->lock);
    return status;
}

/* API to set port config based on port unique ID */
int audio_extn_hw_loopback_set_audio_port_config(struct audio_hw_device *dev,
                                        const struct audio_port_config *config)
{
    int status = 0, n=0, patch_num=-1;
    port_info_t port_info;
    struct audio_port_config *port_out=NULL;
    struct audio_device *adev = audio_loopback_mod->adev;
    int loopback_gain = HW_LOOPBACK_RX_UNITY_GAIN;

    ALOGV("%s %d", __func__, __LINE__);

    if ((audio_loopback_mod == NULL) || (dev == NULL)) {
        ALOGE("%s, Invalid device", __func__);
        status = -EINVAL;
        return status;
    }

    pthread_mutex_lock(&audio_loopback_mod->lock);

    port_info.id = config->id;
    port_info.role = config->role;              /* sink or source */
    port_info.type = config->type;              /* device, mix  */
    port_out = get_port_from_patch_db(&port_info, &audio_loopback_mod->patch_db
                                    , &patch_num);

    if (port_out == NULL) {
        ALOGE("%s, Unable to find a valid matching port in patch \
        database,exiting", __func__);
        status = -EINVAL;
        goto exit_set_port_config;
    }

    port_out->config_mask |= config->config_mask;
    if(config->config_mask & AUDIO_PORT_CONFIG_CHANNEL_MASK)
        port_out->channel_mask = config->channel_mask;
    if(config->config_mask & AUDIO_PORT_CONFIG_FORMAT)
        port_out->format = config->format;
    if(config->config_mask & AUDIO_PORT_CONFIG_GAIN)
        port_out->gain = config->gain;
    if(config->config_mask & AUDIO_PORT_CONFIG_SAMPLE_RATE)
        port_out->sample_rate = config->sample_rate;

    /* Convert gain in millibels to ratio and convert to Q13 */
    loopback_gain = pow(10, (float)((float)port_out->gain.values[0]/2000)) *
                       (1 << 13);
    ALOGV("%s, Port config gain_in_mbells: %d, gain_in_q13 : %d", __func__,
          port_out->gain.values[0], loopback_gain);
    if((port_out->config_mask & AUDIO_PORT_CONFIG_GAIN) &&
        port_out->gain.mode == AUDIO_GAIN_MODE_JOINT ) {
        status = hw_loopback_set_volume(adev, loopback_gain);
        if (status) {
            ALOGE("%s, Error setting loopback gain config: status %d",
                  __func__, status);
        }
    } else {
        ALOGE("%s, Unsupported port config ,exiting", __func__);
        status = -EINVAL;
    }

    /* Currently, port config is not used for anything,
    need to restart session    */
exit_set_port_config:
    pthread_mutex_unlock(&audio_loopback_mod->lock);
    return status;
}

/* Loopback extension initialization, part of hal init sequence */
int audio_extn_hw_loopback_init(struct audio_device *adev)
{
    ALOGV("%s Audio loopback extension initializing", __func__);
    int ret = 0, size = 0;

    if (audio_loopback_mod != NULL) {
        pthread_mutex_lock(&audio_loopback_mod->lock);
        if (audio_loopback_mod->adev == adev) {
            ALOGV("%s %d : Audio loopback module already exists", __func__,
                    __LINE__);
        } else {
            ALOGV("%s %d : Audio loopback module called for invalid device",
                    __func__, __LINE__);
            ret = -EINVAL;
        }
        goto loopback_done;
    }
    audio_loopback_mod = malloc(sizeof(struct audio_loopback));
    if (audio_loopback_mod == NULL) {
        ALOGE("%s, out of memory", __func__);
        ret = -ENOMEM;
        goto loopback_done;
    }

    pthread_mutex_init(&audio_loopback_mod->lock,
                        (const pthread_mutexattr_t *)NULL);
    pthread_mutex_lock(&audio_loopback_mod->lock);
    audio_loopback_mod->adev = adev;

    ret = init_patch_database(&audio_loopback_mod->patch_db);

    audio_loopback_mod->uc_id = USECASE_AUDIO_TRANSCODE_LOOPBACK;
    audio_loopback_mod->uc_type = TRANSCODE_LOOPBACK;

loopback_done:
    if (ret != 0) {
        if (audio_loopback_mod != NULL) {
            pthread_mutex_unlock(&audio_loopback_mod->lock);
            pthread_mutex_destroy(&audio_loopback_mod->lock);
            free(audio_loopback_mod);
            audio_loopback_mod = NULL;
        }
    } else {
        pthread_mutex_unlock(&audio_loopback_mod->lock);
    }
    ALOGV("%s Audio loopback extension initialized", __func__);
    return ret;
}

void audio_extn_hw_loopback_deinit(struct audio_device *adev)
{
    ALOGV("%s Audio loopback extension de-initializing", __func__);

    if (audio_loopback_mod == NULL) {
        ALOGE("%s, loopback module NULL, cannot deinitialize", __func__);
        return;
    }
    pthread_mutex_lock(&audio_loopback_mod->lock);

    if (audio_loopback_mod->adev == adev) {
        if (audio_loopback_mod != NULL) {
            pthread_mutex_unlock(&audio_loopback_mod->lock);
            pthread_mutex_destroy(&audio_loopback_mod->lock);
            free(audio_loopback_mod);
            audio_loopback_mod = NULL;
        }
        return;
    } else {
        ALOGE("%s, loopback module not valid, cannot deinitialize", __func__);
    }
    pthread_mutex_unlock(&audio_loopback_mod->lock);
    return;
}
