summaryrefslogtreecommitdiff
path: root/sound_card_init/max98390d/src/lib.rs
blob: 44b1c6116e89b336bfbd647a0d5410ed01a99995 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// 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(())
}