audio: Add support to enable split A2DP

- Add new audio extension module for a2dp
- Add support to configure AFE encoder based
  on BT device capabilities
- Add support for soft handoff to re-configure
  AFE based on new BT device capability

Change-Id: I5d710fda1a8d8fa3b5d85aaa2b3096cff8fd7ce5
diff --git a/hal/Android.mk b/hal/Android.mk
index 83787e3..adee78b 100644
--- a/hal/Android.mk
+++ b/hal/Android.mk
@@ -243,6 +243,11 @@
     LOCAL_SRC_FILES += audio_extn/source_track.c
 endif
 
+ifeq ($(strip $(AUDIO_FEATURE_ENABLED_SPLIT_A2DP)),true)
+    LOCAL_CFLAGS += -DSPLIT_A2DP_ENABLED
+    LOCAL_SRC_FILES += audio_extn/a2dp.c
+endif
+
 LOCAL_SHARED_LIBRARIES := \
 	liblog \
 	libcutils \
@@ -251,6 +256,7 @@
 	libaudioroute \
 	libdl \
 	libaudioutils \
+	libhardware \
 	libexpat
 
 LOCAL_C_INCLUDES += \
diff --git a/hal/audio_extn/a2dp.c b/hal/audio_extn/a2dp.c
new file mode 100644
index 0000000..414fc79
--- /dev/null
+++ b/hal/audio_extn/a2dp.c
@@ -0,0 +1,733 @@
+/*
+* Copyright (c) 2015-16, 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 "split_a2dp"
+/*#define LOG_NDEBUG 0*/
+#define LOG_NDDEBUG 0
+#include <errno.h>
+#include <cutils/log.h>
+#include <dlfcn.h>
+#include "audio_hw.h"
+#include "platform.h"
+#include "platform_api.h"
+#include <stdlib.h>
+#include <cutils/str_parms.h>
+#include <hardware/audio.h>
+#include <hardware/hardware.h>
+#include <cutils/properties.h>
+
+#ifdef SPLIT_A2DP_ENABLED
+#define AUDIO_PARAMETER_A2DP_STARTED "A2dpStarted"
+#define BT_IPC_LIB_NAME  "libbthost_if.so"
+#define ENC_MEDIA_FMT_NONE                                     0
+#define ENC_MEDIA_FMT_AAC                                  0x00010DA6
+#define ENC_MEDIA_FMT_APTX                                 0x000131ff
+#define ENC_MEDIA_FMT_APTX_HD                              0x00013200
+#define ENC_MEDIA_FMT_SBC                                  0x00010BF2
+#define MEDIA_FMT_AAC_AOT_LC                               2
+#define MEDIA_FMT_AAC_AOT_SBR                              5
+#define MEDIA_FMT_AAC_AOT_PS                               29
+#define MEDIA_FMT_AAC_FORMAT_FLAG_ADTS                     0
+#define MEDIA_FMT_AAC_FORMAT_FLAG_RAW                      3
+#define PCM_CHANNEL_L                                      1
+#define PCM_CHANNEL_R                                      2
+#define PCM_CHANNEL_C                                      3
+#define MEDIA_FMT_SBC_CHANNEL_MODE_MONO                    1
+#define MEDIA_FMT_SBC_CHANNEL_MODE_STEREO                  2
+#define MEDIA_FMT_SBC_CHANNEL_MODE_DUAL_MONO               8
+#define MEDIA_FMT_SBC_CHANNEL_MODE_JOINT_STEREO            9
+#define MEDIA_FMT_SBC_ALLOCATION_METHOD_LOUDNESS           0
+#define MEDIA_FMT_SBC_ALLOCATION_METHOD_SNR                1
+#define MIXER_ENC_CONFIG_BLOCK     "SLIM_7_RX Encoder Config"
+#define MIXER_ENC_FMT_SBC          "SBC"
+#define MIXER_ENC_FMT_AAC          "AAC"
+#define MIXER_ENC_FMT_APTX         "APTX"
+#define MIXER_ENC_FMT_APTXHD       "APTXHD"
+#define MIXER_ENC_FMT_NONE         "NONE"
+
+
+typedef int (*audio_stream_open_t)(void);
+typedef int (*audio_stream_close_t)(void);
+typedef int (*audio_start_stream_t)(void);
+typedef int (*audio_stop_stream_t)(void);
+typedef int (*audio_suspend_stream_t)(void);
+typedef void (*audio_handoff_triggered_t)(void);
+typedef void (*clear_a2dpsuspend_flag_t)(void);
+typedef void * (*audio_get_codec_config_t)(uint8_t *multicast_status,uint8_t *num_dev,
+                               audio_format_t *codec_type);
+
+enum A2DP_STATE {
+    A2DP_STATE_CONNECTED,
+    A2DP_STATE_STARTED,
+    A2DP_STATE_STOPPED,
+    A2DP_STATE_DISCONNECTED,
+};
+
+/* structure used to  update a2dp state machine
+ * to communicate IPC library
+ * to store DSP encoder configuration information
+ */
+struct a2dp_data {
+    struct audio_device *adev;
+    void *bt_lib_handle;
+    audio_stream_open_t audio_stream_open;
+    audio_stream_close_t audio_stream_close;
+    audio_start_stream_t audio_start_stream;
+    audio_stop_stream_t audio_stop_stream;
+    audio_suspend_stream_t audio_suspend_stream;
+    audio_handoff_triggered_t audio_handoff_triggered;
+    clear_a2dpsuspend_flag_t clear_a2dpsuspend_flag;
+    audio_get_codec_config_t audio_get_codec_config;
+    enum A2DP_STATE bt_state;
+    audio_format_t bt_encoder_format;
+    void *enc_config_data;
+    bool a2dp_started;
+    bool a2dp_suspended;
+    int  a2dp_total_active_session_request;
+    bool is_a2dp_offload_supported;
+    bool is_handoff_in_progress;
+};
+
+struct a2dp_data a2dp;
+
+/* START of DSP configurable structures
+ * These values should match with DSP interface defintion
+ */
+
+/* AAC encoder configuration structure. */
+typedef struct aac_enc_cfg_t aac_enc_cfg_t;
+
+/* supported enc_mode are AAC_LC, AAC_SBR, AAC_PS
+ * supported aac_fmt_flag are ADTS/RAW
+ * supported channel_cfg are Native mode, Mono , Stereo
+ */
+struct aac_enc_cfg_t {
+    uint32_t      enc_format;
+    uint32_t      bit_rate;
+    uint32_t      enc_mode;
+    uint16_t      aac_fmt_flag;
+    uint32_t      channel_cfg;
+    uint32_t      sample_rate;
+} ;
+
+/* SBC encoder configuration structure. */
+typedef struct sbc_enc_cfg_t sbc_enc_cfg_t;
+
+/* supported num_subbands are 4/8
+ * supported blk_len are 4, 8, 12, 16
+ * supported channel_mode are MONO, STEREO, DUAL_MONO, JOINT_STEREO
+ * supported alloc_method are LOUNDNESS/SNR
+ * supported bit_rate for mono channel is max 320kbps
+ * supported bit rate for stereo channel is max 512 kbps
+ */
+struct sbc_enc_cfg_t{
+    uint32_t      enc_format;
+    uint32_t      num_subbands;
+    uint32_t      blk_len;
+    uint32_t      channel_mode;
+    uint32_t      alloc_method;
+    uint32_t      bit_rate;
+    uint32_t      sample_rate;
+};
+
+
+/* supported num_channels are Mono/Stereo
+ * supported channel_mapping for mono is CHANNEL_C
+ * supported channel mapping for stereo is CHANNEL_L and CHANNEL_R
+ * custom size and reserved are not used(for future enhancement)
+  */
+struct custom_enc_cfg_aptx_t
+{
+    uint32_t      enc_format;
+    uint32_t      sample_rate;
+    uint16_t      num_channels;
+    uint16_t      reserved;
+    uint8_t       channel_mapping[8];
+    uint32_t      custom_size;
+};
+
+/*********** END of DSP configurable structures ********************/
+
+/* API to identify DSP encoder captabilities */
+static void a2dp_offload_codec_cap_parser(char *value)
+{
+    char *tok = NULL;
+
+    tok = strtok(value, "-");
+    while (tok != NULL) {
+        if (strcmp(tok, "sbc") == 0) {
+            ALOGD("%s: SBC offload supported\n",__func__);
+            a2dp.is_a2dp_offload_supported = true;
+            break;
+        } else if (strcmp(tok, "aptx") == 0) {
+            ALOGD("%s: aptx offload supported\n",__func__);
+            a2dp.is_a2dp_offload_supported = true;
+            break;
+        }
+        tok = strtok(NULL,"-");
+    };
+}
+
+static void update_offload_codec_capabilities()
+{
+    char value[PROPERTY_VALUE_MAX] = {'\0'};
+
+    property_get("persist.bt.a2dp_offload_cap", value, "false");
+    ALOGD("get_offload_codec_capabilities = %s",value);
+    a2dp.is_a2dp_offload_supported =
+            property_get_bool("persist.bt.a2dp_offload_cap", false);
+    if (strcmp(value, "false") != 0)
+        a2dp_offload_codec_cap_parser(value);
+    ALOGD("%s: codec cap = %s",__func__,value);
+}
+
+/* API to open BT IPC library to start IPC communication */
+static void open_a2dp_output()
+{
+    int ret = 0;
+
+    ALOGD(" Open A2DP output start ");
+    if (a2dp.bt_lib_handle == NULL){
+        ALOGD(" Requesting for BT lib handle");
+        a2dp.bt_lib_handle = dlopen(BT_IPC_LIB_NAME, RTLD_NOW);
+
+        if (a2dp.bt_lib_handle == NULL) {
+            ALOGE("%s: DLOPEN failed for %s", __func__, BT_IPC_LIB_NAME);
+            ret = -ENOSYS;
+            goto init_fail;
+        } else {
+            a2dp.audio_stream_open = (audio_stream_open_t)
+                          dlsym(a2dp.bt_lib_handle, "audio_stream_open");
+            a2dp.audio_start_stream = (audio_start_stream_t)
+                          dlsym(a2dp.bt_lib_handle, "audio_start_stream");
+            a2dp.audio_get_codec_config = (audio_get_codec_config_t)
+                          dlsym(a2dp.bt_lib_handle, "audio_get_codec_config");
+            a2dp.audio_suspend_stream = (audio_suspend_stream_t)
+                          dlsym(a2dp.bt_lib_handle, "audio_suspend_stream");
+            a2dp.audio_handoff_triggered = (audio_handoff_triggered_t)
+                          dlsym(a2dp.bt_lib_handle, "audio_handoff_triggered");
+            a2dp.clear_a2dpsuspend_flag = (clear_a2dpsuspend_flag_t)
+                          dlsym(a2dp.bt_lib_handle, "clear_a2dpsuspend_flag");
+            a2dp.audio_stop_stream = (audio_stop_stream_t)
+                          dlsym(a2dp.bt_lib_handle, "audio_stop_stream");
+            a2dp.audio_stream_close = (audio_stream_close_t)
+                          dlsym(a2dp.bt_lib_handle, "audio_stream_close");
+        }
+    }
+
+    if (a2dp.bt_lib_handle && a2dp.audio_stream_open) {
+        if (a2dp.bt_state == A2DP_STATE_DISCONNECTED) {
+            ALOGD("calling BT stream open");
+            ret = a2dp.audio_stream_open();
+            if(ret != 0) {
+                ALOGE("Failed to open output stream for a2dp: status %d", ret);
+                goto init_fail;
+            }
+            a2dp.bt_state = A2DP_STATE_CONNECTED;
+        } else {
+            ALOGD("Called a2dp open with improper state, Ignoring request state %d", a2dp.bt_state);
+        }
+    } else {
+        ALOGE("a2dp handle is not identified, Ignoring open request");
+        a2dp.bt_state = A2DP_STATE_DISCONNECTED;
+        goto init_fail;
+    }
+
+init_fail:
+    if(ret != 0 && (a2dp.bt_lib_handle != NULL)) {
+        dlclose(a2dp.bt_lib_handle);
+        a2dp.bt_lib_handle = NULL;
+    }
+}
+
+static int close_a2dp_output()
+{
+    ALOGV("%s\n",__func__);
+    if (!(a2dp.bt_lib_handle && a2dp.audio_stream_close)) {
+        ALOGE("a2dp handle is not identified, Ignoring close request");
+        return -ENOSYS;
+    }
+    if ((a2dp.bt_state == A2DP_STATE_CONNECTED) &&
+        (a2dp.bt_state == A2DP_STATE_STARTED) &&
+        (a2dp.bt_state == A2DP_STATE_STOPPED)) {
+        ALOGD("calling BT stream close");
+        if(a2dp.audio_stream_close() == false)
+            ALOGE("failed close a2dp control path from BT library");
+        a2dp.a2dp_started = false;
+        a2dp.a2dp_total_active_session_request = 0;
+        a2dp.a2dp_suspended = false;
+        a2dp.bt_encoder_format = AUDIO_FORMAT_INVALID;
+        a2dp.enc_config_data = NULL;
+        a2dp.bt_state = A2DP_STATE_DISCONNECTED;
+    } else {
+        ALOGD("close a2dp called in improper state");
+        a2dp.a2dp_started = false;
+        a2dp.a2dp_total_active_session_request = 0;
+        a2dp.a2dp_suspended = false;
+        a2dp.bt_encoder_format = AUDIO_FORMAT_INVALID;
+        a2dp.enc_config_data = NULL;
+        a2dp.bt_state = A2DP_STATE_DISCONNECTED;
+    }
+
+    return 0;
+}
+
+/* API to configure SBC DSP encoder */
+bool configure_sbc_enc_format(audio_sbc_encoder_config *sbc_bt_cfg)
+{
+    struct mixer_ctl *ctl_enc_data;
+    struct sbc_enc_cfg_t sbc_dsp_cfg;
+    bool is_configured = false;
+    int ret = 0;
+
+    if(sbc_bt_cfg == NULL)
+        return false;
+
+   ctl_enc_data = mixer_get_ctl_by_name(a2dp.adev->mixer, MIXER_ENC_CONFIG_BLOCK);
+    if (!ctl_enc_data) {
+        ALOGE(" ERROR  a2dp encoder CONFIG data mixer control not identifed");
+        is_configured = false;
+        goto fail;
+    }
+    a2dp.bt_encoder_format = AUDIO_FORMAT_SBC;
+    memset(&sbc_dsp_cfg, 0x0, sizeof(struct sbc_enc_cfg_t));
+    sbc_dsp_cfg.enc_format = ENC_MEDIA_FMT_SBC;
+    sbc_dsp_cfg.num_subbands = sbc_bt_cfg->subband;
+    sbc_dsp_cfg.blk_len = sbc_bt_cfg->blk_len;
+    switch(sbc_bt_cfg->channels) {
+        case 0:
+            sbc_dsp_cfg.channel_mode = MEDIA_FMT_SBC_CHANNEL_MODE_MONO;
+            break;
+        case 1:
+            sbc_dsp_cfg.channel_mode = MEDIA_FMT_SBC_CHANNEL_MODE_DUAL_MONO;
+            break;
+        case 3:
+            sbc_dsp_cfg.channel_mode = MEDIA_FMT_SBC_CHANNEL_MODE_JOINT_STEREO;
+            break;
+        case 2:
+        default:
+            sbc_dsp_cfg.channel_mode = MEDIA_FMT_SBC_CHANNEL_MODE_STEREO;
+            break;
+    }
+    if (sbc_bt_cfg->alloc)
+        sbc_dsp_cfg.alloc_method = MEDIA_FMT_SBC_ALLOCATION_METHOD_LOUDNESS;
+    else
+        sbc_dsp_cfg.alloc_method = MEDIA_FMT_SBC_ALLOCATION_METHOD_SNR;
+    sbc_dsp_cfg.bit_rate = sbc_bt_cfg->bitrate;
+    sbc_dsp_cfg.sample_rate = sbc_bt_cfg->sampling_rate;
+    ret = mixer_ctl_set_array(ctl_enc_data, (void *)&sbc_dsp_cfg,
+                                    sizeof(struct sbc_enc_cfg_t));
+    if (ret != 0) {
+        ALOGE("%s: failed to set SBC encoder config", __func__);
+        is_configured = false;
+    } else
+        is_configured = true;
+fail:
+    return is_configured;
+}
+
+/* API to configure APTX DSP encoder */
+bool configure_aptx_enc_format(audio_aptx_encoder_config *aptx_bt_cfg)
+{
+    struct mixer_ctl *ctl_enc_data;
+    struct custom_enc_cfg_aptx_t aptx_dsp_cfg;
+    bool is_configured = false;
+    int ret = 0;
+
+    if(aptx_bt_cfg == NULL)
+        return false;
+
+    ctl_enc_data = mixer_get_ctl_by_name(a2dp.adev->mixer, MIXER_ENC_CONFIG_BLOCK);
+    if (!ctl_enc_data) {
+        ALOGE(" ERROR  a2dp encoder CONFIG data mixer control not identifed");
+        is_configured = false;
+        goto fail;
+    }
+    a2dp.bt_encoder_format = AUDIO_FORMAT_APTX;
+    memset(&aptx_dsp_cfg, 0x0, sizeof(struct custom_enc_cfg_aptx_t));
+    aptx_dsp_cfg.enc_format = ENC_MEDIA_FMT_APTX;
+    aptx_dsp_cfg.sample_rate = aptx_bt_cfg->sampling_rate;
+    aptx_dsp_cfg.num_channels = aptx_bt_cfg->channels;
+    switch(aptx_dsp_cfg.num_channels) {
+        case 1:
+            aptx_dsp_cfg.channel_mapping[0] = PCM_CHANNEL_C;
+            break;
+        case 2:
+        default:
+            aptx_dsp_cfg.channel_mapping[0] = PCM_CHANNEL_L;
+            aptx_dsp_cfg.channel_mapping[1] = PCM_CHANNEL_R;
+            break;
+    }
+    ret = mixer_ctl_set_array(ctl_enc_data, (void *)&aptx_dsp_cfg,
+                              sizeof(struct custom_enc_cfg_aptx_t));
+    if (ret != 0) {
+        ALOGE("%s: Failed to set APTX encoder config", __func__);
+        is_configured = false;
+        goto fail;
+    }
+    is_configured = true;
+fail:
+    return is_configured;
+}
+
+/* API to configure APTX HD DSP encoder
+ * TODO: ADD 24 bit configuration support
+ */
+bool configure_aptx_hd_enc_format(audio_aptx_encoder_config *aptx_bt_cfg)
+{
+    struct mixer_ctl *ctl_enc_data;
+    struct custom_enc_cfg_aptx_t aptx_dsp_cfg;
+    bool is_configured = false;
+    int ret = 0;
+
+    if(aptx_bt_cfg == NULL)
+        return false;
+
+    ctl_enc_data = mixer_get_ctl_by_name(a2dp.adev->mixer, MIXER_ENC_CONFIG_BLOCK);
+    if (!ctl_enc_data) {
+        ALOGE(" ERROR  a2dp encoder CONFIG data mixer control not identifed");
+        is_configured = false;
+        goto fail;
+    }
+    a2dp.bt_encoder_format = AUDIO_FORMAT_APTX_HD;
+    memset(&aptx_dsp_cfg, 0x0, sizeof(struct custom_enc_cfg_aptx_t));
+    aptx_dsp_cfg.enc_format = ENC_MEDIA_FMT_APTX_HD;
+    aptx_dsp_cfg.sample_rate = aptx_bt_cfg->sampling_rate;
+    aptx_dsp_cfg.num_channels = aptx_bt_cfg->channels;
+    switch(aptx_dsp_cfg.num_channels) {
+        case 1:
+            aptx_dsp_cfg.channel_mapping[0] = PCM_CHANNEL_C;
+            break;
+        case 2:
+        default:
+            aptx_dsp_cfg.channel_mapping[0] = PCM_CHANNEL_L;
+            aptx_dsp_cfg.channel_mapping[1] = PCM_CHANNEL_R;
+            break;
+    }
+    ret = mixer_ctl_set_array(ctl_enc_data, (void *)&aptx_dsp_cfg,
+                              sizeof(struct custom_enc_cfg_aptx_t));
+    if (ret != 0) {
+        ALOGE("%s: Failed to set APTX HD encoder config", __func__);
+        is_configured = false;
+        goto fail;
+    }
+    is_configured = true;
+fail:
+    return is_configured;
+}
+
+/* API to configure AAC DSP encoder */
+bool configure_aac_enc_format(audio_aac_encoder_config *aac_bt_cfg)
+{
+    struct mixer_ctl *ctl_enc_data;
+    struct aac_enc_cfg_t aac_dsp_cfg;
+    bool is_configured = false;
+    int ret = 0;
+
+    if(aac_bt_cfg == NULL)
+        return false;
+
+    ctl_enc_data = mixer_get_ctl_by_name(a2dp.adev->mixer, MIXER_ENC_CONFIG_BLOCK);
+    if (!ctl_enc_data) {
+        ALOGE(" ERROR  a2dp encoder CONFIG data mixer control not identifed");
+        is_configured = false;
+        goto fail;
+    }
+    a2dp.bt_encoder_format = AUDIO_FORMAT_AAC;
+    memset(&aac_dsp_cfg, 0x0, sizeof(struct aac_enc_cfg_t));
+    aac_dsp_cfg.enc_format = ENC_MEDIA_FMT_AAC;
+    aac_dsp_cfg.bit_rate = aac_bt_cfg->bitrate;
+    switch(aac_bt_cfg->enc_mode) {
+        case 0:
+            aac_dsp_cfg.enc_mode = MEDIA_FMT_AAC_AOT_LC;
+            break;
+        case 2:
+            aac_dsp_cfg.enc_mode = MEDIA_FMT_AAC_AOT_PS;
+            break;
+        case 1:
+        default:
+            aac_dsp_cfg.enc_mode = MEDIA_FMT_AAC_AOT_SBR;
+            break;
+    }
+    if (aac_bt_cfg->format_flag)
+        aac_dsp_cfg.aac_fmt_flag = MEDIA_FMT_AAC_FORMAT_FLAG_RAW;
+    else
+        aac_dsp_cfg.aac_fmt_flag = MEDIA_FMT_AAC_FORMAT_FLAG_ADTS;
+    aac_dsp_cfg.channel_cfg = aac_bt_cfg->channels;
+    ret = mixer_ctl_set_array(ctl_enc_data, (void *)&aac_dsp_cfg,
+                              sizeof(struct aac_enc_cfg_t));
+    if (ret != 0) {
+        ALOGE("%s: failed to set SBC encoder config", __func__);
+        is_configured = false;
+    } else
+        is_configured = true;
+fail:
+    return is_configured;
+}
+
+bool configure_a2dp_encoder_format()
+{
+    void *codec_info = NULL;
+    uint8_t multi_cast = 0, num_dev = 1;
+    audio_format_t codec_type = AUDIO_FORMAT_INVALID;
+    bool is_configured = false;
+
+    if (!a2dp.audio_get_codec_config) {
+        ALOGE(" a2dp handle is not identified, ignoring a2dp encoder config");
+        return false;
+    }
+    ALOGD("configure_a2dp_encoder_format start");
+    codec_info = a2dp.audio_get_codec_config(&multi_cast, &num_dev,
+                               &codec_type);
+
+    switch(codec_type) {
+        case AUDIO_FORMAT_SBC:
+            ALOGD(" Received SBC encoder supported BT device");
+            is_configured =
+               configure_sbc_enc_format((audio_sbc_encoder_config *)codec_info);
+            break;
+        case AUDIO_FORMAT_APTX:
+            ALOGD(" Received APTX encoder supported BT device");
+            is_configured =
+              configure_aptx_enc_format((audio_aptx_encoder_config *)codec_info);
+            break;
+        case AUDIO_FORMAT_APTX_HD:
+            ALOGD(" Received APTX HD encoder supported BT device");
+            is_configured =
+             configure_aptx_hd_enc_format((audio_aptx_encoder_config *)codec_info);
+            break;
+        case AUDIO_FORMAT_AAC:
+            ALOGD(" Received AAC encoder supported BT device");
+            is_configured =
+              configure_aac_enc_format((audio_aac_encoder_config *)codec_info);
+            break;
+        default:
+            ALOGD(" Received Unsupported encoder formar");
+            is_configured = false;
+            break;
+    }
+    return is_configured;
+}
+
+int audio_extn_a2dp_start_playback()
+{
+    int ret = 0;
+
+    ALOGD("audio_extn_a2dp_start_playback start");
+
+    if(!(a2dp.bt_lib_handle && a2dp.audio_start_stream
+       && a2dp.audio_get_codec_config)) {
+        ALOGE("a2dp handle is not identified, Ignoring start request");
+        return -ENOSYS;
+    }
+
+    if(a2dp.a2dp_suspended == true) {
+        //session will be restarted after suspend completion
+        ALOGD("a2dp start requested during suspend state");
+        a2dp.a2dp_total_active_session_request++;
+        return 0;
+    }
+
+    if (!a2dp.a2dp_started && !a2dp.a2dp_total_active_session_request) {
+        ALOGD("calling BT module stream start");
+        /* This call indicates BT IPC lib to start playback */
+        ret =  a2dp.audio_start_stream();
+        ALOGE("BT controller start return = %d",ret);
+        if (ret != 0 ) {
+           ALOGE("BT controller start failed");
+           a2dp.a2dp_started = false;
+           ret = -ETIMEDOUT;
+        } else {
+           if(configure_a2dp_encoder_format() == true) {
+                a2dp.a2dp_started = true;
+                ret = 0;
+                ALOGD("Start playback successful to BT library");
+           } else {
+                ALOGD(" unable to configure DSP encoder");
+                a2dp.a2dp_started = false;
+                ret = -ETIMEDOUT;
+           }
+        }
+    }
+
+    if (a2dp.a2dp_started)
+        a2dp.a2dp_total_active_session_request++;
+
+    ALOGD("start A2DP playback total active sessions :%d",
+          a2dp.a2dp_total_active_session_request);
+    return ret;
+}
+
+int audio_extn_a2dp_stop_playback()
+{
+    int ret =0;
+
+    ALOGV("audio_extn_a2dp_stop_playback start");
+    if(!(a2dp.bt_lib_handle && a2dp.audio_stop_stream)) {
+        ALOGE("a2dp handle is not identified, Ignoring start request");
+        return -ENOSYS;
+    }
+
+    if(a2dp.a2dp_suspended == true) {
+        ALOGD("STOP playback is called during suspend state");
+
+        // sessions are already closed during suspend, just update active sessions counts
+         if(a2dp.a2dp_total_active_session_request > 0)
+            a2dp.a2dp_total_active_session_request--;
+         return 0;
+    }
+    if (a2dp.a2dp_started && (a2dp.a2dp_total_active_session_request > 0))
+        a2dp.a2dp_total_active_session_request--;
+
+    if ( a2dp.a2dp_started && !a2dp.a2dp_total_active_session_request) {
+        struct mixer_ctl *ctl_enc_config;
+        struct sbc_enc_cfg_t dummy_reset_config;
+
+        ALOGV("calling BT module stream stop");
+        ret = a2dp.audio_stop_stream();
+        if (ret < 0)
+            ALOGE("stop stream to BT IPC lib failed");
+        else
+            ALOGV("stop steam to BT IPC lib successful");
+        a2dp.is_handoff_in_progress = false;
+
+         memset(&dummy_reset_config, 0x0, sizeof(struct sbc_enc_cfg_t));
+        ctl_enc_config = mixer_get_ctl_by_name(a2dp.adev->mixer,
+                                               MIXER_ENC_CONFIG_BLOCK);
+        if (!ctl_enc_config) {
+            ALOGE(" ERROR  a2dp encoder format mixer control not identifed");
+        } else {
+            ret = mixer_ctl_set_array(ctl_enc_config, (void *)&dummy_reset_config,
+                                            sizeof(struct sbc_enc_cfg_t));
+             a2dp.bt_encoder_format = ENC_MEDIA_FMT_NONE;
+        }
+    }
+    if(!a2dp.a2dp_total_active_session_request)
+       a2dp.a2dp_started = false;
+    ALOGD("Stop A2DP playback total active sessions :%d",
+          a2dp.a2dp_total_active_session_request);
+    return 0;
+}
+
+void audio_extn_a2dp_set_parameters(struct str_parms *parms)
+{
+     int ret, val;
+     char value[32]={0};
+
+     if(a2dp.is_a2dp_offload_supported == false) {
+        ALOGV("no supported encoders identified,ignoring a2dp setparam");
+        return;
+     }
+
+     ret = str_parms_get_str(parms, AUDIO_PARAMETER_DEVICE_CONNECT, value,
+                            sizeof(value));
+     if( ret >= 0) {
+         val = atoi(value);
+         if (val & AUDIO_DEVICE_OUT_ALL_A2DP) {
+             ALOGV("Received device connect request for A2DP");
+             open_a2dp_output();
+         }
+         goto param_handled;
+     }
+
+     ret = str_parms_get_str(parms, AUDIO_PARAMETER_DEVICE_DISCONNECT, value,
+                         sizeof(value));
+
+     if( ret >= 0) {
+         val = atoi(value);
+         if (val & AUDIO_DEVICE_OUT_ALL_A2DP) {
+             ALOGV("Received device dis- connect request");
+             close_a2dp_output();
+         }
+         goto param_handled;
+     }
+
+     ret = str_parms_get_str(parms, "A2dpSuspended", value, sizeof(value));
+     if (ret >= 0) {
+         if (a2dp.bt_lib_handle && (a2dp.bt_state != A2DP_STATE_DISCONNECTED) ) {
+             if ((!strncmp(value,"true",sizeof(value)))) {
+                ALOGD("Setting a2dp to suspend state");
+                int active_sessions = a2dp.a2dp_total_active_session_request, count = 0;
+                //Force close all active sessions on suspend (if any)
+                for(count  = 0; count< active_sessions; count ++)
+                    audio_extn_a2dp_stop_playback();
+                a2dp.a2dp_total_active_session_request = active_sessions;
+                a2dp.a2dp_suspended = true;
+
+                if(a2dp.audio_suspend_stream)
+                   a2dp.audio_suspend_stream();
+            } else if (a2dp.a2dp_suspended == true) {
+                ALOGD("Resetting a2dp suspend state");
+                if(a2dp.clear_a2dpsuspend_flag)
+                    a2dp.clear_a2dpsuspend_flag();
+
+                a2dp.a2dp_suspended = false;
+                //Force restart all active sessions post suspend (if any)
+                if(a2dp.a2dp_total_active_session_request > 0){
+                    int active_sessions = a2dp.a2dp_total_active_session_request;
+                    a2dp.a2dp_total_active_session_request = 0;
+                    audio_extn_a2dp_start_playback();
+                    a2dp.a2dp_total_active_session_request = active_sessions;
+                }
+            }
+        }
+        goto param_handled;
+     }
+     ret = str_parms_get_str(parms,"reconfigA2dp", value, sizeof(value));
+     if (ret >= 0) {
+         if (a2dp.bt_lib_handle && (a2dp.bt_state != A2DP_STATE_DISCONNECTED)) {
+             if (!strncmp(value,"true",sizeof(value)))
+                 a2dp.is_handoff_in_progress = true;
+         }
+         goto param_handled;
+     }
+param_handled:
+     ALOGV("end of a2dp setparam");
+}
+
+bool audio_extn_a2dp_is_force_device_switch()
+{
+    //During encoder reconfiguration mode, force a2dp device switch
+    return a2dp.is_handoff_in_progress;
+}
+
+void audio_extn_a2dp_init (void *adev)
+{
+  a2dp.adev = (struct audio_device*)adev;
+  a2dp.bt_lib_handle = NULL;
+  a2dp.a2dp_started = false;
+  a2dp.bt_state = A2DP_STATE_DISCONNECTED;
+  a2dp.a2dp_total_active_session_request = 0;
+  a2dp.a2dp_suspended = false;
+  a2dp.bt_encoder_format = AUDIO_FORMAT_INVALID;
+  a2dp.enc_config_data = NULL;
+  a2dp.is_a2dp_offload_supported = false;
+  a2dp.is_handoff_in_progress = false;
+  update_offload_codec_capabilities();
+}
+#endif // SPLIT_A2DP_ENABLED
diff --git a/hal/audio_extn/audio_extn.c b/hal/audio_extn/audio_extn.c
index 49e649c..b316473 100644
--- a/hal/audio_extn/audio_extn.c
+++ b/hal/audio_extn/audio_extn.c
@@ -755,6 +755,7 @@
    audio_extn_ssr_set_parameters(adev, parms);
    audio_extn_hfp_set_parameters(adev, parms);
    audio_extn_dts_eagle_set_parameters(adev, parms);
+   audio_extn_a2dp_set_parameters(parms);
    audio_extn_ddp_set_parameters(adev, parms);
    audio_extn_ds2_set_parameters(adev, parms);
    audio_extn_customstereo_set_parameters(adev, parms);
diff --git a/hal/audio_extn/audio_extn.h b/hal/audio_extn/audio_extn.h
index fe3fe95..d186a5f 100644
--- a/hal/audio_extn/audio_extn.h
+++ b/hal/audio_extn/audio_extn.h
@@ -171,6 +171,20 @@
                                      char *value, int len);
 #endif
 
+#ifndef SPLIT_A2DP_ENABLED
+#define audio_extn_a2dp_init(adev)                       (0)
+#define audio_extn_a2dp_start_playback()                 (0)
+#define audio_extn_a2dp_stop_playback()                  (0)
+#define audio_extn_a2dp_set_parameters(parms)            (0)
+#define audio_extn_a2dp_is_force_device_switch()         (0)
+#else
+void audio_extn_a2dp_init(void *adev);
+int audio_extn_a2dp_start_playback();
+void audio_extn_a2dp_stop_playback();
+void audio_extn_a2dp_set_parameters(struct str_parms *parms);
+bool audio_extn_a2dp_is_force_device_switch();
+#endif
+
 #ifndef SSR_ENABLED
 #define audio_extn_ssr_check_and_set_usecase(in)      (0)
 #define audio_extn_ssr_init(in, num_out_chan)         (0)
diff --git a/hal/audio_hw.c b/hal/audio_hw.c
index 498bab7..db1e399 100644
--- a/hal/audio_hw.c
+++ b/hal/audio_hw.c
@@ -716,6 +716,13 @@
          audio_extn_spkr_prot_calib_cancel(adev);
 
 
+    if (((SND_DEVICE_OUT_BT_A2DP == snd_device) ||
+       (SND_DEVICE_OUT_SPEAKER_AND_BT_A2DP == snd_device))
+        && (audio_extn_a2dp_start_playback() < 0)) {
+           ALOGE(" fail to configure A2dp control path ");
+           return -EINVAL;
+    }
+
     if (platform_can_enable_spkr_prot_on_device(snd_device) &&
          audio_extn_spkr_prot_is_enabled()) {
        if (platform_get_spkr_prot_acdb_id(snd_device) < 0) {
@@ -791,6 +798,11 @@
 
     if (adev->snd_dev_ref_cnt[snd_device] == 0) {
         ALOGD("%s: snd_device(%d: %s)", __func__, snd_device, device_name);
+
+        if ((SND_DEVICE_OUT_BT_A2DP == snd_device) ||
+           (SND_DEVICE_OUT_SPEAKER_AND_BT_A2DP == snd_device))
+            audio_extn_a2dp_stop_playback();
+
         if (platform_can_enable_spkr_prot_on_device(snd_device) &&
              audio_extn_spkr_prot_is_enabled()) {
             audio_extn_spkr_prot_stop_processing(snd_device);
@@ -832,7 +844,7 @@
     struct audio_usecase *usecase;
     bool switch_device[AUDIO_USECASE_MAX];
     int i, num_uc_to_switch = 0;
-
+    bool force_restart_session = false;
     /*
      * This function is to make sure that all the usecases that are active on
      * the hardware codec backend are always routed to any one device that is
@@ -852,7 +864,15 @@
      */
     bool force_routing = platform_check_and_set_codec_backend_cfg(adev, uc_info,
                          snd_device);
-
+    /* For a2dp device reconfigure all active sessions
+     * with new AFE encoder format based on a2dp state
+     */
+    if ((SND_DEVICE_OUT_BT_A2DP == snd_device ||
+         SND_DEVICE_OUT_SPEAKER_AND_BT_A2DP == snd_device) &&
+         audio_extn_a2dp_is_force_device_switch()) {
+         force_routing = true;
+         force_restart_session = true;
+    }
     ALOGD("%s:becf: force routing %d", __func__, force_routing);
 
     /* Disable all the usecases on the shared backend other than the
@@ -871,8 +891,9 @@
               platform_check_backends_match(snd_device, usecase->out_snd_device));
         if (usecase->type != PCM_CAPTURE &&
             usecase != uc_info &&
-            (usecase->out_snd_device != snd_device || force_routing)  &&
-            usecase->devices & AUDIO_DEVICE_OUT_ALL_CODEC_BACKEND &&
+            (usecase->out_snd_device != snd_device || force_routing) &&
+            (usecase->devices & AUDIO_DEVICE_OUT_ALL_CODEC_BACKEND ||
+            force_restart_session) &&
             platform_check_backends_match(snd_device, usecase->out_snd_device)) {
                 ALOGD("%s:becf: check_usecases (%s) is active on (%s) - disabling ..",
                     __func__, use_case_table[usecase->id],
@@ -1163,6 +1184,14 @@
         }
     }
 
+    // Force all a2dp output devices to reconfigure for proper AFE encode format
+    if((usecase->stream.out) &&
+       (usecase->stream.out->devices & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP) &&
+       audio_extn_a2dp_is_force_device_switch()) {
+         ALOGD("Force a2dp device switch to update new encoder config");
+         ret = true;
+     }
+
     return ret;
 }
 
@@ -2452,6 +2481,17 @@
                 (platform_get_edid_info(adev->platform) != 0) /* HDMI disconnected */) {
             val = AUDIO_DEVICE_OUT_SPEAKER;
         }
+        /*
+         * When A2DP is disconnected the
+         * music playback is paused and the policy manager sends routing=0
+         * But the audioflingercontinues to write data until standby time
+         * (3sec). As BT is turned off, the write gets blocked.
+         * Avoid this by routing audio to speaker until standby.
+         */
+        if ((out->devices & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP) &&
+                (val == AUDIO_DEVICE_NONE)) {
+                val = AUDIO_DEVICE_OUT_SPEAKER;
+        }
 
         /*
          * select_devices() call below switches all the usecases on the same
@@ -4174,6 +4214,22 @@
     }
 
     audio_extn_set_parameters(adev, parms);
+    // reconfigure should be done only after updating a2dpstate in audio extn
+    ret = str_parms_get_str(parms,"reconfigA2dp", value, sizeof(value));
+    if (ret >= 0) {
+        struct audio_usecase *usecase;
+        struct listnode *node;
+        list_for_each(node, &adev->usecase_list) {
+            usecase = node_to_item(node, struct audio_usecase, list);
+            if ((usecase->type == PCM_PLAYBACK) &&
+                (usecase->devices & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP)){
+                ALOGD("reconfigure a2dp... forcing device switch");
+                //force device switch to re configure encoder
+                select_devices(adev, usecase->id);
+                break;
+            }
+        }
+    }
 
 done:
     str_parms_destroy(parms);
diff --git a/hal/msm8916/platform.c b/hal/msm8916/platform.c
index 2047f76..9b7616e 100644
--- a/hal/msm8916/platform.c
+++ b/hal/msm8916/platform.c
@@ -346,6 +346,8 @@
     [SND_DEVICE_OUT_SPEAKER_AND_HDMI] = "speaker-and-hdmi",
     [SND_DEVICE_OUT_BT_SCO] = "bt-sco-headset",
     [SND_DEVICE_OUT_BT_SCO_WB] = "bt-sco-headset-wb",
+    [SND_DEVICE_OUT_BT_A2DP] = "bt-a2dp",
+    [SND_DEVICE_OUT_SPEAKER_AND_BT_A2DP] = "speaker-and-bt-a2dp",
     [SND_DEVICE_OUT_VOICE_TTY_FULL_HEADPHONES] = "voice-tty-full-headphones",
     [SND_DEVICE_OUT_VOICE_TTY_VCO_HEADPHONES] = "voice-tty-vco-headphones",
     [SND_DEVICE_OUT_VOICE_TTY_HCO_HANDSET] = "voice-tty-hco-handset",
@@ -465,6 +467,8 @@
     [SND_DEVICE_OUT_SPEAKER_AND_HDMI] = 14,
     [SND_DEVICE_OUT_BT_SCO] = 22,
     [SND_DEVICE_OUT_BT_SCO_WB] = 39,
+    [SND_DEVICE_OUT_BT_A2DP] = 20,
+    [SND_DEVICE_OUT_SPEAKER_AND_BT_A2DP] = 14,
     [SND_DEVICE_OUT_VOICE_TTY_FULL_HEADPHONES] = 17,
     [SND_DEVICE_OUT_VOICE_TTY_VCO_HEADPHONES] = 17,
     [SND_DEVICE_OUT_VOICE_TTY_HCO_HANDSET] = 37,
@@ -586,6 +590,8 @@
     {TO_NAME_INDEX(SND_DEVICE_OUT_SPEAKER_AND_HDMI)},
     {TO_NAME_INDEX(SND_DEVICE_OUT_BT_SCO)},
     {TO_NAME_INDEX(SND_DEVICE_OUT_BT_SCO_WB)},
+    {TO_NAME_INDEX(SND_DEVICE_OUT_BT_A2DP)},
+    {TO_NAME_INDEX(SND_DEVICE_OUT_SPEAKER_AND_BT_A2DP)},
     {TO_NAME_INDEX(SND_DEVICE_OUT_VOICE_TTY_FULL_HEADPHONES)},
     {TO_NAME_INDEX(SND_DEVICE_OUT_VOICE_TTY_VCO_HEADPHONES)},
     {TO_NAME_INDEX(SND_DEVICE_OUT_VOICE_TTY_HCO_HANDSET)},
@@ -1206,6 +1212,8 @@
     backend_tag_table[SND_DEVICE_OUT_TRANSMISSION_FM] = strdup("transmission-fm");
     backend_tag_table[SND_DEVICE_OUT_HEADPHONES_44_1] = strdup("headphones-44.1");
     backend_tag_table[SND_DEVICE_OUT_VOICE_SPEAKER_VBAT] = strdup("vbat-voice-speaker");
+    backend_tag_table[SND_DEVICE_OUT_BT_A2DP] = strdup("bt-a2dp");
+    backend_tag_table[SND_DEVICE_OUT_SPEAKER_AND_BT_A2DP] = strdup("speaker-and-bt-a2dp");
 
     hw_interface_table[SND_DEVICE_OUT_HDMI] = strdup("HDMI_RX");
     hw_interface_table[SND_DEVICE_OUT_SPEAKER_AND_HDMI] = strdup("SLIMBUS_0_RX-and-HDMI_RX");
@@ -1848,6 +1856,9 @@
     /* init usb */
     audio_extn_usb_init(adev);
 
+    /*init a2dp*/
+    audio_extn_a2dp_init(adev);
+
     /* Read one time ssr property */
     audio_extn_ssr_update_enabled();
     audio_extn_spkr_prot_init(adev);
@@ -2887,6 +2898,9 @@
         } else if (devices == (AUDIO_DEVICE_OUT_USB_DEVICE |
                                AUDIO_DEVICE_OUT_SPEAKER)) {
             snd_device = SND_DEVICE_OUT_SPEAKER_AND_USB_HEADSET;
+        } else if ((devices & AUDIO_DEVICE_OUT_SPEAKER) &&
+                   (devices & AUDIO_DEVICE_OUT_ALL_A2DP)) {
+            snd_device = SND_DEVICE_OUT_SPEAKER_AND_BT_A2DP;
         } else {
             ALOGE("%s: Invalid combo device(%#x)", __func__, devices);
             goto exit;
@@ -2937,6 +2951,8 @@
                 snd_device = SND_DEVICE_OUT_BT_SCO_WB;
             else
                 snd_device = SND_DEVICE_OUT_BT_SCO;
+        } else if (devices & AUDIO_DEVICE_OUT_ALL_A2DP) {
+                snd_device = SND_DEVICE_OUT_BT_A2DP;
         } else if (devices & AUDIO_DEVICE_OUT_SPEAKER) {
                 if (my_data->is_vbat_speaker)
                     snd_device = SND_DEVICE_OUT_VOICE_SPEAKER_VBAT;
@@ -3020,6 +3036,8 @@
             snd_device = SND_DEVICE_OUT_BT_SCO;
     } else if (devices & AUDIO_DEVICE_OUT_AUX_DIGITAL) {
         snd_device = SND_DEVICE_OUT_HDMI ;
+    } else if (devices & AUDIO_DEVICE_OUT_ALL_A2DP) {
+        snd_device = SND_DEVICE_OUT_BT_A2DP;
     } else if (devices & AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET ||
                devices & AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET) {
         ALOGD("%s: setting USB hadset channel capability(2) for Proxy", __func__);
diff --git a/hal/msm8916/platform.h b/hal/msm8916/platform.h
index 756c749..cd5aeb7 100644
--- a/hal/msm8916/platform.h
+++ b/hal/msm8916/platform.h
@@ -98,6 +98,8 @@
     SND_DEVICE_OUT_SPEAKER_AND_HDMI,
     SND_DEVICE_OUT_BT_SCO,
     SND_DEVICE_OUT_BT_SCO_WB,
+    SND_DEVICE_OUT_BT_A2DP,
+    SND_DEVICE_OUT_SPEAKER_AND_BT_A2DP,
     SND_DEVICE_OUT_VOICE_TTY_FULL_HEADPHONES,
     SND_DEVICE_OUT_VOICE_TTY_VCO_HEADPHONES,
     SND_DEVICE_OUT_VOICE_TTY_HCO_HANDSET,
diff --git a/hal/msm8960/platform.h b/hal/msm8960/platform.h
index aab5436..e42af8c 100644
--- a/hal/msm8960/platform.h
+++ b/hal/msm8960/platform.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, 2015 The Linux Foundation. All rights reserved.
+ * Copyright (c) 2013, 2016 The Linux Foundation. All rights reserved.
  * Not a contribution.
  *
  * Copyright (C) 2013 The Android Open Source Project
@@ -64,6 +64,8 @@
     SND_DEVICE_OUT_HDMI,
     SND_DEVICE_OUT_SPEAKER_AND_HDMI,
     SND_DEVICE_OUT_BT_SCO,
+    SND_DEVICE_OUT_BT_A2DP,
+    SND_DEVICE_OUT_SPEAKER_AND_BT_A2DP,
     SND_DEVICE_OUT_BT_SCO_WB,
     SND_DEVICE_OUT_VOICE_TTY_FULL_HEADPHONES,
     SND_DEVICE_OUT_VOICE_TTY_VCO_HEADPHONES,
diff --git a/hal/msm8974/platform.c b/hal/msm8974/platform.c
index 54f2a73..7d8d5c7 100644
--- a/hal/msm8974/platform.c
+++ b/hal/msm8974/platform.c
@@ -347,6 +347,8 @@
     [SND_DEVICE_OUT_SPEAKER_AND_HDMI] = "speaker-and-hdmi",
     [SND_DEVICE_OUT_BT_SCO] = "bt-sco-headset",
     [SND_DEVICE_OUT_BT_SCO_WB] = "bt-sco-headset-wb",
+    [SND_DEVICE_OUT_BT_A2DP] = "bt-a2dp",
+    [SND_DEVICE_OUT_SPEAKER_AND_BT_A2DP] = "speaker-and-bt-a2dp",
     [SND_DEVICE_OUT_VOICE_TTY_FULL_HEADPHONES] = "voice-tty-full-headphones",
     [SND_DEVICE_OUT_VOICE_TTY_VCO_HEADPHONES] = "voice-tty-vco-headphones",
     [SND_DEVICE_OUT_VOICE_TTY_HCO_HANDSET] = "voice-tty-hco-handset",
@@ -461,6 +463,8 @@
     [SND_DEVICE_OUT_SPEAKER_AND_HDMI] = 14,
     [SND_DEVICE_OUT_BT_SCO] = 22,
     [SND_DEVICE_OUT_BT_SCO_WB] = 39,
+    [SND_DEVICE_OUT_BT_A2DP] = 20,
+    [SND_DEVICE_OUT_SPEAKER_AND_BT_A2DP] = 14,
     [SND_DEVICE_OUT_VOICE_TTY_FULL_HEADPHONES] = 17,
     [SND_DEVICE_OUT_VOICE_TTY_VCO_HEADPHONES] = 17,
     [SND_DEVICE_OUT_VOICE_TTY_HCO_HANDSET] = 37,
@@ -577,6 +581,8 @@
     {TO_NAME_INDEX(SND_DEVICE_OUT_SPEAKER_AND_HDMI)},
     {TO_NAME_INDEX(SND_DEVICE_OUT_BT_SCO)},
     {TO_NAME_INDEX(SND_DEVICE_OUT_BT_SCO_WB)},
+    {TO_NAME_INDEX(SND_DEVICE_OUT_BT_A2DP)},
+    {TO_NAME_INDEX(SND_DEVICE_OUT_SPEAKER_AND_BT_A2DP)},
     {TO_NAME_INDEX(SND_DEVICE_OUT_VOICE_TTY_FULL_HEADPHONES)},
     {TO_NAME_INDEX(SND_DEVICE_OUT_VOICE_TTY_VCO_HEADPHONES)},
     {TO_NAME_INDEX(SND_DEVICE_OUT_VOICE_TTY_HCO_HANDSET)},
@@ -1055,7 +1061,8 @@
                  sizeof("apq8084-taiko-i2s-cdp-snd-card"))) {
         plat_data->is_i2s_ext_modem = true;
     }
-    ALOGV("%s, is_i2s_ext_modem:%d",__func__, plat_data->is_i2s_ext_modem);
+    ALOGV("%s, is_i2s_ext_modem:%d soundcard name is %s",__func__,
+           plat_data->is_i2s_ext_modem, snd_card_name);
 
     return plat_data->is_i2s_ext_modem;
 }
@@ -1095,6 +1102,8 @@
     backend_tag_table[SND_DEVICE_OUT_TRANSMISSION_FM] = strdup("transmission-fm");
     backend_tag_table[SND_DEVICE_OUT_HEADPHONES_44_1] = strdup("headphones-44.1");
     backend_tag_table[SND_DEVICE_OUT_VOICE_SPEAKER_VBAT] = strdup("voice-speaker-vbat");
+    backend_tag_table[SND_DEVICE_OUT_BT_A2DP] = strdup("bt-a2dp");
+    backend_tag_table[SND_DEVICE_OUT_SPEAKER_AND_BT_A2DP] = strdup("speaker-and-bt-a2dp");
 
     hw_interface_table[SND_DEVICE_OUT_HEADPHONES_44_1] = strdup("SLIMBUS_5_RX");
     hw_interface_table[SND_DEVICE_OUT_HDMI] = strdup("HDMI_RX");
@@ -1676,6 +1685,9 @@
     /* init usb */
     audio_extn_usb_init(adev);
 
+    /*init a2dp*/
+    audio_extn_a2dp_init(adev);
+
     /* init dap hal */
     audio_extn_dap_hal_init(adev->snd_card);
 
@@ -2659,6 +2671,9 @@
         } else if (devices == (AUDIO_DEVICE_OUT_USB_DEVICE |
                                AUDIO_DEVICE_OUT_SPEAKER)) {
             snd_device = SND_DEVICE_OUT_SPEAKER_AND_USB_HEADSET;
+        } else if ((devices & AUDIO_DEVICE_OUT_SPEAKER) &&
+                   (devices & AUDIO_DEVICE_OUT_ALL_A2DP)) {
+            snd_device = SND_DEVICE_OUT_SPEAKER_AND_BT_A2DP;
         } else {
             ALOGE("%s: Invalid combo device(%#x)", __func__, devices);
             goto exit;
@@ -2714,6 +2729,8 @@
                     snd_device = SND_DEVICE_OUT_VOICE_SPEAKER_VBAT;
                 else
                     snd_device = SND_DEVICE_OUT_VOICE_SPEAKER;
+        } else if (devices & AUDIO_DEVICE_OUT_ALL_A2DP) {
+            snd_device = SND_DEVICE_OUT_BT_A2DP;
         } else if (devices & AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET ||
                    devices & AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET) {
             snd_device = SND_DEVICE_OUT_USB_HEADSET;
@@ -2763,6 +2780,8 @@
             snd_device = SND_DEVICE_OUT_BT_SCO_WB;
         else
             snd_device = SND_DEVICE_OUT_BT_SCO;
+    } else if (devices & AUDIO_DEVICE_OUT_ALL_A2DP) {
+        snd_device = SND_DEVICE_OUT_BT_A2DP;
     } else if (devices & AUDIO_DEVICE_OUT_AUX_DIGITAL) {
         snd_device = SND_DEVICE_OUT_HDMI ;
     } else if (devices & AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET ||
diff --git a/hal/msm8974/platform.h b/hal/msm8974/platform.h
index 24274c6..ec307b8 100644
--- a/hal/msm8974/platform.h
+++ b/hal/msm8974/platform.h
@@ -94,6 +94,8 @@
     SND_DEVICE_OUT_SPEAKER_AND_HDMI,
     SND_DEVICE_OUT_BT_SCO,
     SND_DEVICE_OUT_BT_SCO_WB,
+    SND_DEVICE_OUT_BT_A2DP,
+    SND_DEVICE_OUT_SPEAKER_AND_BT_A2DP,
     SND_DEVICE_OUT_VOICE_TTY_FULL_HEADPHONES,
     SND_DEVICE_OUT_VOICE_TTY_VCO_HEADPHONES,
     SND_DEVICE_OUT_VOICE_TTY_HCO_HANDSET,