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 //! `max98373d` module implements the required initialization workflows for sound 5 //! cards that use max98373d smart amp. 6 //! It currently supports boot time calibration for max98373d. 7 #![deny(missing_docs)] 8 mod dsm_param; 9 mod settings; 10 11 use std::path::Path; 12 use std::time::Duration; 13 use std::{fs, thread}; 14 15 use cros_alsa::{Card, IntControl}; 16 use dsm::{CalibData, Error, Result, SpeakerStatus, ZeroPlayer, DSM}; 17 use sys_util::info; 18 19 use crate::Amp; 20 use dsm_param::*; 21 use settings::{AmpCalibSettings, DeviceSettings}; 22 23 /// It implements the amplifier boot time calibration flow. 24 pub struct Max98373 { 25 card: Card, 26 setting: AmpCalibSettings, 27 } 28 29 impl Amp for Max98373 { 30 /// Performs max98373d boot time calibration. 31 /// 32 /// # Errors 33 /// 34 /// If any amplifiers fail to complete the calibration. boot_time_calibration(&mut self) -> Result<()>35 fn boot_time_calibration(&mut self) -> Result<()> { 36 if !Path::new(&self.setting.dsm_param).exists() { 37 return Err(Error::MissingDSMParam); 38 } 39 40 let num_channels = self.setting.num_channels(); 41 let dsm = DSM::new( 42 &self.card.name(), 43 num_channels, 44 Self::rdc_to_ohm, 45 Self::TEMP_UPPER_LIMIT_CELSIUS, 46 Self::TEMP_LOWER_LIMIT_CELSIUS, 47 ); 48 self.set_volume(VolumeMode::Low)?; 49 50 let calib = if !self.setting.boot_time_calibration_enabled { 51 info!("skip boot time calibration and use vpd values"); 52 // Needs Rdc updates to be done after internal speaker is ready otherwise 53 // it would be overwritten by the DSM blob update. 54 dsm.wait_for_speakers_ready()?; 55 dsm.get_all_vpd_calibration_value()? 56 } else { 57 match dsm.check_speaker_over_heated_workflow()? { 58 SpeakerStatus::Hot(previous_calib) => previous_calib, 59 SpeakerStatus::Cold => { 60 let all_temp = self.get_ambient_temp()?; 61 let all_rdc = self.do_rdc_calibration()?; 62 all_rdc 63 .iter() 64 .zip(all_temp) 65 .enumerate() 66 .map(|(ch, (&rdc, temp))| { 67 dsm.decide_calibration_value_workflow(ch, CalibData { rdc, temp }) 68 }) 69 .collect::<Result<Vec<_>>>()? 70 } 71 } 72 }; 73 self.apply_calibration_value(&calib)?; 74 self.set_volume(VolumeMode::High)?; 75 Ok(()) 76 } 77 } 78 79 impl Max98373 { 80 const TEMP_CALIB_WARM_UP_TIME: Duration = Duration::from_millis(10); 81 const RDC_CALIB_WARM_UP_TIME: Duration = Duration::from_millis(500); 82 const RDC_CALIB_INTERVAL: Duration = Duration::from_millis(200); 83 const CALIB_REPEAT_TIMES: usize = 5; 84 85 const TEMP_UPPER_LIMIT_CELSIUS: f32 = 40.0; 86 const TEMP_LOWER_LIMIT_CELSIUS: f32 = 0.0; 87 88 /// Creates an `Max98373`. 89 /// # Arguments 90 /// 91 /// * `card_name` - card_name. 92 /// * `config_path` - config file path. 93 /// 94 /// # Results 95 /// 96 /// * `Max98373` - It implements the Max98373 functions of boot time calibration. 97 /// 98 /// # Errors 99 /// 100 /// * If `Card` creation from sound card name fails. new(card_name: &str, config_path: &Path) -> Result<Self>101 pub fn new(card_name: &str, config_path: &Path) -> Result<Self> { 102 let conf = fs::read_to_string(config_path) 103 .map_err(|e| Error::FileIOFailed(config_path.to_path_buf(), e))?; 104 let settings = DeviceSettings::from_yaml_str(&conf)?; 105 Ok(Self { 106 card: Card::new(card_name)?, 107 setting: settings.amp_calibrations, 108 }) 109 } 110 111 /// Triggers the amplifier calibration and reads the calibrated rdc. 112 /// To get accurate calibration results, the main thread calibrates the amplifier while 113 /// the `zero_player` starts another thread to play zeros to the speakers. do_rdc_calibration(&mut self) -> Result<Vec<i32>>114 fn do_rdc_calibration(&mut self) -> Result<Vec<i32>> { 115 let mut zero_player: ZeroPlayer = Default::default(); 116 zero_player.start(Self::RDC_CALIB_WARM_UP_TIME)?; 117 // Playback of zeros is started for Self::RDC_CALIB_WARM_UP_TIME, and the main thread 118 // can start the calibration. 119 self.set_spt_mode(SPTMode::OFF)?; 120 self.set_calibration_mode(CalibMode::ON)?; 121 // Playback of zeros is started, and the main thread can start the calibration. 122 let mut avg_rdc = vec![0; self.setting.num_channels()]; 123 for _ in 0..Self::CALIB_REPEAT_TIMES { 124 let rdc = self.get_adaptive_rdc()?; 125 for i in 0..self.setting.num_channels() { 126 avg_rdc[i] += rdc[i]; 127 } 128 thread::sleep(Self::RDC_CALIB_INTERVAL); 129 } 130 self.set_spt_mode(SPTMode::ON)?; 131 self.set_calibration_mode(CalibMode::OFF)?; 132 zero_player.stop()?; 133 134 avg_rdc = avg_rdc 135 .iter() 136 .map(|val| val / Self::CALIB_REPEAT_TIMES as i32) 137 .collect(); 138 Ok(avg_rdc) 139 } 140 141 /// Sets the card volume control to the given VolumeMode. set_volume(&mut self, mode: VolumeMode) -> Result<()>142 fn set_volume(&mut self, mode: VolumeMode) -> Result<()> { 143 let mut dsm_param = DSMParam::new( 144 &mut self.card, 145 self.setting.num_channels(), 146 &self.setting.dsm_param_read_ctrl, 147 )?; 148 149 dsm_param.set_volume_mode(mode); 150 151 self.card 152 .control_tlv_by_name(&self.setting.dsm_param_write_ctrl)? 153 .save(dsm_param.into()) 154 .map_err(Error::DSMParamUpdateFailed)?; 155 Ok(()) 156 } 157 158 /// Applies the calibration value to the amp. apply_calibration_value(&mut self, calib: &[CalibData]) -> Result<()>159 fn apply_calibration_value(&mut self, calib: &[CalibData]) -> Result<()> { 160 let mut dsm_param = DSMParam::new( 161 &mut self.card, 162 self.setting.num_channels(), 163 &self.setting.dsm_param_read_ctrl, 164 )?; 165 for ch in 0..self.setting.num_channels() { 166 dsm_param.set_rdc(ch, calib[ch].rdc); 167 dsm_param.set_ambient_temp(ch, Self::celsius_to_dsm_unit(calib[ch].temp)); 168 } 169 self.card 170 .control_tlv_by_name(&self.setting.dsm_param_write_ctrl)? 171 .save(dsm_param.into()) 172 .map_err(Error::DSMParamUpdateFailed)?; 173 Ok(()) 174 } 175 176 /// Rdc (ohm) = [ID:0x12] * 3.66 / 2^27 177 #[inline] rdc_to_ohm(x: i32) -> f32178 fn rdc_to_ohm(x: i32) -> f32 { 179 (3.66 * x as f32) / (1 << 27) as f32 180 } 181 182 /// Returns the ambient temperature in celsius degree. get_ambient_temp(&mut self) -> Result<Vec<f32>>183 fn get_ambient_temp(&mut self) -> Result<Vec<f32>> { 184 let mut zero_player: ZeroPlayer = Default::default(); 185 zero_player.start(Self::TEMP_CALIB_WARM_UP_TIME)?; 186 let mut temps = Vec::new(); 187 for x in 0..self.setting.num_channels() as usize { 188 let temp = self 189 .card 190 .control_by_name::<IntControl>(&self.setting.temp_ctrl[x])? 191 .get()?; 192 let celsius = Self::measured_temp_to_celsius(temp); 193 temps.push(celsius); 194 } 195 zero_player.stop()?; 196 197 Ok(temps) 198 } 199 200 /// Converts the measured ambient temperature to celsius unit. 201 #[inline] measured_temp_to_celsius(temp: i32) -> f32202 fn measured_temp_to_celsius(temp: i32) -> f32 { 203 // Measured Temperature (°C) = ([Mixer Val] * 1.28) - 29 204 (temp as f32 * 1.28) - 29.0 205 } 206 207 /// Converts the ambient temperature from celsius to the DsmSetAPI::DsmAmbientTemp unit. 208 #[inline] celsius_to_dsm_unit(celsius: f32) -> i32209 fn celsius_to_dsm_unit(celsius: f32) -> i32 { 210 // Temperature (℃) = [ID:0x12] / 2^19 211 (celsius * (1 << 19) as f32) as i32 212 } 213 214 /// Sets the amp to the given smart pilot signal mode. set_spt_mode(&mut self, mode: SPTMode) -> Result<()>215 fn set_spt_mode(&mut self, mode: SPTMode) -> Result<()> { 216 let mut dsm_param = DSMParam::new( 217 &mut self.card, 218 self.setting.num_channels(), 219 &self.setting.dsm_param_read_ctrl, 220 )?; 221 dsm_param.set_spt_mode(mode); 222 self.card 223 .control_tlv_by_name(&self.setting.dsm_param_write_ctrl)? 224 .save(dsm_param.into()) 225 .map_err(Error::DSMParamUpdateFailed)?; 226 Ok(()) 227 } 228 229 /// Sets the amp to the given the calibration mode. set_calibration_mode(&mut self, mode: CalibMode) -> Result<()>230 fn set_calibration_mode(&mut self, mode: CalibMode) -> Result<()> { 231 let mut dsm_param = DSMParam::new( 232 &mut self.card, 233 self.setting.num_channels(), 234 &self.setting.dsm_param_read_ctrl, 235 )?; 236 dsm_param.set_calibration_mode(mode); 237 self.card 238 .control_tlv_by_name(&self.setting.dsm_param_write_ctrl)? 239 .save(dsm_param.into()) 240 .map_err(Error::DSMParamUpdateFailed)?; 241 Ok(()) 242 } 243 244 /// Reads the calibrated rdc. 245 /// Must be called when the calibration mode in on. get_adaptive_rdc(&mut self) -> Result<Vec<i32>>246 fn get_adaptive_rdc(&mut self) -> Result<Vec<i32>> { 247 let dsm_param = DSMParam::new( 248 &mut self.card, 249 self.setting.num_channels(), 250 &self.setting.dsm_param_read_ctrl, 251 )?; 252 Ok(dsm_param.get_adaptive_rdc()) 253 } 254 } 255 256 #[cfg(test)] 257 mod tests { 258 use super::*; 259 #[test] celsius_to_dsm_unit()260 fn celsius_to_dsm_unit() { 261 assert_eq!(Max98373::celsius_to_dsm_unit(37.0), 0x01280000); 262 assert_eq!(Max98373::celsius_to_dsm_unit(50.0), 0x01900000); 263 } 264 265 #[test] rdc_to_ohm()266 fn rdc_to_ohm() { 267 assert_eq!(Max98373::rdc_to_ohm(0x05cea0c7), 2.656767); 268 } 269 270 #[test] measured_temp_to_celsius()271 fn measured_temp_to_celsius() { 272 assert_eq!(Max98373::measured_temp_to_celsius(56), 42.68); 273 } 274 } 275