sound_card_init: add Max98373 boot time calibration logic

1. Implement functions to access the DSM calibration data from DSP.
2. Implement max98373 boot time calibration flow as the following
   steps:
   a. Limit the speaker volume to protect the speakers.
   b. Check whether the speakers are overheated.
   c. Trigger the DSM calibration from DSP.
   d. Decide and apply a good calibration value and remove the speaker
      output limitation.

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

Cq-Depend: chromium:2597532
Cq-Depend: chromium:2606416
Change-Id: I7a9ab8064727588fb00de818020a01218ed4d03d
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/adhd/+/2595662
Reviewed-by: Chih-Yang Hsia <paulhsia@chromium.org>
Tested-by: Judy Hsiao <judyhsiao@chromium.org>
Commit-Queue: Judy Hsiao <judyhsiao@chromium.org>
diff --git a/sound_card_init/Cargo.lock b/sound_card_init/Cargo.lock
index d2092cd..959959e 100644
--- a/sound_card_init/Cargo.lock
+++ b/sound_card_init/Cargo.lock
@@ -19,6 +19,7 @@
  "remain",
  "serde",
  "serde_yaml",
+ "sof_sys",
  "sys_util",
 ]
 
@@ -90,9 +91,9 @@
 
 [[package]]
 name = "dtoa"
-version = "0.4.5"
+version = "0.4.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3"
+checksum = "88d7ed2934d741c6b37e33e3832298e8850b53fd2d2bea03873375596c7cea4e"
 
 [[package]]
 name = "getopts"
@@ -105,9 +106,9 @@
 
 [[package]]
 name = "libc"
-version = "0.2.71"
+version = "0.2.82"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49"
+checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929"
 
 [[package]]
 name = "libcras"
@@ -122,15 +123,15 @@
 
 [[package]]
 name = "linked-hash-map"
-version = "0.5.3"
+version = "0.5.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a"
+checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
 
 [[package]]
 name = "pkg-config"
-version = "0.3.17"
+version = "0.3.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
+checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
 
 [[package]]
 name = "poll_token_derive"
@@ -143,27 +144,27 @@
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.18"
+version = "1.0.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa"
+checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
 dependencies = [
  "unicode-xid",
 ]
 
 [[package]]
 name = "quote"
-version = "1.0.6"
+version = "1.0.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea"
+checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
 dependencies = [
  "proc-macro2",
 ]
 
 [[package]]
 name = "remain"
-version = "0.2.1"
+version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99c861227fc40c8da6fdaa3d58144ac84c0537080a43eb1d7d45c28f88dcb888"
+checksum = "70ba1e78fa68412cb93ef642fd4d20b9a941be49ee9333875ebaf13112673ea7"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -172,18 +173,18 @@
 
 [[package]]
 name = "serde"
-version = "1.0.111"
+version = "1.0.119"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9124df5b40cbd380080b2cc6ab894c040a3070d995f5c9dc77e18c34a8ae37d"
+checksum = "9bdd36f49e35b61d49efd8aa7fc068fd295961fd2286d0b2ee9a4c7a14e99cc3"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.111"
+version = "1.0.119"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f2c3ac8e6ca1e9c80b8be1023940162bf81ae3cffbb1809474152f2ce1eb250"
+checksum = "552954ce79a059ddd5fd68c271592374bd15cab2274970380c000118aeffe1cd"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -192,9 +193,9 @@
 
 [[package]]
 name = "serde_yaml"
-version = "0.8.12"
+version = "0.8.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "16c7a592a1ec97c9c1c68d75b6e537dcbf60c7618e038e7841e00af1d9ccf0c4"
+checksum = "971be8f6e4d4a47163b405a3df70d14359186f9ab0f3a3ec37df144ca1ce089f"
 dependencies = [
  "dtoa",
  "linked-hash-map",
@@ -203,6 +204,10 @@
 ]
 
 [[package]]
+name = "sof_sys"
+version = "0.1.0"
+
+[[package]]
 name = "sound_card_init"
 version = "0.1.0"
 dependencies = [
@@ -215,14 +220,15 @@
  "remain",
  "serde",
  "serde_yaml",
+ "sof_sys",
  "sys_util",
 ]
 
 [[package]]
 name = "syn"
-version = "1.0.30"
+version = "1.0.58"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93a56fabc59dce20fe48b6c832cc249c713e7ed88fa28b0ee0a3bfcaae5fe4e2"
+checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -259,21 +265,21 @@
 
 [[package]]
 name = "unicode-width"
-version = "0.1.7"
+version = "0.1.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
+checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
 
 [[package]]
 name = "unicode-xid"
-version = "0.2.0"
+version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
+checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
 
 [[package]]
 name = "yaml-rust"
-version = "0.4.4"
+version = "0.4.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d"
+checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
 dependencies = [
  "linked-hash-map",
 ]
diff --git a/sound_card_init/Cargo.toml b/sound_card_init/Cargo.toml
index 4169455..9d5a107 100644
--- a/sound_card_init/Cargo.toml
+++ b/sound_card_init/Cargo.toml
@@ -5,6 +5,12 @@
 edition = "2018"
 description = "Sound Card Initializer"
 
+[workspace]
+members = [
+    "amp",
+    "dsm"
+]
+
 [dependencies]
 amp = { path = "amp" }
 audio_streams = "*"
@@ -15,6 +21,7 @@
 remain = "0.2.1"
 serde = { version = "1.0", features = ["derive"] }
 serde_yaml = "0.8.11"
+sof_sys = "*"
 sys_util = "*"
 
 [patch.crates-io]
@@ -22,4 +29,5 @@
 cros_alsa = { path = "../cros_alsa" } # ignored by ebuild
 cros_alsa_derive = { path = "../cros_alsa/cros_alsa_derive" } # ignored by ebuild
 libcras = { path = "../cras/client/libcras" }  # ignored by ebuild
+sof_sys = { path = "../sof_sys" }  # ignored by ebuild
 sys_util = { path = "../../../platform/crosvm/sys_util" } # ignored by ebuild
diff --git a/sound_card_init/amp/Cargo.lock b/sound_card_init/amp/Cargo.lock
new file mode 100644
index 0000000..679e60c
--- /dev/null
+++ b/sound_card_init/amp/Cargo.lock
@@ -0,0 +1,254 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "alsa-sys"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "644d308f5822c2b39fba5a6d850f74c208bf73c61d1d2dfad62505d6960e4977"
+dependencies = [
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "amp"
+version = "0.1.0"
+dependencies = [
+ "cros_alsa",
+ "dsm",
+ "libcras",
+ "remain",
+ "serde",
+ "serde_yaml",
+ "sof_sys",
+ "sys_util",
+]
+
+[[package]]
+name = "android_log-sys"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85965b6739a430150bdd138e2374a98af0c3ee0d030b3bb7fc3bddff58d0102e"
+
+[[package]]
+name = "assertions"
+version = "0.1.0"
+
+[[package]]
+name = "audio_streams"
+version = "0.1.0"
+dependencies = [
+ "sync",
+ "sys_util",
+]
+
+[[package]]
+name = "cras-sys"
+version = "0.1.0"
+dependencies = [
+ "audio_streams",
+ "data_model",
+]
+
+[[package]]
+name = "cros_alsa"
+version = "0.1.0"
+dependencies = [
+ "alsa-sys",
+ "cros_alsa_derive",
+ "libc",
+ "remain",
+]
+
+[[package]]
+name = "cros_alsa_derive"
+version = "0.1.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "data_model"
+version = "0.1.0"
+dependencies = [
+ "assertions",
+ "libc",
+]
+
+[[package]]
+name = "dsm"
+version = "0.1.0"
+dependencies = [
+ "audio_streams",
+ "cros_alsa",
+ "libcras",
+ "remain",
+ "serde",
+ "serde_yaml",
+ "sys_util",
+]
+
+[[package]]
+name = "dtoa"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88d7ed2934d741c6b37e33e3832298e8850b53fd2d2bea03873375596c7cea4e"
+
+[[package]]
+name = "libc"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cca32fa0182e8c0989459524dc356b8f2b5c10f1b9eb521b7d182c03cf8c5ff"
+
+[[package]]
+name = "libcras"
+version = "0.1.0"
+dependencies = [
+ "audio_streams",
+ "cras-sys",
+ "data_model",
+ "libc",
+ "sys_util",
+]
+
+[[package]]
+name = "linked-hash-map"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
+
+[[package]]
+name = "poll_token_derive"
+version = "0.1.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "remain"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ba1e78fa68412cb93ef642fd4d20b9a941be49ee9333875ebaf13112673ea7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.123"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.123"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_yaml"
+version = "0.8.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "971be8f6e4d4a47163b405a3df70d14359186f9ab0f3a3ec37df144ca1ce089f"
+dependencies = [
+ "dtoa",
+ "linked-hash-map",
+ "serde",
+ "yaml-rust",
+]
+
+[[package]]
+name = "sof_sys"
+version = "0.1.0"
+
+[[package]]
+name = "syn"
+version = "1.0.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "sync"
+version = "0.1.0"
+
+[[package]]
+name = "sys_util"
+version = "0.1.0"
+dependencies = [
+ "android_log-sys",
+ "data_model",
+ "libc",
+ "poll_token_derive",
+ "sync",
+ "syscall_defines",
+ "tempfile",
+]
+
+[[package]]
+name = "syscall_defines"
+version = "0.1.0"
+
+[[package]]
+name = "tempfile"
+version = "3.0.7"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
+
+[[package]]
+name = "yaml-rust"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
+dependencies = [
+ "linked-hash-map",
+]
diff --git a/sound_card_init/amp/Cargo.toml b/sound_card_init/amp/Cargo.toml
index b7cfe85..f912bcf 100644
--- a/sound_card_init/amp/Cargo.toml
+++ b/sound_card_init/amp/Cargo.toml
@@ -11,4 +11,5 @@
 remain = "0.2.1"
 serde = { version = "1.0", features = ["derive"]}
 serde_yaml = "0.8.11"
+sof_sys = "*"
 sys_util = "*"
diff --git a/sound_card_init/amp/src/lib.rs b/sound_card_init/amp/src/lib.rs
index 4c489a3..7486ff0 100644
--- a/sound_card_init/amp/src/lib.rs
+++ b/sound_card_init/amp/src/lib.rs
@@ -5,10 +5,12 @@
 //! to create `Amp` objects.
 #![deny(missing_docs)]
 
+mod max98373d;
 mod max98390d;
 
 use dsm::Error;
 
+use max98373d::Max98373;
 use max98390d::Max98390;
 
 type Result<T> = std::result::Result<T, Error>;
@@ -29,6 +31,7 @@
     pub fn build(&self) -> Result<Box<dyn Amp>> {
         match self.sound_card_id {
             "sofcmlmax98390d" => Ok(Box::new(Max98390::new(self.sound_card_id)?) as Box<dyn Amp>),
+            "sofrt5682" => Ok(Box::new(Max98373::new(self.sound_card_id)?) as Box<dyn Amp>),
             _ => Err(Error::UnsupportedSoundCard(self.sound_card_id.to_owned())),
         }
     }
diff --git a/sound_card_init/amp/src/max98373d/dsm_param.rs b/sound_card_init/amp/src/max98373d/dsm_param.rs
new file mode 100644
index 0000000..d925752
--- /dev/null
+++ b/sound_card_init/amp/src/max98373d/dsm_param.rs
@@ -0,0 +1,211 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+use std::mem;
+
+use cros_alsa::{Card, TLV};
+use sof_sys::sof_abi_hdr;
+
+use dsm::{self, Error, Result};
+
+/// Amp volume mode enumeration used by set_volume().
+#[derive(Copy, Clone, PartialEq)]
+pub enum VolumeMode {
+    /// Low mode protects the speaker by limiting its output volume if the
+    /// calibration has not been completed successfully.
+    Low = 0x1009B9CF,
+    /// High mode removes the speaker output volume limitation after
+    /// having successfully completed the calibration.
+    High = 0x20000000,
+}
+
+#[derive(Copy, Clone)]
+/// Calibration mode enumeration.
+pub enum CalibMode {
+    ON = 0x4,
+    OFF = 0x1,
+}
+
+#[derive(Copy, Clone)]
+/// Smart pilot signal mode mode enumeration.
+pub enum SPTMode {
+    ON = 0x1,
+    OFF = 0x0,
+}
+
+#[derive(Copy, Clone)]
+/// DSM Parem field enumeration.
+enum DsmAPI {
+    ParamCount = 0x0,
+    CalibMode = 0x1,
+    MakeupGain = 0x5,
+    DsmRdc = 0x6,
+    DsmAmbientTemp = 0x8,
+    AdaptiveRdc = 0x12,
+    SPTMode = 0x68,
+}
+
+#[derive(Debug)]
+/// It implements functions to access the `DSMParam` fields.
+pub struct DSMParam {
+    param_count: usize,
+    num_channels: usize,
+    tlv: TLV,
+}
+
+impl DSMParam {
+    const DWORD_PER_PARAM: usize = 2;
+    const VALUE_OFFSET: usize = 1;
+    const SOF_HEADER_SIZE: usize = mem::size_of::<sof_abi_hdr>() / mem::size_of::<i32>();
+
+    /// Creates an `DSMParam`.
+    /// # Arguments
+    ///
+    /// * `card` - `&Card`.
+    /// * `num_channels` - number of channels.
+    /// * `ctl_name` - the mixer control name to access the DSM param.
+    ///
+    /// # Results
+    ///
+    /// * `DSMParam` - It is initialized by the content of the given byte control .
+    ///
+    /// # Errors
+    ///
+    /// * If `Card` creation from sound card name fails.
+    pub fn new(card: &mut Card, num_channels: usize, ctl_name: &str) -> Result<Self> {
+        let tlv = card.control_tlv_by_name(ctl_name)?.load()?;
+        Self::try_from_tlv(tlv, num_channels)
+    }
+
+    /// Sets DSMParam to the given calibration mode.
+    pub fn set_calibration_mode(&mut self, mode: CalibMode) {
+        for channel in 0..self.num_channels {
+            self.set(channel, DsmAPI::CalibMode, mode as i32);
+        }
+    }
+
+    /// Sets DSMParam to the given smart pilot signal mode.
+    pub fn set_spt_mode(&mut self, mode: SPTMode) {
+        for channel in 0..self.num_channels {
+            self.set(channel, DsmAPI::SPTMode, mode as i32);
+        }
+    }
+
+    /// Sets DSMParam to the given VolumeMode.
+    pub fn set_volume_mode(&mut self, mode: VolumeMode) {
+        for channel in 0..self.num_channels {
+            self.set(channel, DsmAPI::MakeupGain, mode as i32);
+        }
+    }
+
+    /// Reads the calibrated rdc from DSMParam.
+    pub fn get_adaptive_rdc(&self) -> Vec<i32> {
+        self.get(DsmAPI::AdaptiveRdc)
+    }
+
+    /// Sets DSMParam to the given the calibrated rdc.
+    pub fn set_rdc(&mut self, ch: usize, rdc: i32) {
+        self.set(ch, DsmAPI::DsmRdc, rdc);
+    }
+
+    /// Sets DSMParam to the given calibrated temp.
+    pub fn set_ambient_temp(&mut self, ch: usize, temp: i32) {
+        self.set(ch, DsmAPI::DsmAmbientTemp, temp);
+    }
+
+    /// Sets the `id` field to the given `val`.
+    fn set(&mut self, channel: usize, id: DsmAPI, val: i32) {
+        let pos = Self::value_pos(self.param_count, channel, id);
+        self.tlv[pos] = val as u32;
+    }
+
+    /// Gets the val from the `id` field from all the channels.
+    fn get(&self, id: DsmAPI) -> Vec<i32> {
+        (0..self.num_channels)
+            .map(|channel| {
+                let pos = Self::value_pos(self.param_count, channel, id);
+                self.tlv[pos] as i32
+            })
+            .collect()
+    }
+
+    fn try_from_tlv(tlv: TLV, num_channels: usize) -> Result<Self> {
+        let param_count_pos = Self::value_pos(0, 0, DsmAPI::ParamCount);
+
+        if tlv.len() < param_count_pos {
+            return Err(Error::InvalidDSMParam);
+        }
+
+        let param_count = tlv[param_count_pos] as usize;
+
+        if tlv.len() != Self::SOF_HEADER_SIZE + param_count * num_channels * Self::DWORD_PER_PARAM {
+            return Err(Error::InvalidDSMParam);
+        }
+
+        Ok(Self {
+            param_count,
+            num_channels,
+            tlv,
+        })
+    }
+
+    #[inline]
+    fn value_pos(param_count: usize, channel: usize, id: DsmAPI) -> usize {
+        Self::SOF_HEADER_SIZE
+            + (channel * param_count + id as usize) * Self::DWORD_PER_PARAM
+            + Self::VALUE_OFFSET
+    }
+}
+
+impl Into<TLV> for DSMParam {
+    fn into(self) -> TLV {
+        self.tlv
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    const PARAM_COUNT: usize = 138;
+    const CHANNEL_COUNT: usize = 2;
+
+    #[test]
+    fn test_dsmparam_try_from_tlv_ok() {
+        let mut data = vec![
+            0u32;
+            DSMParam::SOF_HEADER_SIZE
+                + CHANNEL_COUNT * PARAM_COUNT * DSMParam::DWORD_PER_PARAM
+        ];
+        data[DSMParam::value_pos(PARAM_COUNT, 0, DsmAPI::ParamCount)] = PARAM_COUNT as u32;
+        data[DSMParam::value_pos(PARAM_COUNT, 1, DsmAPI::ParamCount)] = PARAM_COUNT as u32;
+
+        let tlv = TLV::new(0, data);
+        assert!(DSMParam::try_from_tlv(tlv, CHANNEL_COUNT).is_ok());
+    }
+
+    #[test]
+    fn test_dsmparam_try_from_invalid_len() {
+        let data = vec![0u32; DSMParam::SOF_HEADER_SIZE];
+
+        let tlv = TLV::new(0, data);
+        assert_eq!(
+            DSMParam::try_from_tlv(tlv, CHANNEL_COUNT).unwrap_err(),
+            Error::InvalidDSMParam
+        );
+    }
+
+    #[test]
+    fn test_dsmparam_try_from_param_count() {
+        let data = vec![
+            0u32;
+            DSMParam::SOF_HEADER_SIZE
+                + CHANNEL_COUNT * PARAM_COUNT * DSMParam::DWORD_PER_PARAM
+        ];
+
+        let tlv = TLV::new(0, data);
+        assert_eq!(
+            DSMParam::try_from_tlv(tlv, CHANNEL_COUNT).unwrap_err(),
+            Error::InvalidDSMParam
+        );
+    }
+}
diff --git a/sound_card_init/amp/src/max98373d/mod.rs b/sound_card_init/amp/src/max98373d/mod.rs
new file mode 100644
index 0000000..166f64e
--- /dev/null
+++ b/sound_card_init/amp/src/max98373d/mod.rs
@@ -0,0 +1,267 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//! `max98373d` module implements the required initialization workflows for sound
+//! cards that use max98373d smart amp.
+//! It currently supports boot time calibration for max98373d.
+#![deny(missing_docs)]
+mod dsm_param;
+mod settings;
+
+use std::path::{Path, PathBuf};
+use std::time::Duration;
+use std::{fs, thread};
+
+use cros_alsa::{Card, IntControl};
+use dsm::{CalibData, Error, Result, SpeakerStatus, ZeroPlayer, DSM};
+
+use crate::{Amp, CONF_DIR};
+use dsm_param::*;
+use settings::{AmpCalibSettings, DeviceSettings};
+
+/// It implements the amplifier boot time calibration flow.
+pub struct Max98373 {
+    card: Card,
+    setting: AmpCalibSettings,
+}
+
+impl Amp for Max98373 {
+    /// Performs max98373d boot time calibration.
+    ///
+    /// # Errors
+    ///
+    /// If any amplifiers fail to complete the calibration.
+    fn boot_time_calibration(&mut self) -> Result<()> {
+        if !Path::new(&self.setting.dsm_param).exists() {
+            return Err(Error::MissingDSMParam);
+        }
+
+        let num_channels = self.setting.num_channels();
+        let dsm = DSM::new(
+            &self.card.name(),
+            num_channels,
+            Self::rdc_to_ohm,
+            Self::TEMP_UPPER_LIMIT_CELSIUS,
+            Self::TEMP_LOWER_LIMIT_CELSIUS,
+        );
+        self.set_volume(VolumeMode::Low)?;
+
+        let calib = match dsm.check_speaker_over_heated_workflow()? {
+            SpeakerStatus::Hot(previous_calib) => previous_calib,
+            SpeakerStatus::Cold => {
+                let all_temp = self.get_ambient_temp()?;
+                let all_rdc = self.do_rdc_calibration()?;
+                all_rdc
+                    .iter()
+                    .zip(all_temp)
+                    .enumerate()
+                    .map(|(ch, (&rdc, temp))| {
+                        dsm.decide_calibration_value_workflow(ch, CalibData { rdc, temp })
+                    })
+                    .collect::<Result<Vec<_>>>()?
+            }
+        };
+        self.apply_calibration_value(&calib)?;
+        self.set_volume(VolumeMode::High)?;
+        Ok(())
+    }
+}
+
+impl Max98373 {
+    const TEMP_CALIB_WARM_UP_TIME: Duration = Duration::from_millis(10);
+    const RDC_CALIB_WARM_UP_TIME: Duration = Duration::from_millis(500);
+    const RDC_CALIB_INTERVAL: Duration = Duration::from_millis(200);
+    const CALIB_REPEAT_TIMES: usize = 5;
+
+    const TEMP_UPPER_LIMIT_CELSIUS: f32 = 40.0;
+    const TEMP_LOWER_LIMIT_CELSIUS: f32 = 0.0;
+
+    /// Creates an `Max98373`.
+    /// # Arguments
+    ///
+    /// * `card_name` - card_name.
+    ///
+    /// # Results
+    ///
+    /// * `Max98373` - It implements the Max98373 functions of boot time calibration.
+    ///
+    /// # Errors
+    ///
+    /// * If `Card` creation from sound card name fails.
+    pub fn new(card_name: &str) -> Result<Self> {
+        let config_path = PathBuf::from(CONF_DIR)
+            .join(card_name)
+            .with_extension("yaml");
+        let conf =
+            fs::read_to_string(&config_path).map_err(|e| Error::FileIOFailed(config_path, e))?;
+        let settings = DeviceSettings::from_yaml_str(&conf)?;
+        Ok(Self {
+            card: Card::new(card_name)?,
+            setting: settings.amp_calibrations,
+        })
+    }
+
+    /// Triggers the amplifier calibration and reads the calibrated rdc.
+    /// To get accurate calibration results, the main thread calibrates the amplifier while
+    /// the `zero_player` starts another thread to play zeros to the speakers.
+    fn do_rdc_calibration(&mut self) -> Result<Vec<i32>> {
+        let mut zero_player: ZeroPlayer = Default::default();
+        zero_player.start(Self::RDC_CALIB_WARM_UP_TIME)?;
+        // Playback of zeros is started for Self::RDC_CALIB_WARM_UP_TIME, and the main thread
+        // can start the calibration.
+        self.set_spt_mode(SPTMode::OFF)?;
+        self.set_calibration_mode(CalibMode::ON)?;
+        // Playback of zeros is started, and the main thread can start the calibration.
+        let mut avg_rdc = vec![0; self.setting.num_channels()];
+        for _ in 0..Self::CALIB_REPEAT_TIMES {
+            let rdc = self.get_adaptive_rdc()?;
+            for i in 0..self.setting.num_channels() {
+                avg_rdc[i] += rdc[i];
+            }
+            thread::sleep(Self::RDC_CALIB_INTERVAL);
+        }
+        self.set_spt_mode(SPTMode::ON)?;
+        self.set_calibration_mode(CalibMode::OFF)?;
+        zero_player.stop()?;
+
+        avg_rdc = avg_rdc
+            .iter()
+            .map(|val| val / Self::CALIB_REPEAT_TIMES as i32)
+            .collect();
+        Ok(avg_rdc)
+    }
+
+    /// Sets the card volume control to the given VolumeMode.
+    fn set_volume(&mut self, mode: VolumeMode) -> Result<()> {
+        let mut dsm_param = DSMParam::new(
+            &mut self.card,
+            self.setting.num_channels(),
+            &self.setting.dsm_param_read_ctrl,
+        )?;
+
+        dsm_param.set_volume_mode(mode);
+
+        self.card
+            .control_tlv_by_name(&self.setting.dsm_param_write_ctrl)?
+            .save(dsm_param.into())
+            .map_err(Error::DSMParamUpdateFailed)?;
+        Ok(())
+    }
+
+    /// Applies the calibration value to the amp.
+    fn apply_calibration_value(&mut self, calib: &[CalibData]) -> Result<()> {
+        let mut dsm_param = DSMParam::new(
+            &mut self.card,
+            self.setting.num_channels(),
+            &self.setting.dsm_param_read_ctrl,
+        )?;
+        for ch in 0..self.setting.num_channels() {
+            dsm_param.set_rdc(ch, calib[ch].rdc);
+            dsm_param.set_ambient_temp(ch, Self::celsius_to_dsm_unit(calib[ch].temp));
+        }
+        self.card
+            .control_tlv_by_name(&self.setting.dsm_param_write_ctrl)?
+            .save(dsm_param.into())
+            .map_err(Error::DSMParamUpdateFailed)?;
+        Ok(())
+    }
+
+    /// Rdc (ohm) = [ID:0x12] * 3.66 / 2^27
+    #[inline]
+    fn rdc_to_ohm(x: i32) -> f32 {
+        (3.66 * x as f32) / (1 << 27) as f32
+    }
+
+    /// Returns the ambient temperature in celsius degree.
+    fn get_ambient_temp(&mut self) -> Result<Vec<f32>> {
+        let mut zero_player: ZeroPlayer = Default::default();
+        zero_player.start(Self::TEMP_CALIB_WARM_UP_TIME)?;
+        let mut temps = Vec::new();
+        for x in 0..self.setting.num_channels() as usize {
+            let temp = self
+                .card
+                .control_by_name::<IntControl>(&self.setting.temp_ctrl[x])?
+                .get()?;
+            let celsius = Self::measured_temp_to_celsius(temp);
+            temps.push(celsius);
+        }
+        zero_player.stop()?;
+
+        Ok(temps)
+    }
+
+    /// Converts the measured ambient temperature to celsius unit.
+    #[inline]
+    fn measured_temp_to_celsius(temp: i32) -> f32 {
+        // Measured Temperature (°C) = ([Mixer Val] * 1.28) - 29
+        (temp as f32 * 1.28) - 29.0
+    }
+
+    /// Converts the ambient temperature from celsius to the DsmSetAPI::DsmAmbientTemp unit.
+    #[inline]
+    fn celsius_to_dsm_unit(celsius: f32) -> i32 {
+        // Temperature (℃) = [ID:0x12] / 2^19
+        (celsius * (1 << 19) as f32) as i32
+    }
+
+    /// Sets the amp to the given smart pilot signal mode.
+    fn set_spt_mode(&mut self, mode: SPTMode) -> Result<()> {
+        let mut dsm_param = DSMParam::new(
+            &mut self.card,
+            self.setting.num_channels(),
+            &self.setting.dsm_param_read_ctrl,
+        )?;
+        dsm_param.set_spt_mode(mode);
+        self.card
+            .control_tlv_by_name(&self.setting.dsm_param_write_ctrl)?
+            .save(dsm_param.into())
+            .map_err(Error::DSMParamUpdateFailed)?;
+        Ok(())
+    }
+
+    /// Sets the amp to the given the calibration mode.
+    fn set_calibration_mode(&mut self, mode: CalibMode) -> Result<()> {
+        let mut dsm_param = DSMParam::new(
+            &mut self.card,
+            self.setting.num_channels(),
+            &self.setting.dsm_param_read_ctrl,
+        )?;
+        dsm_param.set_calibration_mode(mode);
+        self.card
+            .control_tlv_by_name(&self.setting.dsm_param_write_ctrl)?
+            .save(dsm_param.into())
+            .map_err(Error::DSMParamUpdateFailed)?;
+        Ok(())
+    }
+
+    /// Reads the calibrated rdc.
+    /// Must be called when the calibration mode in on.
+    fn get_adaptive_rdc(&mut self) -> Result<Vec<i32>> {
+        let dsm_param = DSMParam::new(
+            &mut self.card,
+            self.setting.num_channels(),
+            &self.setting.dsm_param_read_ctrl,
+        )?;
+        Ok(dsm_param.get_adaptive_rdc())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    #[test]
+    fn celsius_to_dsm_unit() {
+        assert_eq!(Max98373::celsius_to_dsm_unit(37.0), 0x01280000);
+        assert_eq!(Max98373::celsius_to_dsm_unit(50.0), 0x01900000);
+    }
+
+    #[test]
+    fn rdc_to_ohm() {
+        assert_eq!(Max98373::rdc_to_ohm(0x05cea0c7), 2.656767);
+    }
+
+    #[test]
+    fn measured_temp_to_celsius() {
+        assert_eq!(Max98373::measured_temp_to_celsius(56), 42.68);
+    }
+}
diff --git a/sound_card_init/amp/src/max98373d/settings.rs b/sound_card_init/amp/src/max98373d/settings.rs
new file mode 100644
index 0000000..b944961
--- /dev/null
+++ b/sound_card_init/amp/src/max98373d/settings.rs
@@ -0,0 +1,40 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+use std::string::String;
+
+use dsm::{self, Error, Result};
+use serde::Deserialize;
+/// `DeviceSettings` includes the settings of max98373. It currently includes:
+/// * the settings of amplifier calibration.
+/// * the path of dsm_param.
+#[derive(Debug, Default, PartialEq, Deserialize, Clone)]
+pub struct DeviceSettings {
+    pub amp_calibrations: AmpCalibSettings,
+}
+
+/// `AmpCalibSettings` includes the settings needed for amplifier calibration.
+#[derive(Debug, Default, PartialEq, Deserialize, Clone)]
+pub struct AmpCalibSettings {
+    pub dsm_param_read_ctrl: String,
+    pub dsm_param_write_ctrl: String,
+    pub temp_ctrl: Vec<String>,
+    // Path of the dsm_param.bin file.
+    pub dsm_param: String,
+}
+
+impl AmpCalibSettings {
+    /// Returns the number of channels.
+    pub fn num_channels(&self) -> usize {
+        self.temp_ctrl.len()
+    }
+}
+
+impl DeviceSettings {
+    /// Creates a `DeviceSettings` from a yaml str.
+    pub fn from_yaml_str(conf: &str) -> Result<DeviceSettings> {
+        let settings: DeviceSettings = serde_yaml::from_str(conf)
+            .map_err(|e| Error::DeserializationFailed("DeviceSettings".to_owned(), e))?;
+        Ok(settings)
+    }
+}
diff --git a/sound_card_init/dsm/src/error.rs b/sound_card_init/dsm/src/error.rs
index b93cba1..4b6e8dc 100644
--- a/sound_card_init/dsm/src/error.rs
+++ b/sound_card_init/dsm/src/error.rs
@@ -21,9 +21,11 @@
 pub enum Error {
     AlsaCardError(cros_alsa::CardError),
     AlsaControlError(cros_alsa::ControlError),
+    AlsaControlTLVError(cros_alsa::ControlTLVError),
     CalibrationTimeout,
     CrasClientFailed(libcras::Error),
     DeserializationFailed(String, serde_yaml::Error),
+    DSMParamUpdateFailed(cros_alsa::ControlTLVError),
     FileIOFailed(PathBuf, io::Error),
     InternalSpeakerNotFound,
     InvalidDatastore,
@@ -46,6 +48,16 @@
     ZeroPlayerIsRunning,
 }
 
+impl PartialEq for Error {
+    // Implement eq for more Error when needed.
+    fn eq(&self, other: &Error) -> bool {
+        match (self, other) {
+            (Error::InvalidDSMParam, Error::InvalidDSMParam) => true,
+            _ => false,
+        }
+    }
+}
+
 impl From<cros_alsa::CardError> for Error {
     fn from(err: cros_alsa::CardError) -> Error {
         Error::AlsaCardError(err)
@@ -58,6 +70,12 @@
     }
 }
 
+impl From<cros_alsa::ControlTLVError> for Error {
+    fn from(err: cros_alsa::ControlTLVError) -> Error {
+        Error::AlsaControlTLVError(err)
+    }
+}
+
 impl<T> From<PoisonError<T>> for Error {
     fn from(_: PoisonError<T>) -> Error {
         Error::MutexPoisonError
@@ -70,9 +88,11 @@
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         use Error::*;
         match self {
-            AlsaCardError(e) => write!(f, "{}", e),
-            AlsaControlError(e) => write!(f, "{}", e),
+            AlsaCardError(e) => write!(f, "AlsaCardError: {}", e),
+            AlsaControlError(e) => write!(f, "AlsaControlError: {}", e),
+            AlsaControlTLVError(e) => write!(f, "AlsaControlTLVError: {}", e),
             CalibrationTimeout => write!(f, "calibration is not finished in time"),
+            DSMParamUpdateFailed(e) => write!(f, "failed to update DsmParam, err: {}", e),
             CrasClientFailed(e) => write!(f, "failed to create cras client: {}", e),
             DeserializationFailed(file_path, e) => {
                 write!(f, "failed to parse {}: {}", file_path, e)
diff --git a/sound_card_init/seccomp/sound_card_init-seccomp-amd64.policy b/sound_card_init/seccomp/sound_card_init-seccomp-amd64.policy
index b1bcd6b..0404924 100644
--- a/sound_card_init/seccomp/sound_card_init-seccomp-amd64.policy
+++ b/sound_card_init/seccomp/sound_card_init-seccomp-amd64.policy
@@ -2,78 +2,79 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-lseek: 1
-read: 1
+access: 1
+arch_prctl: 1
+bind: 1
+brk: 1
+clone: 1
 close: 1
-openat: 1
-mmap: arg2 in ~PROT_EXEC || arg2 in ~PROT_WRITE
-fstat: 1
-rt_sigaction: 1
-prlimit64: 1
-mprotect: arg2 in ~PROT_EXEC || arg2 in ~PROT_WRITE
-ioctl: arg1 == 0x5401 || arg1 == 0xc4c85512 || arg1 == 0x540f || arg1 == 0x80045500 || arg1 == 0xc4c85513 || arg1 == 0x81785501 || arg1 == 0x5413 || arg1 == 0xc1105511
-stat: 1
-munmap: 1
-write: 1
-setresuid: 1
-recvmsg: 1
-fcntl: 1
-futex: 1
+connect: 1
+dup2: 1
+dup: 1
 epoll_create1: 1
 epoll_ctl: 1
 epoll_wait: 1
-setresgid: 1
-sigaltstack: 1
-rt_sigprocmask: 1
-access: 1
-getuid: 1
-brk: 1
-getpid: 1
-socket: arg0 == 0x10 || arg0 == 0x1
-getdents: 1
-recvfrom: 1
-set_robust_list: 1
-umask: 1
-unlink: 1
-sendto: 1
-setgroups: 1
-connect: 1
-geteuid: 1
-prctl: arg0 == 0x3 || arg0 == 0x4
-clone: 1
-dup: 1
-sched_getaffinity: 1
 execve: 1
-arch_prctl: 1
-set_tid_address: 1
-getgroups: 1
-getresuid: 1
-getresgid: 1
-pipe2: 1
-ppoll: 1
-statx: 1
-socketpair: 1
-sendmsg: 1
-madvise: 1
 exit: 1
 exit_group: 1
-getppid: 1
-getpgid: 1
-getsid: 1
-getgid: 1
-getegid: 1
+fcntl: 1
+fstat: 1
+futex: 1
 getcwd: 1
-uname: 1
-bind: 1
-getsockname: 1
-setuid: 1
-setgid: 1
-getpriority: 1
-setpriority: 1
+getdents: 1
+getegid: 1
+geteuid: 1
+getgid: 1
+getgroups: 1
+getpgid: 1
 getpgrp: 1
-dup2: 1
+getpid: 1
+getppid: 1
+getpriority: 1
 getrandom: 1
-rt_sigreturn: 1
-wait4: 1
+getresgid: 1
+getresuid: 1
+getsid: 1
+getsockname: 1
+getuid: 1
+ioctl: arg1 == 0x5401 || arg1 == 0xc4c85512 || arg1 == 0x540f || arg1 == 0x80045500 || arg1 == 0xc4c85513 || arg1 == 0x81785501 || arg1 == 0x5413 || arg1 == 0xc1105511 || arg1 == 0x81785501 || arg1 == 0x80045500 || arg1 == 0xc008551a || arg1 == 0xc4c85512 || arg1 == 0xc008551b || arg1 == 0xc1105511
+lseek: 1
+madvise: 1
+mmap: arg2 in ~PROT_EXEC || arg2 in ~PROT_WRITE
+mprotect: arg2 in ~PROT_EXEC || arg2 in ~PROT_WRITE
+munmap: 1
+nanosleep: 1
+openat: 1
+pipe2: 1
+ppoll: 1
+prctl: arg0 == 0x3 || arg0 == 0x4
+prlimit64: 1
+read: 1
+recvfrom: 1
+recvmsg: 1
 restart_syscall: 1
-sched_yield: 1
\ No newline at end of file
+rt_sigaction: 1
+rt_sigprocmask: 1
+rt_sigreturn: 1
+sched_getaffinity: 1
+sched_yield: 1
+sendmsg: 1
+sendto: 1
+set_robust_list: 1
+set_tid_address: 1
+setgid: 1
+setgroups: 1
+setpriority: 1
+setresgid: 1
+setresuid: 1
+setuid: 1
+sigaltstack: 1
+socket: arg0 == 0x10 || arg0 == 0x1
+socketpair: 1
+stat: 1
+statx: 1
+umask: 1
+uname: 1
+unlink: 1
+wait4: 1
+write: 1