Merge remote-tracking branch 'aosp/upstream-main' into master am: 072dce8ca5 am: bcf1f249f1 am: eebbf2e2b4

Original change: https://android-review.googlesource.com/c/platform/external/adhd/+/1673705

Change-Id: I430a839193d1532b4d541a388e2a1c2be9a30f69
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index f8818dc..e8af785 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -7,10 +7,10 @@
 
 on:
   push:
-    branches: [master]
+    branches: [main]
   pull_request:
     # The branches below must be a subset of the branches above
-    branches: [master]
+    branches: [main]
   schedule:
     - cron: '0 11 * * 1'
 
diff --git a/audio_streams/src/audio_streams.rs b/audio_streams/src/audio_streams.rs
index 5290357..e5fc83c 100644
--- a/audio_streams/src/audio_streams.rs
+++ b/audio_streams/src/audio_streams.rs
@@ -341,8 +341,9 @@
 impl PlaybackBufferStream for NoopStream {
     fn next_playback_buffer(&mut self) -> Result<PlaybackBuffer, BoxError> {
         if let Some(start_time) = self.start_time {
-            if start_time.elapsed() < self.next_frame {
-                std::thread::sleep(self.next_frame - start_time.elapsed());
+            let elapsed = start_time.elapsed();
+            if elapsed < self.next_frame {
+                std::thread::sleep(self.next_frame - elapsed);
             }
             self.next_frame += self.interval;
         } else {
@@ -358,19 +359,6 @@
 }
 
 /// No-op control for `NoopStream`s.
-/// Should be deprecated once all existing use of DummyStreamControl removed.
-#[derive(Default)]
-pub struct DummyStreamControl;
-
-impl DummyStreamControl {
-    pub fn new() -> Self {
-        DummyStreamControl {}
-    }
-}
-
-impl StreamControl for DummyStreamControl {}
-
-/// No-op control for `NoopStream`s.
 #[derive(Default)]
 pub struct NoopStreamControl;
 
diff --git a/audio_streams/src/capture.rs b/audio_streams/src/capture.rs
index 930f182..6a32cf1 100644
--- a/audio_streams/src/capture.rs
+++ b/audio_streams/src/capture.rs
@@ -157,8 +157,9 @@
 impl CaptureBufferStream for NoopCaptureStream {
     fn next_capture_buffer(&mut self) -> Result<CaptureBuffer, BoxError> {
         if let Some(start_time) = self.start_time {
-            if start_time.elapsed() < self.next_frame {
-                std::thread::sleep(self.next_frame - start_time.elapsed());
+            let elapsed = start_time.elapsed();
+            if elapsed < self.next_frame {
+                std::thread::sleep(self.next_frame - elapsed);
             }
             self.next_frame += self.interval;
         } else {
diff --git a/audio_streams/src/shm_streams.rs b/audio_streams/src/shm_streams.rs
index 13475cd..b11626f 100644
--- a/audio_streams/src/shm_streams.rs
+++ b/audio_streams/src/shm_streams.rs
@@ -271,10 +271,10 @@
         self.frame_rate
     }
 
-    fn wait_for_next_action_with_timeout<'a>(
-        &'a mut self,
+    fn wait_for_next_action_with_timeout(
+        &mut self,
         timeout: Duration,
-    ) -> GenericResult<Option<ServerRequest<'a>>> {
+    ) -> GenericResult<Option<ServerRequest>> {
         let elapsed = self.start_time.elapsed();
         if elapsed < self.next_frame {
             if timeout < self.next_frame - elapsed {
@@ -399,10 +399,10 @@
         self.frame_rate
     }
 
-    fn wait_for_next_action_with_timeout<'a>(
-        &'a mut self,
+    fn wait_for_next_action_with_timeout(
+        &mut self,
         timeout: Duration,
-    ) -> GenericResult<Option<ServerRequest<'a>>> {
+    ) -> GenericResult<Option<ServerRequest>> {
         {
             let start_time = Instant::now();
             let &(ref lock, ref cvar) = &*self.request_notifier;
diff --git a/cras/README.dbus-api b/cras/README.dbus-api
index c55a8df..f347358 100644
--- a/cras/README.dbus-api
+++ b/cras/README.dbus-api
@@ -236,7 +236,3 @@
 		HotwordTriggered(int64 tv_sec, int64 tv_nsec)
 
 			Indicates that hotword was triggered at the given timestamp.
-
-		BluetoothBatteryChanged(string address, uint32 level)
-
-			Indicates the battery level of a bluetooth device changed.
diff --git a/cras/client/cras-sys/src/gen.rs b/cras/client/cras-sys/src/gen.rs
index 0375a0b..6fb4cdf 100644
--- a/cras/client/cras-sys/src/gen.rs
+++ b/cras/client/cras-sys/src/gen.rs
@@ -748,7 +748,9 @@
     CRAS_CAPTURE = 2,
     CRAS_VMS_LEGACY = 3,
     CRAS_VMS_UNIFIED = 4,
-    CRAS_NUM_CONN_TYPE = 5,
+    CRAS_PLUGIN_PLAYBACK = 5,
+    CRAS_PLUGIN_UNIFIED = 6,
+    CRAS_NUM_CONN_TYPE = 7,
 }
 #[repr(u32)]
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
@@ -798,6 +800,9 @@
     CRAS_CLIENT_TYPE_CROSVM = 6,
     CRAS_CLIENT_TYPE_SERVER_STREAM = 7,
     CRAS_CLIENT_TYPE_LACROS = 8,
+    CRAS_CLIENT_TYPE_PLUGIN = 9,
+    CRAS_CLIENT_TYPE_ARCVM = 10,
+    CRAS_NUM_CLIENT_TYPE = 11,
 }
 impl CRAS_STREAM_EFFECT {
     pub const APM_ECHO_CANCELLATION: CRAS_STREAM_EFFECT = CRAS_STREAM_EFFECT(1);
@@ -914,24 +919,25 @@
     BT_A2DP_START = 6,
     BT_A2DP_SUSPENDED = 7,
     BT_CODEC_SELECTION = 8,
-    BT_DEV_CONNECTED_CHANGE = 9,
-    BT_DEV_CONN_WATCH_CB = 10,
-    BT_DEV_SUSPEND_CB = 11,
-    BT_HFP_NEW_CONNECTION = 12,
-    BT_HFP_REQUEST_DISCONNECT = 13,
-    BT_HFP_SUPPORTED_FEATURES = 14,
-    BT_HFP_HF_INDICATOR = 15,
-    BT_HFP_SET_SPEAKER_GAIN = 16,
-    BT_HFP_UPDATE_SPEAKER_GAIN = 17,
-    BT_HSP_NEW_CONNECTION = 18,
-    BT_HSP_REQUEST_DISCONNECT = 19,
-    BT_NEW_AUDIO_PROFILE_AFTER_CONNECT = 20,
-    BT_RESET = 21,
-    BT_SCO_CONNECT = 22,
-    BT_TRANSPORT_ACQUIRE = 23,
-    BT_TRANSPORT_RELEASE = 24,
-    BT_TRANSPORT_SET_VOLUME = 25,
-    BT_TRANSPORT_UPDATE_VOLUME = 26,
+    BT_DEV_CONNECTED = 9,
+    BT_DEV_DISCONNECTED = 10,
+    BT_DEV_CONN_WATCH_CB = 11,
+    BT_DEV_SUSPEND_CB = 12,
+    BT_HFP_NEW_CONNECTION = 13,
+    BT_HFP_REQUEST_DISCONNECT = 14,
+    BT_HFP_SUPPORTED_FEATURES = 15,
+    BT_HFP_HF_INDICATOR = 16,
+    BT_HFP_SET_SPEAKER_GAIN = 17,
+    BT_HFP_UPDATE_SPEAKER_GAIN = 18,
+    BT_HSP_NEW_CONNECTION = 19,
+    BT_HSP_REQUEST_DISCONNECT = 20,
+    BT_NEW_AUDIO_PROFILE_AFTER_CONNECT = 21,
+    BT_RESET = 22,
+    BT_SCO_CONNECT = 23,
+    BT_TRANSPORT_ACQUIRE = 24,
+    BT_TRANSPORT_RELEASE = 25,
+    BT_TRANSPORT_SET_VOLUME = 26,
+    BT_TRANSPORT_UPDATE_VOLUME = 27,
 }
 #[repr(C, packed)]
 #[derive(Debug, Copy, Clone)]
@@ -2117,12 +2123,15 @@
     pub bt_wbs_enabled: i32,
     pub deprioritize_bt_wbs_mic: i32,
     pub main_thread_debug_info: main_thread_debug_info,
+    pub num_input_streams_with_permission: [u32; 11usize],
+    pub noise_cancellation_enabled: i32,
+    pub hotword_pause_at_suspend: i32,
 }
 #[test]
 fn bindgen_test_layout_cras_server_state() {
     assert_eq!(
         ::std::mem::size_of::<cras_server_state>(),
-        1414292usize,
+        1414344usize,
         concat!("Size of: ", stringify!(cras_server_state))
     );
     assert_eq!(
@@ -2520,6 +2529,45 @@
             stringify!(main_thread_debug_info)
         )
     );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<cras_server_state>())).num_input_streams_with_permission
+                as *const _ as usize
+        },
+        1414292usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(cras_server_state),
+            "::",
+            stringify!(num_input_streams_with_permission)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<cras_server_state>())).noise_cancellation_enabled as *const _
+                as usize
+        },
+        1414336usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(cras_server_state),
+            "::",
+            stringify!(noise_cancellation_enabled)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<cras_server_state>())).hotword_pause_at_suspend as *const _
+                as usize
+        },
+        1414340usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(cras_server_state),
+            "::",
+            stringify!(hotword_pause_at_suspend)
+        )
+    );
 }
 pub const cras_notify_device_action_CRAS_DEVICE_ACTION_ADD: cras_notify_device_action = 0;
 pub const cras_notify_device_action_CRAS_DEVICE_ACTION_REMOVE: cras_notify_device_action = 1;
diff --git a/cras/client/cras-sys/src/lib.rs b/cras/client/cras-sys/src/lib.rs
index 8128575..2b3d21e 100644
--- a/cras/client/cras-sys/src/lib.rs
+++ b/cras/client/cras-sys/src/lib.rs
@@ -10,6 +10,7 @@
 use std::fmt;
 use std::iter::FromIterator;
 use std::os::raw::c_char;
+use std::str::FromStr;
 use std::time::Duration;
 
 #[allow(dead_code)]
@@ -47,6 +48,7 @@
 pub enum Error {
     InvalidChannel(i8),
     InvalidClientType(u32),
+    InvalidClientTypeStr,
     InvalidStreamType(u32),
 }
 
@@ -68,6 +70,7 @@
                 t,
                 CRAS_CLIENT_TYPE::CRAS_CLIENT_TYPE_SERVER_STREAM as u32 + 1
             ),
+            InvalidClientTypeStr => write!(f, "Invalid client type string"),
             InvalidStreamType(t) => write!(
                 f,
                 "Stream type {} is not within valid range [0, {})",
@@ -426,6 +429,18 @@
     }
 }
 
+impl FromStr for CRAS_CLIENT_TYPE {
+    type Err = Error;
+    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
+        use CRAS_CLIENT_TYPE::*;
+        match s {
+            "crosvm" => Ok(CRAS_CLIENT_TYPE_CROSVM),
+            "arcvm" => Ok(CRAS_CLIENT_TYPE_ARCVM),
+            _ => Err(Error::InvalidClientTypeStr),
+        }
+    }
+}
+
 impl Default for audio_stream_debug_info {
     fn default() -> Self {
         Self {
diff --git a/cras/client/cras_tests/src/audio.rs b/cras/client/cras_tests/src/audio.rs
index 5ab2247..23018fd 100644
--- a/cras/client/cras_tests/src/audio.rs
+++ b/cras/client/cras_tests/src/audio.rs
@@ -59,7 +59,7 @@
 
 static INTERRUPTED: AtomicBool = AtomicBool::new(false);
 
-extern "C" fn sigint_handler() {
+extern "C" fn sigint_handler(_: c_int) {
     // Check if we've already received one SIGINT. If we have, the program may
     // be misbehaving and not terminating, so to be safe we'll forcefully exit.
     if INTERRUPTED.load(Ordering::Acquire) {
diff --git a/cras/client/libcras/src/cras_stream.rs b/cras/client/libcras/src/cras_stream.rs
index 5914bfd..f600480 100644
--- a/cras/client/libcras/src/cras_stream.rs
+++ b/cras/client/libcras/src/cras_stream.rs
@@ -165,20 +165,20 @@
 
     fn wait_request_data(&mut self) -> Result<(), Error> {
         match self.controls.audio_sock_mut().read_audio_message()? {
-            AudioMessage::Success { id, .. } => match id {
-                CRAS_AUDIO_MESSAGE_ID::AUDIO_MESSAGE_REQUEST_DATA => Ok(()),
-                _ => Err(Error::MessageTypeError),
-            },
+            AudioMessage::Success {
+                id: CRAS_AUDIO_MESSAGE_ID::AUDIO_MESSAGE_REQUEST_DATA,
+                ..
+            } => Ok(()),
             _ => Err(Error::MessageTypeError),
         }
     }
 
     fn wait_data_ready(&mut self) -> Result<u32, Error> {
         match self.controls.audio_sock_mut().read_audio_message()? {
-            AudioMessage::Success { id, frames } => match id {
-                CRAS_AUDIO_MESSAGE_ID::AUDIO_MESSAGE_DATA_READY => Ok(frames),
-                _ => Err(Error::MessageTypeError),
-            },
+            AudioMessage::Success {
+                id: CRAS_AUDIO_MESSAGE_ID::AUDIO_MESSAGE_DATA_READY,
+                frames,
+            } => Ok(frames),
             _ => Err(Error::MessageTypeError),
         }
     }
diff --git a/cras/client/libcras/src/libcras.rs b/cras/client/libcras/src/libcras.rs
index 80d2cff..402a4a2 100644
--- a/cras/client/libcras/src/libcras.rs
+++ b/cras/client/libcras/src/libcras.rs
@@ -136,7 +136,7 @@
     CRAS_CLIENT_TYPE as CrasClientType, CRAS_NODE_TYPE as CrasNodeType,
     CRAS_STREAM_EFFECT as CrasStreamEffect,
 };
-pub use cras_sys::{AudioDebugInfo, CrasIodevInfo, CrasIonodeInfo};
+pub use cras_sys::{AudioDebugInfo, CrasIodevInfo, CrasIonodeInfo, Error as CrasSysError};
 use sys_util::{PollContext, PollToken, SharedMemory};
 
 mod audio_socket;
diff --git a/cras/src/Makefile.am b/cras/src/Makefile.am
index 69fea5f..1e89f81 100644
--- a/cras/src/Makefile.am
+++ b/cras/src/Makefile.am
@@ -55,6 +55,7 @@
 	server/cras_bt_player.c \
 	server/cras_bt_io.c \
 	server/cras_bt_profile.c \
+	server/cras_bt_battery_provider.c \
 	server/cras_dbus.c \
 	server/cras_dbus_util.c \
 	server/cras_dbus_control.c \
@@ -426,6 +427,7 @@
 	byte_buffer_unittest \
 	card_config_unittest \
 	checksum_unittest \
+	cras_abi_unittest \
 	cras_client_unittest \
 	cras_tm_unittest \
 	device_monitor_unittest \
@@ -613,7 +615,7 @@
 	-I$(top_srcdir)/src/common
 a2dp_info_unittest_LDADD = -lgtest -lpthread
 
-a2dp_iodev_unittest_SOURCES = tests/a2dp_iodev_unittest.cc common/sfh.c
+a2dp_iodev_unittest_SOURCES = tests/a2dp_iodev_unittest.cc
 a2dp_iodev_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/server \
 	-I$(top_srcdir)/src/common $(DBUS_CFLAGS)
 a2dp_iodev_unittest_LDADD = -lgtest -lpthread $(DBUS_LIBS)
@@ -686,7 +688,7 @@
 if HAVE_DBUS
 bt_device_unittest_SOURCES = tests/bt_device_unittest.cc \
 	server/cras_bt_device.c \
-	tests/metrics_stub.cc
+	tests/metrics_stub.cc common/sfh.c
 bt_device_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/server \
 	-I$(top_srcdir)/src/common $(DBUS_CFLAGS)
 bt_device_unittest_LDADD = -lgtest -lpthread $(DBUS_LIBS)
@@ -718,6 +720,13 @@
 checksum_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common
 checksum_unittest_LDADD = -lgtest -lpthread
 
+cras_abi_unittest_SOURCES = tests/cras_abi_unittest.cc \
+	common/cras_config.c common/cras_shm.c common/cras_util.c \
+	common/cras_file_wait.c common/cras_audio_format.c
+cras_abi_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
+	-I$(top_srcdir)/src/libcras
+cras_abi_unittest_LDADD = -lgtest -lpthread -lrt -lspeexdsp
+
 cras_client_unittest_SOURCES = tests/cras_client_unittest.cc \
 	common/cras_config.c common/cras_shm.c common/cras_util.c \
 	common/cras_file_wait.c
@@ -854,13 +863,13 @@
 
 if HAVE_DBUS
 hfp_iodev_unittest_SOURCES = tests/hfp_iodev_unittest.cc \
-	server/cras_hfp_iodev.c common/sfh.c
+	server/cras_hfp_iodev.c
 hfp_iodev_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
 	-I$(top_srcdir)/src/server $(DBUS_CFLAGS)
 hfp_iodev_unittest_LDADD = -lgtest -lpthread $(DBUS_LIBS)
 
 hfp_alsa_iodev_unittest_SOURCES = tests/hfp_alsa_iodev_unittest.cc \
-	server/cras_hfp_alsa_iodev.c common/sfh.c
+	server/cras_hfp_alsa_iodev.c
 hfp_alsa_iodev_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) \
 	-I$(top_srcdir)/src/common -I$(top_srcdir)/src/server $(DBUS_CFLAGS)
 hfp_alsa_iodev_unittest_LDADD = -lgtest -lpthread $(DBUS_LIBS)
diff --git a/cras/src/common/cras_file_wait.c b/cras/src/common/cras_file_wait.c
index 9ad9448..190a5e1 100644
--- a/cras/src/common/cras_file_wait.c
+++ b/cras/src/common/cras_file_wait.c
@@ -190,7 +190,7 @@
 	strcpy(file_wait->watch_dir, file_wait->file_path);
 	watch_dir_len = file_wait->file_path_len;
 
-	while (rc == -ENOENT) {
+	while (rc == -ENOENT || rc == -EACCES) {
 		strcpy(file_wait->watch_path, file_wait->watch_dir);
 		watch_path_len = watch_dir_len;
 
diff --git a/cras/src/common/cras_types.h b/cras/src/common/cras_types.h
index 90a0474..544ba02 100644
--- a/cras/src/common/cras_types.h
+++ b/cras/src/common/cras_types.h
@@ -169,6 +169,7 @@
 	CRAS_CLIENT_TYPE_SERVER_STREAM, /* Server stream */
 	CRAS_CLIENT_TYPE_LACROS, /* LaCrOS */
 	CRAS_CLIENT_TYPE_PLUGIN, /* PluginVM */
+	CRAS_CLIENT_TYPE_ARCVM, /* ARCVM */
 	CRAS_NUM_CLIENT_TYPE, /* numbers of CRAS_CLIENT_TYPE */
 };
 
@@ -213,6 +214,7 @@
 	ENUM_STR(CRAS_CLIENT_TYPE_SERVER_STREAM)
 	ENUM_STR(CRAS_CLIENT_TYPE_LACROS)
 	ENUM_STR(CRAS_CLIENT_TYPE_PLUGIN)
+	ENUM_STR(CRAS_CLIENT_TYPE_ARCVM)
 	default:
 		return "INVALID_CLIENT_TYPE";
 	}
@@ -368,7 +370,8 @@
 	BT_A2DP_START,
 	BT_A2DP_SUSPENDED,
 	BT_CODEC_SELECTION,
-	BT_DEV_CONNECTED_CHANGE,
+	BT_DEV_CONNECTED,
+	BT_DEV_DISCONNECTED,
 	BT_DEV_CONN_WATCH_CB,
 	BT_DEV_SUSPEND_CB,
 	BT_HFP_NEW_CONNECTION,
@@ -573,6 +576,12 @@
  *    main_thread_debug_info - ring buffer for storing main thread event logs.
  *    num_input_streams_with_permission - An array containing numbers of input
  *        streams with permission in each client type.
+ *    noise_cancellation_enabled - Whether or not Noise Cancellation is enabled.
+ *    hotword_pause_at_suspend - 1 = Pause hotword detection when the system
+ *        suspends. Hotword detection is resumed after system resumes.
+ *        0 - Hotword detection is allowed to continue running after system
+ *        suspends, so a detected hotword can wake up the device.
+ *
  */
 #define CRAS_SERVER_STATE_VERSION 2
 struct __attribute__((packed, aligned(4))) cras_server_state {
@@ -612,6 +621,8 @@
 	int32_t deprioritize_bt_wbs_mic;
 	struct main_thread_debug_info main_thread_debug_info;
 	uint32_t num_input_streams_with_permission[CRAS_NUM_CLIENT_TYPE];
+	int32_t noise_cancellation_enabled;
+	int32_t hotword_pause_at_suspend;
 };
 
 /* Actions for card add/remove/change. */
diff --git a/cras/src/common/cras_util.h b/cras/src/common/cras_util.h
index ed476b7..96985ab 100644
--- a/cras/src/common/cras_util.h
+++ b/cras/src/common/cras_util.h
@@ -201,6 +201,19 @@
 	return cras_time_to_frames(&time_until, rate);
 }
 
+/* Returns true if the difference between a and b is  shorter than t. */
+static inline bool timespec_diff_shorter_than(const struct timespec *a,
+					      const struct timespec *b,
+					      const struct timespec *t)
+{
+	struct timespec diff;
+	if (timespec_after(a, b))
+		subtract_timespecs(a, b, &diff);
+	else
+		subtract_timespecs(b, a, &diff);
+	return timespec_after(t, &diff);
+}
+
 /* Poll on the given file descriptors.
  *
  * See ppoll(). This implementation changes the value of timeout to the
diff --git a/cras/src/dsp/drc.c b/cras/src/dsp/drc.c
index 1b2639a..e609841 100644
--- a/cras/src/dsp/drc.c
+++ b/cras/src/dsp/drc.c
@@ -104,7 +104,7 @@
 		param[PARAM_RELEASE_ZONE3] = 0.42f;
 		param[PARAM_RELEASE_ZONE4] = 0.98f;
 
-		/* This is effectively a master volume on the compressed
+		/* This is effectively a main volume on the compressed
 		 * signal */
 		param[PARAM_POST_GAIN] = 0; /* dB */
 		param[PARAM_ENABLED] = 0;
diff --git a/cras/src/dsp/drc_kernel.c b/cras/src/dsp/drc_kernel.c
index c0eb100..8c3404f 100644
--- a/cras/src/dsp/drc_kernel.c
+++ b/cras/src/dsp/drc_kernel.c
@@ -257,7 +257,7 @@
 	/* Empirical/perceptual tuning. */
 	full_range_makeup_gain = powf(full_range_makeup_gain, 0.6f);
 
-	dk->master_linear_gain =
+	dk->main_linear_gain =
 		decibels_to_linear(db_post_gain) * full_range_makeup_gain;
 
 	/* Attack parameters. */
@@ -566,7 +566,7 @@
 #include <arm_neon.h>
 static void dk_compress_output(struct drc_kernel *dk)
 {
-	const float master_linear_gain = dk->master_linear_gain;
+	const float main_linear_gain = dk->main_linear_gain;
 	const float envelope_rate = dk->envelope_rate;
 	const float scaled_desired_gain = dk->scaled_desired_gain;
 	const float compressor_gain = dk->compressor_gain;
@@ -638,7 +638,7 @@
 			  [A7]"w"(A7),
 			  [base]"w"(vdupq_n_f32(scaled_desired_gain)),
 			  [r4]"w"(vdupq_n_f32(r*r*r*r)),
-			  [g]"w"(vdupq_n_f32(master_linear_gain))
+			  [g]"w"(vdupq_n_f32(main_linear_gain))
 			: /* clobber */
 			  "memory", "cc");
 		// clang-format on
@@ -698,7 +698,7 @@
 			  [A7]"w"(A7),
 			  [one]"w"(vdupq_n_f32(1)),
 			  [r4]"w"(vdupq_n_f32(r*r*r*r)),
-			  [g]"w"(vdupq_n_f32(master_linear_gain))
+			  [g]"w"(vdupq_n_f32(main_linear_gain))
 			: /* clobber */
 			  "memory", "cc");
 		// clang-format on
@@ -709,7 +709,7 @@
 #include <emmintrin.h>
 static void dk_compress_output(struct drc_kernel *dk)
 {
-	const float master_linear_gain = dk->master_linear_gain;
+	const float main_linear_gain = dk->main_linear_gain;
 	const float envelope_rate = dk->envelope_rate;
 	const float scaled_desired_gain = dk->scaled_desired_gain;
 	const float compressor_gain = dk->compressor_gain;
@@ -789,7 +789,7 @@
 			  [A7]"x"(A7),
 			  [base]"x"(_mm_set1_ps(scaled_desired_gain)),
 			  [r4]"x"(_mm_set1_ps(r*r*r*r)),
-			  [g]"x"(_mm_set1_ps(master_linear_gain))
+			  [g]"x"(_mm_set1_ps(main_linear_gain))
 			: /* clobber */
 			  "memory", "cc");
 		// clang-format on
@@ -862,7 +862,7 @@
 			  [A7]"x"(A7),
 			  [one]"x"(_mm_set1_ps(1)),
 			  [r4]"x"(_mm_set1_ps(r*r*r*r)),
-			  [g]"x"(_mm_set1_ps(master_linear_gain))
+			  [g]"x"(_mm_set1_ps(main_linear_gain))
 			: /* clobber */
 			  "memory", "cc");
 		// clang-format on
@@ -872,7 +872,7 @@
 #else
 static void dk_compress_output(struct drc_kernel *dk)
 {
-	const float master_linear_gain = dk->master_linear_gain;
+	const float main_linear_gain = dk->main_linear_gain;
 	const float envelope_rate = dk->envelope_rate;
 	const float scaled_desired_gain = dk->scaled_desired_gain;
 	const float compressor_gain = dk->compressor_gain;
@@ -902,8 +902,8 @@
 				float post_warp_compressor_gain =
 					warp_sinf(x[j] + base);
 
-				/* Calculate total gain using master gain. */
-				float total_gain = master_linear_gain *
+				/* Calculate total gain using main gain. */
+				float total_gain = main_linear_gain *
 						   post_warp_compressor_gain;
 
 				/* Apply final gain. */
@@ -936,8 +936,8 @@
 				float post_warp_compressor_gain =
 					warp_sinf(x[j]);
 
-				/* Calculate total gain using master gain. */
-				float total_gain = master_linear_gain *
+				/* Calculate total gain using main gain. */
+				float total_gain = main_linear_gain *
 						   post_warp_compressor_gain;
 
 				/* Apply final gain. */
diff --git a/cras/src/dsp/drc_kernel.h b/cras/src/dsp/drc_kernel.h
index 1157f22..2ed9956 100644
--- a/cras/src/dsp/drc_kernel.h
+++ b/cras/src/dsp/drc_kernel.h
@@ -67,7 +67,7 @@
 	float kA, kB, kC, kD, kE;
 
 	/* Calculated parameters */
-	float master_linear_gain;
+	float main_linear_gain;
 	float attack_frames;
 	float sat_release_frames_inv_neg;
 	float sat_release_rate_at_neg_two_db;
diff --git a/cras/src/libcras/cras_client.c b/cras/src/libcras/cras_client.c
index 3fe631d..8420db1 100644
--- a/cras/src/libcras/cras_client.c
+++ b/cras/src/libcras/cras_client.c
@@ -119,7 +119,8 @@
 };
 
 /* Parameters used when setting up a capture or playback stream. See comment
- * above cras_client_create_stream_params in the header for descriptions. */
+ * above cras_client_stream_params_create or libcras_stream_params_set in the
+ * header for descriptions. */
 struct cras_stream_params {
 	enum CRAS_STREAM_DIRECTION direction;
 	size_t buffer_frames;
@@ -133,6 +134,7 @@
 	cras_unified_cb_t unified_cb;
 	cras_error_cb_t err_cb;
 	struct cras_audio_format format;
+	libcras_stream_cb_t stream_cb;
 };
 
 /* Represents an attached audio stream.
@@ -274,6 +276,92 @@
 	void *user_data;
 };
 
+struct cras_stream_cb_data {
+	cras_stream_id_t stream_id;
+	enum CRAS_STREAM_DIRECTION direction;
+	uint8_t *buf;
+	unsigned int frames;
+	struct timespec sample_ts;
+	void *user_arg;
+};
+
+int stream_cb_get_stream_id(struct cras_stream_cb_data *data,
+			    cras_stream_id_t *id)
+{
+	*id = data->stream_id;
+	return 0;
+}
+
+int stream_cb_get_buf(struct cras_stream_cb_data *data, uint8_t **buf)
+{
+	*buf = data->buf;
+	return 0;
+}
+
+int stream_cb_get_frames(struct cras_stream_cb_data *data, unsigned int *frames)
+{
+	*frames = data->frames;
+	return 0;
+}
+
+int stream_cb_get_latency(struct cras_stream_cb_data *data,
+			  struct timespec *latency)
+{
+	if (data->direction == CRAS_STREAM_INPUT)
+		cras_client_calc_capture_latency(&data->sample_ts, latency);
+	else
+		cras_client_calc_playback_latency(&data->sample_ts, latency);
+	return 0;
+}
+
+int stream_cb_get_user_arg(struct cras_stream_cb_data *data, void **user_arg)
+{
+	*user_arg = data->user_arg;
+	return 0;
+}
+
+struct libcras_stream_cb_data *
+libcras_stream_cb_data_create(cras_stream_id_t stream_id,
+			      enum CRAS_STREAM_DIRECTION direction,
+			      uint8_t *buf, unsigned int frames,
+			      struct timespec sample_ts, void *user_arg)
+{
+	struct libcras_stream_cb_data *data =
+		(struct libcras_stream_cb_data *)calloc(
+			1, sizeof(struct libcras_stream_cb_data));
+	if (!data) {
+		syslog(LOG_ERR, "cras_client: calloc: %s", strerror(errno));
+		return NULL;
+	}
+	data->data_ = (struct cras_stream_cb_data *)calloc(
+		1, sizeof(struct cras_stream_cb_data));
+	if (!data->data_) {
+		syslog(LOG_ERR, "cras_client: calloc: %s", strerror(errno));
+		free(data);
+		return NULL;
+	}
+	data->api_version = CRAS_API_VERSION;
+	data->get_stream_id = stream_cb_get_stream_id;
+	data->get_buf = stream_cb_get_buf;
+	data->get_frames = stream_cb_get_frames;
+	data->get_latency = stream_cb_get_latency;
+	data->get_user_arg = stream_cb_get_user_arg;
+	data->data_->stream_id = stream_id;
+	data->data_->direction = direction;
+	data->data_->buf = buf;
+	data->data_->frames = frames;
+	data->data_->sample_ts = sample_ts;
+	data->data_->user_arg = user_arg;
+	return data;
+}
+
+void libcras_stream_cb_data_destroy(struct libcras_stream_cb_data *data)
+{
+	if (data)
+		free(data->data_);
+	free(data);
+}
+
 /*
  * Local Helpers
  */
@@ -283,6 +371,10 @@
 static int handle_message_from_server(struct cras_client *client);
 static int reregister_notifications(struct cras_client *client);
 
+static struct libcras_node_info *
+libcras_node_info_create(struct cras_iodev_info *iodev,
+			 struct cras_ionode_info *ionode);
+
 /*
  * Unlock the server_state_rwlock if lock_rc is 0.
  *
@@ -1084,6 +1176,7 @@
 	uint8_t *captured_frames;
 	struct timespec ts;
 	int rc = 0;
+	struct libcras_stream_cb_data *data;
 
 	config = stream->config;
 	/* If this message is for an output stream, log error and drop it. */
@@ -1098,14 +1191,24 @@
 
 	cras_timespec_to_timespec(&ts, &stream->shm->header->ts);
 
-	if (config->unified_cb)
+	if (config->stream_cb) {
+		data = libcras_stream_cb_data_create(
+			stream->id, stream->direction, captured_frames,
+			num_frames, ts, config->user_data);
+		if (!data)
+			return -errno;
+		frames = config->stream_cb(data);
+		libcras_stream_cb_data_destroy(data);
+		data = NULL;
+	} else if (config->unified_cb) {
 		frames = config->unified_cb(stream->client, stream->id,
 					    captured_frames, NULL, num_frames,
 					    &ts, NULL, config->user_data);
-	else
+	} else {
 		frames = config->aud_cb(stream->client, stream->id,
 					captured_frames, num_frames, &ts,
 					config->user_data);
+	}
 	if (frames < 0) {
 		send_stream_message(stream, CLIENT_STREAM_EOF);
 		rc = frames;
@@ -1152,6 +1255,7 @@
 	struct cras_stream_params *config;
 	struct cras_audio_shm *shm = stream->shm;
 	struct timespec ts;
+	struct libcras_stream_cb_data *data;
 
 	config = stream->config;
 
@@ -1169,13 +1273,24 @@
 	cras_timespec_to_timespec(&ts, &shm->header->ts);
 
 	/* Get samples from the user */
-	if (config->unified_cb)
+	if (config->stream_cb) {
+		data = libcras_stream_cb_data_create(stream->id,
+						     stream->direction, buf,
+						     num_frames, ts,
+						     config->user_data);
+		if (!data)
+			return -errno;
+		frames = config->stream_cb(data);
+		libcras_stream_cb_data_destroy(data);
+		data = NULL;
+	} else if (config->unified_cb) {
 		frames = config->unified_cb(stream->client, stream->id, NULL,
 					    buf, num_frames, NULL, &ts,
 					    config->user_data);
-	else
+	} else {
 		frames = config->aud_cb(stream->client, stream->id, buf,
 					num_frames, &ts, config->user_data);
+	}
 	if (frames < 0) {
 		send_stream_message(stream, CLIENT_STREAM_EOF);
 		rc = frames;
@@ -2255,6 +2370,7 @@
 	params->user_data = user_data;
 	params->aud_cb = aud_cb;
 	params->unified_cb = 0;
+	params->stream_cb = 0;
 	params->err_cb = err_cb;
 	memcpy(&(params->format), format, sizeof(*format));
 	return params;
@@ -2328,6 +2444,7 @@
 	params->user_data = user_data;
 	params->aud_cb = 0;
 	params->unified_cb = unified_cb;
+	params->stream_cb = 0;
 	params->err_cb = err_cb;
 	memcpy(&(params->format), format, sizeof(*format));
 
@@ -2350,7 +2467,8 @@
 	if (client == NULL || config == NULL || stream_id_out == NULL)
 		return -EINVAL;
 
-	if (config->aud_cb == NULL && config->unified_cb == NULL)
+	if (config->stream_cb == NULL && config->aud_cb == NULL &&
+	    config->unified_cb == NULL)
 		return -EINVAL;
 
 	if (config->err_cb == NULL)
@@ -3815,3 +3933,317 @@
 	free(handle);
 	return 0;
 }
+
+int get_nodes(struct cras_client *client, enum CRAS_STREAM_DIRECTION direction,
+	      struct libcras_node_info ***nodes, size_t *num)
+{
+	struct cras_iodev_info iodevs[CRAS_MAX_IODEVS];
+	struct cras_ionode_info ionodes[CRAS_MAX_IONODES];
+	size_t num_devs = CRAS_MAX_IODEVS, num_nodes = CRAS_MAX_IONODES;
+	int rc, i, j;
+
+	*num = 0;
+	if (direction == CRAS_STREAM_INPUT) {
+		rc = cras_client_get_input_devices(client, iodevs, ionodes,
+						   &num_devs, &num_nodes);
+	} else {
+		rc = cras_client_get_output_devices(client, iodevs, ionodes,
+						    &num_devs, &num_nodes);
+	}
+
+	if (rc < 0) {
+		syslog(LOG_ERR, "Failed to get devices: %d", rc);
+		return rc;
+	}
+
+	*nodes = (struct libcras_node_info **)calloc(
+		num_nodes, sizeof(struct libcras_node_info *));
+
+	for (i = 0; i < num_devs; i++) {
+		for (j = 0; j < num_nodes; j++) {
+			if (iodevs[i].idx != ionodes[j].iodev_idx)
+				continue;
+			(*nodes)[*num] = libcras_node_info_create(&iodevs[i],
+								  &ionodes[j]);
+			if ((*nodes)[*num] == NULL) {
+				rc = -errno;
+				goto clean;
+			}
+			(*num)++;
+		}
+	}
+	return 0;
+clean:
+	for (i = 0; i < *num; i++)
+		libcras_node_info_destroy((*nodes)[i]);
+	free(*nodes);
+	*nodes = NULL;
+	*num = 0;
+	return rc;
+}
+
+int get_default_output_buffer_size(struct cras_client *client, int *size)
+{
+	int rc = cras_client_get_default_output_buffer_size(client);
+	if (rc < 0)
+		return rc;
+	*size = rc;
+	return 0;
+}
+
+int get_aec_group_id(struct cras_client *client, int *id)
+{
+	int rc = cras_client_get_aec_group_id(client);
+	if (rc < 0)
+		return rc;
+	*id = rc;
+	return 0;
+}
+
+int get_aec_supported(struct cras_client *client, int *supported)
+{
+	*supported = cras_client_get_aec_supported(client);
+	return 0;
+}
+
+int get_system_muted(struct cras_client *client, int *muted)
+{
+	*muted = cras_client_get_system_muted(client);
+	return 0;
+}
+
+int get_loopback_dev_idx(struct cras_client *client, int *idx)
+{
+	int rc = cras_client_get_first_dev_type_idx(
+		client, CRAS_NODE_TYPE_POST_MIX_PRE_DSP, CRAS_STREAM_INPUT);
+	if (rc < 0)
+		return rc;
+	*idx = rc;
+	return 0;
+}
+
+struct libcras_client *libcras_client_create()
+{
+	struct libcras_client *client = (struct libcras_client *)calloc(
+		1, sizeof(struct libcras_client));
+	if (!client) {
+		syslog(LOG_ERR, "cras_client: calloc failed");
+		return NULL;
+	}
+	if (cras_client_create(&client->client_)) {
+		libcras_client_destroy(client);
+		return NULL;
+	}
+	client->api_version = CRAS_API_VERSION;
+	client->connect = cras_client_connect;
+	client->connect_timeout = cras_client_connect_timeout;
+	client->connected_wait = cras_client_connected_wait;
+	client->run_thread = cras_client_run_thread;
+	client->stop = cras_client_stop;
+	client->add_pinned_stream = cras_client_add_pinned_stream;
+	client->rm_stream = cras_client_rm_stream;
+	client->set_stream_volume = cras_client_set_stream_volume;
+	client->get_nodes = get_nodes;
+	client->get_default_output_buffer_size = get_default_output_buffer_size;
+	client->get_aec_group_id = get_aec_group_id;
+	client->get_aec_supported = get_aec_supported;
+	client->get_system_muted = get_system_muted;
+	client->set_system_mute = cras_client_set_system_mute;
+	client->get_loopback_dev_idx = get_loopback_dev_idx;
+	return client;
+}
+
+void libcras_client_destroy(struct libcras_client *client)
+{
+	cras_client_destroy(client->client_);
+	free(client);
+}
+
+int stream_params_set(struct cras_stream_params *params,
+		      enum CRAS_STREAM_DIRECTION direction,
+		      size_t buffer_frames, size_t cb_threshold,
+		      enum CRAS_STREAM_TYPE stream_type,
+		      enum CRAS_CLIENT_TYPE client_type, uint32_t flags,
+		      void *user_data, libcras_stream_cb_t stream_cb,
+		      cras_error_cb_t err_cb, size_t rate,
+		      snd_pcm_format_t format, size_t num_channels)
+{
+	params->direction = direction;
+	params->buffer_frames = buffer_frames;
+	params->cb_threshold = cb_threshold;
+	params->stream_type = stream_type;
+	params->client_type = client_type;
+	params->flags = flags;
+	params->user_data = user_data;
+	params->stream_cb = stream_cb;
+	params->err_cb = err_cb;
+	params->format.frame_rate = rate;
+	params->format.format = format;
+	params->format.num_channels = num_channels;
+	return 0;
+}
+
+int stream_params_set_channel_layout(struct cras_stream_params *params,
+				     int length, const int8_t *layout)
+{
+	if (length != CRAS_CH_MAX)
+		return -EINVAL;
+	return cras_audio_format_set_channel_layout(&params->format, layout);
+}
+
+struct libcras_stream_params *libcras_stream_params_create()
+{
+	struct libcras_stream_params *params =
+		(struct libcras_stream_params *)calloc(
+			1, sizeof(struct libcras_stream_params));
+	if (!params) {
+		syslog(LOG_ERR, "cras_client: calloc failed");
+		return NULL;
+	}
+	params->params_ = (struct cras_stream_params *)calloc(
+		1, sizeof(struct cras_stream_params));
+	if (params->params_ == NULL) {
+		syslog(LOG_ERR, "cras_client: calloc failed");
+		free(params->params_);
+		return NULL;
+	}
+	params->api_version = CRAS_API_VERSION;
+	params->set = stream_params_set;
+	params->set_channel_layout = stream_params_set_channel_layout;
+	params->enable_aec = cras_client_stream_params_enable_aec;
+	return params;
+}
+
+void libcras_stream_params_destroy(struct libcras_stream_params *params)
+{
+	free(params->params_);
+	free(params);
+}
+
+struct cras_node_info {
+	uint64_t id;
+	uint32_t dev_idx;
+	uint32_t node_idx;
+	uint32_t max_supported_channels;
+	bool plugged;
+	bool active;
+	char type[CRAS_NODE_TYPE_BUFFER_SIZE];
+	char node_name[CRAS_NODE_NAME_BUFFER_SIZE];
+	char dev_name[CRAS_IODEV_NAME_BUFFER_SIZE];
+};
+
+int cras_node_info_get_id(struct cras_node_info *node, uint64_t *id)
+{
+	(*id) = node->id;
+	return 0;
+}
+
+int cras_node_info_get_dev_idx(struct cras_node_info *node, uint32_t *dev_idx)
+{
+	(*dev_idx) = node->dev_idx;
+	return 0;
+}
+
+int cras_node_info_get_node_idx(struct cras_node_info *node, uint32_t *node_idx)
+{
+	(*node_idx) = node->node_idx;
+	return 0;
+}
+
+int cras_node_info_get_max_supported_channels(struct cras_node_info *node,
+					      uint32_t *max_supported_channels)
+{
+	(*max_supported_channels) = node->max_supported_channels;
+	return 0;
+}
+
+int cras_node_info_is_plugged(struct cras_node_info *node, bool *is_plugged)
+{
+	(*is_plugged) = node->plugged;
+	return 0;
+}
+
+int cras_node_info_is_active(struct cras_node_info *node, bool *is_active)
+{
+	(*is_active) = node->active;
+	return 0;
+}
+
+int cras_node_info_get_type(struct cras_node_info *node, char **type)
+{
+	(*type) = node->type;
+	return 0;
+}
+
+int cras_node_info_get_node_name(struct cras_node_info *node, char **node_name)
+{
+	(*node_name) = node->node_name;
+	return 0;
+}
+
+int cras_node_info_get_dev_name(struct cras_node_info *node, char **dev_name)
+{
+	(*dev_name) = node->dev_name;
+	return 0;
+}
+
+struct libcras_node_info *
+libcras_node_info_create(struct cras_iodev_info *iodev,
+			 struct cras_ionode_info *ionode)
+{
+	struct libcras_node_info *node = (struct libcras_node_info *)calloc(
+		1, sizeof(struct libcras_node_info));
+	if (!node) {
+		syslog(LOG_ERR, "cras_client: calloc failed");
+		return NULL;
+	}
+	node->node_ = (struct cras_node_info *)calloc(
+		1, sizeof(struct cras_node_info));
+	if (node->node_ == NULL) {
+		syslog(LOG_ERR, "cras_client: calloc failed");
+		free(node);
+		return NULL;
+	}
+	node->api_version = CRAS_API_VERSION;
+	node->node_->id =
+		cras_make_node_id(ionode->iodev_idx, ionode->ionode_idx);
+	node->node_->dev_idx = ionode->iodev_idx;
+	node->node_->node_idx = ionode->ionode_idx;
+	node->node_->max_supported_channels = iodev->max_supported_channels;
+	node->node_->plugged = ionode->plugged;
+	node->node_->active = ionode->active;
+	strncpy(node->node_->type, ionode->type, CRAS_NODE_TYPE_BUFFER_SIZE);
+	node->node_->type[CRAS_NODE_TYPE_BUFFER_SIZE - 1] = '\0';
+	strncpy(node->node_->node_name, ionode->name,
+		CRAS_NODE_NAME_BUFFER_SIZE);
+	node->node_->node_name[CRAS_NODE_NAME_BUFFER_SIZE - 1] = '\0';
+	strncpy(node->node_->dev_name, iodev->name,
+		CRAS_IODEV_NAME_BUFFER_SIZE);
+	node->node_->dev_name[CRAS_IODEV_NAME_BUFFER_SIZE - 1] = '\0';
+	node->get_id = cras_node_info_get_id;
+	node->get_dev_idx = cras_node_info_get_dev_idx;
+	node->get_node_idx = cras_node_info_get_node_idx;
+	node->get_max_supported_channels =
+		cras_node_info_get_max_supported_channels;
+	node->is_plugged = cras_node_info_is_plugged;
+	node->is_active = cras_node_info_is_active;
+	node->get_type = cras_node_info_get_type;
+	node->get_node_name = cras_node_info_get_node_name;
+	node->get_dev_name = cras_node_info_get_dev_name;
+	return node;
+}
+
+void libcras_node_info_destroy(struct libcras_node_info *node)
+{
+	free(node->node_);
+	free(node);
+}
+
+void libcras_node_info_array_destroy(struct libcras_node_info **nodes,
+				     size_t num)
+{
+	int i;
+	for (i = 0; i < num; i++)
+		libcras_node_info_destroy(nodes[i]);
+	free(nodes);
+}
diff --git a/cras/src/libcras/cras_client.h b/cras/src/libcras/cras_client.h
index f7a18b5..f26a081 100644
--- a/cras/src/libcras/cras_client.h
+++ b/cras/src/libcras/cras_client.h
@@ -1308,6 +1308,699 @@
 int cras_client_set_num_active_streams_changed_callback(
 	struct cras_client *client,
 	cras_client_num_active_streams_changed_callback cb);
+
+/*
+ * The functions below prefixed with libcras wrap the original CRAS library
+ * They provide an interface that maps the pointers to the functions above.
+ * Please add a new function instead of modifying the existing function.
+ * Here are some rules about how to add a new function:
+ * 1. Increase the CRAS_API_VERSION by 1.
+ * 2. Write a new function in cras_client.c.
+ * 3. Append the corresponding pointer to the structure. Remeber DO NOT change
+ *    the order of functions in the structs.
+ * 4. Assign the pointer to the new function in cras_client.c.
+ * 5. Create the inline function in cras_client.h, which is used by clients.
+ *    Remember to add DISABLE_CFI_ICALL on the inline function.
+ * 6. Add CHECK_VERSION in the inline function. If the api_version is smaller
+ *    than the supported version, this inline function will return -ENOSYS.
+ */
+
+#define CRAS_API_VERSION 1
+#define CHECK_VERSION(object, version)                                         \
+	if (object->api_version < version) {                                   \
+		return -ENOSYS;                                                \
+	}
+
+/*
+ * The inline functions use the indirect function call. Therefore, they are
+ * incompatible with CFI-icall.
+ */
+#define DISABLE_CFI_ICALL __attribute__((no_sanitize("cfi-icall")))
+
+struct libcras_node_info {
+	int api_version;
+	struct cras_node_info *node_;
+	int (*get_id)(struct cras_node_info *node, uint64_t *id);
+	int (*get_dev_idx)(struct cras_node_info *node, uint32_t *dev_idx);
+	int (*get_node_idx)(struct cras_node_info *node, uint32_t *node_idx);
+	int (*get_max_supported_channels)(struct cras_node_info *node,
+					  uint32_t *max_supported_channels);
+	int (*is_plugged)(struct cras_node_info *node, bool *plugged);
+	int (*is_active)(struct cras_node_info *node, bool *active);
+	int (*get_type)(struct cras_node_info *node, char **name);
+	int (*get_node_name)(struct cras_node_info *node, char **name);
+	int (*get_dev_name)(struct cras_node_info *node, char **name);
+};
+
+struct libcras_client {
+	int api_version;
+	struct cras_client *client_;
+	int (*connect)(struct cras_client *client);
+	int (*connect_timeout)(struct cras_client *client,
+			       unsigned int timeout_ms);
+	int (*connected_wait)(struct cras_client *client);
+	int (*run_thread)(struct cras_client *client);
+	int (*stop)(struct cras_client *client);
+	int (*add_pinned_stream)(struct cras_client *client, uint32_t dev_idx,
+				 cras_stream_id_t *stream_id_out,
+				 struct cras_stream_params *config);
+	int (*rm_stream)(struct cras_client *client,
+			 cras_stream_id_t stream_id);
+	int (*set_stream_volume)(struct cras_client *client,
+				 cras_stream_id_t stream_id,
+				 float volume_scaler);
+	int (*get_nodes)(struct cras_client *client,
+			 enum CRAS_STREAM_DIRECTION direction,
+			 struct libcras_node_info ***nodes, size_t *num);
+	int (*get_default_output_buffer_size)(struct cras_client *client,
+					      int *size);
+	int (*get_aec_group_id)(struct cras_client *client, int *id);
+	int (*get_aec_supported)(struct cras_client *client, int *supported);
+	int (*get_system_muted)(struct cras_client *client, int *muted);
+	int (*set_system_mute)(struct cras_client *client, int mute);
+	int (*get_loopback_dev_idx)(struct cras_client *client, int *idx);
+};
+
+struct cras_stream_cb_data;
+struct libcras_stream_cb_data {
+	int api_version;
+	struct cras_stream_cb_data *data_;
+	int (*get_stream_id)(struct cras_stream_cb_data *data,
+			     cras_stream_id_t *id);
+	int (*get_buf)(struct cras_stream_cb_data *data, uint8_t **buf);
+	int (*get_frames)(struct cras_stream_cb_data *data,
+			  unsigned int *frames);
+	int (*get_latency)(struct cras_stream_cb_data *data,
+			   struct timespec *latency);
+	int (*get_user_arg)(struct cras_stream_cb_data *data, void **user_arg);
+};
+typedef int (*libcras_stream_cb_t)(struct libcras_stream_cb_data *data);
+
+struct libcras_stream_params {
+	int api_version;
+	struct cras_stream_params *params_;
+	int (*set)(struct cras_stream_params *params,
+		   enum CRAS_STREAM_DIRECTION direction, size_t buffer_frames,
+		   size_t cb_threshold, enum CRAS_STREAM_TYPE stream_type,
+		   enum CRAS_CLIENT_TYPE client_type, uint32_t flags,
+		   void *user_data, libcras_stream_cb_t stream_cb,
+		   cras_error_cb_t err_cb, size_t rate, snd_pcm_format_t format,
+		   size_t num_channels);
+	int (*set_channel_layout)(struct cras_stream_params *params, int length,
+				  const int8_t *layout);
+	void (*enable_aec)(struct cras_stream_params *params);
+};
+
+/*
+ * Creates a new client.
+ * Returns:
+ *    If success, return a valid libcras_client pointer. Otherwise, return
+ *    NULL.
+ */
+struct libcras_client *libcras_client_create();
+
+/*
+ * Destroys a client.
+ * Args:
+ *    client - pointer returned from "libcras_client_create".
+ */
+void libcras_client_destroy(struct libcras_client *client);
+
+/*
+ * Connects a client to the running server.
+ * Waits forever (until interrupted or connected).
+ * Args:
+ *    client - pointer returned from "libcras_client_create".
+ * Returns:
+ *    0 on success, or a negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_client_connect(struct libcras_client *client)
+{
+	return client->connect(client->client_);
+}
+
+/*
+ * Connects a client to the running server, retries until timeout.
+ * Args:
+ *    client - pointer returned from "libcras_client_create".
+ *    timeout_ms - timeout in milliseconds or negative to wait forever.
+ * Returns:
+ *    0 on success, or a negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_client_connect_timeout(struct libcras_client *client,
+					  unsigned int timeout_ms)
+{
+	return client->connect_timeout(client->client_, timeout_ms);
+}
+
+/*
+ * Wait up to 1 second for the client thread to complete the server connection.
+ *
+ * After libcras_client_run_thread() is executed, this function can be
+ * used to ensure that the connection has been established with the server and
+ * ensure that any information about the server is up to date. If
+ * libcras_client_run_thread() has not yet been executed, or
+ * libcras_client_stop() was executed and thread isn't running, then this
+ * function returns -EINVAL.
+ *
+ * Args:
+ *    client - pointer returned from "libcras_client_create".
+ * Returns:
+ *    0 on success, or a negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_client_connected_wait(struct libcras_client *client)
+{
+	return client->connected_wait(client->client_);
+}
+
+/*
+ * Begins running the client control thread.
+ *
+ * Required for stream operations and other operations noted below.
+ *
+ * Args:
+ *    client - pointer returned from "libcras_client_create".
+ * Returns:
+ *    0 on success, or a negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_client_run_thread(struct libcras_client *client)
+{
+	return client->run_thread(client->client_);
+}
+
+/*
+ * Stops running a client.
+ * This function is executed automatically by cras_client_destroy().
+ * Args:
+ *    client - pointer returned from "libcras_client_create".
+ * Returns:
+ *    0 on success or if the thread was already stopped, -EINVAL if the client
+ *    isn't valid.
+ */
+DISABLE_CFI_ICALL
+inline int libcras_client_stop(struct libcras_client *client)
+{
+	return client->stop(client->client_);
+}
+
+/*
+ * Creates a pinned stream and return the stream id or < 0 on error.
+ *
+ * Requires execution of libcras_client_run_thread(), and an active
+ * connection to the audio server.
+ *
+ * Args:
+ *    client - pointer returned from "libcras_client_create".
+ *    dev_idx - Index of the device to attach the newly created stream.
+ *    stream_id_out - On success will be filled with the new stream id.
+ *        Guaranteed to be set before any callbacks are made.
+ *    params - The pointer specifying the parameters for the stream.
+ *        (returned from libcras_stream_params_create)
+ * Returns:
+ *    0 on success, negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_client_add_pinned_stream(
+	struct libcras_client *client, uint32_t dev_idx,
+	cras_stream_id_t *stream_id_out, struct libcras_stream_params *params)
+{
+	return client->add_pinned_stream(client->client_, dev_idx,
+					 stream_id_out, params->params_);
+}
+
+/*
+ * Removes a currently playing/capturing stream.
+ *
+ * Requires execution of libcras_client_run_thread().
+ *
+ * Args:
+ *    client - pointer returned from "libcras_client_create".
+ *    stream_id - ID returned from libcras_client_add_stream to identify
+ *        the stream to remove.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_client_rm_stream(struct libcras_client *client,
+				    cras_stream_id_t stream_id)
+{
+	return client->rm_stream(client->client_, stream_id);
+}
+
+/*
+ * Sets the volume scaling factor for the given stream.
+ *
+ * Requires execution of cras_client_run_thread().
+ *
+ * Args:
+ *    client - pointer returned from "libcras_client_create".
+ *    stream_id - ID returned from libcras_client_add_stream.
+ *    volume_scaler - 0.0-1.0 the new value to scale this stream by.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_client_set_stream_volume(struct libcras_client *client,
+					    cras_stream_id_t stream_id,
+					    float volume_scaler)
+{
+	return client->set_stream_volume(client->client_, stream_id,
+					 volume_scaler);
+}
+
+/*
+ * Gets the current list of audio nodes.
+ *
+ * Args:
+ *    client - Pointer returned from "libcras_client_create".
+ *    direction - Input or output.
+ *    nodes - Array that will be filled with libcras_node_info pointers.
+ *    num - Pointer to store the size of the array.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ *    Remember to call libcras_node_info_array_destroy to free the array.
+ */
+DISABLE_CFI_ICALL
+inline int libcras_client_get_nodes(struct libcras_client *client,
+				    enum CRAS_STREAM_DIRECTION direction,
+				    struct libcras_node_info ***nodes,
+				    size_t *num)
+{
+	return client->get_nodes(client->client_, direction, nodes, num);
+}
+
+/*
+ * Gets the default output buffer size.
+ * Args:
+ *    client - Pointer returned from "libcras_client_create".
+ *    size - The pointer to save the result.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int
+libcras_client_get_default_output_buffer_size(struct libcras_client *client,
+					      int *size)
+{
+	return client->get_default_output_buffer_size(client->client_, size);
+}
+
+/*
+ * Gets the AEC group ID.
+ * Args:
+ *    client - Pointer returned from "libcras_client_create".
+ *    id - The pointer to save the result.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_client_get_aec_group_id(struct libcras_client *client,
+					   int *id)
+{
+	return client->get_aec_group_id(client->client_, id);
+}
+
+/*
+ * Gets whether AEC is supported.
+ * Args:
+ *    client - Pointer returned from "libcras_client_create".
+ *    supported - The pointer to save the result.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_client_get_aec_supported(struct libcras_client *client,
+					    int *supported)
+{
+	return client->get_aec_supported(client->client_, supported);
+}
+
+/*
+ * Gets whether the system is muted.
+ * Args:
+ *    client - Pointer returned from "libcras_client_create".
+ *    muted - The pointer to save the result.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_client_get_system_muted(struct libcras_client *client,
+					   int *muted)
+{
+	return client->get_aec_group_id(client->client_, muted);
+}
+
+/*
+ * Mutes or unmutes the system.
+ * Args:
+ *    client - Pointer returned from "libcras_client_create".
+ *    mute - 1 is to mute and 0 is to unmute.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_client_set_system_mute(struct libcras_client *client,
+					  int mute)
+{
+	return client->set_system_mute(client->client_, mute);
+}
+
+/*
+ * Gets the index of the loopback device.
+ * Args:
+ *    client - Pointer returned from "libcras_client_create".
+ *    idx - The pointer to save the result.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_client_get_loopback_dev_idx(struct libcras_client *client,
+					       int *idx)
+{
+	return client->get_loopback_dev_idx(client->client_, idx);
+}
+
+/*
+ * Creates a new struct to save stream params.
+ * Returns:
+ *    If success, return a valid libcras_stream_params pointer. Otherwise,
+ *    return NULL.
+ */
+struct libcras_stream_params *libcras_stream_params_create();
+
+/*
+ * Destroys a stream params instance.
+ * Args:
+ *    params - The pointer returned from libcras_stream_params_create.
+ */
+void libcras_stream_params_destroy(struct libcras_stream_params *params);
+
+/*
+ * Setup stream configuration parameters.
+ * Args:
+ *    params - The pointer returned from libcras_stream_params_create.
+ *    direction - Playback(CRAS_STREAM_OUTPUT) or capture(CRAS_STREAM_INPUT).
+ *    buffer_frames - total number of audio frames to buffer (dictates latency).
+ *    cb_threshold - For playback, call back for more data when the buffer
+ *        reaches this level. For capture, this is ignored (Audio callback will
+ *        be called when buffer_frames have been captured).
+ *    stream_type - Media or talk (currently only support "default").
+ *    client_type - The client type, like Chrome or CrOSVM.
+ *    flags - Currently only used for CRAS_INPUT_STREAM_FLAG.
+ *    user_data - Pointer that will be passed to the callback.
+ *    stream_cb - The audio callback. Called when audio is needed(playback) or
+ *        ready(capture).
+ *    err_cb - Called when there is an error with the stream.
+ *    rate - The sample rate of the audio stream.
+ *    format - The format of the audio stream.
+ *    num_channels - The number of channels of the audio stream.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_stream_params_set(
+	struct libcras_stream_params *params,
+	enum CRAS_STREAM_DIRECTION direction, size_t buffer_frames,
+	size_t cb_threshold, enum CRAS_STREAM_TYPE stream_type,
+	enum CRAS_CLIENT_TYPE client_type, uint32_t flags, void *user_data,
+	libcras_stream_cb_t stream_cb, cras_error_cb_t err_cb, size_t rate,
+	snd_pcm_format_t format, size_t num_channels)
+{
+	return params->set(params->params_, direction, buffer_frames,
+			   cb_threshold, stream_type, client_type, flags,
+			   user_data, stream_cb, err_cb, rate, format,
+			   num_channels);
+}
+
+/*
+ * Sets channel layout on given stream parameter.
+ * Args:
+ *    params - The pointer returned from libcras_stream_params_create.
+ *    length - The length of the array.
+ *    layout - An integer array representing the position of each channel in
+ *    enum CRAS_CHANNEL.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int
+libcras_stream_params_set_channel_layout(struct libcras_stream_params *params,
+					 int length, const int8_t *layout)
+{
+	return params->set_channel_layout(params->params_, length, layout);
+}
+
+/*
+ * Enables AEC on given stream parameter.
+ * Args:
+ *    params - The pointer returned from libcras_stream_params_create.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int
+libcras_stream_params_enable_aec(struct libcras_stream_params *params)
+{
+	params->enable_aec(params->params_);
+	return 0;
+}
+
+/*
+ * Gets stream id from the callback data.
+ * Args:
+ *    data - The pointer passed to the callback function.
+ *    id - The pointer to save the stream id.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int
+libcras_stream_cb_data_get_stream_id(struct libcras_stream_cb_data *data,
+				     cras_stream_id_t *id)
+{
+	return data->get_stream_id(data->data_, id);
+}
+
+/*
+ * Gets stream buf from the callback data.
+ * Args:
+ *    data - The pointer passed to the callback function.
+ *    buf - The pointer to save the stream buffer.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_stream_cb_data_get_buf(struct libcras_stream_cb_data *data,
+					  uint8_t **buf)
+{
+	return data->get_buf(data->data_, buf);
+}
+
+/*
+ * Gets how many frames to read or play from the callback data.
+ * Args:
+ *    data - The pointer passed to the callback function.
+ *    frames - The pointer to save the number of frames.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int
+libcras_stream_cb_data_get_frames(struct libcras_stream_cb_data *data,
+				  unsigned int *frames)
+{
+	return data->get_frames(data->data_, frames);
+}
+
+/*
+ * Gets the latency from the callback data.
+ * Args:
+ *    data - The pointer passed to the callback function.
+ *    frames - The timespec pointer to save the latency.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int
+libcras_stream_cb_data_get_latency(struct libcras_stream_cb_data *data,
+				   struct timespec *latency)
+{
+	return data->get_latency(data->data_, latency);
+}
+
+/*
+ * Gets the user data from the callback data.
+ * Args:
+ *    data - The pointer passed to the callback function.
+ *    frames - The pointer to save the user data.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int
+libcras_stream_cb_data_get_usr_arg(struct libcras_stream_cb_data *data,
+				   void **user_arg)
+{
+	return data->get_user_arg(data->data_, user_arg);
+}
+
+/*
+ * Destroys a node info instance.
+ * Args:
+ *    node - The libcras_node_info pointer to destroy.
+ */
+void libcras_node_info_destroy(struct libcras_node_info *node);
+
+/*
+ * Destroys a node info array.
+ * Args:
+ *    nodes - The libcras_node_info pointer array to destroy.
+ *    num - The size of the array.
+ */
+void libcras_node_info_array_destroy(struct libcras_node_info **nodes,
+				     size_t num);
+
+/*
+ * Gets ID from the node info pointer.
+ * Args:
+ *    node - The node info pointer. (Returned from libcras_client_get_nodes)
+ *    id - The pointer to save ID.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_node_info_get_id(struct libcras_node_info *node,
+				    uint64_t *id)
+{
+	return node->get_id(node->node_, id);
+}
+
+/*
+ * Gets device index from the node info pointer.
+ * Args:
+ *    node - The node info pointer. (Returned from libcras_client_get_nodes)
+ *    dev_idx - The pointer to the device index.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_node_info_get_dev_idx(struct libcras_node_info *node,
+					 uint32_t *dev_idx)
+{
+	return node->get_dev_idx(node->node_, dev_idx);
+}
+
+/*
+ * Gets node index from the node info pointer.
+ * Args:
+ *    node - The node info pointer. (Returned from libcras_client_get_nodes)
+ *    node_idx - The pointer to save the node index.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_node_info_get_node_idx(struct libcras_node_info *node,
+					  uint32_t *node_idx)
+{
+	return node->get_node_idx(node->node_, node_idx);
+}
+
+/*
+ * Gets the max supported channels from the node info pointer.
+ * Args:
+ *    node - The node info pointer. (Returned from libcras_client_get_nodes)
+ *    max_supported_channels - The pointer to save the result.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int
+libcras_node_info_get_max_supported_channels(struct libcras_node_info *node,
+					     uint32_t *max_supported_channels)
+{
+	return node->get_max_supported_channels(node->node_,
+						max_supported_channels);
+}
+
+/*
+ * Gets whether the node is plugged from the node info pointer.
+ * Args:
+ *    node - The node info pointer. (Returned from libcras_client_get_nodes)
+ *    plugged - The pointer to save the result.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_node_info_is_plugged(struct libcras_node_info *node,
+					bool *plugged)
+{
+	return node->is_plugged(node->node_, plugged);
+}
+
+/*
+ * Gets whether the node is active from the node info pointer.
+ * Args:
+ *    node - The node info pointer. (Returned from libcras_client_get_nodes)
+ *    active - The pointer to save the result.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_node_info_is_active(struct libcras_node_info *node,
+				       bool *active)
+{
+	return node->is_active(node->node_, active);
+}
+
+/*
+ * Gets device type from the node info pointer.
+ * Args:
+ *    node - The node info pointer. (Returned from libcras_client_get_nodes)
+ *    type - The pointer to save the device type.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_node_info_get_type(struct libcras_node_info *node,
+				      char **type)
+{
+	return node->get_type(node->node_, type);
+}
+
+/*
+ * Gets device name from the node info pointer.
+ * Args:
+ *    node - The node info pointer. (Returned from libcras_client_get_nodes)
+ *    name - The pointer to save the device name.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_node_info_get_node_name(struct libcras_node_info *node,
+					   char **name)
+{
+	return node->get_node_name(node->node_, name);
+}
+
+/*
+ * Gets node name from the node info pointer.
+ * Args:
+ *    node - The node info pointer. (Returned from libcras_client_get_nodes)
+ *    name - The pointer to save the node name.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_node_info_get_dev_name(struct libcras_node_info *node,
+					  char **name)
+{
+	return node->get_dev_name(node->node_, name);
+}
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/cras/src/server/audio_thread.c b/cras/src/server/audio_thread.c
index cd155e8..48bb0dc 100644
--- a/cras/src/server/audio_thread.c
+++ b/cras/src/server/audio_thread.c
@@ -443,7 +443,8 @@
 {
 	int rc;
 
-	rc = dev_io_append_stream(&thread->open_devs[stream->direction], stream,
+	rc = dev_io_append_stream(&thread->open_devs[CRAS_STREAM_OUTPUT],
+				  &thread->open_devs[CRAS_STREAM_INPUT], stream,
 				  iodevs, num_iodevs);
 	if (rc < 0)
 		return rc;
diff --git a/cras/src/server/config/cras_board_config.c b/cras/src/server/config/cras_board_config.c
index 14d3fa0..e36ea3c 100644
--- a/cras/src/server/config/cras_board_config.c
+++ b/cras/src/server/config/cras_board_config.c
@@ -14,6 +14,7 @@
 static const int32_t AEC_GROUP_ID_DEFAULT = -1;
 static const int32_t BLUETOOTH_WBS_ENABLED_INI_DEFAULT = 1;
 static const int32_t BLUETOOTH_DEPRIORITIZE_WBS_MIC_INI_DEFAULT = 0;
+static const int32_t HOTWORD_PAUSE_AT_SUSPEND_DEFAULT = 0;
 
 #define CONFIG_NAME "board.ini"
 #define DEFAULT_OUTPUT_BUF_SIZE_INI_KEY "output:default_output_buffer_size"
@@ -22,6 +23,7 @@
 #define BLUETOOTH_WBS_ENABLED_INI_KEY "bluetooth:wbs_enabled"
 #define BLUETOOTH_DEPRIORITIZE_WBS_MIC_INI_KEY "bluetooth:deprioritize_wbs_mic"
 #define UCM_IGNORE_SUFFIX_KEY "ucm:ignore_suffix"
+#define HOTWORD_PAUSE_AT_SUSPEND "hotword:pause_at_suspend"
 
 void cras_board_config_get(const char *config_path,
 			   struct cras_board_config *board_config)
@@ -85,6 +87,11 @@
 			syslog(LOG_ERR, "Failed to call strdup: %d", errno);
 	}
 
+	snprintf(ini_key, MAX_INI_KEY_LENGTH, HOTWORD_PAUSE_AT_SUSPEND);
+	ini_key[MAX_INI_KEY_LENGTH] = 0;
+	board_config->hotword_pause_at_suspend = iniparser_getint(
+		ini, ini_key, HOTWORD_PAUSE_AT_SUSPEND_DEFAULT);
+
 	iniparser_freedict(ini);
 	syslog(LOG_DEBUG, "Loaded ini file %s", ini_name);
 }
diff --git a/cras/src/server/config/cras_board_config.h b/cras/src/server/config/cras_board_config.h
index 2ecde26..d4bd849 100644
--- a/cras/src/server/config/cras_board_config.h
+++ b/cras/src/server/config/cras_board_config.h
@@ -15,6 +15,7 @@
 	int32_t bt_wbs_enabled;
 	int32_t deprioritize_bt_wbs_mic;
 	char *ucm_ignore_suffix;
+	int32_t hotword_pause_at_suspend;
 };
 
 /* Gets a configuration based on the config file specified.
diff --git a/cras/src/server/cras_a2dp_iodev.c b/cras/src/server/cras_a2dp_iodev.c
index 6c43475..b8a606e 100644
--- a/cras/src/server/cras_a2dp_iodev.c
+++ b/cras/src/server/cras_a2dp_iodev.c
@@ -24,7 +24,6 @@
 #include "cras_bt_device.h"
 #include "cras_iodev.h"
 #include "cras_util.h"
-#include "sfh.h"
 #include "rtp.h"
 #include "utlist.h"
 
@@ -644,10 +643,7 @@
 
 	snprintf(iodev->info.name, sizeof(iodev->info.name), "%s", name);
 	iodev->info.name[ARRAY_SIZE(iodev->info.name) - 1] = '\0';
-	iodev->info.stable_id =
-		SuperFastHash(cras_bt_device_object_path(device),
-			      strlen(cras_bt_device_object_path(device)),
-			      strlen(cras_bt_device_object_path(device)));
+	iodev->info.stable_id = cras_bt_device_get_stable_id(device);
 
 	iodev->configure_dev = configure_dev;
 	iodev->frames_queued = frames_queued;
diff --git a/cras/src/server/cras_alsa_io.c b/cras/src/server/cras_alsa_io.c
index da4ef63..275a681 100644
--- a/cras/src/server/cras_alsa_io.c
+++ b/cras/src/server/cras_alsa_io.c
@@ -391,6 +391,7 @@
 	snd_pcm_t *handle;
 	int rc;
 	const char *pcm_name = NULL;
+	int enable_noise_cancellation;
 
 	if (aio->base.direction == CRAS_STREAM_OUTPUT) {
 		struct alsa_output_node *aout =
@@ -412,6 +413,19 @@
 
 	aio->handle = handle;
 
+	/* Enable or disable noise cancellation if it supports. */
+	if (aio->ucm && iodev->direction == CRAS_STREAM_INPUT &&
+	    ucm_node_noise_cancellation_exists(aio->ucm,
+					       iodev->active_node->name)) {
+		enable_noise_cancellation =
+			cras_system_get_noise_cancellation_enabled();
+		rc = ucm_enable_node_noise_cancellation(
+			aio->ucm, iodev->active_node->name,
+			enable_noise_cancellation);
+		if (rc < 0)
+			return rc;
+	}
+
 	return 0;
 }
 
@@ -2021,6 +2035,17 @@
 	return 0;
 }
 
+static int support_noise_cancellation(const struct cras_iodev *iodev)
+{
+	struct alsa_io *aio = (struct alsa_io *)iodev;
+
+	if (!aio->ucm || !iodev->active_node)
+		return 0;
+
+	return ucm_node_noise_cancellation_exists(aio->ucm,
+						  iodev->active_node->name);
+}
+
 /*
  * Exported Interface.
  */
@@ -2098,6 +2123,7 @@
 	iodev->get_num_severe_underruns = get_num_severe_underruns;
 	iodev->get_valid_frames = get_valid_frames;
 	iodev->set_swap_mode_for_node = cras_iodev_dsp_set_swap_mode_for_node;
+	iodev->support_noise_cancellation = support_noise_cancellation;
 
 	if (card_type == ALSA_CARD_TYPE_USB)
 		iodev->min_buffer_level = USB_EXTRA_BUFFER_FRAMES;
@@ -2418,12 +2444,10 @@
 				      unsigned dev_enabled)
 {
 	struct alsa_io *aio = (struct alsa_io *)iodev;
+	int rc = 0;
 
-	if (iodev->active_node == ionode) {
-		enable_active_ucm(aio, dev_enabled);
-		init_device_settings(aio);
-		return 0;
-	}
+	if (iodev->active_node == ionode)
+		goto skip;
 
 	/* Disable jack ucm before switching node. */
 	enable_active_ucm(aio, 0);
@@ -2433,7 +2457,16 @@
 	cras_iodev_set_active_node(iodev, ionode);
 	aio->base.dsp_name = get_active_dsp_name(aio);
 	cras_iodev_update_dsp(iodev);
+skip:
 	enable_active_ucm(aio, dev_enabled);
+	if (ionode->type == CRAS_NODE_TYPE_HOTWORD) {
+		if (dev_enabled) {
+			rc = ucm_enable_hotword_model(aio->ucm);
+			if (rc < 0)
+				return rc;
+		} else
+			ucm_disable_all_hotword_models(aio->ucm);
+	}
 	/* Setting the volume will also unmute if the system isn't muted. */
 	init_device_settings(aio);
 	return 0;
diff --git a/cras/src/server/cras_alsa_mixer.c b/cras/src/server/cras_alsa_mixer.c
index 1070557..3379d95 100644
--- a/cras/src/server/cras_alsa_mixer.c
+++ b/cras/src/server/cras_alsa_mixer.c
@@ -943,7 +943,7 @@
 	assert(cras_mixer);
 
 	/* dBFS is normally < 0 to specify the attenuation from max. max is the
-	 * combined max of the master controls and the current output.
+	 * combined max of the main controls and the current output.
 	 */
 	to_set = dBFS + cras_mixer->max_volume_dB;
 	if (cras_alsa_mixer_has_volume(mixer_output))
diff --git a/cras/src/server/cras_alsa_mixer.h b/cras/src/server/cras_alsa_mixer.h
index 3f730cf..878fbe5 100644
--- a/cras/src/server/cras_alsa_mixer.h
+++ b/cras/src/server/cras_alsa_mixer.h
@@ -147,7 +147,7 @@
  * Args:
  *    cras_mixer - Mixer to set the volume in.
  *    muted - 1 if muted, 0 if not.
- *    mixer_input - The mixer input to mute if no master mute.
+ *    mixer_input - The mixer input to mute if no card mute.
  */
 void cras_alsa_mixer_set_capture_mute(struct cras_alsa_mixer *cras_mixer,
 				      int muted,
diff --git a/cras/src/server/cras_alsa_ucm.c b/cras/src/server/cras_alsa_ucm.c
index 9759a50..3e46f6a 100644
--- a/cras/src/server/cras_alsa_ucm.c
+++ b/cras/src/server/cras_alsa_ucm.c
@@ -22,7 +22,6 @@
 static const char dsp_name_var[] = "DspName";
 static const char playback_mixer_elem_var[] = "PlaybackMixerElem";
 static const char capture_mixer_elem_var[] = "CaptureMixerElem";
-static const char swap_mode_suffix[] = "Swap Mode";
 static const char min_buffer_level_var[] = "MinBufferLevel";
 static const char dma_period_var[] = "DmaPeriodMicrosecs";
 static const char disable_software_volume[] = "DisableSoftwareVolume";
@@ -38,6 +37,11 @@
 static const char preempt_hotword_var[] = "PreemptHotword";
 static const char echo_reference_dev_name_var[] = "EchoReferenceDev";
 
+/* SectionModifier prefixes and suffixes. */
+static const char hotword_model_prefix[] = "Hotword Model";
+static const char swap_mode_suffix[] = "Swap Mode";
+static const char noise_cancellation_suffix[] = "Noise Cancellation";
+
 /*
  * Set this value in a SectionDevice to specify the intrinsic sensitivity in
  * 0.01 dBFS/Pa. It currently only supports input devices. You should get the
@@ -54,7 +58,6 @@
  * 0.01 dB.
  */
 static const char default_node_gain[] = "DefaultNodeGain";
-static const char hotword_model_prefix[] = "Hotword Model";
 static const char fully_specified_ucm_var[] = "FullySpecifiedUCM";
 static const char main_volume_names[] = "MainVolumeNames";
 
@@ -64,6 +67,8 @@
 	"Speech", "Pro Audio",	"Accessibility",
 };
 
+static const size_t max_section_name_len = 100;
+
 /* Represents a list of section names found in UCM. */
 struct section_name {
 	const char *name;
@@ -72,9 +77,10 @@
 
 struct cras_use_case_mgr {
 	snd_use_case_mgr_t *mgr;
-	const char *name;
+	char *name;
 	unsigned int avail_use_cases;
 	enum CRAS_STREAM_TYPE use_case;
+	char *hotword_modifier;
 };
 
 static inline const char *uc_verb(struct cras_use_case_mgr *mgr)
@@ -376,6 +382,21 @@
 	return names;
 }
 
+/* Gets the modifier name of Noise Cancellation for the given node_name. */
+static void ucm_get_node_noise_cancellation_name(const char *node_name,
+						 char *mod_name)
+{
+	size_t len =
+		strlen(node_name) + 1 + strlen(noise_cancellation_suffix) + 1;
+	if (len > max_section_name_len) {
+		syslog(LOG_ERR,
+		       "Length of the given section name is %zu > %zu(max)",
+		       len, max_section_name_len);
+		len = max_section_name_len;
+	}
+	snprintf(mod_name, len, "%s %s", node_name, noise_cancellation_suffix);
+}
+
 /* Exported Interface */
 
 struct cras_use_case_mgr *ucm_create(const char *name)
@@ -394,6 +415,10 @@
 	if (!mgr)
 		return NULL;
 
+	mgr->name = strdup(name);
+	if (!mgr->name)
+		goto cleanup;
+
 	rc = snd_use_case_mgr_open(&mgr->mgr, name);
 	if (rc) {
 		syslog(LOG_WARNING, "Can not open ucm for card %s, rc = %d",
@@ -401,8 +426,8 @@
 		goto cleanup;
 	}
 
-	mgr->name = name;
 	mgr->avail_use_cases = 0;
+	mgr->hotword_modifier = NULL;
 	num_verbs = snd_use_case_get_list(mgr->mgr, "_verbs", &list);
 	for (i = 0; i < num_verbs; i += 2) {
 		for (j = 0; j < CRAS_STREAM_NUM_TYPES; ++j) {
@@ -424,6 +449,7 @@
 cleanup_mgr:
 	snd_use_case_mgr_close(mgr->mgr);
 cleanup:
+	free(mgr->name);
 	free(mgr);
 	return NULL;
 }
@@ -431,6 +457,8 @@
 void ucm_destroy(struct cras_use_case_mgr *mgr)
 {
 	snd_use_case_mgr_close(mgr->mgr);
+	free(mgr->hotword_modifier);
+	free(mgr->name);
 	free(mgr);
 }
 
@@ -487,6 +515,51 @@
 	return rc;
 }
 
+int ucm_node_noise_cancellation_exists(struct cras_use_case_mgr *mgr,
+				       const char *node_name)
+{
+	char *node_modifier_name = NULL;
+	int exists;
+
+	node_modifier_name = (char *)malloc(max_section_name_len);
+	if (!node_modifier_name)
+		return 0;
+	ucm_get_node_noise_cancellation_name(node_name, node_modifier_name);
+	exists = ucm_mod_exists_with_name(mgr, node_modifier_name);
+	free((void *)node_modifier_name);
+	return exists;
+}
+
+int ucm_enable_node_noise_cancellation(struct cras_use_case_mgr *mgr,
+				       const char *node_name, int enable)
+{
+	char *node_modifier_name = NULL;
+	int rc;
+
+	node_modifier_name = (char *)malloc(max_section_name_len);
+	if (!node_modifier_name)
+		return -ENOMEM;
+	ucm_get_node_noise_cancellation_name(node_name, node_modifier_name);
+	if (!ucm_mod_exists_with_name(mgr, node_modifier_name)) {
+		syslog(LOG_ERR, "Can not find modifier %s.",
+		       node_modifier_name);
+		free((void *)node_modifier_name);
+		return -EPERM;
+	}
+	if (modifier_enabled(mgr, node_modifier_name) == !!enable) {
+		syslog(LOG_DEBUG, "Modifier %s is already %s.",
+		       node_modifier_name, enable ? "enabled" : "disabled");
+		free((void *)node_modifier_name);
+		return 0;
+	}
+
+	syslog(LOG_DEBUG, "UCM %s Modifier %s", enable ? "enable" : "disable",
+	       node_modifier_name);
+	rc = ucm_set_modifier_enabled(mgr, node_modifier_name, enable);
+	free((void *)node_modifier_name);
+	return rc;
+}
+
 int ucm_set_enabled(struct cras_use_case_mgr *mgr, const char *dev, int enable)
 {
 	int rc;
@@ -984,14 +1057,61 @@
 	return models;
 }
 
-int ucm_set_hotword_model(struct cras_use_case_mgr *mgr, const char *model)
+void ucm_disable_all_hotword_models(struct cras_use_case_mgr *mgr)
 {
 	const char **list;
 	int num_enmods, mod_idx;
-	char *model_mod = NULL;
+
+	if (!mgr)
+		return;
+
+	/* Disable all currently enabled hotword model modifiers. */
+	num_enmods = snd_use_case_get_list(mgr->mgr, "_enamods", &list);
+	if (num_enmods <= 0)
+		return;
+
+	for (mod_idx = 0; mod_idx < num_enmods; mod_idx++) {
+		if (!strncmp(list[mod_idx], hotword_model_prefix,
+			     strlen(hotword_model_prefix)))
+			ucm_set_modifier_enabled(mgr, list[mod_idx], 0);
+	}
+	snd_use_case_free_list(list, num_enmods);
+}
+
+int ucm_enable_hotword_model(struct cras_use_case_mgr *mgr)
+{
+	if (mgr->hotword_modifier)
+		return ucm_set_modifier_enabled(mgr, mgr->hotword_modifier, 1);
+	return -EINVAL;
+}
+
+static int ucm_is_modifier_enabled(struct cras_use_case_mgr *mgr,
+				   char *modifier, long *value)
+{
+	int rc;
+	char *id;
+	size_t len = strlen(modifier) + 11 + 1;
+
+	id = (char *)malloc(len);
+
+	if (!id)
+		return -ENOMEM;
+
+	snprintf(id, len, "_modstatus/%s", modifier);
+	rc = snd_use_case_geti(mgr->mgr, id, value);
+	free(id);
+	return rc;
+}
+
+int ucm_set_hotword_model(struct cras_use_case_mgr *mgr, const char *model)
+{
+	char *model_mod;
+	long mod_status = 0;
 	size_t model_mod_size =
 		strlen(model) + 1 + strlen(hotword_model_prefix) + 1;
+
 	model_mod = (char *)malloc(model_mod_size);
+
 	if (!model_mod)
 		return -ENOMEM;
 	snprintf(model_mod, model_mod_size, "%s %s", hotword_model_prefix,
@@ -1001,21 +1121,16 @@
 		return -EINVAL;
 	}
 
-	/* Disable all currently enabled horword model modifiers. */
-	num_enmods = snd_use_case_get_list(mgr->mgr, "_enamods", &list);
-	if (num_enmods <= 0)
-		goto enable_mod;
+	/* If check failed, just move on, dont fail incoming model */
+	if (mgr->hotword_modifier)
+		ucm_is_modifier_enabled(mgr, mgr->hotword_modifier,
+					&mod_status);
 
-	for (mod_idx = 0; mod_idx < num_enmods; mod_idx++) {
-		if (!strncmp(list[mod_idx], hotword_model_prefix,
-			     strlen(hotword_model_prefix)))
-			ucm_set_modifier_enabled(mgr, list[mod_idx], 0);
-	}
-	snd_use_case_free_list(list, num_enmods);
-
-enable_mod:
-	ucm_set_modifier_enabled(mgr, model_mod, 1);
-	free((void *)model_mod);
+	ucm_disable_all_hotword_models(mgr);
+	free(mgr->hotword_modifier);
+	mgr->hotword_modifier = model_mod;
+	if (mod_status)
+		return ucm_enable_hotword_model(mgr);
 	return 0;
 }
 
diff --git a/cras/src/server/cras_alsa_ucm.h b/cras/src/server/cras_alsa_ucm.h
index 99a8b44..55c3cf6 100644
--- a/cras/src/server/cras_alsa_ucm.h
+++ b/cras/src/server/cras_alsa_ucm.h
@@ -67,6 +67,28 @@
 int ucm_enable_swap_mode(struct cras_use_case_mgr *mgr, const char *node_name,
 			 int enable);
 
+/* Checks if modifier of noise cancellation for given node_name exists in ucm.
+ * Args:
+ *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
+ *    node_name - The node name.
+ * Returns:
+ *    1 if it exists, 0 otherwise.
+ */
+int ucm_node_noise_cancellation_exists(struct cras_use_case_mgr *mgr,
+				       const char *node_name);
+
+/* Enables or disables noise cancellation for the given node_name. First checks
+ * if the modifier is already enabled or disabled.
+ * Args:
+ *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
+ *    node_name - The node name.
+ *    enable - Enable device if non-zero.
+ * Returns:
+ *    0 on success or negative error code on failure.
+ */
+int ucm_enable_node_noise_cancellation(struct cras_use_case_mgr *mgr,
+				       const char *node_name, int enable);
+
 /* Enables or disables a UCM device.  First checks if the device is already
  * enabled or disabled.
  * Args:
@@ -306,11 +328,26 @@
 /* Sets the desired hotword model.
  * Args:
  *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
+ *    model - locale for model
  * Returns:
  *    0 on success or negative error code on failure.
  */
 int ucm_set_hotword_model(struct cras_use_case_mgr *mgr, const char *model);
 
+/* Enable previously set hotword modifier
+ * Args:
+ *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
+ * Returns:
+ *    0 on success or negative error code on failure.
+ */
+int ucm_enable_hotword_model(struct cras_use_case_mgr *mgr);
+
+/* Disable all hotword model modifiers
+ * Args:
+ *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
+ */
+void ucm_disable_all_hotword_models(struct cras_use_case_mgr *mgr);
+
 /* Checks if this card has fully specified UCM config.
  *
  * Args:
diff --git a/cras/src/server/cras_apm_list.c b/cras/src/server/cras_apm_list.c
index ac57d86..ab89113 100644
--- a/cras/src/server/cras_apm_list.c
+++ b/cras/src/server/cras_apm_list.c
@@ -61,9 +61,10 @@
  *        stream.
  *    work_queue - A task queue instance created and destroyed by
  *        libwebrtc_apm.
- *    use_tuned_settings - True if this APM uses settings tuned specifically
- *        for this hardware in AEC use case. Otherwise it uses the generic
- *        settings like run inside browser.
+ *    is_aec_use_case - True if the input and output devices pair is in the
+ *        typical AEC use case. This flag decides whether to use settings
+ *        tuned specifically for this hardware if exists. Otherwise it uses
+ *        the generic settings like run inside browser.
  */
 struct cras_apm {
 	webrtc_apm apm_ptr;
@@ -74,7 +75,7 @@
 	struct cras_audio_format fmt;
 	struct cras_audio_area *area;
 	void *work_queue;
-	bool use_tuned_settings;
+	bool is_aec_use_case;
 	struct cras_apm *prev, *next;
 };
 
@@ -239,13 +240,12 @@
 	int ch;
 	int8_t layout[CRAS_CH_MAX];
 
-	/* Assume device format has correct channel layout populated. */
-	if (apm_fmt->num_channels <= 2)
-		return;
-
-	/* If the device provides recording from more channels than we care
-	 * about, construct a new channel layout containing subset of original
-	 * channels that matches either FL, FR, or FC.
+	/* Using the format from dev_fmt is dangerous because input device
+	 * could have wild configurations like unuse the 1st channel and
+	 * connects 2nd channel to the only mic. Data in the first channel
+	 * is what APM cares about so always construct a new channel layout
+	 * containing subset of original channels that matches either FL, FR,
+	 * or FC.
 	 * TODO(hychao): extend the logic when we have a stream that wants
 	 * to record channels like RR(rear right).
 	 */
@@ -290,16 +290,16 @@
 
 	/* Use tuned settings only when the forward dev(capture) and reverse
 	 * dev(playback) both are in typical AEC use case. */
-	apm->use_tuned_settings = is_aec_use_case;
+	apm->is_aec_use_case = is_aec_use_case;
 	if (rmodule->odev) {
-		apm->use_tuned_settings &=
+		apm->is_aec_use_case &=
 			cras_iodev_is_aec_use_case(rmodule->odev->active_node);
 	}
 
 	/* Use the configs tuned specifically for internal device. Otherwise
 	 * just pass NULL so every other settings will be default. */
 	apm->apm_ptr =
-		apm->use_tuned_settings ?
+		apm->is_aec_use_case ?
 			webrtc_apm_create(apm->fmt.num_channels,
 					  apm->fmt.frame_rate, aec_ini,
 					  apm_ini) :
@@ -691,7 +691,9 @@
 
 bool cras_apm_list_get_use_tuned_settings(struct cras_apm *apm)
 {
-	return apm->use_tuned_settings;
+	/* If input and output devices in AEC use case, plus that a
+	 * tuned setting is provided. */
+	return apm->is_aec_use_case && (aec_ini || apm_ini);
 }
 
 void cras_apm_list_set_aec_dump(struct cras_apm_list *list, void *dev_ptr,
diff --git a/cras/src/server/cras_bt_battery_provider.c b/cras/src/server/cras_bt_battery_provider.c
new file mode 100644
index 0000000..13e6590
--- /dev/null
+++ b/cras/src/server/cras_bt_battery_provider.c
@@ -0,0 +1,371 @@
+/* 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.
+ */
+#include <dbus/dbus.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "cras_bt_adapter.h"
+#include "cras_bt_battery_provider.h"
+#include "cras_bt_constants.h"
+#include "cras_dbus_util.h"
+#include "cras_observer.h"
+#include "utlist.h"
+
+/* CRAS registers one battery provider to BlueZ, so we use a singleton. */
+static struct cras_bt_battery_provider battery_provider = {
+	.object_path = CRAS_DEFAULT_BATTERY_PROVIDER,
+	.interface = BLUEZ_INTERFACE_BATTERY_PROVIDER,
+	.conn = NULL,
+	.is_registered = false,
+	.observer = NULL,
+	.batteries = NULL,
+};
+
+static int cmp_battery_address(const struct cras_bt_battery *battery,
+			       const char *address)
+{
+	return strcmp(battery->address, address);
+}
+
+static void replace_colon_with_underscore(char *str)
+{
+	for (int i = 0; str[i]; i++) {
+		if (str[i] == ':')
+			str[i] = '_';
+	}
+}
+
+/* Converts address XX:XX:XX:XX:XX:XX to Battery Provider object path:
+ * /org/chromium/Cras/Bluetooth/BatteryProvider/XX_XX_XX_XX_XX_XX
+ */
+static char *address_to_battery_path(const char *address)
+{
+	char *object_path = malloc(strlen(CRAS_DEFAULT_BATTERY_PROVIDER) +
+				   strlen(address) + 2);
+
+	sprintf(object_path, "%s/%s", CRAS_DEFAULT_BATTERY_PROVIDER, address);
+	replace_colon_with_underscore(object_path);
+
+	return object_path;
+}
+
+/* Converts address XX:XX:XX:XX:XX:XX to device object path:
+ * /org/bluez/hci0/dev_XX_XX_XX_XX_XX_XX
+ */
+static char *address_to_device_path(const char *address)
+{
+	char *object_path = malloc(strlen(CRAS_DEFAULT_BATTERY_PREFIX) +
+				   strlen(address) + 1);
+
+	sprintf(object_path, "%s%s", CRAS_DEFAULT_BATTERY_PREFIX, address);
+	replace_colon_with_underscore(object_path);
+
+	return object_path;
+}
+
+static struct cras_bt_battery *battery_new(const char *address, uint32_t level)
+{
+	struct cras_bt_battery *battery;
+
+	battery = calloc(1, sizeof(struct cras_bt_battery));
+	battery->address = strdup(address);
+	battery->object_path = address_to_battery_path(address);
+	battery->device_path = address_to_device_path(address);
+	battery->level = level;
+
+	return battery;
+}
+
+static void battery_free(struct cras_bt_battery *battery)
+{
+	if (battery->address)
+		free(battery->address);
+	if (battery->object_path)
+		free(battery->object_path);
+	if (battery->device_path)
+		free(battery->device_path);
+	free(battery);
+}
+
+static void populate_battery_properties(DBusMessageIter *iter,
+					const struct cras_bt_battery *battery)
+{
+	DBusMessageIter dict, entry, variant;
+	const char *property_percentage = "Percentage";
+	const char *property_device = "Device";
+	uint8_t level = battery->level;
+
+	dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &dict);
+
+	dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL,
+					 &entry);
+	dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING,
+				       &property_percentage);
+	dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT,
+					 DBUS_TYPE_BYTE_AS_STRING, &variant);
+	dbus_message_iter_append_basic(&variant, DBUS_TYPE_BYTE, &level);
+	dbus_message_iter_close_container(&entry, &variant);
+	dbus_message_iter_close_container(&dict, &entry);
+
+	dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL,
+					 &entry);
+	dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING,
+				       &property_device);
+	dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT,
+					 DBUS_TYPE_OBJECT_PATH_AS_STRING,
+					 &variant);
+	dbus_message_iter_append_basic(&variant, DBUS_TYPE_OBJECT_PATH,
+				       &battery->device_path);
+	dbus_message_iter_close_container(&entry, &variant);
+	dbus_message_iter_close_container(&dict, &entry);
+
+	dbus_message_iter_close_container(iter, &dict);
+}
+
+/* Creates a new battery object and exposes it on D-Bus. */
+static struct cras_bt_battery *
+get_or_create_battery(struct cras_bt_battery_provider *provider,
+		      const char *address, uint32_t level)
+{
+	struct cras_bt_battery *battery;
+	DBusMessage *msg;
+	DBusMessageIter iter, dict, entry;
+
+	LL_SEARCH(provider->batteries, battery, address, cmp_battery_address);
+
+	if (battery)
+		return battery;
+
+	syslog(LOG_DEBUG, "Creating new battery for %s", address);
+
+	battery = battery_new(address, level);
+	LL_APPEND(provider->batteries, battery);
+
+	msg = dbus_message_new_signal(CRAS_DEFAULT_BATTERY_PROVIDER,
+				      DBUS_INTERFACE_OBJECT_MANAGER,
+				      DBUS_SIGNAL_INTERFACES_ADDED);
+
+	dbus_message_iter_init_append(msg, &iter);
+	dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH,
+				       &battery->object_path);
+	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sa{sv}}",
+					 &dict);
+	dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL,
+					 &entry);
+	dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING,
+				       &provider->interface);
+	populate_battery_properties(&entry, battery);
+	dbus_message_iter_close_container(&dict, &entry);
+	dbus_message_iter_close_container(&iter, &dict);
+
+	if (!dbus_connection_send(provider->conn, msg, NULL)) {
+		syslog(LOG_ERR,
+		       "Error sending " DBUS_SIGNAL_INTERFACES_ADDED " signal");
+	}
+
+	dbus_message_unref(msg);
+
+	return battery;
+}
+
+/* Updates the level of a battery object and signals it on D-Bus. */
+static void
+update_battery_level(const struct cras_bt_battery_provider *provider,
+		     struct cras_bt_battery *battery, uint32_t level)
+{
+	DBusMessage *msg;
+	DBusMessageIter iter;
+
+	if (battery->level == level)
+		return;
+
+	battery->level = level;
+
+	msg = dbus_message_new_signal(battery->object_path,
+				      DBUS_INTERFACE_PROPERTIES,
+				      DBUS_SIGNAL_PROPERTIES_CHANGED);
+
+	dbus_message_iter_init_append(msg, &iter);
+	dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING,
+				       &provider->interface);
+	populate_battery_properties(&iter, battery);
+
+	if (!dbus_connection_send(provider->conn, msg, NULL)) {
+		syslog(LOG_ERR, "Error sending " DBUS_SIGNAL_PROPERTIES_CHANGED
+				" signal");
+	}
+
+	dbus_message_unref(msg);
+}
+
+/* Invoked when HFP sends an alert about a battery value change. */
+static void on_bt_battery_changed(void *context, const char *address,
+				  uint32_t level)
+{
+	struct cras_bt_battery_provider *provider = context;
+
+	syslog(LOG_DEBUG, "Battery changed for address %s, level %d", address,
+	       level);
+
+	if (!provider->is_registered) {
+		syslog(LOG_WARNING, "Received battery level update while "
+				    "battery provider is not registered");
+		return;
+	}
+
+	struct cras_bt_battery *battery =
+		get_or_create_battery(provider, address, level);
+
+	update_battery_level(provider, battery, level);
+}
+
+/* Invoked when we receive a D-Bus return of RegisterBatteryProvider from
+ * BlueZ.
+ */
+static void
+cras_bt_on_battery_provider_registered(DBusPendingCall *pending_call,
+				       void *data)
+{
+	DBusMessage *reply;
+	struct cras_bt_battery_provider *provider = data;
+	struct cras_observer_ops observer_ops;
+
+	reply = dbus_pending_call_steal_reply(pending_call);
+	dbus_pending_call_unref(pending_call);
+
+	if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) {
+		syslog(LOG_ERR, "RegisterBatteryProvider returned error: %s",
+		       dbus_message_get_error_name(reply));
+		dbus_message_unref(reply);
+		return;
+	}
+
+	syslog(LOG_INFO, "RegisterBatteryProvider succeeded");
+
+	provider->is_registered = true;
+
+	memset(&observer_ops, 0, sizeof(observer_ops));
+	observer_ops.bt_battery_changed = on_bt_battery_changed;
+	provider->observer = cras_observer_add(&observer_ops, provider);
+
+	dbus_message_unref(reply);
+}
+
+int cras_bt_register_battery_provider(DBusConnection *conn,
+				      const struct cras_bt_adapter *adapter)
+{
+	const char *adapter_path;
+	DBusMessage *method_call;
+	DBusMessageIter message_iter;
+	DBusPendingCall *pending_call;
+
+	if (battery_provider.is_registered) {
+		syslog(LOG_ERR, "Battery Provider already registered");
+		return -EBUSY;
+	}
+
+	if (battery_provider.conn)
+		dbus_connection_unref(battery_provider.conn);
+
+	battery_provider.conn = conn;
+	dbus_connection_ref(battery_provider.conn);
+
+	adapter_path = cras_bt_adapter_object_path(adapter);
+	method_call = dbus_message_new_method_call(
+		BLUEZ_SERVICE, adapter_path,
+		BLUEZ_INTERFACE_BATTERY_PROVIDER_MANAGER,
+		"RegisterBatteryProvider");
+	if (!method_call)
+		return -ENOMEM;
+
+	dbus_message_iter_init_append(method_call, &message_iter);
+	dbus_message_iter_append_basic(&message_iter, DBUS_TYPE_OBJECT_PATH,
+				       &battery_provider.object_path);
+
+	if (!dbus_connection_send_with_reply(conn, method_call, &pending_call,
+					     DBUS_TIMEOUT_USE_DEFAULT)) {
+		dbus_message_unref(method_call);
+		return -ENOMEM;
+	}
+
+	dbus_message_unref(method_call);
+
+	if (!pending_call)
+		return -EIO;
+
+	if (!dbus_pending_call_set_notify(
+		    pending_call, cras_bt_on_battery_provider_registered,
+		    &battery_provider, NULL)) {
+		dbus_pending_call_cancel(pending_call);
+		dbus_pending_call_unref(pending_call);
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+/* Removes a battery object and signals the removal on D-Bus as well. */
+static void cleanup_battery(struct cras_bt_battery_provider *provider,
+			    struct cras_bt_battery *battery)
+{
+	DBusMessage *msg;
+	DBusMessageIter iter, entry;
+
+	if (!battery)
+		return;
+
+	LL_DELETE(provider->batteries, battery);
+
+	msg = dbus_message_new_signal(CRAS_DEFAULT_BATTERY_PROVIDER,
+				      DBUS_INTERFACE_OBJECT_MANAGER,
+				      DBUS_SIGNAL_INTERFACES_REMOVED);
+
+	dbus_message_iter_init_append(msg, &iter);
+	dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH,
+				       &battery->object_path);
+	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+					 DBUS_TYPE_STRING_AS_STRING, &entry);
+	dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING,
+				       &provider->interface);
+	dbus_message_iter_close_container(&iter, &entry);
+
+	if (!dbus_connection_send(provider->conn, msg, NULL)) {
+		syslog(LOG_ERR, "Error sending " DBUS_SIGNAL_INTERFACES_REMOVED
+				" signal");
+	}
+
+	dbus_message_unref(msg);
+
+	battery_free(battery);
+}
+
+void cras_bt_battery_provider_reset()
+{
+	struct cras_bt_battery *battery;
+
+	syslog(LOG_INFO, "Resetting battery provider");
+
+	if (!battery_provider.is_registered)
+		return;
+
+	battery_provider.is_registered = false;
+
+	LL_FOREACH (battery_provider.batteries, battery) {
+		cleanup_battery(&battery_provider, battery);
+	}
+
+	if (battery_provider.conn) {
+		dbus_connection_unref(battery_provider.conn);
+		battery_provider.conn = NULL;
+	}
+
+	if (battery_provider.observer) {
+		cras_observer_remove(battery_provider.observer);
+		battery_provider.observer = NULL;
+	}
+}
diff --git a/cras/src/server/cras_bt_battery_provider.h b/cras/src/server/cras_bt_battery_provider.h
new file mode 100644
index 0000000..1998cd7
--- /dev/null
+++ b/cras/src/server/cras_bt_battery_provider.h
@@ -0,0 +1,47 @@
+/* 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.
+ */
+
+#ifndef CRAS_BT_BATTERY_PROVIDER_H_
+#define CRAS_BT_BATTERY_PROVIDER_H_
+
+#include <dbus/dbus.h>
+#include <stdbool.h>
+
+#include "cras_bt_adapter.h"
+
+/* Object to represent a battery that is exposed to BlueZ. */
+struct cras_bt_battery {
+	char *address;
+	char *object_path;
+	char *device_path;
+	uint32_t level;
+	struct cras_bt_battery *next;
+};
+
+/* Object to register as battery provider so that bluetoothd will monitor
+ * battery objects that we expose.
+ */
+struct cras_bt_battery_provider {
+	const char *object_path;
+	const char *interface;
+	DBusConnection *conn;
+	bool is_registered;
+	struct cras_observer_client *observer;
+	struct cras_bt_battery *batteries;
+};
+
+/* Registers battery provider to bluetoothd. This is used when a Bluetooth
+ * adapter got enumerated.
+ * Args:
+ *    conn - The D-Bus connection.
+ *    adapter - The enumerated bluetooth adapter.
+ */
+int cras_bt_register_battery_provider(DBusConnection *conn,
+				      const struct cras_bt_adapter *adapter);
+
+/* Resets internal state of battery provider. */
+void cras_bt_battery_provider_reset();
+
+#endif /* CRAS_BT_BATTERY_PROVIDER_H_ */
diff --git a/cras/src/server/cras_bt_constants.h b/cras/src/server/cras_bt_constants.h
index 618ac87..318aeca 100644
--- a/cras/src/server/cras_bt_constants.h
+++ b/cras/src/server/cras_bt_constants.h
@@ -9,6 +9,9 @@
 #define BLUEZ_SERVICE "org.bluez"
 
 #define BLUEZ_INTERFACE_ADAPTER "org.bluez.Adapter1"
+#define BLUEZ_INTERFACE_BATTERY_PROVIDER "org.bluez.BatteryProvider1"
+#define BLUEZ_INTERFACE_BATTERY_PROVIDER_MANAGER                               \
+	"org.bluez.BatteryProviderManager1"
 #define BLUEZ_INTERFACE_DEVICE "org.bluez.Device1"
 #define BLUEZ_INTERFACE_MEDIA "org.bluez.Media1"
 #define BLUEZ_INTERFACE_MEDIA_ENDPOINT "org.bluez.MediaEndpoint1"
@@ -21,6 +24,9 @@
 #ifndef DBUS_INTERFACE_OBJECT_MANAGER
 #define DBUS_INTERFACE_OBJECT_MANAGER "org.freedesktop.DBus.ObjectManager"
 #endif
+#define DBUS_SIGNAL_INTERFACES_ADDED "InterfacesAdded"
+#define DBUS_SIGNAL_INTERFACES_REMOVED "InterfacesRemoved"
+#define DBUS_SIGNAL_PROPERTIES_CHANGED "PropertiesChanged"
 
 /* UUIDs taken from lib/uuid.h in the BlueZ source */
 #define HSP_HS_UUID "00001108-0000-1000-8000-00805f9b34fb"
@@ -49,6 +55,10 @@
 #define CRAS_PLAYER_IDENTITY_DEFAULT "DefaultPlayer"
 #define CRAS_PLAYER_METADATA_SIZE_MAX 128 * sizeof(char)
 
+#define CRAS_DEFAULT_BATTERY_PROVIDER                                          \
+	"/org/chromium/Cras/Bluetooth/BatteryProvider"
+#define CRAS_DEFAULT_BATTERY_PREFIX "/org/bluez/hci0/dev_"
+
 /* Instead of letting CRAS obtain the A2DP streaming packet size (a.k.a. AVDTP
  * MTU) from BlueZ Media Transport, force the packet size to the default L2CAP
  * packet size. This prevent the audio peripheral device to negotiate a larger
diff --git a/cras/src/server/cras_bt_device.c b/cras/src/server/cras_bt_device.c
index 70c8747..6b06dd1 100644
--- a/cras/src/server/cras_bt_device.c
+++ b/cras/src/server/cras_bt_device.c
@@ -34,6 +34,7 @@
 #include "cras_server_metrics.h"
 #include "cras_system_state.h"
 #include "cras_tm.h"
+#include "sfh.h"
 #include "utlist.h"
 
 /*
@@ -91,6 +92,7 @@
  *    sco_fd - The file descriptor of the SCO connection.
  *    sco_ref_count - The reference counts of the SCO connection.
  *    suspend_reason - The reason code for why suspend is scheduled.
+ *    stable_id - The unique and persistent id of this bt_device.
  */
 struct cras_bt_device {
 	DBusConnection *conn;
@@ -115,6 +117,7 @@
 	int sco_fd;
 	size_t sco_ref_count;
 	enum cras_bt_device_suspend_reason suspend_reason;
+	unsigned int stable_id;
 
 	struct cras_bt_device *prev, *next;
 };
@@ -174,6 +177,9 @@
 		free(device);
 		return NULL;
 	}
+	device->stable_id =
+		SuperFastHash(device->object_path, strlen(device->object_path),
+			      strlen(device->object_path));
 
 	DL_APPEND(devices, device);
 
@@ -343,6 +349,11 @@
 	return device->object_path;
 }
 
+int cras_bt_device_get_stable_id(const struct cras_bt_device *device)
+{
+	return device->stable_id;
+}
+
 struct cras_bt_adapter *
 cras_bt_device_adapter(const struct cras_bt_device *device)
 {
@@ -704,10 +715,14 @@
 void cras_bt_device_set_connected(struct cras_bt_device *device, int value)
 {
 	struct cras_tm *tm = cras_system_state_get_tm();
-	if (device->connected || value)
-		BTLOG(btlog, BT_DEV_CONNECTED_CHANGE, device->profiles, value);
+	if (!device->connected && value) {
+		BTLOG(btlog, BT_DEV_CONNECTED, device->profiles,
+		      device->stable_id);
+	}
 
 	if (device->connected && !value) {
+		BTLOG(btlog, BT_DEV_DISCONNECTED, device->profiles,
+		      device->stable_id);
 		cras_bt_profile_on_device_disconnected(device);
 		/* Device is disconnected, resets connected profiles and the
 		 * suspend timer which scheduled earlier. */
diff --git a/cras/src/server/cras_bt_device.h b/cras/src/server/cras_bt_device.h
index 4202bc9..9d3a2b9 100644
--- a/cras/src/server/cras_bt_device.h
+++ b/cras/src/server/cras_bt_device.h
@@ -50,6 +50,10 @@
 struct cras_bt_device *cras_bt_device_get(const char *object_path);
 
 const char *cras_bt_device_object_path(const struct cras_bt_device *device);
+
+/* Gets the stable id of given cras_bt_device. */
+int cras_bt_device_get_stable_id(const struct cras_bt_device *device);
+
 struct cras_bt_adapter *
 cras_bt_device_adapter(const struct cras_bt_device *device);
 const char *cras_bt_device_address(const struct cras_bt_device *device);
diff --git a/cras/src/server/cras_bt_io.c b/cras/src/server/cras_bt_io.c
index 9f5c2f7..acdca80 100644
--- a/cras/src/server/cras_bt_io.c
+++ b/cras/src/server/cras_bt_io.c
@@ -527,10 +527,7 @@
 	active->base.idx = btio->next_node_id++;
 	active->base.type = dev->active_node->type;
 	active->base.volume = 100;
-	active->base.stable_id =
-		SuperFastHash(cras_bt_device_object_path(device),
-			      strlen(cras_bt_device_object_path(device)),
-			      strlen(cras_bt_device_object_path(device)));
+	active->base.stable_id = cras_bt_device_get_stable_id(device);
 	active->base.ui_gain_scaler = 1.0f;
 	/*
 	 * If the same headset is connected in wideband mode, we shall assign
diff --git a/cras/src/server/cras_bt_manager.c b/cras/src/server/cras_bt_manager.c
index 77e8079..a710340 100644
--- a/cras/src/server/cras_bt_manager.c
+++ b/cras/src/server/cras_bt_manager.c
@@ -19,6 +19,7 @@
 #include "cras_bt_player.h"
 #include "cras_bt_profile.h"
 #include "cras_bt_transport.h"
+#include "cras_bt_battery_provider.h"
 #include "utlist.h"
 
 struct cras_bt_event_log *btlog;
@@ -120,6 +121,32 @@
 				       object_path);
 			}
 		}
+	} else if (strcmp(interface_name,
+			  BLUEZ_INTERFACE_BATTERY_PROVIDER_MANAGER) == 0) {
+		struct cras_bt_adapter *adapter;
+		int ret;
+
+		syslog(LOG_INFO,
+		       "Bluetooth Battery Provider Manager available");
+
+		adapter = cras_bt_adapter_get(object_path);
+		if (adapter) {
+			syslog(LOG_INFO,
+			       "Registering Battery Provider for adapter %s",
+			       cras_bt_adapter_address(adapter));
+			ret = cras_bt_register_battery_provider(conn, adapter);
+			if (ret != 0) {
+				syslog(LOG_ERR,
+				       "Error registering Battery Provider "
+				       "for adapter %s: %s",
+				       cras_bt_adapter_address(adapter),
+				       strerror(-ret));
+			}
+		} else {
+			syslog(LOG_WARNING,
+			       "Adapter not available when trying to create "
+			       "Battery Provider");
+		}
 	}
 }
 
@@ -158,6 +185,10 @@
 			       cras_bt_transport_object_path(transport));
 			cras_bt_transport_remove(transport);
 		}
+	} else if (strcmp(interface_name,
+			  BLUEZ_INTERFACE_BATTERY_PROVIDER_MANAGER) == 0) {
+		syslog(LOG_INFO, "Bluetooth Battery Provider Manager removed");
+		cras_bt_battery_provider_reset();
 	}
 }
 
diff --git a/cras/src/server/cras_dbus_control.c b/cras/src/server/cras_dbus_control.c
index 3479c3c..b66e127 100644
--- a/cras/src/server/cras_dbus_control.c
+++ b/cras/src/server/cras_dbus_control.c
@@ -125,6 +125,9 @@
 	"    <method name=\"SetWbsEnabled\">\n"                                 \
 	"      <arg name=\"enabled\" type=\"b\" direction=\"in\"/>\n"           \
 	"    </method>\n"                                                       \
+	"    <method name=\"SetNoiseCancellationEnabled\">\n"                   \
+	"      <arg name=\"enabled\" type=\"b\" direction=\"in\"/>\n"           \
+	"    </method>\n"                                                       \
 	"    <method name=\"SetPlayerPlaybackStatus\">\n"                       \
 	"      <arg name=\"status\" type=\"s\" direction=\"in\"/>\n"            \
 	"    </method>\n"                                                       \
@@ -137,8 +140,6 @@
 	"    <method name=\"SetPlayerMetadata\">\n"                             \
 	"      <arg name=\"metadata\" type=\"a{sv}\" direction=\"in\"/>\n"      \
 	"    </method>\n"                                                       \
-	"    <method name=\"ResendBluetoothBattery\">\n"                        \
-	"    </method>\n"                                                       \
 	"  </interface>\n"                                                      \
 	"  <interface name=\"" DBUS_INTERFACE_INTROSPECTABLE "\">\n"            \
 	"    <method name=\"Introspect\">\n"                                    \
@@ -960,6 +961,24 @@
 	return DBUS_HANDLER_RESULT_HANDLED;
 }
 
+static DBusHandlerResult
+handle_set_noise_cancellation_enabled(DBusConnection *conn,
+				      DBusMessage *message, void *arg)
+{
+	int rc;
+	dbus_bool_t enabled;
+
+	rc = get_single_arg(message, DBUS_TYPE_BOOLEAN, &enabled);
+	if (rc)
+		return rc;
+
+	cras_system_set_noise_cancellation_enabled(enabled);
+
+	send_empty_reply(conn, message);
+
+	return DBUS_HANDLER_RESULT_HANDLED;
+}
+
 static DBusHandlerResult handle_set_player_playback_status(DBusConnection *conn,
 							   DBusMessage *message,
 							   void *arg)
@@ -1060,17 +1079,6 @@
 	return DBUS_HANDLER_RESULT_HANDLED;
 }
 
-static DBusHandlerResult handle_resend_bluetooth_battery(DBusConnection *conn,
-							 DBusMessage *message,
-							 void *arg)
-{
-	cras_hfp_ag_resend_device_battery_level();
-
-	send_empty_reply(conn, message);
-
-	return DBUS_HANDLER_RESULT_HANDLED;
-}
-
 /* Handle incoming messages. */
 static DBusHandlerResult handle_control_message(DBusConnection *conn,
 						DBusMessage *message, void *arg)
@@ -1199,6 +1207,10 @@
 					       "SetWbsEnabled")) {
 		return handle_set_wbs_enabled(conn, message, arg);
 	} else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE,
+					       "SetNoiseCancellationEnabled")) {
+		return handle_set_noise_cancellation_enabled(conn, message,
+							     arg);
+	} else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE,
 					       "SetPlayerPlaybackStatus")) {
 		return handle_set_player_playback_status(conn, message, arg);
 	} else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE,
@@ -1210,9 +1222,6 @@
 	} else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE,
 					       "SetPlayerMetadata")) {
 		return handle_set_player_metadata(conn, message, arg);
-	} else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE,
-					       "ResendBluetoothBattery")) {
-		return handle_resend_bluetooth_battery(conn, message, arg);
 	}
 
 	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
@@ -1324,8 +1333,8 @@
 	dbus_uint32_t serial = 0;
 
 	msg = create_dbus_message((dir == CRAS_STREAM_OUTPUT) ?
-					  "ActiveOutputNodeChanged" :
-					  "ActiveInputNodeChanged");
+						"ActiveOutputNodeChanged" :
+						"ActiveInputNodeChanged");
 	if (!msg)
 		return;
 	dbus_message_append_args(msg, DBUS_TYPE_UINT64, &node_id,
@@ -1463,25 +1472,6 @@
 	dbus_message_unref(msg);
 }
 
-static void signal_bt_battery_changed(void *context, const char *address,
-				      uint32_t level)
-{
-	struct cras_dbus_control *control = (struct cras_dbus_control *)context;
-	dbus_uint32_t serial = 0;
-	DBusMessage *msg;
-
-	msg = create_dbus_message("BluetoothBatteryChanged");
-	if (!msg)
-		return;
-
-	dbus_message_append_args(msg, DBUS_TYPE_STRING, &address,
-				 DBUS_TYPE_INVALID);
-	dbus_message_append_args(msg, DBUS_TYPE_UINT32, &level,
-				 DBUS_TYPE_INVALID);
-	dbus_connection_send(control->conn, msg, &serial);
-	dbus_message_unref(msg);
-}
-
 /* Exported Interface */
 
 void cras_dbus_control_start(DBusConnection *conn)
@@ -1523,7 +1513,6 @@
 	observer_ops.hotword_triggered = signal_hotword_triggered;
 	observer_ops.non_empty_audio_state_changed =
 		signal_non_empty_audio_state_changed;
-	observer_ops.bt_battery_changed = signal_bt_battery_changed;
 
 	dbus_control.observer = cras_observer_add(&observer_ops, &dbus_control);
 }
diff --git a/cras/src/server/cras_device_monitor.c b/cras/src/server/cras_device_monitor.c
index 7dd0f5d..e9730a0 100644
--- a/cras/src/server/cras_device_monitor.c
+++ b/cras/src/server/cras_device_monitor.c
@@ -13,6 +13,7 @@
 enum CRAS_DEVICE_MONITOR_MSG_TYPE {
 	RESET_DEVICE,
 	SET_MUTE_STATE,
+	ERROR_CLOSE,
 };
 
 struct cras_device_monitor_message {
@@ -62,6 +63,21 @@
 	return 0;
 }
 
+int cras_device_monitor_error_close(unsigned int dev_idx)
+{
+	struct cras_device_monitor_message msg;
+	int err;
+
+	init_device_msg(&msg, ERROR_CLOSE, dev_idx);
+	err = cras_main_message_send((struct cras_main_message *)&msg);
+	if (err < 0) {
+		syslog(LOG_ERR, "Failed to send device message %d",
+		       ERROR_CLOSE);
+		return err;
+	}
+	return 0;
+}
+
 /* When device is in a bad state, e.g. severe underrun,
  * it might break how audio thread works and cause busy wake up loop.
  * Resetting the device can bring device back to normal state.
@@ -84,6 +100,10 @@
 	case SET_MUTE_STATE:
 		cras_iodev_list_set_dev_mute(device_msg->dev_idx);
 		break;
+	case ERROR_CLOSE:
+		syslog(LOG_ERR, "Close erroneous device in main thread");
+		cras_iodev_list_suspend_dev(device_msg->dev_idx);
+		break;
 	default:
 		syslog(LOG_ERR, "Unknown device message type %u",
 		       device_msg->message_type);
diff --git a/cras/src/server/cras_device_monitor.h b/cras/src/server/cras_device_monitor.h
index ac31adb..eca2372 100644
--- a/cras/src/server/cras_device_monitor.h
+++ b/cras/src/server/cras_device_monitor.h
@@ -15,4 +15,8 @@
 /* Initializes device monitor and sets main thread callback. */
 int cras_device_monitor_init();
 
+/* Asks main thread to close device because error has occured in audio
+ * thread. */
+int cras_device_monitor_error_close(unsigned int dev_idx);
+
 #endif /* CRAS_DEVICE_MONITOR_H_ */
diff --git a/cras/src/server/cras_fmt_conv.c b/cras/src/server/cras_fmt_conv.c
index 509db1e..842529b 100644
--- a/cras/src/server/cras_fmt_conv.c
+++ b/cras/src/server/cras_fmt_conv.c
@@ -216,6 +216,19 @@
 	return s16_stereo_to_51(left, right, center, in, in_frames, out);
 }
 
+static size_t quad_to_51(struct cras_fmt_conv *conv, const uint8_t *in,
+			 size_t in_frames, uint8_t *out)
+{
+	size_t fl, fr, rl, rr;
+
+	fl = conv->out_fmt.channel_layout[CRAS_CH_FL];
+	fr = conv->out_fmt.channel_layout[CRAS_CH_FR];
+	rl = conv->out_fmt.channel_layout[CRAS_CH_RL];
+	rr = conv->out_fmt.channel_layout[CRAS_CH_RR];
+
+	return s16_quad_to_51(fl, fr, rl, rr, in, in_frames, out);
+}
+
 static size_t _51_to_stereo(struct cras_fmt_conv *conv, const uint8_t *in,
 			    size_t in_frames, uint8_t *out)
 {
@@ -398,6 +411,8 @@
 			conv->channel_converter = quad_to_stereo;
 		} else if (in->num_channels == 2 && out->num_channels == 6) {
 			conv->channel_converter = stereo_to_51;
+		} else if (in->num_channels == 4 && out->num_channels == 6) {
+			conv->channel_converter = quad_to_51;
 		} else if (in->num_channels == 6 &&
 			   (out->num_channels == 2 || out->num_channels == 4)) {
 			int in_channel_layout_set = 0;
diff --git a/cras/src/server/cras_fmt_conv_ops.c b/cras/src/server/cras_fmt_conv_ops.c
index a306d21..adc5521 100644
--- a/cras/src/server/cras_fmt_conv_ops.c
+++ b/cras/src/server/cras_fmt_conv_ops.c
@@ -223,6 +223,44 @@
 }
 
 /*
+ * Channel converter: quad to 5.1 surround.
+ *
+ * Fit the front left/right of input to the front left/right of output
+ * and rear left/right of input to the rear left/right of output
+ * respectively and fill others with zero.
+ */
+size_t s16_quad_to_51(size_t font_left, size_t front_right, size_t rear_left,
+		      size_t rear_right, const uint8_t *_in, size_t in_frames,
+		      uint8_t *_out)
+{
+	size_t i;
+	const int16_t *in = (const int16_t *)_in;
+	int16_t *out = (int16_t *)_out;
+
+	memset(out, 0, sizeof(*out) * 6 * in_frames);
+
+	if (font_left != -1 && front_right != -1 && rear_left != -1 &&
+	    rear_right != -1)
+		for (i = 0; i < in_frames; i++) {
+			out[6 * i + font_left] = in[4 * i];
+			out[6 * i + front_right] = in[4 * i + 1];
+			out[6 * i + rear_left] = in[4 * i + 2];
+			out[6 * i + rear_right] = in[4 * i + 3];
+		}
+	else
+		/* Use default 5.1 channel mapping for the conversion.
+		 */
+		for (i = 0; i < in_frames; i++) {
+			out[6 * i] = in[4 * i];
+			out[6 * i + 1] = in[4 * i + 1];
+			out[6 * i + 4] = in[4 * i + 2];
+			out[6 * i + 5] = in[4 * i + 3];
+		}
+
+	return in_frames;
+}
+
+/*
  * Channel converter: 5.1 surround to stereo.
  *
  * The out buffer can have room for just stereo samples. This convert function
diff --git a/cras/src/server/cras_fmt_conv_ops.h b/cras/src/server/cras_fmt_conv_ops.h
index a1a5748..0af7564 100644
--- a/cras/src/server/cras_fmt_conv_ops.h
+++ b/cras/src/server/cras_fmt_conv_ops.h
@@ -46,6 +46,13 @@
 			const uint8_t *in, size_t in_frames, uint8_t *out);
 
 /*
+ * Channel converter: quad to 5.1 surround.
+ */
+size_t s16_quad_to_51(size_t font_left, size_t front_right, size_t rear_left,
+		      size_t rear_right, const uint8_t *in, size_t in_frames,
+		      uint8_t *out);
+
+/*
  * Channel converter: 5.1 surround to stereo.
  */
 size_t s16_51_to_stereo(const uint8_t *in, size_t in_frames, uint8_t *out);
diff --git a/cras/src/server/cras_hfp_ag_profile.c b/cras/src/server/cras_hfp_ag_profile.c
index 9d59d40..b5fcecc 100644
--- a/cras/src/server/cras_hfp_ag_profile.c
+++ b/cras/src/server/cras_hfp_ag_profile.c
@@ -20,7 +20,6 @@
 #include "cras_server_metrics.h"
 #include "cras_system_state.h"
 #include "cras_iodev_list.h"
-#include "cras_observer.h"
 #include "utlist.h"
 #include "packet_status_logger.h"
 
@@ -461,19 +460,6 @@
 	return &wbs_logger;
 }
 
-void cras_hfp_ag_resend_device_battery_level()
-{
-	struct audio_gateway *ag;
-	int level;
-	DL_FOREACH (connected_ags, ag) {
-		level = hfp_slc_get_hf_battery_level(ag->slc_handle);
-		if (level >= 0 && level <= 100)
-			cras_observer_notify_bt_battery_changed(
-				cras_bt_device_address(ag->device),
-				(uint32_t)(level));
-	}
-}
-
 int cras_hsp_ag_profile_create(DBusConnection *conn)
 {
 	return cras_bt_add_profile(conn, &cras_hsp_ag_profile);
diff --git a/cras/src/server/cras_hfp_ag_profile.h b/cras/src/server/cras_hfp_ag_profile.h
index 50d27e0..3de5618 100644
--- a/cras/src/server/cras_hfp_ag_profile.h
+++ b/cras/src/server/cras_hfp_ag_profile.h
@@ -56,8 +56,4 @@
 /* Gets the logger for WBS packet status. */
 struct packet_status_logger *cras_hfp_ag_get_wbs_logger();
 
-/* Iterate all possible AGs (theoratically only one) and signal its battery
- * level */
-void cras_hfp_ag_resend_device_battery_level();
-
 #endif /* CRAS_HFP_AG_PROFILE_H_ */
diff --git a/cras/src/server/cras_hfp_alsa_iodev.c b/cras/src/server/cras_hfp_alsa_iodev.c
index b80a88c..c1b60b3 100644
--- a/cras/src/server/cras_hfp_alsa_iodev.c
+++ b/cras/src/server/cras_hfp_alsa_iodev.c
@@ -12,7 +12,6 @@
 #include "cras_iodev.h"
 #include "cras_system_state.h"
 #include "cras_util.h"
-#include "sfh.h"
 #include "utlist.h"
 #include "cras_bt_device.h"
 
@@ -108,6 +107,7 @@
 		return rc;
 	}
 
+	hfp_set_call_status(hfp_alsa_io->slc, 1);
 	iodev->buffer_size = aio->buffer_size;
 
 	return 0;
@@ -118,6 +118,7 @@
 	struct hfp_alsa_io *hfp_alsa_io = (struct hfp_alsa_io *)iodev;
 	struct cras_iodev *aio = hfp_alsa_io->aio;
 
+	hfp_set_call_status(hfp_alsa_io->slc, 0);
 	cras_bt_device_put_sco(hfp_alsa_io->device);
 	cras_iodev_free_format(iodev);
 	return aio->close_dev(aio);
@@ -259,10 +260,7 @@
 		name = cras_bt_device_object_path(device);
 	snprintf(iodev->info.name, sizeof(iodev->info.name), "%s", name);
 	iodev->info.name[ARRAY_SIZE(iodev->info.name) - 1] = 0;
-	iodev->info.stable_id =
-		SuperFastHash(cras_bt_device_object_path(device),
-			      strlen(cras_bt_device_object_path(device)),
-			      strlen(cras_bt_device_object_path(device)));
+	iodev->info.stable_id = cras_bt_device_get_stable_id(device);
 
 	iodev->open_dev = hfp_alsa_open_dev;
 	iodev->update_supported_formats = hfp_alsa_update_supported_formats;
diff --git a/cras/src/server/cras_hfp_iodev.c b/cras/src/server/cras_hfp_iodev.c
index 7cce373..6a4ced0 100644
--- a/cras/src/server/cras_hfp_iodev.c
+++ b/cras/src/server/cras_hfp_iodev.c
@@ -17,7 +17,6 @@
 #include "cras_iodev.h"
 #include "cras_system_state.h"
 #include "cras_util.h"
-#include "sfh.h"
 #include "utlist.h"
 
 /* Implementation of bluetooth hands-free profile iodev.
@@ -167,6 +166,7 @@
 	hfpio->filled_zeros = 0;
 add_dev:
 	hfp_info_add_iodev(hfpio->info, iodev->direction, iodev->format);
+	hfp_set_call_status(hfpio->slc, 1);
 
 	iodev->buffer_size = hfp_buf_size(hfpio->info, iodev->direction);
 
@@ -181,8 +181,10 @@
 	struct hfp_io *hfpio = (struct hfp_io *)iodev;
 
 	hfp_info_rm_iodev(hfpio->info, iodev->direction);
-	if (hfp_info_running(hfpio->info) && !hfp_info_has_iodev(hfpio->info))
+	if (hfp_info_running(hfpio->info) && !hfp_info_has_iodev(hfpio->info)) {
 		hfp_info_stop(hfpio->info);
+		hfp_set_call_status(hfpio->slc, 0);
+	}
 
 	cras_iodev_free_format(iodev);
 	cras_iodev_free_audio_area(iodev);
@@ -306,10 +308,7 @@
 
 	snprintf(iodev->info.name, sizeof(iodev->info.name), "%s", name);
 	iodev->info.name[ARRAY_SIZE(iodev->info.name) - 1] = 0;
-	iodev->info.stable_id =
-		SuperFastHash(cras_bt_device_object_path(device),
-			      strlen(cras_bt_device_object_path(device)),
-			      strlen(cras_bt_device_object_path(device)));
+	iodev->info.stable_id = cras_bt_device_get_stable_id(device);
 
 	iodev->configure_dev = configure_dev;
 	iodev->frames_queued = frames_queued;
diff --git a/cras/src/server/cras_hfp_slc.c b/cras/src/server/cras_hfp_slc.c
index e4f0127..28f73ed 100644
--- a/cras/src/server/cras_hfp_slc.c
+++ b/cras/src/server/cras_hfp_slc.c
@@ -441,12 +441,10 @@
 		id_str = strtok(NULL, ",");
 	}
 
-	for (id = HFP_MAX_CODECS - 1; id > 0; id--) {
-		if (handle->hf_codec_supported[id]) {
-			handle->preferred_codec = id;
-			break;
-		}
-	}
+	if (hfp_slc_get_wideband_speech_supported(handle))
+		handle->preferred_codec = HFP_CODEC_ID_MSBC;
+	else
+		handle->preferred_codec = HFP_CODEC_ID_CVSD;
 
 	free(tokens);
 	return hfp_send(handle, AT_CMD("OK"));
@@ -609,6 +607,26 @@
 	return hfp_send(handle, AT_CMD("OK"));
 }
 
+/* The AT+CHLD command is used to control call hold, release, and multiparty
+ * states.
+ */
+static int call_hold(struct hfp_slc_handle *handle, const char *buf)
+{
+	int rc;
+
+	// Chrome OS doesn't yet support CHLD features but we need to reply
+	// the query with an empty feature list rather than "ERROR" to increase
+	// interoperability with certain devices (b/172413440).
+	if (strlen(buf) > 8 && buf[7] == '=' && buf[8] == '?') {
+		rc = hfp_send(handle, AT_CMD("+CHLD:"));
+		if (rc)
+			return rc;
+		return hfp_send(handle, AT_CMD("OK"));
+	}
+
+	return hfp_send(handle, AT_CMD("ERROR"));
+}
+
 /* AT+CIND command retrieves the supported indicator and its corresponding
  * range and order index or read current status of indicators. Mandatory
  * support per spec 4.2.
@@ -938,6 +956,70 @@
 	return cras_telephony_event_terminate_call();
 }
 
+/* AT+XEVENT is defined by Android to support vendor specific features.
+ * Currently, the only known supported case for CrOS is the battery event sent
+ * by some Plantronics headsets.
+ */
+static int vendor_specific_features(struct hfp_slc_handle *handle,
+				    const char *cmd)
+{
+	char *tokens, *event, *level_str, *num_of_level_str;
+	int level, num_of_level;
+
+	tokens = strdup(cmd);
+	strtok(tokens, "=");
+	event = strtok(NULL, ",");
+	if (!event)
+		goto error_out;
+
+	/* AT+XEVENT=BATTERY,Level,NumberOfLevel,MinutesOfTalkTime,IsCharging
+	 * Level: The charge level with a zero-based integer.
+	 * NumberOfLevel: How many charging levels there are.
+	 * MinuteOfTalkTime: The estimated number of talk minutes remaining.
+	 * IsCharging: A 0 or 1 value.
+	 *
+	 * We only support the battery level and thus only care about the first
+	 * 3 arguments.
+	 */
+	if (!strncmp(event, "BATTERY", 7)) {
+		level_str = strtok(NULL, ",");
+		num_of_level_str = strtok(NULL, ",");
+		if (!level_str || !num_of_level_str)
+			goto error_out;
+
+		level = atoi(level_str);
+		num_of_level = atoi(num_of_level_str);
+		if (level < 0 || num_of_level <= 1 || level >= num_of_level)
+			goto error_out;
+
+		level = (int64_t)level * 100 / (num_of_level - 1);
+		if (handle->hf_battery != level) {
+			handle->hf_supports_battery_indicator |=
+				CRAS_HFP_BATTERY_INDICATOR_PLANTRONICS;
+			cras_server_metrics_hfp_battery_report(
+				CRAS_HFP_BATTERY_INDICATOR_PLANTRONICS);
+			handle->hf_battery = level;
+			cras_observer_notify_bt_battery_changed(
+				cras_bt_device_address(handle->device),
+				(uint32_t)(level));
+		}
+	}
+
+	free(tokens);
+	/* For Plantronic headsets, it is required to reply "OK" for the first
+	 * AT+XEVENT=USER-AGENT... command to tell the headset our support of
+	 * the xevent protocol. Otherwise, all following events including
+	 * BATTERY won't be sent.
+	 */
+	return hfp_send(handle, AT_CMD("OK"));
+
+error_out:
+	syslog(LOG_ERR, "%s: malformed vendor specific command: '%s'", __func__,
+	       cmd);
+	free(tokens);
+	return hfp_send(handle, AT_CMD("ERROR"));
+}
+
 /* AT commands to support in order to conform HFP specification.
  *
  * An initialized service level connection is the pre-condition for all
@@ -999,6 +1081,8 @@
 	{ "AT+VG", signal_gain_setting },
 	{ "AT+VTS", dtmf_tone },
 	{ "AT+XAPL", apple_supported_features },
+	{ "AT+XEVENT", vendor_specific_features },
+	{ "AT+CHLD", call_hold },
 	{ 0 }
 };
 
@@ -1314,8 +1398,3 @@
 {
 	return handle->hf_supports_battery_indicator;
 }
-
-int hfp_slc_get_hf_battery_level(struct hfp_slc_handle *handle)
-{
-	return handle->hf_battery;
-}
diff --git a/cras/src/server/cras_hfp_slc.h b/cras/src/server/cras_hfp_slc.h
index c3cdc11..99335ea 100644
--- a/cras/src/server/cras_hfp_slc.h
+++ b/cras/src/server/cras_hfp_slc.h
@@ -62,6 +62,7 @@
 #define CRAS_HFP_BATTERY_INDICATOR_NONE 0x0
 #define CRAS_HFP_BATTERY_INDICATOR_HFP 0x1
 #define CRAS_HFP_BATTERY_INDICATOR_APPLE 0x2
+#define CRAS_HFP_BATTERY_INDICATOR_PLANTRONICS 0x4
 
 /* Callback to call when service level connection initialized. */
 typedef int (*hfp_slc_init_cb)(struct hfp_slc_handle *handle);
@@ -145,10 +146,6 @@
  * Apple, HFP, none, or both. */
 int hfp_slc_get_hf_supports_battery_indicator(struct hfp_slc_handle *handle);
 
-/* Gets the battery level for the HF. The data ranges 0 ~ 100. Use -1 for no
- * battery level reported.*/
-int hfp_slc_get_hf_battery_level(struct hfp_slc_handle *handle);
-
 /* Init the codec negotiation process if needed. */
 int hfp_slc_codec_connection_setup(struct hfp_slc_handle *handle);
 
diff --git a/cras/src/server/cras_iodev.c b/cras/src/server/cras_iodev.c
index fd1ce80..651cef7 100644
--- a/cras/src/server/cras_iodev.c
+++ b/cras/src/server/cras_iodev.c
@@ -732,6 +732,17 @@
 	return false;
 }
 
+bool cras_iodev_is_on_internal_card(const struct cras_ionode *node)
+{
+	if (node->type == CRAS_NODE_TYPE_INTERNAL_SPEAKER)
+		return true;
+	if (node->type == CRAS_NODE_TYPE_HEADPHONE)
+		return true;
+	if (node->type == CRAS_NODE_TYPE_MIC)
+		return true;
+	return false;
+}
+
 float cras_iodev_get_software_volume_scaler(struct cras_iodev *iodev)
 {
 	unsigned int volume;
@@ -1010,6 +1021,7 @@
 
 	if (iodev->active_node) {
 		cras_server_metrics_device_runtime(iodev);
+		cras_server_metrics_device_gain(iodev);
 		cras_server_metrics_device_volume(iodev);
 	}
 
@@ -1695,3 +1707,13 @@
 
 	return rc;
 }
+
+bool cras_iodev_support_noise_cancellation(const struct cras_iodev *iodev)
+{
+	if (iodev->direction != CRAS_STREAM_INPUT)
+		return false;
+
+	if (iodev->support_noise_cancellation)
+		return !!iodev->support_noise_cancellation(iodev);
+	return false;
+}
diff --git a/cras/src/server/cras_iodev.h b/cras/src/server/cras_iodev.h
index db16a0f..18a0962 100644
--- a/cras/src/server/cras_iodev.h
+++ b/cras/src/server/cras_iodev.h
@@ -184,6 +184,8 @@
  *        audio thread can sleep before serving this playback dev the next time.
  *        Not implementing this ops means fall back to default behavior in
  *        cras_iodev_default_frames_to_play_in_sleep().
+ * support_noise_cancellation - (Optional) Checks if the device supports noise
+ *                              cancellation.
  * format - The audio format being rendered or captured to hardware.
  * rate_est - Rate estimator to estimate the actual device rate.
  * area - Information about how the samples are stored.
@@ -274,6 +276,7 @@
 	unsigned int (*frames_to_play_in_sleep)(struct cras_iodev *iodev,
 						unsigned int *hw_level,
 						struct timespec *hw_tstamp);
+	int (*support_noise_cancellation)(const struct cras_iodev *iodev);
 	struct cras_audio_format *format;
 	struct rate_estimator *rate_est;
 	struct cras_audio_area *area;
@@ -459,6 +462,9 @@
 /* Checks if the node is the typical playback or capture option for AEC usage. */
 bool cras_iodev_is_aec_use_case(const struct cras_ionode *node);
 
+/* Checks if the node is a playback or capture node on internal card. */
+bool cras_iodev_is_on_internal_card(const struct cras_ionode *node);
+
 /* Adjust the system volume based on the volume of the given node. */
 static inline unsigned int
 cras_iodev_adjust_node_volume(const struct cras_ionode *node,
@@ -833,4 +839,12 @@
 int cras_iodev_drop_frames_by_time(struct cras_iodev *iodev,
 				   struct timespec ts);
 
+/* Checks if an input device supports noise cancellation.
+ * Args:
+ *    iodev - The device.
+ * Returns:
+ *    True if device supports noise cancellation. False otherwise.
+ */
+bool cras_iodev_support_noise_cancellation(const struct cras_iodev *iodev);
+
 #endif /* CRAS_IODEV_H_ */
diff --git a/cras/src/server/cras_iodev_list.c b/cras/src/server/cras_iodev_list.c
index ada2971..b818c97 100644
--- a/cras/src/server/cras_iodev_list.c
+++ b/cras/src/server/cras_iodev_list.c
@@ -91,6 +91,9 @@
 static const unsigned int INIT_DEV_DELAY_MS = 1000;
 /* Flag to indicate that hotword streams are suspended. */
 static int hotword_suspended = 0;
+/* Flag to indicate that suspended hotword streams should be auto-resumed at
+ * system resume. */
+static int hotword_auto_resume = 0;
 
 static void idle_dev_check(struct cras_timer *timer, void *data);
 
@@ -388,8 +391,9 @@
 	MAINLOG(main_log, MAIN_THREAD_DEV_CLOSE, dev->info.idx, 0, 0);
 	remove_all_streams_from_dev(dev);
 	dev->idle_timeout.tv_sec = 0;
-	cras_iodev_close(dev);
+	/* close echo ref first to avoid underrun in hardware */
 	possibly_disable_echo_reference(dev);
+	cras_iodev_close(dev);
 }
 
 static void idle_dev_check(struct cras_timer *timer, void *data)
@@ -490,6 +494,11 @@
 		if (rstream->is_pinned) {
 			struct cras_iodev *dev;
 
+			/* Skip closing hotword stream in the first pass.
+			 * Closing an input device may resume hotword stream
+			 * with its post_close_iodev_hook so we should deal
+			 * with hotword stream in the second pass.
+			 */
 			if ((rstream->flags & HOTWORD_STREAM) == HOTWORD_STREAM)
 				continue;
 
@@ -513,6 +522,14 @@
 	DL_FOREACH (enabled_devs[CRAS_STREAM_INPUT], edev) {
 		close_dev(edev->dev);
 	}
+
+	/* Doing this check after all the other enabled iodevs are closed to
+	 * ensure preempted hotword streams obey the pause_at_suspend flag.
+	 */
+	if (cras_system_get_hotword_pause_at_suspend()) {
+		cras_iodev_list_suspend_hotword_streams();
+		hotword_auto_resume = 1;
+	}
 }
 
 static int stream_added_cb(struct cras_rstream *rstream);
@@ -527,6 +544,14 @@
 
 	MAINLOG(main_log, MAIN_THREAD_RESUME_DEVS, 0, 0, 0);
 
+	/* Auto-resume based on the local flag in case the system state flag has
+	 * changed.
+	 */
+	if (hotword_auto_resume) {
+		cras_iodev_list_resume_hotword_stream();
+		hotword_auto_resume = 0;
+	}
+
 	/*
 	 * To remove the short popped noise caused by applications that can not
 	 * stop playback "right away" after resume, we mute all output devices
@@ -1856,6 +1881,24 @@
 	}
 }
 
+void cras_iodev_list_reset_for_noise_cancellation()
+{
+	struct cras_iodev *dev;
+	bool enabled = cras_system_get_noise_cancellation_enabled();
+
+	DL_FOREACH (devs[CRAS_STREAM_INPUT].iodevs, dev) {
+		if (!cras_iodev_is_open(dev) ||
+		    !cras_iodev_support_noise_cancellation(dev))
+			continue;
+		syslog(LOG_INFO, "Re-open %s for %s noise cancellation",
+		       dev->info.name, enabled ? "enabling" : "disabling");
+		possibly_enable_fallback(CRAS_STREAM_INPUT, false);
+		cras_iodev_list_suspend_dev(dev->info.idx);
+		cras_iodev_list_resume_dev(dev->info.idx);
+		possibly_disable_fallback(CRAS_STREAM_INPUT);
+	}
+}
+
 void cras_iodev_list_reset()
 {
 	struct enabled_dev *edev;
diff --git a/cras/src/server/cras_iodev_list.h b/cras/src/server/cras_iodev_list.h
index 61c3a18..d6e9ba5 100644
--- a/cras/src/server/cras_iodev_list.h
+++ b/cras/src/server/cras_iodev_list.h
@@ -274,6 +274,11 @@
 /* Resumes all hotwording streams. */
 int cras_iodev_list_resume_hotword_stream();
 
+/* Sets the state of noise cancellation for input devices which supports noise
+ * cancellation by suspend, enable/disable, then resume.
+ */
+void cras_iodev_list_reset_for_noise_cancellation();
+
 /* For unit test only. */
 void cras_iodev_list_reset();
 
diff --git a/cras/src/server/cras_rclient_util.c b/cras/src/server/cras_rclient_util.c
index def645e..0af9886 100644
--- a/cras/src/server/cras_rclient_util.c
+++ b/cras/src/server/cras_rclient_util.c
@@ -170,6 +170,8 @@
 	if (rc)
 		goto cleanup_config;
 
+	detect_rtc_stream_pair(cras_iodev_list_get_stream_list(), stream);
+
 	/* Tell client about the stream setup. */
 	syslog(LOG_DEBUG, "Send connected for stream %x\n", msg->stream_id);
 
diff --git a/cras/src/server/cras_rstream.c b/cras/src/server/cras_rstream.c
index 94adcea..3c0a0ce 100644
--- a/cras/src/server/cras_rstream.c
+++ b/cras/src/server/cras_rstream.c
@@ -167,6 +167,11 @@
 		syslog(LOG_ERR, "rstream: Invalid stream type.\n");
 		return -EINVAL;
 	}
+	if (config->client_type < CRAS_CLIENT_TYPE_UNKNOWN ||
+	    config->client_type >= CRAS_NUM_CLIENT_TYPE) {
+		syslog(LOG_ERR, "rstream: Invalid client type.\n");
+		return -EINVAL;
+	}
 	if ((config->client_shm_size > 0 && config->client_shm_fd < 0) ||
 	    (config->client_shm_size == 0 && config->client_shm_fd >= 0)) {
 		syslog(LOG_ERR, "rstream: invalid client-provided shm info\n");
@@ -287,8 +292,8 @@
 	stream->cb_threshold = config->cb_threshold;
 	stream->client = config->client;
 	stream->shm = NULL;
-	stream->master_dev.dev_id = NO_DEVICE;
-	stream->master_dev.dev_ptr = NULL;
+	stream->main_dev.dev_id = NO_DEVICE;
+	stream->main_dev.dev_ptr = NULL;
 	stream->num_missed_cb = 0;
 	stream->is_pinned = (config->dev_idx != NO_DEVICE);
 	stream->pinned_dev_idx = config->dev_idx;
@@ -426,12 +431,12 @@
 	if (buffer_share_add_id(rstream->buf_state, dev_id, dev_ptr) == 0)
 		rstream->num_attached_devs++;
 
-	/* TODO(hychao): Handle master device assignment for complicated
+	/* TODO(hychao): Handle main device assignment for complicated
 	 * routing case.
 	 */
-	if (rstream->master_dev.dev_id == NO_DEVICE) {
-		rstream->master_dev.dev_id = dev_id;
-		rstream->master_dev.dev_ptr = dev_ptr;
+	if (rstream->main_dev.dev_id == NO_DEVICE) {
+		rstream->main_dev.dev_id = dev_id;
+		rstream->main_dev.dev_ptr = dev_ptr;
 	}
 }
 
@@ -440,18 +445,18 @@
 	if (buffer_share_rm_id(rstream->buf_state, dev_id) == 0)
 		rstream->num_attached_devs--;
 
-	if (rstream->master_dev.dev_id == dev_id) {
+	if (rstream->main_dev.dev_id == dev_id) {
 		int i;
 		struct id_offset *o;
 
-		/* Choose the first device id as master. */
-		rstream->master_dev.dev_id = NO_DEVICE;
-		rstream->master_dev.dev_ptr = NULL;
+		/* Choose the first device id as a main device. */
+		rstream->main_dev.dev_id = NO_DEVICE;
+		rstream->main_dev.dev_ptr = NULL;
 		for (i = 0; i < rstream->buf_state->id_sz; i++) {
 			o = &rstream->buf_state->wr_idx[i];
 			if (o->used) {
-				rstream->master_dev.dev_id = o->id;
-				rstream->master_dev.dev_ptr = o->data;
+				rstream->main_dev.dev_id = o->id;
+				rstream->main_dev.dev_ptr = o->data;
 				break;
 			}
 		}
diff --git a/cras/src/server/cras_rstream.h b/cras/src/server/cras_rstream.h
index 3bf7df0..d57c13b 100644
--- a/cras/src/server/cras_rstream.h
+++ b/cras/src/server/cras_rstream.h
@@ -20,12 +20,12 @@
 struct cras_rclient;
 struct dev_mix;
 
-/* Holds informations about the master active device.
+/* Holds informations about the main active device.
  * Members:
- *    dev_id - id of the master device.
- *    dev_ptr - pointer to the master device.
+ *    dev_id - id of the main device.
+ *    dev_ptr - pointer to the main device.
  */
-struct master_dev_info {
+struct main_dev_info {
 	int dev_id;
 	void *dev_ptr;
 };
@@ -42,7 +42,7 @@
  *    fd - Socket for requesting and sending audio buffer events.
  *    buffer_frames - Buffer size in frames.
  *    cb_threshold - Callback client when this much is left.
- *    master_dev_info - The info of the master device this stream attaches to.
+ *    main_dev_info - The info of the main device this stream attaches to.
  *    is_draining - The stream is draining and waiting to be removed.
  *    client - The client who uses this stream.
  *    shm - shared memory
@@ -74,7 +74,7 @@
 	size_t buffer_frames;
 	size_t cb_threshold;
 	int is_draining;
-	struct master_dev_info master_dev;
+	struct main_dev_info main_dev;
 	struct cras_rclient *client;
 	struct cras_audio_shm *shm;
 	struct cras_audio_area *audio_area;
diff --git a/cras/src/server/cras_server_metrics.c b/cras/src/server/cras_server_metrics.c
index ef4011b..7e48710 100644
--- a/cras/src/server/cras_server_metrics.c
+++ b/cras/src/server/cras_server_metrics.c
@@ -25,7 +25,9 @@
 const char kBusyloopLength[] = "Cras.BusyloopLength";
 const char kDeviceTypeInput[] = "Cras.DeviceTypeInput";
 const char kDeviceTypeOutput[] = "Cras.DeviceTypeOutput";
+const char kDeviceGain[] = "Cras.DeviceGain";
 const char kDeviceVolume[] = "Cras.DeviceVolume";
+const char kFetchDelayMilliSeconds[] = "Cras.FetchDelayMilliSeconds";
 const char kHighestDeviceDelayInput[] = "Cras.HighestDeviceDelayInput";
 const char kHighestDeviceDelayOutput[] = "Cras.HighestDeviceDelayOutput";
 const char kHighestInputHardwareLevel[] = "Cras.HighestInputHardwareLevel";
@@ -47,12 +49,12 @@
 const char kMissedCallbackSecondTimeOutput[] =
 	"Cras.MissedCallbackSecondTimeOutput";
 const char kNoCodecsFoundMetric[] = "Cras.NoCodecsFoundAtBoot";
-const char kStreamTimeoutMilliSeconds[] = "Cras.StreamTimeoutMilliSeconds";
 const char kStreamCallbackThreshold[] = "Cras.StreamCallbackThreshold";
 const char kStreamClientTypeInput[] = "Cras.StreamClientTypeInput";
 const char kStreamClientTypeOutput[] = "Cras.StreamClientTypeOutput";
 const char kStreamFlags[] = "Cras.StreamFlags";
 const char kStreamEffects[] = "Cras.StreamEffects";
+const char kStreamRuntime[] = "Cras.StreamRuntime";
 const char kStreamSamplingFormat[] = "Cras.StreamSamplingFormat";
 const char kStreamSamplingRate[] = "Cras.StreamSamplingRate";
 const char kUnderrunsPerDevice[] = "Cras.UnderrunsPerDevice";
@@ -93,6 +95,7 @@
 	BT_WIDEBAND_SELECTED_CODEC,
 	BUSYLOOP,
 	BUSYLOOP_LENGTH,
+	DEVICE_GAIN,
 	DEVICE_RUNTIME,
 	DEVICE_VOLUME,
 	HIGHEST_DEVICE_DELAY_INPUT,
@@ -163,7 +166,8 @@
 };
 
 struct cras_server_metrics_stream_data {
-	enum CRAS_CLIENT_TYPE type;
+	enum CRAS_CLIENT_TYPE client_type;
+	enum CRAS_STREAM_TYPE stream_type;
 	enum CRAS_STREAM_DIRECTION direction;
 	struct timespec runtime;
 };
@@ -304,6 +308,31 @@
 		return "ServerStream";
 	case CRAS_CLIENT_TYPE_LACROS:
 		return "LaCrOS";
+	case CRAS_CLIENT_TYPE_PLUGIN:
+		return "PluginVM";
+	case CRAS_CLIENT_TYPE_ARCVM:
+		return "ARCVM";
+	default:
+		return "InvalidType";
+	}
+}
+
+static inline const char *
+metrics_stream_type_str(enum CRAS_STREAM_TYPE stream_type)
+{
+	switch (stream_type) {
+	case CRAS_STREAM_TYPE_DEFAULT:
+		return "Default";
+	case CRAS_STREAM_TYPE_MULTIMEDIA:
+		return "Multimedia";
+	case CRAS_STREAM_TYPE_VOICE_COMMUNICATION:
+		return "VoiceCommunication";
+	case CRAS_STREAM_TYPE_SPEECH_RECOGNITION:
+		return "SpeechRecognition";
+	case CRAS_STREAM_TYPE_PRO_AUDIO:
+		return "ProAudio";
+	case CRAS_STREAM_TYPE_ACCESSIBILITY:
+		return "Accessibility";
 	default:
 		return "InvalidType";
 	}
@@ -394,6 +423,69 @@
 	}
 }
 
+/*
+ * Logs metrics for each group it belongs to. The UMA does not merge subgroups
+ * automatically so we need to log them separately.
+ *
+ * For example, if we call this function with argument (3, 48000,
+ * Cras.StreamSamplingRate, Input, Chrome), it will send 48000 to below
+ * metrics:
+ * Cras.StreamSamplingRate.Input.Chrome
+ * Cras.StreamSamplingRate.Input
+ * Cras.StreamSamplingRate
+ */
+static void log_sparse_histogram_each_level(int num, int sample, ...)
+{
+	char metrics_name[METRICS_NAME_BUFFER_SIZE] = {};
+	va_list valist;
+	int i, len = 0;
+
+	va_start(valist, sample);
+
+	for (i = 0; i < num && len < METRICS_NAME_BUFFER_SIZE; i++) {
+		int metric_len =
+			snprintf(metrics_name + len,
+				 METRICS_NAME_BUFFER_SIZE - len, "%s%s",
+				 i ? "." : "", va_arg(valist, char *));
+		// Exit early on error or running out of bufferspace. Avoids
+		// logging partial or corrupted strings.
+		if (metric_len < 0 ||
+		    metric_len > METRICS_NAME_BUFFER_SIZE - len)
+			break;
+		len += metric_len;
+		cras_metrics_log_sparse_histogram(metrics_name, sample);
+	}
+
+	va_end(valist);
+}
+
+static void log_histogram_each_level(int num, int sample, int min, int max,
+				     int nbuckets, ...)
+{
+	char metrics_name[METRICS_NAME_BUFFER_SIZE] = {};
+	va_list valist;
+	int i, len = 0;
+
+	va_start(valist, nbuckets);
+
+	for (i = 0; i < num && len < METRICS_NAME_BUFFER_SIZE; i++) {
+		int metric_len =
+			snprintf(metrics_name + len,
+				 METRICS_NAME_BUFFER_SIZE - len, "%s%s",
+				 i ? "." : "", va_arg(valist, char *));
+		// Exit early on error or running out of bufferspace. Avoids
+		// logging partial or corrupted strings.
+		if (metric_len < 0 ||
+		    metric_len > METRICS_NAME_BUFFER_SIZE - len)
+			break;
+		len += metric_len;
+		cras_metrics_log_histogram(metrics_name, sample, min, max,
+					   nbuckets);
+	}
+
+	va_end(valist);
+}
+
 int cras_server_metrics_hfp_sco_connection_error(
 	enum CRAS_METRICS_BT_SCO_ERROR_TYPE type)
 {
@@ -536,6 +628,31 @@
 	return 0;
 }
 
+int cras_server_metrics_device_gain(struct cras_iodev *iodev)
+{
+	struct cras_server_metrics_message msg;
+	union cras_server_metrics_data data;
+	int err;
+
+	if (iodev->direction == CRAS_STREAM_OUTPUT)
+		return 0;
+
+	data.device_data.type = get_metrics_device_type(iodev);
+	data.device_data.value =
+		(unsigned)100 * iodev->active_node->ui_gain_scaler;
+
+	init_server_metrics_msg(&msg, DEVICE_GAIN, data);
+
+	err = cras_server_metrics_message_send(
+		(struct cras_main_message *)&msg);
+	if (err < 0) {
+		syslog(LOG_ERR, "Failed to send metrics message: DEVICE_GAIN");
+		return err;
+	}
+
+	return 0;
+}
+
 int cras_server_metrics_device_volume(struct cras_iodev *iodev)
 {
 	struct cras_server_metrics_message msg;
@@ -640,13 +757,31 @@
 	return 0;
 }
 
-int cras_server_metrics_longest_fetch_delay(unsigned delay_msec)
+/* Logs longest fetch delay of a stream. */
+int cras_server_metrics_longest_fetch_delay(const struct cras_rstream *stream)
 {
 	struct cras_server_metrics_message msg;
 	union cras_server_metrics_data data;
 	int err;
 
-	data.value = delay_msec;
+	data.stream_data.client_type = stream->client_type;
+	data.stream_data.stream_type = stream->stream_type;
+	data.stream_data.direction = stream->direction;
+
+	/*
+	 * There is no delay when the sleep_interval_ts larger than the
+	 * longest_fetch_interval.
+	 */
+	if (!timespec_after(&stream->longest_fetch_interval,
+			    &stream->sleep_interval_ts)) {
+		data.stream_data.runtime.tv_sec = 0;
+		data.stream_data.runtime.tv_nsec = 0;
+	} else {
+		subtract_timespecs(&stream->longest_fetch_interval,
+				   &stream->sleep_interval_ts,
+				   &data.stream_data.runtime);
+	}
+
 	init_server_metrics_msg(&msg, LONGEST_FETCH_DELAY, data);
 	err = cras_server_metrics_message_send(
 		(struct cras_main_message *)&msg);
@@ -869,7 +1004,8 @@
 	struct timespec now;
 	int err;
 
-	data.stream_data.type = stream->client_type;
+	data.stream_data.client_type = stream->client_type;
+	data.stream_data.stream_type = stream->stream_type;
 	data.stream_data.direction = stream->direction;
 	clock_gettime(CLOCK_MONOTONIC_RAW, &now);
 	subtract_timespecs(&now, &stream->start_ts, &data.stream_data.runtime);
@@ -899,7 +1035,9 @@
 	if (rc < 0)
 		return rc;
 	rc = cras_server_metrics_stream_runtime(stream);
-	return rc;
+	if (rc < 0)
+		return rc;
+	return cras_server_metrics_longest_fetch_delay(stream);
 }
 
 int cras_server_metrics_busyloop(struct timespec *ts, unsigned count)
@@ -960,6 +1098,15 @@
 		cras_metrics_log_sparse_histogram(kDeviceTypeOutput, data.type);
 }
 
+static void metrics_device_gain(struct cras_server_metrics_device_data data)
+{
+	char metrics_name[METRICS_NAME_BUFFER_SIZE];
+
+	snprintf(metrics_name, METRICS_NAME_BUFFER_SIZE, "%s.%s", kDeviceGain,
+		 metrics_device_type_str(data.type));
+	cras_metrics_log_histogram(metrics_name, data.value, 0, 2000, 20);
+}
+
 static void metrics_device_volume(struct cras_server_metrics_device_data data)
 {
 	char metrics_name[METRICS_NAME_BUFFER_SIZE];
@@ -969,21 +1116,24 @@
 	cras_metrics_log_histogram(metrics_name, data.value, 0, 100, 20);
 }
 
+static void
+metrics_longest_fetch_delay(struct cras_server_metrics_stream_data data)
+{
+	int fetch_delay_msec =
+		data.runtime.tv_sec * 1000 + data.runtime.tv_nsec / 1000000;
+	log_histogram_each_level(3, fetch_delay_msec, 0, 10000, 20,
+				 kFetchDelayMilliSeconds,
+				 metrics_client_type_str(data.client_type),
+				 metrics_stream_type_str(data.stream_type));
+}
+
 static void metrics_stream_runtime(struct cras_server_metrics_stream_data data)
 {
-	char metrics_name[METRICS_NAME_BUFFER_SIZE];
-
-	snprintf(metrics_name, METRICS_NAME_BUFFER_SIZE, "Cras.%sStreamRuntime",
-		 data.direction == CRAS_STREAM_INPUT ? "Input" : "Output");
-	cras_metrics_log_histogram(metrics_name, (unsigned)data.runtime.tv_sec,
-				   0, 10000, 20);
-
-	snprintf(metrics_name, METRICS_NAME_BUFFER_SIZE,
-		 "Cras.%sStreamRuntime.%s",
-		 data.direction == CRAS_STREAM_INPUT ? "Input" : "Output",
-		 metrics_client_type_str(data.type));
-	cras_metrics_log_histogram(metrics_name, (unsigned)data.runtime.tv_sec,
-				   0, 10000, 20);
+	log_histogram_each_level(
+		4, (int)data.runtime.tv_sec, 0, 10000, 20, kStreamRuntime,
+		data.direction == CRAS_STREAM_INPUT ? "Input" : "Output",
+		metrics_client_type_str(data.client_type),
+		metrics_stream_type_str(data.stream_type));
 }
 
 static void metrics_busyloop(struct cras_server_metrics_timespec_data data)
@@ -996,40 +1146,6 @@
 	cras_metrics_log_histogram(metrics_name, data.count, 0, 1000, 20);
 }
 
-/*
- * Logs metrics for each group it belongs to. The UMA does not merge subgroups
- * automatically so we need to log them separately.
- *
- * For example, if we call this function with argument (3, 48000,
- * Cras.StreamSamplingRate, Input, Chrome), it will send 48000 to below
- * metrics:
- * Cras.StreamSamplingRate.Input.Chrome
- * Cras.StreamSamplingRate.Input
- * Cras.StreamSamplingRate
- */
-static void log_sparse_histogram_each_level(int num, int sample, ...)
-{
-	char metrics_name[METRICS_NAME_BUFFER_SIZE] = {};
-	va_list valist;
-	int i, len = 0;
-
-	va_start(valist, sample);
-
-	for (i = 0; i < num && len < METRICS_NAME_BUFFER_SIZE; i++) {
-		int metric_len = snprintf(metrics_name + len,
-				METRICS_NAME_BUFFER_SIZE - len, "%s%s",
-				i ? "." : "", va_arg(valist, char *));
-		// Exit early on error or running out of bufferspace. Avoids
-		// logging partial or corrupted strings.
-		if (metric_len < 0 || metric_len > METRICS_NAME_BUFFER_SIZE - len)
-			break;
-		len += metric_len;
-		cras_metrics_log_sparse_histogram(metrics_name, sample);
-	}
-
-	va_end(valist);
-}
-
 static void
 metrics_stream_config(struct cras_server_metrics_stream_config config)
 {
@@ -1105,6 +1221,9 @@
 			kHfpWidebandSpeechSelectedCodec,
 			metrics_msg->data.value);
 		break;
+	case DEVICE_GAIN:
+		metrics_device_gain(metrics_msg->data.device_data);
+		break;
 	case DEVICE_RUNTIME:
 		metrics_device_runtime(metrics_msg->data.device_data);
 		break;
@@ -1132,9 +1251,7 @@
 					   20);
 		break;
 	case LONGEST_FETCH_DELAY:
-		cras_metrics_log_histogram(kStreamTimeoutMilliSeconds,
-					   metrics_msg->data.value, 1, 20000,
-					   10);
+		metrics_longest_fetch_delay(metrics_msg->data.stream_data);
 		break;
 	case MISSED_CB_FIRST_TIME_INPUT:
 		cras_metrics_log_histogram(kMissedCallbackFirstTimeInput,
diff --git a/cras/src/server/cras_server_metrics.h b/cras/src/server/cras_server_metrics.h
index 91f13c3..e845808 100644
--- a/cras/src/server/cras_server_metrics.h
+++ b/cras/src/server/cras_server_metrics.h
@@ -49,6 +49,9 @@
 /* Logs runtime of a device. */
 int cras_server_metrics_device_runtime(struct cras_iodev *iodev);
 
+/* Logs the gain of a device. */
+int cras_server_metrics_device_gain(struct cras_iodev *iodev);
+
 /* Logs the volume of a device. */
 int cras_server_metrics_device_volume(struct cras_iodev *iodev);
 
@@ -61,9 +64,6 @@
 int cras_server_metrics_highest_hw_level(unsigned hw_level,
 					 enum CRAS_STREAM_DIRECTION direction);
 
-/* Logs the longest fetch delay of a stream in millisecond. */
-int cras_server_metrics_longest_fetch_delay(unsigned delay_msec);
-
 /* Logs the number of underruns of a device. */
 int cras_server_metrics_num_underruns(unsigned num_underruns);
 
diff --git a/cras/src/server/cras_system_state.c b/cras/src/server/cras_system_state.c
index 331ecb1..366afb5 100644
--- a/cras/src/server/cras_system_state.c
+++ b/cras/src/server/cras_system_state.c
@@ -14,9 +14,11 @@
 #include <syslog.h>
 
 #include "cras_alsa_card.h"
+#include "cras_alert.h"
 #include "cras_board_config.h"
 #include "cras_config.h"
 #include "cras_device_blocklist.h"
+#include "cras_iodev_list.h"
 #include "cras_observer.h"
 #include "cras_shm.h"
 #include "cras_system_state.h"
@@ -158,6 +160,9 @@
 	exp_state->bt_wbs_enabled = board_config.bt_wbs_enabled;
 	exp_state->deprioritize_bt_wbs_mic =
 		board_config.deprioritize_bt_wbs_mic;
+	exp_state->noise_cancellation_enabled = 0;
+	exp_state->hotword_pause_at_suspend =
+		board_config.hotword_pause_at_suspend;
 
 	if ((rc = pthread_mutex_init(&state.update_lock, 0) != 0)) {
 		syslog(LOG_ERR, "Fatal: system state mutex init");
@@ -341,6 +346,7 @@
 {
 	state.exp_state->suspended = suspended;
 	cras_observer_notify_suspend_changed(suspended);
+	cras_alert_process_all_pending_alerts();
 }
 
 void cras_system_set_volume_limits(long min, long max)
@@ -399,6 +405,20 @@
 	return state.bt_fix_a2dp_packet_size;
 }
 
+void cras_system_set_noise_cancellation_enabled(bool enabled)
+{
+	/* When the flag is toggled, propagate to all iodevs immediately. */
+	if (cras_system_get_noise_cancellation_enabled() != enabled) {
+		state.exp_state->noise_cancellation_enabled = enabled;
+		cras_iodev_list_reset_for_noise_cancellation();
+	}
+}
+
+bool cras_system_get_noise_cancellation_enabled()
+{
+	return !!state.exp_state->noise_cancellation_enabled;
+}
+
 bool cras_system_check_ignore_ucm_suffix(const char *card_name)
 {
 	/* Check the general case: ALSA Loopback card "Loopback". */
@@ -414,6 +434,16 @@
 	return false;
 }
 
+bool cras_system_get_hotword_pause_at_suspend()
+{
+	return !!state.exp_state->hotword_pause_at_suspend;
+}
+
+void cras_system_set_hotword_pause_at_suspend(bool pause)
+{
+	state.exp_state->hotword_pause_at_suspend = pause;
+}
+
 int cras_system_add_alsa_card(struct cras_alsa_card_info *alsa_card_info)
 {
 	struct card_list *card;
diff --git a/cras/src/server/cras_system_state.h b/cras/src/server/cras_system_state.h
index ff04606..bd09395 100644
--- a/cras/src/server/cras_system_state.h
+++ b/cras/src/server/cras_system_state.h
@@ -134,9 +134,21 @@
 /* Gets the flag of Bluetooth fixed A2DP packet size. */
 bool cras_system_get_bt_fix_a2dp_packet_size_enabled();
 
+/* Sets the flag to enable or disable Noise Cancellation. */
+void cras_system_set_noise_cancellation_enabled(bool enabled);
+
+/* Gets the flag of Noise Cancellation. */
+bool cras_system_get_noise_cancellation_enabled();
+
 /* Checks if the card ignores the ucm suffix. */
 bool cras_system_check_ignore_ucm_suffix(const char *card_name);
 
+/* Returns true if hotword detection is paused at system suspend. */
+bool cras_system_get_hotword_pause_at_suspend();
+
+/* Sets whether to pause hotword detection at system suspend. */
+void cras_system_set_hotword_pause_at_suspend(bool pause);
+
 /* Adds a card at the given index to the system.  When a new card is found
  * (through a udev event notification) this will add the card to the system,
  * causing its devices to become available for playback/capture.
diff --git a/cras/src/server/dev_io.c b/cras/src/server/dev_io.c
index 42fe955..b311b22 100644
--- a/cras/src/server/dev_io.c
+++ b/cras/src/server/dev_io.c
@@ -10,6 +10,7 @@
 #include "audio_thread_log.h"
 #include "cras_audio_area.h"
 #include "cras_audio_thread_monitor.h"
+#include "cras_device_monitor.h"
 #include "cras_iodev.h"
 #include "cras_non_empty_audio_handler.h"
 #include "cras_rstream.h"
@@ -47,33 +48,78 @@
 /* The number of devices playing/capturing non-empty stream(s). */
 static int non_empty_device_count = 0;
 
-/* Gets the master device which the stream is attached to. */
-static inline struct cras_iodev *get_master_dev(const struct dev_stream *stream)
+/* The timestamp of last EIO error time. */
+static struct timespec last_io_err_time = { 0, 0 };
+
+/* The gap time to avoid repeated error close request to main thread. */
+static const int ERROR_CLOSE_GAP_TIME_SECS = 10;
+
+/* Gets the main device which the stream is attached to. */
+static inline struct cras_iodev *get_main_dev(const struct dev_stream *stream)
 {
-	return (struct cras_iodev *)stream->stream->master_dev.dev_ptr;
+	return (struct cras_iodev *)stream->stream->main_dev.dev_ptr;
 }
 
 /* Updates the estimated sample rate of open device to all attached
  * streams.
  */
-static void update_estimated_rate(struct open_dev *adev)
+static void update_estimated_rate(struct open_dev *adev,
+				  struct open_dev *odev_list,
+				  bool self_rate_need_update)
 {
-	struct cras_iodev *master_dev;
+	struct cras_iodev *main_dev;
 	struct cras_iodev *dev = adev->dev;
+	struct cras_iodev *tracked_dev = NULL;
 	struct dev_stream *dev_stream;
+	double dev_rate_ratio;
+	double main_dev_rate_ratio;
+
+	/*
+	 * If there is an output device on the same sound card running with the same
+	 * sampling rate, use the rate of that output device for this device.
+	 */
+	if (dev->direction == CRAS_STREAM_INPUT &&
+	    cras_iodev_is_on_internal_card(dev->active_node)) {
+		struct open_dev *odev;
+		DL_FOREACH (odev_list, odev) {
+			if (!cras_iodev_is_on_internal_card(
+				    odev->dev->active_node))
+				continue;
+			if (odev->dev->format->frame_rate !=
+			    dev->format->frame_rate)
+				continue;
+			tracked_dev = odev->dev;
+			break;
+		}
+	}
+
+	/*
+	 * Self-owned rate esimator does not need to udpate rate. There is no tracked
+	 * output device. So there is no need to update.
+	 */
+	if (!self_rate_need_update && !tracked_dev)
+		return;
 
 	DL_FOREACH (dev->streams, dev_stream) {
-		master_dev = get_master_dev(dev_stream);
-		if (master_dev == NULL) {
-			syslog(LOG_ERR, "Fail to find master open dev.");
+		main_dev = get_main_dev(dev_stream);
+		if (main_dev == NULL) {
+			syslog(LOG_ERR, "Fail to find main open dev.");
 			continue;
 		}
 
-		dev_stream_set_dev_rate(
-			dev_stream, dev->format->frame_rate,
-			cras_iodev_get_est_rate_ratio(dev),
-			cras_iodev_get_est_rate_ratio(master_dev),
-			adev->coarse_rate_adjust);
+		if (tracked_dev) {
+			dev_rate_ratio =
+				cras_iodev_get_est_rate_ratio(tracked_dev);
+			main_dev_rate_ratio = dev_rate_ratio;
+		} else {
+			dev_rate_ratio = cras_iodev_get_est_rate_ratio(dev);
+			main_dev_rate_ratio =
+				cras_iodev_get_est_rate_ratio(main_dev);
+		}
+
+		dev_stream_set_dev_rate(dev_stream, dev->format->frame_rate,
+					dev_rate_ratio, main_dev_rate_ratio,
+					adev->coarse_rate_adjust);
 	}
 }
 
@@ -464,7 +510,7 @@
  *    adev - The device to capture samples from.
  * Returns 0 on success.
  */
-static int capture_to_streams(struct open_dev *adev)
+static int capture_to_streams(struct open_dev *adev, struct open_dev *odev_list)
 {
 	struct cras_iodev *idev = adev->dev;
 	snd_pcm_uframes_t remainder, hw_level, cap_limit;
@@ -486,14 +532,29 @@
 	ATLOG(atlog, AUDIO_THREAD_READ_AUDIO_TSTAMP, idev->info.idx,
 	      hw_tstamp.tv_sec, hw_tstamp.tv_nsec);
 	if (timespec_is_nonzero(&hw_tstamp)) {
+		bool self_rate_need_update;
+
 		if (hw_level < idev->min_cb_level / 2)
 			adev->coarse_rate_adjust = 1;
 		else if (hw_level > idev->max_cb_level * 2)
 			adev->coarse_rate_adjust = -1;
 		else
 			adev->coarse_rate_adjust = 0;
-		if (cras_iodev_update_rate(idev, hw_level, &hw_tstamp))
-			update_estimated_rate(adev);
+
+		/*
+		 * This values means whether the rate estimator in the device
+		 * wants to update estimated rate.
+		 */
+		self_rate_need_update =
+			!!cras_iodev_update_rate(idev, hw_level, &hw_tstamp);
+
+		/*
+		 * Always calls update_estimated_rate so that new output rate
+		 * has a chance to propagate to input. In update_estimated_rate,
+		 * it will decide whether the new rate is from self rate estimator
+		 * or from the tracked output device.
+		 */
+		update_estimated_rate(adev, odev_list, self_rate_need_update);
 	}
 
 	cap_limit = get_stream_limit(adev, hw_level, &cap_limit_stream);
@@ -579,12 +640,13 @@
  *    write_limit - The maximum number of frames to write to dst.
  *
  * Returns:
- *    The number of frames rendered on success, a negative error code otherwise.
+ *    The number of frames rendered on success.
  *    This number of frames is the minimum of the amount of frames each stream
  *    could provide which is the maximum that can currently be rendered.
  */
-static int write_streams(struct open_dev **odevs, struct open_dev *adev,
-			 uint8_t *dst, size_t write_limit)
+static unsigned int write_streams(struct open_dev **odevs,
+				  struct open_dev *adev, uint8_t *dst,
+				  size_t write_limit)
 {
 	struct cras_iodev *odev = adev->dev;
 	struct dev_stream *curr;
@@ -746,7 +808,7 @@
 			adev->coarse_rate_adjust = 0;
 
 		if (cras_iodev_update_rate(odev, hw_level, &hw_tstamp))
-			update_estimated_rate(adev);
+			update_estimated_rate(adev, NULL, true);
 	}
 	ATLOG(atlog, AUDIO_THREAD_FILL_AUDIO, adev->dev->info.idx, hw_level,
 	      odev->min_cb_level);
@@ -768,9 +830,6 @@
 		/* TODO(dgreid) - This assumes interleaved audio. */
 		dst = area->channels[0].buf;
 		written = write_streams(odevs, adev, dst, frames);
-		if (written < 0) /* pcm has been closed */
-			return (int)written;
-
 		if (written < (snd_pcm_sframes_t)frames)
 			/* Got all the samples from client that we can, but it
 			 * won't fill the request. */
@@ -934,27 +993,46 @@
 static void handle_dev_err(int err_rc, struct open_dev **odevs,
 			   struct open_dev *adev)
 {
+	struct timespec diff, now;
 	if (err_rc == -EPIPE) {
 		/* Handle severe underrun. */
 		ATLOG(atlog, AUDIO_THREAD_SEVERE_UNDERRUN, adev->dev->info.idx,
 		      0, 0);
 		cras_iodev_reset_request(adev->dev);
 		cras_audio_thread_event_severe_underrun();
+	} else if (err_rc == -EIO) {
+		syslog(LOG_WARNING, "I/O err, reseting %s dev %s",
+		       adev->dev->direction == CRAS_STREAM_OUTPUT ? "output" :
+								    "input",
+		       adev->dev->info.name);
+		clock_gettime(CLOCK_REALTIME, &now);
+		subtract_timespecs(&now, &last_io_err_time, &diff);
+		if ((last_io_err_time.tv_sec == 0 &&
+		     last_io_err_time.tv_nsec == 0) ||
+		    diff.tv_sec > ERROR_CLOSE_GAP_TIME_SECS)
+			cras_iodev_reset_request(adev->dev);
+		else
+			cras_device_monitor_error_close(adev->dev->info.idx);
+
+		last_io_err_time = now;
+	} else {
+		syslog(LOG_ERR, "Dev %s err %d", adev->dev->info.name, err_rc);
 	}
 	/* Device error, remove it. */
 	dev_io_rm_open_dev(odevs, adev);
 }
 
-int dev_io_capture(struct open_dev **list)
+int dev_io_capture(struct open_dev **list, struct open_dev **olist)
 {
 	struct open_dev *idev_list = *list;
+	struct open_dev *odev_list = *olist;
 	struct open_dev *adev;
 	int rc;
 
 	DL_FOREACH (idev_list, adev) {
 		if (!cras_iodev_is_open(adev->dev))
 			continue;
-		rc = capture_to_streams(adev);
+		rc = capture_to_streams(adev, odev_list);
 		if (rc < 0)
 			handle_dev_err(rc, list, adev);
 	}
@@ -1105,7 +1183,7 @@
 	update_longest_wake(*idevs, &now);
 
 	dev_io_playback_fetch(*odevs);
-	dev_io_capture(idevs);
+	dev_io_capture(idevs, odevs);
 	dev_io_send_captured_samples(*idevs);
 	dev_io_playback_write(odevs, output_converter);
 }
@@ -1259,14 +1337,61 @@
 		dev_stream_destroy(out);
 }
 
-int dev_io_append_stream(struct open_dev **dev_list,
+/*
+ * Finds a matched input stream from open device list.
+ * The definition of the matched streams: Two streams having
+ * the same sampling rate and the same cb_threshold.
+ * This means their sleep time intervals should be very close
+ * if we neglect device estimated rate.
+ */
+static struct dev_stream *
+find_matched_input_stream(const struct cras_rstream *out_stream,
+			  struct open_dev *odev_list)
+{
+	struct open_dev *odev;
+	struct dev_stream *dev_stream;
+	size_t out_rate = out_stream->format.frame_rate;
+	size_t out_cb_threshold = cras_rstream_get_cb_threshold(out_stream);
+
+	DL_FOREACH (odev_list, odev) {
+		DL_FOREACH (odev->dev->streams, dev_stream) {
+			if (dev_stream->stream->format.frame_rate != out_rate)
+				continue;
+			if (cras_rstream_get_cb_threshold(dev_stream->stream) !=
+			    out_cb_threshold)
+				continue;
+			return dev_stream;
+		}
+	}
+	return NULL;
+}
+
+static bool
+find_matched_input_stream_next_cb_ts(const struct cras_rstream *stream,
+				     struct open_dev *odev_list,
+				     const struct timespec **next_cb_ts,
+				     const struct timespec **sleep_interval_ts)
+{
+	struct dev_stream *dev_stream =
+		find_matched_input_stream(stream, odev_list);
+	if (dev_stream) {
+		*next_cb_ts = dev_stream_next_cb_ts(dev_stream);
+		*sleep_interval_ts = dev_stream_sleep_interval_ts(dev_stream);
+		return *next_cb_ts != NULL;
+	}
+	return false;
+}
+
+int dev_io_append_stream(struct open_dev **odevs, struct open_dev **idevs,
 			 struct cras_rstream *stream,
 			 struct cras_iodev **iodevs, unsigned int num_iodevs)
 {
+	struct open_dev **dev_list;
 	struct open_dev *open_dev;
 	struct cras_iodev *dev;
 	struct dev_stream *out;
 	struct timespec init_cb_ts;
+	const struct timespec *init_sleep_interval_ts = NULL;
 	struct timespec extra_sleep;
 	const struct timespec *stream_ts;
 	unsigned int i;
@@ -1274,6 +1399,11 @@
 	int level;
 	int rc = 0;
 
+	if (stream->direction == CRAS_STREAM_OUTPUT)
+		dev_list = odevs;
+	else
+		dev_list = idevs;
+
 	for (i = 0; i < num_iodevs; i++) {
 		DL_SEARCH_SCALAR(*dev_list, open_dev, dev, iodevs[i]);
 		if (!open_dev)
@@ -1318,35 +1448,55 @@
 		 * may cause device buffer level stack up.
 		 */
 		if (stream->direction == CRAS_STREAM_OUTPUT) {
-			DL_FOREACH (dev->streams, out) {
-				stream_ts = dev_stream_next_cb_ts(out);
-				if (stream_ts &&
-				    (!cb_ts_set ||
-				     timespec_after(&init_cb_ts, stream_ts))) {
-					init_cb_ts = *stream_ts;
-					cb_ts_set = true;
+			/*
+			 * If there is a matched input stream, find its next cb time.
+			 * Use that as the initial cb time for this output stream.
+			 */
+			const struct timespec *in_stream_ts;
+			const struct timespec *in_stream_sleep_interval_ts;
+			bool found_matched_input;
+			found_matched_input =
+				find_matched_input_stream_next_cb_ts(
+					stream, *idevs, &in_stream_ts,
+					&in_stream_sleep_interval_ts);
+			if (found_matched_input) {
+				init_cb_ts = *in_stream_ts;
+				init_sleep_interval_ts =
+					in_stream_sleep_interval_ts;
+			} else {
+				DL_FOREACH (dev->streams, out) {
+					stream_ts = dev_stream_next_cb_ts(out);
+					if (stream_ts &&
+					    (!cb_ts_set ||
+					     timespec_after(&init_cb_ts,
+							    stream_ts))) {
+						init_cb_ts = *stream_ts;
+						cb_ts_set = true;
+					}
 				}
-			}
-			if (!cb_ts_set) {
-				level = cras_iodev_get_valid_frames(
-					dev, &init_cb_ts);
-				if (level < 0) {
-					syslog(LOG_ERR,
-					       "Failed to set output init_cb_ts, rc = %d",
-					       level);
-					rc = -EINVAL;
-					break;
+				if (!cb_ts_set) {
+					level = cras_iodev_get_valid_frames(
+						dev, &init_cb_ts);
+					if (level < 0) {
+						syslog(LOG_ERR,
+						       "Failed to set output init_cb_ts, rc = %d",
+						       level);
+						rc = -EINVAL;
+						break;
+					}
+					level -= cras_frames_at_rate(
+						stream->format.frame_rate,
+						cras_rstream_get_cb_threshold(
+							stream),
+						dev->format->frame_rate);
+					if (level < 0)
+						level = 0;
+					cras_frames_to_time(
+						level, dev->format->frame_rate,
+						&extra_sleep);
+					add_timespecs(&init_cb_ts,
+						      &extra_sleep);
 				}
-				level -= cras_frames_at_rate(
-					stream->format.frame_rate,
-					cras_rstream_get_cb_threshold(stream),
-					dev->format->frame_rate);
-				if (level < 0)
-					level = 0;
-				cras_frames_to_time(level,
-						    dev->format->frame_rate,
-						    &extra_sleep);
-				add_timespecs(&init_cb_ts, &extra_sleep);
 			}
 		} else {
 			/*
@@ -1365,7 +1515,7 @@
 		}
 
 		out = dev_stream_create(stream, dev->info.idx, dev->format, dev,
-					&init_cb_ts);
+					&init_cb_ts, init_sleep_interval_ts);
 		if (!out) {
 			rc = -EINVAL;
 			break;
@@ -1418,20 +1568,6 @@
 			 struct cras_rstream *stream, struct cras_iodev *dev)
 {
 	struct open_dev *open_dev;
-	struct timespec delay;
-	unsigned fetch_delay_msec;
-
-	/* Metrics log the longest fetch delay of this stream. */
-	if (timespec_after(&stream->longest_fetch_interval,
-			   &stream->sleep_interval_ts)) {
-		subtract_timespecs(&stream->longest_fetch_interval,
-				   &stream->sleep_interval_ts, &delay);
-		fetch_delay_msec =
-			delay.tv_sec * 1000 + delay.tv_nsec / 1000000;
-		if (fetch_delay_msec)
-			cras_server_metrics_longest_fetch_delay(
-				fetch_delay_msec);
-	}
 
 	ATLOG(atlog, AUDIO_THREAD_STREAM_REMOVED, stream->stream_id, 0, 0);
 
diff --git a/cras/src/server/dev_io.h b/cras/src/server/dev_io.h
index 259bbab..ca71a80 100644
--- a/cras/src/server/dev_io.h
+++ b/cras/src/server/dev_io.h
@@ -58,8 +58,9 @@
  * Captures samples from each device in the list.
  *    list - Pointer to the list of input devices.  Devices that fail to read
  *           will be removed from the list.
+ *    olist - Pointer to the list of output devices.
  */
-int dev_io_capture(struct open_dev **list);
+int dev_io_capture(struct open_dev **list, struct open_dev **olist);
 
 /*
  * Send samples that have been captured to their streams.
@@ -101,7 +102,7 @@
 				      unsigned int dev_idx);
 
 /* Append a new stream to a specified set of iodevs. */
-int dev_io_append_stream(struct open_dev **dev_list,
+int dev_io_append_stream(struct open_dev **odevs, struct open_dev **idevs,
 			 struct cras_rstream *stream,
 			 struct cras_iodev **iodevs, unsigned int num_iodevs);
 
diff --git a/cras/src/server/dev_stream.c b/cras/src/server/dev_stream.c
index 025aedd..be5a6da 100644
--- a/cras/src/server/dev_stream.c
+++ b/cras/src/server/dev_stream.c
@@ -63,7 +63,8 @@
 struct dev_stream *dev_stream_create(struct cras_rstream *stream,
 				     unsigned int dev_id,
 				     const struct cras_audio_format *dev_fmt,
-				     void *dev_ptr, struct timespec *cb_ts)
+				     void *dev_ptr, struct timespec *cb_ts,
+				     const struct timespec *sleep_interval_ts)
 {
 	struct dev_stream *out;
 	struct cras_audio_format *stream_fmt = &stream->format;
@@ -122,8 +123,15 @@
 	out->conv_buffer = byte_buffer_create(buf_bytes);
 	out->conv_area = cras_audio_area_create(ofmt->num_channels);
 
-	cras_frames_to_time(cras_rstream_get_cb_threshold(stream),
-			    stream_fmt->frame_rate, &stream->sleep_interval_ts);
+	/* Use sleep interval hint from argument if it is provided */
+	if (sleep_interval_ts) {
+		stream->sleep_interval_ts = *sleep_interval_ts;
+	} else {
+		cras_frames_to_time(cras_rstream_get_cb_threshold(stream),
+				    stream_fmt->frame_rate,
+				    &stream->sleep_interval_ts);
+	}
+
 	stream->next_cb_ts = *cb_ts;
 
 	/* Sets up the stream & dev pair. */
@@ -149,9 +157,9 @@
 
 void dev_stream_set_dev_rate(struct dev_stream *dev_stream,
 			     unsigned int dev_rate, double dev_rate_ratio,
-			     double master_rate_ratio, int coarse_rate_adjust)
+			     double main_rate_ratio, int coarse_rate_adjust)
 {
-	if (dev_stream->dev_id == dev_stream->stream->master_dev.dev_id) {
+	if (dev_stream->dev_id == dev_stream->stream->main_dev.dev_id) {
 		cras_fmt_conv_set_linear_resample_rates(dev_stream->conv,
 							dev_rate, dev_rate);
 		cras_frames_to_time_precise(
@@ -159,9 +167,8 @@
 			dev_stream->stream->format.frame_rate * dev_rate_ratio,
 			&dev_stream->stream->sleep_interval_ts);
 	} else {
-		double new_rate =
-			dev_rate * dev_rate_ratio / master_rate_ratio +
-			coarse_rate_adjust_step * coarse_rate_adjust;
+		double new_rate = dev_rate * dev_rate_ratio / main_rate_ratio +
+				  coarse_rate_adjust_step * coarse_rate_adjust;
 		cras_fmt_conv_set_linear_resample_rates(dev_stream->conv,
 							dev_rate, new_rate);
 	}
diff --git a/cras/src/server/dev_stream.h b/cras/src/server/dev_stream.h
index c39a801..6b34d5d 100644
--- a/cras/src/server/dev_stream.h
+++ b/cras/src/server/dev_stream.h
@@ -46,30 +46,47 @@
 	int is_running;
 };
 
+/*
+ * Creates a dev_stream.
+ *
+ * Args:
+ *    stream - The associated rstream.
+ *    dev_id - Index of the device.
+ *    dev_fmt - The format of the device.
+ *    dev_ptr - A pointer to the device
+ *    cb_ts - A pointer to the initial callback time.
+ *    sleep_interval_ts - A pointer to the initial sleep interval.
+ *        Set to null to calculate the value from device rate and block size.
+ *        Note that we need this argument so that output device sleep interval
+ *        can use input device sleep interval in the beginning to have perfect
+ *        alignment in WebRTC use case.
+ * Returns the pointer to the created dev_stream.
+ */
 struct dev_stream *dev_stream_create(struct cras_rstream *stream,
 				     unsigned int dev_id,
 				     const struct cras_audio_format *dev_fmt,
-				     void *dev_ptr, struct timespec *cb_ts);
+				     void *dev_ptr, struct timespec *cb_ts,
+				     const struct timespec *sleep_interval_ts);
 void dev_stream_destroy(struct dev_stream *dev_stream);
 
 /*
  * Update the estimated sample rate of the device. For multiple active
  * devices case, the linear resampler will be configured by the estimated
- * rate ration of the master device and the current active device the
+ * rate ration of the main device and the current active device the
  * rstream attaches to.
  *
  * Args:
  *    dev_stream - The structure holding the stream.
  *    dev_rate - The sample rate device is using.
  *    dev_rate_ratio - The ratio of estimated rate and used rate.
- *    master_rate_ratio - The ratio of estimated rate and used rate of
- *        master device.
+ *    main_rate_ratio - The ratio of estimated rate and used rate of
+ *        main device.
  *    coarse_rate_adjust - The flag to indicate the direction device
  *        sample rate should adjust to.
  */
 void dev_stream_set_dev_rate(struct dev_stream *dev_stream,
 			     unsigned int dev_rate, double dev_rate_ratio,
-			     double master_rate_ratio, int coarse_rate_adjust);
+			     double main_rate_ratio, int coarse_rate_adjust);
 
 /*
  * Renders count frames from shm into dst.  Updates count if anything is
diff --git a/cras/src/server/server_stream.c b/cras/src/server/server_stream.c
index 6644c46..36d5496 100644
--- a/cras/src/server/server_stream.c
+++ b/cras/src/server/server_stream.c
@@ -83,6 +83,5 @@
 		syslog(LOG_ERR, "No server stream to destroy");
 		return;
 	}
-	/* Schedule remove stream in next main thread loop. */
-	cras_system_add_task(server_stream_rm_cb, stream_list);
+	server_stream_rm_cb(stream_list);
 }
diff --git a/cras/src/server/stream_list.c b/cras/src/server/stream_list.c
index 719608a..04ef9fe 100644
--- a/cras/src/server/stream_list.c
+++ b/cras/src/server/stream_list.c
@@ -3,12 +3,19 @@
  * found in the LICENSE file.
  */
 
+#include <syslog.h>
 #include "cras_rstream.h"
 #include "cras_tm.h"
 #include "cras_types.h"
 #include "stream_list.h"
 #include "utlist.h"
 
+/*
+ * If the time difference of two streams is short than 10s, they may be the RTC
+ * streams.
+ */
+static const struct timespec RTC_STREAM_THRESHOLD = { 10, 0 };
+
 struct stream_list {
 	struct cras_rstream *streams;
 	struct cras_rstream *streams_to_delete;
@@ -154,3 +161,28 @@
 	}
 	return false;
 }
+
+void detect_rtc_stream_pair(struct stream_list *list,
+			    struct cras_rstream *stream)
+{
+	struct cras_rstream *next_stream;
+	if (stream->cb_threshold != 480)
+		return;
+	if (stream->client_type != CRAS_CLIENT_TYPE_CHROME &&
+	    stream->client_type != CRAS_CLIENT_TYPE_LACROS)
+		return;
+	DL_FOREACH (list->streams, next_stream) {
+		if (next_stream->cb_threshold == 480 &&
+		    next_stream->direction != stream->direction &&
+		    next_stream->client_type == stream->client_type &&
+		    timespec_diff_shorter_than(&stream->start_ts,
+					       &next_stream->start_ts,
+					       &RTC_STREAM_THRESHOLD)) {
+			stream->stream_type =
+				CRAS_STREAM_TYPE_VOICE_COMMUNICATION;
+			next_stream->stream_type =
+				CRAS_STREAM_TYPE_VOICE_COMMUNICATION;
+			return;
+		}
+	}
+}
diff --git a/cras/src/server/stream_list.h b/cras/src/server/stream_list.h
index 0a9b86a..a527bc9 100644
--- a/cras/src/server/stream_list.h
+++ b/cras/src/server/stream_list.h
@@ -55,3 +55,14 @@
  */
 bool stream_list_has_pinned_stream(struct stream_list *list,
 				   unsigned int dev_idx);
+
+/*
+ * Detects whether there is a RTC stream pair based on these rules:
+ * 1. The cb_threshold is 480.
+ * 2. The direction of two streams are opposite.
+ * 3. Two streams are from the same client. (Chrome or LaCrOS)
+ * 4. The start time of two streams are close enough. (shorter than 1s)
+ * If all rules are passed, set the stream type to the voice communication.
+ */
+void detect_rtc_stream_pair(struct stream_list *list,
+			    struct cras_rstream *stream);
diff --git a/cras/src/tests/a2dp_iodev_unittest.cc b/cras/src/tests/a2dp_iodev_unittest.cc
index 523a62e..06c1cd3 100644
--- a/cras/src/tests/a2dp_iodev_unittest.cc
+++ b/cras/src/tests/a2dp_iodev_unittest.cc
@@ -803,6 +803,10 @@
   return "/org/bluez/hci0/dev_1A_2B_3C_4D_5E_6F";
 }
 
+int cras_bt_device_get_stable_id(const struct cras_bt_device* device) {
+  return 123;
+}
+
 void cras_bt_device_append_iodev(struct cras_bt_device* device,
                                  struct cras_iodev* iodev,
                                  enum cras_bt_device_profile profile) {
diff --git a/cras/src/tests/alsa_io_unittest.cc b/cras/src/tests/alsa_io_unittest.cc
index b3059a2..021b478 100644
--- a/cras/src/tests/alsa_io_unittest.cc
+++ b/cras/src/tests/alsa_io_unittest.cc
@@ -2559,6 +2559,10 @@
   sys_set_volume_limits_called++;
 }
 
+bool cras_system_get_noise_cancellation_enabled() {
+  return false;
+}
+
 //  From cras_alsa_mixer.
 void cras_alsa_mixer_set_dBFS(struct cras_alsa_mixer* m,
                               long dB_level,
@@ -2807,6 +2811,17 @@
   return -EINVAL;
 }
 
+int ucm_node_noise_cancellation_exists(struct cras_use_case_mgr* mgr,
+                                       const char* node_name) {
+  return 0;
+}
+
+int ucm_enable_node_noise_cancellation(struct cras_use_case_mgr* mgr,
+                                       const char* node_name,
+                                       int enable) {
+  return 0;
+}
+
 struct cras_volume_curve* cras_volume_curve_create_default() {
   return &default_curve;
 }
@@ -2888,6 +2903,12 @@
   return NULL;
 }
 
+void ucm_disable_all_hotword_models(struct cras_use_case_mgr* mgr) {}
+
+int ucm_enable_hotword_model(struct cras_use_case_mgr* mgr) {
+  return 0;
+}
+
 int ucm_get_default_node_gain(struct cras_use_case_mgr* mgr,
                               const char* dev,
                               long* gain) {
diff --git a/cras/src/tests/alsa_mixer_unittest.cc b/cras/src/tests/alsa_mixer_unittest.cc
index edf6110..b3db9de 100644
--- a/cras/src/tests/alsa_mixer_unittest.cc
+++ b/cras/src/tests/alsa_mixer_unittest.cc
@@ -381,7 +381,7 @@
   mixer_control_destroy(mixer_output);
 }
 
-TEST(AlsaMixer, CreateOneMasterElement) {
+TEST(AlsaMixer, CreateOneMainElement) {
   struct cras_alsa_mixer* c;
   int element_playback_volume[] = {
       1,
@@ -419,10 +419,10 @@
   EXPECT_EQ(3, snd_mixer_selem_get_name_called);
   EXPECT_EQ(1, snd_mixer_elem_next_called);
 
-  /* set mute should be called for Master. */
+  /* set mute should be called for Main. */
   cras_alsa_mixer_set_mute(c, 0, NULL);
   EXPECT_EQ(1, snd_mixer_selem_set_playback_switch_all_called);
-  /* set volume should be called for Master. */
+  /* set volume should be called for Main. */
   cras_alsa_mixer_set_dBFS(c, 0, NULL);
   EXPECT_EQ(1, snd_mixer_selem_set_playback_dB_all_called);
 
@@ -515,15 +515,15 @@
   EXPECT_EQ(5, snd_mixer_selem_get_name_called);
   EXPECT_EQ(3, snd_mixer_selem_has_playback_switch_called);
 
-  /* Set mute should be called for Master only. */
+  /* Set mute should be called for Main only. */
   cras_alsa_mixer_set_mute(c, 0, NULL);
   EXPECT_EQ(1, snd_mixer_selem_set_playback_switch_all_called);
 
-  /* Set volume should be called for Master and PCM. If Master doesn't set to
+  /* Set volume should be called for Main and PCM. If Main doesn't set to
    * anything but zero then the entire volume should be passed to the PCM
    * control.*/
 
-  /* Set volume should be called for Master and PCM. (without mixer_output) */
+  /* Set volume should be called for Main and PCM. (without mixer_output) */
   snd_mixer_selem_get_playback_dB_return_values = get_dB_returns;
   snd_mixer_selem_get_playback_dB_return_values_length =
       ARRAY_SIZE(get_dB_returns);
@@ -557,8 +557,8 @@
   EXPECT_EQ(1, snd_mixer_selem_has_playback_switch_called);
   EXPECT_EQ(1, snd_mixer_selem_get_playback_dB_range_called);
 
-  /* Set volume should be called for Master, PCM, and the mixer_output passed
-   * in. If Master doesn't set to anything but zero then the entire volume
+  /* Set volume should be called for Main, PCM, and the mixer_output passed
+   * in. If Main doesn't set to anything but zero then the entire volume
    * should be passed to the PCM control.*/
   cras_alsa_mixer_set_dBFS(c, -50, mixer_output);
   EXPECT_EQ(3, snd_mixer_selem_set_playback_dB_all_called);
@@ -566,8 +566,8 @@
   EXPECT_EQ(30, set_dB_values[0]);
   EXPECT_EQ(30, set_dB_values[1]);
   EXPECT_EQ(30, set_dB_values[2]);
-  /* Set volume should be called for Master and PCM. Since the controls were
-   * sorted, Master should get the volume remaining after PCM is set, in this
+  /* Set volume should be called for Main and PCM. Since the controls were
+   * sorted, Main should get the volume remaining after PCM is set, in this
    * case -50 - -24 = -26. */
   long get_dB_returns2[] = {
       -25,
@@ -584,7 +584,7 @@
   cras_alsa_mixer_set_dBFS(c, -50, mixer_output);
   EXPECT_EQ(2, snd_mixer_selem_set_playback_dB_all_called);
   EXPECT_EQ(2, snd_mixer_selem_get_playback_dB_called);
-  EXPECT_EQ(54, set_dB_values[0]);  // Master
+  EXPECT_EQ(54, set_dB_values[0]);  // Main
   EXPECT_EQ(30, set_dB_values[1]);  // PCM
 
   cras_alsa_mixer_destroy(c);
@@ -639,7 +639,7 @@
   EXPECT_EQ(5, snd_mixer_selem_get_name_called);
   EXPECT_EQ(3, snd_mixer_selem_has_capture_switch_called);
 
-  /* Set mute should be called for Master only. */
+  /* Set mute should be called for Main only. */
   cras_alsa_mixer_set_capture_mute(c, 0, NULL);
   EXPECT_EQ(1, snd_mixer_selem_set_capture_switch_all_called);
   /* Set volume should be called for Capture and Digital Capture. If Capture
@@ -773,7 +773,7 @@
 
     ResetStubData();
     snd_mixer_first_elem_return_value =
-        reinterpret_cast<snd_mixer_elem_t*>(1);  // Master
+        reinterpret_cast<snd_mixer_elem_t*>(1);  // Main
     snd_mixer_elem_next_return_values = elements;
     snd_mixer_elem_next_return_values_length = ARRAY_SIZE(elements);
     snd_mixer_selem_has_playback_volume_return_values = element_playback_volume;
diff --git a/cras/src/tests/alsa_ucm_unittest.cc b/cras/src/tests/alsa_ucm_unittest.cc
index 44c3587..1b351dd 100644
--- a/cras/src/tests/alsa_ucm_unittest.cc
+++ b/cras/src/tests/alsa_ucm_unittest.cc
@@ -28,11 +28,13 @@
 static std::vector<std::string> snd_use_case_get_id;
 static int snd_use_case_set_return;
 static std::map<std::string, std::string> snd_use_case_get_value;
+static std::map<std::string, unsigned> snd_use_case_geti_value;
 static unsigned snd_use_case_set_called;
 static std::vector<std::pair<std::string, std::string> > snd_use_case_set_param;
 static std::map<std::string, const char**> fake_list;
 static std::map<std::string, unsigned> fake_list_size;
 static unsigned snd_use_case_free_list_called;
+static unsigned snd_use_case_geti_called;
 static std::vector<std::string> list_devices_callback_names;
 static std::vector<void*> list_devices_callback_args;
 static struct cras_use_case_mgr cras_ucm_mgr;
@@ -45,10 +47,12 @@
   snd_use_case_set_return = 0;
   snd_use_case_get_called = 0;
   snd_use_case_set_called = 0;
+  snd_use_case_geti_called = 0;
   snd_use_case_set_param.clear();
   snd_use_case_free_list_called = 0;
   snd_use_case_get_id.clear();
   snd_use_case_get_value.clear();
+  snd_use_case_geti_value.clear();
   fake_list.clear();
   fake_list_size.clear();
   fake_list["_verbs"] = avail_verbs;
@@ -57,6 +61,7 @@
   list_devices_callback_args.clear();
   snd_use_case_mgr_open_mgr_ptr = reinterpret_cast<snd_use_case_mgr_t*>(0x55);
   cras_ucm_mgr.use_case = CRAS_STREAM_TYPE_DEFAULT;
+  cras_ucm_mgr.hotword_modifier = NULL;
 }
 
 static void list_devices_callback(const char* section_name, void* arg) {
@@ -522,26 +527,85 @@
   const char* modifiers[] = {"Hotword Model en", "Comment1",
                              "Hotword Model jp", "Comment2",
                              "Hotword Model de", "Comment3"};
-  const char* enabled_mods[] = {"Hotword Model en"};
+  const char* enabled_mods[] = {"Hotword Model jp"};
+  int ret;
+  std::string id = "_modstatus/Hotword Model jp";
   ResetStubData();
 
+  snd_use_case_geti_value[id] = 1;
   fake_list["_modifiers/HiFi"] = modifiers;
   fake_list_size["_modifiers/HiFi"] = 6;
 
   EXPECT_EQ(-EINVAL, ucm_set_hotword_model(mgr, "zh"));
   EXPECT_EQ(0, snd_use_case_set_called);
 
+  ret = ucm_set_hotword_model(mgr, "jp");
+
+  EXPECT_EQ(0, ret);
+  EXPECT_EQ(0, snd_use_case_set_called);
+  EXPECT_EQ(0, strcmp(mgr->hotword_modifier, "Hotword Model jp"));
+
   fake_list["_enamods"] = enabled_mods;
   fake_list_size["_enamods"] = 1;
-  ucm_set_hotword_model(mgr, "jp");
-
+  ret = ucm_set_hotword_model(mgr, "de");
+  EXPECT_EQ(0, ret);
   EXPECT_EQ(2, snd_use_case_set_called);
+  EXPECT_EQ(1, snd_use_case_geti_called);
+  EXPECT_EQ(
+      snd_use_case_set_param[0],
+      std::make_pair(std::string("_dismod"), std::string("Hotword Model jp")));
+  EXPECT_EQ(
+      snd_use_case_set_param[1],
+      std::make_pair(std::string("_enamod"), std::string("Hotword Model de")));
+  free(mgr->hotword_modifier);
+}
+
+TEST(AlsaUcm, DisableAllHotwordModels) {
+  struct cras_use_case_mgr* mgr = &cras_ucm_mgr;
+  const char* modifiers[] = {"Hotword Model en", "Comment1",
+                             "Hotword Model jp", "Comment2",
+                             "Hotword Model de", "Comment3"};
+  const char* enabled_mods[] = {"Hotword Model en"};
+  ResetStubData();
+
+  fake_list["_modifiers/HiFi"] = modifiers;
+  fake_list_size["_modifiers/HiFi"] = 6;
+  fake_list["_enamods"] = enabled_mods;
+  fake_list_size["_enamods"] = 1;
+
+  ucm_disable_all_hotword_models(mgr);
+
+  EXPECT_EQ(1, snd_use_case_set_called);
   EXPECT_EQ(
       snd_use_case_set_param[0],
       std::make_pair(std::string("_dismod"), std::string("Hotword Model en")));
+}
+
+TEST(AlsaUcm, EnableHotwordModel) {
+  struct cras_use_case_mgr* mgr = &cras_ucm_mgr;
+  const char* modifiers[] = {"Hotword Model en", "Comment1",
+                             "Hotword Model jp", "Comment2",
+                             "Hotword Model de", "Comment3"};
+  const char* enabled_mods[] = {""};
+  int ret;
+  ResetStubData();
+
+  fake_list["_modifiers/HiFi"] = modifiers;
+  fake_list_size["_modifiers/HiFi"] = 6;
+  fake_list["_enamods"] = enabled_mods;
+  fake_list_size["_enamods"] = 0;
+
+  EXPECT_EQ(-EINVAL, ucm_enable_hotword_model(mgr));
+
+  mgr->hotword_modifier = strdup("Hotword Model de");
+  ret = ucm_enable_hotword_model(mgr);
+
+  EXPECT_EQ(0, ret);
+  EXPECT_EQ(1, snd_use_case_set_called);
   EXPECT_EQ(
-      snd_use_case_set_param[1],
-      std::make_pair(std::string("_enamod"), std::string("Hotword Model jp")));
+      snd_use_case_set_param[0],
+      std::make_pair(std::string("_enamod"), std::string("Hotword Model de")));
+  free(mgr->hotword_modifier);
 }
 
 TEST(AlsaUcm, SwapModeExists) {
@@ -629,6 +693,76 @@
   EXPECT_EQ(1, snd_use_case_set_called);
 }
 
+TEST(AlsaUcm, NoiseCancellationExists) {
+  struct cras_use_case_mgr* mgr = &cras_ucm_mgr;
+  int rc;
+  const char* node = "Internal Mic";
+  const char* modifiers_1[] = {"Internal Mic Noise Cancellation", "Comment"};
+  const char* modifiers_2[] = {"Internal Mic Noise Augmentation", "Comment"};
+  const char* modifiers_3[] = {"Microphone Noise Cancellation", "Comment"};
+
+  ResetStubData();
+
+  fake_list["_modifiers/HiFi"] = modifiers_1;
+  fake_list_size["_modifiers/HiFi"] = 2;
+  rc = ucm_node_noise_cancellation_exists(mgr, node);
+  EXPECT_EQ(1, rc);
+
+  fake_list["_modifiers/HiFi"] = modifiers_2;
+  fake_list_size["_modifiers/HiFi"] = 2;
+  rc = ucm_node_noise_cancellation_exists(mgr, node);
+  EXPECT_EQ(0, rc);
+
+  fake_list["_modifiers/HiFi"] = modifiers_3;
+  fake_list_size["_modifiers/HiFi"] = 2;
+  rc = ucm_node_noise_cancellation_exists(mgr, node);
+  EXPECT_EQ(0, rc);
+}
+
+TEST(AlsaUcm, EnableDisableNoiseCancellation) {
+  struct cras_use_case_mgr* mgr = &cras_ucm_mgr;
+  int rc;
+  const char* modifiers[] = {"Internal Mic Noise Cancellation", "Comment1",
+                             "Microphone Noise Cancellation", "Comment2"};
+  const char* modifiers_enabled[] = {"Internal Mic Noise Cancellation"};
+
+  ResetStubData();
+
+  fake_list["_modifiers/HiFi"] = modifiers;
+  fake_list_size["_modifiers/HiFi"] = 4;
+
+  fake_list["_enamods"] = modifiers_enabled;
+  fake_list_size["_enamods"] = 1;
+
+  snd_use_case_set_return = 0;
+
+  rc = ucm_enable_node_noise_cancellation(mgr, "Line In", 1);
+  EXPECT_EQ(-EPERM, rc);  // Modifier is not existed
+  EXPECT_EQ(0, snd_use_case_set_called);
+
+  rc = ucm_enable_node_noise_cancellation(mgr, "Line In", 0);
+  EXPECT_EQ(-EPERM, rc);  // Modifier is not existed
+  EXPECT_EQ(0, snd_use_case_set_called);
+
+  rc = ucm_enable_node_noise_cancellation(mgr, "Microphone", 0);
+  EXPECT_EQ(0, rc);  // Modifier is already disabled
+  EXPECT_EQ(0, snd_use_case_set_called);
+
+  rc = ucm_enable_node_noise_cancellation(mgr, "Microphone", 1);
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(1, snd_use_case_set_called);
+
+  snd_use_case_set_called = 0;
+
+  rc = ucm_enable_node_noise_cancellation(mgr, "Internal Mic", 1);
+  EXPECT_EQ(0, rc);  // Modifier is already enabled
+  EXPECT_EQ(0, snd_use_case_set_called);
+
+  rc = ucm_enable_node_noise_cancellation(mgr, "Internal Mic", 0);
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(1, snd_use_case_set_called);
+}
+
 TEST(AlsaFlag, GetFlag) {
   struct cras_use_case_mgr* mgr = &cras_ucm_mgr;
   char* flag_value;
@@ -1406,6 +1540,19 @@
   return 0;
 }
 
+int snd_use_case_geti(snd_use_case_mgr_t* uc_mgr,
+                      const char* identifier,
+                      long* value) {
+  snd_use_case_geti_called++;
+  if (snd_use_case_geti_value.find(identifier) ==
+      snd_use_case_geti_value.end()) {
+    *value = 0;
+    return -1;
+  }
+  *value = snd_use_case_geti_value[identifier];
+  return 0;
+}
+
 } /* extern "C" */
 
 }  //  namespace
diff --git a/cras/src/tests/apm_list_unittest.cc b/cras/src/tests/apm_list_unittest.cc
index 09c7b86..65e712f 100644
--- a/cras/src/tests/apm_list_unittest.cc
+++ b/cras/src/tests/apm_list_unittest.cc
@@ -79,6 +79,64 @@
   rmdir(dir);
 }
 
+static void init_channel_layout(struct cras_audio_format* fmt) {
+  int i;
+  for (i = 0; i < CRAS_CH_MAX; i++)
+    fmt->channel_layout[i] = -1;
+}
+
+TEST(ApmList, AddApmInputDevUnuseFirstChannel) {
+  struct cras_audio_format fmt;
+  struct cras_audio_format* val;
+  struct cras_apm* apm;
+  int ch;
+  const int num_test_casts = 9;
+  int test_layouts[num_test_casts][CRAS_CH_MAX] = {
+      {0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+      {0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+      {0, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+      {1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+      {1, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+      {2, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+      {2, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+      {3, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+      {3, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1}};
+  int test_num_channels[num_test_casts] = {1, 2, 2, 2, 2, 3, 4, 4, 4};
+
+  fmt.frame_rate = 48000;
+  fmt.format = SND_PCM_FORMAT_S16_LE;
+
+  cras_apm_list_init("");
+  list = cras_apm_list_create(stream_ptr, APM_ECHO_CANCELLATION);
+  EXPECT_NE((void*)NULL, list);
+
+  for (int i = 0; i < num_test_casts; i++) {
+    fmt.num_channels = test_num_channels[i];
+    init_channel_layout(&fmt);
+    for (ch = 0; ch < CRAS_CH_MAX; ch++)
+      fmt.channel_layout[ch] = test_layouts[i][ch];
+
+    /* Input dev is of aec use case. */
+    apm = cras_apm_list_add_apm(list, dev_ptr, &fmt, 1);
+    EXPECT_NE((void*)NULL, apm);
+
+    /* Assert that the post-processing format never has an unset
+     * first channel in the layout. */
+    bool first_channel_found_in_layout = 0;
+    val = cras_apm_list_get_format(apm);
+    for (ch = 0; ch < CRAS_CH_MAX; ch++)
+      if (0 == val->channel_layout[ch])
+        first_channel_found_in_layout = 1;
+
+    EXPECT_EQ(1, first_channel_found_in_layout);
+
+    cras_apm_list_remove_apm(list, dev_ptr);
+  }
+
+  cras_apm_list_destroy(list);
+  cras_apm_list_deinit();
+}
+
 TEST(ApmList, AddRemoveApm) {
   struct cras_audio_format fmt;
   char* dir;
@@ -170,6 +228,9 @@
   fmt.num_channels = 2;
   fmt.frame_rate = 48000;
   fmt.format = SND_PCM_FORMAT_S16_LE;
+  init_channel_layout(&fmt);
+  fmt.channel_layout[CRAS_CH_FL] = 0;
+  fmt.channel_layout[CRAS_CH_FR] = 1;
 
   cras_apm_list_init("");
 
diff --git a/cras/src/tests/audio_thread_unittest.cc b/cras/src/tests/audio_thread_unittest.cc
index 6b78c69..93045e0 100644
--- a/cras/src/tests/audio_thread_unittest.cc
+++ b/cras/src/tests/audio_thread_unittest.cc
@@ -56,6 +56,7 @@
 static enum CRAS_IODEV_RAMP_REQUEST cras_iodev_start_ramp_request;
 static struct timespec clock_gettime_retspec;
 static struct timespec init_cb_ts_;
+static struct timespec sleep_interval_ts_;
 static std::map<const struct dev_stream*, struct timespec>
     dev_stream_wake_time_val;
 static int cras_device_monitor_set_device_mute_state_called;
@@ -1225,10 +1226,12 @@
                                      unsigned int dev_id,
                                      const struct cras_audio_format* dev_fmt,
                                      void* dev_ptr,
-                                     struct timespec* cb_ts) {
+                                     struct timespec* cb_ts,
+                                     const struct timespec* sleep_interval_ts) {
   struct dev_stream* out = static_cast<dev_stream*>(calloc(1, sizeof(*out)));
   out->stream = stream;
   init_cb_ts_ = *cb_ts;
+  sleep_interval_ts_ = *sleep_interval_ts;
   return out;
 }
 
@@ -1268,7 +1271,7 @@
 void dev_stream_set_dev_rate(struct dev_stream* dev_stream,
                              unsigned int dev_rate,
                              double dev_rate_ratio,
-                             double master_rate_ratio,
+                             double main_rate_ratio,
                              int coarse_rate_adjust) {}
 
 void dev_stream_update_frames(const struct dev_stream* dev_stream) {}
@@ -1409,12 +1412,19 @@
   cras_device_monitor_set_device_mute_state_called++;
   return 0;
 }
+int cras_device_monitor_error_close(unsigned int dev_idx) {
+  return 0;
+}
 
 int cras_iodev_drop_frames_by_time(struct cras_iodev* iodev,
                                    struct timespec ts) {
   return 0;
 }
 
+bool cras_iodev_is_on_internal_card(const struct cras_ionode* node) {
+  return 0;
+}
+
 //  From librt.
 int clock_gettime(clockid_t clk_id, struct timespec* tp) {
   *tp = clock_gettime_retspec;
diff --git a/cras/src/tests/bt_io_unittest.cc b/cras/src/tests/bt_io_unittest.cc
index ee013cf..dd02652 100644
--- a/cras/src/tests/bt_io_unittest.cc
+++ b/cras/src/tests/bt_io_unittest.cc
@@ -458,6 +458,10 @@
   return "/fake/object/path";
 }
 
+int cras_bt_device_get_stable_id(const struct cras_bt_device* device) {
+  return 123;
+}
+
 int cras_bt_device_get_use_hardware_volume(struct cras_bt_device* device) {
   return 1;
 }
diff --git a/cras/src/tests/capture_rclient_unittest.cc b/cras/src/tests/capture_rclient_unittest.cc
index b749f1a..446fddf 100644
--- a/cras/src/tests/capture_rclient_unittest.cc
+++ b/cras/src/tests/capture_rclient_unittest.cc
@@ -270,4 +270,9 @@
   return true;
 }
 
+void detect_rtc_stream_pair(struct stream_list* list,
+                            struct cras_rstream* stream) {
+  return;
+}
+
 }  // extern "C"
diff --git a/cras/src/tests/control_rclient_unittest.cc b/cras/src/tests/control_rclient_unittest.cc
index d6b63aa..63e3c8f 100644
--- a/cras/src/tests/control_rclient_unittest.cc
+++ b/cras/src/tests/control_rclient_unittest.cc
@@ -967,4 +967,11 @@
   return NULL;
 }
 
+void detect_rtc_stream_pair(struct stream_list* list,
+                            struct cras_rstream* stream) {
+  return;
+}
+
+void cras_system_set_hotword_pause_at_suspend(bool pause) {}
+
 }  // extern "C"
diff --git a/cras/src/tests/cras_abi_unittest.cc b/cras/src/tests/cras_abi_unittest.cc
new file mode 100644
index 0000000..d566a9b
--- /dev/null
+++ b/cras/src/tests/cras_abi_unittest.cc
@@ -0,0 +1,139 @@
+/* Copyright 2021 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.
+ */
+
+#include <gtest/gtest.h>
+
+extern "C" {
+#include "cras_client.c"
+#include "cras_client.h"
+
+inline int libcras_unsupported_func(struct libcras_client* client) {
+  CHECK_VERSION(client, INT_MAX);
+  return 0;
+}
+
+cras_stream_id_t cb_stream_id;
+uint8_t* cb_buf;
+unsigned int cb_frames;
+struct timespec cb_latency;
+void* cb_usr_arg;
+int get_stream_cb_called;
+struct timespec now;
+
+int get_stream_cb(struct libcras_stream_cb_data* data) {
+  get_stream_cb_called++;
+  EXPECT_NE((void*)NULL, data);
+  EXPECT_EQ(0, libcras_stream_cb_data_get_stream_id(data, &cb_stream_id));
+  EXPECT_EQ(0, libcras_stream_cb_data_get_buf(data, &cb_buf));
+  EXPECT_EQ(0, libcras_stream_cb_data_get_frames(data, &cb_frames));
+  EXPECT_EQ(0, libcras_stream_cb_data_get_latency(data, &cb_latency));
+  EXPECT_EQ(0, libcras_stream_cb_data_get_usr_arg(data, &cb_usr_arg));
+  return 0;
+}
+}
+
+namespace {
+class CrasAbiTestSuite : public testing::Test {
+ protected:
+  struct cras_audio_shm* InitShm(int frames) {
+    struct cras_audio_shm* shm =
+        static_cast<struct cras_audio_shm*>(calloc(1, sizeof(*shm)));
+    shm->header =
+        static_cast<cras_audio_shm_header*>(calloc(1, sizeof(*shm->header)));
+    cras_shm_set_frame_bytes(shm, 4);
+    uint32_t used_size = frames * 4;
+    cras_shm_set_used_size(shm, used_size);
+    shm->samples_info.length = used_size * 2;
+    memcpy(&shm->header->config, &shm->config, sizeof(shm->config));
+    return shm;
+  }
+
+  void DestroyShm(struct cras_audio_shm* shm) {
+    if (shm)
+      free(shm->header);
+    free(shm);
+  }
+
+  virtual void SetUp() { get_stream_cb_called = 0; }
+};
+
+TEST_F(CrasAbiTestSuite, CheckUnsupportedFunction) {
+  auto* client = libcras_client_create();
+  EXPECT_NE((void*)NULL, client);
+  EXPECT_EQ(-ENOSYS, libcras_unsupported_func(client));
+  libcras_client_destroy(client);
+}
+
+TEST_F(CrasAbiTestSuite, BasicStream) {
+  auto* client = libcras_client_create();
+  EXPECT_NE((void*)NULL, client);
+  auto* stream = libcras_stream_params_create();
+  EXPECT_NE((void*)NULL, stream);
+  /* Returns timeout because there is no real CRAS server in unittest. */
+  EXPECT_EQ(-ETIMEDOUT, libcras_client_connect_timeout(client, 0));
+  EXPECT_EQ(0, libcras_client_run_thread(client));
+  EXPECT_EQ(0, libcras_stream_params_set(stream, CRAS_STREAM_INPUT, 480, 480,
+                                         CRAS_STREAM_TYPE_DEFAULT,
+                                         CRAS_CLIENT_TYPE_TEST, 0, NULL, NULL,
+                                         NULL, 48000, SND_PCM_FORMAT_S16, 2));
+  cras_stream_id_t id;
+  /* Fails to add a stream because the stream callback is not set. */
+  EXPECT_EQ(-EINVAL, libcras_client_add_pinned_stream(client, 0, &id, stream));
+  /* Fails to set a stream volume because the stream is not added. */
+  EXPECT_EQ(-EINVAL, libcras_client_set_stream_volume(client, id, 1.0));
+  EXPECT_EQ(0, libcras_client_rm_stream(client, id));
+  EXPECT_EQ(0, libcras_client_stop(client));
+  libcras_stream_params_destroy(stream);
+  libcras_client_destroy(client);
+}
+
+TEST_F(CrasAbiTestSuite, StreamCallback) {
+  struct client_stream stream;
+  struct cras_stream_params params;
+  stream.id = 0x123;
+  stream.direction = CRAS_STREAM_INPUT;
+  stream.flags = 0;
+  stream.config = &params;
+  params.stream_cb = get_stream_cb;
+  params.cb_threshold = 480;
+  params.user_data = (void*)0x321;
+  stream.shm = InitShm(960);
+  stream.shm->header->write_offset[0] = 960 * 4;
+  stream.shm->header->write_buf_idx = 0;
+  stream.shm->header->read_offset[0] = 0;
+  stream.shm->header->read_buf_idx = 0;
+  now.tv_sec = 100;
+  now.tv_nsec = 0;
+  stream.shm->header->ts.tv_sec = 90;
+  stream.shm->header->ts.tv_nsec = 0;
+
+  handle_capture_data_ready(&stream, 480);
+
+  EXPECT_EQ(1, get_stream_cb_called);
+  EXPECT_EQ(stream.id, cb_stream_id);
+  EXPECT_EQ(cras_shm_get_write_buffer_base(stream.shm), cb_buf);
+  EXPECT_EQ(480, cb_frames);
+  EXPECT_EQ(10, cb_latency.tv_sec);
+  EXPECT_EQ(0, cb_latency.tv_nsec);
+  EXPECT_EQ((void*)0x321, cb_usr_arg);
+
+  DestroyShm(stream.shm);
+}
+
+}  // namespace
+
+extern "C" {
+
+int clock_gettime(clockid_t clk_id, struct timespec* tp) {
+  *tp = now;
+  return 0;
+}
+}
+
+int main(int argc, char** argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  openlog(NULL, LOG_PERROR, LOG_USER);
+  return RUN_ALL_TESTS();
+}
diff --git a/cras/src/tests/dev_io_stubs.cc b/cras/src/tests/dev_io_stubs.cc
index b74162b..d97dde5 100644
--- a/cras/src/tests/dev_io_stubs.cc
+++ b/cras/src/tests/dev_io_stubs.cc
@@ -151,6 +151,11 @@
                                static_cast<size_t>(dev->max_cb_level));
   dev->largest_cb_level = std::max(stream->rstream->cb_threshold,
                                    static_cast<size_t>(dev->max_cb_level));
+
+  if (stream->rstream->main_dev.dev_id == NO_DEVICE) {
+    stream->rstream->main_dev.dev_id = dev->info.idx;
+    stream->rstream->main_dev.dev_ptr = dev.get();
+  }
 }
 
 void fill_audio_format(cras_audio_format* format, unsigned int rate) {
diff --git a/cras/src/tests/dev_io_unittest.cc b/cras/src/tests/dev_io_unittest.cc
index 096e3ed..2dbf344 100644
--- a/cras/src/tests/dev_io_unittest.cc
+++ b/cras/src/tests/dev_io_unittest.cc
@@ -8,6 +8,7 @@
 #include <time.h>
 
 #include <memory>
+#include <unordered_map>
 
 extern "C" {
 #include "cras_iodev.h"    // stubbed
@@ -29,6 +30,13 @@
 static float dev_stream_capture_software_gain_scaler_val;
 static float input_data_get_software_gain_scaler_val;
 static unsigned int dev_stream_capture_avail_ret = 480;
+struct set_dev_rate_data {
+  unsigned int dev_rate;
+  double dev_rate_ratio;
+  double main_rate_ratio;
+  int coarse_rate_adjust;
+};
+std::unordered_map<struct dev_stream*, set_dev_rate_data> set_dev_rate_map;
 
 namespace {
 
@@ -39,6 +47,7 @@
     iodev_stub_reset();
     rstream_stub_reset();
     fill_audio_format(&format, 48000);
+    set_dev_rate_map.clear();
     stream = create_stream(1, 1, CRAS_STREAM_INPUT, cb_threshold, &format);
   }
 
@@ -70,6 +79,7 @@
 
 TEST_F(DevIoSuite, CaptureGain) {
   struct open_dev* dev_list = NULL;
+  struct open_dev* odev_list = NULL;
   struct timespec ts;
   DevicePtr dev = create_device(CRAS_STREAM_INPUT, cb_threshold, &format,
                                 CRAS_NODE_TYPE_MIC);
@@ -82,20 +92,80 @@
   /* The applied scaler gain should match what is reported by input_data. */
   dev->dev->active_node->ui_gain_scaler = 1.0f;
   input_data_get_software_gain_scaler_val = 1.0f;
-  dev_io_capture(&dev_list);
+  dev_io_capture(&dev_list, &odev_list);
   EXPECT_EQ(1.0f, dev_stream_capture_software_gain_scaler_val);
 
   input_data_get_software_gain_scaler_val = 0.99f;
-  dev_io_capture(&dev_list);
+  dev_io_capture(&dev_list, &odev_list);
   EXPECT_EQ(0.99f, dev_stream_capture_software_gain_scaler_val);
 
   dev->dev->active_node->ui_gain_scaler = 0.6f;
   input_data_get_software_gain_scaler_val = 0.7f;
-  dev_io_capture(&dev_list);
+  dev_io_capture(&dev_list, &odev_list);
   EXPECT_FLOAT_EQ(0.42f, dev_stream_capture_software_gain_scaler_val);
 }
 
 /*
+ * When input and output devices are on the internal sound card,
+ * and their device rates are the same, use the estimated rate
+ * on the output device as the estimated rate of input device.
+ */
+TEST_F(DevIoSuite, CopyOutputEstimatedRate) {
+  struct open_dev* idev_list = NULL;
+  struct open_dev* odev_list = NULL;
+  struct timespec ts;
+  DevicePtr out_dev = create_device(CRAS_STREAM_OUTPUT, cb_threshold, &format,
+                                    CRAS_NODE_TYPE_INTERNAL_SPEAKER);
+  DevicePtr in_dev = create_device(CRAS_STREAM_INPUT, cb_threshold, &format,
+                                   CRAS_NODE_TYPE_MIC);
+
+  in_dev->dev->state = CRAS_IODEV_STATE_NORMAL_RUN;
+  iodev_stub_frames_queued(in_dev->dev.get(), 20, ts);
+  DL_APPEND(idev_list, in_dev->odev.get());
+  add_stream_to_dev(in_dev->dev, stream);
+  DL_APPEND(odev_list, out_dev->odev.get());
+  iodev_stub_on_internal_card(out_dev->dev->active_node, 1);
+  iodev_stub_on_internal_card(in_dev->dev->active_node, 1);
+
+  iodev_stub_est_rate_ratio(in_dev->dev.get(), 0.8f);
+  iodev_stub_est_rate_ratio(out_dev->dev.get(), 1.2f);
+
+  dev_io_capture(&idev_list, &odev_list);
+
+  EXPECT_FLOAT_EQ(1.2f, set_dev_rate_map[stream->dstream.get()].dev_rate_ratio);
+}
+
+/*
+ * When input and output devices are not both on the internal sound card,
+ * estimated rates are independent.
+ */
+TEST_F(DevIoSuite, InputOutputIndependentEstimatedRate) {
+  struct open_dev* idev_list = NULL;
+  struct open_dev* odev_list = NULL;
+  struct timespec ts;
+  DevicePtr out_dev = create_device(CRAS_STREAM_OUTPUT, cb_threshold, &format,
+                                    CRAS_NODE_TYPE_INTERNAL_SPEAKER);
+  DevicePtr in_dev = create_device(CRAS_STREAM_INPUT, cb_threshold, &format,
+                                   CRAS_NODE_TYPE_USB);
+
+  in_dev->dev->state = CRAS_IODEV_STATE_NORMAL_RUN;
+  iodev_stub_frames_queued(in_dev->dev.get(), 20, ts);
+  DL_APPEND(idev_list, in_dev->odev.get());
+  add_stream_to_dev(in_dev->dev, stream);
+  DL_APPEND(odev_list, out_dev->odev.get());
+  iodev_stub_on_internal_card(out_dev->dev->active_node, 1);
+  iodev_stub_on_internal_card(in_dev->dev->active_node, 0);
+
+  iodev_stub_est_rate_ratio(in_dev->dev.get(), 0.8f);
+  iodev_stub_est_rate_ratio(out_dev->dev.get(), 1.2f);
+  iodev_stub_update_rate(in_dev->dev.get(), 1);
+
+  dev_io_capture(&idev_list, &odev_list);
+
+  EXPECT_FLOAT_EQ(0.8f, set_dev_rate_map[stream->dstream.get()].dev_rate_ratio);
+}
+
+/*
  * If any hw_level is larger than 1.5 * largest_cb_level and
  * DROP_FRAMES_THRESHOLD_MS, reset all input devices.
  */
@@ -332,8 +402,16 @@
 void dev_stream_set_dev_rate(struct dev_stream* dev_stream,
                              unsigned int dev_rate,
                              double dev_rate_ratio,
-                             double master_rate_ratio,
-                             int coarse_rate_adjust) {}
+                             double main_rate_ratio,
+                             int coarse_rate_adjust) {
+  set_dev_rate_data new_data;
+  new_data.dev_rate = dev_rate;
+  new_data.dev_rate_ratio = dev_rate_ratio;
+  new_data.main_rate_ratio = main_rate_ratio;
+  new_data.coarse_rate_adjust = coarse_rate_adjust;
+
+  set_dev_rate_map[dev_stream] = new_data;
+}
 int dev_stream_capture_update_rstream(struct dev_stream* dev_stream) {
   return 0;
 }
@@ -373,7 +451,11 @@
                                      unsigned int dev_id,
                                      const struct cras_audio_format* dev_fmt,
                                      void* dev_ptr,
-                                     struct timespec* cb_ts) {
+                                     struct timespec* cb_ts,
+                                     const struct timespec* sleep_interval_ts) {
+  return 0;
+}
+int cras_device_monitor_error_close(unsigned int dev_idx) {
   return 0;
 }
 }  // extern "C"
diff --git a/cras/src/tests/dev_stream_unittest.cc b/cras/src/tests/dev_stream_unittest.cc
index 640ca93..700376f 100644
--- a/cras/src/tests/dev_stream_unittest.cc
+++ b/cras/src/tests/dev_stream_unittest.cc
@@ -334,7 +334,7 @@
   out_fmt.frame_rate = 48000;  // Output from converter is device rate.
   config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
   dev_stream =
-      dev_stream_create(&rstream_, 0, &fmt_s16le_48, (void*)0x55, &cb_ts);
+      dev_stream_create(&rstream_, 0, &fmt_s16le_48, (void*)0x55, &cb_ts, NULL);
   EXPECT_EQ(1, config_format_converter_called);
   EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer);
   // Converter tmp and output buffers are large enough for device output.
@@ -346,6 +346,24 @@
   dev_stream_destroy(dev_stream);
 }
 
+TEST_F(CreateSuite, CreateOutputWithSchedule) {
+  struct dev_stream* dev_stream;
+  unsigned int dev_id = 9;
+  // init_cb_ts and non-null init_sleep_ts will be used.
+  struct timespec init_cb_ts = {1, 2};
+  struct timespec init_sleep_ts = {3, 4};
+
+  rstream_.direction = CRAS_STREAM_OUTPUT;
+  dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_48, (void*)0x55,
+                                 &init_cb_ts, &init_sleep_ts);
+
+  EXPECT_EQ(init_cb_ts.tv_sec, rstream_.next_cb_ts.tv_sec);
+  EXPECT_EQ(init_cb_ts.tv_nsec, rstream_.next_cb_ts.tv_nsec);
+  EXPECT_EQ(init_sleep_ts.tv_sec, rstream_.sleep_interval_ts.tv_sec);
+  EXPECT_EQ(init_sleep_ts.tv_nsec, rstream_.sleep_interval_ts.tv_nsec);
+  dev_stream_destroy(dev_stream);
+}
+
 TEST_F(CreateSuite, CreateSRC44from48Input) {
   struct dev_stream* dev_stream;
   struct cras_audio_format processed_fmt = fmt_s16le_48;
@@ -358,7 +376,7 @@
   config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
   cras_rstream_post_processing_format_val = &processed_fmt;
   dev_stream =
-      dev_stream_create(&rstream_, 0, &fmt_s16le_48, (void*)0x55, &cb_ts);
+      dev_stream_create(&rstream_, 0, &fmt_s16le_48, (void*)0x55, &cb_ts, NULL);
   EXPECT_EQ(1, config_format_converter_called);
   EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer);
   // Converter tmp and output buffers are large enough for device input.
@@ -378,8 +396,8 @@
   in_fmt.frame_rate = 48000;   // Stream rate.
   out_fmt.frame_rate = 44100;  // Device rate.
   config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
-  dev_stream =
-      dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void*)0x55, &cb_ts);
+  dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void*)0x55,
+                                 &cb_ts, NULL);
   EXPECT_EQ(1, config_format_converter_called);
   EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer);
   // Converter tmp and output buffers are large enough for stream input.
@@ -396,8 +414,8 @@
   in_fmt.frame_rate = 44100;   // Device rate.
   out_fmt.frame_rate = 48000;  // Stream rate.
   config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
-  dev_stream =
-      dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void*)0x55, &cb_ts);
+  dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void*)0x55,
+                                 &cb_ts, NULL);
   EXPECT_EQ(1, config_format_converter_called);
   EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer);
   // Converter tmp and output buffers are large enough for stream output.
@@ -414,7 +432,7 @@
   out_fmt.frame_rate = 48000;  // Device rate.
   config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
   dev_stream =
-      dev_stream_create(&rstream_, 0, &fmt_s16le_48, (void*)0x55, &cb_ts);
+      dev_stream_create(&rstream_, 0, &fmt_s16le_48, (void*)0x55, &cb_ts, NULL);
   EXPECT_EQ(1, config_format_converter_called);
   EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer);
   // Converter tmp and output buffers are large enough for device output.
@@ -435,7 +453,7 @@
   out_fmt.frame_rate = 8000;  // Stream rate.
   config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
   dev_stream =
-      dev_stream_create(&rstream_, 0, &fmt_s16le_48, (void*)0x55, &cb_ts);
+      dev_stream_create(&rstream_, 0, &fmt_s16le_48, (void*)0x55, &cb_ts, NULL);
   EXPECT_EQ(1, config_format_converter_called);
   EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer);
   // Converter tmp and output buffers are large enough for device input.
@@ -455,7 +473,7 @@
   out_fmt.frame_rate = 8000;  // Device rate.
   config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
   dev_stream =
-      dev_stream_create(&rstream_, 0, &fmt_s16le_8, (void*)0x55, &cb_ts);
+      dev_stream_create(&rstream_, 0, &fmt_s16le_8, (void*)0x55, &cb_ts, NULL);
   EXPECT_EQ(1, config_format_converter_called);
   EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer);
   // Converter tmp and output buffers are large enough for stream input.
@@ -473,7 +491,7 @@
   out_fmt.frame_rate = 48000;  // Stream rate.
   config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
   dev_stream =
-      dev_stream_create(&rstream_, 0, &fmt_s16le_8, (void*)0x55, &cb_ts);
+      dev_stream_create(&rstream_, 0, &fmt_s16le_8, (void*)0x55, &cb_ts, NULL);
   EXPECT_EQ(1, config_format_converter_called);
   EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer);
   // Converter tmp and output buffers are large enough for stream output.
@@ -490,8 +508,8 @@
   in_fmt.frame_rate = 44100;   // Device rate.
   out_fmt.frame_rate = 48000;  // Stream rate.
   config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
-  dev_stream =
-      dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void*)0x55, &cb_ts);
+  dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void*)0x55,
+                                 &cb_ts, NULL);
   EXPECT_EQ(1, config_format_converter_called);
   EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer);
   // Converter tmp and output buffers are large enough for stream output.
@@ -510,8 +528,8 @@
   rstream_.format = fmt_s16le_48;
   rstream_.direction = CRAS_STREAM_INPUT;
   config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
-  dev_stream =
-      dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void*)0x55, &cb_ts);
+  dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void*)0x55,
+                                 &cb_ts, NULL);
   EXPECT_EQ(1, config_format_converter_called);
   EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer);
   EXPECT_LE(
@@ -529,16 +547,16 @@
   dev_stream_destroy(dev_stream);
 }
 
-TEST_F(CreateSuite, SetDevRateNotMasterDev) {
+TEST_F(CreateSuite, SetDevRateNotMainDev) {
   struct dev_stream* dev_stream;
   unsigned int dev_id = 9;
 
   rstream_.format = fmt_s16le_48;
   rstream_.direction = CRAS_STREAM_INPUT;
-  rstream_.master_dev.dev_id = 4;
+  rstream_.main_dev.dev_id = 4;
   config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
   dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_44_1,
-                                 (void*)0x55, &cb_ts);
+                                 (void*)0x55, &cb_ts, NULL);
 
   dev_stream_set_dev_rate(dev_stream, 44100, 1.01, 1.0, 0);
   EXPECT_EQ(1, cras_fmt_conv_set_linear_resample_rates_called);
@@ -557,17 +575,17 @@
   dev_stream_destroy(dev_stream);
 }
 
-TEST_F(CreateSuite, SetDevRateMasterDev) {
+TEST_F(CreateSuite, SetDevRateMainDev) {
   struct dev_stream* dev_stream;
   unsigned int dev_id = 9;
   unsigned int expected_ts_nsec;
 
   rstream_.format = fmt_s16le_48;
   rstream_.direction = CRAS_STREAM_INPUT;
-  rstream_.master_dev.dev_id = dev_id;
+  rstream_.main_dev.dev_id = dev_id;
   config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
   dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_44_1,
-                                 (void*)0x55, &cb_ts);
+                                 (void*)0x55, &cb_ts, NULL);
 
   dev_stream_set_dev_rate(dev_stream, 44100, 1.01, 1.0, 0);
   EXPECT_EQ(1, cras_fmt_conv_set_linear_resample_rates_called);
@@ -661,7 +679,7 @@
   unsigned int dev_id = 9;
 
   dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_44_1,
-                                 (void*)0x55, &cb_ts);
+                                 (void*)0x55, &cb_ts, NULL);
 
   dev_stream_flush_old_audio_messages(dev_stream);
   EXPECT_EQ(1, cras_rstream_flush_old_audio_messages_called);
@@ -673,7 +691,7 @@
   unsigned int dev_id = 9;
 
   dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_44_1,
-                                 (void*)0x55, &cb_ts);
+                                 (void*)0x55, &cb_ts, NULL);
 
   // dev_stream_is_pending_reply is only a wrapper.
   cras_rstream_is_pending_reply_ret = 0;
@@ -694,7 +712,7 @@
 
   rstream_.direction = CRAS_STREAM_INPUT;
   dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_44_1,
-                                 (void*)0x55, &cb_ts);
+                                 (void*)0x55, &cb_ts, NULL);
 
   // Assume there is a next_cb_ts on rstream.
   rstream_.next_cb_ts.tv_sec = 1;
@@ -791,7 +809,7 @@
   rstream_.direction = CRAS_STREAM_INPUT;
   rstream_.flags |= BULK_AUDIO_OK;
   dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_44_1,
-                                 (void*)0x55, &cb_ts);
+                                 (void*)0x55, &cb_ts, NULL);
 
   // Assume there is a next_cb_ts on rstream.
   rstream_.next_cb_ts.tv_sec = 1;
@@ -864,7 +882,7 @@
 
   rstream_.direction = CRAS_STREAM_INPUT;
   dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_44_1,
-                                 (void*)0x55, &cb_ts);
+                                 (void*)0x55, &cb_ts, NULL);
   dev_stream->stream->flags = TRIGGER_ONLY;
   dev_stream->stream->triggered = 0;
 
@@ -896,7 +914,7 @@
 
   rstream_.direction = CRAS_STREAM_INPUT;
   dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_44_1,
-                                 (void*)0x55, &cb_ts);
+                                 (void*)0x55, &cb_ts, NULL);
 
   // Assume there is a next_cb_ts on rstream.
   rstream_.next_cb_ts.tv_sec = 1;
@@ -929,8 +947,8 @@
   int needed_frames_from_device = 0;
 
   rstream_.direction = CRAS_STREAM_INPUT;
-  dev_stream =
-      dev_stream_create(&rstream_, dev_id, &fmt_s16le_48, (void*)0x55, &cb_ts);
+  dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_48, (void*)0x55,
+                                 &cb_ts, NULL);
 
   // Assume there is a next_cb_ts on rstream, that is, 1.005 seconds.
   rstream_.next_cb_ts.tv_sec = 1;
@@ -994,7 +1012,7 @@
 
   rstream_.direction = CRAS_STREAM_OUTPUT;
   dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_44_1,
-                                 (void*)0x55, &cb_ts);
+                                 (void*)0x55, &cb_ts, NULL);
 
   // Case 1: The new next_cb_ts is greater than now. Do not need to reschedule.
   rstream_.next_cb_ts.tv_sec = 2;
diff --git a/cras/src/tests/fmt_conv_ops_unittest.cc b/cras/src/tests/fmt_conv_ops_unittest.cc
index ebe8b65..0baf37b 100644
--- a/cras/src/tests/fmt_conv_ops_unittest.cc
+++ b/cras/src/tests/fmt_conv_ops_unittest.cc
@@ -418,6 +418,39 @@
   }
 }
 
+// Test Quad to 5.1 conversion. S16_LE.
+TEST(FormatConverterOpsTest, QuadTo51S16LE) {
+  const size_t frames = 4096;
+  const size_t in_ch = 4;
+  const size_t out_ch = 6;
+  const unsigned int fl_quad = 0;
+  const unsigned int fr_quad = 1;
+  const unsigned int rl_quad = 2;
+  const unsigned int rr_quad = 3;
+
+  const unsigned int fl_51 = 0;
+  const unsigned int fr_51 = 1;
+  const unsigned int center_51 = 2;
+  const unsigned int lfe_51 = 3;
+  const unsigned int rl_51 = 4;
+  const unsigned int rr_51 = 5;
+
+  S16LEPtr src = CreateS16LE(frames * in_ch);
+  S16LEPtr dst = CreateS16LE(frames * out_ch);
+
+  size_t ret = s16_quad_to_51(fl_51, fr_51, rl_51, rr_51, (uint8_t*)src.get(),
+                              frames, (uint8_t*)dst.get());
+  EXPECT_EQ(ret, frames);
+  for (size_t i = 0; i < frames; ++i) {
+    EXPECT_EQ(0, dst[i * 6 + center_51]);
+    EXPECT_EQ(0, dst[i * 6 + lfe_51]);
+    EXPECT_EQ(src[i * 4 + fl_quad], dst[i * 6 + fl_51]);
+    EXPECT_EQ(src[i * 4 + fr_quad], dst[i * 6 + fr_51]);
+    EXPECT_EQ(src[i * 4 + rl_quad], dst[i * 6 + rl_51]);
+    EXPECT_EQ(src[i * 4 + rr_quad], dst[i * 6 + rr_51]);
+  }
+}
+
 // Test Stereo to 5.1 conversion.  S16_LE, LeftRight.
 TEST(FormatConverterOpsTest, StereoTo51S16LELeftRight) {
   const size_t frames = 4096;
diff --git a/cras/src/tests/hfp_alsa_iodev_unittest.cc b/cras/src/tests/hfp_alsa_iodev_unittest.cc
index c5bd4e9..8756c20 100644
--- a/cras/src/tests/hfp_alsa_iodev_unittest.cc
+++ b/cras/src/tests/hfp_alsa_iodev_unittest.cc
@@ -259,7 +259,7 @@
               hfp_alsa_io->aio->format->channel_layout[i]);
 
   EXPECT_EQ(1, fake_configure_dev_called);
-  EXPECT_EQ(0, hfp_set_call_status_called);
+  EXPECT_EQ(1, hfp_set_call_status_called);
   EXPECT_EQ(buf_size, iodev->buffer_size);
 
   hfp_alsa_iodev_destroy(iodev);
@@ -273,7 +273,7 @@
                                 CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY);
   iodev->close_dev(iodev);
 
-  EXPECT_EQ(0, hfp_set_call_status_called);
+  EXPECT_EQ(1, hfp_set_call_status_called);
   EXPECT_EQ(1, cras_iodev_free_format_called);
   EXPECT_EQ(1, fake_close_dev_called);
 
@@ -507,6 +507,10 @@
   return "/fake/object/path";
 }
 
+int cras_bt_device_get_stable_id(const struct cras_bt_device* device) {
+  return 123;
+}
+
 void cras_iodev_free_resources(struct cras_iodev* iodev) {
   cras_iodev_free_resources_called++;
 }
diff --git a/cras/src/tests/hfp_iodev_unittest.cc b/cras/src/tests/hfp_iodev_unittest.cc
index 18262bf..1275ef2 100644
--- a/cras/src/tests/hfp_iodev_unittest.cc
+++ b/cras/src/tests/hfp_iodev_unittest.cc
@@ -285,6 +285,10 @@
   return "/fake/object/path";
 }
 
+int cras_bt_device_get_stable_id(const struct cras_bt_device* device) {
+  return 123;
+}
+
 // From cras_hfp_info
 int hfp_info_add_iodev(struct hfp_info* info,
                        enum CRAS_STREAM_DIRECTION direction,
diff --git a/cras/src/tests/iodev_list_unittest.cc b/cras/src/tests/iodev_list_unittest.cc
index 272537f..8c71214 100644
--- a/cras/src/tests/iodev_list_unittest.cc
+++ b/cras/src/tests/iodev_list_unittest.cc
@@ -95,6 +95,7 @@
 static int audio_thread_disconnect_stream_called;
 static struct cras_iodev fake_sco_in_dev, fake_sco_out_dev;
 static struct cras_ionode fake_sco_in_node, fake_sco_out_node;
+static int server_state_hotword_pause_at_suspend;
 
 int dev_idx_in_vector(std::vector<unsigned int> v, unsigned int idx) {
   return std::find(v.begin(), v.end(), idx) != v.end();
@@ -238,6 +239,7 @@
     mock_empty_iodev[1].state = CRAS_IODEV_STATE_CLOSE;
     mock_empty_iodev[1].update_active_node = update_active_node;
     mock_hotword_iodev.update_active_node = update_active_node;
+    server_state_hotword_pause_at_suspend = 0;
   }
 
   virtual void TearDown() {
@@ -1942,6 +1944,105 @@
   cras_iodev_list_deinit();
 }
 
+TEST_F(IoDevTestSuite, HotwordStreamsPausedAtSystemSuspend) {
+  struct cras_rstream rstream;
+  struct cras_rstream* stream_list = NULL;
+  cras_iodev_list_init();
+
+  node1.type = CRAS_NODE_TYPE_HOTWORD;
+  d1_.direction = CRAS_STREAM_INPUT;
+  EXPECT_EQ(0, cras_iodev_list_add_input(&d1_));
+
+  d1_.format = &fmt_;
+
+  memset(&rstream, 0, sizeof(rstream));
+  rstream.is_pinned = 1;
+  rstream.pinned_dev_idx = d1_.info.idx;
+  rstream.flags = HOTWORD_STREAM;
+
+  /* Add a hotword stream. */
+  EXPECT_EQ(0, stream_add_cb(&rstream));
+  EXPECT_EQ(1, audio_thread_add_stream_called);
+  EXPECT_EQ(&d1_, audio_thread_add_stream_dev);
+  EXPECT_EQ(&rstream, audio_thread_add_stream_stream);
+
+  DL_APPEND(stream_list, &rstream);
+  stream_list_get_ret = stream_list;
+
+  server_state_hotword_pause_at_suspend = 1;
+
+  /* Trigger system suspend. Verify hotword stream is moved to empty dev. */
+  observer_ops->suspend_changed(NULL, 1);
+  EXPECT_EQ(1, audio_thread_disconnect_stream_called);
+  EXPECT_EQ(&rstream, audio_thread_disconnect_stream_stream);
+  EXPECT_EQ(&d1_, audio_thread_disconnect_stream_dev);
+  EXPECT_EQ(2, audio_thread_add_stream_called);
+  EXPECT_EQ(&rstream, audio_thread_add_stream_stream);
+  EXPECT_EQ(&mock_hotword_iodev, audio_thread_add_stream_dev);
+
+  /* Trigger system resume. Verify hotword stream is moved to real dev.*/
+  observer_ops->suspend_changed(NULL, 0);
+  EXPECT_EQ(2, audio_thread_disconnect_stream_called);
+  EXPECT_EQ(&rstream, audio_thread_disconnect_stream_stream);
+  EXPECT_EQ(&mock_hotword_iodev, audio_thread_disconnect_stream_dev);
+  EXPECT_EQ(3, audio_thread_add_stream_called);
+  EXPECT_EQ(&rstream, audio_thread_add_stream_stream);
+  EXPECT_EQ(&d1_, audio_thread_add_stream_dev);
+
+  server_state_hotword_pause_at_suspend = 0;
+  audio_thread_disconnect_stream_called = 0;
+  audio_thread_add_stream_called = 0;
+
+  /* Trigger system suspend. Verify hotword stream is not touched. */
+  observer_ops->suspend_changed(NULL, 1);
+  EXPECT_EQ(0, audio_thread_disconnect_stream_called);
+  EXPECT_EQ(0, audio_thread_add_stream_called);
+
+  /* Trigger system resume. Verify hotword stream is not touched.*/
+  observer_ops->suspend_changed(NULL, 0);
+  EXPECT_EQ(0, audio_thread_disconnect_stream_called);
+  EXPECT_EQ(0, audio_thread_add_stream_called);
+
+  cras_iodev_list_deinit();
+}
+
+TEST_F(IoDevTestSuite, SetNoiseCancellation) {
+  struct cras_rstream rstream;
+  struct cras_rstream* stream_list = NULL;
+  int rc;
+
+  memset(&rstream, 0, sizeof(rstream));
+
+  cras_iodev_list_init();
+
+  d1_.direction = CRAS_STREAM_INPUT;
+  rc = cras_iodev_list_add_input(&d1_);
+  ASSERT_EQ(0, rc);
+
+  d1_.format = &fmt_;
+
+  rstream.direction = CRAS_STREAM_INPUT;
+
+  audio_thread_add_open_dev_called = 0;
+  audio_thread_rm_open_dev_called = 0;
+  cras_iodev_list_add_active_node(CRAS_STREAM_INPUT,
+                                  cras_make_node_id(d1_.info.idx, 1));
+  DL_APPEND(stream_list, &rstream);
+  stream_add_cb(&rstream);
+  stream_list_get_ret = stream_list;
+  EXPECT_EQ(1, audio_thread_add_stream_called);
+  EXPECT_EQ(1, audio_thread_add_open_dev_called);
+
+  // reset_for_noise_cancellation causes device suspend & resume
+  // While suspending d1_: rm d1_, open fallback
+  // While resuming d1_: rm fallback, open d1_
+  cras_iodev_list_reset_for_noise_cancellation();
+  EXPECT_EQ(3, audio_thread_add_open_dev_called);
+  EXPECT_EQ(2, audio_thread_rm_open_dev_called);
+
+  cras_iodev_list_deinit();
+}
+
 }  //  namespace
 
 int main(int argc, char** argv) {
@@ -1964,6 +2065,10 @@
   return system_get_mute_return;
 }
 
+bool cras_system_get_noise_cancellation_enabled() {
+  return false;
+}
+
 struct audio_thread* audio_thread_create() {
   return &thread;
 }
@@ -2103,6 +2208,10 @@
   set_node_plugged_called++;
 }
 
+bool cras_iodev_support_noise_cancellation(const struct cras_iodev* iodev) {
+  return true;
+}
+
 int cras_iodev_start_volume_ramp(struct cras_iodev* odev,
                                  unsigned int old_volume,
                                  unsigned int new_volume) {
@@ -2246,4 +2355,8 @@
   return 0;
 }
 
+bool cras_system_get_hotword_pause_at_suspend() {
+  return !!server_state_hotword_pause_at_suspend;
+}
+
 }  // extern "C"
diff --git a/cras/src/tests/iodev_stub.cc b/cras/src/tests/iodev_stub.cc
index 3dbb61d..2e84faa 100644
--- a/cras/src/tests/iodev_stub.cc
+++ b/cras/src/tests/iodev_stub.cc
@@ -21,12 +21,30 @@
 std::unordered_map<cras_iodev*, cb_data> frames_queued_map;
 std::unordered_map<cras_iodev*, cb_data> valid_frames_map;
 std::unordered_map<cras_iodev*, timespec> drop_time_map;
+std::unordered_map<const cras_iodev*, double> est_rate_ratio_map;
+std::unordered_map<const cras_iodev*, int> update_rate_map;
+std::unordered_map<const cras_ionode*, int> on_internal_card_map;
 }  // namespace
 
 void iodev_stub_reset() {
   frames_queued_map.clear();
   valid_frames_map.clear();
   drop_time_map.clear();
+  est_rate_ratio_map.clear();
+  update_rate_map.clear();
+  on_internal_card_map.clear();
+}
+
+void iodev_stub_est_rate_ratio(cras_iodev* iodev, double ratio) {
+  est_rate_ratio_map.insert({iodev, ratio});
+}
+
+void iodev_stub_update_rate(cras_iodev* iodev, int data) {
+  update_rate_map.insert({iodev, data});
+}
+
+void iodev_stub_on_internal_card(cras_ionode* node, int data) {
+  on_internal_card_map.insert({node, data});
 }
 
 void iodev_stub_frames_queued(cras_iodev* iodev, int ret, timespec ts) {
@@ -67,7 +85,11 @@
 }
 
 double cras_iodev_get_est_rate_ratio(const struct cras_iodev* iodev) {
-  return 1.0;
+  auto elem = est_rate_ratio_map.find(iodev);
+  if (elem != est_rate_ratio_map.end()) {
+    return elem->second;
+  }
+  return 1.0f;
 }
 
 int cras_iodev_get_dsp_delay(const struct cras_iodev* iodev) {
@@ -93,6 +115,10 @@
 int cras_iodev_update_rate(struct cras_iodev* iodev,
                            unsigned int level,
                            struct timespec* level_tstamp) {
+  auto elem = update_rate_map.find(iodev);
+  if (elem != update_rate_map.end()) {
+    return elem->second;
+  }
   return 0;
 }
 
@@ -188,4 +214,12 @@
   drop_time_map.insert({iodev, ts});
   return 0;
 }
+
+bool cras_iodev_is_on_internal_card(const struct cras_ionode* node) {
+  auto elem = on_internal_card_map.find(node);
+  if (elem != on_internal_card_map.end()) {
+    return elem->second;
+  }
+  return 1;
+}
 }  // extern "C"
diff --git a/cras/src/tests/iodev_stub.h b/cras/src/tests/iodev_stub.h
index dde1b9f..e8016dd 100644
--- a/cras/src/tests/iodev_stub.h
+++ b/cras/src/tests/iodev_stub.h
@@ -10,6 +10,12 @@
 
 void iodev_stub_reset();
 
+void iodev_stub_est_rate_ratio(cras_iodev* iodev, double ratio);
+
+void iodev_stub_update_rate(cras_iodev* iodev, int data);
+
+void iodev_stub_on_internal_card(cras_ionode* node, int data);
+
 void iodev_stub_frames_queued(cras_iodev* iodev, int ret, timespec ts);
 
 void iodev_stub_valid_frames(cras_iodev* iodev, int ret, timespec ts);
diff --git a/cras/src/tests/iodev_unittest.cc b/cras/src/tests/iodev_unittest.cc
index 21dc4d5..24b2b38 100644
--- a/cras/src/tests/iodev_unittest.cc
+++ b/cras/src/tests/iodev_unittest.cc
@@ -2404,6 +2404,20 @@
   EXPECT_EQ(1, cras_audio_thread_event_dev_overrun_called);
 }
 
+TEST(IoDev, OnInternalCard) {
+  static struct cras_ionode node;
+  node.type = CRAS_NODE_TYPE_INTERNAL_SPEAKER;
+  EXPECT_EQ(1, cras_iodev_is_on_internal_card(&node));
+  node.type = CRAS_NODE_TYPE_HEADPHONE;
+  EXPECT_EQ(1, cras_iodev_is_on_internal_card(&node));
+  node.type = CRAS_NODE_TYPE_MIC;
+  EXPECT_EQ(1, cras_iodev_is_on_internal_card(&node));
+  node.type = CRAS_NODE_TYPE_USB;
+  EXPECT_EQ(0, cras_iodev_is_on_internal_card(&node));
+  node.type = CRAS_NODE_TYPE_BLUETOOTH;
+  EXPECT_EQ(0, cras_iodev_is_on_internal_card(&node));
+}
+
 extern "C" {
 
 struct main_thread_event_log* main_log;
diff --git a/cras/src/tests/playback_rclient_unittest.cc b/cras/src/tests/playback_rclient_unittest.cc
index 75cbe55..31ceda7 100644
--- a/cras/src/tests/playback_rclient_unittest.cc
+++ b/cras/src/tests/playback_rclient_unittest.cc
@@ -300,4 +300,9 @@
   return audio_format_valid;
 }
 
+void detect_rtc_stream_pair(struct stream_list* list,
+                            struct cras_rstream* stream) {
+  return;
+}
+
 }  // extern "C"
diff --git a/cras/src/tests/rstream_unittest.cc b/cras/src/tests/rstream_unittest.cc
index 593c805..d8dae24 100644
--- a/cras/src/tests/rstream_unittest.cc
+++ b/cras/src/tests/rstream_unittest.cc
@@ -32,6 +32,7 @@
 
     config_.stream_id = 555;
     config_.stream_type = CRAS_STREAM_TYPE_DEFAULT;
+    config_.client_type = CRAS_CLIENT_TYPE_UNKNOWN;
     config_.direction = CRAS_STREAM_OUTPUT;
     config_.dev_idx = NO_DEVICE;
     config_.flags = 0;
diff --git a/cras/src/tests/server_metrics_unittest.cc b/cras/src/tests/server_metrics_unittest.cc
index fe80e26..e23906e 100644
--- a/cras/src/tests/server_metrics_unittest.cc
+++ b/cras/src/tests/server_metrics_unittest.cc
@@ -132,20 +132,6 @@
   EXPECT_EQ(sent_msgs[0].data.value, hw_level);
 }
 
-TEST(ServerMetricsTestSuite, SetMetricsLongestFetchDelay) {
-  ResetStubData();
-  unsigned int delay = 100;
-
-  cras_server_metrics_longest_fetch_delay(delay);
-
-  EXPECT_EQ(sent_msgs.size(), 1);
-  EXPECT_EQ(sent_msgs[0].header.type, CRAS_MAIN_METRICS);
-  EXPECT_EQ(sent_msgs[0].header.length,
-            sizeof(struct cras_server_metrics_message));
-  EXPECT_EQ(sent_msgs[0].metrics_type, LONGEST_FETCH_DELAY);
-  EXPECT_EQ(sent_msgs[0].data.value, delay);
-}
-
 TEST(ServerMetricsTestSuite, SetMetricsNumUnderruns) {
   ResetStubData();
   unsigned int underrun = 10;
@@ -283,13 +269,18 @@
   stream.num_missed_cb = 5;
   stream.first_missed_cb_ts.tv_sec = 100;
   stream.first_missed_cb_ts.tv_nsec = 0;
+  stream.longest_fetch_interval.tv_sec = 1;
+  stream.longest_fetch_interval.tv_nsec = 0;
+  stream.sleep_interval_ts.tv_sec = 0;
+  stream.sleep_interval_ts.tv_nsec = 5000000;
 
   stream.direction = CRAS_STREAM_INPUT;
   stream.client_type = CRAS_CLIENT_TYPE_TEST;
+  stream.stream_type = CRAS_STREAM_TYPE_DEFAULT;
   cras_server_metrics_stream_destroy(&stream);
 
   subtract_timespecs(&clock_gettime_retspec, &stream.start_ts, &diff_ts);
-  EXPECT_EQ(sent_msgs.size(), 3);
+  EXPECT_EQ(sent_msgs.size(), 4);
 
   // Log missed cb frequency.
   EXPECT_EQ(sent_msgs[0].header.type, CRAS_MAIN_METRICS);
@@ -315,9 +306,23 @@
   EXPECT_EQ(sent_msgs[2].header.length,
             sizeof(struct cras_server_metrics_message));
   EXPECT_EQ(sent_msgs[2].metrics_type, STREAM_RUNTIME);
-  EXPECT_EQ(sent_msgs[2].data.stream_data.type, CRAS_CLIENT_TYPE_TEST);
+  EXPECT_EQ(sent_msgs[2].data.stream_data.client_type, CRAS_CLIENT_TYPE_TEST);
+  EXPECT_EQ(sent_msgs[2].data.stream_data.stream_type,
+            CRAS_STREAM_TYPE_DEFAULT);
   EXPECT_EQ(sent_msgs[2].data.stream_data.direction, CRAS_STREAM_INPUT);
   EXPECT_EQ(sent_msgs[2].data.stream_data.runtime.tv_sec, 1000);
+
+  // Log longest fetch delay.
+  EXPECT_EQ(sent_msgs[3].header.type, CRAS_MAIN_METRICS);
+  EXPECT_EQ(sent_msgs[3].header.length,
+            sizeof(struct cras_server_metrics_message));
+  EXPECT_EQ(sent_msgs[3].metrics_type, LONGEST_FETCH_DELAY);
+  EXPECT_EQ(sent_msgs[3].data.stream_data.client_type, CRAS_CLIENT_TYPE_TEST);
+  EXPECT_EQ(sent_msgs[3].data.stream_data.stream_type,
+            CRAS_STREAM_TYPE_DEFAULT);
+  EXPECT_EQ(sent_msgs[3].data.stream_data.direction, CRAS_STREAM_INPUT);
+  EXPECT_EQ(sent_msgs[3].data.stream_data.runtime.tv_sec, 0);
+  EXPECT_EQ(sent_msgs[3].data.stream_data.runtime.tv_nsec, 995000000);
 }
 
 TEST(ServerMetricsTestSuite, SetMetricsBusyloop) {
diff --git a/cras/src/tests/stream_list_unittest.cc b/cras/src/tests/stream_list_unittest.cc
index 40be35d..500774f 100644
--- a/cras/src/tests/stream_list_unittest.cc
+++ b/cras/src/tests/stream_list_unittest.cc
@@ -37,6 +37,10 @@
   (*stream)->direction = stream_config->direction;
   if (stream_config->format)
     (*stream)->format = *(stream_config->format);
+  (*stream)->cb_threshold = stream_config->cb_threshold;
+  (*stream)->client_type = stream_config->client_type;
+  (*stream)->stream_type = stream_config->stream_type;
+  clock_gettime(CLOCK_MONOTONIC_RAW, &(*stream)->start_ts);
 
   return 0;
 }
@@ -129,6 +133,68 @@
   stream_list_destroy(l);
 }
 
+TEST(StreamList, DetectRtcStreamPair) {
+  struct stream_list* l;
+  struct cras_rstream *s1, *s2, *s3, *s4;
+  struct cras_rstream_config s1_config, s2_config, s3_config, s4_config;
+
+  s1_config.stream_id = 0x5001;
+  s1_config.direction = CRAS_STREAM_OUTPUT;
+  s1_config.cb_threshold = 480;
+  s1_config.client_type = CRAS_CLIENT_TYPE_CHROME;
+  s1_config.stream_type = CRAS_STREAM_TYPE_DEFAULT;
+  s1_config.format = NULL;
+
+  s2_config.stream_id = 0x5002;
+  s2_config.direction = CRAS_STREAM_INPUT;
+  s2_config.cb_threshold = 480;
+  s2_config.client_type = CRAS_CLIENT_TYPE_CHROME;
+  s2_config.stream_type = CRAS_STREAM_TYPE_DEFAULT;
+  s2_config.format = NULL;
+
+  // s3 is not a RTC stream because the cb threshold is not 480.
+  s3_config.stream_id = 0x5003;
+  s3_config.direction = CRAS_STREAM_INPUT;
+  s3_config.cb_threshold = 500;
+  s3_config.client_type = CRAS_CLIENT_TYPE_CHROME;
+  s3_config.stream_type = CRAS_STREAM_TYPE_DEFAULT;
+  s3_config.format = NULL;
+
+  // s4 is not a RTC stream because it is not from the same client with s1.
+  s4_config.stream_id = 0x5004;
+  s4_config.direction = CRAS_STREAM_INPUT;
+  s4_config.cb_threshold = 480;
+  s4_config.client_type = CRAS_CLIENT_TYPE_LACROS;
+  s4_config.stream_type = CRAS_STREAM_TYPE_DEFAULT;
+  s4_config.format = NULL;
+
+  reset_test_data();
+  l = stream_list_create(added_cb, removed_cb, create_rstream_cb,
+                         destroy_rstream_cb, NULL);
+  stream_list_add(l, &s1_config, &s1);
+  EXPECT_EQ(1, add_called);
+  EXPECT_EQ(1, create_called);
+  EXPECT_EQ(&s1_config, create_config);
+
+  stream_list_add(l, &s2_config, &s2);
+  detect_rtc_stream_pair(l, s2);
+  stream_list_add(l, &s3_config, &s3);
+  detect_rtc_stream_pair(l, s3);
+  stream_list_add(l, &s4_config, &s4);
+  detect_rtc_stream_pair(l, s4);
+
+  EXPECT_EQ(CRAS_STREAM_TYPE_VOICE_COMMUNICATION, s1->stream_type);
+  EXPECT_EQ(CRAS_STREAM_TYPE_VOICE_COMMUNICATION, s2->stream_type);
+  EXPECT_EQ(CRAS_STREAM_TYPE_DEFAULT, s3->stream_type);
+  EXPECT_EQ(CRAS_STREAM_TYPE_DEFAULT, s4->stream_type);
+
+  EXPECT_EQ(0, stream_list_rm(l, 0x5001));
+  EXPECT_EQ(0, stream_list_rm(l, 0x5002));
+  EXPECT_EQ(0, stream_list_rm(l, 0x5003));
+  EXPECT_EQ(0, stream_list_rm(l, 0x5004));
+  stream_list_destroy(l);
+}
+
 extern "C" {
 
 struct cras_timer* cras_tm_create_timer(struct cras_tm* tm,
diff --git a/cras/src/tests/system_state_unittest.cc b/cras/src/tests/system_state_unittest.cc
index 0450df3..45224bc 100644
--- a/cras/src/tests/system_state_unittest.cc
+++ b/cras/src/tests/system_state_unittest.cc
@@ -39,7 +39,9 @@
 static size_t cras_observer_notify_suspend_changed_called;
 static size_t cras_observer_notify_num_active_streams_called;
 static size_t cras_observer_notify_input_streams_with_permission_called;
+static size_t cras_iodev_list_reset_for_noise_cancellation_called;
 static struct cras_board_config fake_board_config;
+static size_t cras_alert_process_all_pending_alerts_called;
 
 static void ResetStubData() {
   cras_alsa_card_create_called = 0;
@@ -60,6 +62,8 @@
   cras_observer_notify_suspend_changed_called = 0;
   cras_observer_notify_num_active_streams_called = 0;
   cras_observer_notify_input_streams_with_permission_called = 0;
+  cras_alert_process_all_pending_alerts_called = 0;
+  cras_iodev_list_reset_for_noise_cancellation_called = 0;
   memset(&fake_board_config, 0, sizeof(fake_board_config));
 }
 
@@ -275,6 +279,7 @@
 
   cras_system_set_suspended(1);
   EXPECT_EQ(1, cras_observer_notify_suspend_changed_called);
+  EXPECT_EQ(1, cras_alert_process_all_pending_alerts_called);
   EXPECT_EQ(1, cras_system_get_suspended());
 
   cras_system_set_suspended(0);
@@ -431,6 +436,33 @@
   cras_system_state_deinit();
 }
 
+TEST(SystemStateSuite, SetNoiseCancellationEnabled) {
+  ResetStubData();
+  do_sys_init();
+
+  EXPECT_EQ(0, cras_system_get_noise_cancellation_enabled());
+
+  cras_system_set_noise_cancellation_enabled(0);
+  EXPECT_EQ(0, cras_system_get_noise_cancellation_enabled());
+  EXPECT_EQ(0, cras_iodev_list_reset_for_noise_cancellation_called);
+
+  cras_system_set_noise_cancellation_enabled(1);
+  EXPECT_EQ(1, cras_system_get_noise_cancellation_enabled());
+  EXPECT_EQ(1, cras_iodev_list_reset_for_noise_cancellation_called);
+
+  cras_system_set_noise_cancellation_enabled(1);
+  EXPECT_EQ(1, cras_system_get_noise_cancellation_enabled());
+  // cras_iodev_list_reset_for_noise_cancellation shouldn't be called if state
+  // is already enabled/disabled.
+  EXPECT_EQ(1, cras_iodev_list_reset_for_noise_cancellation_called);
+
+  cras_system_set_noise_cancellation_enabled(0);
+  EXPECT_EQ(0, cras_system_get_noise_cancellation_enabled());
+  EXPECT_EQ(2, cras_iodev_list_reset_for_noise_cancellation_called);
+
+  cras_system_state_deinit();
+}
+
 extern "C" {
 
 struct cras_alsa_card* cras_alsa_card_create(
@@ -527,6 +559,14 @@
   *board_config = fake_board_config;
 }
 
+void cras_alert_process_all_pending_alerts() {
+  cras_alert_process_all_pending_alerts_called++;
+}
+
+void cras_iodev_list_reset_for_noise_cancellation() {
+  cras_iodev_list_reset_for_noise_cancellation_called++;
+}
+
 }  // extern "C"
 }  // namespace
 
diff --git a/cras/src/tests/timing_unittest.cc b/cras/src/tests/timing_unittest.cc
index 8a2de65..964f30c 100644
--- a/cras/src/tests/timing_unittest.cc
+++ b/cras/src/tests/timing_unittest.cc
@@ -111,20 +111,21 @@
 
 // Add a new input stream, make sure the initial next_cb_ts is 0.
 TEST_F(TimingSuite, NewInputStreamInit) {
-  struct open_dev* dev_list_ = NULL;
+  struct open_dev* odev_list_ = NULL;
+  struct open_dev* idev_list_ = NULL;
 
   cras_audio_format format;
   fill_audio_format(&format, 48000);
   DevicePtr dev =
       create_device(CRAS_STREAM_INPUT, 1024, &format, CRAS_NODE_TYPE_MIC);
-  DL_APPEND(dev_list_, dev->odev.get());
+  DL_APPEND(idev_list_, dev->odev.get());
   struct cras_iodev* iodev = dev->odev->dev;
 
   ShmPtr shm = create_shm(480);
   RstreamPtr rstream =
       create_rstream(1, CRAS_STREAM_INPUT, 480, &format, shm.get());
 
-  dev_io_append_stream(&dev_list_, rstream.get(), &iodev, 1);
+  dev_io_append_stream(&odev_list_, &idev_list_, rstream.get(), &iodev, 1);
 
   EXPECT_EQ(0, rstream->next_cb_ts.tv_sec);
   EXPECT_EQ(0, rstream->next_cb_ts.tv_nsec);
@@ -806,23 +807,68 @@
 
 // When a new output stream is added, there are two rules to determine the
 // initial next_cb_ts.
-// 1. If the device already has streams, the next_cb_ts will be the earliest
+// 1. If there is a matched input stream, use the next_cb_ts and
+//    sleep_interval_ts from that input stream as the initial values.
+// 2. If the device already has streams, the next_cb_ts will be the earliest
 // next callback time from these streams.
-// 2. If there are no other streams, the next_cb_ts will be set to the time
+// 3. If there are no other streams, the next_cb_ts will be set to the time
 // when the valid frames in device is lower than cb_threshold. (If it is
 // already lower than cb_threshold, set next_cb_ts to now.)
 
 // Test rule 1.
+// There is a matched input stream. The next_cb_ts of the newly added output
+// stream will use the next_cb_ts from the input stream.
+TEST_F(TimingSuite, NewOutputStreamInitExistMatchedStream) {
+  struct open_dev* odev_list_ = NULL;
+  struct open_dev* idev_list_ = NULL;
+
+  cras_audio_format format;
+  fill_audio_format(&format, 48000);
+  DevicePtr out_dev = create_device(CRAS_STREAM_OUTPUT, 1024, &format,
+                                    CRAS_NODE_TYPE_HEADPHONE);
+  DL_APPEND(odev_list_, out_dev->odev.get());
+  struct cras_iodev* out_iodev = out_dev->odev->dev;
+
+  DevicePtr in_dev =
+      create_device(CRAS_STREAM_INPUT, 1024, &format, CRAS_NODE_TYPE_MIC);
+  DL_APPEND(idev_list_, in_dev->odev.get());
+
+  StreamPtr in_stream = create_stream(1, 1, CRAS_STREAM_INPUT, 480, &format);
+  add_stream_to_dev(in_dev->dev, in_stream);
+  in_stream->rstream->next_cb_ts.tv_sec = 54321;
+  in_stream->rstream->next_cb_ts.tv_nsec = 12345;
+  in_stream->rstream->sleep_interval_ts.tv_sec = 321;
+  in_stream->rstream->sleep_interval_ts.tv_nsec = 123;
+
+  ShmPtr shm = create_shm(480);
+  RstreamPtr rstream =
+      create_rstream(1, CRAS_STREAM_OUTPUT, 480, &format, shm.get());
+
+  dev_io_append_stream(&odev_list_, &idev_list_, rstream.get(), &out_iodev, 1);
+
+  EXPECT_EQ(in_stream->rstream->next_cb_ts.tv_sec, rstream->next_cb_ts.tv_sec);
+  EXPECT_EQ(in_stream->rstream->next_cb_ts.tv_nsec,
+            rstream->next_cb_ts.tv_nsec);
+  EXPECT_EQ(in_stream->rstream->sleep_interval_ts.tv_sec,
+            rstream->sleep_interval_ts.tv_sec);
+  EXPECT_EQ(in_stream->rstream->sleep_interval_ts.tv_nsec,
+            rstream->sleep_interval_ts.tv_nsec);
+
+  dev_stream_destroy(out_iodev->streams);
+}
+
+// Test rule 2.
 // The device already has streams, the next_cb_ts will be the earliest
 // next_cb_ts from these streams.
 TEST_F(TimingSuite, NewOutputStreamInitStreamInDevice) {
-  struct open_dev* dev_list_ = NULL;
+  struct open_dev* odev_list_ = NULL;
+  struct open_dev* idev_list_ = NULL;
 
   cras_audio_format format;
   fill_audio_format(&format, 48000);
   DevicePtr dev = create_device(CRAS_STREAM_OUTPUT, 1024, &format,
                                 CRAS_NODE_TYPE_HEADPHONE);
-  DL_APPEND(dev_list_, dev->odev.get());
+  DL_APPEND(odev_list_, dev->odev.get());
   struct cras_iodev* iodev = dev->odev->dev;
 
   StreamPtr stream = create_stream(1, 1, CRAS_STREAM_OUTPUT, 480, &format);
@@ -834,7 +880,7 @@
   RstreamPtr rstream =
       create_rstream(1, CRAS_STREAM_OUTPUT, 480, &format, shm.get());
 
-  dev_io_append_stream(&dev_list_, rstream.get(), &iodev, 1);
+  dev_io_append_stream(&odev_list_, &idev_list_, rstream.get(), &iodev, 1);
 
   EXPECT_EQ(stream->rstream->next_cb_ts.tv_sec, rstream->next_cb_ts.tv_sec);
   EXPECT_EQ(stream->rstream->next_cb_ts.tv_nsec, rstream->next_cb_ts.tv_nsec);
@@ -842,17 +888,18 @@
   dev_stream_destroy(iodev->streams->next);
 }
 
-// Test rule 2.
+// Test rule 3.
 // The there are no streams and no frames in device buffer. The next_cb_ts
 // will be set to now.
 TEST_F(TimingSuite, NewOutputStreamInitNoStreamNoFramesInDevice) {
-  struct open_dev* dev_list_ = NULL;
+  struct open_dev* odev_list_ = NULL;
+  struct open_dev* idev_list_ = NULL;
 
   cras_audio_format format;
   fill_audio_format(&format, 48000);
   DevicePtr dev = create_device(CRAS_STREAM_OUTPUT, 1024, &format,
                                 CRAS_NODE_TYPE_HEADPHONE);
-  DL_APPEND(dev_list_, dev->odev.get());
+  DL_APPEND(odev_list_, dev->odev.get());
   struct cras_iodev* iodev = dev->odev->dev;
 
   struct timespec start;
@@ -862,7 +909,7 @@
   RstreamPtr rstream =
       create_rstream(1, CRAS_STREAM_OUTPUT, 480, &format, shm.get());
 
-  dev_io_append_stream(&dev_list_, rstream.get(), &iodev, 1);
+  dev_io_append_stream(&odev_list_, &idev_list_, rstream.get(), &iodev, 1);
 
   EXPECT_EQ(start.tv_sec, rstream->next_cb_ts.tv_sec);
   EXPECT_EQ(start.tv_nsec, rstream->next_cb_ts.tv_nsec);
@@ -875,13 +922,14 @@
 // next_cb_ts will be set to the time that valid frames in device is lower
 // than cb_threshold.
 TEST_F(TimingSuite, NewOutputStreamInitNoStreamSomeFramesInDevice) {
-  struct open_dev* dev_list_ = NULL;
+  struct open_dev* odev_list_ = NULL;
+  struct open_dev* idev_list_ = NULL;
 
   cras_audio_format format;
   fill_audio_format(&format, 48000);
   DevicePtr dev = create_device(CRAS_STREAM_OUTPUT, 1024, &format,
                                 CRAS_NODE_TYPE_HEADPHONE);
-  DL_APPEND(dev_list_, dev->odev.get());
+  DL_APPEND(odev_list_, dev->odev.get());
   struct cras_iodev* iodev = dev->odev->dev;
 
   struct timespec start;
@@ -893,7 +941,7 @@
   RstreamPtr rstream =
       create_rstream(1, CRAS_STREAM_OUTPUT, 480, &format, shm.get());
 
-  dev_io_append_stream(&dev_list_, rstream.get(), &iodev, 1);
+  dev_io_append_stream(&odev_list_, &idev_list_, rstream.get(), &iodev, 1);
 
   // The next_cb_ts should be 10ms from now. At that time there are
   // only 480 valid frames in the device.
diff --git a/cras/src/tools/cras_test_client/cras_test_client.c b/cras/src/tools/cras_test_client/cras_test_client.c
index 5a7b3e0..7a85185 100644
--- a/cras/src/tools/cras_test_client/cras_test_client.c
+++ b/cras/src/tools/cras_test_client/cras_test_client.c
@@ -1017,10 +1017,13 @@
 		printf("%-30s dir %u codec id %u\n", "CODEC_SELECTION", data1,
 		       data2);
 		break;
-	case BT_DEV_CONNECTED_CHANGE:
-		printf("%-30s supported profiles 0x%.2x now %s\n",
-		       "DEV_CONNECTED_CHANGE", data1,
-		       data2 ? "connected" : "disconnected");
+	case BT_DEV_CONNECTED:
+		printf("%-30s supported profiles 0x%.2x stable_id 0x%08x\n",
+		       "DEV_CONNECTED", data1, data2);
+		break;
+	case BT_DEV_DISCONNECTED:
+		printf("%-30s supported profiles 0x%.2x stable_id 0x%08x\n",
+		       "DEV_DISCONNECTED", data1, data2);
 		break;
 	case BT_DEV_CONN_WATCH_CB:
 		printf("%-30s %u retries left, supported profiles 0x%.2x\n",
diff --git a/cros_alsa/src/card.rs b/cros_alsa/src/card.rs
index fa88018..42beef2 100644
--- a/cros_alsa/src/card.rs
+++ b/cros_alsa/src/card.rs
@@ -10,6 +10,7 @@
 use crate::control::{self, Control};
 use crate::control_primitive;
 use crate::control_primitive::{Ctl, ElemId, ElemIface};
+use crate::control_tlv::{self, ControlTLV};
 
 pub type Result<T> = std::result::Result<T, Error>;
 
@@ -21,6 +22,8 @@
     AlsaControlAPI(control_primitive::Error),
     /// Error occurs in Control.
     Control(control::Error),
+    /// Error occurs in ControlTLV.
+    ControlTLV(control_tlv::Error),
 }
 
 impl error::Error for Error {}
@@ -31,6 +34,12 @@
     }
 }
 
+impl From<control_tlv::Error> for Error {
+    fn from(err: control_tlv::Error) -> Error {
+        Error::ControlTLV(err)
+    }
+}
+
 impl From<control_primitive::Error> for Error {
     fn from(err: control_primitive::Error) -> Error {
         Error::AlsaControlAPI(err)
@@ -43,11 +52,13 @@
         match self {
             AlsaControlAPI(e) => write!(f, "{}", e),
             Control(e) => write!(f, "{}", e),
+            ControlTLV(e) => write!(f, "{}", e),
         }
     }
 }
 
 /// `Card` represents a sound card.
+#[derive(Debug)]
 pub struct Card {
     handle: Ctl,
     name: String,
@@ -82,7 +93,7 @@
     /// # Errors
     ///
     /// * If control name is an invalid CString.
-    /// * If control dose not exist.
+    /// * If control does not exist.
     /// * If `Control` elem_type() mismatches the type of underlying mixer control.
     /// * If `Control` size() mismatches the number of value entries of underlying mixer control.
     pub fn control_by_name<'a, T: 'a>(&'a mut self, control_name: &str) -> Result<T>
@@ -92,4 +103,15 @@
         let id = ElemId::new(ElemIface::Mixer, control_name)?;
         Ok(T::from(&mut self.handle, id)?)
     }
+
+    /// Creates a `ControlTLV` from control name.
+    ///
+    /// # Errors
+    ///
+    /// * If control name is an invalid CString.
+    /// * If control does not exist.
+    pub fn control_tlv_by_name<'a>(&'a mut self, control_name: &str) -> Result<ControlTLV<'a>> {
+        let id = ElemId::new(ElemIface::Mixer, control_name)?;
+        Ok(ControlTLV::new(&mut self.handle, id)?)
+    }
 }
diff --git a/cros_alsa/src/control_primitive.rs b/cros_alsa/src/control_primitive.rs
index 4e1214d..227aef2 100644
--- a/cros_alsa/src/control_primitive.rs
+++ b/cros_alsa/src/control_primitive.rs
@@ -16,9 +16,8 @@
 use remain::sorted;
 
 pub type Result<T> = std::result::Result<T, Error>;
-type BoxError = Box<dyn error::Error + Send + Sync>;
 
-#[derive(Debug)]
+#[derive(Debug, PartialEq)]
 /// Possible errors that can occur in FFI functions.
 pub enum FFIError {
     Rc(i32),
@@ -36,7 +35,7 @@
 }
 
 #[sorted]
-#[derive(Debug)]
+#[derive(Debug, PartialEq)]
 /// Possible errors that can occur in cros-alsa::control_primitive.
 pub enum Error {
     /// Control with the given name does not exist.
@@ -51,10 +50,13 @@
     ElemInfoMallocFailed(FFIError),
     /// Failed to call snd_ctl_elem_value_malloc().
     ElemValueMallocFailed(FFIError),
-    /// Input is not a valid CString.
-    InvalidCString(BoxError),
+    /// The slice used to create a CStr does not have one and only one null
+    /// byte positioned at the end.
+    FromBytesWithNulError(FromBytesWithNulError),
     /// Failed to convert to a valid ElemType.
     InvalidElemType(u32),
+    /// An error indicating that an interior nul byte was found.
+    NulError(NulError),
     /// Failed to call snd_strerror().
     SndStrErrorFailed(i32),
     /// UTF-8 validation failed
@@ -73,8 +75,9 @@
             ElemIdMallocFailed(e) => write!(f, "snd_ctl_elem_id_malloc failed: {}", e),
             ElemInfoMallocFailed(e) => write!(f, "snd_ctl_elem_info_malloc failed: {}", e),
             ElemValueMallocFailed(e) => write!(f, "snd_ctl_elem_value_malloc failed: {}", e),
-            InvalidCString(e) => write!(f, "invalid CString: {}", e),
+            FromBytesWithNulError(e) => write!(f, "invalid CString: {}", e),
             InvalidElemType(v) => write!(f, "invalid ElemType: {}", v),
+            NulError(e) => write!(f, "invalid CString: {}", e),
             SndStrErrorFailed(e) => write!(f, "snd_strerror() failed: {}", e),
             Utf8Error(e) => write!(f, "{}", e),
         }
@@ -95,13 +98,13 @@
 
 impl From<FromBytesWithNulError> for Error {
     fn from(err: FromBytesWithNulError) -> Error {
-        Error::InvalidCString(err.into())
+        Error::FromBytesWithNulError(err)
     }
 }
 
 impl From<NulError> for Error {
     fn from(err: NulError) -> Error {
-        Error::InvalidCString(err.into())
+        Error::NulError(err)
     }
 }
 
@@ -324,9 +327,22 @@
         // Safe because self.0.as_ptr() is a valid snd_ctl_elem_info_t*.
         unsafe { snd_ctl_elem_info_get_count(self.0.as_ptr()) as usize }
     }
+
+    /// Safe [snd_ctl_elem_info_is_tlv_readable](https://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html#gaac6bb412e5a9fffb5509e98a10de45b5) wrapper.
+    pub fn tlv_readable(&self) -> bool {
+        // Safe because self.0.as_ptr() is a valid snd_ctl_elem_info_t*.
+        unsafe { snd_ctl_elem_info_is_tlv_readable(self.0.as_ptr()) as usize == 1 }
+    }
+
+    /// Safe [snd_ctl_elem_info_is_tlv_writable](https://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html#gacfbaae80d710b6feac682f8ba10a0341) wrapper.
+    pub fn tlv_writable(&self) -> bool {
+        // Safe because self.0.as_ptr() is a valid snd_ctl_elem_info_t*.
+        unsafe { snd_ctl_elem_info_is_tlv_writable(self.0.as_ptr()) as usize == 1 }
+    }
 }
 
 /// [snd_ctl_t](https://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html#ga06628f38def84a0fe3da74041db9d51f) wrapper.
+#[derive(Debug)]
 pub struct Ctl(ptr::NonNull<snd_ctl_t>, PhantomData<snd_ctl_t>);
 
 impl Drop for Ctl {
diff --git a/cros_alsa/src/control_tlv.rs b/cros_alsa/src/control_tlv.rs
new file mode 100644
index 0000000..e1f3551
--- /dev/null
+++ b/cros_alsa/src/control_tlv.rs
@@ -0,0 +1,313 @@
+// 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.
+
+//! `ControlTLV` supports read and write of the alsa TLV byte controls
+//!  Users can obtain a ControlTLV by Card::control_tlv_by_name().
+//! # Examples
+//! This is an example of how to use a `TLV`.
+//!
+//! ```
+//! use std::assert_eq;
+//! use std::convert::TryFrom;
+//! use std::error::Error;
+//!
+//! use cros_alsa::{TLV, ControlTLVError};
+//! use cros_alsa::elem::Elem;
+//!
+//! type Result<T> = std::result::Result<T, ControlTLVError>;
+//!
+//!     let mut tlv = TLV::new(0, vec![1,2,3,4]);
+//!     assert_eq!(4, tlv.len());
+//!     assert_eq!(0, tlv.tlv_type());
+//!     assert_eq!(2, tlv[1]);
+//!     tlv[1] = 8;
+//!     assert_eq!(vec![1,8,3,4], tlv.value().to_vec());
+//!     assert_eq!(vec![0,16,1,8,3,4], Into::<Vec<u32>>::into(tlv));
+//!
+//! ```
+
+use std::{
+    convert::TryFrom,
+    fmt,
+    ops::{Index, IndexMut},
+    slice::SliceIndex,
+};
+use std::{error, mem::size_of};
+
+use remain::sorted;
+
+use crate::control_primitive::{self, Ctl, ElemId, ElemInfo, ElemType};
+
+/// The Result type of cros-alsa::control.
+pub type Result<T> = std::result::Result<T, Error>;
+
+#[sorted]
+#[derive(Debug, PartialEq)]
+/// Possible errors that can occur in cros-alsa::control.
+pub enum Error {
+    /// Failed to call AlsaControlAPI.
+    AlsaControlAPI(control_primitive::Error),
+    /// Failed to convert buffer to TLV struct.
+    InvalidTLV,
+    /// ElemInfo::count() is not multiple of size_of::<u32>.
+    InvalidTLVSize(String, usize),
+    /// ElemInfo::elem_type() is not ElemType::Bytes.
+    InvalidTLVType(String, ElemType),
+    /// The control is not readable.
+    TLVNotReadable,
+    /// The control is not writeable.
+    TLVNotWritable,
+    /// Failed to call snd_ctl_elem_tlv_read.
+    TLVReadFailed(i32),
+    /// Failed to call snd_ctl_elem_tlv_write.
+    TVLWriteFailed(i32),
+}
+
+impl error::Error for Error {}
+
+impl From<control_primitive::Error> for Error {
+    fn from(err: control_primitive::Error) -> Error {
+        Error::AlsaControlAPI(err)
+    }
+}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use Error::*;
+        match self {
+            AlsaControlAPI(e) => write!(f, "{}", e),
+            InvalidTLV => write!(f, "failed to convert to TLV"),
+            InvalidTLVSize(name, elem_size) => write!(
+                f,
+                "ElemInfo::size() of {} should be multiple of size_of::<u32>, get: {}",
+                name, elem_size
+            ),
+            InvalidTLVType(name, elem_type) => write!(
+                f,
+                "invalid ElemInfo::elem_type() of {}: expect: {}, get: {}",
+                name,
+                ElemType::Bytes,
+                elem_type
+            ),
+            TLVNotReadable => write!(f, "the control is not readable."),
+            TLVNotWritable => write!(f, "the control is not writable."),
+            TLVReadFailed(rc) => write!(f, "snd_ctl_elem_tlv_read failed: {}", rc),
+            TVLWriteFailed(rc) => write!(f, "snd_ctl_elem_tlv_write failed: {}", rc),
+        }
+    }
+}
+
+/// TLV struct represents the TLV data to be read from
+/// or write to an alsa TLV byte control.
+#[derive(Debug)]
+pub struct TLV {
+    /// data[Self::TYPE_OFFSET] contains the tlv type.
+    /// data[Self::LEN_OFFSET] contains the length of the value in bytes.
+    /// data[Self::VALUE_OFFSET..] contains the data.
+    data: Vec<u32>,
+}
+
+impl TLV {
+    const TYPE_OFFSET: usize = 0;
+    const LEN_OFFSET: usize = 1;
+    const VALUE_OFFSET: usize = 2;
+    const TLV_HEADER_SIZE_BYTES: usize = Self::VALUE_OFFSET * size_of::<u32>();
+
+    /// Initializes a `TLV` by giving the tlv type and tlv value.
+    pub fn new(tlv_type: u32, tlv_value: Vec<u32>) -> Self {
+        let mut data = vec![0; 2];
+        data[Self::TYPE_OFFSET] = tlv_type;
+        data[Self::LEN_OFFSET] = (tlv_value.len() * size_of::<u32>()) as u32;
+        data.extend(tlv_value.iter());
+        Self { data }
+    }
+
+    /// Returns the type of the tlv.
+    pub fn tlv_type(&self) -> u32 {
+        self.data[Self::TYPE_OFFSET]
+    }
+
+    /// Returns the length of the tlv value in dword.
+    pub fn len(&self) -> usize {
+        self.data[Self::LEN_OFFSET] as usize / size_of::<u32>()
+    }
+
+    /// Returns whether the tlv value is empty.
+    pub fn is_empty(&self) -> bool {
+        self.data[Self::LEN_OFFSET] == 0
+    }
+
+    /// Returns the tlv value in slice.
+    pub fn value(&self) -> &[u32] {
+        &self.data[Self::VALUE_OFFSET..]
+    }
+}
+
+impl<I: SliceIndex<[u32]>> Index<I> for TLV {
+    type Output = I::Output;
+    #[inline]
+    fn index(&self, index: I) -> &Self::Output {
+        &self.data[Self::VALUE_OFFSET..][index]
+    }
+}
+
+impl<I: SliceIndex<[u32]>> IndexMut<I> for TLV {
+    #[inline]
+    fn index_mut(&mut self, index: I) -> &mut Self::Output {
+        &mut self.data[Self::VALUE_OFFSET..][index]
+    }
+}
+
+impl TryFrom<Vec<u32>> for TLV {
+    type Error = Error;
+
+    /// Constructs a TLV from a vector with the following alsa tlv header validation:
+    ///  1 . tlv_buf[Self::LEN_OFFSET] should be multiple of size_of::<u32>
+    ///  2 . tlv_buf[Self::LEN_OFFSET] is the length of tlv value in byte and
+    ///      should be less than the buffer length * size_of::<u32>.
+    fn try_from(data: Vec<u32>) -> Result<Self> {
+        if data.len() < 2 {
+            return Err(Error::InvalidTLV);
+        }
+
+        if data[Self::LEN_OFFSET] % size_of::<u32>() as u32 != 0 {
+            return Err(Error::InvalidTLV);
+        }
+
+        if data[Self::LEN_OFFSET] / size_of::<u32>() as u32
+            > data[Self::VALUE_OFFSET..].len() as u32
+        {
+            return Err(Error::InvalidTLV);
+        }
+
+        Ok(Self { data })
+    }
+}
+
+impl Into<Vec<u32>> for TLV {
+    /// Returns the raw tlv data buffer (including the tlv header).
+    fn into(self) -> Vec<u32> {
+        self.data
+    }
+}
+
+/// `ControlTLV` supports read and write of the alsa TLV byte controls.
+pub struct ControlTLV<'a> {
+    handle: &'a mut Ctl,
+    id: ElemId,
+}
+
+impl<'a> ControlTLV<'a> {
+    /// Called by `Card` to create a `ControlTLV`.
+    pub fn new(handle: &'a mut Ctl, id: ElemId) -> Result<Self> {
+        let info = ElemInfo::new(handle, &id)?;
+        if info.count() % size_of::<u32>() != 0 {
+            return Err(Error::InvalidTLVSize(id.name()?.to_owned(), info.count()));
+        }
+        match info.elem_type()? {
+            ElemType::Bytes => Ok(Self { handle, id }),
+            _ => Err(Error::InvalidTLVType(
+                id.name()?.to_owned(),
+                info.elem_type()?,
+            )),
+        }
+    }
+
+    /// Reads data from the byte control by `snd_ctl_elem_tlv_read`
+    ///
+    /// #
+    /// # Errors
+    ///
+    /// * If it fails to read from the control.
+    pub fn load(&mut self) -> Result<TLV> {
+        if !ElemInfo::new(self.handle, &self.id)?.tlv_readable() {
+            return Err(Error::TLVNotReadable);
+        }
+
+        let tlv_size = ElemInfo::new(self.handle, &self.id)?.count() + TLV::TLV_HEADER_SIZE_BYTES;
+
+        let mut tlv_buf = vec![0; tlv_size / size_of::<u32>()];
+        // Safe because handle.as_mut_ptr() is a valid *mut snd_ctl_t, id_as_ptr is valid and
+        // tlv_buf.as_mut_ptr() is also valid.
+        let rc = unsafe {
+            alsa_sys::snd_ctl_elem_tlv_read(
+                self.handle.as_mut_ptr(),
+                self.id.as_ptr(),
+                tlv_buf.as_mut_ptr(),
+                tlv_size as u32,
+            )
+        };
+        if rc < 0 {
+            return Err(Error::TLVReadFailed(rc));
+        }
+        Ok(TLV::try_from(tlv_buf)?)
+    }
+
+    /// Writes to the byte control by `snd_ctl_elem_tlv_write`
+    ///
+    /// # Results
+    ///
+    /// * `changed` - false on success.
+    ///             - true on success when value was changed.
+    /// #
+    /// # Errors
+    ///
+    /// * If it fails to write to the control.
+    pub fn save(&mut self, tlv: TLV) -> Result<bool> {
+        if !ElemInfo::new(self.handle, &self.id)?.tlv_writable() {
+            return Err(Error::TLVNotReadable);
+        }
+        // Safe because handle.as_mut_ptr() is a valid *mut snd_ctl_t, id_as_ptr is valid and
+        // tlv.as_mut_ptr() is also valid.
+        let rc = unsafe {
+            alsa_sys::snd_ctl_elem_tlv_write(
+                self.handle.as_mut_ptr(),
+                self.id.as_ptr(),
+                Into::<Vec<u32>>::into(tlv).as_mut_ptr(),
+            )
+        };
+        if rc < 0 {
+            return Err(Error::TVLWriteFailed(rc));
+        }
+        Ok(rc > 0)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    #[test]
+    fn test_tlv_try_from_raw_vec() {
+        let tlv_buf = vec![0, 12, 2, 3, 4];
+        assert!(TLV::try_from(tlv_buf).is_ok());
+    }
+
+    #[test]
+    fn test_tlv_length_is_not_multiple_of_sizeof_int() {
+        // Invalid tlv length in data[Self::LEN_OFFSET].
+        let tlv_buf = vec![0, 1, 2, 3, 4];
+        assert_eq!(TLV::try_from(tlv_buf).unwrap_err(), Error::InvalidTLV);
+    }
+
+    #[test]
+    fn test_tlv_length_larger_than_buff_size() {
+        // Invalid tlv length in data[Self::LEN_OFFSET].
+        let tlv_buf = vec![0, 16, 2, 3, 4];
+        assert_eq!(TLV::try_from(tlv_buf).unwrap_err(), Error::InvalidTLV);
+    }
+
+    #[test]
+    fn test_tlv_length_less_than_two() {
+        // tlv buffer length < 2
+        let tlv_buf = vec![0];
+        assert_eq!(TLV::try_from(tlv_buf).unwrap_err(), Error::InvalidTLV);
+    }
+
+    #[test]
+    fn test_tlv_length_equal_two() {
+        // tlv buffer size = 2.
+        let tlv_buf = vec![0, 0];
+        assert!(TLV::try_from(tlv_buf).is_ok());
+    }
+}
diff --git a/cros_alsa/src/lib.rs b/cros_alsa/src/lib.rs
index aec3d52..070d221 100644
--- a/cros_alsa/src/lib.rs
+++ b/cros_alsa/src/lib.rs
@@ -44,14 +44,17 @@
 mod card;
 mod control;
 mod control_primitive;
+pub mod control_tlv;
 pub mod elem;
 
 pub use self::card::Card;
 pub use self::control::{Control, ControlOps, IntControl, StereoVolumeControl, SwitchControl};
 pub use self::control_primitive::{Ctl, ElemId};
+pub use self::control_tlv::{ControlTLV, TLV};
 
 pub use self::card::Error as CardError;
 pub use self::control::Error as ControlError;
+pub use self::control_tlv::Error as ControlTLVError;
 pub use self::elem::Error as ElemError;
 
 #[allow(unused_imports)]
diff --git a/init/cras.sh b/init/cras.sh
index ca2d6b9..91114c0 100644
--- a/init/cras.sh
+++ b/init/cras.sh
@@ -48,7 +48,6 @@
         -k 'tmpfs,/run,tmpfs,MS_NODEV|MS_NOEXEC|MS_NOSUID,mode=755,size=10M' \
         -b /run/cras,/run/cras,1 \
         -b /run/dbus,/run/dbus,1 \
-        -b /run/systemd/journal \
         -b /run/udev,/run/udev \
         -b /dev,/dev \
         -b /dev/shm,/dev/shm,1 \
diff --git a/scripts/audio_tuning/frontend/audio.js b/scripts/audio_tuning/frontend/audio.js
index b90be95..98870cd 100644
--- a/scripts/audio_tuning/frontend/audio.js
+++ b/scripts/audio_tuning/frontend/audio.js
@@ -1570,7 +1570,7 @@
   var kneeThresholdDb;
   var kneeThreshold;
   var ykneeThresholdDb;
-  var masterLinearGain;
+  var mainLinearGain;
 
   var maxOutputDb = 6;
   var minOutputDb = -36;
@@ -1662,10 +1662,10 @@
     kneeThreshold = dBToLinear(kneeThresholdDb);
     ykneeThresholdDb = linearToDb(kneeCurve(kneeThreshold, curve_k));
 
-    /* Calculate masterLinearGain */
+    /* Calculate mainLinearGain */
     var fullRangeGain = saturate(1, curve_k);
     var fullRangeMakeupGain = Math.pow(1 / fullRangeGain, 0.6);
-    masterLinearGain = dBToLinear(boost) * fullRangeMakeupGain;
+    mainLinearGain = dBToLinear(boost) * fullRangeMakeupGain;
 
     /* Clear canvas */
     var width = canvas.width;
@@ -1725,7 +1725,7 @@
       var inputDb = xpixelToDb(x);
       var inputLinear = dBToLinear(inputDb);
       var outputLinear = saturate(inputLinear, curve_k);
-      outputLinear *= masterLinearGain;
+      outputLinear *= mainLinearGain;
       var outputDb = linearToDb(outputLinear);
       var y = dBToYPixel(outputDb);
 
diff --git a/seccomp/cras-seccomp-amd64.policy b/seccomp/cras-seccomp-amd64.policy
index 021ced1..afa6ace 100644
--- a/seccomp/cras-seccomp-amd64.policy
+++ b/seccomp/cras-seccomp-amd64.policy
@@ -20,6 +20,7 @@
 access: 1
 fcntl: 1
 getdents: 1
+getdents64: 1
 sendmsg: 1
 stat: 1
 statfs: 1
@@ -38,6 +39,7 @@
 socketpair: 1
 unlink: 1
 nanosleep: 1
+clock_nanosleep: 1
 pipe: 1
 ftruncate: 1
 fallocate: 1
diff --git a/seccomp/cras-seccomp-arm.policy b/seccomp/cras-seccomp-arm.policy
index e823244..cbaa622 100644
--- a/seccomp/cras-seccomp-arm.policy
+++ b/seccomp/cras-seccomp-arm.policy
@@ -3,9 +3,11 @@
 # found in the LICENSE file.
 
 clock_gettime: 1
+clock_gettime64: 1
 poll: 1
 read: 1
 ppoll: 1
+ppoll_time64: 1
 write: 1
 writev: 1
 recv: 1
@@ -41,6 +43,7 @@
 ftruncate: 1
 fallocate: 1
 futex: 1
+futex_time64: 1
 execve: 1
 set_robust_list: 1
 socket: arg0 == AF_UNIX || arg0 == AF_BLUETOOTH || arg0 == AF_NETLINK
@@ -64,6 +67,8 @@
 getsockname: 1
 getdents: 1
 nanosleep: 1
+clock_nanosleep: 1
+clock_nanosleep_time64: 1
 epoll_ctl: 1
 sched_setscheduler: 1
 restart_syscall: 1
@@ -72,6 +77,7 @@
 exit: 1
 prctl: arg0 == PR_SET_NAME
 clock_getres: 1
+clock_getres_time64: 1
 epoll_create1: 1
 fchmod: 1
 setpriority: 1
diff --git a/seccomp/cras-seccomp-arm64.policy b/seccomp/cras-seccomp-arm64.policy
index d06a741..4b52355 100644
--- a/seccomp/cras-seccomp-arm64.policy
+++ b/seccomp/cras-seccomp-arm64.policy
@@ -68,6 +68,7 @@
 lsetxattr: 1
 madvise: 1
 nanosleep: 1
+clock_nanosleep: 1
 restart_syscall: 1
 rt_sigprocmask: 1
 rt_sigreturn: 1
diff --git a/sof_sys/.gitignore b/sof_sys/.gitignore
new file mode 100644
index 0000000..fa8d85a
--- /dev/null
+++ b/sof_sys/.gitignore
@@ -0,0 +1,2 @@
+Cargo.lock
+target
diff --git a/sof_sys/.rustfmt.toml b/sof_sys/.rustfmt.toml
new file mode 100644
index 0000000..a2db301
--- /dev/null
+++ b/sof_sys/.rustfmt.toml
@@ -0,0 +1,2 @@
+use_field_init_shorthand = true
+use_try_shorthand = true
diff --git a/sof_sys/Cargo.toml b/sof_sys/Cargo.toml
new file mode 100644
index 0000000..21934ee
--- /dev/null
+++ b/sof_sys/Cargo.toml
@@ -0,0 +1,4 @@
+[package]
+name = "sof_sys"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
diff --git a/sof_sys/generator/.gitignore b/sof_sys/generator/.gitignore
new file mode 100644
index 0000000..03314f7
--- /dev/null
+++ b/sof_sys/generator/.gitignore
@@ -0,0 +1 @@
+Cargo.lock
diff --git a/sof_sys/generator/Cargo.toml b/sof_sys/generator/Cargo.toml
new file mode 100644
index 0000000..672d41d
--- /dev/null
+++ b/sof_sys/generator/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "generator"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+[dependencies]
+bindgen = "0.43.0"
diff --git a/sof_sys/generator/README.md b/sof_sys/generator/README.md
new file mode 100644
index 0000000..3e2ca77
--- /dev/null
+++ b/sof_sys/generator/README.md
@@ -0,0 +1 @@
+Use `cargo run` to generate rust bindings at sof_sys/src/bindings.rs
diff --git a/sof_sys/generator/src/main.rs b/sof_sys/generator/src/main.rs
new file mode 100644
index 0000000..60f0102
--- /dev/null
+++ b/sof_sys/generator/src/main.rs
@@ -0,0 +1,42 @@
+// 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.
+extern crate bindgen;
+
+use std::fs::File;
+use std::io::Write;
+use std::path::PathBuf;
+
+fn main() {
+    let bindings = bindgen::Builder::default()
+        .header("wrapper.h")
+        .derive_debug(false)
+        .clang_arg("-I../../../sound-open-firmware-private/src/include")
+        .whitelist_type("sof_abi_hdr")
+        .whitelist_type("sof_ipc_ctrl_cmd")
+        .generate()
+        .expect("Unable to generate bindings");
+
+    let header = b"// 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.
+/*
+ * generated from files in sound-open-firmware-private/src/include:
+ * kernel/header.h
+ * ipc/control.h
+ */
+
+";
+
+    // Write the bindings to the $OUT_DIR/bindings.rs file.
+    let out_path = PathBuf::from("../src").join("bindings.rs");
+
+    let mut output_file =
+        File::create(&out_path).expect(&format!("Couldn't create {:?}", out_path));
+    output_file
+        .write_all(header)
+        .expect("Couldn't write header");
+    output_file
+        .write_all(bindings.to_string().as_bytes())
+        .expect("Couldn't write bindings");
+}
diff --git a/sof_sys/generator/wrapper.h b/sof_sys/generator/wrapper.h
new file mode 100644
index 0000000..5bac0f5
--- /dev/null
+++ b/sof_sys/generator/wrapper.h
@@ -0,0 +1,5 @@
+// Copyright 2021 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.
+#include <kernel/header.h>
+#include <ipc/control.h>
diff --git a/sof_sys/src/bindings.rs b/sof_sys/src/bindings.rs
new file mode 100644
index 0000000..7bed0dc
--- /dev/null
+++ b/sof_sys/src/bindings.rs
@@ -0,0 +1,154 @@
+// 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.
+/*
+ * generated from files in sound-open-firmware-private/src/include:
+ * kernel/header.h
+ * ipc/control.h
+ */
+
+/* automatically generated by rust-bindgen */
+
+#[repr(C)]
+#[derive(Default)]
+pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>);
+impl<T> __IncompleteArrayField<T> {
+    #[inline]
+    pub fn new() -> Self {
+        __IncompleteArrayField(::std::marker::PhantomData)
+    }
+    #[inline]
+    pub unsafe fn as_ptr(&self) -> *const T {
+        ::std::mem::transmute(self)
+    }
+    #[inline]
+    pub unsafe fn as_mut_ptr(&mut self) -> *mut T {
+        ::std::mem::transmute(self)
+    }
+    #[inline]
+    pub unsafe fn as_slice(&self, len: usize) -> &[T] {
+        ::std::slice::from_raw_parts(self.as_ptr(), len)
+    }
+    #[inline]
+    pub unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T] {
+        ::std::slice::from_raw_parts_mut(self.as_mut_ptr(), len)
+    }
+}
+impl<T> ::std::fmt::Debug for __IncompleteArrayField<T> {
+    fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        fmt.write_str("__IncompleteArrayField")
+    }
+}
+impl<T> ::std::clone::Clone for __IncompleteArrayField<T> {
+    #[inline]
+    fn clone(&self) -> Self {
+        Self::new()
+    }
+}
+impl<T> ::std::marker::Copy for __IncompleteArrayField<T> {}
+pub type __uint32_t = ::std::os::raw::c_uint;
+/// \brief Header for all non IPC ABI data.
+///
+/// Identifies data type, size and ABI.
+/// Data header used for all component data structures and binary blobs sent to
+/// firmware as runtime data. This data is typically sent by userspace
+/// applications and tunnelled through any OS kernel (via binary kcontrol on
+/// Linux) to the firmware.
+#[repr(C, packed)]
+pub struct sof_abi_hdr {
+    ///< 'S', 'O', 'F', '\0'
+    pub magic: u32,
+    ///< component specific type
+    pub type_: u32,
+    ///< size in bytes of data excl. this struct
+    pub size: u32,
+    ///< SOF ABI version
+    pub abi: u32,
+    ///< reserved for future use
+    pub reserved: [u32; 4usize],
+    ///< Component data - opaque to core
+    pub data: __IncompleteArrayField<u32>,
+}
+#[test]
+fn bindgen_test_layout_sof_abi_hdr() {
+    assert_eq!(
+        ::std::mem::size_of::<sof_abi_hdr>(),
+        32usize,
+        concat!("Size of: ", stringify!(sof_abi_hdr))
+    );
+    assert_eq!(
+        ::std::mem::align_of::<sof_abi_hdr>(),
+        1usize,
+        concat!("Alignment of ", stringify!(sof_abi_hdr))
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<sof_abi_hdr>())).magic as *const _ as usize },
+        0usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(sof_abi_hdr),
+            "::",
+            stringify!(magic)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<sof_abi_hdr>())).type_ as *const _ as usize },
+        4usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(sof_abi_hdr),
+            "::",
+            stringify!(type_)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<sof_abi_hdr>())).size as *const _ as usize },
+        8usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(sof_abi_hdr),
+            "::",
+            stringify!(size)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<sof_abi_hdr>())).abi as *const _ as usize },
+        12usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(sof_abi_hdr),
+            "::",
+            stringify!(abi)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<sof_abi_hdr>())).reserved as *const _ as usize },
+        16usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(sof_abi_hdr),
+            "::",
+            stringify!(reserved)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<sof_abi_hdr>())).data as *const _ as usize },
+        32usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(sof_abi_hdr),
+            "::",
+            stringify!(data)
+        )
+    );
+}
+///< maps to ALSA volume style controls
+pub const sof_ipc_ctrl_cmd_SOF_CTRL_CMD_VOLUME: sof_ipc_ctrl_cmd = 0;
+///< maps to ALSA enum style controls
+pub const sof_ipc_ctrl_cmd_SOF_CTRL_CMD_ENUM: sof_ipc_ctrl_cmd = 1;
+///< maps to ALSA switch style controls
+pub const sof_ipc_ctrl_cmd_SOF_CTRL_CMD_SWITCH: sof_ipc_ctrl_cmd = 2;
+///< maps to ALSA binary style controls
+pub const sof_ipc_ctrl_cmd_SOF_CTRL_CMD_BINARY: sof_ipc_ctrl_cmd = 3;
+/// Control command type.
+pub type sof_ipc_ctrl_cmd = u32;
diff --git a/sof_sys/src/lib.rs b/sof_sys/src/lib.rs
new file mode 100644
index 0000000..57119cf
--- /dev/null
+++ b/sof_sys/src/lib.rs
@@ -0,0 +1,12 @@
+// 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.
+#![allow(clippy::unreadable_literal)]
+#![allow(clippy::cognitive_complexity)]
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+
+pub mod bindings;
+#[allow(unused_imports)]
+pub use bindings::sof_abi_hdr;
diff --git a/sound_card_init/Cargo.lock b/sound_card_init/Cargo.lock
index 06448e9..c89ad1e 100644
--- a/sound_card_init/Cargo.lock
+++ b/sound_card_init/Cargo.lock
@@ -11,6 +11,26 @@
 ]
 
 [[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"
 
@@ -58,10 +78,23 @@
 ]
 
 [[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.5"
+version = "0.4.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3"
+checksum = "88d7ed2934d741c6b37e33e3832298e8850b53fd2d2bea03873375596c7cea4e"
 
 [[package]]
 name = "getopts"
@@ -74,9 +107,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"
@@ -91,29 +124,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"
-
-[[package]]
-name = "max98390d"
-version = "0.1.0"
-dependencies = [
- "audio_streams",
- "cros_alsa",
- "libcras",
- "remain",
- "serde",
- "serde_yaml",
- "sys_util",
- "utils",
-]
+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"
@@ -126,27 +145,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",
@@ -155,18 +174,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",
@@ -175,9 +194,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",
@@ -186,26 +205,31 @@
 ]
 
 [[package]]
+name = "sof_sys"
+version = "0.1.0"
+
+[[package]]
 name = "sound_card_init"
 version = "0.1.0"
 dependencies = [
+ "amp",
  "audio_streams",
  "cros_alsa",
+ "dsm",
  "getopts",
  "libcras",
- "max98390d",
  "remain",
  "serde",
  "serde_yaml",
+ "sof_sys",
  "sys_util",
- "utils",
 ]
 
 [[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",
@@ -220,6 +244,7 @@
 name = "sys_util"
 version = "0.1.0"
 dependencies = [
+ "android_log-sys",
  "data_model",
  "libc",
  "poll_token_derive",
@@ -241,30 +266,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"
-
-[[package]]
-name = "utils"
-version = "0.1.0"
-dependencies = [
- "remain",
- "serde",
- "serde_yaml",
-]
+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 d4a2b37..9d5a107 100644
--- a/sound_card_init/Cargo.toml
+++ b/sound_card_init/Cargo.toml
@@ -5,16 +5,23 @@
 edition = "2018"
 description = "Sound Card Initializer"
 
+[workspace]
+members = [
+    "amp",
+    "dsm"
+]
+
 [dependencies]
+amp = { path = "amp" }
 audio_streams = "*"
 cros_alsa = "*"
+dsm = { path = "dsm" }
 getopts = "0.2"
 libcras = "*"
 remain = "0.2.1"
-max98390d = { path = "max98390d" }
-utils = { path = "utils" }
 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/max98390d/Cargo.toml b/sound_card_init/amp/Cargo.toml
similarity index 81%
copy from sound_card_init/max98390d/Cargo.toml
copy to sound_card_init/amp/Cargo.toml
index f3bbae6..62e63db 100644
--- a/sound_card_init/max98390d/Cargo.toml
+++ b/sound_card_init/amp/Cargo.toml
@@ -1,5 +1,5 @@
 [package]
-name = "max98390d"
+name = "amp"
 version = "0.1.0"
 authors = ["The Chromium OS Authors"]
 edition = "2018"
@@ -7,10 +7,10 @@
 
 [dependencies]
 cros_alsa = "*"
-audio_streams = "*"
 libcras = "*"
+dsm = { path = "../dsm" }
 remain = "0.2.1"
 serde = { version = "1.0", features = ["derive"]}
 serde_yaml = "0.8.11"
+sof_sys = "*"
 sys_util = "*"
-utils = { path = "../utils" }
\ No newline at end of file
diff --git a/sound_card_init/amp/src/lib.rs b/sound_card_init/amp/src/lib.rs
new file mode 100644
index 0000000..7114233
--- /dev/null
+++ b/sound_card_init/amp/src/lib.rs
@@ -0,0 +1,58 @@
+// Copyright 2021 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.
+//! `amp` crate provides `Amp` trait for amplifier initializations and `AmpBuilder`
+//! to create `Amp` objects.
+#![deny(missing_docs)]
+
+mod max98373d;
+mod max98390d;
+use std::path::PathBuf;
+
+use dsm::Error;
+
+use max98373d::Max98373;
+use max98390d::Max98390;
+
+type Result<T> = std::result::Result<T, Error>;
+const CONF_DIR: &str = "/etc/sound_card_init";
+
+/// It creates `Amp` object based on the sound card name.
+pub struct AmpBuilder<'a> {
+    sound_card_id: &'a str,
+    config_path: PathBuf,
+}
+
+impl<'a> AmpBuilder<'a> {
+    /// Creates an `AmpBuilder`.
+    /// # Arguments
+    ///
+    /// * `card_name` - card name.
+    /// * `conf_file` - config file name.
+    pub fn new(sound_card_id: &'a str, conf_file: &'a str) -> Self {
+        let config_path = PathBuf::from(CONF_DIR).join(conf_file);
+        AmpBuilder {
+            sound_card_id,
+            config_path,
+        }
+    }
+
+    /// Creates an `Amp` based on the sound card name.
+    pub fn build(&self) -> Result<Box<dyn Amp>> {
+        match self.sound_card_id {
+            "sofcmlmax98390d" => {
+                Ok(Box::new(Max98390::new(self.sound_card_id, &self.config_path)?) as Box<dyn Amp>)
+            }
+            "sofrt5682" => {
+                Ok(Box::new(Max98373::new(self.sound_card_id, &self.config_path)?) as Box<dyn Amp>)
+            }
+            _ => Err(Error::UnsupportedSoundCard(self.sound_card_id.to_owned())),
+        }
+    }
+}
+
+/// It defines the required functions of amplifier objects.
+pub trait Amp {
+    /// The amplifier boot time calibration flow.
+    fn boot_time_calibration(&mut self) -> Result<()>;
+}
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..1ee29ce
--- /dev/null
+++ b/sound_card_init/amp/src/max98373d/mod.rs
@@ -0,0 +1,274 @@
+// 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;
+use std::time::Duration;
+use std::{fs, thread};
+
+use cros_alsa::{Card, IntControl};
+use dsm::{CalibData, Error, Result, SpeakerStatus, ZeroPlayer, DSM};
+use sys_util::info;
+
+use crate::Amp;
+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 = if !self.setting.boot_time_calibration_enabled {
+            info!("skip boot time calibration and use vpd values");
+            // Needs Rdc updates to be done after internal speaker is ready otherwise
+            // it would be overwritten by the DSM blob update.
+            dsm.wait_for_speakers_ready()?;
+            dsm.get_all_vpd_calibration_value()?
+        } else {
+            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.
+    /// * `config_path` - config file path.
+    ///
+    /// # 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, config_path: &Path) -> Result<Self> {
+        let conf = fs::read_to_string(config_path)
+            .map_err(|e| Error::FileIOFailed(config_path.to_path_buf(), 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..1d6e64e
--- /dev/null
+++ b/sound_card_init/amp/src/max98373d/settings.rs
@@ -0,0 +1,41 @@
+// 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,
+    pub boot_time_calibration_enabled: bool,
+}
+
+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/amp/src/max98390d/mod.rs b/sound_card_init/amp/src/max98390d/mod.rs
new file mode 100644
index 0000000..601165e
--- /dev/null
+++ b/sound_card_init/amp/src/max98390d/mod.rs
@@ -0,0 +1,213 @@
+// 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.
+//! `max98390d` module implements the required initialization workflows for sound
+//! cards that use max98390d smart amp.
+//! It currently supports boot time calibration for max98390d.
+#![deny(missing_docs)]
+mod settings;
+
+use std::time::Duration;
+use std::{fs, path::Path};
+
+use cros_alsa::{Card, IntControl, SwitchControl};
+use dsm::{CalibData, Error, Result, SpeakerStatus, TempConverter, ZeroPlayer, DSM};
+
+use crate::Amp;
+use settings::{AmpCalibSettings, DeviceSettings};
+
+/// Amp volume mode emulation used by set_volume().
+#[derive(PartialEq, Clone, Copy)]
+enum VolumeMode {
+    /// Low mode protects the speaker by limiting its output volume if the
+    /// calibration has not been completed successfully.
+    Low = 138,
+    /// High mode removes the speaker output volume limitation after
+    /// having successfully completed the calibration.
+    High = 148,
+}
+
+/// It implements the Max98390 functions of boot time calibration.
+#[derive(Debug)]
+pub struct Max98390 {
+    card: Card,
+    setting: AmpCalibSettings,
+}
+
+impl Amp for Max98390 {
+    /// Performs max98390d boot time calibration.
+    ///
+    /// # Errors
+    ///
+    /// If the amplifier fails to complete the calibration.
+    fn boot_time_calibration(&mut self) -> Result<()> {
+        if !Path::new(&self.setting.dsm_param).exists() {
+            return Err(Error::MissingDSMParam);
+        }
+
+        let mut dsm = DSM::new(
+            &self.card.name(),
+            self.setting.num_channels(),
+            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()? {
+            SpeakerStatus::Hot(previous_calib) => previous_calib,
+            SpeakerStatus::Cold => self
+                .do_calibration()?
+                .iter()
+                .enumerate()
+                .map(|(ch, calib_data)| dsm.decide_calibration_value_workflow(ch, *calib_data))
+                .collect::<Result<Vec<_>>>()?,
+        };
+        self.apply_calibration_value(calib)?;
+        self.set_volume(VolumeMode::High)?;
+        Ok(())
+    }
+}
+
+impl Max98390 {
+    const TEMP_UPPER_LIMIT_CELSIUS: f32 = 40.0;
+    const TEMP_LOWER_LIMIT_CELSIUS: f32 = 0.0;
+    const RDC_CALIB_WARM_UP_TIME: Duration = Duration::from_millis(300);
+
+    /// Creates an `Max98390`.
+    /// # Arguments
+    ///
+    /// * `card_name` - card name.
+    /// * `config_path` - config file path.
+    ///
+    /// # Results
+    ///
+    /// * `Max98390` - It implements the Max98390 functions of boot time calibration.
+    ///
+    /// # Errors
+    ///
+    /// * If `Card` creation from sound card name fails.
+    pub fn new(card_name: &str, config_path: &Path) -> Result<Self> {
+        let conf = fs::read_to_string(config_path)
+            .map_err(|e| Error::FileIOFailed(config_path.to_path_buf(), e))?;
+        let settings = DeviceSettings::from_yaml_str(&conf)?;
+        Ok(Self {
+            card: Card::new(card_name)?,
+            setting: settings.amp_calibrations,
+        })
+    }
+
+    /// Sets the card volume control to given VolumeMode.
+    fn set_volume(&mut self, mode: VolumeMode) -> Result<()> {
+        for control in &self.setting.controls {
+            self.card
+                .control_by_name::<IntControl>(&control.volume_ctrl)?
+                .set(mode as i32)?;
+        }
+        Ok(())
+    }
+
+    /// Applies the calibration value to the amp.
+    fn apply_calibration_value(&mut self, calib: Vec<CalibData>) -> Result<()> {
+        for (ch, &CalibData { rdc, temp }) in calib.iter().enumerate() {
+            self.card
+                .control_by_name::<IntControl>(&self.setting.controls[ch].rdc_ctrl)?
+                .set(rdc)?;
+            self.card
+                .control_by_name::<IntControl>(&self.setting.controls[ch].temp_ctrl)?
+                .set(Self::celsius_to_dsm_unit(temp))?;
+        }
+        Ok(())
+    }
+
+    /// Triggers the amplifier calibration and reads the calibrated rdc and ambient_temp value
+    /// from the mixer control.
+    /// 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_calibration(&mut self) -> Result<Vec<CalibData>> {
+        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.
+        let setting = &self.setting;
+        let card = &mut self.card;
+        let calib = setting
+            .controls
+            .iter()
+            .map(|control| {
+                card.control_by_name::<SwitchControl>(&control.calib_ctrl)?
+                    .on()?;
+                let rdc = card
+                    .control_by_name::<IntControl>(&control.rdc_ctrl)?
+                    .get()?;
+                let temp = card
+                    .control_by_name::<IntControl>(&control.temp_ctrl)?
+                    .get()?;
+                card.control_by_name::<SwitchControl>(&control.calib_ctrl)?
+                    .off()?;
+                Ok(CalibData {
+                    rdc,
+                    temp: Self::dsm_unit_to_celsius(temp),
+                })
+            })
+            .collect::<Result<Vec<CalibData>>>()?;
+        zero_player.stop()?;
+        Ok(calib)
+    }
+
+    /// 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
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    #[test]
+    fn celsius_to_dsm_unit() {
+        assert_eq!(
+            Max98390::celsius_to_dsm_unit(Max98390::TEMP_UPPER_LIMIT_CELSIUS),
+            1638
+        );
+        assert_eq!(
+            Max98390::celsius_to_dsm_unit(Max98390::TEMP_LOWER_LIMIT_CELSIUS),
+            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/max98390d/src/settings.rs b/sound_card_init/amp/src/max98390d/settings.rs
similarity index 62%
rename from sound_card_init/max98390d/src/settings.rs
rename to sound_card_init/amp/src/max98390d/settings.rs
index 32feefd..316f25b 100644
--- a/sound_card_init/max98390d/src/settings.rs
+++ b/sound_card_init/amp/src/max98390d/settings.rs
@@ -3,35 +3,18 @@
 // found in the LICENSE file.
 use std::string::String;
 
+use dsm::{self, Error, Result};
 use serde::Deserialize;
 
-use crate::error::{Error, Result};
-
 /// `DeviceSettings` includes the settings of max98390. 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: Vec<AmpCalibSettings>,
-    pub dsm_param: String,
+    pub amp_calibrations: AmpCalibSettings,
 }
-
-/// `AmpCalibSettings` includes the settings needed for amplifier calibration.
 #[derive(Debug, Default, PartialEq, Deserialize, Clone)]
-pub struct AmpCalibSettings {
-    /// `AmpSettings`.
-    pub amp: AmpSettings,
-    /// Vpd file of Rdc.
-    pub rdc_vpd: String,
-    /// Vpd file of ambient temperature.
-    pub temp_vpd: String,
-    /// File to store the boot time calibration values.
-    pub calib_file: String,
-}
-
-/// `AmpSettings` represents mixer control names and amp params needed for amplifier calibration.
-#[derive(Debug, Default, PartialEq, Deserialize, Clone)]
-pub struct AmpSettings {
+pub struct AmpCalibCtrl {
     // Mixer control to get/set rdc value.
     pub rdc_ctrl: String,
     // Mixer control to get/set ambient temperature value.
@@ -40,14 +23,22 @@
     pub calib_ctrl: String,
     // Mixer control to adjust volume.
     pub volume_ctrl: String,
-    // The threshold to put volume into normal.
-    pub volume_high_limit: i32,
-    // The threshold to put volume into protected mode.
-    pub volume_low_limit: i32,
-    // The upper limit of a valid temperature value.
-    pub temp_upper_limit: i32,
-    // The lower limit of a valid temperature value.
-    pub temp_lower_limit: i32,
+}
+
+/// `AmpCalibSettings` includes the settings needed for amplifier calibration.
+#[derive(Debug, Default, PartialEq, Deserialize, Clone)]
+pub struct AmpCalibSettings {
+    // Mixer control to get/set rdc value.
+    pub controls: Vec<AmpCalibCtrl>,
+    // 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.controls.len()
+    }
 }
 
 impl DeviceSettings {
diff --git a/sound_card_init/max98390d/Cargo.lock b/sound_card_init/dsm/Cargo.lock
similarity index 100%
rename from sound_card_init/max98390d/Cargo.lock
rename to sound_card_init/dsm/Cargo.lock
diff --git a/sound_card_init/max98390d/Cargo.toml b/sound_card_init/dsm/Cargo.toml
similarity index 86%
rename from sound_card_init/max98390d/Cargo.toml
rename to sound_card_init/dsm/Cargo.toml
index f3bbae6..280896d 100644
--- a/sound_card_init/max98390d/Cargo.toml
+++ b/sound_card_init/dsm/Cargo.toml
@@ -1,5 +1,5 @@
 [package]
-name = "max98390d"
+name = "dsm"
 version = "0.1.0"
 authors = ["The Chromium OS Authors"]
 edition = "2018"
@@ -12,5 +12,4 @@
 remain = "0.2.1"
 serde = { version = "1.0", features = ["derive"]}
 serde_yaml = "0.8.11"
-sys_util = "*"
-utils = { path = "../utils" }
\ No newline at end of file
+sys_util = "*"
\ No newline at end of file
diff --git a/sound_card_init/dsm/src/datastore.rs b/sound_card_init/dsm/src/datastore.rs
new file mode 100644
index 0000000..f0180cc
--- /dev/null
+++ b/sound_card_init/dsm/src/datastore.rs
@@ -0,0 +1,72 @@
+// 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::fs::{remove_file, File};
+use std::io::{prelude::*, BufReader, BufWriter};
+use std::path::PathBuf;
+
+use serde::{Deserialize, Serialize};
+use sys_util::info;
+
+use crate::error::{Error, Result};
+
+/// `Datastore`, which stores and reads calibration values in yaml format.
+#[derive(Debug, Deserialize, Serialize, Copy, Clone)]
+pub enum Datastore {
+    /// Indicates using values in VPD.
+    UseVPD,
+    DSM {
+        rdc: i32,
+        temp: i32,
+    },
+}
+
+impl Datastore {
+    /// The dir of datastore.
+    pub const DATASTORE_DIR: &'static str = "/var/lib/sound_card_init";
+
+    /// Creates a `Datastore` and initializes its fields from the datastore file.
+    pub fn from_file(snd_card: &str, channel: usize) -> Result<Datastore> {
+        let path = Self::path(snd_card, channel);
+        let reader =
+            BufReader::new(File::open(&path).map_err(|e| Error::FileIOFailed(path.to_owned(), e))?);
+        let datastore: Datastore =
+            serde_yaml::from_reader(reader).map_err(|e| Error::SerdeError(path.to_owned(), e))?;
+        Ok(datastore)
+    }
+
+    /// Saves a `Datastore` to file.
+    pub fn save(&self, snd_card: &str, channel: usize) -> Result<()> {
+        let path = Self::path(snd_card, channel);
+
+        let mut writer = BufWriter::new(
+            File::create(&path).map_err(|e| Error::FileIOFailed(path.to_owned(), e))?,
+        );
+        writer
+            .write(
+                serde_yaml::to_string(self)
+                    .map_err(|e| Error::SerdeError(path.to_owned(), e))?
+                    .as_bytes(),
+            )
+            .map_err(|e| Error::FileIOFailed(path.to_owned(), e))?;
+        writer
+            .flush()
+            .map_err(|e| Error::FileIOFailed(path.to_owned(), e))?;
+        info!("update Datastore {}: {:?}", path.to_string_lossy(), self);
+        Ok(())
+    }
+
+    /// Deletes the datastore file.
+    pub fn delete(snd_card: &str, channel: usize) -> Result<()> {
+        let path = Self::path(snd_card, channel);
+        remove_file(&path).map_err(|e| Error::FileIOFailed(path.to_owned(), e))?;
+        info!("datastore: {:#?} is deleted.", path);
+        Ok(())
+    }
+
+    fn path(snd_card: &str, channel: usize) -> PathBuf {
+        PathBuf::from(Self::DATASTORE_DIR)
+            .join(snd_card)
+            .join(format!("calib_{}", channel))
+    }
+}
diff --git a/sound_card_init/dsm/src/error.rs b/sound_card_init/dsm/src/error.rs
new file mode 100644
index 0000000..4b6e8dc
--- /dev/null
+++ b/sound_card_init/dsm/src/error.rs
@@ -0,0 +1,128 @@
+// 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::any::Any;
+use std::error;
+use std::fmt;
+use std::io;
+use std::num::ParseIntError;
+use std::path::PathBuf;
+use std::sync::PoisonError;
+use std::time;
+
+use remain::sorted;
+
+use crate::CalibData;
+
+pub type Result<T> = std::result::Result<T, Error>;
+
+#[sorted]
+#[derive(Debug)]
+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,
+    InvalidDSMParam,
+    InvalidShutDownTime,
+    InvalidTemperature(f32),
+    LargeCalibrationDiff(CalibData),
+    MissingDSMParam,
+    MutexPoisonError,
+    NewPlayStreamFailed(libcras::BoxError),
+    NextPlaybackBufferFailed(libcras::BoxError),
+    PlaybackFailed(io::Error),
+    SerdeError(PathBuf, serde_yaml::Error),
+    StartPlaybackTimeout,
+    SystemTimeError(time::SystemTimeError),
+    UnsupportedSoundCard(String),
+    VPDParseFailed(String, ParseIntError),
+    WorkerPanics(Box<dyn Any + Send + 'static>),
+    ZeroPlayerIsNotRunning,
+    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)
+    }
+}
+
+impl From<cros_alsa::ControlError> for Error {
+    fn from(err: cros_alsa::ControlError) -> Error {
+        Error::AlsaControlError(err)
+    }
+}
+
+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
+    }
+}
+
+impl error::Error for Error {}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use Error::*;
+        match self {
+            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)
+            }
+            FileIOFailed(file_path, e) => write!(f, "{:#?}: {}", file_path, e),
+            InvalidShutDownTime => write!(f, "invalid shutdown time"),
+            InternalSpeakerNotFound => write!(f, "internal speaker is not found in cras"),
+            InvalidTemperature(temp) => write!(
+                f,
+                "invalid calibration temperature: {}, and there is no datastore",
+                temp
+            ),
+            InvalidDatastore => write!(f, "invalid datastore format"),
+            InvalidDSMParam => write!(f, "invalid dsm param from kcontrol"),
+            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),
+            NextPlaybackBufferFailed(e) => write!(f, "{}", e),
+            PlaybackFailed(e) => write!(f, "{}", e),
+            SerdeError(file_path, e) => write!(f, "{:?}: {}", file_path, e),
+            StartPlaybackTimeout => write!(f, "playback is not started in time"),
+            SystemTimeError(e) => write!(f, "{}", e),
+            UnsupportedSoundCard(name) => write!(f, "unsupported sound card: {}", name),
+            VPDParseFailed(file_path, e) => write!(f, "failed to parse vpd {}: {}", file_path, e),
+            WorkerPanics(e) => write!(f, "run_play_zero_worker panics: {:#?}", e),
+            ZeroPlayerIsNotRunning => write!(f, "zero player is not running"),
+            ZeroPlayerIsRunning => write!(f, "zero player is running"),
+        }
+    }
+}
diff --git a/sound_card_init/dsm/src/lib.rs b/sound_card_init/dsm/src/lib.rs
new file mode 100644
index 0000000..0b3ec64
--- /dev/null
+++ b/sound_card_init/dsm/src/lib.rs
@@ -0,0 +1,335 @@
+// 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.
+//! `dsm` crate implements the required initialization workflows for smart amps.
+
+mod datastore;
+mod error;
+pub mod utils;
+mod vpd;
+mod zero_player;
+
+use std::{
+    thread,
+    time::{Duration, SystemTime, UNIX_EPOCH},
+};
+
+use libcras::{CrasClient, CrasNodeType};
+use sys_util::{error, info};
+
+use crate::datastore::Datastore;
+pub use crate::error::{Error, Result};
+use crate::utils::{run_time, shutdown_time};
+use crate::vpd::VPD;
+pub use crate::zero_player::ZeroPlayer;
+
+#[derive(Debug, Clone, Copy)]
+/// `CalibData` represents the calibration data.
+pub struct CalibData {
+    /// The DC resistance of the speaker is DSM unit.
+    pub rdc: 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
+/// DSM::check_speaker_over_heated_workflow.
+pub enum SpeakerStatus {
+    ///`SpeakerStatus::Cold` means the speakers are not overheated and the Amp can
+    /// trigger the boot time calibration.
+    Cold,
+    /// `SpeakerStatus::Hot(Vec<CalibData>)` means the speakers may be too hot for calibration.
+    /// The boot time calibration should be skipped and the Amp should use the previous
+    /// calibration values returned by the enum.
+    Hot(Vec<CalibData>),
+}
+
+/// `DSM`, which implements the required initialization workflows for smart amps.
+pub struct DSM {
+    snd_card: String,
+    num_channels: usize,
+    temp_converter: TempConverter,
+    rdc_to_ohm: fn(i32) -> f32,
+    temp_upper_limit: f32,
+    temp_lower_limit: f32,
+}
+
+impl DSM {
+    const SPEAKER_COOL_DOWN_TIME: Duration = Duration::from_secs(180);
+    const CALI_ERROR_UPPER_LIMIT: f32 = 0.3;
+    const CALI_ERROR_LOWER_LIMIT: f32 = 0.03;
+
+    /// Creates a `DSM`
+    ///
+    /// # Arguments
+    ///
+    /// * `snd_card` - `sound card name`.
+    /// * `num_channels` - `number of channels`.
+    /// * `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.
+    ///
+    /// # Results
+    ///
+    /// * `DSM` - It implements the required initialization workflows for smart amps.
+    pub fn new(
+        snd_card: &str,
+        num_channels: usize,
+        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_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
+    /// SpeakerStatus::Hot(Vec<CalibData>).
+    ///
+    /// # Results
+    ///
+    /// * `SpeakerStatus::Cold` - which means the speakers are not overheated and the Amp can
+    ///    trigger the boot time calibration.
+    /// * `SpeakerStatus::Hot(Vec<CalibData>)` - when the speakers may be too hot. The boot
+    ///   time calibration should be skipped and the Amp should use the previous calibration values
+    ///   returned by the enum.
+    ///
+    /// # Errors
+    ///
+    /// * The speakers are overheated and there are no previous calibration values stored.
+    /// * Cannot determine whether the speakers are overheated as previous shutdown time record is
+    ///   invalid.
+    pub fn check_speaker_over_heated_workflow(&self) -> Result<SpeakerStatus> {
+        if self.is_first_boot() {
+            return Ok(SpeakerStatus::Cold);
+        }
+        match self.is_speaker_over_heated() {
+            Ok(overheated) => {
+                if overheated {
+                    let calib: Vec<CalibData> = (0..self.num_channels)
+                        .map(|ch| -> Result<CalibData> { self.get_previous_calibration_value(ch) })
+                        .collect::<Result<Vec<CalibData>>>()?;
+                    info!("the speakers are hot, the boot time calibration should be skipped");
+                    return Ok(SpeakerStatus::Hot(calib));
+                }
+                Ok(SpeakerStatus::Cold)
+            }
+            Err(err) => {
+                // We cannot assume the speakers are not replaced or not overheated
+                // when the shutdown time file is invalid; therefore we can not use the datastore
+                // value anymore and we can not trigger boot time calibration.
+                for ch in 0..self.num_channels {
+                    if let Err(e) = Datastore::delete(&self.snd_card, ch) {
+                        error!("error delete datastore: {}", e);
+                    }
+                }
+                Err(err)
+            }
+        }
+    }
+
+    /// Decides a good calibration value and updates the stored value according to the following
+    /// logic:
+    /// * Returns the previous value if the ambient temperature is not within a valid range.
+    /// * Returns Error::LargeCalibrationDiff if rdc difference is larger than
+    ///   `CALI_ERROR_UPPER_LIMIT`.
+    /// * Returns the previous value if the rdc difference is smaller than `CALI_ERROR_LOWER_LIMIT`.
+    /// * Returns the boot time calibration value and updates the datastore value if the rdc.
+    ///   difference is between `CALI_ERROR_UPPER_LIMIT` and `CALI_ERROR_LOWER_LIMIT`.
+    ///
+    /// # Arguments
+    ///
+    /// * `card` - `&Card`.
+    /// * `channel` - `channel number`.
+    /// * `calib_data` - `boot time calibrated data`.
+    ///
+    /// # Results
+    ///
+    /// * `CalibData` - the calibration data to be applied according to the deciding logic.
+    ///
+    /// # Errors
+    ///
+    /// * VPD does not exist.
+    /// * rdc difference is larger than `CALI_ERROR_UPPER_LIMIT`.
+    /// * Failed to update Datastore.
+    pub fn decide_calibration_value_workflow(
+        &self,
+        channel: usize,
+        calib_data: CalibData,
+    ) -> Result<CalibData> {
+        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, 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);
+                (false, self.get_vpd_calibration_value(channel)?)
+            }
+        };
+
+        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))
+        } else if diff < Self::CALI_ERROR_LOWER_LIMIT {
+            if !datastore_exist {
+                Datastore::UseVPD.save(&self.snd_card, channel)?;
+            }
+            Ok(previous_calib)
+        } else {
+            Datastore::DSM {
+                rdc: calib_data.rdc,
+                temp: (self.temp_converter.celsius_to_vpd)(calib_data.temp),
+            }
+            .save(&self.snd_card, channel)?;
+            Ok(calib_data)
+        }
+    }
+
+    /// Gets the calibration values from vpd.
+    ///
+    /// # Results
+    ///
+    /// * `Vec<CalibData>` - the calibration values in vpd.
+    ///
+    /// # Errors
+    ///
+    /// * Failed to read vpd.
+    pub fn get_all_vpd_calibration_value(&self) -> Result<Vec<CalibData>> {
+        (0..self.num_channels)
+            .map(|ch| self.get_vpd_calibration_value(ch))
+            .collect::<Result<Vec<_>>>()
+    }
+
+    /// Blocks until the internal speakers are ready.
+    ///
+    /// # Errors
+    ///
+    /// * Failed to wait the internal speakers to be ready.
+    pub fn wait_for_speakers_ready(&self) -> Result<()> {
+        let find_speaker = || -> Result<()> {
+            let cras_client = CrasClient::new().map_err(Error::CrasClientFailed)?;
+            let _node = cras_client
+                .output_nodes()
+                .find(|node| node.node_type == CrasNodeType::CRAS_NODE_TYPE_INTERNAL_SPEAKER)
+                .ok_or(Error::InternalSpeakerNotFound)?;
+            Ok(())
+        };
+        // TODO(b/155007305): Implement cras_client.wait_node_change and use it here.
+        const RETRY: usize = 3;
+        const RETRY_INTERVAL: Duration = Duration::from_millis(500);
+        for _ in 0..RETRY {
+            match find_speaker() {
+                Ok(_) => return Ok(()),
+                Err(e) => error!("retry on finding speaker: {}", e),
+            };
+            thread::sleep(RETRY_INTERVAL);
+        }
+        Err(Error::InternalSpeakerNotFound)
+    }
+
+    fn is_first_boot(&self) -> bool {
+        !run_time::exists(&self.snd_card)
+    }
+
+    // If (Current time - the latest CRAS shutdown time) < cool_down_time, we assume that
+    // the speakers may be overheated.
+    fn is_speaker_over_heated(&self) -> Result<bool> {
+        let last_run = run_time::from_file(&self.snd_card)?;
+        let last_shutdown = shutdown_time::from_file()?;
+        if last_shutdown < last_run {
+            return Err(Error::InvalidShutDownTime);
+        }
+
+        let now = SystemTime::now()
+            .duration_since(UNIX_EPOCH)
+            .map_err(Error::SystemTimeError)?;
+
+        let elapsed = now
+            .checked_sub(last_shutdown)
+            .ok_or(Error::InvalidShutDownTime)?;
+
+        if elapsed < Self::SPEAKER_COOL_DOWN_TIME {
+            return Ok(true);
+        }
+        Ok(false)
+    }
+
+    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 => 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),
+        })
+    }
+}
diff --git a/sound_card_init/utils/src/lib.rs b/sound_card_init/dsm/src/utils.rs
similarity index 93%
rename from sound_card_init/utils/src/lib.rs
rename to sound_card_init/dsm/src/utils.rs
index e8edce7..64f6c97 100644
--- a/sound_card_init/utils/src/lib.rs
+++ b/sound_card_init/dsm/src/utils.rs
@@ -4,19 +4,14 @@
 //! It contains common utils shared within sound_card_init.
 #![deny(missing_docs)]
 
-//! The error definitions for utils.
-pub mod error;
-
 use std::fs::File;
 use std::io::{prelude::*, BufReader, BufWriter};
 use std::path::PathBuf;
 use std::time::Duration;
 
+use crate::datastore::Datastore;
 use crate::error::{Error, Result};
 
-/// The path of datastore.
-pub const DATASTORE_DIR: &str = "/var/lib/sound_card_init";
-
 fn duration_from_file(path: &PathBuf) -> Result<Duration> {
     let reader =
         BufReader::new(File::open(&path).map_err(|e| Error::FileIOFailed(path.clone(), e))?);
@@ -80,7 +75,7 @@
     }
 
     fn run_time_file(snd_card: &str) -> PathBuf {
-        PathBuf::from(DATASTORE_DIR)
+        PathBuf::from(Datastore::DATASTORE_DIR)
             .join(snd_card)
             .join(RUN_TIME_FILE)
     }
diff --git a/sound_card_init/max98390d/src/vpd.rs b/sound_card_init/dsm/src/vpd.rs
similarity index 68%
rename from sound_card_init/max98390d/src/vpd.rs
rename to sound_card_init/dsm/src/vpd.rs
index f5ac322..b00864c 100644
--- a/sound_card_init/max98390d/src/vpd.rs
+++ b/sound_card_init/dsm/src/vpd.rs
@@ -13,25 +13,26 @@
 /// `VPD`, which represents the amplifier factory calibration values.
 #[derive(Default, Debug)]
 pub struct VPD {
-    /// dsm_calib_r0 is (11 / 3) / actual_rdc * 2^20.
     pub dsm_calib_r0: i32,
-    /// dsm_calib_temp is actual_temp * 2^12 / 100.
     pub dsm_calib_temp: i32,
 }
 
 impl VPD {
-    /// Creates a `VPD` and initializes its fields from the given VPD files.
-    pub fn from_file(rdc_file: &str, temp_file: &str) -> Result<VPD> {
+    /// Creates a `VPD` and initializes its fields from VPD_DIR/dsm_calib_r0_{channel}.
+    /// # Arguments
+    ///
+    /// * `channel` - channel number.
+    pub fn new(channel: usize) -> Result<VPD> {
         let mut vpd: VPD = Default::default();
-        vpd.dsm_calib_r0 = read_vpd_files(rdc_file)?;
-        vpd.dsm_calib_temp = read_vpd_files(temp_file)?;
+        vpd.dsm_calib_r0 = read_vpd_files(&format!("dsm_calib_r0_{}", channel))?;
+        vpd.dsm_calib_temp = read_vpd_files(&format!("dsm_calib_temp_{}", channel))?;
         Ok(vpd)
     }
 }
 
 fn read_vpd_files(file: &str) -> Result<i32> {
     let path = PathBuf::from(VPD_DIR).with_file_name(file);
-    let io_err = |e| Error::FileIOFailed(path.to_string_lossy().to_string(), e);
+    let io_err = |e| Error::FileIOFailed(path.to_owned(), e);
     let mut reader = BufReader::new(File::open(&path).map_err(io_err)?);
     let mut line = String::new();
     reader.read_line(&mut line).map_err(io_err)?;
diff --git a/sound_card_init/dsm/src/zero_player.rs b/sound_card_init/dsm/src/zero_player.rs
new file mode 100644
index 0000000..441f7ff
--- /dev/null
+++ b/sound_card_init/dsm/src/zero_player.rs
@@ -0,0 +1,209 @@
+// 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::io::Write;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::{Arc, Condvar, Mutex};
+use std::thread;
+use std::thread::JoinHandle;
+use std::time::Duration;
+
+use audio_streams::SampleFormat;
+use libcras::{CrasClient, CrasNodeType};
+use sys_util::error;
+
+use crate::error::{Error, Result};
+
+/// `ZeroPlayer` provides the functionality to play zeros sample in the background thread.
+#[derive(Default)]
+pub struct ZeroPlayer {
+    thread_info: Option<PlayZeroWorkerInfo>,
+}
+
+impl Drop for ZeroPlayer {
+    fn drop(&mut self) {
+        if self.thread_info.is_some() {
+            if let Err(e) = self.stop() {
+                error!("{}", e);
+            }
+        }
+    }
+}
+
+impl ZeroPlayer {
+    /// It takes about 400 ms to get CRAS_NODE_TYPE_INTERNAL_SPEAKER during the boot time.
+    const TIMEOUT: Duration = Duration::from_millis(1000);
+
+    /// Returns whether the ZeroPlayer is running.
+    pub fn running(&self) -> bool {
+        self.thread_info.is_some()
+    }
+
+    /// Starts to play zeros for at most `max_playback_time`.
+    /// This function blocks and returns until playback has started for `min_playback_time`.
+    /// This function must be called when self.running() returns false.
+    ///
+    /// # Arguments
+    ///
+    /// * `min_playback_time` - It blocks and returns until playback has started for
+    ///                         `min_playback_time`.
+    ///
+    /// # Errors
+    ///
+    /// * If it's called when the `ZeroPlayer` is already running.
+    /// * Failed to find internal speakers.
+    /// * Failed to start the background thread.
+    pub fn start(&mut self, min_playback_time: Duration) -> Result<()> {
+        if self.running() {
+            return Err(Error::ZeroPlayerIsRunning);
+        }
+        self.thread_info = Some(PlayZeroWorkerInfo::new(min_playback_time));
+        if let Some(thread_info) = &mut self.thread_info {
+            // Block until playback of zeros has started for min_playback_time or timeout.
+            let (lock, cvar) = &*(thread_info.ready);
+            let result = cvar.wait_timeout_while(
+                lock.lock()?,
+                min_playback_time + ZeroPlayer::TIMEOUT,
+                |&mut is_ready| !is_ready,
+            )?;
+            if result.1.timed_out() {
+                return Err(Error::StartPlaybackTimeout);
+            }
+        }
+        Ok(())
+    }
+
+    /// Stops playing zeros in the background thread.
+    /// This function must be called when self.running() returns true.
+    ///
+    /// # Errors
+    ///
+    /// * If it's called again when the `ZeroPlayer` is not running.
+    /// * Failed to play zeros to internal speakers via CRAS client.
+    /// * Failed to join the background thread.
+    pub fn stop(&mut self) -> Result<()> {
+        match self.thread_info.take() {
+            Some(mut thread_info) => Ok(thread_info.destroy()?),
+            None => Err(Error::ZeroPlayerIsNotRunning),
+        }
+    }
+}
+
+// Audio thread book-keeping data
+struct PlayZeroWorkerInfo {
+    thread: Option<JoinHandle<Result<()>>>,
+    // Uses `thread_run` to notify the background thread to stop.
+    thread_run: Arc<AtomicBool>,
+    // The background thread uses `ready` to notify the main thread that playback
+    // of zeros has started for min_playback_time.
+    ready: Arc<(Mutex<bool>, Condvar)>,
+}
+
+impl Drop for PlayZeroWorkerInfo {
+    fn drop(&mut self) {
+        if let Err(e) = self.destroy() {
+            error!("{}", e);
+        }
+    }
+}
+
+impl PlayZeroWorkerInfo {
+    // Spawns the PlayZeroWorker.
+    fn new(min_playback_time: Duration) -> Self {
+        let thread_run = Arc::new(AtomicBool::new(false));
+        let ready = Arc::new((Mutex::new(false), Condvar::new()));
+        let mut worker = PlayZeroWorker::new(min_playback_time, thread_run.clone(), ready.clone());
+        Self {
+            thread: Some(thread::spawn(move || -> Result<()> {
+                worker.run()?;
+                Ok(())
+            })),
+            thread_run,
+            ready,
+        }
+    }
+
+    // Joins the PlayZeroWorker.
+    fn destroy(&mut self) -> Result<()> {
+        self.thread_run.store(false, Ordering::Relaxed);
+        if let Some(handle) = self.thread.take() {
+            let res = handle.join().map_err(Error::WorkerPanics)?;
+            return match res {
+                Err(e) => Err(e),
+                Ok(_) => Ok(()),
+            };
+        }
+        Ok(())
+    }
+}
+
+struct PlayZeroWorker {
+    min_playback_time: Duration,
+    // Uses `thread_run` to notify the background thread to stop.
+    thread_run: Arc<AtomicBool>,
+    // The background thread uses `ready` to notify the main thread that playback
+    // of zeros has started for min_playback_time.
+    ready: Arc<(Mutex<bool>, Condvar)>,
+}
+
+impl PlayZeroWorker {
+    const FRAMES_PER_BUFFER: usize = 256;
+    const FRAME_RATE: u32 = 48000;
+    const NUM_CHANNELS: usize = 2;
+    const FORMAT: SampleFormat = SampleFormat::S16LE;
+
+    fn new(
+        min_playback_time: Duration,
+        thread_run: Arc<AtomicBool>,
+        ready: Arc<(Mutex<bool>, Condvar)>,
+    ) -> Self {
+        Self {
+            min_playback_time,
+            thread_run,
+            ready,
+        }
+    }
+
+    fn run(&mut self) -> Result<()> {
+        let mut cras_client = CrasClient::new().map_err(Error::CrasClientFailed)?;
+        // TODO(b/155007305): Implement cras_client.wait_node_change and use it here.
+        let node = cras_client
+            .output_nodes()
+            .find(|node| node.node_type == CrasNodeType::CRAS_NODE_TYPE_INTERNAL_SPEAKER)
+            .ok_or(Error::InternalSpeakerNotFound)?;
+        let local_buffer =
+            vec![0u8; Self::FRAMES_PER_BUFFER * Self::NUM_CHANNELS * Self::FORMAT.sample_bytes()];
+        let min_playback_iterations = (Self::FRAME_RATE
+            * self.min_playback_time.as_millis() as u32)
+            / Self::FRAMES_PER_BUFFER as u32
+            / 1000;
+        let (_control, mut stream) = cras_client
+            .new_pinned_playback_stream(
+                node.iodev_index,
+                Self::NUM_CHANNELS,
+                Self::FORMAT,
+                Self::FRAME_RATE,
+                Self::FRAMES_PER_BUFFER,
+            )
+            .map_err(|e| Error::NewPlayStreamFailed(e))?;
+
+        let mut iter = 0;
+        self.thread_run.store(true, Ordering::Relaxed);
+        while self.thread_run.load(Ordering::Relaxed) {
+            let mut buffer = stream
+                .next_playback_buffer()
+                .map_err(|e| Error::NextPlaybackBufferFailed(e))?;
+            let _write_frames = buffer.write(&local_buffer).map_err(Error::PlaybackFailed)?;
+
+            // Notifies the main thread that playback of zeros has started for min_playback_time.
+            if iter == min_playback_iterations {
+                let (lock, cvar) = &*self.ready;
+                let mut is_ready = lock.lock()?;
+                *is_ready = true;
+                cvar.notify_one();
+            }
+            iter += 1;
+        }
+        Ok(())
+    }
+}
diff --git a/sound_card_init/max98390d/src/amp_calibration.rs b/sound_card_init/max98390d/src/amp_calibration.rs
deleted file mode 100644
index 3534022..0000000
--- a/sound_card_init/max98390d/src/amp_calibration.rs
+++ /dev/null
@@ -1,319 +0,0 @@
-// 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::fmt;
-use std::io::Write;
-use std::sync::atomic::{AtomicBool, Ordering};
-use std::sync::{Arc, Condvar, Mutex};
-use std::thread;
-use std::thread::JoinHandle;
-use std::time::{Duration, Instant};
-
-use audio_streams::SampleFormat;
-use cros_alsa::{Card, IntControl, SwitchControl};
-use libcras::{CrasClient, CrasNodeType};
-use sys_util::{error, info};
-
-use crate::{
-    datastore::Datastore,
-    error::{Error, Result},
-    settings::AmpCalibSettings,
-    vpd::VPD,
-};
-
-const CALI_ERROR_UPPER_LIMIT: f32 = 0.3;
-const CALI_ERROR_LOWER_LIMIT: f32 = 0.03;
-
-const FRAMES_PER_BUFFER: usize = 256;
-const FRAME_RATE: u32 = 48000;
-const NUM_CHANNELS: usize = 2;
-const FORMAT: SampleFormat = SampleFormat::S16LE;
-const DURATION_MS: u32 = 1000;
-const WARM_UP_DURATION_MS: u32 = 300;
-
-/// Amp volume mode emulation used by set_volume().
-#[derive(PartialEq)]
-pub enum VolumeMode {
-    /// Low mode protects the speaker by limiting its output volume if the
-    /// calibration has not been completed successfully.
-    Low,
-    /// High mode removes the speaker output volume limitation after
-    /// having successfully completed the calibration.
-    High,
-}
-
-/// It implements the amplifier boot time calibration flow.
-pub struct AmpCalibration<'a> {
-    card: &'a mut Card,
-    setting: AmpCalibSettings,
-}
-
-impl<'a> fmt::Debug for AmpCalibration<'a> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.debug_struct("AmpCalibration")
-            .field("snd_card_id", &self.card.name())
-            .field("amp_calib", &self.setting)
-            .finish()
-    }
-}
-
-impl<'a> AmpCalibration<'a> {
-    /// Creates an `AmpCalibration`.
-    /// # Arguments
-    ///
-    /// * `card` - `&Card`.
-    /// * `setting` - `AmpCalibSettings`.
-    ///
-    /// # Results
-    ///
-    /// * `AmpCalibration` - It implements the amplifier boot time calibration flow.
-    ///
-    /// # Errors
-    ///
-    /// * If `Card` creation from sound card name fails.
-    pub fn new(card: &mut Card, setting: AmpCalibSettings) -> Result<AmpCalibration> {
-        let amp = AmpCalibration { card, setting };
-
-        Ok(amp)
-    }
-
-    /// Sets card volume control to given VolumeMode.
-    pub fn set_volume(&mut self, mode: VolumeMode) -> Result<()> {
-        match mode {
-            VolumeMode::High => self
-                .card
-                .control_by_name::<IntControl>(&self.setting.amp.volume_ctrl)?
-                .set(self.setting.amp.volume_high_limit)?,
-            VolumeMode::Low => self
-                .card
-                .control_by_name::<IntControl>(&self.setting.amp.volume_ctrl)?
-                .set(self.setting.amp.volume_low_limit)?,
-        }
-        Ok(())
-    }
-
-    /// The implementation of max98390d boot time calibration logic.
-    ///
-    /// The boot time calibration logic includes the following steps:
-    ///  * Gets results from `do_calibration`.
-    ///  * Decides whether the new calibration result should replace the stored value.
-    ///  * Applies a good calibration value.
-    pub fn run(&mut self) -> Result<()> {
-        let vpd = VPD::from_file(&self.setting.rdc_vpd, &self.setting.temp_vpd)?;
-        let (rdc_cali, temp_cali) = self.do_calibration()?;
-        let datastore = match Datastore::from_file(self.card.name(), &self.setting.calib_file) {
-            Ok(sci_calib) => Some(sci_calib),
-            Err(e) => {
-                info!("failure in Datastore::from_file: {}", e);
-                None
-            }
-        };
-
-        // 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.
-        let rdc_diff = |x: i32, x_ref: i32| (x - x_ref).abs() as f32 / x as f32;
-
-        let diff: f32 = match datastore {
-            None => rdc_diff(rdc_cali, vpd.dsm_calib_r0),
-            Some(d) => match d {
-                Datastore::UseVPD => rdc_diff(rdc_cali, vpd.dsm_calib_r0),
-                Datastore::DSM { rdc, .. } => rdc_diff(rdc_cali, rdc),
-            },
-        };
-
-        if !self.validate_temperature(temp_cali) {
-            info!("invalid temperature: {}.", temp_cali);
-            return match datastore {
-                None => Err(Error::InvalidTemperature(temp_cali)),
-                Some(d) => self.apply_datastore(d),
-            };
-        }
-
-        if diff > CALI_ERROR_UPPER_LIMIT {
-            return Err(Error::LargeCalibrationDiff(rdc_cali, temp_cali));
-        } else if diff < CALI_ERROR_LOWER_LIMIT {
-            match datastore {
-                None => Datastore::UseVPD.save(self.card.name(), &self.setting.calib_file)?,
-                Some(d) => self.apply_datastore(d)?,
-            }
-        } else {
-            info!("apply boot time calibration values.");
-            self.card
-                .control_by_name::<IntControl>(&self.setting.amp.rdc_ctrl)?
-                .set(rdc_cali)?;
-            self.card
-                .control_by_name::<IntControl>(&self.setting.amp.temp_ctrl)?
-                .set(temp_cali)?;
-            Datastore::DSM {
-                rdc: rdc_cali,
-                ambient_temp: temp_cali,
-            }
-            .save(self.card.name(), &self.setting.calib_file)?;
-        }
-        Ok(())
-    }
-
-    fn apply_datastore(&mut self, d: Datastore) -> Result<()> {
-        info!("apply datastore values.");
-        match d {
-            Datastore::UseVPD => Ok(()),
-            Datastore::DSM { rdc, ambient_temp } => {
-                self.card
-                    .control_by_name::<IntControl>(&self.setting.amp.rdc_ctrl)?
-                    .set(rdc)?;
-                self.card
-                    .control_by_name::<IntControl>(&self.setting.amp.temp_ctrl)?
-                    .set(ambient_temp)?;
-                Ok(())
-            }
-        }
-    }
-
-    fn validate_temperature(&self, temp: i32) -> bool {
-        temp < self.setting.amp.temp_upper_limit && temp > self.setting.amp.temp_lower_limit
-    }
-
-    /// Triggers the amplifier calibration and reads the calibrated rdc and ambient_temp value
-    /// from the mixer control.
-    /// To get accurate calibration results, the main thread calibrates the amplifier while
-    /// the another thread plays zeros to the speakers.
-    fn do_calibration(&mut self) -> Result<(i32, i32)> {
-        // The playback worker uses `playback_started` to notify the main thread that playback
-        // of zeros has started.
-        let playback_started = Arc::new((Mutex::new(false), Condvar::new()));
-        // Shares `calib_finished` to the playback worker and uses it to notify the worker when
-        // the calibration is finished.
-        let calib_finished = Arc::new(AtomicBool::new(false));
-        let handle =
-            AmpCalibration::run_play_zero_worker(playback_started.clone(), calib_finished.clone())?;
-
-        // Waits until zero playback starts or timeout.
-        let mut timeout = Duration::from_millis(1000);
-        let (lock, cvar) = &*playback_started;
-        let mut started = lock.lock()?;
-        while !*started {
-            let start_time = Instant::now();
-            started = cvar.wait_timeout(started, timeout)?.0;
-            if *started {
-                break;
-            } else {
-                let elapsed = start_time.elapsed();
-                if elapsed > timeout {
-                    return Err(Error::StartPlaybackTimeout);
-                } else {
-                    // Spurious wakes. Decrements the sleep duration by the amount slept.
-                    timeout -= start_time.elapsed();
-                }
-            }
-        }
-
-        // Playback of zeros is started, and the main thread can start the calibration.
-        self.card
-            .control_by_name::<SwitchControl>(&self.setting.amp.calib_ctrl)?
-            .on()?;
-        let rdc = self
-            .card
-            .control_by_name::<IntControl>(&self.setting.amp.rdc_ctrl)?
-            .get()?;
-        let temp = self
-            .card
-            .control_by_name::<IntControl>(&self.setting.amp.temp_ctrl)?
-            .get()?;
-        self.card
-            .control_by_name::<SwitchControl>(&self.setting.amp.calib_ctrl)?
-            .off()?;
-        // Notifies the play_zero_worker that the calibration is finished.
-        calib_finished.store(true, Ordering::Relaxed);
-
-        // If play_zero_worker has error during the calibration, returns an error to keep the volume
-        // low to protect the speaker.
-        match handle.join() {
-            Ok(res) => {
-                if let Err(e) = res {
-                    error!("run_play_zero_worker has error: {}", e);
-                    return Err(e);
-                }
-            }
-            Err(e) => {
-                error!("run_play_zero_worker panics: {:?}", e);
-                return Err(Error::WorkerPanics);
-            }
-        }
-
-        Ok((rdc, temp))
-    }
-
-    // Creates a thread to play zeros to the internal speakers.
-    fn run_play_zero_worker(
-        playback_started: Arc<(Mutex<bool>, Condvar)>,
-        calib_finished: Arc<AtomicBool>,
-    ) -> Result<JoinHandle<Result<()>>> {
-        let mut cras_client = CrasClient::new().map_err(Error::CrasClientFailed)?;
-        // TODO(b/155007305): Implement cras_client.wait_node_change and use it here.
-        let node = cras_client
-            .output_nodes()
-            .find(|node| node.node_type == CrasNodeType::CRAS_NODE_TYPE_INTERNAL_SPEAKER)
-            .ok_or(Error::InternalSpeakerNotFound)?;
-
-        let handle = thread::spawn(move || -> Result<()> {
-            let local_buffer = [0u8; FRAMES_PER_BUFFER * NUM_CHANNELS * 2];
-            let iterations = (FRAME_RATE * DURATION_MS) / FRAMES_PER_BUFFER as u32 / 1000;
-            let warm_up_iterations =
-                (FRAME_RATE * WARM_UP_DURATION_MS) / FRAMES_PER_BUFFER as u32 / 1000;
-
-            let (_control, mut stream) = cras_client
-                .new_pinned_playback_stream(
-                    node.iodev_index,
-                    NUM_CHANNELS,
-                    FORMAT,
-                    FRAME_RATE,
-                    FRAMES_PER_BUFFER,
-                )
-                .map_err(|e| Error::NewPlayStreamFailed(e))?;
-
-            // Plays zeros for at most DURATION_MS.
-            for i in 0..iterations {
-                if calib_finished.load(Ordering::Relaxed) {
-                    break;
-                }
-                let mut buffer = stream
-                    .next_playback_buffer()
-                    .map_err(|e| Error::NextPlaybackBufferFailed(e))?;
-                let _write_frames = buffer.write(&local_buffer).map_err(Error::PlaybackFailed)?;
-
-                // Notifies the main thread to start the calibration.
-                // The mute playing time need to be longer than WARM_UP_DURATION_MS to get rdc properly.
-                if i == warm_up_iterations {
-                    let (lock, cvar) = &*playback_started;
-                    let mut started = lock.lock()?;
-                    *started = true;
-                    cvar.notify_one();
-                }
-                // The playback_started lock is unlocked here when `started` goes out of scope.
-            }
-
-            // Returns an error if the calibration is not finished before playback stops.
-            if !calib_finished.load(Ordering::Relaxed) {
-                return Err(Error::CalibrationTimeout);
-            }
-            Ok(())
-        });
-
-        Ok(handle)
-    }
-
-    /// Skips max98390d boot time calibration when the speaker may be hot.
-    ///
-    /// If datastore exists, applies the stored value and sets volume to high.
-    /// If datastore does not exist, sets volume to low.
-    pub fn hot_speaker_workflow(&mut self) -> Result<()> {
-        if let Ok(sci_calib) = Datastore::from_file(self.card.name(), &self.setting.calib_file) {
-            self.apply_datastore(sci_calib)?;
-            self.set_volume(VolumeMode::High)?;
-            return Ok(());
-        }
-        info!("no datastore, set volume low");
-        self.set_volume(VolumeMode::Low)
-    }
-}
diff --git a/sound_card_init/max98390d/src/datastore.rs b/sound_card_init/max98390d/src/datastore.rs
deleted file mode 100644
index 12ebff7..0000000
--- a/sound_card_init/max98390d/src/datastore.rs
+++ /dev/null
@@ -1,56 +0,0 @@
-// 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::fs::File;
-use std::io::{prelude::*, BufReader, BufWriter};
-use std::path::PathBuf;
-
-use serde::{Deserialize, Serialize};
-use sys_util::info;
-use utils::DATASTORE_DIR;
-
-use crate::error::{Error, Result};
-
-/// `Datastore`, which stores and reads calibration values in yaml format.
-#[derive(Debug, Deserialize, Serialize, Copy, Clone)]
-pub enum Datastore {
-    /// Indicates using values in VPD.
-    UseVPD,
-    /// rdc is (11 / 3) / actual_rdc * 2^20.
-    /// ambient_temp is actual_temp * 2^12 / 100.
-    DSM { rdc: i32, ambient_temp: i32 },
-}
-
-impl Datastore {
-    /// Creates a `Datastore` and initializes its fields from DATASTORE_DIR/<snd_card>/{file}.
-    pub fn from_file(snd_card: &str, file: &str) -> Result<Datastore> {
-        let path = PathBuf::from(DATASTORE_DIR).join(snd_card).join(file);
-
-        let io_err = |e| Error::FileIOFailed(path.to_string_lossy().to_string(), e);
-        let parse_err = |e: serde_yaml::Error| {
-            Error::DeserializationFailed(path.to_string_lossy().to_string(), e)
-        };
-
-        let reader = BufReader::new(File::open(&path).map_err(io_err)?);
-        let datastore: Datastore = serde_yaml::from_reader(reader).map_err(parse_err)?;
-        Ok(datastore)
-    }
-
-    /// Saves a `Datastore` to DATASTORE_DIR/<snd_card>/{file}.
-    pub fn save(&self, snd_card: &str, file: &str) -> Result<()> {
-        let path = PathBuf::from(DATASTORE_DIR).join(snd_card).join(file);
-        let io_err = |e| Error::FileIOFailed(path.to_string_lossy().to_string(), e);
-
-        let mut writer = BufWriter::new(File::create(&path).map_err(io_err)?);
-        writer
-            .write(
-                serde_yaml::to_string(self)
-                    .map_err(Error::SerializationFailed)?
-                    .as_bytes(),
-            )
-            .map_err(io_err)?;
-        writer.flush().map_err(io_err)?;
-        info!("update Datastore {}: {:?}", path.to_string_lossy(), self);
-        Ok(())
-    }
-}
diff --git a/sound_card_init/max98390d/src/error.rs b/sound_card_init/max98390d/src/error.rs
deleted file mode 100644
index 778ff96..0000000
--- a/sound_card_init/max98390d/src/error.rs
+++ /dev/null
@@ -1,102 +0,0 @@
-// 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::error;
-use std::fmt;
-use std::io;
-use std::num::ParseIntError;
-use std::sync::PoisonError;
-use std::time;
-
-use remain::sorted;
-
-pub type Result<T> = std::result::Result<T, Error>;
-
-#[sorted]
-#[derive(Debug)]
-pub enum Error {
-    AlsaCardError(cros_alsa::CardError),
-    AlsaControlError(cros_alsa::ControlError),
-    CalibrationFailed,
-    CalibrationTimeout,
-    CrasClientFailed(libcras::Error),
-    DeserializationFailed(String, serde_yaml::Error),
-    FileIOFailed(String, io::Error),
-    HotSpeaker,
-    InternalSpeakerNotFound,
-    InvalidDatastore,
-    InvalidShutDownTime,
-    InvalidTemperature(i32),
-    LargeCalibrationDiff(i32, i32),
-    MissingDSMParam,
-    MutexPoisonError,
-    NewPlayStreamFailed(libcras::BoxError),
-    NextPlaybackBufferFailed(libcras::BoxError),
-    PlaybackFailed(io::Error),
-    ReadTimestampFailed(utils::error::Error),
-    SerializationFailed(serde_yaml::Error),
-    StartPlaybackTimeout,
-    SystemTimeError(time::SystemTimeError),
-    VPDParseFailed(String, ParseIntError),
-    WorkerPanics,
-}
-
-impl From<cros_alsa::CardError> for Error {
-    fn from(err: cros_alsa::CardError) -> Error {
-        Error::AlsaCardError(err)
-    }
-}
-
-impl From<cros_alsa::ControlError> for Error {
-    fn from(err: cros_alsa::ControlError) -> Error {
-        Error::AlsaControlError(err)
-    }
-}
-
-impl<T> From<PoisonError<T>> for Error {
-    fn from(_: PoisonError<T>) -> Error {
-        Error::MutexPoisonError
-    }
-}
-
-impl error::Error for Error {}
-
-impl fmt::Display for Error {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        use Error::*;
-        match self {
-            AlsaCardError(e) => write!(f, "{}", e),
-            AlsaControlError(e) => write!(f, "{}", e),
-            CalibrationFailed => write!(f, "amp calibration failed"),
-            CalibrationTimeout => write!(f, "calibration is not finished in time"),
-            CrasClientFailed(e) => write!(f, "failed to create cras client: {}", e),
-            DeserializationFailed(file, e) => write!(f, "failed to parse {}: {}", file, e),
-            FileIOFailed(file, e) => write!(f, "{}: {}", file, e),
-            InvalidShutDownTime => write!(f, "invalid shutdown time"),
-            InternalSpeakerNotFound => write!(f, "internal speaker is not found in cras"),
-            InvalidTemperature(temp) => write!(
-                f,
-                "invalid calibration temperature: {}, and there is no datastore",
-                temp
-            ),
-            InvalidDatastore => write!(f, "invalid datastore format"),
-            HotSpeaker => write!(f, "skip boot time calibration as the speakers may be hot"),
-            LargeCalibrationDiff(rdc, temp) => write!(
-                f,
-                "calibration difference is too large, rdc: {}, temp: {}",
-                rdc, temp
-            ),
-            MissingDSMParam => write!(f, "missing dsm_param.bin"),
-            MutexPoisonError => write!(f, "mutex is poisoned"),
-            NewPlayStreamFailed(e) => write!(f, "{}", e),
-            NextPlaybackBufferFailed(e) => write!(f, "{}", e),
-            PlaybackFailed(e) => write!(f, "{}", e),
-            ReadTimestampFailed(e) => write!(f, "{}", e),
-            SerializationFailed(e) => write!(f, "failed to serialize yaml: {}", e),
-            StartPlaybackTimeout => write!(f, "playback is not started in time"),
-            SystemTimeError(e) => write!(f, "{}", e),
-            VPDParseFailed(file, e) => write!(f, "failed to parse vpd {}: {}", file, e),
-            WorkerPanics => write!(f, "run_play_zero_worker panics"),
-        }
-    }
-}
diff --git a/sound_card_init/max98390d/src/lib.rs b/sound_card_init/max98390d/src/lib.rs
deleted file mode 100644
index 44b1c61..0000000
--- a/sound_card_init/max98390d/src/lib.rs
+++ /dev/null
@@ -1,153 +0,0 @@
-// 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.
-//! `max98390d` crate implements the required initialization workflows.
-//! It currently supports boot time calibration for max98390d.
-#![deny(missing_docs)]
-mod amp_calibration;
-mod datastore;
-mod error;
-mod settings;
-mod vpd;
-
-use std::fs;
-use std::path::{Path, PathBuf};
-use std::time::{Duration, SystemTime, UNIX_EPOCH};
-
-use cros_alsa::Card;
-use sys_util::error;
-use utils::{run_time, shutdown_time, DATASTORE_DIR};
-
-use crate::amp_calibration::{AmpCalibration, VolumeMode};
-use crate::error::{Error, Result};
-use crate::settings::DeviceSettings;
-
-const SPEAKER_COOL_DOWN_TIME: Duration = Duration::from_secs(180);
-
-/// Performs max98390d boot time calibration.
-///
-///
-/// # Arguments
-///
-/// * `snd_card` - The sound card name, ex: sofcmlmax98390d.
-/// * `conf` - The `DeviceSettings` in yaml format.
-///
-/// # Errors
-///
-/// If any amplifiers fail to complete the calibration.
-pub fn run_max98390d(snd_card: &str, conf: &str) -> Result<()> {
-    let settings = DeviceSettings::from_yaml_str(conf)?;
-    let mut card = Card::new(snd_card)?;
-
-    if !Path::new(&settings.dsm_param).exists() {
-        set_all_volume_low(&mut card, &settings);
-        return Err(Error::MissingDSMParam);
-    }
-
-    // Needs to check whether the speakers are over heated if it is not the first time boot.
-    if run_time::exists(snd_card) {
-        if let Err(err) = check_speaker_over_heated(snd_card, SPEAKER_COOL_DOWN_TIME) {
-            match err {
-                Error::HotSpeaker => run_all_hot_speaker_workflow(&mut card, &settings),
-                _ => {
-                    // We cannot assume the speakers are not replaced or not over heated
-                    // when the shutdown time file is invalid; therefore we can not use the datastore
-                    // value anymore and we can not trigger boot time calibration.
-                    del_all_datastore(snd_card, &settings);
-                    set_all_volume_low(&mut card, &settings);
-                }
-            };
-            return Err(err);
-        };
-    }
-
-    // If some error occurs during the calibration, the iteration will continue running the
-    // calibration for the next amp.
-    let results: Vec<Result<()>> = settings
-        .amp_calibrations
-        .into_iter()
-        .map(|s| {
-            let mut amp_calib = AmpCalibration::new(&mut card, s)?;
-            amp_calib.set_volume(VolumeMode::Low)?;
-            amp_calib.run()?;
-            amp_calib.set_volume(VolumeMode::High)?;
-            Ok(())
-        })
-        .filter_map(|res| res.err())
-        .map(|e| {
-            error!("calibration error: {}. volume remains low.", e);
-            Err(e)
-        })
-        .collect();
-
-    if !results.is_empty() {
-        return Err(Error::CalibrationFailed);
-    }
-
-    Ok(())
-}
-
-fn del_all_datastore(snd_card: &str, settings: &DeviceSettings) {
-    for s in &settings.amp_calibrations {
-        if let Err(e) = fs::remove_file(
-            PathBuf::from(DATASTORE_DIR)
-                .join(snd_card)
-                .join(&s.calib_file),
-        ) {
-            error!("failed to remove datastore: {}.", e);
-        }
-    }
-}
-
-fn run_all_hot_speaker_workflow(card: &mut Card, settings: &DeviceSettings) {
-    for s in &settings.amp_calibrations {
-        let mut amp_calib = match AmpCalibration::new(card, s.clone()) {
-            Ok(amp) => amp,
-            Err(e) => {
-                error!("{}.", e);
-                continue;
-            }
-        };
-        if let Err(e) = amp_calib.hot_speaker_workflow() {
-            error!("failed to run hot_speaker_workflow: {}.", e);
-        }
-    }
-}
-
-fn set_all_volume_low(card: &mut Card, settings: &DeviceSettings) {
-    for s in &settings.amp_calibrations {
-        let mut amp_calib = match AmpCalibration::new(card, s.clone()) {
-            Ok(amp) => amp,
-            Err(e) => {
-                error!("{}.", e);
-                continue;
-            }
-        };
-        if let Err(e) = amp_calib.set_volume(VolumeMode::Low) {
-            error!("failed to set volume to low: {}.", e);
-        }
-    }
-}
-
-// If (Current time - the latest CRAS shutdown time) < cool_down_time, we assume that
-// the speakers may be over heated.
-fn check_speaker_over_heated(snd_card: &str, cool_down_time: Duration) -> Result<()> {
-    let last_run = run_time::from_file(snd_card).map_err(Error::ReadTimestampFailed)?;
-    let last_shutdown = shutdown_time::from_file().map_err(Error::ReadTimestampFailed)?;
-    if last_shutdown < last_run {
-        return Err(Error::InvalidShutDownTime);
-    }
-
-    let now = SystemTime::now()
-        .duration_since(UNIX_EPOCH)
-        .map_err(Error::SystemTimeError)?;
-
-    let elapsed = now
-        .checked_sub(last_shutdown)
-        .ok_or(Error::InvalidShutDownTime)?;
-
-    if elapsed < cool_down_time {
-        return Err(Error::HotSpeaker);
-    }
-    Ok(())
-}
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..d06f225 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,81 @@
 # 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
+getdents64: 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
+clock_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
diff --git a/sound_card_init/sound_card_init.conf b/sound_card_init/sound_card_init.conf
index 7ab0211..40bc88f 100644
--- a/sound_card_init/sound_card_init.conf
+++ b/sound_card_init/sound_card_init.conf
@@ -31,44 +31,50 @@
   fi
 end script
 
-# Here (in order) are a list of the args added:
-# --uts: Create and enter new UTS namespace (hostname/NIS domain name).
-# -e: doesn't need network access.
-# -l: process doesn't use SysV shared memory or IPC.
-# -N: doesn't need to modify control groups settings.
-# -v: run inside a new VFS namespace.
-# -p -r: process doesn't need to access other processes in the system.
-# -n: process doesn't need new privileges.
-# -P: set /mnt/empty as the root fs.
-# -b: bind /
-# -k: Get a writeable and empty /run tmpfs path.
-# -b: need /run/cras to connect cras.
-# -b: /run/systemd/journal: needed for syslog.
-# -b: need /dev to send ioctls to the system's block devices.
-# -k: empty /sys tmpfs path.
-# -b: need /sys/firmware/vpd/ro/ access to read the default calibration value in vpd.
-# -k: get a writeable and empty /var tmpfs path.
-# -b: need /var/lib/sound_card_init/$SOUND_CARD_ID writable access for datastore update.
-# -b: need /var/lib/cras readable
-exec minijail0 \
-    --uts \
-    -e \
-    -l \
-    -N \
-    -v \
-    -p -r \
-    -n \
-    -P /mnt/empty \
-    -b / \
-    -k 'tmpfs,/run,tmpfs,MS_NODEV|MS_NOEXEC|MS_NOSUID,mode=755,size=10M' \
-    -b /run/cras \
-    -b /run/systemd/journal \
-    -b /dev \
-    -k 'tmpfs,/sys,tmpfs,MS_NODEV|MS_NOEXEC|MS_NOSUID,mode=755,size=10M' \
-    -b /sys/firmware/vpd/ro/ \
-    -k 'tmpfs,/var,tmpfs,MS_NODEV|MS_NOEXEC|MS_NOSUID,mode=755,size=10M' \
-    -b /var/lib/sound_card_init/"${SOUND_CARD_ID}"/,,1 \
-    -b /var/lib/cras/ \
-    -u sound_card_init -g sound_card_init -G \
-    -S /usr/share/policy/sound_card_init-seccomp.policy \
-    /usr/bin/sound_card_init "--id=${SOUND_CARD_ID}"
+
+script
+  CONFIG="$(cros_config /audio/main sound-card-init-conf)"
+  if [ -f /etc/sound_card_init/"${CONFIG}" ]; then
+    # Here (in order) are a list of the args added:
+    # --uts: Create and enter new UTS namespace (hostname/NIS domain name).
+    # -e: doesn't need network access.
+    # -l: process doesn't use SysV shared memory or IPC.
+    # -N: doesn't need to modify control groups settings.
+    # -v: run inside a new VFS namespace.
+    # -p -r: process doesn't need to access other processes in the system.
+    # -n: process doesn't need new privileges.
+    # -P: set /mnt/empty as the root fs.
+    # -b: bind /
+    # -k: Get a writeable and empty /run tmpfs path.
+    # -b: need /run/cras to connect cras.
+    # -b: need /dev to send ioctls to the system's block devices.
+    # -k: empty /sys tmpfs path.
+    # -b: need /sys/firmware/vpd/ro/ access to read the default calibration
+    #     value in vpd.
+    # -k: get a writeable and empty /var tmpfs path.
+    # -b: need /var/lib/sound_card_init/$SOUND_CARD_ID writable access for
+    #     datastore update.
+    # -b: need /var/lib/cras readable
+    exec minijail0 \
+        --uts \
+        -e \
+        -l \
+        -N \
+        -v \
+        -p -r \
+        -n \
+        -P /mnt/empty \
+        -b / \
+        -k 'tmpfs,/run,tmpfs,MS_NODEV|MS_NOEXEC|MS_NOSUID,mode=755,size=10M' \
+        -b /run/cras \
+        -b /dev \
+        -k 'tmpfs,/sys,tmpfs,MS_NODEV|MS_NOEXEC|MS_NOSUID,mode=755,size=10M' \
+        -b /sys/firmware/vpd/ro/ \
+        -k 'tmpfs,/var,tmpfs,MS_NODEV|MS_NOEXEC|MS_NOSUID,mode=755,size=10M' \
+        -b /var/lib/sound_card_init/"${SOUND_CARD_ID}"/,,1 \
+        -b /var/lib/cras/ \
+        -u sound_card_init -g sound_card_init -G \
+        -S /usr/share/policy/sound_card_init-seccomp.policy \
+        /usr/bin/sound_card_init "--id=${SOUND_CARD_ID}" "--conf=${CONFIG}"
+  fi
+end script
\ No newline at end of file
diff --git a/sound_card_init/src/main.rs b/sound_card_init/src/main.rs
index 3f49ed9..806b7d5 100644
--- a/sound_card_init/src/main.rs
+++ b/sound_card_init/src/main.rs
@@ -14,9 +14,6 @@
 use std::env;
 use std::error;
 use std::fmt;
-use std::fs;
-use std::io;
-use std::path::PathBuf;
 use std::process;
 use std::string::String;
 
@@ -24,24 +21,22 @@
 use remain::sorted;
 use sys_util::{error, info, syslog};
 
-use max98390d::run_max98390d;
-use utils::run_time;
+use amp::AmpBuilder;
+use dsm::utils::run_time;
 
 type Result<T> = std::result::Result<T, Error>;
-const CONF_DIR: &str = "/etc/sound_card_init";
 
 #[derive(Default)]
 struct Args {
     pub sound_card_id: String,
+    pub conf: String,
 }
 
 #[sorted]
 #[derive(Debug)]
 enum Error {
     MissingOption(String),
-    OpenConfigFailed(String, io::Error),
     ParseArgsFailed(getopts::Fail),
-    UnsupportedSoundCard(String),
 }
 
 impl error::Error for Error {}
@@ -51,9 +46,7 @@
         use Error::*;
         match self {
             MissingOption(option) => write!(f, "missing required option: {}", option),
-            OpenConfigFailed(file, e) => write!(f, "failed to open file {}: {}", file, e),
             ParseArgsFailed(e) => write!(f, "parse_args failed: {}", e),
-            UnsupportedSoundCard(name) => write!(f, "unsupported sound card: {}", name),
         }
     }
 }
@@ -66,6 +59,12 @@
 fn parse_args() -> Result<Args> {
     let mut opts = Options::new();
     opts.optopt("", "id", "sound card id", "ID");
+    opts.optopt(
+        "",
+        "conf",
+        "the config file name. It should be $(cros_config /audio/main sound-card-init-conf)",
+        "CONFIG_NAME",
+    );
     opts.optflag("h", "help", "print help menu");
     let matches = opts
         .parse(&env::args().collect::<Vec<_>>()[1..])
@@ -87,31 +86,28 @@
             e
         })?;
 
-    Ok(Args { sound_card_id })
+    let conf = matches
+        .opt_str("conf")
+        .ok_or_else(|| Error::MissingOption("conf".to_owned()))
+        .map_err(|e| {
+            print_usage(&opts);
+            e
+        })?;
+
+    Ok(Args {
+        sound_card_id,
+        conf,
+    })
 }
 
-fn get_config(args: &Args) -> Result<String> {
-    let config_path = PathBuf::from(CONF_DIR)
-        .join(&args.sound_card_id)
-        .with_extension("yaml");
-
-    fs::read_to_string(&config_path)
-        .map_err(|e| Error::OpenConfigFailed(config_path.to_string_lossy().to_string(), e))
-}
-
-/// Parses the CONF_DIR/<sound_card_id>.yaml and starts sound card initialization.
+/// Parses the CONF_DIR/${args.conf}.yaml and starts the boot time calibration.
 fn sound_card_init(args: &Args) -> std::result::Result<(), Box<dyn error::Error>> {
-    info!("sound_card_id: {}", args.sound_card_id);
-    let conf = get_config(args)?;
+    info!("sound_card_id: {}, conf:{}", args.sound_card_id, args.conf);
+    AmpBuilder::new(&args.sound_card_id, &args.conf)
+        .build()?
+        .boot_time_calibration()?;
 
-    match args.sound_card_id.as_str() {
-        "sofcmlmax98390d" => {
-            run_max98390d(&args.sound_card_id, &conf)?;
-            info!("run_max98390d() finished successfully.");
-            Ok(())
-        }
-        _ => Err(Error::UnsupportedSoundCard(args.sound_card_id.clone()).into()),
-    }
+    Ok(())
 }
 
 fn main() {
@@ -124,8 +120,9 @@
         }
     };
 
-    if let Err(e) = sound_card_init(&args) {
-        error!("sound_card_init: {}", e);
+    match sound_card_init(&args) {
+        Ok(_) => info!("sound_card_init finished successfully."),
+        Err(e) => error!("sound_card_init: {}", e),
     }
 
     if let Err(e) = run_time::now_to_file(&args.sound_card_id) {
diff --git a/sound_card_init/utils/Cargo.lock b/sound_card_init/utils/Cargo.lock
deleted file mode 100644
index fd53fb0..0000000
--- a/sound_card_init/utils/Cargo.lock
+++ /dev/null
@@ -1,109 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-[[package]]
-name = "dtoa"
-version = "0.4.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b"
-
-[[package]]
-name = "linked-hash-map"
-version = "0.5.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a"
-
-[[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.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
-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.116"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96fe57af81d28386a513cbc6858332abc6117cfdb5999647c6444b8f43a370a5"
-dependencies = [
- "serde_derive",
-]
-
-[[package]]
-name = "serde_derive"
-version = "1.0.116"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f630a6370fd8e457873b4bd2ffdae75408bc291ba72be773772a4c2a065d9ae8"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "serde_yaml"
-version = "0.8.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae3e2dd40a7cdc18ca80db804b7f461a39bb721160a85c9a1fa30134bf3c02a5"
-dependencies = [
- "dtoa",
- "linked-hash-map",
- "serde",
- "yaml-rust",
-]
-
-[[package]]
-name = "syn"
-version = "1.0.44"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e03e57e4fcbfe7749842d53e24ccb9aa12b7252dbe5e91d2acad31834c8b8fdd"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-xid",
-]
-
-[[package]]
-name = "unicode-xid"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
-
-[[package]]
-name = "utils"
-version = "0.1.0"
-dependencies = [
- "remain",
- "serde",
- "serde_yaml",
-]
-
-[[package]]
-name = "yaml-rust"
-version = "0.4.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d"
-dependencies = [
- "linked-hash-map",
-]
diff --git a/sound_card_init/utils/Cargo.toml b/sound_card_init/utils/Cargo.toml
deleted file mode 100644
index 8c688de..0000000
--- a/sound_card_init/utils/Cargo.toml
+++ /dev/null
@@ -1,11 +0,0 @@
-[package]
-name = "utils"
-version = "0.1.0"
-authors = ["The Chromium OS Authors"]
-edition = "2018"
-description = "Utils for sound_card_init"
-
-[dependencies]
-remain = "0.2.1"
-serde = { version = "1.0", features = ["derive"]}
-serde_yaml = "0.8.11"
diff --git a/sound_card_init/utils/src/error.rs b/sound_card_init/utils/src/error.rs
deleted file mode 100644
index 93d53b9..0000000
--- a/sound_card_init/utils/src/error.rs
+++ /dev/null
@@ -1,39 +0,0 @@
-// 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.
-
-//! This mod contains all possible errors that can occur within utils.
-use std::fmt;
-use std::io;
-use std::time;
-use std::{error, path::PathBuf};
-
-use remain::sorted;
-
-/// Alias for a `Result` with the error type `utils::Error`.
-pub type Result<T> = std::result::Result<T, Error>;
-
-/// This type represents all possible errors that can occur within utils.
-#[sorted]
-#[derive(Debug)]
-pub enum Error {
-    /// It wraps file path with the io::Error.
-    FileIOFailed(PathBuf, io::Error),
-    /// It wraps file path with the serde_yaml::Error.
-    SerdeError(PathBuf, serde_yaml::Error),
-    /// It wraps time::SystemTimeError.
-    SystemTimeError(time::SystemTimeError),
-}
-
-impl error::Error for Error {}
-
-impl fmt::Display for Error {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        use Error::*;
-        match self {
-            FileIOFailed(file, e) => write!(f, "{:?}: {}", file, e),
-            SerdeError(file, e) => write!(f, "{:?}: {}", file, e),
-            SystemTimeError(e) => write!(f, "{}", e),
-        }
-    }
-}
diff --git a/ucm-config/for_all_boards/HUAWEI USB-C HEADSET/HUAWEI USB-C HEADSET.conf b/ucm-config/for_all_boards/HUAWEI USB-C HEADSET/HUAWEI USB-C HEADSET.conf
new file mode 100644
index 0000000..c1db519
--- /dev/null
+++ b/ucm-config/for_all_boards/HUAWEI USB-C HEADSET/HUAWEI USB-C HEADSET.conf
@@ -0,0 +1,6 @@
+Comment "HUAWEI USB-C HEADSET"
+
+SectionUseCase."HiFi" {
+	File "HiFi.conf"
+	Comment "Default"
+}
diff --git a/ucm-config/for_all_boards/HUAWEI USB-C HEADSET/HiFi.conf b/ucm-config/for_all_boards/HUAWEI USB-C HEADSET/HiFi.conf
new file mode 100644
index 0000000..d48942b
--- /dev/null
+++ b/ucm-config/for_all_boards/HUAWEI USB-C HEADSET/HiFi.conf
@@ -0,0 +1,25 @@
+SectionVerb {
+	Value {
+		FullySpecifiedUCM "1"
+	}
+
+	EnableSequence [
+		cdev "hw:HEADSET"
+		cset "name='PCM Playback Volume' 45"
+	]
+
+	DisableSequence [
+	]
+}
+
+SectionDevice."HUAWEI USB-C HEADSET Output".0 {
+	Value {
+		PlaybackPCM "hw:HEADSET,0"
+	}
+}
+
+SectionDevice."HUAWEI USB-C HEADSET Input".0 {
+	Value {
+		CapturePCM "hw:HEADSET,0"
+	}
+}
diff --git a/ucm-config/for_all_boards/Mi Dual Driver Earphones Type-C/HiFi.conf b/ucm-config/for_all_boards/Mi Dual Driver Earphones Type-C/HiFi.conf
new file mode 100644
index 0000000..9d63e16
--- /dev/null
+++ b/ucm-config/for_all_boards/Mi Dual Driver Earphones Type-C/HiFi.conf
@@ -0,0 +1,25 @@
+SectionVerb {
+	Value {
+		FullySpecifiedUCM "1"
+	}
+
+	EnableSequence [
+		cdev "hw:TypeC"
+		cset "name='PCM Playback Volume' 45"
+	]
+
+	DisableSequence [
+	]
+}
+
+SectionDevice."Mi Earphones Output".0 {
+	Value {
+		PlaybackPCM "hw:TypeC,0"
+	}
+}
+
+SectionDevice."Mi Earphones Input".0 {
+	Value {
+		CapturePCM "hw:TypeC,0"
+	}
+}
diff --git a/ucm-config/for_all_boards/Mi Dual Driver Earphones Type-C/Mi Dual Driver Earphones Type-C.conf b/ucm-config/for_all_boards/Mi Dual Driver Earphones Type-C/Mi Dual Driver Earphones Type-C.conf
new file mode 100644
index 0000000..df19b47
--- /dev/null
+++ b/ucm-config/for_all_boards/Mi Dual Driver Earphones Type-C/Mi Dual Driver Earphones Type-C.conf
@@ -0,0 +1,6 @@
+Comment "Mi Dual Driver Earphones Type-C"
+
+SectionUseCase."HiFi" {
+	File "HiFi.conf"
+	Comment "Default"
+}
diff --git a/ucm-config/for_all_boards/Scarlett 2i2 USB/HiFi.conf b/ucm-config/for_all_boards/Scarlett 2i2 USB/HiFi.conf
new file mode 100644
index 0000000..f49d681
--- /dev/null
+++ b/ucm-config/for_all_boards/Scarlett 2i2 USB/HiFi.conf
@@ -0,0 +1,27 @@
+SectionVerb {
+	Value {
+		FullySpecifiedUCM "1"
+	}
+	EnableSequence [
+		cdev "hw:USB,0"
+	]
+	DisableSequence [
+	]
+}
+
+SectionDevice."Scarlett 2i2 USB Output".0 {
+	Comment "Scarlett 2i2 Output"
+
+	Value {
+		PlaybackPCM "hw:USB,0"
+		PlaybackRate "48000"
+	}
+}
+
+SectionDevice."Scarlett 2i2 USB Input".0 {
+	Comment "Scarlett 2i2 Input"
+	Value {
+		CapturePCM "hw:USB,0"
+		CaptureRate "48000"
+	}
+}
diff --git a/ucm-config/for_all_boards/Scarlett 2i2 USB/Scarlett 2i2 USB.conf b/ucm-config/for_all_boards/Scarlett 2i2 USB/Scarlett 2i2 USB.conf
new file mode 100644
index 0000000..88bac6b
--- /dev/null
+++ b/ucm-config/for_all_boards/Scarlett 2i2 USB/Scarlett 2i2 USB.conf
@@ -0,0 +1,5 @@
+Comment "Scarlett 2i2 USB"
+SectionUseCase."HiFi" {
+	File "HiFi.conf"
+	Comment "Default"
+}
diff --git a/ucm-config/for_all_boards/Scarlett 2i4 USB/HiFi.conf b/ucm-config/for_all_boards/Scarlett 2i4 USB/HiFi.conf
new file mode 100644
index 0000000..894683c
--- /dev/null
+++ b/ucm-config/for_all_boards/Scarlett 2i4 USB/HiFi.conf
@@ -0,0 +1,27 @@
+SectionVerb {
+	Value {
+		FullySpecifiedUCM "1"
+	}
+	EnableSequence [
+		cdev "hw:USB,0"
+	]
+	DisableSequence [
+	]
+}
+
+SectionDevice."Scarlett 2i4 USB Output".0 {
+	Comment "Scarlett 2i4 Output"
+
+	Value {
+		PlaybackPCM "hw:USB,0"
+		PlaybackRate "48000"
+	}
+}
+
+SectionDevice."Scarlett 2i4 USB Input".0 {
+	Comment "Scarlett 2i4 Input"
+	Value {
+		CapturePCM "hw:USB,0"
+		CaptureRate "48000"
+	}
+}
diff --git a/ucm-config/for_all_boards/Scarlett 2i4 USB/Scarlett 2i4 USB.conf b/ucm-config/for_all_boards/Scarlett 2i4 USB/Scarlett 2i4 USB.conf
new file mode 100644
index 0000000..71ea169
--- /dev/null
+++ b/ucm-config/for_all_boards/Scarlett 2i4 USB/Scarlett 2i4 USB.conf
@@ -0,0 +1,5 @@
+Comment "Scarlett 2i4 USB"
+SectionUseCase."HiFi" {
+	File "HiFi.conf"
+	Comment "Default"
+}
diff --git a/unblocked_terms.txt b/unblocked_terms.txt
index 674e3d3..cba7545 100644
--- a/unblocked_terms.txt
+++ b/unblocked_terms.txt
@@ -5,7 +5,6 @@
 #
 # See repohooks/README.md for more details.
 
-dummy
 master
 \bnative
 white.?list