sound_card_init: `DSM` uses celsius as the unit of temperature

To make `DSM` more readable and maintainable, we:

1. Uses celsius as the unit of temperature in `DSM` as it's more easy
   to understand.
2. Allow each amps to define the conversion between celsius and
   the unit in VPD::dsm_calib_temp in `DSM`. Max98390 uses its dsm unit
   for VPD::dsm_calib_temp and needs to convert it to celsius
   to communicate with `DSM` struct.
3. Replace rdc_diff with rdc_to_ohm as compute the rdc difference in
   real DC resistence is more general.

BUG=b:157210111
TEST=/sbin/initctl start sound_card_init SOUND_CARD_ID=sofcmlmax98390d

Change-Id: I8dbcc44b365192657f382ae8a43e091354f2e4cf
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/adhd/+/2650004
Tested-by: Judy Hsiao <judyhsiao@chromium.org>
Reviewed-by: Chih-Yang Hsia <paulhsia@chromium.org>
Commit-Queue: Judy Hsiao <judyhsiao@chromium.org>
diff --git a/sound_card_init/amp/src/max98390d/mod.rs b/sound_card_init/amp/src/max98390d/mod.rs
index 57c8ea8..f989255 100644
--- a/sound_card_init/amp/src/max98390d/mod.rs
+++ b/sound_card_init/amp/src/max98390d/mod.rs
@@ -14,7 +14,7 @@
 };
 
 use cros_alsa::{Card, IntControl, SwitchControl};
-use dsm::{CalibData, Error, Result, SpeakerStatus, ZeroPlayer, DSM};
+use dsm::{CalibData, Error, Result, SpeakerStatus, TempConverter, ZeroPlayer, DSM};
 
 use crate::{Amp, CONF_DIR};
 use settings::{AmpCalibSettings, DeviceSettings};
@@ -48,13 +48,17 @@
             return Err(Error::MissingDSMParam);
         }
 
-        let dsm = DSM::new(
+        let mut dsm = DSM::new(
             &self.card.name(),
             self.setting.num_channels(),
-            Self::rdc_diff,
-            Self::celsius_to_dsm_unit(Self::TEMP_UPPER_LIMIT_CELSIUS),
-            Self::celsius_to_dsm_unit(Self::TEMP_LOWER_LIMIT_CELSIUS),
+            Self::rdc_to_ohm,
+            Self::TEMP_UPPER_LIMIT_CELSIUS,
+            Self::TEMP_LOWER_LIMIT_CELSIUS,
         );
+        dsm.set_temp_converter(TempConverter::new(
+            Self::dsm_unit_to_celsius,
+            Self::celsius_to_dsm_unit,
+        ));
 
         self.set_volume(VolumeMode::Low)?;
         let calib = match dsm.check_speaker_over_heated_workflow()? {
@@ -102,11 +106,6 @@
         })
     }
 
-    /// Converts the ambient temperature from celsius to the DSM unit.
-    fn celsius_to_dsm_unit(celsius: f32) -> f32 {
-        celsius * ((1 << 12) as f32) / 100.0
-    }
-
     /// Sets the card volume control to given VolumeMode.
     fn set_volume(&mut self, mode: VolumeMode) -> Result<()> {
         for control in &self.setting.controls {
@@ -125,7 +124,7 @@
                 .set(rdc)?;
             self.card
                 .control_by_name::<IntControl>(&self.setting.controls[ch].temp_ctrl)?
-                .set(temp)?;
+                .set(Self::celsius_to_dsm_unit(temp))?;
         }
         Ok(())
     }
@@ -155,19 +154,32 @@
                     .get()?;
                 card.control_by_name::<SwitchControl>(&control.calib_ctrl)?
                     .off()?;
-                Ok(CalibData { rdc, temp })
+                Ok(CalibData {
+                    rdc,
+                    temp: Self::dsm_unit_to_celsius(temp),
+                })
             })
             .collect::<Result<Vec<CalibData>>>()?;
         zero_player.stop()?;
         Ok(calib)
     }
 
-    // rdc_cali is (11 / 3) / actual_rdc * 2^20.
-    // 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.
-    fn rdc_diff(x: i32, x_ref: i32) -> f32 {
-        (x - x_ref).abs() as f32 / x as f32
+    /// Converts the ambient temperature from celsius to the DSM unit.
+    #[inline]
+    fn celsius_to_dsm_unit(celsius: f32) -> i32 {
+        (celsius * ((1 << 12) as f32) / 100.0) as i32
+    }
+
+    /// Converts the ambient temperature from  DSM unit to celsius.
+    #[inline]
+    fn dsm_unit_to_celsius(temp: i32) -> f32 {
+        temp as f32 * 100.0 / (1 << 12) as f32
+    }
+
+    /// Converts the calibrated value to real DC resistance in ohm unit.
+    #[inline]
+    fn rdc_to_ohm(x: i32) -> f32 {
+        3.66 * (1 << 20) as f32 / x as f32
     }
 }
 
@@ -178,11 +190,29 @@
     fn celsius_to_dsm_unit() {
         assert_eq!(
             Max98390::celsius_to_dsm_unit(Max98390::TEMP_UPPER_LIMIT_CELSIUS),
-            1638.4
+            1638
         );
         assert_eq!(
             Max98390::celsius_to_dsm_unit(Max98390::TEMP_LOWER_LIMIT_CELSIUS),
-            0.0
+            0
         );
     }
+
+    #[test]
+    fn dsm_unit_to_celsius() {
+        assert_eq!(
+            Max98390::dsm_unit_to_celsius(1638).round(),
+            Max98390::TEMP_UPPER_LIMIT_CELSIUS
+        );
+        assert_eq!(
+            Max98390::dsm_unit_to_celsius(0),
+            Max98390::TEMP_LOWER_LIMIT_CELSIUS
+        );
+    }
+
+    #[test]
+    fn rdc_to_ohm() {
+        assert_eq!(Max98390::rdc_to_ohm(1123160), 3.416956);
+        assert_eq!(Max98390::rdc_to_ohm(1157049), 3.3168762);
+    }
 }
diff --git a/sound_card_init/dsm/src/error.rs b/sound_card_init/dsm/src/error.rs
index 1240cb1..b93cba1 100644
--- a/sound_card_init/dsm/src/error.rs
+++ b/sound_card_init/dsm/src/error.rs
@@ -12,6 +12,8 @@
 
 use remain::sorted;
 
+use crate::CalibData;
+
 pub type Result<T> = std::result::Result<T, Error>;
 
 #[sorted]
@@ -27,8 +29,8 @@
     InvalidDatastore,
     InvalidDSMParam,
     InvalidShutDownTime,
-    InvalidTemperature(i32),
-    LargeCalibrationDiff(i32, i32),
+    InvalidTemperature(f32),
+    LargeCalibrationDiff(CalibData),
     MissingDSMParam,
     MutexPoisonError,
     NewPlayStreamFailed(libcras::BoxError),
@@ -85,11 +87,9 @@
             ),
             InvalidDatastore => write!(f, "invalid datastore format"),
             InvalidDSMParam => write!(f, "invalid dsm param from kcontrol"),
-            LargeCalibrationDiff(rdc, temp) => write!(
-                f,
-                "calibration difference is too large, rdc: {}, temp: {}",
-                rdc, temp
-            ),
+            LargeCalibrationDiff(calib) => {
+                write!(f, "calibration difference is too large, calib: {:?}", calib)
+            }
             MissingDSMParam => write!(f, "missing dsm_param.bin"),
             MutexPoisonError => write!(f, "mutex is poisoned"),
             NewPlayStreamFailed(e) => write!(f, "{}", e),
diff --git a/sound_card_init/dsm/src/lib.rs b/sound_card_init/dsm/src/lib.rs
index b6e40ed..29c571c 100644
--- a/sound_card_init/dsm/src/lib.rs
+++ b/sound_card_init/dsm/src/lib.rs
@@ -22,10 +22,45 @@
 #[derive(Debug, Clone, Copy)]
 /// `CalibData` represents the calibration data.
 pub struct CalibData {
-    /// The DC resistance of the speaker.
+    /// The DC resistance of the speaker is DSM unit.
     pub rdc: i32,
-    /// The ambient temperature at which the rdc is measured.
-    pub temp: i32,
+    /// The ambient temperature in celsius unit at which the rdc is measured.
+    pub temp: f32,
+}
+
+/// `TempConverter` converts the temperature value between celsius and unit in VPD::dsm_calib_temp.
+pub struct TempConverter {
+    vpd_to_celsius: fn(i32) -> f32,
+    celsius_to_vpd: fn(f32) -> i32,
+}
+
+impl Default for TempConverter {
+    fn default() -> Self {
+        let vpd_to_celsius = |x: i32| x as f32;
+        let celsius_to_vpd = |x: f32| x.round() as i32;
+        Self {
+            vpd_to_celsius,
+            celsius_to_vpd,
+        }
+    }
+}
+
+impl TempConverter {
+    /// Creates a `TempConverter`
+    ///
+    /// # Arguments
+    ///
+    /// * `vpd_to_celsius` - function to convert VPD::dsm_calib_temp to celsius unit`
+    /// * `celsius_to_vpd` - function to convert celsius unit to VPD::dsm_calib_temp`
+    /// # Results
+    ///
+    /// * `TempConverter` - it converts the temperature value between celsius and unit in VPD::dsm_calib_temp.
+    pub fn new(vpd_to_celsius: fn(i32) -> f32, celsius_to_vpd: fn(f32) -> i32) -> Self {
+        Self {
+            vpd_to_celsius,
+            celsius_to_vpd,
+        }
+    }
 }
 
 /// `SpeakerStatus` are the possible return results of
@@ -44,7 +79,8 @@
 pub struct DSM {
     snd_card: String,
     num_channels: usize,
-    rdc_diff: fn(i32, i32) -> f32,
+    temp_converter: TempConverter,
+    rdc_to_ohm: fn(i32) -> f32,
     temp_upper_limit: f32,
     temp_lower_limit: f32,
 }
@@ -60,8 +96,7 @@
     ///
     /// * `snd_card` - `sound card name`.
     /// * `num_channels` - `number of channels`.
-    /// * `rdc_diff` - `fn(rdc: i32, rdc_ref: i32) -> f32 to calculate the rdc difference`.
-    /// *              rdc is the newly calibrated rdc and rdc_ref is the reference rdc.
+    /// * `rdc_to_ohm` - `fn(rdc: i32) -> f32 to convert the CalibData::rdc to ohm unit`.
     /// * `temp_upper_limit` - the high limit of the valid ambient temperature in dsm unit.
     /// * `temp_lower_limit` - the low limit of the valid ambient temperature in dsm unit.
     ///
@@ -71,19 +106,29 @@
     pub fn new(
         snd_card: &str,
         num_channels: usize,
-        rdc_diff: fn(i32, i32) -> f32,
+        rdc_to_ohm: fn(i32) -> f32,
         temp_upper_limit: f32,
         temp_lower_limit: f32,
     ) -> Self {
         Self {
             snd_card: snd_card.to_owned(),
             num_channels,
-            rdc_diff,
+            rdc_to_ohm,
+            temp_converter: TempConverter::default(),
             temp_upper_limit,
             temp_lower_limit,
         }
     }
 
+    /// Sets self.temp_converter to the given temp_converter.
+    ///
+    /// # Arguments
+    ///
+    /// * `temp_converter` - the convert function to use.
+    pub fn set_temp_converter(&mut self, temp_converter: TempConverter) {
+        self.temp_converter = temp_converter;
+    }
+
     /// Checks whether the speakers are overheated or not according to the previous shutdown time.
     /// The boot time calibration should be skipped when the speakers may be too hot
     /// and the Amp should use the previous calibration value returned by the
@@ -160,35 +205,36 @@
         channel: usize,
         calib_data: CalibData,
     ) -> Result<CalibData> {
-        if !((calib_data.temp as f32) < self.temp_upper_limit
-            && (calib_data.temp as f32) > self.temp_lower_limit)
-        {
+        if calib_data.temp < self.temp_lower_limit || calib_data.temp > self.temp_upper_limit {
             info!("invalid temperature: {}.", calib_data.temp);
             return self
                 .get_previous_calibration_value(channel)
                 .map_err(|_| Error::InvalidTemperature(calib_data.temp));
         }
-        let (datastore_exist, rdc, temp) = match self.get_previous_calibration_value(channel) {
-            Ok(CalibData { rdc, temp }) => (true, rdc, temp),
+        let (datastore_exist, previous_calib) = match self.get_previous_calibration_value(channel) {
+            Ok(previous_calib) => (true, previous_calib),
             Err(e) => {
                 info!("{}, use vpd as previous calibration value", e);
-                let vpd = VPD::new(channel)?;
-                (false, vpd.dsm_calib_r0, vpd.dsm_calib_temp)
+                (false, self.get_vpd_calibration_value(channel)?)
             }
         };
 
-        let diff: f32 = (self.rdc_diff)(calib_data.rdc, rdc);
+        let diff = {
+            let calib_rdc_ohm = (self.rdc_to_ohm)(calib_data.rdc);
+            let previous_rdc_ohm = (self.rdc_to_ohm)(previous_calib.rdc);
+            (calib_rdc_ohm - previous_rdc_ohm) / previous_rdc_ohm
+        };
         if diff > Self::CALI_ERROR_UPPER_LIMIT {
-            Err(Error::LargeCalibrationDiff(calib_data.rdc, calib_data.temp))
+            Err(Error::LargeCalibrationDiff(calib_data))
         } else if diff < Self::CALI_ERROR_LOWER_LIMIT {
             if !datastore_exist {
                 Datastore::UseVPD.save(&self.snd_card, channel)?;
             }
-            Ok(CalibData { rdc, temp })
+            Ok(previous_calib)
         } else {
             Datastore::DSM {
                 rdc: calib_data.rdc,
-                temp: calib_data.temp,
+                temp: (self.temp_converter.celsius_to_vpd)(calib_data.temp),
             }
             .save(&self.snd_card, channel)?;
             Ok(calib_data)
@@ -225,14 +271,19 @@
     fn get_previous_calibration_value(&self, ch: usize) -> Result<CalibData> {
         let sci_calib = Datastore::from_file(&self.snd_card, ch)?;
         match sci_calib {
-            Datastore::UseVPD => {
-                let vpd = VPD::new(ch)?;
-                Ok(CalibData {
-                    rdc: vpd.dsm_calib_r0,
-                    temp: vpd.dsm_calib_temp,
-                })
-            }
-            Datastore::DSM { rdc, temp } => Ok(CalibData { rdc, temp }),
+            Datastore::UseVPD => self.get_vpd_calibration_value(ch),
+            Datastore::DSM { rdc, temp } => Ok(CalibData {
+                rdc,
+                temp: (self.temp_converter.vpd_to_celsius)(temp),
+            }),
         }
     }
+
+    fn get_vpd_calibration_value(&self, channel: usize) -> Result<CalibData> {
+        let vpd = VPD::new(channel)?;
+        Ok(CalibData {
+            rdc: vpd.dsm_calib_r0,
+            temp: (self.temp_converter.vpd_to_celsius)(vpd.dsm_calib_temp),
+        })
+    }
 }