Loading hal/audio_extn/cirrus_playback.c +376 −107 Original line number Diff line number Diff line Loading @@ -40,10 +40,20 @@ struct cirrus_playback_session { void *adev_handle; pthread_mutex_t fb_prot_mutex; pthread_mutex_t calibration_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 { Loading Loading @@ -77,9 +87,13 @@ struct crus_rx_run_case_ctrl_t { #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 Config" #define CRUS_SP_EXT_CONFIG_MIXER "Cirrus SP EXT Config" #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 Loading @@ -102,6 +116,12 @@ struct crus_rx_run_case_ctrl_t { #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 *) Loading @@ -109,7 +129,7 @@ struct crus_rx_run_case_ctrl_t { #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 *) #define CRUS_SP_DEFAULT_AMBIENT_TEMP 23 static struct pcm_config pcm_config_cirrus_tx = { .channels = 2, Loading @@ -135,19 +155,42 @@ static struct pcm_config pcm_config_cirrus_rx = { static struct cirrus_playback_session handle; static void *audio_extn_cirrus_calibration_thread(); #ifdef ENABLE_CIRRUS_DETECTION static void *audio_extn_cirrus_failure_detect_thread(); #endif void audio_extn_spkr_prot_init(void *adev) { 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; pthread_mutex_init(&handle.fb_prot_mutex, NULL); (void)pthread_create(&handle.calibration_thread, (const pthread_attr_t *) NULL, audio_extn_cirrus_calibration_thread, &handle); } 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; FILE *cal_file; int ret = 0, dev_file; struct mixer_ctl *ctl = NULL; FILE *cal_file = NULL; int ret = 0, dev_file = -1; char *buffer = NULL; uint32_t option = 1; char value[PROPERTY_VALUE_MAX]; bool store_calib_data = false; ALOGI("%s: Calibration thread", __func__); ALOGI("%s: Running speaker calibration", __func__); dev_file = open(CRUS_SP_FILE, O_RDWR | O_NONBLOCK); if (dev_file < 0) { Loading @@ -159,17 +202,17 @@ static int audio_extn_cirrus_run_calibration() { buffer = calloc(1, CRUS_PARAM_TEMP_MAX_LENGTH); if (!buffer) { ret = -EINVAL; ALOGE("%s: allocate memory failed", __func__); ret = -ENOMEM; goto exit; } cal_file = fopen(CRUS_CAL_FILE, "r"); if (cal_file) { size_t bytes; bytes = fread(&result, 1, sizeof(result), cal_file); if (bytes < sizeof(result)) { ALOGE("%s: Cirrus SP calibration file cannot be read (%d)", __func__, ret); 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; Loading Loading @@ -224,19 +267,34 @@ static int audio_extn_cirrus_run_calibration() { goto exit; } if(store_calib_data) { 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) { if (cal_file == NULL) { ALOGE("%s: Cannot create Cirrus SP calibration file (%s)", __func__, strerror(errno)); ret = -EINVAL; goto exit; } ret = fwrite(&result, 1, sizeof(result), cal_file); if (ret < 0) { ALOGE("%s: Unable to save Cirrus SP calibration data (%d)", __func__, ret); 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; Loading @@ -247,7 +305,6 @@ static int audio_extn_cirrus_run_calibration() { ALOGI("%s: Cirrus calibration file successfully written", __func__); } } header.size = sizeof(header); header.module_id = CRUS_MODULE_ID_TX; Loading @@ -259,7 +316,6 @@ static int audio_extn_cirrus_run_calibration() { if (ret < 0) { ALOGE("%s: Cirrus SP calibration IOCTL failure (%d)", __func__, ret); close(dev_file); ret = -EINVAL; goto exit; } Loading @@ -273,14 +329,18 @@ static int audio_extn_cirrus_run_calibration() { goto exit; } mixer_ctl_set_value(ctl, 0, 0); // Set RX external firmware config 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 = sizeof(buffer); header.data_length = CRUS_PARAM_TEMP_MAX_LENGTH; header.data = buffer; ret = ioctl(dev_file, CRUS_SP_IOCTL_GET, &header); Loading @@ -293,6 +353,7 @@ static int audio_extn_cirrus_run_calibration() { ALOGI("%s: Cirrus SP successfully calibrated", __func__); exit: if (dev_file >= 0) close(dev_file); free(buffer); ALOGV("%s: Exit", __func__); Loading @@ -300,40 +361,84 @@ exit: return ret; } static int audio_extn_cirrus_set_default_tuning() { static int audio_extn_cirrus_load_usecase_configs(void) { struct audio_device *adev = handle.adev_handle; struct mixer_ctl *ctl; int ret = 0; 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 = audio_extn_get_snd_card_split(); ALOGI("%s: Setting default tuning config", __func__); ALOGI("%s: Loading usecase tuning configs", __func__); ctl = mixer_get_ctl_by_name(adev->mixer, CRUS_SP_EXT_CONFIG_MIXER); if (!ctl) { ALOGE("%s: Could not get ctl for mixer cmd - %s", __func__, CRUS_SP_EXT_CONFIG_MIXER); 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; } mixer_ctl_set_value(ctl, 0, 1); // Set TX external firmware config mixer_ctl_set_value(ctl, 0, 0); // Set RX external firmware config 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; } void *audio_extn_cirrus_calibration_thread() { 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 = 0; uint32_t rx_use_case = USECASE_AUDIO_PLAYBACK_DEEP_BUFFER; int32_t pcm_dev_rx_id, prev_state; uint32_t retries = 5; pthread_mutex_lock(&handle.calibration_mutex); ALOGI("%s: PCM Stream thread", __func__); while (!adev->platform && retries) { Loading @@ -342,13 +447,17 @@ void *audio_extn_cirrus_calibration_thread() retries--; } uc_info_rx = (struct audio_usecase *)calloc(1, sizeof(*uc_info_rx)); prev_state = handle.state; handle.state = CALIBRATING; uc_info_rx = (struct audio_usecase *)calloc(1, sizeof(struct audio_usecase)); if (!uc_info_rx) { ret = -EINVAL; ALOGE("%s: rx usecase can not be found", __func__); goto exit; } pthread_mutex_lock(&adev->lock); uc_info_rx->id = rx_use_case; 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; Loading @@ -357,80 +466,232 @@ void *audio_extn_cirrus_calibration_thread() enable_snd_device(adev, SND_DEVICE_OUT_SPEAKER); enable_audio_route(adev, uc_info_rx); pcm_dev_rx_id = 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)) { if (handle.pcm_rx && !pcm_is_ready(handle.pcm_rx)) { ALOGE("%s: PCM device not ready: %s", __func__, pcm_get_error(handle.pcm_rx ? handle.pcm_rx : 0)); ret = -EINVAL; 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)); ret = -EINVAL; pthread_mutex_unlock(&adev->lock); goto close_stream; } ALOGV("%s: PCM thread streaming", __func__); pthread_mutex_unlock(&adev->lock); ALOGI("%s: PCM thread streaming", __func__); ret = audio_extn_cirrus_run_calibration(); if (ret < 0) { ALOGE("%s: Calibration procedure failed (%d)", __func__, ret); } ALOGE_IF(ret < 0, "%s: Calibration procedure failed (%d)", __func__, ret); ret = audio_extn_cirrus_set_default_tuning(); if (ret < 0) { ALOGE("%s: Set tuning configs 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) { ALOGV("%s: pcm_rx_close", __func__); ALOGI("%s: pcm_rx_close", __func__); pcm_close(handle.pcm_rx); handle.pcm_rx = NULL; } disable_audio_route(adev, uc_info_rx); disable_snd_device(adev, SND_DEVICE_OUT_SPEAKER); list_remove(&uc_info_rx->list); free(uc_info_rx); pthread_mutex_unlock(&adev->lock); exit: pthread_mutex_unlock(&handle.calibration_mutex); 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; } void audio_extn_spkr_prot_init(void *adev) { ALOGI("%s: Initialize Cirrus Logic Playback module", __func__); #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; struct snd_card_split *snd_split_handle = NULL; 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; } if (!adev) { ALOGE("%s: Invalid params", __func__); return; buffer = calloc(1, CRUS_PARAM_TEMP_MAX_LENGTH); if (!buffer) { ALOGE("%s: allocate memory failed", __func__); goto exit; } memset(&handle, 0, sizeof(handle)); 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; snd_split_handle = audio_extn_get_snd_card_split(); usleep(FAIL_DETECT_INIT_WAIT_US); handle.adev_handle = adev; 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; } pthread_mutex_init(&handle.fb_prot_mutex, NULL); pthread_mutex_init(&handle.calibration_mutex, NULL); zL = buffer[2] * amp_factor; zR = buffer[4] * amp_factor; (void)pthread_create(&handle.calibration_thread, (const pthread_attr_t *) NULL, audio_extn_cirrus_calibration_thread, &handle); 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 audio_extn_spkr_prot_start_processing(snd_device_t snd_device) { struct audio_usecase *uc_info_tx; struct audio_device *adev = handle.adev_handle; Loading @@ -445,6 +706,7 @@ int audio_extn_spkr_prot_start_processing(snd_device_t snd_device) { uc_info_tx = (struct audio_usecase *)calloc(1, sizeof(*uc_info_tx)); if (!uc_info_tx) { ALOGE("%s: allocate memory failed", __func__); return -ENOMEM; } Loading Loading @@ -476,26 +738,31 @@ int audio_extn_spkr_prot_start_processing(snd_device_t snd_device) { pcm_dev_tx_id, PCM_IN, &pcm_config_cirrus_tx); if (!handle.pcm_tx || !pcm_is_ready(handle.pcm_tx)) { ALOGD("%s: PCM device not ready: %s", __func__, pcm_get_error(handle.pcm_tx ? handle.pcm_tx : 0)); 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) { ALOGI("%s: retrying pcm_start...", __func__); usleep(500 * 1000); if (pcm_start(handle.pcm_tx) < 0) { ALOGI("%s: pcm start for TX failed; error = %s", __func__, 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) { ALOGI("%s: Disable and bail out", __func__); handle.state = IDLE; if (handle.pcm_tx) { ALOGI("%s: pcm_tx_close", __func__); pcm_close(handle.pcm_tx); Loading @@ -521,6 +788,7 @@ void audio_extn_spkr_prot_stop_processing(snd_device_t snd_device) { pthread_mutex_lock(&handle.fb_prot_mutex); handle.state = IDLE; uc_info_tx = get_usecase_from_list(adev, USECASE_AUDIO_SPKR_CALIB_TX); if (uc_info_tx) { Loading Loading @@ -551,6 +819,7 @@ bool audio_extn_spkr_prot_is_enabled() { int audio_extn_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; Loading hal/audio_hw.c +1 −0 Original line number Diff line number Diff line Loading @@ -662,6 +662,7 @@ int enable_snd_device(struct audio_device *adev, if ((snd_device == SND_DEVICE_OUT_SPEAKER || snd_device == SND_DEVICE_OUT_SPEAKER_SAFE || snd_device == SND_DEVICE_OUT_SPEAKER_REVERSE || snd_device == SND_DEVICE_OUT_VOICE_SPEAKER) && audio_extn_spkr_prot_is_enabled()) { if (platform_get_snd_device_acdb_id(snd_device) < 0) { Loading Loading
hal/audio_extn/cirrus_playback.c +376 −107 Original line number Diff line number Diff line Loading @@ -40,10 +40,20 @@ struct cirrus_playback_session { void *adev_handle; pthread_mutex_t fb_prot_mutex; pthread_mutex_t calibration_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 { Loading Loading @@ -77,9 +87,13 @@ struct crus_rx_run_case_ctrl_t { #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 Config" #define CRUS_SP_EXT_CONFIG_MIXER "Cirrus SP EXT Config" #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 Loading @@ -102,6 +116,12 @@ struct crus_rx_run_case_ctrl_t { #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 *) Loading @@ -109,7 +129,7 @@ struct crus_rx_run_case_ctrl_t { #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 *) #define CRUS_SP_DEFAULT_AMBIENT_TEMP 23 static struct pcm_config pcm_config_cirrus_tx = { .channels = 2, Loading @@ -135,19 +155,42 @@ static struct pcm_config pcm_config_cirrus_rx = { static struct cirrus_playback_session handle; static void *audio_extn_cirrus_calibration_thread(); #ifdef ENABLE_CIRRUS_DETECTION static void *audio_extn_cirrus_failure_detect_thread(); #endif void audio_extn_spkr_prot_init(void *adev) { 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; pthread_mutex_init(&handle.fb_prot_mutex, NULL); (void)pthread_create(&handle.calibration_thread, (const pthread_attr_t *) NULL, audio_extn_cirrus_calibration_thread, &handle); } 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; FILE *cal_file; int ret = 0, dev_file; struct mixer_ctl *ctl = NULL; FILE *cal_file = NULL; int ret = 0, dev_file = -1; char *buffer = NULL; uint32_t option = 1; char value[PROPERTY_VALUE_MAX]; bool store_calib_data = false; ALOGI("%s: Calibration thread", __func__); ALOGI("%s: Running speaker calibration", __func__); dev_file = open(CRUS_SP_FILE, O_RDWR | O_NONBLOCK); if (dev_file < 0) { Loading @@ -159,17 +202,17 @@ static int audio_extn_cirrus_run_calibration() { buffer = calloc(1, CRUS_PARAM_TEMP_MAX_LENGTH); if (!buffer) { ret = -EINVAL; ALOGE("%s: allocate memory failed", __func__); ret = -ENOMEM; goto exit; } cal_file = fopen(CRUS_CAL_FILE, "r"); if (cal_file) { size_t bytes; bytes = fread(&result, 1, sizeof(result), cal_file); if (bytes < sizeof(result)) { ALOGE("%s: Cirrus SP calibration file cannot be read (%d)", __func__, ret); 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; Loading Loading @@ -224,19 +267,34 @@ static int audio_extn_cirrus_run_calibration() { goto exit; } if(store_calib_data) { 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) { if (cal_file == NULL) { ALOGE("%s: Cannot create Cirrus SP calibration file (%s)", __func__, strerror(errno)); ret = -EINVAL; goto exit; } ret = fwrite(&result, 1, sizeof(result), cal_file); if (ret < 0) { ALOGE("%s: Unable to save Cirrus SP calibration data (%d)", __func__, ret); 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; Loading @@ -247,7 +305,6 @@ static int audio_extn_cirrus_run_calibration() { ALOGI("%s: Cirrus calibration file successfully written", __func__); } } header.size = sizeof(header); header.module_id = CRUS_MODULE_ID_TX; Loading @@ -259,7 +316,6 @@ static int audio_extn_cirrus_run_calibration() { if (ret < 0) { ALOGE("%s: Cirrus SP calibration IOCTL failure (%d)", __func__, ret); close(dev_file); ret = -EINVAL; goto exit; } Loading @@ -273,14 +329,18 @@ static int audio_extn_cirrus_run_calibration() { goto exit; } mixer_ctl_set_value(ctl, 0, 0); // Set RX external firmware config 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 = sizeof(buffer); header.data_length = CRUS_PARAM_TEMP_MAX_LENGTH; header.data = buffer; ret = ioctl(dev_file, CRUS_SP_IOCTL_GET, &header); Loading @@ -293,6 +353,7 @@ static int audio_extn_cirrus_run_calibration() { ALOGI("%s: Cirrus SP successfully calibrated", __func__); exit: if (dev_file >= 0) close(dev_file); free(buffer); ALOGV("%s: Exit", __func__); Loading @@ -300,40 +361,84 @@ exit: return ret; } static int audio_extn_cirrus_set_default_tuning() { static int audio_extn_cirrus_load_usecase_configs(void) { struct audio_device *adev = handle.adev_handle; struct mixer_ctl *ctl; int ret = 0; 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 = audio_extn_get_snd_card_split(); ALOGI("%s: Setting default tuning config", __func__); ALOGI("%s: Loading usecase tuning configs", __func__); ctl = mixer_get_ctl_by_name(adev->mixer, CRUS_SP_EXT_CONFIG_MIXER); if (!ctl) { ALOGE("%s: Could not get ctl for mixer cmd - %s", __func__, CRUS_SP_EXT_CONFIG_MIXER); 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; } mixer_ctl_set_value(ctl, 0, 1); // Set TX external firmware config mixer_ctl_set_value(ctl, 0, 0); // Set RX external firmware config 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; } void *audio_extn_cirrus_calibration_thread() { 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 = 0; uint32_t rx_use_case = USECASE_AUDIO_PLAYBACK_DEEP_BUFFER; int32_t pcm_dev_rx_id, prev_state; uint32_t retries = 5; pthread_mutex_lock(&handle.calibration_mutex); ALOGI("%s: PCM Stream thread", __func__); while (!adev->platform && retries) { Loading @@ -342,13 +447,17 @@ void *audio_extn_cirrus_calibration_thread() retries--; } uc_info_rx = (struct audio_usecase *)calloc(1, sizeof(*uc_info_rx)); prev_state = handle.state; handle.state = CALIBRATING; uc_info_rx = (struct audio_usecase *)calloc(1, sizeof(struct audio_usecase)); if (!uc_info_rx) { ret = -EINVAL; ALOGE("%s: rx usecase can not be found", __func__); goto exit; } pthread_mutex_lock(&adev->lock); uc_info_rx->id = rx_use_case; 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; Loading @@ -357,80 +466,232 @@ void *audio_extn_cirrus_calibration_thread() enable_snd_device(adev, SND_DEVICE_OUT_SPEAKER); enable_audio_route(adev, uc_info_rx); pcm_dev_rx_id = 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)) { if (handle.pcm_rx && !pcm_is_ready(handle.pcm_rx)) { ALOGE("%s: PCM device not ready: %s", __func__, pcm_get_error(handle.pcm_rx ? handle.pcm_rx : 0)); ret = -EINVAL; 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)); ret = -EINVAL; pthread_mutex_unlock(&adev->lock); goto close_stream; } ALOGV("%s: PCM thread streaming", __func__); pthread_mutex_unlock(&adev->lock); ALOGI("%s: PCM thread streaming", __func__); ret = audio_extn_cirrus_run_calibration(); if (ret < 0) { ALOGE("%s: Calibration procedure failed (%d)", __func__, ret); } ALOGE_IF(ret < 0, "%s: Calibration procedure failed (%d)", __func__, ret); ret = audio_extn_cirrus_set_default_tuning(); if (ret < 0) { ALOGE("%s: Set tuning configs 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) { ALOGV("%s: pcm_rx_close", __func__); ALOGI("%s: pcm_rx_close", __func__); pcm_close(handle.pcm_rx); handle.pcm_rx = NULL; } disable_audio_route(adev, uc_info_rx); disable_snd_device(adev, SND_DEVICE_OUT_SPEAKER); list_remove(&uc_info_rx->list); free(uc_info_rx); pthread_mutex_unlock(&adev->lock); exit: pthread_mutex_unlock(&handle.calibration_mutex); 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; } void audio_extn_spkr_prot_init(void *adev) { ALOGI("%s: Initialize Cirrus Logic Playback module", __func__); #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; struct snd_card_split *snd_split_handle = NULL; 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; } if (!adev) { ALOGE("%s: Invalid params", __func__); return; buffer = calloc(1, CRUS_PARAM_TEMP_MAX_LENGTH); if (!buffer) { ALOGE("%s: allocate memory failed", __func__); goto exit; } memset(&handle, 0, sizeof(handle)); 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; snd_split_handle = audio_extn_get_snd_card_split(); usleep(FAIL_DETECT_INIT_WAIT_US); handle.adev_handle = adev; 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; } pthread_mutex_init(&handle.fb_prot_mutex, NULL); pthread_mutex_init(&handle.calibration_mutex, NULL); zL = buffer[2] * amp_factor; zR = buffer[4] * amp_factor; (void)pthread_create(&handle.calibration_thread, (const pthread_attr_t *) NULL, audio_extn_cirrus_calibration_thread, &handle); 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 audio_extn_spkr_prot_start_processing(snd_device_t snd_device) { struct audio_usecase *uc_info_tx; struct audio_device *adev = handle.adev_handle; Loading @@ -445,6 +706,7 @@ int audio_extn_spkr_prot_start_processing(snd_device_t snd_device) { uc_info_tx = (struct audio_usecase *)calloc(1, sizeof(*uc_info_tx)); if (!uc_info_tx) { ALOGE("%s: allocate memory failed", __func__); return -ENOMEM; } Loading Loading @@ -476,26 +738,31 @@ int audio_extn_spkr_prot_start_processing(snd_device_t snd_device) { pcm_dev_tx_id, PCM_IN, &pcm_config_cirrus_tx); if (!handle.pcm_tx || !pcm_is_ready(handle.pcm_tx)) { ALOGD("%s: PCM device not ready: %s", __func__, pcm_get_error(handle.pcm_tx ? handle.pcm_tx : 0)); 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) { ALOGI("%s: retrying pcm_start...", __func__); usleep(500 * 1000); if (pcm_start(handle.pcm_tx) < 0) { ALOGI("%s: pcm start for TX failed; error = %s", __func__, 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) { ALOGI("%s: Disable and bail out", __func__); handle.state = IDLE; if (handle.pcm_tx) { ALOGI("%s: pcm_tx_close", __func__); pcm_close(handle.pcm_tx); Loading @@ -521,6 +788,7 @@ void audio_extn_spkr_prot_stop_processing(snd_device_t snd_device) { pthread_mutex_lock(&handle.fb_prot_mutex); handle.state = IDLE; uc_info_tx = get_usecase_from_list(adev, USECASE_AUDIO_SPKR_CALIB_TX); if (uc_info_tx) { Loading Loading @@ -551,6 +819,7 @@ bool audio_extn_spkr_prot_is_enabled() { int audio_extn_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; Loading
hal/audio_hw.c +1 −0 Original line number Diff line number Diff line Loading @@ -662,6 +662,7 @@ int enable_snd_device(struct audio_device *adev, if ((snd_device == SND_DEVICE_OUT_SPEAKER || snd_device == SND_DEVICE_OUT_SPEAKER_SAFE || snd_device == SND_DEVICE_OUT_SPEAKER_REVERSE || snd_device == SND_DEVICE_OUT_VOICE_SPEAKER) && audio_extn_spkr_prot_is_enabled()) { if (platform_get_snd_device_acdb_id(snd_device) < 0) { Loading