Skip to main content

esp_idf_hal/i2s/
tdm.rs

1//! Time-division multiplexing (TDM) support for I2S.
2use super::*;
3use crate::gpio::*;
4
5use esp_idf_sys::*;
6
7pub(super) mod config {
8    #[allow(unused)]
9    use crate::{gpio::*, i2s::config::*};
10    use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Not};
11    use esp_idf_sys::*;
12
13    /// Automatic total number of slots, equivalent to the maximum active slot number.
14    pub const TDM_AUTO_SLOT_NUM: u32 = 0;
15
16    /// Automatic word-select signal width, equivalent to half the width of a frame.
17    pub const TDM_AUTO_WS_WIDTH: u32 = 0;
18
19    /// Time-division multiplexing (TDM) mode configuration for the I2S peripheral.
20    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
21    pub struct TdmConfig {
22        /// The base channel configuration.
23        pub(super) channel_cfg: Config,
24
25        /// TDM mode channel clock configuration.
26        clk_cfg: TdmClkConfig,
27
28        /// TDM mode channel slot configuration.
29        slot_cfg: TdmSlotConfig,
30
31        /// TDM mode channel data configuration.
32        #[cfg(not(esp_idf_version_major = "4"))]
33        gpio_cfg: TdmGpioConfig,
34    }
35
36    impl TdmConfig {
37        /// Create a new TDM mode channel configuration from the given base configuration, clock configuration, slot
38        /// configuration, and GPIO configuration.
39        #[inline(always)]
40        pub fn new(
41            channel_cfg: Config,
42            clk_cfg: TdmClkConfig,
43            slot_cfg: TdmSlotConfig,
44            #[cfg(not(esp_idf_version_major = "4"))] gpio_cfg: TdmGpioConfig,
45        ) -> Self {
46            Self {
47                channel_cfg,
48                clk_cfg,
49                slot_cfg,
50                #[cfg(not(esp_idf_version_major = "4"))]
51                gpio_cfg,
52            }
53        }
54
55        /// Convert just the clock config to the SDK representation. Used by
56        /// the runtime `reconfigure_tdm` paths that don't touch GPIO.
57        #[cfg(all(not(esp_idf_version_major = "4"), esp_idf_soc_i2s_supports_tdm))]
58        #[inline(always)]
59        pub(crate) fn clk_cfg_as_sdk(&self) -> i2s_tdm_clk_config_t {
60            self.clk_cfg.as_sdk()
61        }
62
63        /// Convert just the slot config to the SDK representation. Used by
64        /// the runtime `reconfigure_tdm` paths that don't touch GPIO.
65        #[cfg(all(not(esp_idf_version_major = "4"), esp_idf_soc_i2s_supports_tdm))]
66        #[inline(always)]
67        pub(crate) fn slot_cfg_as_sdk(&self) -> i2s_tdm_slot_config_t {
68            self.slot_cfg.as_sdk()
69        }
70
71        /// Convert to the ESP-IDF SDK `i2s_tdm_config_t` representation.
72        #[cfg(not(esp_idf_version_major = "4"))]
73        #[inline(always)]
74        pub(super) fn as_sdk<'d>(
75            &self,
76            bclk: impl InputPin + OutputPin + 'd,
77            din: Option<impl InputPin + 'd>,
78            dout: Option<impl OutputPin + 'd>,
79            mclk: Option<impl InputPin + OutputPin + 'd>,
80            ws: impl InputPin + OutputPin + 'd,
81        ) -> i2s_tdm_config_t {
82            i2s_tdm_config_t {
83                clk_cfg: self.clk_cfg.as_sdk(),
84                slot_cfg: self.slot_cfg.as_sdk(),
85                gpio_cfg: self.gpio_cfg.as_sdk(bclk, din, dout, mclk, ws),
86            }
87        }
88
89        /// Convert to the ESP-IDF SDK `i2s_driver_config_t` representation.
90        ///
91        /// # Note
92        /// The mode field is not fully set by this function. Only the controller/target field is set. Before using,
93        /// the following bits must be considered: `I2S_MODE_TX`, `I2S_MODE_RX`. `I2S_MODE_DAC_BUILT_IN`, and
94        /// `I2S_MODE_ADC_BUILT_IN`, and `I2S_MODE_PDM` should not be used here.
95        #[cfg(esp_idf_version_major = "4")]
96        pub(crate) fn as_sdk(&self) -> i2s_driver_config_t {
97            i2s_driver_config_t {
98                mode: self.channel_cfg.role.as_sdk(),
99                sample_rate: self.clk_cfg.sample_rate_hz,
100                bits_per_sample: self.slot_cfg.data_bit_width.as_sdk(),
101                channel_format: i2s_channel_fmt_t_I2S_CHANNEL_FMT_MULTIPLE, // mono mode doesn't make sense in TDM
102                communication_format: self.slot_cfg.comm_fmt.as_sdk(),
103                intr_alloc_flags: 1 << 1, // ESP_INTR_FLAG_LEVEL1
104                dma_buf_count: self.channel_cfg.dma_buffer_count as i32,
105                dma_buf_len: self.channel_cfg.frames_per_buffer as i32,
106                #[cfg(any(esp32, esp32s2))]
107                use_apll: matches!(self.clk_cfg.clk_src, ClockSource::Apll),
108                #[cfg(not(any(esp32, esp32s2)))]
109                use_apll: false,
110                tx_desc_auto_clear: self.channel_cfg.auto_clear,
111                fixed_mclk: 0,
112                mclk_multiple: self.clk_cfg.mclk_multiple.as_sdk(),
113                bits_per_chan: self.slot_cfg.slot_bit_width.as_sdk(),
114                chan_mask: self.slot_cfg.slot_mask.as_sdk(),
115                total_chan: self.slot_cfg.slot_mask.0.count_ones(),
116                left_align: self.slot_cfg.left_align,
117                big_edin: self.slot_cfg.big_endian,
118                bit_order_msb: !self.slot_cfg.bit_order_lsb,
119                skip_msk: self.slot_cfg.skip_mask,
120            }
121        }
122    }
123
124    /// TDM mode channel clock configuration.
125    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
126    pub struct TdmClkConfig {
127        /// I2S sample rate.
128        sample_rate_hz: u32,
129
130        /// Clock source.
131        clk_src: ClockSource,
132
133        /// The multiple of MCLK to the sample rate.
134        mclk_multiple: MclkMultiple,
135
136        /// The division from MCLK to BCLK. This is used only in I2S target (slave) mode. This should not be smaller
137        /// than TDM_BCLK_DIV_MIN (8). Increase this field if the target device is not able to transmit data in time.
138        #[cfg(esp_idf_version_at_least_5_1_0)]
139        bclk_div: u32,
140    }
141
142    /// The minimum division from MCLK to BCLK.
143    pub const TDM_BCLK_DIV_MIN: u32 = 8;
144
145    impl TdmClkConfig {
146        /// Create a TDM clock configuration with the specified rate (in Hz), clock source, and MCLK multiple of
147        /// the sample rate.
148        #[cfg(not(esp_idf_version_at_least_5_1_0))]
149        #[inline(always)]
150        pub fn new(sample_rate_hz: u32, clk_src: ClockSource, mclk_multiple: MclkMultiple) -> Self {
151            Self {
152                sample_rate_hz,
153                clk_src,
154                mclk_multiple,
155            }
156        }
157
158        /// Create a TDM clock configuration with the specified rate (in Hz), clock source, and MCLK multiple of
159        /// the sample rate.
160        #[cfg(esp_idf_version_at_least_5_1_0)]
161        #[inline(always)]
162        pub fn new(sample_rate_hz: u32, clk_src: ClockSource, mclk_multiple: MclkMultiple) -> Self {
163            Self {
164                sample_rate_hz,
165                clk_src,
166                mclk_multiple,
167                bclk_div: TDM_BCLK_DIV_MIN,
168            }
169        }
170
171        /// Create a TDM clock configuration with the specified rate in Hz. This will set the clock source to
172        /// PLL_F160M and the MCLK multiple to 256 times the sample rate.
173        ///
174        /// # Note
175        /// Set the mclk_multiple to [`MclkMultiple::M384`] when using 24-bit data width. Otherwise, the sample rate
176        /// might be imprecise since the BCLK division is not an integer.
177        #[cfg(not(esp_idf_version_at_least_5_1_0))]
178        #[inline(always)]
179        pub fn from_sample_rate_hz(rate: u32) -> Self {
180            Self {
181                sample_rate_hz: rate,
182                clk_src: ClockSource::default(),
183                mclk_multiple: MclkMultiple::M256,
184            }
185        }
186
187        /// Create a TDM clock configuration with the specified rate in Hz. This will set the clock source to
188        /// PLL_F160M, MCLK multiple to 256 times the sample rate, and MCLK to BCLK division to 8.
189        ///
190        /// # Note
191        /// Set the mclk_multiple to [MclkMultiple::M384] when using 24-bit data width. Otherwise, the sample rate
192        /// might be imprecise since the BCLK division is not an integer.
193        #[cfg(esp_idf_version_at_least_5_1_0)]
194        #[inline(always)]
195        pub fn from_sample_rate_hz(rate: u32) -> Self {
196            Self {
197                sample_rate_hz: rate,
198                clk_src: ClockSource::default(),
199                mclk_multiple: MclkMultiple::M256,
200                bclk_div: TDM_BCLK_DIV_MIN,
201            }
202        }
203
204        /// Set the clock source on this TDM clock configuration.
205        #[inline(always)]
206        pub fn clk_src(mut self, clk_src: ClockSource) -> Self {
207            self.clk_src = clk_src;
208            self
209        }
210
211        /// Set the MCLK multiple on this TDM clock configuration.
212        #[inline(always)]
213        pub fn mclk_multiple(mut self, mclk_multiple: MclkMultiple) -> Self {
214            self.mclk_multiple = mclk_multiple;
215            self
216        }
217
218        /// Set the MCLK to BCLK division on this TDM clock configuration.
219        #[cfg(esp_idf_version_at_least_5_1_0)]
220        #[inline(always)]
221        pub fn bclk_div(mut self, bclk_div: u32) -> Self {
222            self.bclk_div = bclk_div;
223            self
224        }
225
226        /// Convert to the ESP-IDF SDK `i2s_tdm_clk_config_t` representation.
227        #[cfg(not(esp_idf_version_at_least_5_1_0))]
228        #[inline(always)]
229        pub(crate) fn as_sdk(&self) -> i2s_tdm_clk_config_t {
230            i2s_tdm_clk_config_t {
231                sample_rate_hz: self.sample_rate_hz,
232                clk_src: self.clk_src.as_sdk(),
233                mclk_multiple: self.mclk_multiple.as_sdk(),
234            }
235        }
236
237        /// Convert to the ESP-IDF SDK `i2s_tdm_clk_config_t` representation.
238        #[cfg(esp_idf_version_at_least_5_1_0)]
239        #[allow(clippy::needless_update)]
240        #[inline(always)]
241        pub(crate) fn as_sdk(&self) -> i2s_tdm_clk_config_t {
242            i2s_tdm_clk_config_t {
243                sample_rate_hz: self.sample_rate_hz,
244                clk_src: self.clk_src.as_sdk(),
245                mclk_multiple: self.mclk_multiple.as_sdk(),
246                bclk_div: self.bclk_div,
247                ..Default::default()
248            }
249        }
250    }
251
252    #[cfg(esp_idf_version_major = "4")]
253    pub type TdmCommFormat = crate::i2s::std::config::StdCommFormat;
254
255    /// TDM mode GPIO (general purpose input/output) configuration.
256    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
257    pub struct TdmGpioConfig {
258        /// Invert the BCLK signal.
259        bclk_invert: bool,
260
261        /// Invert the MCLK signal.
262        mclk_invert: bool,
263
264        /// Invert the WS signal.
265        ws_invert: bool,
266    }
267
268    impl TdmGpioConfig {
269        /// Create a new TDM mode GPIO configuration with the specified inversion flags for BCLK, MCLK, and WS.
270        pub fn new(bclk_invert: bool, mclk_invert: bool, ws_invert: bool) -> Self {
271            Self {
272                bclk_invert,
273                mclk_invert,
274                ws_invert,
275            }
276        }
277
278        /// Set the BCLK inversion flag on this TDM GPIO configuration.
279        #[inline(always)]
280        pub fn bclk_invert(mut self, bclk_invert: bool) -> Self {
281            self.bclk_invert = bclk_invert;
282            self
283        }
284
285        /// Set the MCLK inversion flag on this TDM GPIO configuration.
286        #[inline(always)]
287        pub fn mclk_invert(mut self, mclk_invert: bool) -> Self {
288            self.mclk_invert = mclk_invert;
289            self
290        }
291
292        /// Set the WS inversion flag on this TDM GPIO configuration.
293        #[inline(always)]
294        pub fn ws_invert(mut self, ws_invert: bool) -> Self {
295            self.ws_invert = ws_invert;
296            self
297        }
298
299        /// Convert to the ESP-IDF SDK `i2s_tdm_gpio_config_t` representation.
300        #[cfg(not(esp_idf_version_major = "4"))]
301        pub(crate) fn as_sdk<'d>(
302            &self,
303            bclk: impl InputPin + OutputPin + 'd,
304            din: Option<impl InputPin + 'd>,
305            dout: Option<impl OutputPin + 'd>,
306            mclk: Option<impl InputPin + OutputPin + 'd>,
307            ws: impl InputPin + OutputPin + 'd,
308        ) -> i2s_tdm_gpio_config_t {
309            let invert_flags = i2s_tdm_gpio_config_t__bindgen_ty_1 {
310                _bitfield_1: i2s_tdm_gpio_config_t__bindgen_ty_1::new_bitfield_1(
311                    self.mclk_invert as u32,
312                    self.bclk_invert as u32,
313                    self.ws_invert as u32,
314                ),
315                ..Default::default()
316            };
317
318            i2s_tdm_gpio_config_t {
319                bclk: bclk.pin() as _,
320                din: if let Some(din) = din {
321                    din.pin() as _
322                } else {
323                    -1
324                },
325                dout: if let Some(dout) = dout {
326                    dout.pin() as _
327                } else {
328                    -1
329                },
330                mclk: if let Some(mclk) = mclk {
331                    mclk.pin() as _
332                } else {
333                    -1
334                },
335                ws: ws.pin() as _,
336                invert_flags,
337            }
338        }
339    }
340
341    /// TDM mode slot configuration.
342    ///
343    /// To create a slot configuration, use [`TdmSlotConfig::philips_slot_default`],
344    /// [`TdmSlotConfig::pcm_short_slot_default`], [`TdmSlotConfig::pcm_long_slot_default`], or
345    /// [`TdmSlotConfig::msb_slot_default`], then customize it as needed.
346    ///
347    /// In TDM mode, WS (word select, sometimes called LRCLK or left/right clock) becomes a frame synchronization
348    /// signal that signals the first slot of a frame. The two sides of the TDM link must agree on the number
349    /// of channels, data bit width, and frame synchronization pattern; this cannot be determined by examining the
350    /// signal itself.
351    ///
352    /// The Philips default pulls the WS line low one BCK period before the first data bit of the first slot is
353    /// sent and holds it low for 50% of the frame.
354    ///
355    #[doc = include_str!("tdm_slot_philips.svg")]
356    ///
357    /// MSB (most-significant bit) mode is similar to Philips mode, except the WS line is pulled low at the same time
358    /// the first data bit of the first slot is sent. It is held low for 50% of the frame.
359    ///
360    #[doc = include_str!("tdm_slot_msb.svg")]
361    ///
362    /// PCM (pulse-code modulation) short mode pulls the WS line *high* one BCK period before the first data bit of
363    /// the first slot is sent, keeps it high for one BCK, then pulls it low for the remainder of the frame.
364    #[doc = include_str!("tdm_slot_pcm_short.svg")]
365    /// PCM long mode pulls the WS line *high* one BCK period before the first data bit of the first slot is sent,
366    /// keeps it high until just before the last data bit of the first slot is sent, then pulls it low for the
367    /// remainder of the frame.
368    #[doc = include_str!("tdm_slot_pcm_long.svg")]
369    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
370    ///
371    /// Diagrams from _ESP-IDF Programming Guide_; rendered by Wavedrom.
372    pub struct TdmSlotConfig {
373        /// I2S sample data bit width (valid data bits per sample).
374        data_bit_width: DataBitWidth,
375
376        /// I2S slot bit width (total bits per slot).
377        slot_bit_width: SlotBitWidth,
378
379        /// Which slots are active in the TDM frame.
380        slot_mask: TdmSlotMask,
381
382        /// The word select (WS) signal width, in terms of the bit clock (BCK) periods.
383        #[cfg(not(esp_idf_version_major = "4"))]
384        ws_width: u32,
385
386        /// The word select signal polarity; `true` enables the high level first.
387        #[cfg(not(esp_idf_version_major = "4"))]
388        ws_polarity: bool,
389
390        /// Set to enable the additional bit-shift needed in Philips mode.
391        #[cfg(not(esp_idf_version_major = "4"))]
392        bit_shift: bool,
393
394        #[cfg(esp_idf_version_major = "4")]
395        comm_fmt: TdmCommFormat,
396
397        /// Enable left-alignment.
398        left_align: bool,
399
400        /// Enable big-endian.
401        big_endian: bool,
402
403        /// Enable LSB-first.
404        bit_order_lsb: bool,
405
406        /// Set to enable the skip mask. When enabled, only the data of the enabled channels will be sent. Otherwise,
407        /// all data stored in the DMA transmit buffer will be sent.
408        skip_mask: bool,
409
410        /// The total number of slots. If this is smaller than the highest activated channel number, it will be set
411        /// to that number automatically.
412        total_slots: u32,
413    }
414
415    impl TdmSlotConfig {
416        /// Update the data bit width on this TDM slot configuration.
417        #[inline(always)]
418        #[must_use]
419        pub fn data_bit_width(mut self, data_bit_width: DataBitWidth) -> Self {
420            self.data_bit_width = data_bit_width;
421            self
422        }
423
424        /// Update the slot bit width on this TDM slot configuration.
425        ///
426        /// This is normally set to [`SlotBitWidth::Auto`] to match `[data_bit_width][TdmSlotConfig::data_bit_width()]`.
427        #[inline(always)]
428        #[must_use]
429        pub fn slot_bit_width(mut self, slot_bit_width: SlotBitWidth) -> Self {
430            self.slot_bit_width = slot_bit_width;
431            self
432        }
433
434        /// Update the slot mask on this TDM slot configuration.
435        #[inline(always)]
436        #[must_use]
437        pub fn slot_mask(mut self, slot_mask: TdmSlotMask) -> Self {
438            self.slot_mask = slot_mask;
439            self
440        }
441
442        /// Update the word select signal width on this TDM slot configuration.
443        ///
444        /// This sets the number of bits to keep the word select signal active at the start of each frame. If this is
445        /// set to 0 ([`TDM_AUTO_WS_WIDTH`]), the word select signal will be kept active for half of the frame.
446        #[cfg(not(esp_idf_version_major = "4"))]
447        #[inline(always)]
448        #[must_use]
449        pub fn ws_width(mut self, ws_width: u32) -> Self {
450            self.ws_width = ws_width;
451            self
452        }
453
454        /// Update the word select signal polarity on this TDM slot configuration.
455        ///
456        /// Setting this to `true` will make the word select (WS) signal active high at the start (PCM modes).
457        /// Setting this to `false` will make the WS signal active low at the start (Philips and MSB modes).
458        #[cfg(not(esp_idf_version_major = "4"))]
459        #[inline(always)]
460        #[must_use]
461        pub fn ws_polarity(mut self, ws_polarity: bool) -> Self {
462            self.ws_polarity = ws_polarity;
463            self
464        }
465
466        /// Update the bit shift flag on this TDM slot configuration.
467        ///
468        /// Setting this to `true` will activate the word select (WS) signal lone BCK period before the first data bit
469        /// of the first slot is sent (Philips and PCM modes). Setting this to `false` will activate the WS
470        /// signal at the same time the first data bit of the first slot is sent (MSB mode).
471        #[cfg(not(esp_idf_version_major = "4"))]
472        #[inline(always)]
473        #[must_use]
474        pub fn bit_shift(mut self, bit_shift: bool) -> Self {
475            self.bit_shift = bit_shift;
476            self
477        }
478
479        /// Update the communication format on this TDM slot configuration.
480        #[cfg(esp_idf_version_major = "4")]
481        #[inline(always)]
482        #[must_use]
483        pub fn comm_fmt(mut self, comm_fmt: TdmCommFormat) -> Self {
484            self.comm_fmt = comm_fmt;
485            self
486        }
487
488        /// Update the left-alignment flag on this TDM slot configuration.
489        ///
490        /// This only has an effect when `[slot_bit_width][TdmSlotMask::slot_bit_width()]` is greater than
491        /// `[data_bit_width][TdmSlotMask::data_bit_width()]`. Setting this to `true` will left-align the data in the slot and
492        /// fill the right-most bits (usually the least-significant bits) with zeros. Setting this to `false` will right-align the
493        /// data in the slot and fill the left-most bits (usually the most-significant bits) with zeros.
494        #[inline(always)]
495        #[must_use]
496        pub fn left_align(mut self, left_align: bool) -> Self {
497            self.left_align = left_align;
498            self
499        }
500
501        /// Update the big-endian flag on this TDM slot configuration.
502        ///
503        /// This affects the interpretation of the data when `[data_bit_width][TdmSlotMask::data_bit_width()]` is
504        /// greater than 8. Setting this to
505        /// `true` will interpret the data as big-endian. Setting this to `false` will interpret the data as
506        /// little-endian (the default, and the native endian-ness of all ESP32 microcontrollers).
507        #[inline(always)]
508        #[must_use]
509        pub fn big_endian(mut self, big_endian: bool) -> Self {
510            self.big_endian = big_endian;
511            self
512        }
513
514        /// Update the LSB-first flag on this TDM slot configuration.
515        ///
516        /// Setting this to `true` will transmit data LSB-first (no known modes do this). Setting this to `false`
517        /// will transmit data MSB-first (the default for all known modes).
518        #[inline(always)]
519        #[must_use]
520        pub fn bit_order_lsb(mut self, bit_order_lsb: bool) -> Self {
521            self.bit_order_lsb = bit_order_lsb;
522            self
523        }
524
525        /// Update the skip mask flag on this TDM slot configuration.
526        ///
527        /// Setting this to `true` will ignore `[slot_mask][TdmSlotMask::slot_mask()]` and transmit all slots. Setting this to `false` will
528        /// respect the slot mask.
529        #[inline(always)]
530        #[must_use]
531        pub fn skip_mask(mut self, skip_mask: bool) -> Self {
532            self.skip_mask = skip_mask;
533            self
534        }
535
536        /// Update the total number of slots on this TDM slot configuration.
537        ///
538        /// Setting this to 0 ([`TDM_AUTO_SLOT_NUM`]) will automatically set the total number of slots to the
539        /// the number of active slots in `[slot_mask][TdmSlotMask::slot_mask()]`.
540        #[inline(always)]
541        #[must_use]
542        pub fn total_slots(mut self, total_slots: u32) -> Self {
543            self.total_slots = total_slots;
544            self
545        }
546
547        /// Configure in Philips format with the active slots enabled by the specified mask.
548        #[inline(always)]
549        #[must_use]
550        pub fn philips_slot_default(bits_per_sample: DataBitWidth, slot_mask: TdmSlotMask) -> Self {
551            Self {
552                data_bit_width: bits_per_sample,
553                slot_bit_width: SlotBitWidth::Auto,
554                slot_mask,
555                #[cfg(not(esp_idf_version_major = "4"))]
556                ws_width: TDM_AUTO_WS_WIDTH,
557                #[cfg(not(esp_idf_version_major = "4"))]
558                ws_polarity: false,
559                #[cfg(not(esp_idf_version_major = "4"))]
560                bit_shift: true,
561                #[cfg(esp_idf_version_major = "4")]
562                comm_fmt: TdmCommFormat::Philips,
563                left_align: false,
564                big_endian: false,
565                bit_order_lsb: false,
566                skip_mask: false,
567                total_slots: TDM_AUTO_SLOT_NUM,
568            }
569        }
570
571        /// Configure in MSB format with the active slots enabled by the specified mask.
572        #[inline(always)]
573        #[must_use]
574        pub fn msb_slot_default(bits_per_sample: DataBitWidth, slot_mask: TdmSlotMask) -> Self {
575            Self {
576                data_bit_width: bits_per_sample,
577                slot_bit_width: SlotBitWidth::Auto,
578                slot_mask,
579                #[cfg(not(esp_idf_version_major = "4"))]
580                ws_width: TDM_AUTO_WS_WIDTH,
581                #[cfg(not(esp_idf_version_major = "4"))]
582                ws_polarity: false,
583                #[cfg(not(esp_idf_version_major = "4"))]
584                bit_shift: false,
585                #[cfg(esp_idf_version_major = "4")]
586                comm_fmt: TdmCommFormat::Msb,
587                left_align: false,
588                big_endian: false,
589                bit_order_lsb: false,
590                skip_mask: false,
591                total_slots: TDM_AUTO_SLOT_NUM,
592            }
593        }
594
595        /// Configure in PCM (short) format with the active slots enabled by the specified mask.
596        #[inline(always)]
597        #[must_use]
598        pub fn pcm_short_slot_default(
599            bits_per_sample: DataBitWidth,
600            slot_mask: TdmSlotMask,
601        ) -> Self {
602            Self {
603                data_bit_width: bits_per_sample,
604                slot_bit_width: SlotBitWidth::Auto,
605                slot_mask,
606                #[cfg(not(esp_idf_version_major = "4"))]
607                ws_width: 1,
608                #[cfg(not(esp_idf_version_major = "4"))]
609                ws_polarity: true,
610                #[cfg(not(esp_idf_version_major = "4"))]
611                bit_shift: false,
612                #[cfg(esp_idf_version_major = "4")]
613                comm_fmt: TdmCommFormat::PcmShort,
614                left_align: false,
615                big_endian: false,
616                bit_order_lsb: false,
617                skip_mask: false,
618                total_slots: TDM_AUTO_SLOT_NUM,
619            }
620        }
621
622        /// Configure in PCM (long) format with the active slots enabled by the specified mask.
623        #[inline(always)]
624        #[must_use]
625        pub fn pcm_long_slot_default(
626            bits_per_sample: DataBitWidth,
627            slot_mask: TdmSlotMask,
628        ) -> Self {
629            Self {
630                data_bit_width: bits_per_sample,
631                slot_bit_width: SlotBitWidth::Auto,
632                slot_mask,
633                #[cfg(not(esp_idf_version_major = "4"))]
634                ws_width: bits_per_sample.into(),
635                #[cfg(not(esp_idf_version_major = "4"))]
636                ws_polarity: true,
637                #[cfg(not(esp_idf_version_major = "4"))]
638                bit_shift: false,
639                #[cfg(esp_idf_version_major = "4")]
640                comm_fmt: TdmCommFormat::PcmLong,
641                left_align: false,
642                big_endian: false,
643                bit_order_lsb: false,
644                skip_mask: false,
645                total_slots: TDM_AUTO_SLOT_NUM,
646            }
647        }
648
649        /// Convert to the ESP-IDF SDK `i2s_tdm_slot_config_t` representation.
650        #[cfg(not(esp_idf_version_major = "4"))]
651        #[inline(always)]
652        pub(crate) fn as_sdk(&self) -> i2s_tdm_slot_config_t {
653            i2s_tdm_slot_config_t {
654                data_bit_width: self.data_bit_width.as_sdk(),
655                slot_bit_width: self.slot_bit_width.as_sdk(),
656                slot_mode: SlotMode::Stereo.as_sdk(), // mono mode doesn't make sense in TDM
657                slot_mask: self.slot_mask.as_sdk(),
658                ws_width: self.ws_width,
659                ws_pol: self.ws_polarity,
660                bit_shift: self.bit_shift,
661                left_align: self.left_align,
662                big_endian: self.big_endian,
663                bit_order_lsb: self.bit_order_lsb,
664                skip_mask: self.skip_mask,
665                total_slot: self.total_slots,
666            }
667        }
668    }
669
670    /// An individual TDM slot.
671    #[derive(Copy, Clone, Debug, Eq, PartialEq)]
672    pub enum TdmSlot {
673        /// TDM slot #0
674        Slot0,
675
676        /// TDM slot #1
677        Slot1,
678
679        /// TDM slot #2
680        Slot2,
681
682        /// TDM slot #3
683        Slot3,
684
685        /// TDM slot #4
686        Slot4,
687
688        /// TDM slot #5
689        Slot5,
690
691        /// TDM slot #6
692        Slot6,
693
694        /// TDM slot #7
695        Slot7,
696
697        /// TDM slot #8
698        Slot8,
699
700        /// TDM slot #9
701        Slot9,
702
703        /// TDM slot #10
704        Slot10,
705
706        /// TDM slot #11
707        Slot11,
708
709        /// TDM slot #12
710        Slot12,
711
712        /// TDM slot #13
713        Slot13,
714
715        /// TDM slot #14
716        Slot14,
717
718        /// TDM slot #15
719        Slot15,
720    }
721
722    /// Mask of TDM slots to enable.
723    #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
724    pub struct TdmSlotMask(u16);
725
726    /// Attempt to convert from a `u8` to a `TdmSlot`.
727    impl TryFrom<u8> for TdmSlot {
728        type Error = EspError;
729
730        fn try_from(slot: u8) -> Result<Self, Self::Error> {
731            match slot {
732                0 => Ok(Self::Slot0),
733                1 => Ok(Self::Slot1),
734                2 => Ok(Self::Slot2),
735                3 => Ok(Self::Slot3),
736                4 => Ok(Self::Slot4),
737                5 => Ok(Self::Slot5),
738                6 => Ok(Self::Slot6),
739                7 => Ok(Self::Slot7),
740                8 => Ok(Self::Slot8),
741                9 => Ok(Self::Slot9),
742                10 => Ok(Self::Slot10),
743                11 => Ok(Self::Slot11),
744                12 => Ok(Self::Slot12),
745                13 => Ok(Self::Slot13),
746                14 => Ok(Self::Slot14),
747                15 => Ok(Self::Slot15),
748                _ => Err(EspError::from(ESP_ERR_INVALID_ARG).unwrap()),
749            }
750        }
751    }
752
753    /// Convert a `TdmSlot` to a `u8`.
754    impl From<TdmSlot> for u8 {
755        fn from(slot: TdmSlot) -> u8 {
756            match slot {
757                TdmSlot::Slot0 => 0,
758                TdmSlot::Slot1 => 1,
759                TdmSlot::Slot2 => 2,
760                TdmSlot::Slot3 => 3,
761                TdmSlot::Slot4 => 4,
762                TdmSlot::Slot5 => 5,
763                TdmSlot::Slot6 => 6,
764                TdmSlot::Slot7 => 7,
765                TdmSlot::Slot8 => 8,
766                TdmSlot::Slot9 => 9,
767                TdmSlot::Slot10 => 10,
768                TdmSlot::Slot11 => 11,
769                TdmSlot::Slot12 => 12,
770                TdmSlot::Slot13 => 13,
771                TdmSlot::Slot14 => 14,
772                TdmSlot::Slot15 => 15,
773            }
774        }
775    }
776
777    /// Convert a `TdmSlot` into a `TdmSlotMask`.
778    impl From<TdmSlot> for TdmSlotMask {
779        #[inline(always)]
780        fn from(slot: TdmSlot) -> TdmSlotMask {
781            TdmSlotMask(1 << u8::from(slot))
782        }
783    }
784
785    /// Bitwise AND a`TdmSlot` with another `TdmSlot` to produce a `TdmSlotMask`.
786    ///
787    /// If the slots are the same, the result is a `TdmSlotMask` containing that slot.
788    /// Otherwise, the result is an empty slot mask.
789    impl BitAnd<TdmSlot> for TdmSlot {
790        type Output = TdmSlotMask;
791
792        #[inline(always)]
793        fn bitand(self, rhs: Self) -> Self::Output {
794            TdmSlotMask::from(self) & TdmSlotMask::from(rhs)
795        }
796    }
797
798    /// Bitwise AND a `TdmSlot` with a `TdmSlotMask` to produce a `TdmSlotMask`.
799    ///
800    /// If the slot mask contains the slot, the result is a `TdmSlotMask` containing that slot.
801    /// Otherwise, the result is an empty slot mask.
802    impl BitAnd<TdmSlotMask> for TdmSlot {
803        type Output = TdmSlotMask;
804
805        #[inline(always)]
806        fn bitand(self, rhs: TdmSlotMask) -> Self::Output {
807            TdmSlotMask::from(self) & rhs
808        }
809    }
810
811    /// Bitwise AND a `TdmSlotMask` with a `TdmSlot` to produce a `TdmSlotMask`.
812    ///
813    /// If the slot mask contains the slot, the result is a `TdmSlotMask` containing that slot.
814    /// Otherwise, the result is an empty slot mask.
815    impl BitAnd<TdmSlot> for TdmSlotMask {
816        type Output = TdmSlotMask;
817
818        #[inline(always)]
819        fn bitand(self, rhs: TdmSlot) -> Self::Output {
820            self & TdmSlotMask::from(rhs)
821        }
822    }
823
824    /// Bitwise AND a `TdmSlotMask` with another `TdmSlotMask` to produce a `TdmSlotMask`.
825    ///
826    /// The result is a slot mask containing the slots that are common to both slot masks.
827    impl BitAnd<TdmSlotMask> for TdmSlotMask {
828        type Output = Self;
829
830        #[inline(always)]
831        fn bitand(self, rhs: Self) -> Self::Output {
832            Self(self.0 & rhs.0)
833        }
834    }
835
836    /// Bitwise AND a `TdmSlotMask` with a `TdmSlot` and assign the result to `self`.
837    ///
838    /// If the slot mask contains the slot, the result is a `TdmSlotMask` containing that slot.
839    /// Otherwise, the result is an empty slot mask.
840    impl BitAndAssign<TdmSlot> for TdmSlotMask {
841        #[inline(always)]
842        fn bitand_assign(&mut self, rhs: TdmSlot) {
843            self.0 &= TdmSlotMask::from(rhs).0;
844        }
845    }
846
847    /// Bitwise AND a `TdmSlotMask` with another `TdmSlotMask` and assign the result to `self`.
848    ///
849    /// The result is a slot mask containing the slots that are common to both slot masks.
850    impl BitAndAssign<TdmSlotMask> for TdmSlotMask {
851        #[inline(always)]
852        fn bitand_assign(&mut self, rhs: Self) {
853            self.0 &= rhs.0;
854        }
855    }
856
857    /// Bitwise OR a`TdmSlot` with another `TdmSlot` to produce a `TdmSlotMask`.
858    ///
859    /// The result is a `TdmSlotMask` containing both slots.
860    impl BitOr<TdmSlot> for TdmSlot {
861        type Output = TdmSlotMask;
862
863        #[inline(always)]
864        fn bitor(self, rhs: Self) -> Self::Output {
865            TdmSlotMask::from(self) | TdmSlotMask::from(rhs)
866        }
867    }
868
869    /// Bitwise OR a`TdmSlot` with a `TdmSlotMask` to produce a `TdmSlotMask`.
870    ///
871    /// The result is a `TdmSlotMask` containing the slot and all slots in the slot mask.
872    impl BitOr<TdmSlotMask> for TdmSlot {
873        type Output = TdmSlotMask;
874
875        #[inline(always)]
876        fn bitor(self, rhs: TdmSlotMask) -> Self::Output {
877            TdmSlotMask::from(self) | rhs
878        }
879    }
880
881    /// Bitwise OR a`TdmSlotMask` with a `TdmSlot` to produce a `TdmSlotMask`.
882    ///
883    /// The result is a `TdmSlotMask` containing the slot and all slots in the slot mask.
884    impl BitOr<TdmSlot> for TdmSlotMask {
885        type Output = TdmSlotMask;
886
887        #[inline(always)]
888        fn bitor(self, rhs: TdmSlot) -> Self::Output {
889            self | TdmSlotMask::from(rhs)
890        }
891    }
892
893    /// Bitwise OR a`TdmSlotMask` with another `TdmSlotMask` to produce a `TdmSlotMask`.
894    ///
895    /// The result is a `TdmSlotMask` containing the slots in either slot mask.
896    impl BitOr<TdmSlotMask> for TdmSlotMask {
897        type Output = Self;
898
899        #[inline(always)]
900        fn bitor(self, rhs: Self) -> Self::Output {
901            Self(self.0 | rhs.0)
902        }
903    }
904
905    /// Bitwise OR a`TdmSlotMask` with a `TdmSlot` and assign the result to `self`.
906    ///
907    /// The result is a `TdmSlotMask` containing the slot and all slots in the slot mask.
908    impl BitOrAssign<TdmSlot> for TdmSlotMask {
909        #[inline(always)]
910        fn bitor_assign(&mut self, rhs: TdmSlot) {
911            self.0 |= TdmSlotMask::from(rhs).0;
912        }
913    }
914
915    /// Bitwise OR a`TdmSlotMask` with another `TdmSlotMask` and assign the result to `self.
916    ///
917    /// The result is a `TdmSlotMask` containing the slots in either slot mask.
918    impl BitOrAssign<TdmSlotMask> for TdmSlotMask {
919        #[inline(always)]
920        fn bitor_assign(&mut self, rhs: Self) {
921            self.0 |= rhs.0;
922        }
923    }
924
925    /// Produce the bitwise NOT of a `TdmSlot` to produce a `TdmSlotMask` containing all slots
926    /// except the original slot.
927    impl Not for TdmSlot {
928        type Output = TdmSlotMask;
929
930        #[inline(always)]
931        fn not(self) -> Self::Output {
932            !TdmSlotMask::from(self)
933        }
934    }
935
936    /// Produce the bitwise NOT of a `TdmSlotMask` to produce a `TdmSlotMask` containing all slots
937    /// except the slots in the original slot mask.
938    impl Not for TdmSlotMask {
939        type Output = Self;
940
941        fn not(self) -> Self::Output {
942            Self(!self.0)
943        }
944    }
945
946    impl TdmSlotMask {
947        /// Creates a `TdmSlotMask` from the raw bit mask value.
948        #[inline(always)]
949        pub fn from_mask_value(value: u16) -> Self {
950            Self(value)
951        }
952
953        /// Indicates whether this slot mask is empty.
954        #[inline(always)]
955        pub fn is_empty(&self) -> bool {
956            self.0 == 0
957        }
958
959        /// Returns the number of slots in the slot mask.
960        #[inline(always)]
961        pub fn len(&self) -> usize {
962            self.0.count_ones() as usize
963        }
964
965        /// Returns the mask value as a `u16`.
966        #[inline(always)]
967        pub fn mask_value(&self) -> u16 {
968            self.0
969        }
970
971        /// Converts this mask to an ESP-IDF SDK `i2s_tdm_slot_mask_t` value.
972        #[cfg(not(esp_idf_version_major = "4"))]
973        #[inline(always)]
974        pub(super) fn as_sdk(&self) -> i2s_tdm_slot_mask_t {
975            self.0 as i2s_tdm_slot_mask_t
976        }
977
978        /// Converts this mask to an ESP-IDF SDK `i2s_channel_t` value.
979        #[cfg(esp_idf_version_major = "4")]
980        #[inline(always)]
981        pub(super) fn as_sdk(&self) -> i2s_channel_t {
982            ((self.0 as u32) << 16) as i2s_channel_t
983        }
984    }
985}
986
987impl<'d, Dir> I2sDriver<'d, Dir> {
988    #[cfg(not(esp_idf_version_major = "4"))]
989    #[allow(clippy::too_many_arguments)]
990    fn internal_new_tdm<I2S: I2s + 'd>(
991        _i2s: I2S,
992        config: &config::TdmConfig,
993        rx: bool,
994        tx: bool,
995        bclk: impl InputPin + OutputPin + 'd,
996        din: Option<impl InputPin + 'd>,
997        dout: Option<impl OutputPin + 'd>,
998        mclk: Option<impl InputPin + OutputPin + 'd>,
999        ws: impl InputPin + OutputPin + 'd,
1000    ) -> Result<Self, EspError> {
1001        let chan_cfg = config.channel_cfg.as_sdk(I2S::port());
1002
1003        let this = Self::internal_new::<I2S>(&chan_cfg, rx, tx)?;
1004
1005        // Create the channel configuration.
1006        let tdm_config = config.as_sdk(bclk, din, dout, mclk, ws);
1007
1008        if rx {
1009            unsafe {
1010                // Open the RX channel.
1011                esp!(i2s_channel_init_tdm_mode(this.rx_handle, &tdm_config))?;
1012            }
1013        }
1014
1015        if tx {
1016            unsafe {
1017                // Open the TX channel.
1018                esp!(i2s_channel_init_tdm_mode(this.tx_handle, &tdm_config))?;
1019            }
1020        }
1021
1022        Ok(this)
1023    }
1024
1025    #[cfg(esp_idf_version_major = "4")]
1026    #[allow(clippy::too_many_arguments)]
1027    fn internal_new_tdm<I2S: I2s + 'd>(
1028        _i2s: I2S,
1029        config: &config::TdmConfig,
1030        rx: bool,
1031        tx: bool,
1032        bclk: impl InputPin + OutputPin + 'd,
1033        din: Option<impl InputPin + 'd>,
1034        dout: Option<impl OutputPin + 'd>,
1035        mclk: Option<impl InputPin + OutputPin + 'd>,
1036        ws: impl InputPin + OutputPin + 'd,
1037    ) -> Result<Self, EspError> {
1038        let mut driver_cfg = config.as_sdk();
1039
1040        if rx {
1041            driver_cfg.mode |= i2s_mode_t_I2S_MODE_RX;
1042        }
1043
1044        if tx {
1045            driver_cfg.mode |= i2s_mode_t_I2S_MODE_TX;
1046        }
1047
1048        let this = Self::internal_new::<I2S>(&driver_cfg)?;
1049
1050        // Set the pin configuration.
1051        let pin_cfg = i2s_pin_config_t {
1052            bck_io_num: bclk.pin() as _,
1053            data_in_num: din.map(|din| din.pin() as _).unwrap_or(-1),
1054            data_out_num: dout.map(|dout| dout.pin() as _).unwrap_or(-1),
1055            mck_io_num: mclk.map(|mclk| mclk.pin() as _).unwrap_or(-1),
1056            ws_io_num: ws.pin() as _,
1057        };
1058
1059        // Safety: &pin_cfg is a valid pointer to an i2s_pin_config_t.
1060        unsafe {
1061            esp!(i2s_set_pin(this.port as _, &pin_cfg))?;
1062        }
1063
1064        Ok(this)
1065    }
1066}
1067
1068impl<'d> I2sDriver<'d, I2sBiDir> {
1069    /// Create a new TDM mode driver for the given I2S peripheral with both the receive and transmit channels open.
1070    #[cfg(not(any(esp32, esp32s2)))]
1071    #[cfg_attr(feature = "nightly", doc(cfg(not(any(esp32, esp32s2)))))]
1072    #[allow(clippy::too_many_arguments)]
1073    pub fn new_tdm_bidir<I2S: I2s + 'd>(
1074        i2s: I2S,
1075        config: &config::TdmConfig,
1076        bclk: impl InputPin + OutputPin + 'd,
1077        din: impl InputPin + 'd,
1078        dout: impl OutputPin + 'd,
1079        mclk: Option<impl InputPin + OutputPin + 'd>,
1080        ws: impl InputPin + OutputPin + 'd,
1081    ) -> Result<Self, EspError> {
1082        Self::internal_new_tdm(
1083            i2s,
1084            config,
1085            true,
1086            true,
1087            bclk,
1088            Some(din),
1089            Some(dout),
1090            mclk,
1091            ws,
1092        )
1093    }
1094}
1095
1096impl<'d> I2sDriver<'d, I2sRx> {
1097    /// Create a new TDM mode driver for the given I2S peripheral with only the receive channel open.
1098    #[cfg(not(any(esp32, esp32s2)))]
1099    #[cfg_attr(feature = "nightly", doc(cfg(not(any(esp32, esp32s2)))))]
1100    #[allow(clippy::too_many_arguments)]
1101    pub fn new_tdm_rx<I2S: I2s + 'd>(
1102        i2s: I2S,
1103        config: &config::TdmConfig,
1104        bclk: impl InputPin + OutputPin + 'd,
1105        din: impl InputPin + 'd,
1106        mclk: Option<impl InputPin + OutputPin + 'd>,
1107        ws: impl InputPin + OutputPin + 'd,
1108    ) -> Result<Self, EspError> {
1109        Self::internal_new_tdm(
1110            i2s,
1111            config,
1112            true,
1113            false,
1114            bclk,
1115            Some(din),
1116            AnyIOPin::none(),
1117            mclk,
1118            ws,
1119        )
1120    }
1121}
1122
1123impl<'d> I2sDriver<'d, I2sTx> {
1124    /// Create a new TDM mode driver for the given I2S peripheral with only the transmit channel open.
1125    #[cfg(not(any(esp32, esp32s2)))]
1126    #[cfg_attr(feature = "nightly", doc(cfg(not(any(esp32, esp32s2)))))]
1127    #[allow(clippy::too_many_arguments)]
1128    pub fn new_tdm_tx<I2S: I2s + 'd>(
1129        i2s: I2S,
1130        config: &config::TdmConfig,
1131        bclk: impl InputPin + OutputPin + 'd,
1132        dout: impl OutputPin + 'd,
1133        mclk: Option<impl InputPin + OutputPin + 'd>,
1134        ws: impl InputPin + OutputPin + 'd,
1135    ) -> Result<Self, EspError> {
1136        Self::internal_new_tdm(
1137            i2s,
1138            config,
1139            false,
1140            true,
1141            bclk,
1142            AnyIOPin::none(),
1143            Some(dout),
1144            mclk,
1145            ws,
1146        )
1147    }
1148}
1149
1150/// TDM-mode runtime reconfiguration.
1151///
1152/// Reconfigure the clock + slot config of an already-initialised TDM mode
1153/// channel without tearing the driver down, the TDM counterpart of
1154/// [`I2sDriver::rx_reconfigure_std`].
1155///
1156/// The channel is briefly disabled while the reconfigure happens and
1157/// re-enabled on success. GPIO pins are not touched.
1158#[cfg(all(not(esp_idf_version_major = "4"), esp_idf_soc_i2s_supports_tdm))]
1159impl<Dir> I2sDriver<'_, Dir>
1160where
1161    Dir: I2sRxSupported,
1162{
1163    /// Reconfigure the RX channel's clock + slot from a new [`config::TdmConfig`].
1164    ///
1165    /// Fails if the channel is not currently enabled.
1166    pub fn rx_reconfigure_tdm(&mut self, config: &config::TdmConfig) -> Result<(), EspError> {
1167        let clk_cfg = config.clk_cfg_as_sdk();
1168        let slot_cfg = config.slot_cfg_as_sdk();
1169        unsafe {
1170            esp!(i2s_channel_disable(self.rx_handle))?;
1171            esp!(i2s_channel_reconfig_tdm_clock(self.rx_handle, &clk_cfg))?;
1172            esp!(i2s_channel_reconfig_tdm_slot(self.rx_handle, &slot_cfg))?;
1173            esp!(i2s_channel_enable(self.rx_handle))?;
1174        }
1175        Ok(())
1176    }
1177}
1178
1179/// TDM-mode runtime reconfiguration.
1180///
1181/// Reconfigure the clock + slot config of an already-initialised TDM mode
1182/// channel without tearing the driver down, the TDM counterpart of
1183/// [`I2sDriver::tx_reconfigure_std`].
1184///
1185/// The channel is briefly disabled while the reconfigure happens and
1186/// re-enabled on success. GPIO pins are not touched.
1187#[cfg(all(not(esp_idf_version_major = "4"), esp_idf_soc_i2s_supports_tdm))]
1188impl<Dir> I2sDriver<'_, Dir>
1189where
1190    Dir: I2sTxSupported,
1191{
1192    /// Reconfigure the TX channel's clock + slot from a new [`config::TdmConfig`].
1193    ///
1194    /// Fails if the channel is not currently enabled.
1195    pub fn tx_reconfigure_tdm(&mut self, config: &config::TdmConfig) -> Result<(), EspError> {
1196        let clk_cfg = config.clk_cfg_as_sdk();
1197        let slot_cfg = config.slot_cfg_as_sdk();
1198        unsafe {
1199            esp!(i2s_channel_disable(self.tx_handle))?;
1200            esp!(i2s_channel_reconfig_tdm_clock(self.tx_handle, &clk_cfg))?;
1201            esp!(i2s_channel_reconfig_tdm_slot(self.tx_handle, &slot_cfg))?;
1202            esp!(i2s_channel_enable(self.tx_handle))?;
1203        }
1204        Ok(())
1205    }
1206}