blob: d925752228443f7b6432327e492ebf1c7cf2d27b [file] [log] [blame]
// 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
);
}
}