1
use enumset::EnumSet;
2

            
3
use super::{I2sClockSource, I2sInterrupt, private::I2sClockDividers};
4
#[cfg(not(i2s_version = "1"))]
5
use crate::clock::ll::I2sClkConfig as ModuleClockConfig;
6
#[cfg(i2s_version = "1")]
7
use crate::clock::ll::I2sMclkConfig as ModuleClockConfig;
8
use crate::{
9
    clock::ll::{ClockTree, I2sInstance},
10
    gpio::{InputSignal, OutputSignal},
11
    pac::i2s0::RegisterBlock,
12
};
13

            
14
#[cfg_attr(i2s_version = "1", path = "v1.rs")]
15
#[cfg_attr(i2s_version = "2", path = "v2.rs")]
16
#[cfg_attr(i2s_version = "3", path = "v3.rs")]
17
mod version;
18

            
19
/// Peripheral data describing a particular I2S instance.
20
///
21
/// All the per-instance data (register block pointer, system peripheral marker and the
22
/// GPIO matrix signals) is stored here, so that the driver can operate on a single
23
/// type-erased `&'static Info` regardless of which concrete I2S peripheral is used.
24
#[doc(hidden)]
25
#[non_exhaustive]
26
pub struct Info {
27
    /// Pointer to the register block for this I2S instance.
28
    pub register_block: *const RegisterBlock,
29

            
30
    /// The system peripheral marker.
31
    pub peripheral: crate::system::Peripheral,
32

            
33
    /// MCLK output signal.
34
    #[cfg(not(esp32))] // MCLK on ESP32 requires special handling
35
    pub mclk: OutputSignal,
36

            
37
    /// BCLK (TX) output signal.
38
    pub bclk: OutputSignal,
39

            
40
    /// WS (TX) output signal.
41
    pub ws: OutputSignal,
42

            
43
    /// BCLK (RX) output signal.
44
    pub bclk_rx: OutputSignal,
45

            
46
    /// WS (RX) output signal.
47
    pub ws_rx: OutputSignal,
48

            
49
    /// Data out signals.
50
    pub dout_lines: &'static [OutputSignal],
51

            
52
    /// Data in signals.
53
    pub din_lines: &'static [InputSignal],
54

            
55
    /// PDM TX supported.
56
    pub pdm_tx: bool,
57

            
58
    /// PDM RX supported.
59
    pub pdm_rx: bool,
60

            
61
    /// Hardware PCM-to-PDM format conversion filter supported on TX.
62
    pub pcm2pdm: bool,
63

            
64
    /// Hardware PDM-to-PCM format conversion filter supported on RX.
65
    pub pdm2pcm: bool,
66

            
67
    /// Clock tree instance for this I2S peripheral.
68
    pub clock_instance: I2sInstance,
69
}
70

            
71
// SAFETY: The register block pointer refers to a static peripheral memory region.
72
unsafe impl Sync for Info {}
73

            
74
impl Info {
75
    pub(crate) fn regs(&self) -> &RegisterBlock {
76
26
        unsafe { &*self.register_block }
77
    }
78

            
79
    pub(crate) fn dout(&self, line: u8) -> Option<OutputSignal> {
80
        // (line 1 for two-line DAC).
81
        self.dout_lines.get(line as usize).copied()
82
    }
83

            
84
    pub(crate) fn din(&self, line: u8) -> Option<InputSignal> {
85
        // (line 0 is the default DIN signal).
86
        self.din_lines.get(line as usize).copied()
87
    }
88

            
89
    pub(crate) fn interrupts(&self) -> EnumSet<I2sInterrupt> {
90
        let mut res = EnumSet::new();
91
        let ints = self.regs().int_st().read();
92

            
93
        if ints.rx_hung().bit() {
94
            res.insert(I2sInterrupt::RxHung);
95
        }
96
        if ints.tx_hung().bit() {
97
            res.insert(I2sInterrupt::TxHung);
98
        }
99
        #[cfg(not(i2s_version = "1"))]
100
        if ints.rx_done().bit() {
101
            res.insert(I2sInterrupt::RxDone);
102
        }
103
        #[cfg(not(i2s_version = "1"))]
104
        if ints.tx_done().bit() {
105
            res.insert(I2sInterrupt::TxDone);
106
        }
107

            
108
        res
109
    }
110

            
111
    pub(crate) fn enable_listen(&self, interrupts: EnumSet<I2sInterrupt>, enable: bool) {
112
        self.regs().int_ena().modify(|_, w| {
113
            for interrupt in interrupts {
114
                match interrupt {
115
                    I2sInterrupt::RxHung => w.rx_hung().bit(enable),
116
                    I2sInterrupt::TxHung => w.tx_hung().bit(enable),
117
                    #[cfg(not(i2s_version = "1"))]
118
                    I2sInterrupt::RxDone => w.rx_done().bit(enable),
119
                    #[cfg(not(i2s_version = "1"))]
120
                    I2sInterrupt::TxDone => w.tx_done().bit(enable),
121
                };
122
            }
123
            w
124
        });
125
    }
126

            
127
    pub(crate) fn clear_interrupts(&self, interrupts: EnumSet<I2sInterrupt>) {
128
        self.regs().int_clr().write(|w| {
129
            for interrupt in interrupts {
130
                match interrupt {
131
                    I2sInterrupt::RxHung => w.rx_hung().clear_bit_by_one(),
132
                    I2sInterrupt::TxHung => w.tx_hung().clear_bit_by_one(),
133
                    #[cfg(not(i2s_version = "1"))]
134
                    I2sInterrupt::RxDone => w.rx_done().clear_bit_by_one(),
135
                    #[cfg(not(i2s_version = "1"))]
136
                    I2sInterrupt::TxDone => w.tx_done().clear_bit_by_one(),
137
                };
138
            }
139
            w
140
        });
141
    }
142
}
143

            
144
/// Describes a module clock for the clock tree: the source to select, and the MCLK divider.
145
fn clock_config(source: I2sClockSource, dividers: &I2sClockDividers) -> ModuleClockConfig {
146
    ModuleClockConfig::new(
147
        source,
148
25
        dividers.mclk_divider,
149
        dividers.denominator,
150
        dividers.numerator,
151
    )
152
}
153

            
154
impl Info {
155
    /// Configures the module clock that both directions share.
156
    #[cfg(i2s_version = "1")]
157
    pub(crate) fn configure_mclk(&self, source: I2sClockSource, dividers: &I2sClockDividers) {
158
        ClockTree::with(|clocks| {
159
            self.clock_instance
160
                .configure_mclk(clocks, clock_config(source, dividers));
161
        });
162
    }
163

            
164
    /// Configures the module clock of the transmitter.
165
    #[cfg(not(i2s_version = "1"))]
166
25
    pub(crate) fn configure_tx_mclk(&self, source: I2sClockSource, dividers: &I2sClockDividers) {
167
        ClockTree::with(|clocks| {
168
            self.clock_instance
169
25
                .configure_tx_clk(clocks, clock_config(source, dividers));
170
        });
171
25
    }
172

            
173
    /// Configures the module clock of the receiver.
174
    #[cfg(not(i2s_version = "1"))]
175
25
    pub(crate) fn configure_rx_mclk(&self, source: I2sClockSource, dividers: &I2sClockDividers) {
176
        ClockTree::with(|clocks| {
177
            self.clock_instance
178
25
                .configure_rx_clk(clocks, clock_config(source, dividers));
179
        });
180
25
    }
181

            
182
    /// Selects the module clock that drives the MCLK pad.
183
    #[cfg(not(i2s_version = "1"))]
184
    pub(crate) fn configure_mclk_pad(&self, sel: super::MclkOut) {
185
        ClockTree::with(|clocks| {
186
24
            self.clock_instance.configure_mclk_out(clocks, sel);
187
        });
188
    }
189
}