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