hal: Add support for feedback speaker protection

Cherry-picked and squashed CAF commits:
f538cefc0747e6bc2cff296ad295f6a37e17f7a5
e2761eb87903d958e19989875595a642cc7d7ada
924e13ddd76dcb9a7dd693b7af60db27cdb5a1df
28e9ed3b2fb68ecbfdae00ce387e7942ae37940b
cc9bb1477583269215330cb7aeaebe4f808c03b3
e85d046e57863f55a61f6e445b371e8b55907bad
cc96e41e5cd7a1a8a9662f5d844c6e3867bd7573
229659c499ddff858cbcc4336cb035335e38bd2c

Change-Id: Id56ff10214eaed343299cc3dd8649d0621c27c45
diff --git a/hal/Android.mk b/hal/Android.mk
index 346fcb6..564deb3 100644
--- a/hal/Android.mk
+++ b/hal/Android.mk
@@ -31,6 +31,7 @@
 	audio_extn/ext_speaker.c \
 	$(AUDIO_PLATFORM)/platform.c
 
+
 LOCAL_SHARED_LIBRARIES := \
 	liblog \
 	libcutils \
@@ -71,6 +72,11 @@
     LOCAL_SRC_FILES += audio_extn/soundtrigger.c
 endif
 
+ifeq ($(strip $(AUDIO_FEATURE_ENABLED_SPKR_PROTECTION)),true)
+    LOCAL_CFLAGS += -DSPKR_PROT_ENABLED
+    LOCAL_SRC_FILES += audio_extn/spkr_protection.c
+endif
+
 LOCAL_MODULE := audio.primary.$(TARGET_BOARD_PLATFORM)
 
 LOCAL_MODULE_RELATIVE_PATH := hw
diff --git a/hal/audio_extn/audio_extn.h b/hal/audio_extn/audio_extn.h
index e4f0374..ae91833 100644
--- a/hal/audio_extn/audio_extn.h
+++ b/hal/audio_extn/audio_extn.h
@@ -25,6 +25,24 @@
 void audio_extn_extspk_set_mode(void* extn, audio_mode_t mode);
 void audio_extn_extspk_set_voice_vol(void* extn, float vol);
 
+#ifndef SPKR_PROT_ENABLED
+#define audio_extn_spkr_prot_init(adev)       (0)
+#define audio_extn_spkr_prot_start_processing(snd_device)    (-EINVAL)
+#define audio_extn_spkr_prot_calib_cancel(adev) (0)
+#define audio_extn_spkr_prot_stop_processing(snd_device)     (0)
+#define audio_extn_spkr_prot_is_enabled() (false)
+#define audio_extn_spkr_prot_get_acdb_id(snd_device)         (-EINVAL)
+#define audio_extn_get_spkr_prot_snd_device(snd_device) (snd_device)
+#else
+void audio_extn_spkr_prot_init(void *adev);
+int audio_extn_spkr_prot_start_processing(snd_device_t snd_device);
+void audio_extn_spkr_prot_stop_processing(snd_device_t snd_device);
+bool audio_extn_spkr_prot_is_enabled();
+int audio_extn_spkr_prot_get_acdb_id(snd_device_t snd_device);
+int audio_extn_get_spkr_prot_snd_device(snd_device_t snd_device);
+void audio_extn_spkr_prot_calib_cancel(void *adev);
+#endif
+
 #ifndef HFP_ENABLED
 #define audio_extn_hfp_is_active(adev)                  (0)
 #define audio_extn_hfp_get_usecase()                    (-1)
diff --git a/hal/audio_extn/spkr_protection.c b/hal/audio_extn/spkr_protection.c
new file mode 100644
index 0000000..6303217
--- /dev/null
+++ b/hal/audio_extn/spkr_protection.c
@@ -0,0 +1,904 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "audio_hw_spkr_prot"
+/*#define LOG_NDEBUG 0*/
+//#define LOG_NDDEBUG 0
+
+#include <errno.h>
+#include <math.h>
+#include <cutils/log.h>
+#include <fcntl.h>
+#include "audio_hw.h"
+#include "platform.h"
+#include "platform_api.h"
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <dlfcn.h>
+#include <math.h>
+#include <cutils/properties.h>
+#include "audio_extn.h"
+#include <linux/msm_audio_calibration.h>
+
+#ifdef SPKR_PROT_ENABLED
+
+/*Range of spkr temparatures -30C to 80C*/
+#define MIN_SPKR_TEMP_Q6 (-30 * (1 << 6))
+#define MAX_SPKR_TEMP_Q6 (80 * (1 << 6))
+#define VI_FEED_CHANNEL "VI_FEED_TX Channels"
+
+/*Set safe temp value to 40C*/
+#define SAFE_SPKR_TEMP 40
+#define SAFE_SPKR_TEMP_Q6 (SAFE_SPKR_TEMP * (1 << 6))
+
+/*Range of resistance values 2ohms to 40 ohms*/
+#define MIN_RESISTANCE_SPKR_Q24 (2 * (1 << 24))
+#define MAX_RESISTANCE_SPKR_Q24 (40 * (1 << 24))
+
+/*Path where the calibration file will be stored*/
+#define CALIB_FILE "/data/misc/audio/audio.cal"
+
+/*Time between retries for calibartion or intial wait time
+  after boot up*/
+#define WAIT_TIME_SPKR_CALIB (60 * 1000 * 1000)
+
+#define MIN_SPKR_IDLE_SEC (60 * 30)
+
+/*Once calibration is started sleep for 1 sec to allow
+  the calibration to kick off*/
+#define SLEEP_AFTER_CALIB_START (3000)
+
+/*If calibration is in progress wait for 200 msec before querying
+  for status again*/
+#define WAIT_FOR_GET_CALIB_STATUS (200)
+#define GET_SPKR_PROT_CAL_TIMEOUT_MSEC (5000)
+
+/*Speaker states*/
+#define SPKR_NOT_CALIBRATED -1
+#define SPKR_CALIBRATED 1
+
+/*Speaker processing state*/
+#define SPKR_PROCESSING_IN_PROGRESS 1
+#define SPKR_PROCESSING_IN_IDLE 0
+
+/*Modes of Speaker Protection*/
+enum speaker_protection_mode {
+    SPKR_PROTECTION_DISABLED = -1,
+    SPKR_PROTECTION_MODE_PROCESSING = 0,
+    SPKR_PROTECTION_MODE_CALIBRATE = 1,
+};
+
+struct speaker_prot_session {
+    int spkr_prot_mode;
+    int spkr_processing_state;
+    int thermal_client_handle;
+    pthread_mutex_t mutex_spkr_prot;
+    pthread_t spkr_calibration_thread;
+    pthread_mutex_t spkr_prot_thermalsync_mutex;
+    pthread_cond_t spkr_prot_thermalsync;
+    int cancel_spkr_calib;
+    pthread_cond_t spkr_calib_cancel;
+    pthread_mutex_t spkr_calib_cancelack_mutex;
+    pthread_cond_t spkr_calibcancel_ack;
+    pthread_t speaker_prot_threadid;
+    void *thermal_handle;
+    void *adev_handle;
+    int spkr_prot_t0;
+    struct pcm *pcm_rx;
+    struct pcm *pcm_tx;
+    int (*thermal_client_register_callback)
+    (char *client_name, int (*callback)(int), void *data);
+    void (*thermal_client_unregister_callback)(int handle);
+    int (*thermal_client_request)(char *client_name, int req_data);
+    bool spkr_prot_enable;
+    bool spkr_in_use;
+   struct timespec spkr_last_time_used;
+};
+
+static struct pcm_config pcm_config_skr_prot = {
+    .channels = 4,
+    .rate = 48000,
+    .period_size = 256,
+    .period_count = 4,
+    .format = PCM_FORMAT_S16_LE,
+    .start_threshold = 0,
+    .stop_threshold = INT_MAX,
+    .avail_min = 0,
+};
+
+static struct speaker_prot_session handle;
+static int vi_feed_no_channels;
+
+static void spkr_prot_set_spkrstatus(bool enable)
+{
+    struct timespec ts;
+    if (enable)
+       handle.spkr_in_use = true;
+    else {
+       handle.spkr_in_use = false;
+       clock_gettime(CLOCK_BOOTTIME, &handle.spkr_last_time_used);
+   }
+}
+
+void audio_extn_spkr_prot_calib_cancel(void *adev)
+{
+    pthread_t threadid;
+    struct audio_usecase *uc_info;
+    int count = 0;
+    threadid = pthread_self();
+    ALOGV("%s: Entry", __func__);
+    if (pthread_equal(handle.speaker_prot_threadid, threadid) || !adev) {
+        ALOGV("%s: Calibration not in progress.. nothihg to cancel", __func__);
+        return;
+    }
+    uc_info = get_usecase_from_list(adev, USECASE_AUDIO_SPKR_CALIB_RX);
+    if (uc_info) {
+            pthread_mutex_lock(&handle.mutex_spkr_prot);
+            pthread_mutex_lock(&handle.spkr_calib_cancelack_mutex);
+            handle.cancel_spkr_calib = 1;
+            pthread_cond_signal(&handle.spkr_calib_cancel);
+            pthread_mutex_unlock(&handle.mutex_spkr_prot);
+            pthread_cond_wait(&handle.spkr_calibcancel_ack,
+            &handle.spkr_calib_cancelack_mutex);
+            pthread_mutex_unlock(&handle.spkr_calib_cancelack_mutex);
+    }
+    ALOGV("%s: Exit", __func__);
+}
+
+static bool is_speaker_in_use(unsigned long *sec)
+{
+    struct timespec temp;
+    if (!sec) {
+        ALOGE("%s: Invalid params", __func__);
+        return true;
+    }
+     if (handle.spkr_in_use) {
+        *sec = 0;
+         return true;
+     } else {
+         clock_gettime(CLOCK_BOOTTIME, &temp);
+         *sec = temp.tv_sec - handle.spkr_last_time_used.tv_sec;
+         return false;
+     }
+}
+
+
+static int get_spkr_prot_cal(int cal_fd,
+				struct audio_cal_info_msm_spk_prot_status *status)
+{
+    int ret = 0;
+    struct audio_cal_fb_spk_prot_status    cal_data;
+
+    if (cal_fd < 0) {
+        ALOGE("%s: Error: cal_fd = %d", __func__, cal_fd);
+        ret = -EINVAL;
+        goto done;
+    }
+
+    if (status == NULL) {
+        ALOGE("%s: Error: status NULL", __func__);
+        ret = -EINVAL;
+        goto done;
+    }
+
+    cal_data.hdr.data_size = sizeof(cal_data);
+    cal_data.hdr.version = VERSION_0_0;
+    cal_data.hdr.cal_type = AFE_FB_SPKR_PROT_CAL_TYPE;
+    cal_data.hdr.cal_type_size = sizeof(cal_data.cal_type);
+    cal_data.cal_type.cal_hdr.version = VERSION_0_0;
+    cal_data.cal_type.cal_hdr.buffer_number = 0;
+    cal_data.cal_type.cal_data.mem_handle = -1;
+
+    if (ioctl(cal_fd, AUDIO_GET_CALIBRATION, &cal_data)) {
+        ALOGE("%s: Error: AUDIO_GET_CALIBRATION failed!",
+            __func__);
+        ret = -ENODEV;
+        goto done;
+    }
+
+    status->r0[SP_V2_SPKR_1] = cal_data.cal_type.cal_info.r0[SP_V2_SPKR_1];
+    status->r0[SP_V2_SPKR_2] = cal_data.cal_type.cal_info.r0[SP_V2_SPKR_2];
+    status->status = cal_data.cal_type.cal_info.status;
+done:
+    return ret;
+}
+
+static int set_spkr_prot_cal(int cal_fd,
+				struct audio_cal_info_spk_prot_cfg *protCfg)
+{
+    int ret = 0;
+    struct audio_cal_fb_spk_prot_cfg    cal_data;
+    char value[PROPERTY_VALUE_MAX];
+
+    if (cal_fd < 0) {
+        ALOGE("%s: Error: cal_fd = %d", __func__, cal_fd);
+        ret = -EINVAL;
+        goto done;
+    }
+
+    if (protCfg == NULL) {
+        ALOGE("%s: Error: status NULL", __func__);
+        ret = -EINVAL;
+        goto done;
+    }
+
+    memset(&cal_data, 0, sizeof(cal_data));
+    cal_data.hdr.data_size = sizeof(cal_data);
+    cal_data.hdr.version = VERSION_0_0;
+    cal_data.hdr.cal_type = AFE_FB_SPKR_PROT_CAL_TYPE;
+    cal_data.hdr.cal_type_size = sizeof(cal_data.cal_type);
+    cal_data.cal_type.cal_hdr.version = VERSION_0_0;
+    cal_data.cal_type.cal_hdr.buffer_number = 0;
+    cal_data.cal_type.cal_info.r0[SP_V2_SPKR_1] = protCfg->r0[SP_V2_SPKR_1];
+    cal_data.cal_type.cal_info.r0[SP_V2_SPKR_2] = protCfg->r0[SP_V2_SPKR_2];
+    cal_data.cal_type.cal_info.t0[SP_V2_SPKR_1] = protCfg->t0[SP_V2_SPKR_1];
+    cal_data.cal_type.cal_info.t0[SP_V2_SPKR_2] = protCfg->t0[SP_V2_SPKR_2];
+    cal_data.cal_type.cal_info.mode = protCfg->mode;
+    property_get("persist.spkr.cal.duration", value, "0");
+    if (atoi(value) > 0) {
+        ALOGD("%s: quick calibration enabled", __func__);
+        cal_data.cal_type.cal_info.quick_calib_flag = 1;
+    } else {
+        ALOGD("%s: quick calibration disabled", __func__);
+        cal_data.cal_type.cal_info.quick_calib_flag = 0;
+    }
+
+    cal_data.cal_type.cal_data.mem_handle = -1;
+
+    if (ioctl(cal_fd, AUDIO_SET_CALIBRATION, &cal_data)) {
+        ALOGE("%s: Error: AUDIO_SET_CALIBRATION failed!",
+            __func__);
+        ret = -ENODEV;
+        goto done;
+    }
+done:
+    return ret;
+}
+
+static int vi_feed_get_channels(struct audio_device *adev)
+{
+    struct mixer_ctl *ctl;
+    const char *mixer_ctl_name = VI_FEED_CHANNEL;
+    int value;
+
+    ALOGV("%s: entry", __func__);
+    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);
+        goto error;
+    }
+    value = mixer_ctl_get_value(ctl, 0);
+    if (value < 0)
+        goto error;
+    else
+        return value+1;
+error:
+     return -EINVAL;
+}
+
+// must be called with adev->lock acquired
+static int spkr_calibrate(int t0)
+{
+    struct audio_device *adev = handle.adev_handle;
+    struct audio_cal_info_spk_prot_cfg protCfg;
+    struct audio_cal_info_msm_spk_prot_status status;
+    bool cleanup = false, disable_rx = false, disable_tx = false;
+    int acdb_fd = -1;
+    struct audio_usecase *uc_info_rx = NULL, *uc_info_tx = NULL;
+    int32_t pcm_dev_rx_id = -1, pcm_dev_tx_id = -1;
+    struct timespec ts;
+    int retry_duration;
+
+    if (!adev) {
+        ALOGE("%s: Invalid params", __func__);
+        return -EINVAL;
+    }
+    if (!list_empty(&adev->usecase_list)) {
+        ALOGD("%s: Usecase present retry speaker protection", __func__);
+        return -EAGAIN;
+    }
+    acdb_fd = open("/dev/msm_audio_cal",O_RDWR | O_NONBLOCK);
+    if (acdb_fd < 0) {
+        ALOGE("%s: spkr_prot_thread open msm_acdb failed", __func__);
+        return -ENODEV;
+    } else {
+        protCfg.mode = MSM_SPKR_PROT_CALIBRATION_IN_PROGRESS;
+        /* HAL for speaker protection gets only one Temperature */
+        protCfg.t0[SP_V2_SPKR_1] = t0;
+        protCfg.t0[SP_V2_SPKR_2] = t0;
+        if (set_spkr_prot_cal(acdb_fd, &protCfg)) {
+            ALOGE("%s: spkr_prot_thread set failed AUDIO_SET_SPEAKER_PROT",
+            __func__);
+            status.status = -ENODEV;
+            goto exit;
+        }
+    }
+    uc_info_rx = (struct audio_usecase *)calloc(1, sizeof(struct audio_usecase));
+    if (!uc_info_rx) {
+        return -ENOMEM;
+    }
+    uc_info_rx->id = USECASE_AUDIO_SPKR_CALIB_RX;
+    uc_info_rx->type = PCM_PLAYBACK;
+    uc_info_rx->in_snd_device = SND_DEVICE_NONE;
+    uc_info_rx->stream.out = adev->primary_output;
+    uc_info_rx->out_snd_device = SND_DEVICE_OUT_SPEAKER_PROTECTED;
+    disable_rx = true;
+    list_add_tail(&adev->usecase_list, &uc_info_rx->list);
+    enable_snd_device(adev, SND_DEVICE_OUT_SPEAKER_PROTECTED);
+    enable_audio_route(adev, uc_info_rx);
+
+    pcm_dev_rx_id = platform_get_pcm_device_id(uc_info_rx->id, PCM_PLAYBACK);
+    ALOGV("%s: pcm device id %d", __func__, pcm_dev_rx_id);
+    if (pcm_dev_rx_id < 0) {
+        ALOGE("%s: Invalid pcm device for usecase (%d)",
+              __func__, uc_info_rx->id);
+        status.status = -ENODEV;
+        goto exit;
+    }
+    handle.pcm_rx = handle.pcm_tx = NULL;
+    handle.pcm_rx = pcm_open(adev->snd_card,
+                             pcm_dev_rx_id,
+                             PCM_OUT, &pcm_config_skr_prot);
+    if (handle.pcm_rx && !pcm_is_ready(handle.pcm_rx)) {
+        ALOGE("%s: %s", __func__, pcm_get_error(handle.pcm_rx));
+        status.status = -EIO;
+        goto exit;
+    }
+    uc_info_tx = (struct audio_usecase *)
+    calloc(1, sizeof(struct audio_usecase));
+    if (!uc_info_tx) {
+        status.status = -ENOMEM;
+        goto exit;
+    }
+    uc_info_tx->id = USECASE_AUDIO_SPKR_CALIB_TX;
+    uc_info_tx->type = PCM_CAPTURE;
+    uc_info_tx->in_snd_device = SND_DEVICE_IN_CAPTURE_VI_FEEDBACK;
+    uc_info_tx->out_snd_device = SND_DEVICE_NONE;
+
+    disable_tx = true;
+    list_add_tail(&adev->usecase_list, &uc_info_tx->list);
+    enable_snd_device(adev, SND_DEVICE_IN_CAPTURE_VI_FEEDBACK);
+    enable_audio_route(adev, uc_info_tx);
+
+    pcm_dev_tx_id = platform_get_pcm_device_id(uc_info_tx->id, PCM_CAPTURE);
+    if (pcm_dev_tx_id < 0) {
+        ALOGE("%s: Invalid pcm device for usecase (%d)",
+              __func__, uc_info_tx->id);
+        status.status = -ENODEV;
+        goto exit;
+    }
+    handle.pcm_tx = pcm_open(adev->snd_card,
+                             pcm_dev_tx_id,
+                             PCM_IN, &pcm_config_skr_prot);
+    if (handle.pcm_tx && !pcm_is_ready(handle.pcm_tx)) {
+        ALOGE("%s: %s", __func__, pcm_get_error(handle.pcm_tx));
+        status.status = -EIO;
+        goto exit;
+    }
+    if (pcm_start(handle.pcm_rx) < 0) {
+        ALOGE("%s: pcm start for RX failed", __func__);
+        status.status = -EINVAL;
+        goto exit;
+    }
+    if (pcm_start(handle.pcm_tx) < 0) {
+        ALOGE("%s: pcm start for TX failed", __func__);
+        status.status = -EINVAL;
+        goto exit;
+    }
+    cleanup = true;
+    clock_gettime(CLOCK_REALTIME, &ts);
+    ts.tv_sec += (SLEEP_AFTER_CALIB_START/1000);
+    ts.tv_nsec = 0;
+    pthread_mutex_lock(&handle.mutex_spkr_prot);
+    pthread_mutex_unlock(&adev->lock);
+
+    (void)pthread_cond_timedwait(&handle.spkr_calib_cancel,
+                                 &handle.mutex_spkr_prot, &ts);
+    ALOGD("%s: Speaker calibration done", __func__);
+    pthread_mutex_lock(&handle.spkr_calib_cancelack_mutex);
+    if (handle.cancel_spkr_calib) {
+        status.status = -EAGAIN;
+        goto exit;
+    }
+
+    if (acdb_fd >= 0) {
+        status.status = -EINVAL;
+        retry_duration = 0;
+        while (!get_spkr_prot_cal(acdb_fd, &status) &&
+               retry_duration < GET_SPKR_PROT_CAL_TIMEOUT_MSEC) {
+            if (!status.status) {
+                ALOGD("%s: spkr_prot_thread calib Success R0 %d %d",
+                 __func__, status.r0[SP_V2_SPKR_1], status.r0[SP_V2_SPKR_2]);
+                FILE *fp;
+
+                vi_feed_no_channels = vi_feed_get_channels(adev);
+                ALOGD("%s: vi_feed_no_channels %d", __func__, vi_feed_no_channels);
+                if (vi_feed_no_channels < 0) {
+                    ALOGE("%s: no of channels negative !!", __func__);
+                    /* limit the number of channels to 2*/
+                    vi_feed_no_channels = 2;
+                }
+
+                fp = fopen(CALIB_FILE,"wb");
+                if (!fp) {
+                    ALOGE("%s: spkr_prot_thread File open failed %s",
+                    __func__, strerror(errno));
+                    status.status = -ENODEV;
+                } else {
+                    int i;
+                    /* HAL for speaker protection is always calibrating for stereo usecase*/
+                    for (i = 0; i < vi_feed_no_channels; i++) {
+                        fwrite(&status.r0[i], sizeof(status.r0[i]), 1, fp);
+                        fwrite(&protCfg.t0[i], sizeof(protCfg.t0[i]), 1, fp);
+                    }
+                    fclose(fp);
+                }
+                break;
+            } else if (status.status == -EAGAIN) {
+                  ALOGD("%s: spkr_prot_thread try again", __func__);
+                  usleep(WAIT_FOR_GET_CALIB_STATUS * 1000);
+                  retry_duration += WAIT_FOR_GET_CALIB_STATUS;
+            } else {
+                ALOGE("%s: spkr_prot_thread get failed status %d",
+                __func__, status.status);
+                break;
+            }
+        }
+    }
+
+exit:
+    if (handle.pcm_rx)
+        pcm_close(handle.pcm_rx);
+    handle.pcm_rx = NULL;
+
+    if (handle.pcm_tx)
+        pcm_close(handle.pcm_tx);
+    handle.pcm_tx = NULL;
+
+    /* Clear TX calibration to handset mic */
+    platform_send_audio_calibration(adev->platform, SND_DEVICE_IN_HANDSET_MIC);
+    if (!status.status) {
+        protCfg.mode = MSM_SPKR_PROT_CALIBRATED;
+        protCfg.r0[SP_V2_SPKR_1] = status.r0[SP_V2_SPKR_1];
+        protCfg.r0[SP_V2_SPKR_2] = status.r0[SP_V2_SPKR_2];
+        if (set_spkr_prot_cal(acdb_fd, &protCfg))
+            ALOGE("%s: spkr_prot_thread disable calib mode", __func__);
+        else
+            handle.spkr_prot_mode = MSM_SPKR_PROT_CALIBRATED;
+    } else {
+        protCfg.mode = MSM_SPKR_PROT_NOT_CALIBRATED;
+        handle.spkr_prot_mode = MSM_SPKR_PROT_NOT_CALIBRATED;
+        if (set_spkr_prot_cal(acdb_fd, &protCfg))
+            ALOGE("%s: spkr_prot_thread disable calib mode failed", __func__);
+    }
+    if (acdb_fd >= 0)
+        close(acdb_fd);
+
+    if (!handle.cancel_spkr_calib && cleanup) {
+        pthread_mutex_unlock(&handle.spkr_calib_cancelack_mutex);
+        pthread_cond_wait(&handle.spkr_calib_cancel, &handle.mutex_spkr_prot);
+        pthread_mutex_lock(&handle.spkr_calib_cancelack_mutex);
+    }
+    if (disable_rx) {
+        list_remove(&uc_info_rx->list);
+        disable_snd_device(adev, SND_DEVICE_OUT_SPEAKER_PROTECTED);
+        disable_audio_route(adev, uc_info_rx);
+    }
+    if (disable_tx) {
+        list_remove(&uc_info_tx->list);
+        disable_snd_device(adev, SND_DEVICE_IN_CAPTURE_VI_FEEDBACK);
+        disable_audio_route(adev, uc_info_tx);
+    }
+    if (uc_info_rx) free(uc_info_rx);
+    if (uc_info_tx) free(uc_info_tx);
+    if (cleanup) {
+        if (handle.cancel_spkr_calib)
+            pthread_cond_signal(&handle.spkr_calibcancel_ack);
+        handle.cancel_spkr_calib = 0;
+        pthread_mutex_unlock(&handle.spkr_calib_cancelack_mutex);
+        pthread_mutex_unlock(&handle.mutex_spkr_prot);
+        pthread_mutex_lock(&adev->lock);
+    }
+
+    return status.status;
+}
+
+static void* spkr_calibration_thread()
+{
+    unsigned long sec = 0;
+    int t0;
+    bool goahead = false;
+    struct audio_cal_info_spk_prot_cfg protCfg;
+    FILE *fp;
+    int acdb_fd;
+    struct audio_device *adev = handle.adev_handle;
+    unsigned long min_idle_time = MIN_SPKR_IDLE_SEC;
+    char value[PROPERTY_VALUE_MAX];
+
+    /* If the value of this persist.spkr.cal.duration is 0
+     * then it means it will take 30min to calibrate
+     * and if the value is greater than zero then it would take
+     * that much amount of time to calibrate.
+     */
+    property_get("persist.spkr.cal.duration", value, "0");
+    if (atoi(value) > 0)
+        min_idle_time = atoi(value);
+    handle.speaker_prot_threadid = pthread_self();
+    ALOGD("spkr_prot_thread enable prot Entry");
+    acdb_fd = open("/dev/msm_audio_cal",O_RDWR | O_NONBLOCK);
+    if (acdb_fd >= 0) {
+        /*Set processing mode with t0/r0*/
+        protCfg.mode = MSM_SPKR_PROT_NOT_CALIBRATED;
+        if (set_spkr_prot_cal(acdb_fd, &protCfg)) {
+            ALOGE("%s: spkr_prot_thread enable prot failed", __func__);
+            handle.spkr_prot_mode = MSM_SPKR_PROT_DISABLED;
+            close(acdb_fd);
+        } else
+            handle.spkr_prot_mode = MSM_SPKR_PROT_NOT_CALIBRATED;
+    } else {
+        handle.spkr_prot_mode = MSM_SPKR_PROT_DISABLED;
+        ALOGE("%s: Failed to open acdb node", __func__);
+    }
+    if (handle.spkr_prot_mode == MSM_SPKR_PROT_DISABLED) {
+        ALOGD("%s: Speaker protection disabled", __func__);
+        pthread_exit(0);
+        return NULL;
+    }
+
+    fp = fopen(CALIB_FILE,"rb");
+    if (fp) {
+        int i;
+        bool spkr_calibrated = true;
+        /* HAL for speaker protection is always calibrating for stereo usecase*/
+        vi_feed_no_channels = vi_feed_get_channels(adev);
+        ALOGD("%s: vi_feed_no_channels %d", __func__, vi_feed_no_channels);
+        if (vi_feed_no_channels < 0) {
+            ALOGE("%s: no of channels negative !!", __func__);
+            /* limit the number of channels to 2*/
+            vi_feed_no_channels = 2;
+        }
+        for (i = 0; i < vi_feed_no_channels; i++) {
+            fread(&protCfg.r0[i], sizeof(protCfg.r0[i]), 1, fp);
+            fread(&protCfg.t0[i], sizeof(protCfg.t0[i]), 1, fp);
+        }
+        ALOGD("%s: spkr_prot_thread r0 value %d %d",
+               __func__, protCfg.r0[SP_V2_SPKR_1], protCfg.r0[SP_V2_SPKR_2]);
+        ALOGD("%s: spkr_prot_thread t0 value %d %d",
+               __func__, protCfg.t0[SP_V2_SPKR_1], protCfg.t0[SP_V2_SPKR_2]);
+        fclose(fp);
+        /*Valid tempature range: -30C to 80C(in q6 format)
+          Valid Resistance range: 2 ohms to 40 ohms(in q24 format)*/
+        for (i = 0; i < vi_feed_no_channels; i++) {
+            if (!((protCfg.t0[i] > MIN_SPKR_TEMP_Q6) && (protCfg.t0[i] < MAX_SPKR_TEMP_Q6)
+                && (protCfg.r0[i] >= MIN_RESISTANCE_SPKR_Q24)
+                && (protCfg.r0[i] < MAX_RESISTANCE_SPKR_Q24))) {
+                spkr_calibrated = false;
+                break;
+            }
+        }
+        if (spkr_calibrated) {
+            ALOGD("%s: Spkr calibrated", __func__);
+            protCfg.mode = MSM_SPKR_PROT_CALIBRATED;
+            if (set_spkr_prot_cal(acdb_fd, &protCfg)) {
+                ALOGE("%s: enable prot failed", __func__);
+                handle.spkr_prot_mode = MSM_SPKR_PROT_DISABLED;
+            } else
+                handle.spkr_prot_mode = MSM_SPKR_PROT_CALIBRATED;
+            close(acdb_fd);
+            pthread_exit(0);
+            return NULL;
+        }
+        close(acdb_fd);
+    }
+
+    while (1) {
+        ALOGV("%s: start calibration", __func__);
+        if (!handle.thermal_client_request("spkr",1)) {
+            ALOGD("%s: wait for callback from thermal daemon", __func__);
+            pthread_mutex_lock(&handle.spkr_prot_thermalsync_mutex);
+            pthread_cond_wait(&handle.spkr_prot_thermalsync,
+            &handle.spkr_prot_thermalsync_mutex);
+            /*Convert temp into q6 format*/
+            t0 = (handle.spkr_prot_t0 * (1 << 6));
+            pthread_mutex_unlock(&handle.spkr_prot_thermalsync_mutex);
+            if (t0 < MIN_SPKR_TEMP_Q6 || t0 > MAX_SPKR_TEMP_Q6) {
+                ALOGE("%s: Calibration temparature error %d", __func__,
+                      handle.spkr_prot_t0);
+                continue;
+            }
+            ALOGD("%s: Request t0 success value %d", __func__,
+            handle.spkr_prot_t0);
+        } else {
+            ALOGE("%s: Request t0 failed", __func__);
+            /*Assume safe value for temparature*/
+            t0 = SAFE_SPKR_TEMP_Q6;
+        }
+        goahead = false;
+        pthread_mutex_lock(&adev->lock);
+        if (is_speaker_in_use(&sec)) {
+            ALOGD("%s: Speaker in use retry calibration", __func__);
+            pthread_mutex_unlock(&adev->lock);
+            continue;
+        } else {
+            ALOGD("%s: speaker idle %ld min time %ld", __func__, sec, min_idle_time);
+            if (sec < min_idle_time) {
+                ALOGD("%s: speaker idle is less retry", __func__);
+                pthread_mutex_unlock(&adev->lock);
+                continue;
+            }
+            goahead = true;
+        }
+        if (!list_empty(&adev->usecase_list)) {
+            ALOGD("%s: Usecase active re-try calibration", __func__);
+            goahead = false;
+            pthread_mutex_unlock(&adev->lock);
+        }
+        if (goahead) {
+                int status;
+                status = spkr_calibrate(t0);
+                pthread_mutex_unlock(&adev->lock);
+                if (status == -EAGAIN) {
+                    ALOGE("%s: failed to calibrate try again %s",
+                    __func__, strerror(status));
+                    continue;
+                } else {
+                    ALOGE("%s: calibrate status %s", __func__, strerror(status));
+                }
+                ALOGD("%s: spkr_prot_thread end calibration", __func__);
+                break;
+        }
+    }
+    if (handle.thermal_client_handle)
+        handle.thermal_client_unregister_callback(handle.thermal_client_handle);
+    handle.thermal_client_handle = 0;
+    if (handle.thermal_handle)
+        dlclose(handle.thermal_handle);
+    handle.thermal_handle = NULL;
+    pthread_exit(0);
+    return NULL;
+}
+
+static int thermal_client_callback(int temp)
+{
+    pthread_mutex_lock(&handle.spkr_prot_thermalsync_mutex);
+    ALOGD("%s: spkr_prot set t0 %d and signal", __func__, temp);
+    if (handle.spkr_prot_mode == MSM_SPKR_PROT_NOT_CALIBRATED)
+        handle.spkr_prot_t0 = temp;
+    pthread_cond_signal(&handle.spkr_prot_thermalsync);
+    pthread_mutex_unlock(&handle.spkr_prot_thermalsync_mutex);
+    return 0;
+}
+
+void audio_extn_spkr_prot_init(void *adev)
+{
+    char value[PROPERTY_VALUE_MAX];
+    ALOGD("%s: Initialize speaker protection module", __func__);
+    memset(&handle, 0, sizeof(handle));
+    if (!adev) {
+        ALOGE("%s: Invalid params", __func__);
+        return;
+    }
+    property_get("persist.speaker.prot.enable", value, "");
+    handle.spkr_prot_enable = false;
+    if (!strncmp("true", value, 4))
+       handle.spkr_prot_enable = true;
+    if (!handle.spkr_prot_enable) {
+        ALOGD("%s: Speaker protection disabled", __func__);
+        return;
+    }
+    handle.adev_handle = adev;
+    handle.spkr_prot_mode = MSM_SPKR_PROT_DISABLED;
+    handle.spkr_processing_state = SPKR_PROCESSING_IN_IDLE;
+    handle.spkr_prot_t0 = -1;
+    pthread_cond_init(&handle.spkr_prot_thermalsync, NULL);
+    pthread_cond_init(&handle.spkr_calib_cancel, NULL);
+    pthread_cond_init(&handle.spkr_calibcancel_ack, NULL);
+    pthread_mutex_init(&handle.mutex_spkr_prot, NULL);
+    pthread_mutex_init(&handle.spkr_calib_cancelack_mutex, NULL);
+    pthread_mutex_init(&handle.spkr_prot_thermalsync_mutex, NULL);
+    handle.thermal_handle = dlopen("/vendor/lib/libthermalclient.so",
+            RTLD_NOW);
+    if (!handle.thermal_handle) {
+        ALOGE("%s: DLOPEN for thermal client failed", __func__);
+    } else {
+        /*Query callback function symbol*/
+        handle.thermal_client_register_callback =
+       (int (*)(char *, int (*)(int),void *))
+        dlsym(handle.thermal_handle, "thermal_client_register_callback");
+        handle.thermal_client_unregister_callback =
+        (void (*)(int) )
+        dlsym(handle.thermal_handle, "thermal_client_unregister_callback");
+        if (!handle.thermal_client_register_callback ||
+            !handle.thermal_client_unregister_callback) {
+            ALOGE("%s: DLSYM thermal_client_register_callback failed", __func__);
+        } else {
+            /*Register callback function*/
+            handle.thermal_client_handle =
+            handle.thermal_client_register_callback("spkr", thermal_client_callback, NULL);
+            if (!handle.thermal_client_handle) {
+                ALOGE("%s: thermal_client_register_callback failed", __func__);
+            } else {
+                ALOGD("%s: spkr_prot thermal_client_register_callback success", __func__);
+                handle.thermal_client_request = (int (*)(char *, int))
+                dlsym(handle.thermal_handle, "thermal_client_request");
+            }
+        }
+    }
+    if (handle.thermal_client_request) {
+        ALOGD("%s: Create calibration thread", __func__);
+        (void)pthread_create(&handle.spkr_calibration_thread,
+        (const pthread_attr_t *) NULL, spkr_calibration_thread, &handle);
+    } else {
+        ALOGE("%s: thermal_client_request failed", __func__);
+        if (handle.thermal_client_handle &&
+            handle.thermal_client_unregister_callback)
+            handle.thermal_client_unregister_callback(handle.thermal_client_handle);
+        if (handle.thermal_handle)
+            dlclose(handle.thermal_handle);
+        handle.thermal_handle = NULL;
+        handle.spkr_prot_enable = false;
+    }
+
+    if (handle.spkr_prot_enable) {
+        char platform[PROPERTY_VALUE_MAX];
+        property_get("ro.board.platform", platform, "");
+        if (!strncmp("apq8084", platform, sizeof("apq8084"))) {
+            platform_set_snd_device_backend(SND_DEVICE_OUT_VOICE_SPEAKER,
+                                            "speaker-protected");
+        }
+    }
+}
+
+int audio_extn_spkr_prot_get_acdb_id(snd_device_t snd_device)
+{
+    int acdb_id;
+
+    switch(snd_device) {
+    case SND_DEVICE_OUT_SPEAKER:
+        acdb_id = platform_get_snd_device_acdb_id(SND_DEVICE_OUT_SPEAKER_PROTECTED);
+        break;
+    case SND_DEVICE_OUT_VOICE_SPEAKER:
+        acdb_id = platform_get_snd_device_acdb_id(SND_DEVICE_OUT_VOICE_SPEAKER_PROTECTED);
+        break;
+    default:
+        acdb_id = -EINVAL;
+        break;
+    }
+    return acdb_id;
+}
+
+int audio_extn_get_spkr_prot_snd_device(snd_device_t snd_device)
+{
+    if (!handle.spkr_prot_enable)
+        return snd_device;
+
+    switch(snd_device) {
+    case SND_DEVICE_OUT_SPEAKER:
+        return SND_DEVICE_OUT_SPEAKER_PROTECTED;
+    case SND_DEVICE_OUT_VOICE_SPEAKER:
+        return SND_DEVICE_OUT_VOICE_SPEAKER_PROTECTED;
+    default:
+        return snd_device;
+    }
+}
+
+int audio_extn_spkr_prot_start_processing(snd_device_t snd_device)
+{
+    struct audio_usecase *uc_info_tx;
+    struct audio_device *adev = handle.adev_handle;
+    int32_t pcm_dev_tx_id = -1, ret = 0;
+
+    ALOGV("%s: Entry", __func__);
+    if (!adev) {
+       ALOGE("%s: Invalid params", __func__);
+       return -EINVAL;
+    }
+    snd_device = audio_extn_get_spkr_prot_snd_device(snd_device);
+    spkr_prot_set_spkrstatus(true);
+    uc_info_tx = (struct audio_usecase *)calloc(1, sizeof(struct audio_usecase));
+    if (!uc_info_tx) {
+        return -ENOMEM;
+    }
+    ALOGV("%s: snd_device(%d: %s)", __func__, snd_device,
+           platform_get_snd_device_name(snd_device));
+    audio_route_apply_and_update_path(adev->audio_route,
+           platform_get_snd_device_name(snd_device));
+
+    pthread_mutex_lock(&handle.mutex_spkr_prot);
+    if (handle.spkr_processing_state == SPKR_PROCESSING_IN_IDLE) {
+        uc_info_tx->id = USECASE_AUDIO_SPKR_CALIB_TX;
+        uc_info_tx->type = PCM_CAPTURE;
+        uc_info_tx->in_snd_device = SND_DEVICE_IN_CAPTURE_VI_FEEDBACK;
+        uc_info_tx->out_snd_device = SND_DEVICE_NONE;
+        handle.pcm_tx = NULL;
+        list_add_tail(&adev->usecase_list, &uc_info_tx->list);
+        enable_snd_device(adev, SND_DEVICE_IN_CAPTURE_VI_FEEDBACK);
+        enable_audio_route(adev, uc_info_tx);
+
+        pcm_dev_tx_id = platform_get_pcm_device_id(uc_info_tx->id, PCM_CAPTURE);
+        if (pcm_dev_tx_id < 0) {
+            ALOGE("%s: Invalid pcm device for usecase (%d)",
+                  __func__, uc_info_tx->id);
+            ret = -ENODEV;
+            goto exit;
+        }
+        handle.pcm_tx = pcm_open(adev->snd_card,
+                                 pcm_dev_tx_id,
+                                 PCM_IN, &pcm_config_skr_prot);
+        if (handle.pcm_tx && !pcm_is_ready(handle.pcm_tx)) {
+            ALOGE("%s: %s", __func__, pcm_get_error(handle.pcm_tx));
+            ret = -EIO;
+            goto exit;
+        }
+        if (pcm_start(handle.pcm_tx) < 0) {
+            ALOGE("%s: pcm start for TX failed", __func__);
+            ret = -EINVAL;
+        }
+    }
+
+exit:
+    /* Clear VI feedback cal and replace with handset MIC  */
+    platform_send_audio_calibration(adev->platform, SND_DEVICE_IN_HANDSET_MIC);
+    if (ret) {
+        if (handle.pcm_tx)
+            pcm_close(handle.pcm_tx);
+        handle.pcm_tx = NULL;
+        list_remove(&uc_info_tx->list);
+        disable_snd_device(adev, SND_DEVICE_IN_CAPTURE_VI_FEEDBACK);
+        disable_audio_route(adev, uc_info_tx);
+        free(uc_info_tx);
+    } else
+        handle.spkr_processing_state = SPKR_PROCESSING_IN_PROGRESS;
+    pthread_mutex_unlock(&handle.mutex_spkr_prot);
+    ALOGV("%s: Exit", __func__);
+    return ret;
+}
+
+void audio_extn_spkr_prot_stop_processing(snd_device_t snd_device)
+{
+    struct audio_usecase *uc_info_tx;
+    struct audio_device *adev = handle.adev_handle;
+
+    ALOGV("%s: Entry", __func__);
+    snd_device = audio_extn_get_spkr_prot_snd_device(snd_device);
+    spkr_prot_set_spkrstatus(false);
+    pthread_mutex_lock(&handle.mutex_spkr_prot);
+    if (adev && handle.spkr_processing_state == SPKR_PROCESSING_IN_PROGRESS) {
+        uc_info_tx = get_usecase_from_list(adev, USECASE_AUDIO_SPKR_CALIB_TX);
+        if (handle.pcm_tx)
+            pcm_close(handle.pcm_tx);
+        handle.pcm_tx = NULL;
+        disable_snd_device(adev, SND_DEVICE_IN_CAPTURE_VI_FEEDBACK);
+        if (uc_info_tx) {
+            list_remove(&uc_info_tx->list);
+            disable_audio_route(adev, uc_info_tx);
+            free(uc_info_tx);
+        }
+    }
+    handle.spkr_processing_state = SPKR_PROCESSING_IN_IDLE;
+    pthread_mutex_unlock(&handle.mutex_spkr_prot);
+    if (adev)
+        audio_route_reset_and_update_path(adev->audio_route,
+                                      platform_get_snd_device_name(snd_device));
+    ALOGV("%s: Exit", __func__);
+}
+
+bool audio_extn_spkr_prot_is_enabled()
+{
+    return handle.spkr_prot_enable;
+}
+#endif /*SPKR_PROT_ENABLED*/
diff --git a/hal/audio_hw.c b/hal/audio_hw.c
index 55f5c19..20763ac 100644
--- a/hal/audio_hw.c
+++ b/hal/audio_hw.c
@@ -158,6 +158,9 @@
     [USECASE_QCHAT_CALL] = "qchat-call",
     [USECASE_VOWLAN_CALL] = "vowlan-call",
 
+    [USECASE_AUDIO_SPKR_CALIB_RX] = "spkr-rx-calib",
+    [USECASE_AUDIO_SPKR_CALIB_TX] = "spkr-vi-record",
+
     [USECASE_AUDIO_PLAYBACK_AFE_PROXY] = "afe-proxy-playback",
     [USECASE_AUDIO_RECORD_AFE_PROXY] = "afe-proxy-record",
 };
@@ -279,6 +282,9 @@
     audio_extn_sound_trigger_update_device_status(snd_device,
                                     ST_EVENT_SND_DEVICE_BUSY);
 
+    if (audio_extn_spkr_prot_is_enabled())
+         audio_extn_spkr_prot_calib_cancel(adev);
+
     if (platform_send_audio_calibration(adev->platform, snd_device) < 0) {
         adev->snd_dev_ref_cnt[snd_device]--;
         audio_extn_sound_trigger_update_device_status(snd_device,
@@ -286,9 +292,22 @@
         return -EINVAL;
     }
 
-    const char * dev_path = platform_get_snd_device_name(snd_device);
-    ALOGD("%s: snd_device(%d: %s)", __func__, snd_device, dev_path);
-    audio_route_apply_and_update_path(adev->audio_route, dev_path);
+    if ((snd_device == SND_DEVICE_OUT_SPEAKER ||
+        snd_device == SND_DEVICE_OUT_VOICE_SPEAKER) &&
+        audio_extn_spkr_prot_is_enabled()) {
+        if (audio_extn_spkr_prot_get_acdb_id(snd_device) < 0) {
+            adev->snd_dev_ref_cnt[snd_device]--;
+            return -EINVAL;
+        }
+        if (audio_extn_spkr_prot_start_processing(snd_device)) {
+            ALOGE("%s: spkr_start_processing failed", __func__);
+            return -EINVAL;
+        }
+    } else {
+        const char * dev_path = platform_get_snd_device_name(snd_device);
+        ALOGD("%s: snd_device(%d: %s)", __func__, snd_device, dev_path);
+        audio_route_apply_and_update_path(adev->audio_route, dev_path);
+    }
 
     return 0;
 }
@@ -309,10 +328,15 @@
     if (adev->snd_dev_ref_cnt[snd_device] == 0) {
         const char * dev_path = platform_get_snd_device_name(snd_device);
         ALOGD("%s: snd_device(%d: %s)", __func__, snd_device, dev_path);
-        audio_route_reset_and_update_path(adev->audio_route, dev_path);
+        if ((snd_device == SND_DEVICE_OUT_SPEAKER ||
+            snd_device == SND_DEVICE_OUT_VOICE_SPEAKER) &&
+            audio_extn_spkr_prot_is_enabled()) {
+            audio_extn_spkr_prot_stop_processing(snd_device);
+        } else {
+            audio_route_reset_and_update_path(adev->audio_route, dev_path);
+        }
         audio_extn_sound_trigger_update_device_status(snd_device,
                                         ST_EVENT_SND_DEVICE_FREE);
-
     }
     return 0;
 }
diff --git a/hal/audio_hw.h b/hal/audio_hw.h
index cb043ed..d531744 100644
--- a/hal/audio_hw.h
+++ b/hal/audio_hw.h
@@ -77,6 +77,9 @@
     USECASE_INCALL_REC_DOWNLINK,
     USECASE_INCALL_REC_UPLINK_AND_DOWNLINK,
 
+    USECASE_AUDIO_SPKR_CALIB_RX,
+    USECASE_AUDIO_SPKR_CALIB_TX,
+
     USECASE_AUDIO_PLAYBACK_AFE_PROXY,
     USECASE_AUDIO_RECORD_AFE_PROXY,
 
diff --git a/hal/msm8960/platform.c b/hal/msm8960/platform.c
index 454221e..e2333ca 100644
--- a/hal/msm8960/platform.c
+++ b/hal/msm8960/platform.c
@@ -417,6 +417,12 @@
     return -ENODEV;
 }
 
+int platform_get_snd_device_acdb_id(snd_device_t snd_device __unused)
+{
+    ALOGE("%s: Not implemented", __func__);
+    return -ENOSYS;
+}
+
 int platform_send_audio_calibration(void *platform, snd_device_t snd_device)
 {
     struct platform_data *my_data = (struct platform_data *)platform;
diff --git a/hal/msm8974/platform.c b/hal/msm8974/platform.c
index 820b4b7..e0ea8ed 100644
--- a/hal/msm8974/platform.c
+++ b/hal/msm8974/platform.c
@@ -26,6 +26,7 @@
 #include <audio_hw.h>
 #include <platform_api.h>
 #include "platform.h"
+#include "audio_extn.h"
 
 #define MIXER_XML_PATH "/system/etc/mixer_paths.xml"
 #define LIB_ACDB_LOADER "libacdbloader.so"
@@ -119,6 +120,9 @@
                                                 AUDIO_RECORD_PCM_DEVICE},
     [USECASE_AUDIO_HFP_SCO] = {HFP_PCM_RX, HFP_SCO_RX},
 
+    [USECASE_AUDIO_SPKR_CALIB_RX] = {SPKR_PROT_CALIB_RX_PCM_DEVICE, -1},
+    [USECASE_AUDIO_SPKR_CALIB_TX] = {-1, SPKR_PROT_CALIB_TX_PCM_DEVICE},
+
     [USECASE_AUDIO_PLAYBACK_AFE_PROXY] = {AFE_PROXY_PLAYBACK_PCM_DEVICE,
                                           AFE_PROXY_RECORD_PCM_DEVICE},
     [USECASE_AUDIO_RECORD_AFE_PROXY] = {AFE_PROXY_PLAYBACK_PCM_DEVICE,
@@ -152,6 +156,8 @@
     [SND_DEVICE_OUT_VOICE_TTY_VCO_HEADPHONES] = "voice-tty-vco-headphones",
     [SND_DEVICE_OUT_VOICE_TTY_HCO_HANDSET] = "voice-tty-hco-handset",
     [SND_DEVICE_OUT_VOICE_TX] = "voice-tx",
+    [SND_DEVICE_OUT_SPEAKER_PROTECTED] = "speaker-protected",
+    [SND_DEVICE_OUT_VOICE_SPEAKER_PROTECTED] = "voice-speaker-protected",
 
     /* Capture sound devices */
     [SND_DEVICE_IN_HANDSET_MIC] = "handset-mic",
@@ -198,6 +204,8 @@
     [SND_DEVICE_IN_VOICE_REC_DMIC_FLUENCE] = "voice-rec-dmic-ef-fluence",
 
     [SND_DEVICE_IN_VOICE_RX] = "voice-rx",
+
+    [SND_DEVICE_IN_CAPTURE_VI_FEEDBACK] = "vi-feedback",
 };
 
 /* ACDB IDs (audio DSP path configuration IDs) for each sound device */
@@ -225,6 +233,8 @@
     [SND_DEVICE_OUT_VOICE_TTY_VCO_HEADPHONES] = 17,
     [SND_DEVICE_OUT_VOICE_TTY_HCO_HANDSET] = 37,
     [SND_DEVICE_OUT_VOICE_TX] = 45,
+    [SND_DEVICE_OUT_SPEAKER_PROTECTED] = 124,
+    [SND_DEVICE_OUT_VOICE_SPEAKER_PROTECTED] = 101,
 
     [SND_DEVICE_IN_HANDSET_MIC] = 4,
     [SND_DEVICE_IN_HANDSET_MIC_AEC] = 106,
@@ -270,6 +280,8 @@
     [SND_DEVICE_IN_VOICE_REC_DMIC_FLUENCE] = 43,
 
     [SND_DEVICE_IN_VOICE_RX] = 44,
+
+    [SND_DEVICE_IN_CAPTURE_VI_FEEDBACK] = 102,
 };
 
 struct name_to_index {
@@ -305,6 +317,8 @@
     {TO_NAME_INDEX(SND_DEVICE_OUT_VOICE_TTY_HCO_HANDSET)},
 
     /* in */
+    {TO_NAME_INDEX(SND_DEVICE_OUT_SPEAKER_PROTECTED)},
+    {TO_NAME_INDEX(SND_DEVICE_OUT_VOICE_SPEAKER_PROTECTED)},
     {TO_NAME_INDEX(SND_DEVICE_IN_HANDSET_MIC)},
     {TO_NAME_INDEX(SND_DEVICE_IN_HANDSET_MIC_AEC)},
     {TO_NAME_INDEX(SND_DEVICE_IN_HANDSET_MIC_NS)},
@@ -346,6 +360,8 @@
     {TO_NAME_INDEX(SND_DEVICE_IN_VOICE_REC_MIC_NS)},
     {TO_NAME_INDEX(SND_DEVICE_IN_VOICE_REC_DMIC_STEREO)},
     {TO_NAME_INDEX(SND_DEVICE_IN_VOICE_REC_DMIC_FLUENCE)},
+
+    {TO_NAME_INDEX(SND_DEVICE_IN_CAPTURE_VI_FEEDBACK)},
 };
 
 static char * backend_table[SND_DEVICE_MAX] = {0};
@@ -796,7 +812,7 @@
     set_platform_defaults(my_data);
 
     /* Initialize platform specific ids and/or backends*/
-    platform_info_init();
+    audio_extn_spkr_prot_init(adev);
 
     /* load csd client */
     platform_csd_init(my_data);
@@ -904,6 +920,15 @@
     return ret;
 }
 
+int platform_get_snd_device_acdb_id(snd_device_t snd_device)
+{
+    if ((snd_device < SND_DEVICE_MIN) || (snd_device >= SND_DEVICE_MAX)) {
+        ALOGE("%s: Invalid snd_device = %d", __func__, snd_device);
+        return -EINVAL;
+    }
+    return acdb_device_table[snd_device];
+}
+
 int platform_send_audio_calibration(void *platform, snd_device_t snd_device)
 {
     struct platform_data *my_data = (struct platform_data *)platform;
@@ -956,7 +981,11 @@
     if (my_data->csd == NULL)
         return ret;
 
-    acdb_rx_id = acdb_device_table[out_snd_device];
+    if (out_snd_device == SND_DEVICE_OUT_VOICE_SPEAKER &&
+        audio_extn_spkr_prot_is_enabled())
+        acdb_rx_id = acdb_device_table[SND_DEVICE_OUT_SPEAKER_PROTECTED];
+    else
+    	acdb_rx_id = acdb_device_table[out_snd_device];
 
     acdb_tx_id = acdb_device_table[in_snd_device];
 
@@ -984,6 +1013,10 @@
     if (my_data->acdb_send_voice_cal == NULL) {
         ALOGE("%s: dlsym error for acdb_send_voice_call", __func__);
     } else {
+        if (out_snd_device == SND_DEVICE_OUT_VOICE_SPEAKER &&
+            audio_extn_spkr_prot_is_enabled())
+            out_snd_device = SND_DEVICE_OUT_VOICE_SPEAKER_PROTECTED;
+
         acdb_rx_id = acdb_device_table[out_snd_device];
         acdb_tx_id = acdb_device_table[in_snd_device];
 
@@ -1008,7 +1041,11 @@
     if (my_data->csd == NULL)
         return ret;
 
-    acdb_rx_id = acdb_device_table[out_snd_device];
+    if (out_snd_device == SND_DEVICE_OUT_VOICE_SPEAKER &&
+        audio_extn_spkr_prot_is_enabled())
+        acdb_rx_id = acdb_device_table[SND_DEVICE_OUT_VOICE_SPEAKER_PROTECTED];
+    else
+        acdb_rx_id = acdb_device_table[out_snd_device];
 
     acdb_tx_id = acdb_device_table[in_snd_device];
 
diff --git a/hal/msm8974/platform.h b/hal/msm8974/platform.h
index d5f45f4..cf7d20d 100644
--- a/hal/msm8974/platform.h
+++ b/hal/msm8974/platform.h
@@ -66,6 +66,8 @@
     SND_DEVICE_OUT_VOICE_TTY_HCO_HANDSET,
     SND_DEVICE_OUT_VOICE_HAC_HANDSET,
     SND_DEVICE_OUT_VOICE_TX,
+    SND_DEVICE_OUT_SPEAKER_PROTECTED,
+    SND_DEVICE_OUT_VOICE_SPEAKER_PROTECTED,
     SND_DEVICE_OUT_END,
 
     /*
@@ -119,6 +121,8 @@
 
     SND_DEVICE_IN_VOICE_RX,
 
+    SND_DEVICE_IN_CAPTURE_VI_FEEDBACK,
+
     SND_DEVICE_IN_END,
 
     SND_DEVICE_MAX = SND_DEVICE_IN_END,
@@ -178,6 +182,8 @@
 #define DEEP_BUFFER_PCM_DEVICE 0
 #define AUDIO_RECORD_PCM_DEVICE 0
 #define MULTIMEDIA2_PCM_DEVICE 1
+#define SPKR_PROT_CALIB_RX_PCM_DEVICE 5
+#define SPKR_PROT_CALIB_TX_PCM_DEVICE 25
 #define PLAYBACK_OFFLOAD_DEVICE 9
 #define LOWLATENCY_PCM_DEVICE 15
 #define VOICE_VSID  0x10C01000
diff --git a/hal/platform_api.h b/hal/platform_api.h
index 701399d..2ab4419 100644
--- a/hal/platform_api.h
+++ b/hal/platform_api.h
@@ -25,6 +25,7 @@
 int platform_get_pcm_device_id(audio_usecase_t usecase, int device_type);
 int platform_get_snd_device_index(char *snd_device_index_name);
 int platform_set_snd_device_acdb_id(snd_device_t snd_device, unsigned int acdb_id);
+int platform_get_snd_device_acdb_id(snd_device_t snd_device);
 int platform_send_audio_calibration(void *platform, snd_device_t snd_device);
 int platform_switch_voice_call_device_pre(void *platform);
 int platform_switch_voice_call_enable_device_config(void *platform,