1
#![cfg_attr(docsrs, procmacros::doc_replace)]
2

            
3
//! Sigma-delta modulation peripheral.
4
//!
5
//! The sigma-delta modulator produces a pulse-density modulated output on a
6
//! GPIO matrix signal. Each channel is a peripheral singleton (`SDM_CH0`,
7
//! `SDM_CH1`, …). Configure a channel with a carrier frequency and pulse
8
//! density, then connect it to one output pin.
9
//!
10
//! ## Examples
11
//!
12
//! Generate a sigma-delta output signal on a GPIO pin.
13
//!
14
//! ```rust, no_run
15
//! # {before_snippet}
16
//! use esp_hal::{
17
//!     sdm::{Channel, ChannelConfig},
18
//!     time::Rate,
19
//! };
20
//!
21
//! let config = ChannelConfig::new()
22
//!     // Select the prescaler that produces the closest available frequency.
23
//!     .with_frequency(Rate::from_khz(500))?
24
//!     .with_duty(128);
25
//! let mut channel = Channel::new(peripherals.SDM_CH0, peripherals.GPIO2, config);
26
//!
27
//! channel.set_duty(192); // duty ranges from 0 to 255
28
//! channel.set_pulse_density(0); // pulse density ranges from -128 to 127
29
//!
30
//! # {after_snippet}
31
//! ```
32
//!
33
//! ## Clock source
34
#![cfg_attr(
35
    not(soc_has_clock_node_iomux_function_clock),
36
    doc = r#"
37
The SDM function clock is derived from the APB clock.
38
"#
39
)]
40
#![cfg_attr(
41
    soc_has_clock_node_iomux_function_clock,
42
    doc = r#"
43
The SDM function clock is derived from the global `IOMUX_FUNCTION_CLOCK`. Its
44
source is shared by every consumer of that clock and is therefore configured
45
globally instead of through [`Channel`]. The available sources and the default for
46
the selected target are listed by
47
[`IomuxFunctionClockConfig`](crate::clock::ll::IomuxFunctionClockConfig).
48

            
49
The source can be selected as part of the global clock configuration before initializing the HAL.
50
"#
51
)]
52
//! Each channel's prescaler divides this function clock to produce its output
53
//! frequency.
54

            
55
use core::fmt;
56

            
57
use crate::{
58
    gpio::{
59
        OutputConfig,
60
        OutputSignal,
61
        PinGuard,
62
        interconnect::{OutputSignal as GpioOutputSignal, PeripheralOutput},
63
    },
64
    peripherals::GPIO_SD,
65
    soc::clocks::{ClockTree, SdmInstance},
66
    system::{GenericPeripheralGuard, Peripheral},
67
    time::Rate,
68
};
69

            
70
/// Immutable per-channel metadata owned by each `SDM_CH*` singleton.
71
#[doc(hidden)]
72
pub struct ChannelInfo {
73
    /// Hardware channel index used to select the register bank.
74
    channel: usize,
75
    /// GPIO matrix output signal for this channel.
76
    signal: OutputSignal,
77
}
78

            
79
/// A peripheral singleton compatible with the sigma-delta driver.
80
#[doc(hidden)]
81
pub trait Instance: crate::private::Sealed + any::Degrade {
82
    /// Returns the metadata for this channel.
83
    fn info(&self) -> &'static ChannelInfo;
84
}
85

            
86
impl Instance for AnySdmChannel<'_> {
87
    fn info(&self) -> &'static ChannelInfo {
88
        any::delegate!(self, channel => { channel.info() })
89
    }
90
}
91

            
92
for_each_sdm_channel! {
93
    (channels $(($num:literal, $peri:ident, $variant:ident, $signal:ident)),*) => {
94
        crate::any_peripheral! {
95
            /// Any SDM channel.
96
            pub peripheral AnySdmChannel<'d> {
97
                $(
98
                    $variant(crate::peripherals::$peri<'d>),
99
                )*
100
            }
101
        }
102
    };
103

            
104
    ($num:literal, $peri:ident, $variant:ident, $signal:ident) => {
105
        impl crate::sdm::Instance for crate::peripherals::$peri<'_> {
106
            fn info(&self) -> &'static ChannelInfo {
107
                static INFO: ChannelInfo = ChannelInfo {
108
                    channel: $num,
109
                    signal: OutputSignal::$signal,
110
                };
111
                &INFO
112
            }
113
        }
114
    };
115
}
116

            
117
/// Sigma-delta configuration or runtime error.
118
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
120
#[non_exhaustive]
121
pub enum Error {
122
    /// The requested frequency cannot be represented by the hardware prescaler.
123
    UnreachableTargetFrequency,
124
    /// The prescaler is outside the supported range.
125
    PrescalerOutOfRange,
126
}
127

            
128
impl fmt::Display for Error {
129
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130
        match self {
131
            Self::UnreachableTargetFrequency => f.write_str("Unreachable target frequency"),
132
            Self::PrescalerOutOfRange => f.write_str("Prescaler out of range"),
133
        }
134
    }
135
}
136

            
137
impl core::error::Error for Error {}
138

            
139
/// Sigma-delta channel configuration.
140
///
141
/// The hardware stores the prescaler and pulse density in the same register,
142
/// so applying a complete channel configuration can update both fields with a
143
/// single register write.
144
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, procmacros::BuilderLite)]
145
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
146
#[non_exhaustive]
147
pub struct ChannelConfig {
148
    /// Raw hardware prescaler value.
149
    #[builder_lite(skip)]
150
    raw_prescaler: u8,
151

            
152
    /// Pulse density in the hardware range `-128..=127`.
153
    pulse_density: i8,
154
}
155

            
156
impl ChannelConfig {
157
    /// Creates a new channel configuration with the default prescaler and pulse
158
    /// density.
159
    pub const fn new() -> Self {
160
        Self {
161
            raw_prescaler: 0,
162
            pulse_density: 0,
163
        }
164
    }
165

            
166
    /// Sets the requested output frequency.
167
    ///
168
    /// Selects the prescaler that produces the closest output frequency using
169
    /// the currently configured SDM clock source.
170
    ///
171
    /// # Errors
172
    ///
173
    /// [`Error::UnreachableTargetFrequency`] when no hardware
174
    /// prescaler can represent the requested frequency.
175
2
    pub fn with_frequency(mut self, frequency: Rate) -> Result<Self, Error> {
176
        self.raw_prescaler = raw_prescaler(prescaler_from_frequency(frequency)?);
177
        Ok(self)
178
2
    }
179

            
180
    /// Sets the hardware prescaler.
181
    ///
182
    /// The hardware divider range is `1..=256`.
183
    ///
184
    /// # Errors
185
    ///
186
    /// [`Error::PrescalerOutOfRange`] when `prescaler` is not in
187
    /// `1..=256`.
188
    pub fn with_prescaler(mut self, prescaler: u16) -> Result<Self, Error> {
189
        check_prescaler(prescaler)?;
190
        self.raw_prescaler = raw_prescaler(prescaler);
191
        Ok(self)
192
    }
193

            
194
    /// Sets duty cycle. `0` maps to the minimum density and `255` maps to the
195
    /// maximum density.
196
    pub const fn with_duty(mut self, duty: u8) -> Self {
197
        self.pulse_density = duty_to_density(duty);
198
        self
199
    }
200
}
201

            
202
/// A connected sigma-delta channel.
203
///
204
/// Dropping a channel disconnects its output pin and releases the SDM clock
205
/// guard.
206
#[derive(Debug)]
207
pub struct Channel<'d> {
208
    channel: AnySdmChannel<'d>,
209
    _pin_guard: PinGuard,
210
    _clock_guard: SdmClockGuard,
211
}
212

            
213
impl<'d> Channel<'d> {
214
    /// Creates a new connected sigma-delta channel.
215
    pub fn new(
216
        channel: impl Instance + 'd,
217
        pin: impl PeripheralOutput<'d>,
218
        config: ChannelConfig,
219
    ) -> Self {
220
        let info = channel.info();
221
2
        let clock_guard = SdmClockGuard::new();
222

            
223
2
        let mut this = Self {
224
            channel: channel.degrade(),
225
2
            _pin_guard: connect_pin(info.signal, pin),
226
            _clock_guard: clock_guard,
227
        };
228
2
        this.apply_config(&config);
229
2
        this
230
    }
231

            
232
    fn index(&self) -> usize {
233
2
        self.channel.info().channel
234
    }
235

            
236
    /// Applies a new channel configuration.
237
    pub fn apply_config(&mut self, config: &ChannelConfig) {
238
        GPIO_SD::regs().sigmadelta(self.index()).write(|w| unsafe {
239
            cfg_select! {
240
                esp32c5 => {
241
                    w.sd_in().bits(config.pulse_density as u8);
242
                    w.sd_prescale().bits(config.raw_prescaler)
243
                }
244
                _ => {
245
                    w.in_().bits(config.pulse_density as u8);
246
                    w.prescale().bits(config.raw_prescaler)
247
                }
248
            }
249
        });
250
2
    }
251

            
252
    /// Sets raw pulse density.
253
    ///
254
    /// The value ranges from `-128` to `127`.
255
    pub fn set_pulse_density(&mut self, density: i8) {
256
        GPIO_SD::regs()
257
            .sigmadelta(self.index())
258
            .modify(|_, w| unsafe {
259
                cfg_select! {
260
                    esp32c5 => w.sd_in().bits(density as u8),
261
                    _ => w.in_().bits(density as u8),
262
                }
263
            });
264
    }
265

            
266
    /// Sets duty cycle. `0` maps to the minimum density and `255` maps to the
267
    /// maximum density.
268
    pub fn set_duty(&mut self, duty: u8) {
269
        self.set_pulse_density(duty_to_density(duty))
270
2
    }
271

            
272
    /// Reads the hardware prescaler.
273
    ///
274
    /// The returned value is in the hardware divider range `1..=256`.
275
    pub fn prescaler(&self) -> u16 {
276
        let reg = GPIO_SD::regs().sigmadelta(self.index()).read();
277
        let bits = cfg_select! {
278
            esp32c5 => reg.sd_prescale().bits(),
279
            _ => reg.prescale().bits(),
280
        };
281

            
282
        bits as u16 + 1
283
    }
284

            
285
    /// Reads the raw pulse density.
286
    ///
287
    /// The returned value is in the hardware range `-128..=127`.
288
    pub fn pulse_density(&self) -> i8 {
289
        let reg = GPIO_SD::regs().sigmadelta(self.index()).read();
290
        let bits = cfg_select! {
291
            esp32c5 => reg.sd_in().bits(),
292
            _ => reg.in_().bits(),
293
        };
294

            
295
        bits as i8
296
    }
297
}
298

            
299
fn connect_pin<'d>(signal: OutputSignal, pin: impl PeripheralOutput<'d>) -> PinGuard {
300
2
    let pin: GpioOutputSignal<'d> = pin.into();
301
    pin.apply_output_config(&OutputConfig::default());
302
    pin.set_output_enable(true);
303
2
    pin.connect_with_guard(signal)
304
}
305

            
306
#[derive(Debug)]
307
struct SdmClockGuard {
308
    // Fields are dropped in declaration order. Release the function clock
309
    // while the GPIO_SD register clock is still available.
310
    _function_clock: SdmFunctionClockGuard,
311
    _peripheral: GenericPeripheralGuard<{ Peripheral::GpioSd as u8 }>,
312
}
313

            
314
impl SdmClockGuard {
315
2
    fn new() -> Self {
316
        let peripheral = GenericPeripheralGuard::new();
317
        let function_clock = SdmFunctionClockGuard::new();
318
        Self {
319
            _function_clock: function_clock,
320
            _peripheral: peripheral,
321
        }
322
2
    }
323
}
324

            
325
#[derive(Debug)]
326
struct SdmFunctionClockGuard;
327

            
328
impl SdmFunctionClockGuard {
329
    fn new() -> Self {
330
2
        ClockTree::with(|clocks| SdmInstance::GpioSd.request_function_clock(clocks));
331
        Self
332
    }
333
}
334

            
335
impl Drop for SdmFunctionClockGuard {
336
    fn drop(&mut self) {
337
        ClockTree::with(|clocks| SdmInstance::GpioSd.release_function_clock(clocks));
338
    }
339
}
340

            
341
fn prescaler_from_frequency(frequency: Rate) -> Result<u16, Error> {
342
    let source_frequency = SdmInstance::GpioSd.function_clock_frequency() as u64;
343
    let requested_frequency = frequency.as_hz() as u64;
344

            
345
2
    if requested_frequency == 0
346
        || requested_frequency > source_frequency
347
        || requested_frequency * 256 < source_frequency
348
    {
349
        return Err(Error::UnreachableTargetFrequency);
350
    }
351

            
352
    // The closest output must be produced by one of the two integers around
353
    // the ideal prescaler.
354
2
    let lower = source_frequency / requested_frequency;
355
    let upper = lower + 1;
356
2
    if upper > 256 {
357
        return Ok(lower as u16);
358
    }
359

            
360
    // Compare |source / prescaler - requested| without truncating either
361
    // resulting frequency.
362
2
    let lower_error = (source_frequency - requested_frequency * lower) * upper;
363
2
    let upper_error = (requested_frequency * upper - source_frequency) * lower;
364

            
365
2
    Ok(if lower_error <= upper_error {
366
        lower as u16
367
    } else {
368
        upper as u16
369
    })
370
}
371

            
372
fn check_prescaler(prescaler: u16) -> Result<(), Error> {
373
    if (1..=256).contains(&prescaler) {
374
        Ok(())
375
    } else {
376
        Err(Error::PrescalerOutOfRange)
377
    }
378
}
379

            
380
fn raw_prescaler(prescaler: u16) -> u8 {
381
2
    (prescaler - 1) as u8
382
}
383

            
384
const fn duty_to_density(duty: u8) -> i8 {
385
    duty.wrapping_sub(128) as i8
386
}