1 // Copyright 2020 The Chromium OS Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 //! `max98390d` module implements the required initialization workflows for sound 5 //! cards that use max98390d smart amp. 6 //! It currently supports boot time calibration for max98390d. 7 #![deny(missing_docs)] 8 mod settings; 9 10 use std::time::Duration; 11 use std::{fs, path::Path}; 12 13 use cros_alsa::{Card, IntControl, SwitchControl}; 14 use dsm::{CalibData, Error, Result, SpeakerStatus, TempConverter, ZeroPlayer, DSM}; 15 16 use crate::Amp; 17 use settings::{AmpCalibSettings, DeviceSettings}; 18 19 /// Amp volume mode emulation used by set_volume(). 20 #[derive(PartialEq, Clone, Copy)] 21 enum VolumeMode { 22 /// Low mode protects the speaker by limiting its output volume if the 23 /// calibration has not been completed successfully. 24 Low = 138, 25 /// High mode removes the speaker output volume limitation after 26 /// having successfully completed the calibration. 27 High = 148, 28 } 29 30 /// It implements the Max98390 functions of boot time calibration. 31 #[derive(Debug)] 32 pub struct Max98390 { 33 card: Card, 34 setting: AmpCalibSettings, 35 } 36 37 impl Amp for Max98390 { 38 /// Performs max98390d boot time calibration. 39 /// 40 /// # Errors 41 /// 42 /// If the amplifier fails to complete the calibration. boot_time_calibration(&mut self) -> Result<()>43 fn boot_time_calibration(&mut self) -> Result<()> { 44 if !Path::new(&self.setting.dsm_param).exists() { 45 return Err(Error::MissingDSMParam); 46 } 47 48 let mut dsm = DSM::new( 49 &self.card.name(), 50 self.setting.num_channels(), 51 Self::rdc_to_ohm, 52 Self::TEMP_UPPER_LIMIT_CELSIUS, 53 Self::TEMP_LOWER_LIMIT_CELSIUS, 54 ); 55 dsm.set_temp_converter(TempConverter::new( 56 Self::dsm_unit_to_celsius, 57 Self::celsius_to_dsm_unit, 58 )); 59 60 self.set_volume(VolumeMode::Low)?; 61 let calib = match dsm.check_speaker_over_heated_workflow()? { 62 SpeakerStatus::Hot(previous_calib) => previous_calib, 63 SpeakerStatus::Cold => self 64 .do_calibration()? 65 .iter() 66 .enumerate() 67 .map(|(ch, calib_data)| dsm.decide_calibration_value_workflow(ch, *calib_data)) 68 .collect::<Result<Vec<_>>>()?, 69 }; 70 self.apply_calibration_value(calib)?; 71 self.set_volume(VolumeMode::High)?; 72 Ok(()) 73 } 74 } 75 76 impl Max98390 { 77 const TEMP_UPPER_LIMIT_CELSIUS: f32 = 40.0; 78 const TEMP_LOWER_LIMIT_CELSIUS: f32 = 0.0; 79 const RDC_CALIB_WARM_UP_TIME: Duration = Duration::from_millis(300); 80 81 /// Creates an `Max98390`. 82 /// # Arguments 83 /// 84 /// * `card_name` - card name. 85 /// * `config_path` - config file path. 86 /// 87 /// # Results 88 /// 89 /// * `Max98390` - It implements the Max98390 functions of boot time calibration. 90 /// 91 /// # Errors 92 /// 93 /// * If `Card` creation from sound card name fails. new(card_name: &str, config_path: &Path) -> Result<Self>94 pub fn new(card_name: &str, config_path: &Path) -> Result<Self> { 95 let conf = fs::read_to_string(config_path) 96 .map_err(|e| Error::FileIOFailed(config_path.to_path_buf(), e))?; 97 let settings = DeviceSettings::from_yaml_str(&conf)?; 98 Ok(Self { 99 card: Card::new(card_name)?, 100 setting: settings.amp_calibrations, 101 }) 102 } 103 104 /// Sets the card volume control to given VolumeMode. set_volume(&mut self, mode: VolumeMode) -> Result<()>105 fn set_volume(&mut self, mode: VolumeMode) -> Result<()> { 106 for control in &self.setting.controls { 107 self.card 108 .control_by_name::<IntControl>(&control.volume_ctrl)? 109 .set(mode as i32)?; 110 } 111 Ok(()) 112 } 113 114 /// Applies the calibration value to the amp. apply_calibration_value(&mut self, calib: Vec<CalibData>) -> Result<()>115 fn apply_calibration_value(&mut self, calib: Vec<CalibData>) -> Result<()> { 116 for (ch, &CalibData { rdc, temp }) in calib.iter().enumerate() { 117 self.card 118 .control_by_name::<IntControl>(&self.setting.controls[ch].rdc_ctrl)? 119 .set(rdc)?; 120 self.card 121 .control_by_name::<IntControl>(&self.setting.controls[ch].temp_ctrl)? 122 .set(Self::celsius_to_dsm_unit(temp))?; 123 } 124 Ok(()) 125 } 126 127 /// Triggers the amplifier calibration and reads the calibrated rdc and ambient_temp value 128 /// from the mixer control. 129 /// To get accurate calibration results, the main thread calibrates the amplifier while 130 /// the `zero_player` starts another thread to play zeros to the speakers. do_calibration(&mut self) -> Result<Vec<CalibData>>131 fn do_calibration(&mut self) -> Result<Vec<CalibData>> { 132 let mut zero_player: ZeroPlayer = Default::default(); 133 zero_player.start(Self::RDC_CALIB_WARM_UP_TIME)?; 134 // Playback of zeros is started for Self::RDC_CALIB_WARM_UP_TIME, and the main thread 135 // can start the calibration. 136 let setting = &self.setting; 137 let card = &mut self.card; 138 let calib = setting 139 .controls 140 .iter() 141 .map(|control| { 142 card.control_by_name::<SwitchControl>(&control.calib_ctrl)? 143 .on()?; 144 let rdc = card 145 .control_by_name::<IntControl>(&control.rdc_ctrl)? 146 .get()?; 147 let temp = card 148 .control_by_name::<IntControl>(&control.temp_ctrl)? 149 .get()?; 150 card.control_by_name::<SwitchControl>(&control.calib_ctrl)? 151 .off()?; 152 Ok(CalibData { 153 rdc, 154 temp: Self::dsm_unit_to_celsius(temp), 155 }) 156 }) 157 .collect::<Result<Vec<CalibData>>>()?; 158 zero_player.stop()?; 159 Ok(calib) 160 } 161 162 /// Converts the ambient temperature from celsius to the DSM unit. 163 #[inline] celsius_to_dsm_unit(celsius: f32) -> i32164 fn celsius_to_dsm_unit(celsius: f32) -> i32 { 165 (celsius * ((1 << 12) as f32) / 100.0) as i32 166 } 167 168 /// Converts the ambient temperature from DSM unit to celsius. 169 #[inline] dsm_unit_to_celsius(temp: i32) -> f32170 fn dsm_unit_to_celsius(temp: i32) -> f32 { 171 temp as f32 * 100.0 / (1 << 12) as f32 172 } 173 174 /// Converts the calibrated value to real DC resistance in ohm unit. 175 #[inline] rdc_to_ohm(x: i32) -> f32176 fn rdc_to_ohm(x: i32) -> f32 { 177 3.66 * (1 << 20) as f32 / x as f32 178 } 179 } 180 181 #[cfg(test)] 182 mod tests { 183 use super::*; 184 #[test] celsius_to_dsm_unit()185 fn celsius_to_dsm_unit() { 186 assert_eq!( 187 Max98390::celsius_to_dsm_unit(Max98390::TEMP_UPPER_LIMIT_CELSIUS), 188 1638 189 ); 190 assert_eq!( 191 Max98390::celsius_to_dsm_unit(Max98390::TEMP_LOWER_LIMIT_CELSIUS), 192 0 193 ); 194 } 195 196 #[test] dsm_unit_to_celsius()197 fn dsm_unit_to_celsius() { 198 assert_eq!( 199 Max98390::dsm_unit_to_celsius(1638).round(), 200 Max98390::TEMP_UPPER_LIMIT_CELSIUS 201 ); 202 assert_eq!( 203 Max98390::dsm_unit_to_celsius(0), 204 Max98390::TEMP_LOWER_LIMIT_CELSIUS 205 ); 206 } 207 208 #[test] rdc_to_ohm()209 fn rdc_to_ohm() { 210 assert_eq!(Max98390::rdc_to_ohm(1123160), 3.416956); 211 assert_eq!(Max98390::rdc_to_ohm(1157049), 3.3168762); 212 } 213 } 214