• 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 //! `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