/* * Copyright (c) 2013 - 2014, 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_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 * 1000) /*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 (*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_MONOTONIC, &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) { ALOGE("%s: Invalid params", __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_MONOTONIC, &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; } 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; bool acquire_device = false; 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); acquire_device = true; (void)pthread_cond_timedwait(&handle.spkr_calib_cancel, &handle.mutex_spkr_prot, &ts); ALOGD("%s: Speaker calibration done", __func__); cleanup = true; 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; while (!get_spkr_prot_cal(acdb_fd, &status)) { /*sleep for 200 ms to check for status check*/ 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); } 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, platform_get_default_app_type(adev->platform), 8000); 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); } } if (acquire_device) 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.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.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.client_register_callback("spkr", thermal_client_callback, NULL); if (!handle.thermal_client_handle) { ALOGE("%s: client_register_callback failed", __func__); } else { ALOGD("%s: spkr_prot 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__); /* cancel speaker calibration */ 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, platform_get_default_app_type(adev->platform), 8000); 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*/