1
//! PDM (pulse-density modulation) configuration for the I2S master driver.
2
//!
3
//! Default configurations and clock calculations follow Espressif's I2S PDM driver
4
//! (<https://github.com/espressif/esp-idf/tree/master/components/esp_driver_i2s>).
5

            
6
mod clock;
7
#[cfg(not(i2s_version = "1"))]
8
mod hp_filter;
9
#[cfg_attr(i2s_version = "1", path = "regs_v1.rs")]
10
#[cfg_attr(i2s_version = "2", path = "regs_v2.rs")]
11
#[cfg_attr(i2s_version = "3", path = "regs_v2.rs")]
12
mod ll;
13

            
14
use super::master::{ConfigError, Instance};
15
use crate::{i2s::master::Info, time::Rate};
16

            
17
/// PDM configuration errors.
18
#[non_exhaustive]
19
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
21
pub enum PdmError {
22
    /// PDM is not supported on this I2S peripheral instance.
23
    UnsupportedInstance,
24
    /// PCM format requested but hardware PCM2PDM/PDM2PCM is unavailable.
25
    PcmFormatUnsupported,
26
    /// PDM clock configuration is invalid.
27
    InvalidClock,
28
    /// PDM slot mask selects no active channels.
29
    InvalidSlotMask,
30
    /// PDM config must enable at least one of TX or RX.
31
    DirectionMissing,
32
    /// Simultaneous PDM TX and RX is not supported.
33
    DuplexUnsupported,
34
    /// PDM data line index is not available on this chip.
35
    InvalidLine,
36
}
37

            
38
impl core::error::Error for PdmError {}
39

            
40
impl core::fmt::Display for PdmError {
41
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
42
        match self {
43
            Self::UnsupportedInstance => {
44
                write!(f, "PDM mode is not supported on this I2S instance")
45
            }
46
            Self::PcmFormatUnsupported => write!(
47
                f,
48
                "PCM PDM format is not supported on this I2S instance; use raw PDM format"
49
            ),
50
            Self::InvalidClock => write!(f, "PDM clock configuration is out of supported range"),
51
            Self::InvalidSlotMask => {
52
                write!(f, "PDM slot mask must select at least one active slot")
53
            }
54
            Self::DirectionMissing => {
55
                write!(f, "PDM configuration must include TX and/or RX settings")
56
            }
57
            Self::DuplexUnsupported => {
58
                write!(f, "PDM full duplex (TX and RX together) is not supported")
59
            }
60
            Self::InvalidLine => write!(f, "PDM data line index is not available on this chip"),
61
        }
62
    }
63
}
64

            
65
/// A peripheral singleton that supports PDM mode.
66
pub trait PdmInstance: Instance {}
67

            
68
for_each_i2s! {
69
    (
70
        $instance:ident, $sys:ident, $mclk:ident,
71
        $bclk:ident, $ws:ident, $bclk_rx:ident, $ws_rx:ident,
72
        $dout:tt, $din:tt, true, $pdm_rx:literal, $pcm2pdm:literal, $pdm2pcm:literal
73
    ) => {
74
        impl PdmInstance for crate::peripherals::$instance<'_> {}
75
    };
76
    (
77
        $instance:ident, $sys:ident, $mclk:ident,
78
        $bclk:ident, $ws:ident, $bclk_rx:ident, $ws_rx:ident,
79
        $dout:tt, $din:tt, false, true, $pcm2pdm:literal, $pdm2pcm:literal
80
    ) => {
81
        impl PdmInstance for crate::peripherals::$instance<'_> {}
82
    };
83
}
84

            
85
/// PDM data format: PCM samples in software vs raw PDM bitstream.
86
1
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
87
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
88
pub enum PdmDataFormat {
89
    /// Hardware filter converts between PCM and PDM when supported.
90
    #[default]
91
    Pcm,
92
    /// Raw PDM samples; no hardware PCM conversion.
93
    Raw,
94
}
95

            
96
/// Mono or stereo slot mode (fixed two hardware slots).
97
1
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
98
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
99
pub enum PdmSlotMode {
100
    /// Single active slot (left by default).
101
    #[default]
102
    Mono,
103
    /// Both slots active (stereo).
104
    Stereo,
105
}
106

            
107
/// Active PDM slot selection.
108
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
109
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
110
pub struct PdmSlotMask(u16);
111

            
112
impl PdmSlotMask {
113
    /// Left slot only.
114
    pub const LEFT: Self = Self(1 << 1);
115
    /// Right slot only.
116
    pub const RIGHT: Self = Self(1 << 0);
117
    /// Both slots active.
118
    pub const BOTH: Self = Self(0b11);
119

            
120
    #[cfg(all(i2s_supports_pdm_rx, not(esp32)))]
121
    /// Line 0, left slot.
122
    pub const LINE0_LEFT: Self = Self(1 << 1);
123
    #[cfg(all(i2s_supports_pdm_rx, not(esp32)))]
124
    /// Line 0, right slot.
125
    pub const LINE0_RIGHT: Self = Self(1 << 0);
126
    #[cfg(all(i2s_supports_pdm_rx, esp32s3))]
127
    /// Line 1, left slot.
128
    pub const LINE1_LEFT: Self = Self(1 << 3);
129
    #[cfg(all(i2s_supports_pdm_rx, esp32s3))]
130
    /// Line 1, right slot.
131
    pub const LINE1_RIGHT: Self = Self(1 << 2);
132
    #[cfg(all(i2s_supports_pdm_rx, esp32s3))]
133
    /// Line 2, left slot.
134
    pub const LINE2_LEFT: Self = Self(1 << 5);
135
    #[cfg(all(i2s_supports_pdm_rx, esp32s3))]
136
    /// Line 2, right slot.
137
    pub const LINE2_RIGHT: Self = Self(1 << 4);
138
    #[cfg(all(i2s_supports_pdm_rx, esp32s3))]
139
    /// Line 3, left slot.
140
    pub const LINE3_LEFT: Self = Self(1 << 7);
141
    #[cfg(all(i2s_supports_pdm_rx, esp32s3))]
142
    /// Line 3, right slot.
143
    pub const LINE3_RIGHT: Self = Self(1 << 6);
144

            
145
    /// Creates a new mask from raw slot bits.
146
    pub const fn from_bits(bits: u16) -> Self {
147
        Self(bits)
148
    }
149

            
150
    /// Raw slot mask bits.
151
    pub const fn bits(self) -> u16 {
152
        self.0
153
    }
154

            
155
    /// Default mask for the given mono/stereo mode.
156
    pub fn for_mode(mode: PdmSlotMode) -> Self {
157
        match mode {
158
            PdmSlotMode::Mono => Self::LEFT,
159
            PdmSlotMode::Stereo => Self::BOTH,
160
        }
161
    }
162
}
163

            
164
/// PDM RX downsample rate (PDM2PCM path).
165
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
166
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
167
pub enum PdmDownsampleRate {
168
    /// 64x downsample (DSR 8s).
169
    #[default]
170
    Dsr8s,
171
    /// 128x downsample (DSR 16s).
172
    Dsr16s,
173
}
174

            
175
/// PDM TX sigma-delta filter scaling.
176
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
177
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
178
pub enum PdmSigScaling {
179
    /// Divides input by 2.
180
    Div2,
181
    /// Multiply input by 1.
182
    #[default]
183
    Mul1,
184
    /// Multiply input by 2.
185
    Mul2,
186
    /// Multiply input by 4.
187
    Mul4,
188
}
189

            
190
impl PdmSigScaling {
191
    pub(crate) fn to_register(self) -> u8 {
192
        match self {
193
            Self::Div2 => 0,
194
            Self::Mul1 => 1,
195
            Self::Mul2 => 2,
196
            Self::Mul4 => 3,
197
        }
198
    }
199
}
200

            
201
/// PDM TX line routing (HW v2+).
202
1
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
203
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
204
pub enum PdmTxLineMode {
205
    /// Single-line codec output (default).
206
    #[default]
207
    OneLineCodec,
208
    /// Single-line DAC output.
209
    OneLineDac,
210
    /// Two-line DAC output (stereo).
211
    TwoLineDac,
212
}
213

            
214
/// PDM TX clock configuration.
215
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
216
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
217
pub struct PdmTxClockConfig {
218
    /// Target PCM sample rate.
219
    pub sample_rate: Rate,
220
    /// Upsampling factor numerator (`fp`).
221
    pub up_sample_fp: u32,
222
    /// Upsampling factor denominator (`fs`).
223
    pub up_sample_fs: u32,
224
    /// Bit clock divider.
225
    pub bclk_div: u32,
226
    /// Source clock for the I2S module clock.
227
    pub clock_source: crate::i2s::master::I2sClockSource,
228
}
229

            
230
/// PDM TX slot / filter configuration.
231
#[derive(Debug, Clone, Copy, PartialEq, procmacros::BuilderLite)]
232
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
233
#[non_exhaustive]
234
pub struct PdmTxSlotConfig {
235
    /// Mono or stereo slot mode.
236
    pub slot_mode: PdmSlotMode,
237
    /// PCM or raw PDM data format.
238
    pub data_format: PdmDataFormat,
239
    /// Sigma-delta prescale value.
240
    pub sd_prescale: u8,
241
    /// Sigma-delta filter input scaling.
242
    pub sd_scale: PdmSigScaling,
243
    /// High-pass filter input scaling.
244
    pub hp_scale: PdmSigScaling,
245
    /// Low-pass filter input scaling.
246
    pub lp_scale: PdmSigScaling,
247
    /// Sinc filter input scaling.
248
    pub sinc_scale: PdmSigScaling,
249
    #[cfg(not(i2s_version = "1"))]
250
    /// Output line routing mode.
251
    pub line_mode: PdmTxLineMode,
252
    /// Enables the TX high-pass filter.
253
    pub hp_en: bool,
254
    /// High-pass filter cut-off frequency in Hz.
255
    pub hp_cut_off_freq_hz: f32,
256
    #[cfg(not(i2s_version = "1"))]
257
    /// Sigma-delta dither bit 0.
258
    pub sd_dither: u8,
259
    #[cfg(not(i2s_version = "1"))]
260
    /// Sigma-delta dither bit 1.
261
    pub sd_dither2: u8,
262
    #[cfg(i2s_version = "1")]
263
    /// Active TX slot mask (ESP32 only).
264
    pub slot_mask: PdmSlotMask,
265
}
266

            
267
impl PdmTxSlotConfig {
268
    fn codec_pcm_default(mode: PdmSlotMode) -> Self {
269
        Self {
270
            slot_mode: mode,
271
            data_format: PdmDataFormat::Pcm,
272
            sd_prescale: 0,
273
            sd_scale: PdmSigScaling::Mul1,
274
            hp_scale: PdmSigScaling::Div2,
275
            lp_scale: PdmSigScaling::Mul1,
276
            sinc_scale: PdmSigScaling::Mul1,
277
            #[cfg(not(i2s_version = "1"))]
278
            line_mode: PdmTxLineMode::OneLineCodec,
279
            hp_en: true,
280
            hp_cut_off_freq_hz: 35.5,
281
            #[cfg(not(i2s_version = "1"))]
282
            sd_dither: 0,
283
            #[cfg(not(i2s_version = "1"))]
284
            sd_dither2: 1,
285
            #[cfg(i2s_version = "1")]
286
            slot_mask: PdmSlotMask::BOTH,
287
        }
288
    }
289

            
290
    #[cfg(not(i2s_version = "1"))]
291
    fn dac_pcm_default(mode: PdmSlotMode) -> Self {
292
        let mut cfg = Self::codec_pcm_default(mode);
293
        cfg.hp_scale = PdmSigScaling::Mul1;
294
        cfg.lp_scale = PdmSigScaling::Mul1;
295
        cfg.sinc_scale = PdmSigScaling::Mul1;
296
        #[cfg(not(i2s_version = "1"))]
297
        {
298
            cfg.line_mode = if mode == PdmSlotMode::Mono {
299
                PdmTxLineMode::OneLineDac
300
            } else {
301
                PdmTxLineMode::TwoLineDac
302
            };
303
        }
304
        cfg
305
    }
306

            
307
    fn raw_default(mode: PdmSlotMode) -> Self {
308
        let mut cfg = Self::codec_pcm_default(mode);
309
        cfg.data_format = PdmDataFormat::Raw;
310
        cfg.hp_en = false;
311
        cfg
312
    }
313
}
314

            
315
/// PDM RX clock configuration.
316
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
317
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
318
pub struct PdmRxClockConfig {
319
    /// Target PCM sample rate.
320
    pub sample_rate: Rate,
321

            
322
    /// PDM2PCM downsample rate.
323
    pub downsample_rate: PdmDownsampleRate,
324

            
325
    /// Bit clock divider.
326
    pub bclk_div: u32,
327
    /// Source clock for the I2S module clock.
328
    pub clock_source: crate::i2s::master::I2sClockSource,
329
}
330

            
331
/// PDM RX slot configuration.
332
#[derive(Debug, Clone, Copy, PartialEq, procmacros::BuilderLite)]
333
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
334
#[non_exhaustive]
335
pub struct PdmRxSlotConfig {
336
    /// Mono or stereo slot mode.
337
    pub slot_mode: PdmSlotMode,
338

            
339
    /// Active RX slot mask.
340
    pub slot_mask: PdmSlotMask,
341

            
342
    /// PCM or raw PDM data format.
343
    pub data_format: PdmDataFormat,
344

            
345
    #[cfg(i2s_supports_pdm_rx_hp_filter)]
346
    /// Enables the RX high-pass filter.
347
    pub hp_en: bool,
348

            
349
    #[cfg(i2s_supports_pdm_rx_hp_filter)]
350
    /// High-pass filter cut-off frequency in Hz.
351
    pub hp_cut_off_freq_hz: f32,
352

            
353
    #[cfg(i2s_supports_pdm_rx_hp_filter)]
354
    /// RX amplification factor (1–15).
355
    pub amplify_num: u32,
356
}
357

            
358
impl PdmRxSlotConfig {
359
    fn pcm_default(mode: PdmSlotMode) -> Self {
360
        Self {
361
            slot_mode: mode,
362
            slot_mask: PdmSlotMask::for_mode(mode),
363
            data_format: PdmDataFormat::Pcm,
364
            #[cfg(i2s_supports_pdm_rx_hp_filter)]
365
            hp_en: true,
366
            #[cfg(i2s_supports_pdm_rx_hp_filter)]
367
            hp_cut_off_freq_hz: 35.5,
368
            #[cfg(i2s_supports_pdm_rx_hp_filter)]
369
            amplify_num: 1,
370
        }
371
    }
372

            
373
    fn raw_default(mode: PdmSlotMode) -> Self {
374
        let mut cfg = Self::pcm_default(mode);
375
        cfg.data_format = PdmDataFormat::Raw;
376
        #[cfg(i2s_supports_pdm_rx_hp_filter)]
377
        {
378
            cfg.hp_en = false;
379
        }
380
        cfg
381
    }
382
}
383

            
384
/// Full PDM TX unit configuration.
385
#[derive(Debug, Clone, Copy, PartialEq)]
386
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
387
pub struct PdmTxConfig {
388
    /// TX clock settings.
389
    pub clock: PdmTxClockConfig,
390

            
391
    /// TX slot and filter settings.
392
    pub slot: PdmTxSlotConfig,
393
}
394

            
395
impl PdmTxConfig {
396
    /// Codec-line defaults (`I2S_PDM_TX_*_DEFAULT_CONFIG`).
397
    pub fn new_codec_default(sample_rate: Rate, mode: PdmSlotMode) -> Self {
398
        Self {
399
            clock: PdmTxClockConfig::codec_default(sample_rate),
400
            slot: default_tx_slot(mode),
401
        }
402
    }
403

            
404
    /// Raw PDM TX defaults (no hardware PCM conversion).
405
    pub fn new_raw_default(sample_rate: Rate, mode: PdmSlotMode) -> Self {
406
        Self {
407
            clock: PdmTxClockConfig::codec_default(sample_rate),
408
            slot: PdmTxSlotConfig::raw_default(mode),
409
        }
410
    }
411

            
412
    /// DAC-line defaults (`I2S_PDM_TX_*_DAC_DEFAULT_CONFIG`, HW v2+).
413
    #[cfg(not(i2s_version = "1"))]
414
    pub fn new_dac_default(sample_rate: Rate, mode: PdmSlotMode) -> Self {
415
        Self {
416
            clock: PdmTxClockConfig::dac_default(sample_rate),
417
            slot: PdmTxSlotConfig::dac_pcm_default(mode),
418
        }
419
    }
420

            
421
    /// Validates TX configuration against hardware capabilities.
422
    pub fn validate(&self, info: &Info) -> Result<(), PdmError> {
423
1
        if self.slot.data_format == PdmDataFormat::Pcm && !info.pcm2pdm {
424
            return Err(PdmError::PcmFormatUnsupported);
425
        }
426
1
        if self.clock.up_sample_fs > 480 {
427
            return Err(PdmError::InvalidClock);
428
        }
429
        Ok(())
430
    }
431
}
432

            
433
fn default_tx_slot(mode: PdmSlotMode) -> PdmTxSlotConfig {
434
    cfg_select! {
435
        i2s_supports_pcm2pdm => PdmTxSlotConfig::codec_pcm_default(mode),
436
        _ => PdmTxSlotConfig::raw_default(mode),
437
    }
438
}
439

            
440
/// Full PDM RX unit configuration.
441
#[derive(Debug, Clone, Copy, PartialEq)]
442
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
443
pub struct PdmRxConfig {
444
    /// RX clock settings.
445
    pub clock: PdmRxClockConfig,
446
    /// RX slot settings.
447
    pub slot: PdmRxSlotConfig,
448
}
449

            
450
impl PdmRxConfig {
451
    /// Default RX config; uses PCM when PDM2PCM is available, otherwise raw.
452
    pub fn new_default(sample_rate: Rate, mode: PdmSlotMode) -> Self {
453
        Self {
454
            clock: PdmRxClockConfig::default(sample_rate),
455
            slot: default_rx_slot(mode),
456
        }
457
    }
458

            
459
    /// PCM RX defaults (requires hardware PDM2PCM support).
460
    pub fn new_pcm_default(sample_rate: Rate, mode: PdmSlotMode) -> Self {
461
        Self {
462
            clock: PdmRxClockConfig::default(sample_rate),
463
            slot: PdmRxSlotConfig::pcm_default(mode),
464
        }
465
    }
466

            
467
    /// Raw PDM RX defaults (no hardware PCM conversion).
468
    pub fn new_raw_default(sample_rate: Rate, mode: PdmSlotMode) -> Self {
469
        Self {
470
            clock: PdmRxClockConfig::default(sample_rate),
471
            slot: PdmRxSlotConfig::raw_default(mode),
472
        }
473
    }
474

            
475
    /// Validates RX configuration against hardware capabilities.
476
    pub fn validate(&self, info: &Info) -> Result<(), PdmError> {
477
1
        if self.slot.data_format == PdmDataFormat::Pcm && !info.pdm2pcm {
478
            return Err(PdmError::PcmFormatUnsupported);
479
        }
480
1
        if self.slot.slot_mask.bits() == 0 {
481
            return Err(PdmError::InvalidSlotMask);
482
        }
483
        Ok(())
484
    }
485
}
486

            
487
fn default_rx_slot(mode: PdmSlotMode) -> PdmRxSlotConfig {
488
    cfg_select! {
489
        i2s_supports_pdm2pcm => PdmRxSlotConfig::pcm_default(mode),
490
        _ => PdmRxSlotConfig::raw_default(mode),
491
    }
492
}
493

            
494
/// PDM mode configuration (simplex TX, RX, or both).
495
#[derive(Debug, Clone, Copy, PartialEq)]
496
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
497
pub struct PdmConfig {
498
    /// Optional TX unit configuration.
499
    pub tx: Option<PdmTxConfig>,
500
    /// Optional RX unit configuration.
501
    pub rx: Option<PdmRxConfig>,
502
    /// Selects which module clock is routed to the MCLK pad.
503
    #[cfg(not(i2s_version = "1"))]
504
    pub mclk_out: crate::i2s::master::MclkOut,
505
}
506

            
507
impl PdmConfig {
508
    /// PDM TX only (recommended simplex setup).
509
    #[cfg(i2s_supports_pdm_tx)]
510
    pub fn tx_only(tx: PdmTxConfig) -> Self {
511
        Self {
512
            tx: Some(tx),
513
            rx: None,
514
            #[cfg(not(i2s_version = "1"))]
515
            mclk_out: crate::i2s::master::MclkOut::Tx,
516
        }
517
    }
518

            
519
    /// PDM RX only (recommended simplex setup).
520
    #[cfg(i2s_supports_pdm_rx)]
521
    pub fn rx_only(rx: PdmRxConfig) -> Self {
522
        Self {
523
            tx: None,
524
            rx: Some(rx),
525
            #[cfg(not(i2s_version = "1"))]
526
            mclk_out: crate::i2s::master::MclkOut::Rx,
527
        }
528
    }
529

            
530
    /// Validates that exactly one direction is configured and that the settings
531
    /// are valid for the given I2S instance.
532
    pub fn validate(&self, info: &Info) -> Result<(), PdmError> {
533
2
        if self.tx.is_none() && self.rx.is_none() {
534
            return Err(PdmError::DirectionMissing);
535
        }
536
1
        if self.tx.is_some() && self.rx.is_some() {
537
            return Err(PdmError::DuplexUnsupported);
538
        }
539
1
        if self.tx.is_some() && !info.pdm_tx {
540
            return Err(PdmError::UnsupportedInstance);
541
        }
542
1
        if self.rx.is_some() && !info.pdm_rx {
543
            return Err(PdmError::UnsupportedInstance);
544
        }
545
        if let Some(tx) = &self.tx {
546
            tx.validate(info)?;
547
        }
548
        if let Some(rx) = &self.rx {
549
            rx.validate(info)?;
550
        }
551
        Ok(())
552
    }
553
}
554

            
555
pub(crate) fn configure_pdm(i2s: &Info, config: &PdmConfig) -> Result<(), ConfigError> {
556
    ll::configure_pdm(i2s, config).map_err(ConfigError::Pdm)
557
}