diff options
Diffstat (limited to 'sound_card_init/max98390d/src/amp_calibration.rs')
-rw-r--r-- | sound_card_init/max98390d/src/amp_calibration.rs | 319 |
1 files changed, 0 insertions, 319 deletions
diff --git a/sound_card_init/max98390d/src/amp_calibration.rs b/sound_card_init/max98390d/src/amp_calibration.rs deleted file mode 100644 index 35340226..00000000 --- a/sound_card_init/max98390d/src/amp_calibration.rs +++ /dev/null @@ -1,319 +0,0 @@ -// Copyright 2020 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -use std::fmt; -use std::io::Write; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, Condvar, Mutex}; -use std::thread; -use std::thread::JoinHandle; -use std::time::{Duration, Instant}; - -use audio_streams::SampleFormat; -use cros_alsa::{Card, IntControl, SwitchControl}; -use libcras::{CrasClient, CrasNodeType}; -use sys_util::{error, info}; - -use crate::{ - datastore::Datastore, - error::{Error, Result}, - settings::AmpCalibSettings, - vpd::VPD, -}; - -const CALI_ERROR_UPPER_LIMIT: f32 = 0.3; -const CALI_ERROR_LOWER_LIMIT: f32 = 0.03; - -const FRAMES_PER_BUFFER: usize = 256; -const FRAME_RATE: u32 = 48000; -const NUM_CHANNELS: usize = 2; -const FORMAT: SampleFormat = SampleFormat::S16LE; -const DURATION_MS: u32 = 1000; -const WARM_UP_DURATION_MS: u32 = 300; - -/// Amp volume mode emulation used by set_volume(). -#[derive(PartialEq)] -pub enum VolumeMode { - /// Low mode protects the speaker by limiting its output volume if the - /// calibration has not been completed successfully. - Low, - /// High mode removes the speaker output volume limitation after - /// having successfully completed the calibration. - High, -} - -/// It implements the amplifier boot time calibration flow. -pub struct AmpCalibration<'a> { - card: &'a mut Card, - setting: AmpCalibSettings, -} - -impl<'a> fmt::Debug for AmpCalibration<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("AmpCalibration") - .field("snd_card_id", &self.card.name()) - .field("amp_calib", &self.setting) - .finish() - } -} - -impl<'a> AmpCalibration<'a> { - /// Creates an `AmpCalibration`. - /// # Arguments - /// - /// * `card` - `&Card`. - /// * `setting` - `AmpCalibSettings`. - /// - /// # Results - /// - /// * `AmpCalibration` - It implements the amplifier boot time calibration flow. - /// - /// # Errors - /// - /// * If `Card` creation from sound card name fails. - pub fn new(card: &mut Card, setting: AmpCalibSettings) -> Result<AmpCalibration> { - let amp = AmpCalibration { card, setting }; - - Ok(amp) - } - - /// Sets card volume control to given VolumeMode. - pub fn set_volume(&mut self, mode: VolumeMode) -> Result<()> { - match mode { - VolumeMode::High => self - .card - .control_by_name::<IntControl>(&self.setting.amp.volume_ctrl)? - .set(self.setting.amp.volume_high_limit)?, - VolumeMode::Low => self - .card - .control_by_name::<IntControl>(&self.setting.amp.volume_ctrl)? - .set(self.setting.amp.volume_low_limit)?, - } - Ok(()) - } - - /// The implementation of max98390d boot time calibration logic. - /// - /// The boot time calibration logic includes the following steps: - /// * Gets results from `do_calibration`. - /// * Decides whether the new calibration result should replace the stored value. - /// * Applies a good calibration value. - pub fn run(&mut self) -> Result<()> { - let vpd = VPD::from_file(&self.setting.rdc_vpd, &self.setting.temp_vpd)?; - let (rdc_cali, temp_cali) = self.do_calibration()?; - let datastore = match Datastore::from_file(self.card.name(), &self.setting.calib_file) { - Ok(sci_calib) => Some(sci_calib), - Err(e) => { - info!("failure in Datastore::from_file: {}", e); - None - } - }; - - // Given that rdc_cali is the inverse of hardware real_rdc, the result of `rdc_diff` - // equals to transforming `rdc`s to `real_rdc`s and calculating the relative difference - // from the `real_rdc`s. - let rdc_diff = |x: i32, x_ref: i32| (x - x_ref).abs() as f32 / x as f32; - - let diff: f32 = match datastore { - None => rdc_diff(rdc_cali, vpd.dsm_calib_r0), - Some(d) => match d { - Datastore::UseVPD => rdc_diff(rdc_cali, vpd.dsm_calib_r0), - Datastore::DSM { rdc, .. } => rdc_diff(rdc_cali, rdc), - }, - }; - - if !self.validate_temperature(temp_cali) { - info!("invalid temperature: {}.", temp_cali); - return match datastore { - None => Err(Error::InvalidTemperature(temp_cali)), - Some(d) => self.apply_datastore(d), - }; - } - - if diff > CALI_ERROR_UPPER_LIMIT { - return Err(Error::LargeCalibrationDiff(rdc_cali, temp_cali)); - } else if diff < CALI_ERROR_LOWER_LIMIT { - match datastore { - None => Datastore::UseVPD.save(self.card.name(), &self.setting.calib_file)?, - Some(d) => self.apply_datastore(d)?, - } - } else { - info!("apply boot time calibration values."); - self.card - .control_by_name::<IntControl>(&self.setting.amp.rdc_ctrl)? - .set(rdc_cali)?; - self.card - .control_by_name::<IntControl>(&self.setting.amp.temp_ctrl)? - .set(temp_cali)?; - Datastore::DSM { - rdc: rdc_cali, - ambient_temp: temp_cali, - } - .save(self.card.name(), &self.setting.calib_file)?; - } - Ok(()) - } - - fn apply_datastore(&mut self, d: Datastore) -> Result<()> { - info!("apply datastore values."); - match d { - Datastore::UseVPD => Ok(()), - Datastore::DSM { rdc, ambient_temp } => { - self.card - .control_by_name::<IntControl>(&self.setting.amp.rdc_ctrl)? - .set(rdc)?; - self.card - .control_by_name::<IntControl>(&self.setting.amp.temp_ctrl)? - .set(ambient_temp)?; - Ok(()) - } - } - } - - fn validate_temperature(&self, temp: i32) -> bool { - temp < self.setting.amp.temp_upper_limit && temp > self.setting.amp.temp_lower_limit - } - - /// Triggers the amplifier calibration and reads the calibrated rdc and ambient_temp value - /// from the mixer control. - /// To get accurate calibration results, the main thread calibrates the amplifier while - /// the another thread plays zeros to the speakers. - fn do_calibration(&mut self) -> Result<(i32, i32)> { - // The playback worker uses `playback_started` to notify the main thread that playback - // of zeros has started. - let playback_started = Arc::new((Mutex::new(false), Condvar::new())); - // Shares `calib_finished` to the playback worker and uses it to notify the worker when - // the calibration is finished. - let calib_finished = Arc::new(AtomicBool::new(false)); - let handle = - AmpCalibration::run_play_zero_worker(playback_started.clone(), calib_finished.clone())?; - - // Waits until zero playback starts or timeout. - let mut timeout = Duration::from_millis(1000); - let (lock, cvar) = &*playback_started; - let mut started = lock.lock()?; - while !*started { - let start_time = Instant::now(); - started = cvar.wait_timeout(started, timeout)?.0; - if *started { - break; - } else { - let elapsed = start_time.elapsed(); - if elapsed > timeout { - return Err(Error::StartPlaybackTimeout); - } else { - // Spurious wakes. Decrements the sleep duration by the amount slept. - timeout -= start_time.elapsed(); - } - } - } - - // Playback of zeros is started, and the main thread can start the calibration. - self.card - .control_by_name::<SwitchControl>(&self.setting.amp.calib_ctrl)? - .on()?; - let rdc = self - .card - .control_by_name::<IntControl>(&self.setting.amp.rdc_ctrl)? - .get()?; - let temp = self - .card - .control_by_name::<IntControl>(&self.setting.amp.temp_ctrl)? - .get()?; - self.card - .control_by_name::<SwitchControl>(&self.setting.amp.calib_ctrl)? - .off()?; - // Notifies the play_zero_worker that the calibration is finished. - calib_finished.store(true, Ordering::Relaxed); - - // If play_zero_worker has error during the calibration, returns an error to keep the volume - // low to protect the speaker. - match handle.join() { - Ok(res) => { - if let Err(e) = res { - error!("run_play_zero_worker has error: {}", e); - return Err(e); - } - } - Err(e) => { - error!("run_play_zero_worker panics: {:?}", e); - return Err(Error::WorkerPanics); - } - } - - Ok((rdc, temp)) - } - - // Creates a thread to play zeros to the internal speakers. - fn run_play_zero_worker( - playback_started: Arc<(Mutex<bool>, Condvar)>, - calib_finished: Arc<AtomicBool>, - ) -> Result<JoinHandle<Result<()>>> { - let mut cras_client = CrasClient::new().map_err(Error::CrasClientFailed)?; - // TODO(b/155007305): Implement cras_client.wait_node_change and use it here. - let node = cras_client - .output_nodes() - .find(|node| node.node_type == CrasNodeType::CRAS_NODE_TYPE_INTERNAL_SPEAKER) - .ok_or(Error::InternalSpeakerNotFound)?; - - let handle = thread::spawn(move || -> Result<()> { - let local_buffer = [0u8; FRAMES_PER_BUFFER * NUM_CHANNELS * 2]; - let iterations = (FRAME_RATE * DURATION_MS) / FRAMES_PER_BUFFER as u32 / 1000; - let warm_up_iterations = - (FRAME_RATE * WARM_UP_DURATION_MS) / FRAMES_PER_BUFFER as u32 / 1000; - - let (_control, mut stream) = cras_client - .new_pinned_playback_stream( - node.iodev_index, - NUM_CHANNELS, - FORMAT, - FRAME_RATE, - FRAMES_PER_BUFFER, - ) - .map_err(|e| Error::NewPlayStreamFailed(e))?; - - // Plays zeros for at most DURATION_MS. - for i in 0..iterations { - if calib_finished.load(Ordering::Relaxed) { - break; - } - let mut buffer = stream - .next_playback_buffer() - .map_err(|e| Error::NextPlaybackBufferFailed(e))?; - let _write_frames = buffer.write(&local_buffer).map_err(Error::PlaybackFailed)?; - - // Notifies the main thread to start the calibration. - // The mute playing time need to be longer than WARM_UP_DURATION_MS to get rdc properly. - if i == warm_up_iterations { - let (lock, cvar) = &*playback_started; - let mut started = lock.lock()?; - *started = true; - cvar.notify_one(); - } - // The playback_started lock is unlocked here when `started` goes out of scope. - } - - // Returns an error if the calibration is not finished before playback stops. - if !calib_finished.load(Ordering::Relaxed) { - return Err(Error::CalibrationTimeout); - } - Ok(()) - }); - - Ok(handle) - } - - /// Skips max98390d boot time calibration when the speaker may be hot. - /// - /// If datastore exists, applies the stored value and sets volume to high. - /// If datastore does not exist, sets volume to low. - pub fn hot_speaker_workflow(&mut self) -> Result<()> { - if let Ok(sci_calib) = Datastore::from_file(self.card.name(), &self.setting.calib_file) { - self.apply_datastore(sci_calib)?; - self.set_volume(VolumeMode::High)?; - return Ok(()); - } - info!("no datastore, set volume low"); - self.set_volume(VolumeMode::Low) - } -} |