1
use core::marker::PhantomData;
2

            
3
use super::AdcHasLineCal;
4
use crate::analog::adc::{AdcCalBasic, AdcCalEfuse, AdcCalScheme, Attenuation, CalibrationAccess};
5

            
6
/// Gain is stored as a `u32`, but is really a fixed-point number.
7
const GAIN_SCALE: u32 = 1 << 16;
8

            
9
/// Line fitting ADC calibration scheme.
10
///
11
/// This scheme implements gain correction based on reference points, and
12
/// returns readings in mV.
13
///
14
/// A reference point is a pair of a reference voltage and the corresponding
15
/// mean raw digital ADC value. Those values are stored in eFuse bit fields for each supported
16
/// attenuation, and there is no way to establish them at runtime: the ADC can only be switched to
17
/// internal ground, which gives the zero-voltage offset rather than a second point to derive a
18
/// gain from.
19
///
20
/// This scheme also includes basic calibration ([`AdcCalBasic`]).
21
///
22
/// # Panics
23
///
24
/// Panics if the chip carries no line-fitting calibration data in eFuse, or if that data gives a
25
/// reference point of zero.
26
#[derive(Clone, Copy)]
27
pub struct AdcCalLine<ADCX> {
28
    basic: AdcCalBasic<ADCX>,
29

            
30
    /// ADC gain.
31
    ///
32
    /// After being de-biased by the basic calibration, the reading is
33
    /// multiplied by this value. Despite the type, it is a fixed-point
34
    /// number with 16 fractional bits.
35
    gain: u32,
36

            
37
    _phantom: PhantomData<ADCX>,
38
}
39

            
40
impl<ADCX> crate::private::Sealed for AdcCalLine<ADCX> {}
41

            
42
impl<ADCX> AdcCalScheme<ADCX> for AdcCalLine<ADCX>
43
where
44
    ADCX: AdcCalEfuse + AdcHasLineCal + CalibrationAccess,
45
{
46
    fn new_cal(atten: Attenuation) -> Self {
47
        Self::new_cal_with_channel(atten, 0)
48
    }
49

            
50
    fn new_cal_with_channel(atten: Attenuation, channel: u8) -> Self {
51
        let basic = AdcCalBasic::<ADCX>::new_cal_with_channel(atten, channel);
52

            
53
        // Get the reference point (Dout, Vin) from efuse. Dout means mean raw ADC value when
54
        // specified Vin applied to input.
55
        // `cal_mv` goes first: it rejects attenuations without a reference point on the ESP32-C2.
56
        let mv = ADCX::cal_mv(atten);
57
2
        let Some(code) = ADCX::cal_code(atten) else {
58
            panic!("This chip needs eFuse calibration data for line fitting")
59
        };
60

            
61
        // Guards the division below, which would otherwise divide by zero.
62
2
        assert!(code != 0, "ADC calibration reference point is zero");
63

            
64
        // Estimate the (assumed) linear relationship between the measured raw value and
65
        // the voltage with the previously done measurement when the chip was
66
        // manufactured.
67
        //
68
        // Note that the constant term is zero because the driver programs the
69
        // zero-voltage bias for every conversion.
70
2
        let gain = mv as u32 * GAIN_SCALE / code as u32;
71

            
72
        Self {
73
            basic,
74
            gain,
75
            _phantom: PhantomData,
76
        }
77
    }
78

            
79
    fn adc_val(&self, val: u16) -> u16 {
80
        let val = self.basic.adc_val(val);
81

            
82
32
        (val as u32 * self.gain / GAIN_SCALE) as u16
83
    }
84
}