966 lines
28 KiB
C
966 lines
28 KiB
C
/*
|
|
* Copyright (C) 2017 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#define LOG_TAG "audio_hw_cirrus_playback"
|
|
/*#define LOG_NDEBUG 0*/
|
|
|
|
#include <errno.h>
|
|
#include <math.h>
|
|
#include <log/log.h>
|
|
#include <fcntl.h>
|
|
#include "../audio_hw.h"
|
|
#include "platform.h"
|
|
#include "platform_api.h"
|
|
#include <sys/stat.h>
|
|
#include <linux/types.h>
|
|
#include <linux/ioctl.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <dlfcn.h>
|
|
#include <math.h>
|
|
#include <pthread.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <cutils/properties.h>
|
|
#include "audio_extn.h"
|
|
|
|
// - external function dependency -
|
|
static fp_platform_get_snd_device_name_t fp_platform_get_snd_device_name;
|
|
static fp_platform_get_pcm_device_id_t fp_platform_get_pcm_device_id;
|
|
static fp_get_usecase_from_list_t fp_get_usecase_from_list;
|
|
static fp_enable_disable_snd_device_t fp_disable_snd_device;
|
|
static fp_enable_disable_snd_device_t fp_enable_snd_device;
|
|
static fp_enable_disable_audio_route_t fp_disable_audio_route;
|
|
static fp_enable_disable_audio_route_t fp_enable_audio_route;
|
|
static fp_audio_extn_get_snd_card_split_t fp_audio_extn_get_snd_card_split;
|
|
|
|
struct cirrus_playback_session {
|
|
void *adev_handle;
|
|
pthread_mutex_t fb_prot_mutex;
|
|
pthread_t calibration_thread;
|
|
#ifdef ENABLE_CIRRUS_DETECTION
|
|
pthread_t failure_detect_thread;
|
|
#endif
|
|
struct pcm *pcm_rx;
|
|
struct pcm *pcm_tx;
|
|
volatile int32_t state;
|
|
};
|
|
|
|
enum cirrus_playback_state {
|
|
INIT = 0,
|
|
CALIBRATING = 1,
|
|
IDLE = 2,
|
|
PLAYBACK = 3
|
|
};
|
|
|
|
struct crus_sp_ioctl_header {
|
|
uint32_t size;
|
|
uint32_t module_id;
|
|
uint32_t param_id;
|
|
uint32_t data_length;
|
|
void *data;
|
|
};
|
|
|
|
/* Payload struct for getting calibration result from DSP module */
|
|
struct cirrus_cal_result_t {
|
|
int32_t status_l;
|
|
int32_t checksum_l;
|
|
int32_t z_l;
|
|
int32_t status_r;
|
|
int32_t checksum_r;
|
|
int32_t z_r;
|
|
};
|
|
|
|
/* Payload struct for setting the RX and TX use cases */
|
|
struct crus_rx_run_case_ctrl_t {
|
|
int32_t value;
|
|
int32_t status_l;
|
|
int32_t checksum_l;
|
|
int32_t z_l;
|
|
int32_t status_r;
|
|
int32_t checksum_r;
|
|
int32_t z_r;
|
|
};
|
|
|
|
#define CRUS_SP_FILE "/dev/msm_cirrus_playback"
|
|
#define CRUS_CAL_FILE "/persist/audio/audio.cal"
|
|
#define CRUS_TX_CONF_FILE "vendor/firmware/crus_sp_config_%s_tx.bin"
|
|
#define CRUS_RX_CONF_FILE "vendor/firmware/crus_sp_config_%s_rx.bin"
|
|
#define CONFIG_FILE_SIZE 128
|
|
|
|
#define CRUS_SP_USECASE_MIXER "Cirrus SP Usecase"
|
|
#define CRUS_SP_LOAD_CONF_MIXER "Cirrus SP Load Config"
|
|
#define CRUS_SP_FAIL_DET_MIXER "Cirrus SP Failure Detection"
|
|
|
|
#define CIRRUS_SP 0x10027053
|
|
|
|
#define CRUS_MODULE_ID_TX 0x00000002
|
|
#define CRUS_MODULE_ID_RX 0x00000001
|
|
|
|
#define CRUS_PARAM_RX_SET_USECASE 0x00A1AF02
|
|
#define CRUS_PARAM_TX_SET_USECASE 0x00A1BF0A
|
|
|
|
#define CRUS_PARAM_RX_SET_CALIB 0x00A1AF03
|
|
#define CRUS_PARAM_TX_SET_CALIB 0x00A1BF03
|
|
|
|
#define CRUS_PARAM_RX_SET_EXT_CONFIG 0x00A1AF05
|
|
#define CRUS_PARAM_TX_SET_EXT_CONFIG 0x00A1BF08
|
|
|
|
#define CRUS_PARAM_RX_GET_TEMP 0x00A1AF07
|
|
#define CRUS_PARAM_TX_GET_TEMP_CAL 0x00A1BF06
|
|
// variables based on CSPL tuning file, max parameter length is 96 integers (384 bytes)
|
|
#define CRUS_PARAM_TEMP_MAX_LENGTH 384
|
|
|
|
#define CRUS_AFE_PARAM_ID_ENABLE 0x00010203
|
|
|
|
#define FAIL_DETECT_INIT_WAIT_US 500000
|
|
#define FAIL_DETECT_LOOP_WAIT_US 300000
|
|
|
|
#define CRUS_DEFAULT_CAL_L 0x2A11
|
|
#define CRUS_DEFAULT_CAL_R 0x29CB
|
|
|
|
#define CRUS_SP_IOCTL_MAGIC 'a'
|
|
|
|
#define CRUS_SP_IOCTL_GET _IOWR(CRUS_SP_IOCTL_MAGIC, 219, void *)
|
|
#define CRUS_SP_IOCTL_SET _IOWR(CRUS_SP_IOCTL_MAGIC, 220, void *)
|
|
#define CRUS_SP_IOCTL_GET_CALIB _IOWR(CRUS_SP_IOCTL_MAGIC, 221, void *)
|
|
#define CRUS_SP_IOCTL_SET_CALIB _IOWR(CRUS_SP_IOCTL_MAGIC, 222, void *)
|
|
|
|
|
|
struct pcm_config pcm_config_cirrus_rx = {
|
|
.channels = 8,
|
|
.rate = 48000,
|
|
.period_size = 320,
|
|
.period_count = 4,
|
|
.format = PCM_FORMAT_S32_LE,
|
|
.start_threshold = 0,
|
|
.stop_threshold = INT_MAX,
|
|
.avail_min = 0,
|
|
};
|
|
|
|
struct pcm_config pcm_config_cirrus_tx = {
|
|
.channels = 2,
|
|
.rate = 48000,
|
|
.period_size = 320,
|
|
.period_count = 4,
|
|
.format = PCM_FORMAT_S16_LE,
|
|
.start_threshold = 0,
|
|
.stop_threshold = INT_MAX,
|
|
.avail_min = 0,
|
|
};
|
|
|
|
static struct cirrus_playback_session handle;
|
|
|
|
#ifdef CIRRUS_FACTORY_CALIBRATION
|
|
static void *audio_extn_cirrus_calibration_thread();
|
|
#else
|
|
static void *audio_extn_cirrus_config_thread();
|
|
#endif
|
|
|
|
#ifdef ENABLE_CIRRUS_DETECTION
|
|
static void *audio_extn_cirrus_failure_detect_thread();
|
|
#endif
|
|
|
|
void spkr_prot_init(void *adev, spkr_prot_init_config_t spkr_prot_init_config_val) {
|
|
ALOGI("%s: Initialize Cirrus Logic Playback module", __func__);
|
|
|
|
memset(&handle, 0, sizeof(handle));
|
|
if (!adev) {
|
|
ALOGE("%s: Invalid params", __func__);
|
|
return;
|
|
}
|
|
|
|
handle.adev_handle = adev;
|
|
handle.state = INIT;
|
|
|
|
// init function pointers
|
|
fp_platform_get_snd_device_name = spkr_prot_init_config_val.fp_platform_get_snd_device_name;
|
|
fp_platform_get_pcm_device_id = spkr_prot_init_config_val.fp_platform_get_pcm_device_id;
|
|
fp_get_usecase_from_list = spkr_prot_init_config_val.fp_get_usecase_from_list;
|
|
fp_disable_snd_device = spkr_prot_init_config_val.fp_disable_snd_device;
|
|
fp_enable_snd_device = spkr_prot_init_config_val.fp_enable_snd_device;
|
|
fp_disable_audio_route = spkr_prot_init_config_val.fp_disable_audio_route;
|
|
fp_enable_audio_route = spkr_prot_init_config_val.fp_enable_audio_route;
|
|
fp_audio_extn_get_snd_card_split = spkr_prot_init_config_val.fp_audio_extn_get_snd_card_split;
|
|
|
|
pthread_mutex_init(&handle.fb_prot_mutex, NULL);
|
|
|
|
#ifdef CIRRUS_FACTORY_CALIBRATION
|
|
(void)pthread_create(&handle.calibration_thread,
|
|
(const pthread_attr_t *) NULL,
|
|
audio_extn_cirrus_calibration_thread, &handle);
|
|
#else
|
|
(void)pthread_create(&handle.calibration_thread,
|
|
(const pthread_attr_t *) NULL,
|
|
audio_extn_cirrus_config_thread, &handle);
|
|
#endif
|
|
}
|
|
|
|
int spkr_prot_deinit() {
|
|
ALOGV("%s: Entry", __func__);
|
|
|
|
#ifdef ENABLE_CIRRUS_DETECTION
|
|
pthread_join(handle.failure_detect_thread, NULL);
|
|
#endif
|
|
pthread_join(handle.calibration_thread, NULL);
|
|
pthread_mutex_destroy(&handle.fb_prot_mutex);
|
|
|
|
ALOGV("%s: Exit", __func__);
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CIRRUS_FACTORY_CALIBRATION
|
|
static int audio_extn_cirrus_run_calibration() {
|
|
struct audio_device *adev = handle.adev_handle;
|
|
struct crus_sp_ioctl_header header;
|
|
struct cirrus_cal_result_t result;
|
|
struct mixer_ctl *ctl = NULL;
|
|
FILE *cal_file = NULL;
|
|
int ret = 0, dev_file = -1;
|
|
char *buffer = NULL;
|
|
uint32_t option = 1;
|
|
|
|
ALOGI("%s: Running speaker calibration", __func__);
|
|
|
|
dev_file = open(CRUS_SP_FILE, O_RDWR | O_NONBLOCK);
|
|
if (dev_file < 0) {
|
|
ALOGE("%s: Failed to open Cirrus Playback IOCTL (%d)",
|
|
__func__, dev_file);
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
buffer = calloc(1, CRUS_PARAM_TEMP_MAX_LENGTH);
|
|
if (!buffer) {
|
|
ALOGE("%s: allocate memory failed", __func__);
|
|
ret = -ENOMEM;
|
|
goto exit;
|
|
}
|
|
|
|
cal_file = fopen(CRUS_CAL_FILE, "r");
|
|
if (cal_file) {
|
|
ret = fread(&result, sizeof(result), 1, cal_file);
|
|
if (ret != 1) {
|
|
ALOGE("%s: Cirrus SP calibration file cannot be read , read size: %lu file error: %d",
|
|
__func__, (unsigned long)ret * sizeof(result), ferror(cal_file));
|
|
ret = -EINVAL;
|
|
fclose(cal_file);
|
|
goto exit;
|
|
}
|
|
|
|
fclose(cal_file);
|
|
} else {
|
|
|
|
ALOGV("%s: Calibrating...", __func__);
|
|
|
|
header.size = sizeof(header);
|
|
header.module_id = CRUS_MODULE_ID_RX;
|
|
header.param_id = CRUS_PARAM_RX_SET_CALIB;
|
|
header.data_length = sizeof(option);
|
|
header.data = &option;
|
|
|
|
ret = ioctl(dev_file, CRUS_SP_IOCTL_SET, &header);
|
|
if (ret < 0) {
|
|
ALOGE("%s: Cirrus SP calibration IOCTL failure (%d)",
|
|
__func__, ret);
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
header.size = sizeof(header);
|
|
header.module_id = CRUS_MODULE_ID_TX;
|
|
header.param_id = CRUS_PARAM_TX_SET_CALIB;
|
|
header.data_length = sizeof(option);
|
|
header.data = &option;
|
|
|
|
ret = ioctl(dev_file, CRUS_SP_IOCTL_SET, &header);
|
|
if (ret < 0) {
|
|
ALOGE("%s: Cirrus SP calibration IOCTL failure (%d)",
|
|
__func__, ret);
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
sleep(2);
|
|
|
|
header.size = sizeof(header);
|
|
header.module_id = CRUS_MODULE_ID_TX;
|
|
header.param_id = CRUS_PARAM_TX_GET_TEMP_CAL;
|
|
header.data_length = sizeof(result);
|
|
header.data = &result;
|
|
|
|
ret = ioctl(dev_file, CRUS_SP_IOCTL_GET, &header);
|
|
if (ret < 0) {
|
|
ALOGE("%s: Cirrus SP calibration IOCTL failure (%d)",
|
|
__func__, ret);
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
if (result.status_l != 1) {
|
|
ALOGE("%s: Left calibration failure. Please check speakers",
|
|
__func__);
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
if (result.status_r != 1) {
|
|
ALOGE("%s: Right calibration failure. Please check speakers",
|
|
__func__);
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
if (ret < 0)
|
|
goto exit;
|
|
|
|
cal_file = fopen(CRUS_CAL_FILE, "wb");
|
|
if (cal_file == NULL) {
|
|
ALOGE("%s: Cannot create Cirrus SP calibration file (%s)",
|
|
__func__, strerror(errno));
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
ret = fwrite(&result, sizeof(result), 1, cal_file);
|
|
|
|
if (ret != 1) {
|
|
ALOGE("%s: Unable to save Cirrus SP calibration data, write size %lu, file error %d",
|
|
__func__, (unsigned long)ret * sizeof(result), ferror(cal_file));
|
|
fclose(cal_file);
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
fclose(cal_file);
|
|
|
|
ALOGI("%s: Cirrus calibration file successfully written",
|
|
__func__);
|
|
}
|
|
|
|
header.size = sizeof(header);
|
|
header.module_id = CRUS_MODULE_ID_TX;
|
|
header.param_id = 0;
|
|
header.data_length = sizeof(result);
|
|
header.data = &result;
|
|
|
|
ret = ioctl(dev_file, CRUS_SP_IOCTL_SET_CALIB, &header);
|
|
|
|
if (ret < 0) {
|
|
ALOGE("%s: Cirrus SP calibration IOCTL failure (%d)", __func__, ret);
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
ctl = mixer_get_ctl_by_name(adev->mixer,
|
|
CRUS_SP_USECASE_MIXER);
|
|
if (!ctl) {
|
|
ALOGE("%s: Could not get ctl for mixer cmd - %s",
|
|
__func__, CRUS_SP_USECASE_MIXER);
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
ret = mixer_ctl_set_value(ctl, 0, 0); // Set RX external firmware config
|
|
if (ret < 0) {
|
|
ALOGE("%s: set default usecase failed", __func__);
|
|
goto exit;
|
|
}
|
|
|
|
sleep(1);
|
|
|
|
header.size = sizeof(header);
|
|
header.module_id = CRUS_MODULE_ID_RX;
|
|
header.param_id = CRUS_PARAM_RX_GET_TEMP;
|
|
header.data_length = CRUS_PARAM_TEMP_MAX_LENGTH;
|
|
header.data = buffer;
|
|
|
|
ret = ioctl(dev_file, CRUS_SP_IOCTL_GET, &header);
|
|
if (ret < 0) {
|
|
ALOGE("%s: Cirrus SP temperature IOCTL failure (%d)", __func__, ret);
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
ALOGI("%s: Cirrus SP successfully calibrated", __func__);
|
|
|
|
exit:
|
|
if (dev_file >= 0)
|
|
close(dev_file);
|
|
free(buffer);
|
|
ALOGV("%s: Exit", __func__);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int audio_extn_cirrus_load_usecase_configs(void) {
|
|
struct audio_device *adev = handle.adev_handle;
|
|
struct mixer_ctl *ctl_uc = NULL, *ctl_config = NULL;
|
|
char *filename = NULL;
|
|
int ret = 0, default_uc = 0;
|
|
struct snd_card_split *snd_split_handle = NULL;
|
|
|
|
snd_split_handle = fp_audio_extn_get_snd_card_split();
|
|
|
|
ALOGI("%s: Loading usecase tuning configs", __func__);
|
|
|
|
ctl_uc = mixer_get_ctl_by_name(adev->mixer, CRUS_SP_USECASE_MIXER);
|
|
ctl_config = mixer_get_ctl_by_name(adev->mixer,
|
|
CRUS_SP_LOAD_CONF_MIXER);
|
|
if (!ctl_uc || !ctl_config) {
|
|
ALOGE("%s: Could not get ctl for mixer commands", __func__);
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
filename = calloc(1 , CONFIG_FILE_SIZE);
|
|
if (!filename) {
|
|
ALOGE("%s: allocate memory failed", __func__);
|
|
ret = -ENOMEM;
|
|
goto exit;
|
|
}
|
|
|
|
default_uc = mixer_ctl_get_value(ctl_uc, 0);
|
|
|
|
ret = mixer_ctl_set_value(ctl_uc, 0, default_uc);
|
|
if (ret < 0) {
|
|
ALOGE("%s set uscase %d failed", __func__, default_uc);
|
|
goto exit;
|
|
}
|
|
|
|
/* Load TX Tuning Config (if available) */
|
|
snprintf(filename, CONFIG_FILE_SIZE, CRUS_TX_CONF_FILE, snd_split_handle->form_factor);
|
|
if (access(filename, R_OK) == 0) {
|
|
ret = mixer_ctl_set_value(ctl_config, 0, 2);
|
|
if (ret < 0) {
|
|
ALOGE("%s set tx config failed", __func__);
|
|
goto exit;
|
|
}
|
|
} else {
|
|
ALOGE("%s: Tuning file not found (%s)", __func__,
|
|
filename);
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
/* Load RX Tuning Config (if available) */
|
|
snprintf(filename, CONFIG_FILE_SIZE, CRUS_RX_CONF_FILE, snd_split_handle->form_factor);
|
|
if (access(filename, R_OK) == 0) {
|
|
ret = mixer_ctl_set_value(ctl_config, 0, 1);
|
|
if (ret < 0) {
|
|
ALOGE("%s set rx config failed", __func__);
|
|
goto exit;
|
|
}
|
|
} else {
|
|
ALOGE("%s: Tuning file not found (%s)", __func__,
|
|
filename);
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
ALOGI("%s: Cirrus SP loaded available usecase configs", __func__);
|
|
exit:
|
|
free(filename);
|
|
ALOGI("%s: Exit", __func__);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void *audio_extn_cirrus_calibration_thread() {
|
|
struct audio_device *adev = handle.adev_handle;
|
|
struct audio_usecase *uc_info_rx = NULL;
|
|
int ret = 0;
|
|
int32_t pcm_dev_rx_id, prev_state;
|
|
uint32_t retries = 5;
|
|
|
|
ALOGI("%s: PCM Stream thread", __func__);
|
|
|
|
while (!adev->platform && retries) {
|
|
sleep(1);
|
|
ALOGI("%s: Waiting...", __func__);
|
|
retries--;
|
|
}
|
|
|
|
prev_state = handle.state;
|
|
handle.state = CALIBRATING;
|
|
|
|
uc_info_rx = (struct audio_usecase *)calloc(1, sizeof(struct audio_usecase));
|
|
if (!uc_info_rx) {
|
|
ALOGE("%s: rx usecase can not be found", __func__);
|
|
goto exit;
|
|
}
|
|
pthread_mutex_lock(&adev->lock);
|
|
|
|
uc_info_rx->id = USECASE_AUDIO_PLAYBACK_DEEP_BUFFER;
|
|
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;
|
|
list_init(&uc_info_rx->device_list);
|
|
list_add_tail(&adev->usecase_list, &uc_info_rx->list);
|
|
|
|
fp_enable_snd_device(adev, SND_DEVICE_OUT_SPEAKER);
|
|
fp_enable_audio_route(adev, uc_info_rx);
|
|
pcm_dev_rx_id = fp_platform_get_pcm_device_id(uc_info_rx->id, PCM_PLAYBACK);
|
|
|
|
if (pcm_dev_rx_id < 0) {
|
|
ALOGE("%s: Invalid pcm device for usecase (%d)",
|
|
__func__, uc_info_rx->id);
|
|
pthread_mutex_unlock(&adev->lock);
|
|
goto exit;
|
|
}
|
|
|
|
handle.pcm_rx = pcm_open(adev->snd_card, pcm_dev_rx_id,
|
|
PCM_OUT, &pcm_config_cirrus_rx);
|
|
|
|
if (handle.pcm_rx && !pcm_is_ready(handle.pcm_rx)) {
|
|
ALOGE("%s: PCM device not ready: %s", __func__,
|
|
pcm_get_error(handle.pcm_rx));
|
|
pthread_mutex_unlock(&adev->lock);
|
|
goto close_stream;
|
|
}
|
|
|
|
if (pcm_start(handle.pcm_rx) < 0) {
|
|
ALOGE("%s: pcm start for RX failed; error = %s", __func__,
|
|
pcm_get_error(handle.pcm_rx));
|
|
pthread_mutex_unlock(&adev->lock);
|
|
goto close_stream;
|
|
}
|
|
pthread_mutex_unlock(&adev->lock);
|
|
ALOGI("%s: PCM thread streaming", __func__);
|
|
|
|
ret = audio_extn_cirrus_run_calibration();
|
|
ALOGE_IF(ret < 0, "%s: Calibration procedure failed (%d)", __func__, ret);
|
|
|
|
ret = audio_extn_cirrus_load_usecase_configs();
|
|
ALOGE_IF(ret < 0, "%s: Set tuning configs failed (%d)", __func__, ret);
|
|
|
|
close_stream:
|
|
pthread_mutex_lock(&adev->lock);
|
|
if (handle.pcm_rx) {
|
|
ALOGI("%s: pcm_rx_close", __func__);
|
|
pcm_close(handle.pcm_rx);
|
|
handle.pcm_rx = NULL;
|
|
}
|
|
|
|
fp_disable_audio_route(adev, uc_info_rx);
|
|
fp_disable_snd_device(adev, SND_DEVICE_OUT_SPEAKER);
|
|
list_remove(&uc_info_rx->list);
|
|
free(uc_info_rx);
|
|
pthread_mutex_unlock(&adev->lock);
|
|
exit:
|
|
handle.state = (prev_state == PLAYBACK) ? PLAYBACK : IDLE;
|
|
|
|
#ifdef ENABLE_CIRRUS_DETECTION
|
|
if (handle.state == PLAYBACK)
|
|
(void)pthread_create(&handle.failure_detect_thread,
|
|
(const pthread_attr_t *) NULL,
|
|
audio_extn_cirrus_failure_detect_thread,
|
|
&handle);
|
|
#endif
|
|
|
|
ALOGV("%s: Exit", __func__);
|
|
|
|
pthread_exit(0);
|
|
return NULL;
|
|
}
|
|
|
|
#else
|
|
static void *audio_extn_cirrus_config_thread(void) {
|
|
struct audio_device *adev = handle.adev_handle;
|
|
struct crus_sp_ioctl_header header;
|
|
struct cirrus_cal_result_t result;
|
|
struct mixer_ctl *ctl_config = NULL;
|
|
FILE *cal_file = NULL;
|
|
int ret = 0, dev_file = -1;
|
|
|
|
ALOGI("%s: ++", __func__);
|
|
|
|
memset(&result, 0, sizeof(result));
|
|
|
|
dev_file = open(CRUS_SP_FILE, O_RDWR | O_NONBLOCK);
|
|
if (dev_file < 0) {
|
|
ALOGE("%s: Failed to open Cirrus Playback IOCTL (%d)",
|
|
__func__, dev_file);
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
cal_file = fopen(CRUS_CAL_FILE, "r");
|
|
if (cal_file) {
|
|
ret = fread(&result, sizeof(result), 1, cal_file);
|
|
|
|
if (ret != 1) {
|
|
ALOGE("%s: Cirrus SP calibration file cannot be read , read size: %lu file error: %d",
|
|
__func__, (unsigned long)ret * sizeof(result), ferror(cal_file));
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
header.size = sizeof(header);
|
|
header.module_id = CRUS_MODULE_ID_TX;
|
|
header.param_id = 0;
|
|
header.data_length = sizeof(result);
|
|
header.data = &result;
|
|
|
|
ret = ioctl(dev_file, CRUS_SP_IOCTL_SET_CALIB, &header);
|
|
|
|
if (ret < 0) {
|
|
ALOGE("%s: Cirrus SP calibration IOCTL failure", __func__);
|
|
goto exit;
|
|
}
|
|
|
|
ctl_config = mixer_get_ctl_by_name(adev->mixer,
|
|
CRUS_SP_LOAD_CONF_MIXER);
|
|
if (!ctl_config) {
|
|
ALOGE("%s: Could not get ctl for mixer commands", __func__);
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
ret = mixer_ctl_set_value(ctl_config, 0, 2);
|
|
if (ret < 0) {
|
|
ALOGE("%s load tx config failed", __func__);
|
|
goto exit;
|
|
}
|
|
|
|
ret = mixer_ctl_set_value(ctl_config, 0, 1);
|
|
if (ret < 0) {
|
|
ALOGE("%s load rx config failed", __func__);
|
|
goto exit;
|
|
}
|
|
|
|
ret = mixer_ctl_set_value(ctl_config, 0, 0);
|
|
if (ret < 0) {
|
|
ALOGE("%s set idle state failed", __func__);
|
|
goto exit;
|
|
}
|
|
|
|
exit:
|
|
if (dev_file >= 0)
|
|
close(dev_file);
|
|
if (cal_file)
|
|
fclose(cal_file);
|
|
|
|
ALOGI("%s: ret: %d --", __func__, ret);
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
#ifdef ENABLE_CIRRUS_DETECTION
|
|
void *audio_extn_cirrus_failure_detect_thread() {
|
|
struct audio_device *adev = handle.adev_handle;
|
|
struct crus_sp_ioctl_header header;
|
|
struct mixer_ctl *ctl = NULL;
|
|
const int32_t r_scale_factor = 100000000;
|
|
const int32_t t_scale_factor = 100000;
|
|
const int32_t r_err_range = 70000000;
|
|
const int32_t t_err_range = 210000;
|
|
const int32_t amp_factor = 71498;
|
|
const int32_t material = 250;
|
|
int32_t *buffer = NULL;
|
|
int ret = 0, dev_file = -1, out_cal0 = 0, out_cal1 = 0;
|
|
int rL = 0, rR = 0, zL = 0, zR = 0, tL = 0, tR = 0;
|
|
int rdL = 0, rdR = 0, tdL = 0, tdR = 0, ambL = 0, ambR = 0;
|
|
bool left_cal_done = false, right_cal_done = false;
|
|
bool det_en = false;
|
|
|
|
ALOGI("%s: Entry", __func__);
|
|
|
|
ctl = mixer_get_ctl_by_name(adev->mixer, CRUS_SP_FAIL_DET_MIXER);
|
|
det_en = mixer_ctl_get_value(ctl, 0);
|
|
|
|
if (!det_en)
|
|
goto exit;
|
|
|
|
dev_file = open(CRUS_SP_FILE, O_RDWR | O_NONBLOCK);
|
|
if (dev_file < 0) {
|
|
ALOGE("%s: Failed to open Cirrus Playback IOCTL (%d)",
|
|
__func__, dev_file);
|
|
goto exit;
|
|
}
|
|
|
|
buffer = calloc(1, CRUS_PARAM_TEMP_MAX_LENGTH);
|
|
if (!buffer) {
|
|
ALOGE("%s: allocate memory failed", __func__);
|
|
goto exit;
|
|
}
|
|
|
|
header.size = sizeof(header);
|
|
header.module_id = CRUS_MODULE_ID_RX;
|
|
header.param_id = CRUS_PARAM_RX_GET_TEMP;
|
|
header.data_length = CRUS_PARAM_TEMP_MAX_LENGTH;
|
|
header.data = buffer;
|
|
|
|
usleep(FAIL_DETECT_INIT_WAIT_US);
|
|
|
|
pthread_mutex_lock(&handle.fb_prot_mutex);
|
|
ret = ioctl(dev_file, CRUS_SP_IOCTL_GET, &header);
|
|
pthread_mutex_unlock(&handle.fb_prot_mutex);
|
|
if (ret < 0) {
|
|
ALOGE("%s: Cirrus SP IOCTL failure (%d)",
|
|
__func__, ret);
|
|
goto exit;
|
|
}
|
|
|
|
zL = buffer[2] * amp_factor;
|
|
zR = buffer[4] * amp_factor;
|
|
|
|
ambL = buffer[10];
|
|
ambR = buffer[6];
|
|
|
|
out_cal0 = buffer[12];
|
|
out_cal1 = buffer[13];
|
|
|
|
left_cal_done = (out_cal0 == 2) && (out_cal1 == 2) &&
|
|
(buffer[2] != CRUS_DEFAULT_CAL_L);
|
|
|
|
out_cal0 = buffer[14];
|
|
out_cal1 = buffer[15];
|
|
|
|
right_cal_done = (out_cal0 == 2) && (out_cal1 == 2) &&
|
|
(buffer[4] != CRUS_DEFAULT_CAL_R);
|
|
|
|
if (left_cal_done) {
|
|
ALOGI("%s: L Speaker Impedance: %d.%08d ohms", __func__,
|
|
zL / r_scale_factor, abs(zL) % r_scale_factor);
|
|
ALOGI("%s: L Calibration Temperature: %d C", __func__, ambL);
|
|
} else
|
|
ALOGE("%s: Left speaker uncalibrated", __func__);
|
|
|
|
if (right_cal_done) {
|
|
ALOGI("%s: R Speaker Impedance: %d.%08d ohms", __func__,
|
|
zR / r_scale_factor, abs(zR) % r_scale_factor);
|
|
ALOGI("%s: R Calibration Temperature: %d C", __func__, ambR);
|
|
} else
|
|
ALOGE("%s: Right speaker uncalibrated", __func__);
|
|
|
|
if (!left_cal_done && !right_cal_done)
|
|
goto exit;
|
|
|
|
ALOGI("%s: Monitoring speaker impedance & temperature...", __func__);
|
|
|
|
while ((handle.state == PLAYBACK) && det_en) {
|
|
pthread_mutex_lock(&handle.fb_prot_mutex);
|
|
ret = ioctl(dev_file, CRUS_SP_IOCTL_GET, &header);
|
|
pthread_mutex_unlock(&handle.fb_prot_mutex);
|
|
if (ret < 0) {
|
|
ALOGE("%s: Cirrus SP IOCTL failure (%d)",
|
|
__func__, ret);
|
|
goto loop;
|
|
}
|
|
|
|
rL = buffer[3];
|
|
rR = buffer[1];
|
|
|
|
zL = buffer[2];
|
|
zR = buffer[4];
|
|
|
|
if ((zL == 0) || (zR == 0))
|
|
goto loop;
|
|
|
|
tdL = (material * t_scale_factor * (rL-zL) / zL);
|
|
tdR = (material * t_scale_factor * (rR-zR) / zR);
|
|
|
|
rL *= amp_factor;
|
|
rR *= amp_factor;
|
|
|
|
zL *= amp_factor;
|
|
zR *= amp_factor;
|
|
|
|
tL = tdL + (ambL * t_scale_factor);
|
|
tR = tdR + (ambR * t_scale_factor);
|
|
|
|
rdL = abs(zL - rL);
|
|
rdR = abs(zR - rR);
|
|
|
|
if (left_cal_done && (rL != 0) && (rdL > r_err_range))
|
|
ALOGI("%s: Left speaker impedance out of range (%d.%08d ohms)",
|
|
__func__, rL / r_scale_factor,
|
|
abs(rL % r_scale_factor));
|
|
|
|
if (right_cal_done && (rR != 0) && (rdR > r_err_range))
|
|
ALOGI("%s: Right speaker impedance out of range (%d.%08d ohms)",
|
|
__func__, rR / r_scale_factor,
|
|
abs(rR % r_scale_factor));
|
|
|
|
if (left_cal_done && (rL != 0) && (tdL > t_err_range))
|
|
ALOGI("%s: Left speaker temperature out of range (%d.%05d C)",
|
|
__func__, tL / t_scale_factor,
|
|
abs(tL % t_scale_factor));
|
|
|
|
if (right_cal_done && (rR != 0) && (tdR > t_err_range))
|
|
ALOGI("%s: Right speaker temperature out of range (%d.%05d C)",
|
|
__func__, tR / t_scale_factor,
|
|
abs(tR % t_scale_factor));
|
|
|
|
loop:
|
|
det_en = mixer_ctl_get_value(ctl, 0);
|
|
usleep(FAIL_DETECT_LOOP_WAIT_US);
|
|
}
|
|
|
|
exit:
|
|
if (dev_file >= 0)
|
|
close(dev_file);
|
|
free(buffer);
|
|
ALOGI("%s: Exit ", __func__);
|
|
|
|
pthread_exit(0);
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
int spkr_prot_start_processing(snd_device_t snd_device) {
|
|
struct audio_usecase *uc_info_tx;
|
|
struct audio_device *adev = handle.adev_handle;
|
|
int32_t pcm_dev_tx_id = -1, ret = 0;
|
|
|
|
ALOGV("%s: Entry", __func__);
|
|
|
|
if (!adev) {
|
|
ALOGE("%s: Invalid params", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
uc_info_tx = (struct audio_usecase *)calloc(1, sizeof(*uc_info_tx));
|
|
if (!uc_info_tx) {
|
|
ALOGE("%s: allocate memory failed", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
audio_route_apply_and_update_path(adev->audio_route,
|
|
fp_platform_get_snd_device_name(snd_device));
|
|
|
|
pthread_mutex_lock(&handle.fb_prot_mutex);
|
|
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;
|
|
list_init(&uc_info_tx->device_list);
|
|
handle.pcm_tx = NULL;
|
|
|
|
list_add_tail(&adev->usecase_list, &uc_info_tx->list);
|
|
|
|
fp_enable_snd_device(adev, SND_DEVICE_IN_CAPTURE_VI_FEEDBACK);
|
|
fp_enable_audio_route(adev, uc_info_tx);
|
|
|
|
pcm_dev_tx_id = fp_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_cirrus_tx);
|
|
|
|
if (handle.pcm_tx && !pcm_is_ready(handle.pcm_tx)) {
|
|
ALOGE("%s: PCM device not ready: %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; error = %s", __func__,
|
|
pcm_get_error(handle.pcm_tx));
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
#ifdef ENABLE_CIRRUS_DETECTION
|
|
if (handle.state == IDLE)
|
|
(void)pthread_create(&handle.failure_detect_thread,
|
|
(const pthread_attr_t *) NULL,
|
|
audio_extn_cirrus_failure_detect_thread,
|
|
&handle);
|
|
#endif
|
|
|
|
handle.state = PLAYBACK;
|
|
exit:
|
|
if (ret) {
|
|
handle.state = IDLE;
|
|
if (handle.pcm_tx) {
|
|
ALOGI("%s: pcm_tx_close", __func__);
|
|
pcm_close(handle.pcm_tx);
|
|
handle.pcm_tx = NULL;
|
|
}
|
|
|
|
fp_disable_audio_route(adev, uc_info_tx);
|
|
fp_disable_snd_device(adev, SND_DEVICE_IN_CAPTURE_VI_FEEDBACK);
|
|
list_remove(&uc_info_tx->list);
|
|
free(uc_info_tx);
|
|
}
|
|
|
|
pthread_mutex_unlock(&handle.fb_prot_mutex);
|
|
ALOGV("%s: Exit", __func__);
|
|
return ret;
|
|
}
|
|
|
|
void 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__);
|
|
|
|
pthread_mutex_lock(&handle.fb_prot_mutex);
|
|
|
|
handle.state = IDLE;
|
|
uc_info_tx = fp_get_usecase_from_list(adev, USECASE_AUDIO_SPKR_CALIB_TX);
|
|
|
|
if (uc_info_tx) {
|
|
if (handle.pcm_tx) {
|
|
ALOGI("%s: pcm_tx_close", __func__);
|
|
pcm_close(handle.pcm_tx);
|
|
handle.pcm_tx = NULL;
|
|
}
|
|
|
|
fp_disable_audio_route(adev, uc_info_tx);
|
|
fp_disable_snd_device(adev, SND_DEVICE_IN_CAPTURE_VI_FEEDBACK);
|
|
list_remove(&uc_info_tx->list);
|
|
free(uc_info_tx);
|
|
|
|
audio_route_reset_path(adev->audio_route,
|
|
fp_platform_get_snd_device_name(snd_device));
|
|
}
|
|
|
|
pthread_mutex_unlock(&handle.fb_prot_mutex);
|
|
|
|
ALOGV("%s: Exit", __func__);
|
|
}
|
|
|
|
bool spkr_prot_is_enabled() {
|
|
return true;
|
|
}
|
|
|
|
int get_spkr_prot_snd_device(snd_device_t snd_device) {
|
|
switch(snd_device) {
|
|
case SND_DEVICE_OUT_SPEAKER:
|
|
case SND_DEVICE_OUT_SPEAKER_REVERSE:
|
|
return SND_DEVICE_OUT_SPEAKER_PROTECTED;
|
|
case SND_DEVICE_OUT_SPEAKER_SAFE:
|
|
return SND_DEVICE_OUT_SPEAKER_SAFE;
|
|
case SND_DEVICE_OUT_VOICE_SPEAKER:
|
|
return SND_DEVICE_OUT_VOICE_SPEAKER_PROTECTED;
|
|
default:
|
|
return snd_device;
|
|
}
|
|
}
|
|
|
|
void spkr_prot_calib_cancel(__unused void *adev) {
|
|
// FIXME: wait or cancel audio_extn_cirrus_run_calibration
|
|
}
|