• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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