esp-hal 1.2.0

Bare-metal HAL for Espressif devices
Documentation
use crate::{
    gpio::lp_io::LpFunction,
    peripherals::{GPIO, LPWR, RTC_IO},
};

macro_rules! lp_io_analog {
    ($pin_peri:ident, $lp_pin:expr, $pin_reg:expr, $prefix:pat, $hold:ident) => {
        paste::paste! {
            #[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
            impl crate::gpio::LpPin for crate::peripherals::$pin_peri<'_> {
                fn lp_number(&self) -> u8 {
                    $lp_pin
                }
            }

            impl crate::peripherals::$pin_peri<'_> {
                #[cfg(feature = "unstable")]
                pub(crate) fn set_analog_impl(&self) {
                    use crate::gpio::{LpPin, Pin};

                    output_enable(self.lp_number(), false);
                    set_open_drain_output(self.number(), false);

                    RTC_IO::regs().$pin_reg.modify(|_, w| {
                        w.[<$prefix fun_ie>]().clear_bit();
                        w.[<$prefix mux_sel>]().set_bit();
                        unsafe { w.[<$prefix fun_sel>]().bits(LpFunction::LP_GPIO as u8) };

                        // Only output pins have PU/PD resistors.
                        for_each_gpio! {
                            ($n:tt, $pin_peri $in_afs:tt $out_afs:tt ($input:tt [Output])) => {
                                w.[<$prefix rue>]().bit(false);
                                w.[<$prefix rde>]().bit(false);
                            };
                        }

                        w
                    });
                }
            }
        }
    };

    (
        $(($pin_peri:ident, $lp_pin:tt, $pin_reg:expr, $prefix:pat, $hold:ident))+
    ) => {
        $(
            lp_io_analog!($pin_peri, $lp_pin, $pin_reg, $prefix, $hold);
        )+

        // Each pad has its own register, so this code selects the register by the low-power number.
        pub(crate) fn set_config(lp: u8, input_enable: bool, mux: bool, func: LpFunction) {
            paste::paste! {
                match lp {
                    $(
                        $lp_pin => {
                            RTC_IO::regs().$pin_reg.modify(|_, w| unsafe {
                                w.[<$prefix fun_ie>]().bit(input_enable);
                                w.[<$prefix mux_sel>]().bit(mux);
                                w.[<$prefix fun_sel>]().bits(func as u8)
                            });
                        }
                    )+
                    _ => unreachable!(),
                }
            }
        }

        /// A hold takes two bits on this chip, one in the pad register and one
        /// in the always-on register. The pad keeps the bit of the pad register
        /// through a reset, so a release must clear both.
        pub(crate) fn pad_hold(lp: u8, enable: bool) {
            paste::paste! {
                match lp {
                    $(
                        $lp_pin => {
                            RTC_IO::regs()
                                .$pin_reg
                                .modify(|_, w| w.[<$prefix hold>]().bit(enable));
                            LPWR::regs()
                                .hold_force()
                                .modify(|_, w| w.$hold().bit(enable));
                        }
                    )+
                    _ => unreachable!(),
                }
            }
        }

        /// Returns whether something holds the pad.
        ///
        /// Either bit of [`pad_hold`] alone keeps the pad frozen.
        pub(crate) fn is_pad_held(lp: u8) -> bool {
            paste::paste! {
                match lp {
                    $(
                        $lp_pin => {
                            RTC_IO::regs().$pin_reg.read().[<$prefix hold>]().bit_is_set()
                                || LPWR::regs().hold_force().read().$hold().bit_is_set()
                        }
                    )+
                    _ => unreachable!(),
                }
            }
        }

        /// One bit for each low-power pad.
        const ALL_PADS: u32 = 0 $( | 1 << $lp_pin )+;

        macro_rules! set_one_pad_field {
            $(
                ($lp_pin, $field:ident, $enable:ident) => {{
                    paste::paste! {
                        RTC_IO::regs()
                            .$pin_reg
                            .modify(|_, w|  w.[<$prefix $field>]().bit($enable));
                    }
                }};
            )+
        }
    };
}

lp_io_analog! {
    (GPIO36, 0,  sensor_pads(),    sense1_, sense1    )
    (GPIO37, 1,  sensor_pads(),    sense2_, sense2    )
    (GPIO38, 2,  sensor_pads(),    sense3_, sense3    )
    (GPIO39, 3,  sensor_pads(),    sense4_, sense4    )
    (GPIO34, 4,  adc_pad(),        adc1_,   adc1      )
    (GPIO35, 5,  adc_pad(),        adc2_,   adc2      )
    (GPIO25, 6,  pad_dac1(),       "",      pdac1     )
    (GPIO26, 7,  pad_dac2(),       "",      pdac2     )
    (GPIO33, 8,  xtal_32k_pad(),   x32n_,   x32n      )
    (GPIO32, 9,  xtal_32k_pad(),   x32p_,   x32p      )
    (GPIO4,  10, touch_pad0(),     "",      touch_pad0)
    (GPIO0,  11, touch_pad1(),     "",      touch_pad1)
    (GPIO2,  12, touch_pad2(),     "",      touch_pad2)
    (GPIO15, 13, touch_pad3(),     "",      touch_pad3)
    (GPIO13, 14, touch_pad4(),     "",      touch_pad4)
    (GPIO12, 15, touch_pad5(),     "",      touch_pad5)
    (GPIO14, 16, touch_pad6(),     "",      touch_pad6)
    (GPIO27, 17, touch_pad7(),     "",      touch_pad7)
}

macro_rules! set_pad_field {
    ($pin:ident, $field:ident, $enable:ident) => {{
        match $pin {
            0 => set_one_pad_field!(0, $field, $enable),
            1 => set_one_pad_field!(1, $field, $enable),
            2 => set_one_pad_field!(2, $field, $enable),
            3 => set_one_pad_field!(3, $field, $enable),
            4 => set_one_pad_field!(4, $field, $enable),
            5 => set_one_pad_field!(5, $field, $enable),
            6 => set_one_pad_field!(6, $field, $enable),
            7 => set_one_pad_field!(7, $field, $enable),
            8 => set_one_pad_field!(8, $field, $enable),
            9 => set_one_pad_field!(9, $field, $enable),
            10 => set_one_pad_field!(10, $field, $enable),
            11 => set_one_pad_field!(11, $field, $enable),
            12 => set_one_pad_field!(12, $field, $enable),
            13 => set_one_pad_field!(13, $field, $enable),
            14 => set_one_pad_field!(14, $field, $enable),
            15 => set_one_pad_field!(15, $field, $enable),
            16 => set_one_pad_field!(16, $field, $enable),
            17 => set_one_pad_field!(17, $field, $enable),
            _ => unreachable!(),
        }
    }};
}

macro_rules! set_pull_field {
    ($pin:ident, $field:ident, $enable:ident) => {{
        match $pin {
            0..=5 => warn!("Pin {} does not have PU/PD resistors", $pin),
            6 => set_one_pad_field!(6, $field, $enable),
            7 => set_one_pad_field!(7, $field, $enable),
            8 => set_one_pad_field!(8, $field, $enable),
            9 => set_one_pad_field!(9, $field, $enable),
            10 => set_one_pad_field!(10, $field, $enable),
            11 => set_one_pad_field!(11, $field, $enable),
            12 => set_one_pad_field!(12, $field, $enable),
            13 => set_one_pad_field!(13, $field, $enable),
            14 => set_one_pad_field!(14, $field, $enable),
            15 => set_one_pad_field!(15, $field, $enable),
            16 => set_one_pad_field!(16, $field, $enable),
            17 => set_one_pad_field!(17, $field, $enable),
            _ => unreachable!(),
        }
    }};
}

/// Returns the bit of the pad of `gpio` in the hold register of the digital pads.
///
/// The register covers the pads of the digital supply, and lists them in its own order. The digital
/// supply feeds no other pad, so no other pad reaches this function.
fn digital_hold_bit(gpio: u8) -> Option<u8> {
    Some(match gpio {
        1 => 1,
        3 => 0,
        5 => 8,
        6..=11 => gpio - 4,
        16..=19 | 21..=23 => gpio - 7,
        _ => return None,
    })
}

/// Takes or releases the hold of the pad of `gpio`.
pub(crate) fn digital_pad_hold(gpio: u8, enable: bool) {
    let Some(bit) = digital_hold_bit(gpio) else {
        return;
    };

    let mask = 1 << bit;
    RTC_IO::regs().dig_pad_hold().modify(|r, w| unsafe {
        let bits = r.dig_pad_hold().bits();
        w.dig_pad_hold()
            .bits(if enable { bits | mask } else { bits & !mask })
    });
}

/// Returns whether something holds the pad of `gpio`.
pub(crate) fn is_digital_pad_held(gpio: u8) -> bool {
    let Some(bit) = digital_hold_bit(gpio) else {
        return false;
    };

    RTC_IO::regs().dig_pad_hold().read().dig_pad_hold().bits() & (1 << bit) != 0
}

pub(crate) fn apply_wakeup(lp: u8, wakeup: bool, level: crate::gpio::Level) {
    RTC_IO::regs().pin(lp as usize).modify(|_, w| unsafe {
        w.wakeup_enable().bit(wakeup);
        w.int_type().bits(crate::gpio::lp_io::wake_trigger(level))
    });
}

/// Returns the pads whose per-pin wakeup path triggered, as a mask of low-power numbers.
pub(crate) fn wakeup_status() -> u32 {
    RTC_IO::regs().status().read().int().bits()
}

/// Clears [`wakeup_status`], so that it reports the next sleep and no earlier sleep.
pub(crate) fn clear_wakeup_status() {
    RTC_IO::regs()
        .status_w1tc()
        .write(|w| unsafe { w.status_int_w1tc().bits(ALL_PADS) });
}

/// Returns the pads that can wake the chip through the per-pin path, as a mask of low-power
/// numbers.
pub(crate) fn wakeup_enabled_mask() -> u32 {
    let mut mask = 0;
    let mut pads = ALL_PADS;
    while pads != 0 {
        let lp = pads.trailing_zeros();
        pads &= !(1 << lp);

        if RTC_IO::regs()
            .pin(lp as usize)
            .read()
            .wakeup_enable()
            .bit_is_set()
        {
            mask |= 1 << lp;
        }
    }
    mask
}

#[expect(dead_code)]
pub(crate) fn init_pin(lp: u8, input_enable: bool) -> u8 {
    set_config(lp, input_enable, true, LpFunction::LP_GPIO);
    lp
}

pub(crate) fn output_enable(lp: u8, enable: bool) {
    if enable {
        RTC_IO::regs()
            .enable_w1ts()
            .write(|w| unsafe { w.enable_w1ts().bits(1 << lp) });
    } else {
        RTC_IO::regs()
            .enable_w1tc()
            .write(|w| unsafe { w.enable_w1tc().bits(1 << lp) });
    }
}

#[expect(dead_code)]
pub(crate) fn input_enable(lp: u8, enable: bool) {
    set_pad_field!(lp, fun_ie, enable);
}

pub(crate) fn pullup_enable(lp: u8, enable: bool) {
    set_pull_field!(lp, rue, enable);
}

pub(crate) fn pulldown_enable(lp: u8, enable: bool) {
    set_pull_field!(lp, rde, enable);
}

// The pad driver bit is part of the digital GPIO peripheral, and this chip gives a pad a different
// low-power number, so this function takes the digital number.
pub(crate) fn set_open_drain_output(gpio: u8, enable: bool) {
    GPIO::regs()
        .pin(gpio as usize)
        .modify(|_, w| w.pad_driver().bit(enable));
}