| /* |
| * Copyright (c) 2013 - 2019, 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 <log/log.h> |
| #include <fcntl.h> |
| #include <dirent.h> |
| #include <pthread.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 <unistd.h> |
| #include <cutils/properties.h> |
| #include "audio_extn.h" |
| #include <linux/msm_audio_calibration.h> |
| #include <linux/msm_audio.h> |
| |
| #ifdef DYNAMIC_LOG_ENABLED |
| #include <log_xml_parser.h> |
| #define LOG_MASK HAL_MOD_FILE_SPKR_PROT |
| #include <log_utils.h> |
| #endif |
| |
| #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" |
| #define WSA8815_SPK1_NAME "wsatz.13" |
| #define WSA8815_SPK2_NAME "wsatz.14" |
| #define WCD_LEFT_BOOST_MAX_STATE "SPKR Left Boost Max State" |
| #define WCD_RIGHT_BOOST_MAX_STATE "SPKR Right Boost Max State" |
| #define WSA_LEFT_BOOST_LEVEL "SpkrLeft Boost Level" |
| #define WSA_RIGHT_BOOST_LEVEL "SpkrRight Boost Level" |
| /* Min and max resistance value in lookup table. */ |
| #define MIN_RESISTANCE_LOOKUP (3.2) |
| #define MAX_RESISTANCE_LOOKUP (8) |
| #define SPKR_PROT_LOOKUP_TABLE_ROWS (49) |
| /* default limiter threshold is 0dB(0x7FFFFFF in natural value) */ |
| #define DEFAULT_LIMITER_TH (0x07FFFFFF) |
| #define AFE_API_VERSION_SUPPORT_SPV3 (0x2) |
| enum wcd_boost_max_state { |
| BOOST_NO_MAX_STATE, |
| BOOST_MAX_STATE_1, |
| BOOST_MAX_STATE_2, |
| }; |
| |
| enum sp_version { |
| SP_V2 = 0x1, |
| SP_V3 = AFE_API_VERSION_SUPPORT_SPV3, |
| }; |
| /*Set safe temp value to 40C*/ |
| #define SAFE_SPKR_TEMP 40 |
| #define SAFE_SPKR_TEMP_Q6 (SAFE_SPKR_TEMP * (1 << 6)) |
| |
| /*Bongo Spkr temp range*/ |
| #define TZ_TEMP_MIN_THRESHOLD (5) |
| #define TZ_TEMP_MAX_THRESHOLD (45) |
| |
| /*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*/ |
| #ifdef LINUX_ENABLED |
| #define CALIB_FILE "/data/audio/audio.cal" |
| #else |
| #define CALIB_FILE "/data/vendor/misc/audio/audio.cal" |
| #endif |
| |
| /*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) |
| #define WAKEUP_MIN_IDLE_CHECK 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 |
| |
| /* In wsa analog mode vi feedback DAI supports at max 2 channels*/ |
| #define WSA_ANALOG_MODE_CHANNELS 2 |
| |
| #define MAX_PATH (256) |
| #define MAX_STR_SIZE (1024) |
| #define THERMAL_SYSFS "/sys/devices/virtual/thermal" |
| #define TZ_TYPE "/sys/devices/virtual/thermal/thermal_zone%d/type" |
| #define TZ_WSA "/sys/devices/virtual/thermal/thermal_zone%d/temp" |
| |
| #define AUDIO_PARAMETER_KEY_SPKR_TZ_1 "spkr_1_tz_name" |
| #define AUDIO_PARAMETER_KEY_SPKR_TZ_2 "spkr_2_tz_name" |
| |
| #define AUDIO_PARAMETER_KEY_FBSP_TRIGGER_SPKR_CAL "trigger_spkr_cal" |
| #define AUDIO_PARAMETER_KEY_FBSP_GET_SPKR_CAL "get_spkr_cal" |
| #define AUDIO_PARAMETER_KEY_FBSP_CFG_WAIT_TIME "fbsp_cfg_wait_time" |
| #define AUDIO_PARAMETER_KEY_FBSP_CFG_FTM_TIME "fbsp_cfg_ftm_time" |
| #define AUDIO_PARAMETER_KEY_FBSP_GET_FTM_PARAM "get_ftm_param" |
| |
| /*Modes of Speaker Protection*/ |
| enum speaker_protection_mode { |
| SPKR_PROTECTION_DISABLED = -1, |
| SPKR_PROTECTION_MODE_PROCESSING = 0, |
| SPKR_PROTECTION_MODE_CALIBRATE = 1, |
| }; |
| |
| struct spkr_prot_r0t0 { |
| int r0[SP_V2_NUM_MAX_SPKRS]; |
| int t0[SP_V2_NUM_MAX_SPKRS]; |
| }; |
| |
| 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; |
| struct spkr_prot_r0t0 sp_r0t0_cal; |
| bool wsa_found; |
| int spkr_1_tzn; |
| int spkr_2_tzn; |
| bool trigger_cal; |
| pthread_mutex_t cal_wait_cond_mutex; |
| pthread_cond_t cal_wait_condition; |
| bool init_check; |
| volatile bool thread_exit; |
| unsigned int sp_version; |
| int limiter_th[SP_V2_NUM_MAX_SPKRS]; |
| }; |
| |
| 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, |
| }; |
| |
| struct spkr_tz_names { |
| char *spkr_1_name; |
| char *spkr_2_name; |
| }; |
| |
| struct spkr_prot_boost { |
| /* bit7-4: first stage; bit 3-0: second stage */ |
| int boost_value; |
| int max_state; |
| }; |
| |
| #define SPKR_PROT_BOOST_VALUE_STATE(value, state) \ |
| { .boost_value = (value), .max_state = (state) } |
| |
| static struct spkr_prot_boost spkr_prot_boost_lookup_table[SPKR_PROT_LOOKUP_TABLE_ROWS] = { |
| SPKR_PROT_BOOST_VALUE_STATE(0xc7, BOOST_MAX_STATE_1), |
| SPKR_PROT_BOOST_VALUE_STATE(0xd7, BOOST_MAX_STATE_1), |
| SPKR_PROT_BOOST_VALUE_STATE(0xd7, BOOST_MAX_STATE_1), |
| SPKR_PROT_BOOST_VALUE_STATE(0xe7, BOOST_MAX_STATE_1), |
| SPKR_PROT_BOOST_VALUE_STATE(0xe7, BOOST_MAX_STATE_1), |
| SPKR_PROT_BOOST_VALUE_STATE(0xf7, BOOST_MAX_STATE_1), |
| SPKR_PROT_BOOST_VALUE_STATE(0x70, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x70, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x71, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x71, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x72, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x72, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x73, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x73, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x74, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x75, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x75, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x76, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x76, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x77, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x77, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x78, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x78, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x79, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x79, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x7a, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x7a, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x7a, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x7b, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x7b, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x7c, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x7c, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x7d, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x7d, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x7e, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x7e, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x7f, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x7f, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x7f, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x7f, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x7f, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x7f, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x7f, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x7f, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x7f, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x7f, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x7f, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x7f, BOOST_MAX_STATE_2), |
| SPKR_PROT_BOOST_VALUE_STATE(0x7f, BOOST_MAX_STATE_2), |
| }; |
| |
| /* 3.2 ohm in q24 format: (3.2 * (1 << 24)) */ |
| #define MIN_LOOKUP_RESISTANCE_SPKR_Q24 (53687091) |
| /* 8 ohm in q24 format: (8 * (1 << 24)) */ |
| #define MAX_LOOKUP_RESISTANCE_SPKR_Q24 (134217728) |
| /* 0.1 ohm in q24 format: (0.1 * (1 <<24)) */ |
| #define LOOKUP_RESISTANCE_GAP_SPKR_Q24 (1677722) |
| |
| /* 3.2ohm : 0.1ohm : 8ohm lookup table */ |
| static int spv3_limiter_th_q27_table[SPKR_PROT_LOOKUP_TABLE_ROWS] = { |
| 85469248, 86758070, 88066327, 89394311, 90637910, 91898809, |
| 93070036, 94364769, 95567425, 96674043, 97906130, 99039829, |
| 100186656, 101346763, 102402340, 103588104, 104667026, 105757185, |
| 106858699, 107847451, 108970736, 109979029, 110996653, 112023692, |
| 113060235, 113975074, 115029672, 115960448, 117033416, 117980405, |
| 118935056, 119897432, 120867596, 121705410, 122690202, 123682964, |
| 124540293, 125403565, 126418282, 127294571, 128176935, 129065415, |
| 129960054, 130860894, 131616362, 132528683, 133447328, 134217728, |
| 134217728 |
| }; |
| static struct speaker_prot_session handle; |
| static int vi_feed_no_channels; |
| static struct spkr_tz_names tz_names; |
| |
| /*=========================================================================== |
| FUNCTION get_tzn |
| |
| Utility function to match a sensor name with thermal zone id. |
| |
| ARGUMENTS |
| sensor_name - name of sensor to match |
| |
| RETURN VALUE |
| Thermal zone id on success, |
| -1 on failure. |
| ===========================================================================*/ |
| int get_tzn(const char *sensor_name) |
| { |
| DIR *tdir = NULL; |
| struct dirent *tdirent = NULL; |
| int found = -1; |
| int tzn = 0; |
| char name[MAX_PATH] = {0}; |
| char cwd[MAX_PATH] = {0}; |
| |
| if (!sensor_name || (strlen(sensor_name) == 0)) |
| return found; |
| |
| if (!getcwd(cwd, sizeof(cwd))) |
| return found; |
| |
| chdir(THERMAL_SYSFS); /* Change dir to read the entries. Doesnt work |
| otherwise */ |
| tdir = opendir(THERMAL_SYSFS); |
| if (!tdir) { |
| ALOGE("Unable to open %s\n", THERMAL_SYSFS); |
| return found; |
| } |
| |
| while ((tdirent = readdir(tdir))) { |
| char buf[50]; |
| struct dirent *tzdirent; |
| DIR *tzdir = NULL; |
| |
| tzdir = opendir(tdirent->d_name); |
| if (!tzdir) |
| continue; |
| while ((tzdirent = readdir(tzdir))) { |
| if (strcmp(tzdirent->d_name, "type")) |
| continue; |
| snprintf(name, MAX_PATH, TZ_TYPE, tzn); |
| ALOGV("Opening %s\n", name); |
| read_line_from_file(name, buf, sizeof(buf)); |
| if (strlen(buf) > 0) |
| buf[strlen(buf) - 1] = '\0'; |
| if (!strcmp(buf, sensor_name)) { |
| ALOGD(" spkr tz name found, %s\n", name); |
| found = 1; |
| break; |
| } |
| tzn++; |
| } |
| closedir(tzdir); |
| if (found == 1) |
| break; |
| } |
| closedir(tdir); |
| chdir(cwd); /* Restore current working dir */ |
| if (found == 1) { |
| found = tzn; |
| ALOGE("Sensor %s found at tz: %d\n", sensor_name, tzn); |
| } |
| return found; |
| } |
| |
| static void spkr_prot_set_spkrstatus(bool enable) |
| { |
| 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; |
| 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; |
| handle.trigger_cal = false; |
| 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]; |
| static int cal_done = 0; |
| |
| 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; |
| #ifdef MSM_SPKR_PROT_SPV3 |
| cal_data.cal_type.cal_info.sp_version = protCfg->sp_version; |
| cal_data.cal_type.cal_info.limiter_th[SP_V2_SPKR_1] = protCfg->limiter_th[SP_V2_SPKR_1]; |
| cal_data.cal_type.cal_info.limiter_th[SP_V2_SPKR_2] = protCfg->limiter_th[SP_V2_SPKR_2]; |
| #endif |
| property_get("persist.vendor.audio.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 { |
| 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; |
| } |
| if (protCfg->mode == MSM_SPKR_PROT_CALIBRATED && !cal_done) { |
| handle.sp_r0t0_cal.r0[SP_V2_SPKR_1] = protCfg->r0[SP_V2_SPKR_1]; |
| handle.sp_r0t0_cal.r0[SP_V2_SPKR_2] = protCfg->r0[SP_V2_SPKR_2]; |
| handle.sp_r0t0_cal.t0[SP_V2_SPKR_1] = protCfg->t0[SP_V2_SPKR_1]; |
| handle.sp_r0t0_cal.t0[SP_V2_SPKR_2] = protCfg->t0[SP_V2_SPKR_2]; |
| cal_done = 1; |
| } |
| 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; |
| } |
| |
| void destroy_thread_params() |
| { |
| pthread_mutex_destroy(&handle.mutex_spkr_prot); |
| pthread_mutex_destroy(&handle.spkr_calib_cancelack_mutex); |
| pthread_mutex_destroy(&handle.cal_wait_cond_mutex); |
| pthread_cond_destroy(&handle.cal_wait_condition); |
| pthread_cond_destroy(&handle.spkr_calib_cancel); |
| pthread_cond_destroy(&handle.spkr_calibcancel_ack); |
| if(!handle.wsa_found) { |
| pthread_mutex_destroy(&handle.spkr_prot_thermalsync_mutex); |
| pthread_cond_destroy(&handle.spkr_prot_thermalsync); |
| } |
| } |
| static void audio_extn_check_wsa(struct audio_device *adev, |
| unsigned int num_of_spkrs, bool *wsa_is_8815) |
| { |
| unsigned int i = 0; |
| if (!handle.wsa_found || |
| platform_spkr_prot_is_wsa_analog_mode(adev)){ |
| for (i = 0; i < num_of_spkrs; i++) |
| wsa_is_8815[i] = false; |
| |
| return; |
| } |
| |
| if (handle.spkr_1_tzn >= 0 && |
| !strncmp(WSA8815_SPK1_NAME, tz_names.spkr_1_name, sizeof(WSA8815_SPK1_NAME))) |
| wsa_is_8815[SP_V2_SPKR_1] = true; |
| |
| if (handle.spkr_2_tzn >= 0 && |
| !strncmp(WSA8815_SPK2_NAME, tz_names.spkr_2_name, sizeof(WSA8815_SPK2_NAME))) |
| wsa_is_8815[SP_V2_SPKR_2] = true; |
| } |
| |
| int audio_extn_set_wcd_boost_max_state(struct audio_device *adev, |
| int boost_max_state, int wsa_num) |
| { |
| struct mixer_ctl *ctl = NULL; |
| const char *mixer_ctl_name[] = { |
| WCD_LEFT_BOOST_MAX_STATE, |
| WCD_RIGHT_BOOST_MAX_STATE |
| }; |
| int status = 0; |
| |
| ctl = mixer_get_ctl_by_name(adev->mixer, mixer_ctl_name[wsa_num]); |
| if (!ctl) { |
| ALOGE("%s: Could not get ctl for mixer cmd - %s", |
| __func__, mixer_ctl_name[wsa_num]); |
| return -EINVAL; |
| } |
| |
| status = mixer_ctl_set_value(ctl, 0, boost_max_state); |
| if (status < 0) { |
| ALOGE("%s: failed to set WCD boost state.\n", __func__); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| int audio_extn_set_wsa_boost_level(struct audio_device *adev, |
| int wsa_num, int boost_table_index) |
| { |
| struct mixer_ctl *ctl; |
| const char *mixer_ctl_name_boost_level[] = { |
| WSA_LEFT_BOOST_LEVEL, |
| WSA_RIGHT_BOOST_LEVEL |
| }; |
| int status = 0; |
| |
| ctl = mixer_get_ctl_by_name(adev->mixer, |
| mixer_ctl_name_boost_level[wsa_num]); |
| if (!ctl) { |
| ALOGE("%s: Could not get ctl for mixer cmd - %s", |
| __func__, mixer_ctl_name_boost_level[wsa_num]); |
| return -EINVAL; |
| } |
| |
| status = mixer_ctl_set_value(ctl, 0, |
| spkr_prot_boost_lookup_table[boost_table_index].boost_value); |
| |
| if (status < 0) { |
| ALOGE("%s: Could not set ctl for mixer %s\n", __func__, |
| mixer_ctl_name_boost_level[wsa_num]); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int audio_extn_spkr_boost_update(struct audio_device *adev, |
| unsigned int wsa_num, unsigned int *index, bool spkr2_is_mono) |
| { |
| float dcr = 0; |
| unsigned int r0_index = 0, wsa_to_set = wsa_num; |
| int boost_max_state = 0; |
| int ret = 0; |
| |
| /* get R0 value */ |
| dcr = ((float)handle.sp_r0t0_cal.r0[wsa_num] / MIN_RESISTANCE_SPKR_Q24 * 2); |
| if (dcr < MIN_RESISTANCE_LOOKUP) { |
| ALOGV("%s: resistance %f changes to min value of 3.2.", |
| __func__, dcr); |
| dcr = MIN_RESISTANCE_LOOKUP; |
| } |
| |
| if (dcr > MAX_RESISTANCE_LOOKUP) { |
| ALOGV("%s: resistance %f changes to max value of 8.", |
| __func__, dcr); |
| dcr = MAX_RESISTANCE_LOOKUP; |
| } |
| |
| r0_index = (int)((dcr - MIN_RESISTANCE_LOOKUP) * 10); |
| if (r0_index >= SPKR_PROT_LOOKUP_TABLE_ROWS) { |
| ALOGE("%s: r0_index=%d overflows.", __func__, r0_index); |
| return -EINVAL; |
| } |
| |
| boost_max_state = spkr_prot_boost_lookup_table[r0_index].max_state; |
| |
| /* In case of wsatz.14 as the only speaker on target, prefix of corresponding |
| * mixer ctl in dirver is named SpkrRight. As a result, we have to fixup the |
| * WSA number. |
| */ |
| if (spkr2_is_mono) |
| wsa_to_set = SP_V2_SPKR_2; |
| |
| ret = audio_extn_set_wcd_boost_max_state(adev, boost_max_state, wsa_to_set); |
| if (ret < 0) { |
| ALOGE("%s: failed to set wcd max boost state.", |
| __func__); |
| return -EINVAL; |
| } |
| |
| ret = audio_extn_set_wsa_boost_level(adev, wsa_to_set, r0_index); |
| if (ret < 0) { |
| ALOGE("%s: failed to set wsa boost level.", |
| __func__); |
| return -EINVAL; |
| } |
| |
| *index = r0_index; |
| |
| return 0; |
| } |
| |
| static void audio_extn_set_boost_and_limiter(struct audio_device *adev, |
| bool spv3_enable, unsigned int afe_api_version) |
| { |
| int chn = 0; |
| int chn_in_use = 0; |
| bool wsa_is_8815[SP_V2_NUM_MAX_SPKRS] = {false, false}; |
| bool spkr2_is_mono_speaker = false; |
| unsigned int r0_index = 0; |
| |
| handle.sp_version = SP_V2; |
| |
| /* |
| * As long as speaker protection is enabled, WCD and WSA |
| * follow lookup table based on R0 impediance regardless |
| * of spv2 or spv3. |
| */ |
| audio_extn_check_wsa(adev, vi_feed_no_channels, wsa_is_8815); |
| if (vi_feed_no_channels == 1 && wsa_is_8815[SP_V2_SPKR_2]) |
| spkr2_is_mono_speaker = true; |
| /* |
| * In case of WSA8815+8810, invalid limiter threshold is sent to DSP |
| * for WSA8810 speaker. DSP ignores the invalid value and use default one. |
| * The approach let spv3 apply on 8815 and spv2 on 8810 respectively. |
| */ |
| for (chn = 0; chn < vi_feed_no_channels; chn++) { |
| chn_in_use = chn; |
| if (spkr2_is_mono_speaker) |
| chn_in_use = SP_V2_SPKR_2; |
| if (wsa_is_8815[chn_in_use] && |
| !audio_extn_spkr_boost_update(adev, chn, |
| &r0_index, spkr2_is_mono_speaker)) { |
| handle.limiter_th[chn] = spv3_limiter_th_q27_table[r0_index]; |
| handle.sp_version = SP_V3; |
| } |
| else { |
| handle.limiter_th[chn] = DEFAULT_LIMITER_TH; |
| } |
| } |
| |
| /* |
| * If spv3 is disabld or ADSP version doesn't comply, |
| * ADSP works with SP_V2 version. |
| */ |
| if (!spv3_enable || afe_api_version < AFE_API_VERSION_SUPPORT_SPV3) |
| handle.sp_version = SP_V2; |
| } |
| |
| static int spkr_calibrate(int t0_spk_1, int t0_spk_2) |
| { |
| 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; |
| int retry_duration; |
| int app_type = 0; |
| |
| memset(&status, 0, sizeof(status)); |
| memset(&protCfg, 0, sizeof(protCfg)); |
| 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; |
| protCfg.t0[SP_V2_SPKR_1] = t0_spk_1; |
| protCfg.t0[SP_V2_SPKR_2] = t0_spk_2; |
| 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; |
| if (audio_extn_is_vbat_enabled()) |
| uc_info_rx->out_snd_device = SND_DEVICE_OUT_SPEAKER_PROTECTED_VBAT; |
| else |
| uc_info_rx->out_snd_device = SND_DEVICE_OUT_SPEAKER_PROTECTED; |
| disable_rx = true; |
| list_add_tail(&adev->usecase_list, &uc_info_rx->list); |
| platform_check_and_set_codec_backend_cfg(adev, uc_info_rx, |
| uc_info_rx->out_snd_device); |
| if (audio_extn_is_vbat_enabled()) |
| enable_snd_device(adev, SND_DEVICE_OUT_SPEAKER_PROTECTED_VBAT); |
| else |
| 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); |
| ALOGV("%s: pcm device id %d", __func__, pcm_dev_tx_id); |
| 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_MONOTONIC, &ts); |
| ts.tv_sec += (SLEEP_AFTER_CALIB_START/1000); |
| 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; |
| retry_duration = 0; |
| while (!get_spkr_prot_cal(acdb_fd, &status) && |
| retry_duration < GET_SPKR_PROT_CAL_TIMEOUT_MSEC) { |
| /*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; |
| 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) { |
| ALOGV("%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 */ |
| if (uc_info_tx != NULL) { |
| ALOGD("%s: UC Info TX is not NULL, updating and sending calibration", |
| __func__); |
| uc_info_tx->in_snd_device = SND_DEVICE_IN_HANDSET_MIC; |
| uc_info_tx->out_snd_device = SND_DEVICE_NONE; |
| app_type = platform_get_default_app_type_v2(adev->platform, |
| PCM_CAPTURE); |
| platform_send_audio_calibration(adev->platform, uc_info_tx, |
| app_type, 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); |
| if (audio_extn_is_vbat_enabled()) |
| disable_snd_device(adev, SND_DEVICE_OUT_SPEAKER_PROTECTED_VBAT); |
| else |
| 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_calibrate_wait() |
| { |
| struct timespec ts; |
| |
| clock_gettime(CLOCK_MONOTONIC, &ts); |
| ts.tv_sec += WAKEUP_MIN_IDLE_CHECK; |
| pthread_mutex_lock(&handle.cal_wait_cond_mutex); |
| pthread_cond_timedwait(&handle.cal_wait_condition, |
| &handle.cal_wait_cond_mutex, &ts); |
| pthread_mutex_unlock(&handle.cal_wait_cond_mutex); |
| } |
| |
| static void* spkr_calibration_thread() |
| { |
| unsigned long sec = 0; |
| int t0; |
| int t0_spk_1 = 0; |
| int t0_spk_2 = 0; |
| bool goahead = false; |
| struct audio_cal_info_spk_prot_cfg protCfg; |
| FILE *fp; |
| int acdb_fd, thermal_fd; |
| struct audio_device *adev = handle.adev_handle; |
| unsigned long min_idle_time = MIN_SPKR_IDLE_SEC; |
| char value[PROPERTY_VALUE_MAX]; |
| char afe_version_value[PROPERTY_VALUE_MAX]; |
| char wsa_path[MAX_PATH] = {0}; |
| int spk_1_tzn, spk_2_tzn; |
| char buf[32] = {0}; |
| int ret; |
| bool spv3_enable = false; |
| unsigned int afe_api_version = 0; |
| struct mixer_ctl *ctl; |
| |
| memset(&protCfg, 0, sizeof(protCfg)); |
| /* If the value of this persist.vendor.audio.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.vendor.audio.spkr.cal.duration", value, "0"); |
| if (atoi(value) > 0) |
| min_idle_time = atoi(value); |
| else { |
| 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; |
| } |
| |
| spv3_enable = property_get_bool("persist.vendor.audio.spv3.enable", false); |
| property_get("persist.vendor.audio.avs.afe_api_version", afe_version_value, |
| "0"); |
| if (atoi(afe_version_value) > 0) |
| afe_api_version = atoi(afe_version_value); |
| |
| fp = fopen(CALIB_FILE,"rb"); |
| if (fp) { |
| int i; |
| bool spkr_calibrated = true; |
| 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); |
| |
| audio_extn_set_boost_and_limiter(adev, spv3_enable, afe_api_version); |
| |
| pthread_exit(0); |
| return NULL; |
| } |
| } |
| if (acdb_fd > 0) |
| close(acdb_fd); |
| |
| ALOGV("%s: start calibration", __func__); |
| while (!handle.thread_exit) { |
| if (handle.wsa_found) { |
| spk_1_tzn = handle.spkr_1_tzn; |
| spk_2_tzn = handle.spkr_2_tzn; |
| goahead = false; |
| pthread_mutex_lock(&adev->lock); |
| if (is_speaker_in_use(&sec)) { |
| ALOGV("%s: WSA Speaker in use retry calibration", __func__); |
| pthread_mutex_unlock(&adev->lock); |
| spkr_calibrate_wait(); |
| continue; |
| } else { |
| ALOGD("%s: wsa speaker idle %ld,minimum time %ld", __func__, sec, min_idle_time); |
| if (!adev->primary_output || |
| ((sec < min_idle_time) && !handle.trigger_cal)) { |
| pthread_mutex_unlock(&adev->lock); |
| spkr_calibrate_wait(); |
| continue; |
| } |
| goahead = true; |
| } |
| if (!list_empty(&adev->usecase_list)) { |
| ALOGD("%s: Usecase active re-try calibration", __func__); |
| pthread_mutex_unlock(&adev->lock); |
| spkr_calibrate_wait(); |
| continue; |
| } |
| if (goahead) { |
| if (spk_1_tzn >= 0) { |
| const char *mixer_ctl_name = "SpkrLeft WSA T0 Init"; |
| snprintf(wsa_path, MAX_PATH, TZ_WSA, spk_1_tzn); |
| ALOGV("%s: wsa_path: %s\n", __func__, wsa_path); |
| thermal_fd = -1; |
| |
| ctl = mixer_get_ctl_by_name(adev->mixer, mixer_ctl_name); |
| if (ctl) { |
| ALOGD("%s: Got ctl for mixer cmd %s", |
| __func__, mixer_ctl_name); |
| mixer_ctl_set_value(ctl, 0, 1); |
| } |
| |
| thermal_fd = open(wsa_path, O_RDONLY); |
| if (thermal_fd > 0) { |
| if ((ret = read(thermal_fd, buf, sizeof(buf))) >= 0) |
| t0_spk_1 = atoi(buf); |
| else |
| ALOGE("%s: read fail for %s err:%d\n", __func__, wsa_path, ret); |
| close(thermal_fd); |
| } else { |
| ALOGE("%s: fd for %s is NULL\n", __func__, wsa_path); |
| } |
| if (ctl) { |
| mixer_ctl_set_value(ctl, 0, 0); |
| } |
| if (t0_spk_1 < TZ_TEMP_MIN_THRESHOLD || |
| t0_spk_1 > TZ_TEMP_MAX_THRESHOLD) { |
| pthread_mutex_unlock(&adev->lock); |
| spkr_calibrate_wait(); |
| continue; |
| } |
| ALOGD("%s: temp T0 for spkr1 %d\n", __func__, t0_spk_1); |
| /*Convert temp into q6 format*/ |
| t0_spk_1 = (t0_spk_1 * (1 << 6)); |
| } |
| if (spk_2_tzn >= 0) { |
| const char *mixer_ctl_name = "SpkrRight WSA T0 Init"; |
| snprintf(wsa_path, MAX_PATH, TZ_WSA, spk_2_tzn); |
| ALOGV("%s: wsa_path: %s\n", __func__, wsa_path); |
| ctl = mixer_get_ctl_by_name(adev->mixer, mixer_ctl_name); |
| if (ctl) { |
| ALOGD("%s: Got ctl for mixer cmd %s", |
| __func__, mixer_ctl_name); |
| mixer_ctl_set_value(ctl, 0, 1); |
| } |
| thermal_fd = open(wsa_path, O_RDONLY); |
| if (thermal_fd > 0) { |
| if ((ret = read(thermal_fd, buf, sizeof(buf))) >= 0) |
| t0_spk_2 = atoi(buf); |
| else |
| ALOGE("%s: read fail for %s err:%d\n", __func__, wsa_path, ret); |
| close(thermal_fd); |
| } else { |
| ALOGE("%s: fd for %s is NULL\n", __func__, wsa_path); |
| } |
| if (ctl) { |
| mixer_ctl_set_value(ctl, 0, 0); |
| } |
| if (t0_spk_2 < TZ_TEMP_MIN_THRESHOLD || |
| t0_spk_2 > TZ_TEMP_MAX_THRESHOLD) { |
| pthread_mutex_unlock(&adev->lock); |
| spkr_calibrate_wait(); |
| continue; |
| } |
| ALOGD("%s: temp T0 for spkr2 %d\n", __func__, t0_spk_2); |
| /*Convert temp into q6 format*/ |
| t0_spk_2 = (t0_spk_2 * (1 << 6)); |
| } |
| } |
| pthread_mutex_unlock(&adev->lock); |
| } else 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; |
| } |
| t0_spk_1 = t0; |
| t0_spk_2 = t0; |
| 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_spk_1 = SAFE_SPKR_TEMP_Q6; |
| t0_spk_2 = SAFE_SPKR_TEMP_Q6; |
| } |
| goahead = false; |
| pthread_mutex_lock(&adev->lock); |
| if (is_speaker_in_use(&sec)) { |
| ALOGV("%s: Speaker in use retry calibration", __func__); |
| pthread_mutex_unlock(&adev->lock); |
| spkr_calibrate_wait(); |
| continue; |
| } else { |
| if (!(sec > min_idle_time || handle.trigger_cal)) { |
| pthread_mutex_unlock(&adev->lock); |
| spkr_calibrate_wait(); |
| continue; |
| } |
| goahead = true; |
| } |
| if (!list_empty(&adev->usecase_list)) { |
| ALOGD("%s: Usecase active re-try calibration", __func__); |
| goahead = false; |
| pthread_mutex_unlock(&adev->lock); |
| spkr_calibrate_wait(); |
| continue; |
| } |
| if (goahead) { |
| int status; |
| /* DSP always calibrates 1st channel data in mono case. |
| * When wsatz14 is the only speaker on target, temperature |
| * sensor data comes in 2nd channel. Therefore, we have to swap |
| * sensor channel to fix the mismatch. |
| */ |
| if ( handle.spkr_1_tzn <= 0 && handle.spkr_2_tzn > 0) |
| status = spkr_calibrate(t0_spk_2, t0_spk_1); |
| else |
| status = spkr_calibrate(t0_spk_1, t0_spk_2); |
| 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; |
| |
| audio_extn_set_boost_and_limiter(adev, spv3_enable, afe_api_version); |
| |
| 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_set_parameters(struct str_parms *parms, |
| char *value, int len) |
| { |
| int err; |
| |
| err = str_parms_get_str(parms, AUDIO_PARAMETER_KEY_SPKR_TZ_1, |
| value, len); |
| if (err >= 0) { |
| tz_names.spkr_1_name = strdup(value); |
| str_parms_del(parms, AUDIO_PARAMETER_KEY_SPKR_TZ_1); |
| } |
| |
| err = str_parms_get_str(parms, AUDIO_PARAMETER_KEY_SPKR_TZ_2, |
| value, len); |
| if (err >= 0) { |
| tz_names.spkr_2_name = strdup(value); |
| str_parms_del(parms, AUDIO_PARAMETER_KEY_SPKR_TZ_2); |
| } |
| |
| ALOGV("%s: tz1: %s, tz2: %s", __func__, |
| tz_names.spkr_1_name, tz_names.spkr_2_name); |
| } |
| |
| static int spkr_vi_channels(struct audio_device *adev) |
| { |
| int vi_channels, vi_channel_num_by_wsa = 0; |
| |
| vi_channels = vi_feed_get_channels(adev); |
| ALOGD("%s: vi_channels %d", __func__, vi_channels); |
| if (vi_channels < 0 || vi_channels > SP_V2_NUM_MAX_SPKRS) { |
| /* limit the number of channels to SP_V2_NUM_MAX_SPKRS */ |
| vi_channels = SP_V2_NUM_MAX_SPKRS; |
| } |
| |
| ALOGD("%s: tz1: %s, tz2: %s", __func__, |
| tz_names.spkr_1_name, tz_names.spkr_2_name); |
| handle.spkr_1_tzn = get_tzn(tz_names.spkr_1_name); |
| handle.spkr_2_tzn = get_tzn(tz_names.spkr_2_name); |
| /* Update VI channel number by WSA number */ |
| if (handle.spkr_1_tzn >= 0) |
| vi_channel_num_by_wsa++; |
| |
| if (handle.spkr_2_tzn >= 0) |
| vi_channel_num_by_wsa++; |
| |
| if (vi_channel_num_by_wsa > 0) |
| handle.wsa_found = true; |
| |
| if (vi_channel_num_by_wsa < vi_channels) |
| vi_channels = vi_channel_num_by_wsa; |
| |
| return vi_channels; |
| } |
| |
| static void get_spkr_prot_thermal_cal(char *param) |
| { |
| int i, status = 0; |
| int r0[SP_V2_NUM_MAX_SPKRS] = {0}, t0[SP_V2_NUM_MAX_SPKRS] = {0}; |
| double dr0[SP_V2_NUM_MAX_SPKRS] = {0}, dt0[SP_V2_NUM_MAX_SPKRS] = {0}; |
| |
| FILE *fp = fopen(CALIB_FILE,"rb"); |
| if (fp) { |
| for (i = 0; i < vi_feed_no_channels; i++) { |
| fread(&r0[i], sizeof(int), 1, fp); |
| fread(&t0[i], sizeof(int), 1, fp); |
| /* Convert from ADSP format to readable format */ |
| dr0[i] = ((double)r0[i])/(1 << 24); |
| dt0[i] = ((double)t0[i])/(1 << 6); |
| } |
| ALOGV("%s: R0= %lf, %lf, T0= %lf, %lf", |
| __func__, dr0[0], dr0[1], dt0[0], dt0[1]); |
| fclose(fp); |
| } else { |
| ALOGE("%s: failed to open cal file\n", __func__); |
| status = -EINVAL; |
| } |
| snprintf(param, MAX_STR_SIZE - strlen(param) - 1, |
| "SpkrCalStatus: %d; R0: %lf, %lf; T0: %lf, %lf", |
| status, dr0[SP_V2_SPKR_1], dr0[SP_V2_SPKR_2], |
| dt0[SP_V2_SPKR_1], dt0[SP_V2_SPKR_2]); |
| ALOGD("%s:: param = %s\n", __func__, param); |
| |
| return; |
| } |
| |
| #ifdef MSM_SPKR_PROT_IN_FTM_MODE |
| |
| static int set_spkr_prot_ftm_cfg(int wait_time, int ftm_time) |
| { |
| int ret = 0; |
| struct audio_cal_sp_th_vi_ftm_cfg th_cal_data; |
| struct audio_cal_sp_ex_vi_ftm_cfg ex_cal_data; |
| |
| int cal_fd = open("/dev/msm_audio_cal",O_RDWR | O_NONBLOCK); |
| if (cal_fd < 0) { |
| ALOGE("%s: open msm_acdb failed", __func__); |
| ret = -ENODEV; |
| goto done; |
| } |
| |
| memset(&th_cal_data, 0, sizeof(th_cal_data)); |
| th_cal_data.hdr.data_size = sizeof(th_cal_data); |
| th_cal_data.hdr.version = VERSION_0_0; |
| th_cal_data.hdr.cal_type = AFE_FB_SPKR_PROT_TH_VI_CAL_TYPE; |
| th_cal_data.hdr.cal_type_size = sizeof(th_cal_data.cal_type); |
| th_cal_data.cal_type.cal_hdr.version = VERSION_0_0; |
| th_cal_data.cal_type.cal_hdr.buffer_number = 0; |
| th_cal_data.cal_type.cal_info.wait_time[SP_V2_SPKR_1] = wait_time; |
| th_cal_data.cal_type.cal_info.wait_time[SP_V2_SPKR_2] = wait_time; |
| th_cal_data.cal_type.cal_info.ftm_time[SP_V2_SPKR_1] = ftm_time; |
| th_cal_data.cal_type.cal_info.ftm_time[SP_V2_SPKR_2] = ftm_time; |
| th_cal_data.cal_type.cal_info.mode = MSM_SPKR_PROT_IN_FTM_MODE; // FTM mode |
| th_cal_data.cal_type.cal_data.mem_handle = -1; |
| |
| if (ioctl(cal_fd, AUDIO_SET_CALIBRATION, &th_cal_data)) |
| ALOGE("%s: failed to set TH VI FTM_CFG, errno = %d", __func__, errno); |
| |
| memset(&ex_cal_data, 0, sizeof(ex_cal_data)); |
| ex_cal_data.hdr.data_size = sizeof(ex_cal_data); |
| ex_cal_data.hdr.version = VERSION_0_0; |
| ex_cal_data.hdr.cal_type = AFE_FB_SPKR_PROT_EX_VI_CAL_TYPE; |
| ex_cal_data.hdr.cal_type_size = sizeof(ex_cal_data.cal_type); |
| ex_cal_data.cal_type.cal_hdr.version = VERSION_0_0; |
| ex_cal_data.cal_type.cal_hdr.buffer_number = 0; |
| ex_cal_data.cal_type.cal_info.wait_time[SP_V2_SPKR_1] = wait_time; |
| ex_cal_data.cal_type.cal_info.wait_time[SP_V2_SPKR_2] = wait_time; |
| ex_cal_data.cal_type.cal_info.ftm_time[SP_V2_SPKR_1] = ftm_time; |
| ex_cal_data.cal_type.cal_info.ftm_time[SP_V2_SPKR_2] = ftm_time; |
| ex_cal_data.cal_type.cal_info.mode = MSM_SPKR_PROT_IN_FTM_MODE; // FTM mode |
| ex_cal_data.cal_type.cal_data.mem_handle = -1; |
| |
| if (ioctl(cal_fd, AUDIO_SET_CALIBRATION, &ex_cal_data)) |
| ALOGE("%s: failed to set EX VI FTM_CFG, ret = %d", __func__, errno); |
| |
| if (cal_fd > 0) |
| close(cal_fd); |
| done: |
| return ret; |
| } |
| |
| static void get_spkr_prot_ftm_param(char *param) |
| { |
| struct audio_cal_sp_th_vi_param th_vi_cal_data; |
| struct audio_cal_sp_ex_vi_param ex_vi_cal_data; |
| int i; |
| int ftm_status[SP_V2_NUM_MAX_SPKRS] = {0}; |
| double rdc[SP_V2_NUM_MAX_SPKRS] = {0}, temp[SP_V2_NUM_MAX_SPKRS] = {0}; |
| double f[SP_V2_NUM_MAX_SPKRS] = {0}, r[SP_V2_NUM_MAX_SPKRS] = {0}, q[SP_V2_NUM_MAX_SPKRS] = {0}; |
| |
| int cal_fd = open("/dev/msm_audio_cal",O_RDWR | O_NONBLOCK); |
| if (cal_fd < 0) { |
| ALOGE("%s: open msm_acdb failed", __func__); |
| goto done; |
| } |
| |
| memset(&th_vi_cal_data, 0, sizeof(th_vi_cal_data)); |
| th_vi_cal_data.cal_type.cal_info.status[SP_V2_SPKR_1] = -EINVAL; |
| th_vi_cal_data.cal_type.cal_info.status[SP_V2_SPKR_2] = -EINVAL; |
| th_vi_cal_data.hdr.data_size = sizeof(th_vi_cal_data); |
| th_vi_cal_data.hdr.version = VERSION_0_0; |
| th_vi_cal_data.hdr.cal_type = AFE_FB_SPKR_PROT_TH_VI_CAL_TYPE; |
| th_vi_cal_data.hdr.cal_type_size = sizeof(th_vi_cal_data.cal_type); |
| th_vi_cal_data.cal_type.cal_hdr.version = VERSION_0_0; |
| th_vi_cal_data.cal_type.cal_hdr.buffer_number = 0; |
| th_vi_cal_data.cal_type.cal_data.mem_handle = -1; |
| |
| if (ioctl(cal_fd, AUDIO_GET_CALIBRATION, &th_vi_cal_data)) |
| ALOGE("%s: Error %d in getting th_vi_cal_data", __func__, errno); |
| |
| memset(&ex_vi_cal_data, 0, sizeof(ex_vi_cal_data)); |
| ex_vi_cal_data.cal_type.cal_info.status[SP_V2_SPKR_1] = -EINVAL; |
| ex_vi_cal_data.cal_type.cal_info.status[SP_V2_SPKR_2] = -EINVAL; |
| ex_vi_cal_data.hdr.data_size = sizeof(ex_vi_cal_data); |
| ex_vi_cal_data.hdr.version = VERSION_0_0; |
| ex_vi_cal_data.hdr.cal_type = AFE_FB_SPKR_PROT_EX_VI_CAL_TYPE; |
| ex_vi_cal_data.hdr.cal_type_size = sizeof(ex_vi_cal_data.cal_type); |
| ex_vi_cal_data.cal_type.cal_hdr.version = VERSION_0_0; |
| ex_vi_cal_data.cal_type.cal_hdr.buffer_number = 0; |
| ex_vi_cal_data.cal_type.cal_data.mem_handle = -1; |
| |
| if (ioctl(cal_fd, AUDIO_GET_CALIBRATION, &ex_vi_cal_data)) |
| ALOGE("%s: Error %d in getting ex_vi_cal_data", __func__, errno); |
| |
| for (i = 0; i < vi_feed_no_channels; i++) { |
| /* Convert from ADSP format to readable format */ |
| rdc[i] = ((double)th_vi_cal_data.cal_type.cal_info.r_dc_q24[i])/(1<<24); |
| temp[i] = ((double)th_vi_cal_data.cal_type.cal_info.temp_q22[i])/(1<<22); |
| f[i] = ((double)ex_vi_cal_data.cal_type.cal_info.freq_q20[i])/(1<<20); |
| r[i] = ((double)ex_vi_cal_data.cal_type.cal_info.resis_q24[i])/(1<<24); |
| q[i] = ((double)ex_vi_cal_data.cal_type.cal_info.qmct_q24[i])/(1<<24); |
| |
| if (th_vi_cal_data.cal_type.cal_info.status[i] == 0 && |
| ex_vi_cal_data.cal_type.cal_info.status[i] == 0) { |
| ftm_status[i] = 0; |
| } else if (th_vi_cal_data.cal_type.cal_info.status[i] == -EAGAIN && |
| ex_vi_cal_data.cal_type.cal_info.status[i] == -EAGAIN) { |
| ftm_status[i] = -EAGAIN; |
| } else { |
| ftm_status[i] = -EINVAL; |
| } |
| } |
| snprintf(param, MAX_STR_SIZE - strlen(param) - 1, |
| "SpkrParamStatus: %d, %d; Rdc: %lf, %lf; Temp: %lf, %lf;" |
| " Freq: %lf, %lf; Rect: %lf, %lf; Qmct: %lf, %lf", |
| ftm_status[SP_V2_SPKR_1], ftm_status[SP_V2_SPKR_2], |
| rdc[SP_V2_SPKR_1], rdc[SP_V2_SPKR_2], temp[SP_V2_SPKR_1], |
| temp[SP_V2_SPKR_2], f[SP_V2_SPKR_1], f[SP_V2_SPKR_2], |
| r[SP_V2_SPKR_1], r[SP_V2_SPKR_2], q[SP_V2_SPKR_1], q[SP_V2_SPKR_2]); |
| ALOGD("%s:: param = %s\n", __func__, param); |
| |
| if (cal_fd > 0) |
| close(cal_fd); |
| done: |
| return; |
| } |
| |
| #else |
| |
| static void get_spkr_prot_ftm_param(char *param __unused) |
| { |
| |
| ALOGD("%s: not supported", __func__); |
| return; |
| } |
| |
| static int set_spkr_prot_ftm_cfg(int wait_time __unused, int ftm_time __unused) |
| { |
| ALOGD("%s: not supported", __func__); |
| return -ENOSYS; |
| } |
| #endif |
| |
| static void spkr_calibrate_signal() |
| { |
| pthread_mutex_lock(&handle.cal_wait_cond_mutex); |
| pthread_cond_signal(&handle.cal_wait_condition); |
| pthread_mutex_unlock(&handle.cal_wait_cond_mutex); |
| } |
| |
| int audio_extn_fbsp_set_parameters(struct str_parms *parms) |
| { |
| int ret= 0 , err; |
| char *value = NULL; |
| int len; |
| char *test_r = NULL; |
| char *cfg_str; |
| int wait_time, ftm_time; |
| char *kv_pairs = str_parms_to_str(parms); |
| |
| if(kv_pairs == NULL) { |
| ret = -ENOMEM; |
| ALOGE("[%s] key-value pair is NULL",__func__); |
| goto done; |
| } |
| ALOGV_IF(kv_pairs != NULL, "%s: enter: %s", __func__, kv_pairs); |
| |
| len = strlen(kv_pairs); |
| value = (char*)calloc(len, sizeof(char)); |
| if(value == NULL) { |
| ret = -ENOMEM; |
| ALOGE("[%s] failed to allocate memory",__func__); |
| goto done; |
| } |
| if (!handle.spkr_prot_enable) { |
| ALOGD("%s: Speaker protection disabled", __func__); |
| goto done; |
| } |
| |
| err = str_parms_get_str(parms, AUDIO_PARAMETER_KEY_FBSP_TRIGGER_SPKR_CAL, value, |
| len); |
| if (err >= 0) { |
| str_parms_del(parms, AUDIO_PARAMETER_KEY_FBSP_TRIGGER_SPKR_CAL); |
| if ((strcmp(value, "true") == 0) || (strcmp(value, "yes") == 0)) { |
| handle.trigger_cal = true; |
| spkr_calibrate_signal(); |
| } |
| goto done; |
| } |
| |
| /* Expected key value pair is in below format: |
| * AUDIO_PARAM_FBSP_CFG_WAIT_TIME=waittime;AUDIO_PARAM_FBSP_CFG_FTM_TIME=ftmtime; |
| * Parse waittime and ftmtime from it. |
| */ |
| err = str_parms_get_str(parms, AUDIO_PARAMETER_KEY_FBSP_CFG_WAIT_TIME, |
| value, len); |
| if (err >= 0) { |
| str_parms_del(parms, AUDIO_PARAMETER_KEY_FBSP_CFG_WAIT_TIME); |
| cfg_str = strtok_r(value, ";", &test_r); |
| if (cfg_str == NULL) { |
| ALOGE("%s: incorrect wait time cfg_str", __func__); |
| ret = -EINVAL; |
| goto done; |
| } |
| wait_time = atoi(cfg_str); |
| ALOGV(" %s: cfg_str = %s, wait_time = %d", __func__, cfg_str, wait_time); |
| |
| err = str_parms_get_str(parms, AUDIO_PARAMETER_KEY_FBSP_CFG_FTM_TIME, |
| value, len); |
| if (err >= 0) { |
| str_parms_del(parms, AUDIO_PARAMETER_KEY_FBSP_CFG_FTM_TIME); |
| cfg_str = strtok_r(value, ";", &test_r); |
| if (cfg_str == NULL) { |
| ALOGE("%s: incorrect ftm time cfg_str", __func__); |
| ret = -EINVAL; |
| goto done; |
| } |
| ftm_time = atoi(cfg_str); |
| ALOGV(" %s: cfg_str = %s, ftm_time = %d", __func__, cfg_str, ftm_time); |
| |
| ret = set_spkr_prot_ftm_cfg(wait_time, ftm_time); |
| if (ret < 0) { |
| ALOGE("%s: set_spkr_prot_ftm_cfg failed", __func__); |
| goto done; |
| } |
| } |
| } |
| |
| done: |
| ALOGV("%s: exit with code(%d)", __func__, ret); |
| |
| if(kv_pairs != NULL) |
| free(kv_pairs); |
| if(value != NULL) |
| free(value); |
| |
| return ret; |
| } |
| |
| int audio_extn_fbsp_get_parameters(struct str_parms *query, |
| struct str_parms *reply) |
| { |
| int err = 0; |
| char value[MAX_STR_SIZE] = {0}; |
| |
| if (!handle.spkr_prot_enable) { |
| ALOGD("%s: Speaker protection disabled", __func__); |
| return -EINVAL; |
| } |
| |
| err = str_parms_get_str(query, AUDIO_PARAMETER_KEY_FBSP_GET_SPKR_CAL, value, |
| sizeof(value)); |
| if (err >= 0) { |
| get_spkr_prot_thermal_cal(value); |
| str_parms_add_str(reply, AUDIO_PARAMETER_KEY_FBSP_GET_SPKR_CAL, value); |
| } |
| err = str_parms_get_str(query, AUDIO_PARAMETER_KEY_FBSP_GET_FTM_PARAM, value, |
| sizeof(value)); |
| if (err >= 0) { |
| get_spkr_prot_ftm_param(value); |
| str_parms_add_str(reply, AUDIO_PARAMETER_KEY_FBSP_GET_FTM_PARAM, value); |
| } |
| return err; |
| } |
| |
| void audio_extn_spkr_prot_init(void *adev) |
| { |
| char value[PROPERTY_VALUE_MAX]; |
| int result = 0; |
| pthread_condattr_t attr; |
| ALOGD("%s: Initialize speaker protection module", __func__); |
| memset(&handle, 0, sizeof(handle)); |
| if (!adev) { |
| ALOGE("%s: Invalid params", __func__); |
| return; |
| } |
| handle.spkr_prot_enable = false; |
| if ((property_get("persist.vendor.audio.speaker.prot.enable", |
| value, NULL) > 0) || |
| (property_get("persist.speaker.prot.enable", |
| value, NULL) > 0)) { |
| if (!strncmp("true", value, 4)) |
| handle.spkr_prot_enable = true; |
| } |
| handle.init_check = false; |
| handle.thread_exit = false; |
| 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; |
| handle.trigger_cal = false; |
| /* HAL for speaker protection is always calibrating for stereo usecase*/ |
| vi_feed_no_channels = spkr_vi_channels(adev); |
| 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; |
| } |
| |
| pthread_condattr_init(&attr); |
| pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); |
| pthread_cond_init(&handle.cal_wait_condition, &attr); |
| pthread_mutex_init(&handle.cal_wait_cond_mutex, NULL); |
| pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); |
| if (handle.wsa_found) { |
| if (platform_spkr_prot_is_wsa_analog_mode(adev) == 1) { |
| ALOGD("%s: WSA analog mode", __func__); |
| pcm_config_skr_prot.channels = WSA_ANALOG_MODE_CHANNELS; |
| } |
| pthread_cond_init(&handle.spkr_calib_cancel, &attr); |
| 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); |
| ALOGD("%s:WSA Create calibration thread", __func__); |
| result = pthread_create(&handle.spkr_calibration_thread, |
| (const pthread_attr_t *) NULL, spkr_calibration_thread, &handle); |
| if (result == 0) { |
| handle.init_check = true; |
| } else { |
| ALOGE("%s: speaker calibration thread creation failed", __func__); |
| destroy_thread_params(); |
| } |
| return; |
| } else { |
| ALOGD("%s: WSA spkr calibration thread is not created", __func__); |
| } |
| pthread_cond_init(&handle.spkr_prot_thermalsync, NULL); |
| pthread_cond_init(&handle.spkr_calib_cancel, &attr); |
| 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__); |
| result = pthread_create(&handle.spkr_calibration_thread, |
| (const pthread_attr_t *) NULL, spkr_calibration_thread, &handle); |
| if (result == 0) { |
| handle.init_check = true; |
| } else { |
| ALOGE("%s: speaker calibration thread creation failed", __func__); |
| destroy_thread_params(); |
| } |
| } 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", |
| "SLIMBUS_0_RX"); |
| } |
| } |
| } |
| |
| int audio_extn_spkr_prot_deinit() |
| { |
| int result = 0; |
| |
| ALOGD("%s: Entering deinit init_check :%d", |
| __func__, handle.init_check); |
| if(!handle.init_check) |
| return -1; |
| |
| handle.thread_exit = true; |
| spkr_calibrate_signal(); |
| result = pthread_join(handle.spkr_calibration_thread, (void **) NULL); |
| if (result < 0) { |
| ALOGE("%s:Unable to join the calibration thread", __func__); |
| return -1; |
| } |
| destroy_thread_params(); |
| memset(&handle, 0, sizeof(handle)); |
| return 0; |
| } |
| |
| int audio_extn_select_spkr_prot_cal_data(snd_device_t snd_device) |
| { |
| struct audio_cal_info_spk_prot_cfg protCfg; |
| int acdb_fd = -1; |
| int ret = 0; |
| |
| acdb_fd = open("/dev/msm_audio_cal", O_RDWR | O_NONBLOCK); |
| if (acdb_fd < 0) { |
| ALOGE("%s: open msm_acdb failed", __func__); |
| return -ENODEV; |
| } |
| switch(snd_device) { |
| case SND_DEVICE_OUT_VOICE_SPEAKER_2_PROTECTED_VBAT: |
| case SND_DEVICE_OUT_VOICE_SPEAKER_2_PROTECTED: |
| protCfg.r0[SP_V2_SPKR_1] = handle.sp_r0t0_cal.r0[SP_V2_SPKR_2]; |
| protCfg.r0[SP_V2_SPKR_2] = handle.sp_r0t0_cal.r0[SP_V2_SPKR_1]; |
| protCfg.t0[SP_V2_SPKR_1] = handle.sp_r0t0_cal.t0[SP_V2_SPKR_2]; |
| protCfg.t0[SP_V2_SPKR_2] = handle.sp_r0t0_cal.t0[SP_V2_SPKR_1]; |
| break; |
| default: |
| protCfg.r0[SP_V2_SPKR_1] = handle.sp_r0t0_cal.r0[SP_V2_SPKR_1]; |
| protCfg.r0[SP_V2_SPKR_2] = handle.sp_r0t0_cal.r0[SP_V2_SPKR_2]; |
| protCfg.t0[SP_V2_SPKR_1] = handle.sp_r0t0_cal.t0[SP_V2_SPKR_1]; |
| protCfg.t0[SP_V2_SPKR_2] = handle.sp_r0t0_cal.t0[SP_V2_SPKR_2]; |
| break; |
| } |
| protCfg.mode = MSM_SPKR_PROT_CALIBRATED; |
| #ifdef MSM_SPKR_PROT_SPV3 |
| protCfg.sp_version = handle.sp_version; |
| protCfg.limiter_th[SP_V2_SPKR_1] = handle.limiter_th[SP_V2_SPKR_1]; |
| protCfg.limiter_th[SP_V2_SPKR_2] = handle.limiter_th[SP_V2_SPKR_2]; |
| #endif |
| ret = set_spkr_prot_cal(acdb_fd, &protCfg); |
| if (ret) |
| ALOGE("%s: speaker protection cal data swap failed", __func__); |
| |
| close(acdb_fd); |
| return ret; |
| } |
| |
| 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; |
| snd_device_t in_snd_device; |
| char device_name[DEVICE_NAME_MAX_SIZE] = {0}; |
| int app_type = 0; |
| |
| ALOGV("%s: Entry", __func__); |
| /* cancel speaker calibration */ |
| if (!adev) { |
| ALOGE("%s: Invalid params", __func__); |
| return -EINVAL; |
| } |
| snd_device = platform_get_spkr_prot_snd_device(snd_device); |
| if (handle.spkr_prot_mode == MSM_SPKR_PROT_CALIBRATED) { |
| ret = audio_extn_select_spkr_prot_cal_data(snd_device); |
| if (ret) { |
| ALOGE("%s: Setting speaker protection cal data failed", __func__); |
| return ret; |
| } |
| } |
| |
| in_snd_device = platform_get_vi_feedback_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; |
| } |
| |
| if (platform_get_snd_device_name_extn(adev->platform, snd_device, device_name) < 0) { |
| ALOGE("%s: Invalid sound device returned", __func__); |
| return -EINVAL; |
| } |
| ALOGD("%s: spkr snd_device(%d: %s)", __func__, snd_device, |
| device_name); |
| audio_route_apply_and_update_path(adev->audio_route, |
| device_name); |
| |
| 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 = in_snd_device; |
| 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, in_snd_device); |
| 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 */ |
| if (uc_info_tx != NULL) { |
| ALOGD("%s: UC Info TX is not NULL, updating and sending calibration", |
| __func__); |
| uc_info_tx->in_snd_device = SND_DEVICE_IN_HANDSET_MIC; |
| uc_info_tx->out_snd_device = SND_DEVICE_NONE; |
| app_type = platform_get_default_app_type_v2(adev->platform, |
| PCM_CAPTURE); |
| platform_send_audio_calibration(adev->platform, uc_info_tx, |
| app_type, 8000); |
| } |
| if (ret) { |
| if (handle.pcm_tx) |
| pcm_close(handle.pcm_tx); |
| handle.pcm_tx = NULL; |
| list_remove(&uc_info_tx->list); |
| uc_info_tx->id = USECASE_AUDIO_SPKR_CALIB_TX; |
| uc_info_tx->type = PCM_CAPTURE; |
| uc_info_tx->in_snd_device = in_snd_device; |
| uc_info_tx->out_snd_device = SND_DEVICE_NONE; |
| disable_snd_device(adev, in_snd_device); |
| 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; |
| snd_device_t in_snd_device; |
| |
| ALOGV("%s: Entry", __func__); |
| snd_device = platform_get_spkr_prot_snd_device(snd_device); |
| spkr_prot_set_spkrstatus(false); |
| in_snd_device = platform_get_vi_feedback_snd_device(snd_device); |
| |
| 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, in_snd_device); |
| 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; |
| } |
| |
| int audio_extn_get_spkr_prot_snd_device(snd_device_t snd_device) { |
| return snd_device; |
| } |
| #endif /*SPKR_PROT_ENABLED*/ |