#![cfg_attr(docsrs, procmacros::doc_replace)]
#![doc = concat!(r#"
> "#, chip!() , r#" specific: There are "#, property!("dedicated_gpio.channel_count", str) , r#" dedicated GPIO channels available.
"#)]
#![cfg_attr(
not(esp32s3),
doc = r#"- [`output_levels_ll`]: read the current output levels of all channels"#
)]
#![cfg_attr(
esp32s3,
doc = r#"- `output_levels_ll`: read the current output levels of all channels (not available on ESP32-S3 due to an LLVM bug, see <https://github.com/espressif/llvm-project/issues/120>)"#
)]
#![cfg_attr(
multi_core,
doc = r#"
<section class="warning">
Dedicated GPIOs are tied to CPU cores and a driver must only be used on the core that has created it.
Do not send the drivers to another core, either directly, or indirectly via a thread that is not pinned to a core.
</section>
"#
)]
use core::{convert::Infallible, marker::PhantomData};
use procmacros::doc_replace;
use strum::EnumCount as _;
use crate::{
gpio::{
AnyPin,
GpioBank,
InputSignal,
Level,
OutputSignal,
interconnect::{PeripheralOutput, PeripheralSignal},
},
peripherals::GPIO_DEDICATED,
private::Sealed,
system::Cpu,
};
for_each_dedicated_gpio!(
(channels $(($ch:literal)),*) => {
paste::paste!(
pub struct DedicatedGpio<'lt> {
$(
#[doc = concat!("Channel ", stringify!($ch))]
pub [< channel $ch >]: DedicatedGpioChannel<'lt, $ch>,
)*
}
);
impl<'lt> DedicatedGpio<'lt> {
pub fn new(_: GPIO_DEDICATED<'lt>) -> Self {
#[cfg(dedicated_gpio_needs_initialization)]
Self::initialize();
paste::paste!(
Self {
$([<channel $ch>]: DedicatedGpioChannel {
input: DedicatedGpioInputChannel { _marker: PhantomData },
output: DedicatedGpioOutputChannel { _marker: PhantomData },
},)*
}
)
}
}
};
);
impl DedicatedGpio<'_> {
#[cfg(dedicated_gpio_needs_initialization)]
fn initialize() {
use crate::system::{Peripheral, PeripheralClockControl};
if PeripheralClockControl::enable(Peripheral::DedicatedGpio) {
PeripheralClockControl::reset(Peripheral::DedicatedGpio);
} else {
PeripheralClockControl::disable(Peripheral::DedicatedGpio);
}
#[cfg(esp32s2)]
{
let regs = unsafe { esp32s2::DEDICATED_GPIO::steal() };
regs.out_cpu()
.write(|w| unsafe { w.bits((1 << property!("dedicated_gpio.channel_count")) - 1) });
}
}
}
pub trait InputChannel: Sealed {
#[doc(hidden)]
const CH: u8;
#[doc(hidden)]
fn mask(&self) -> u32 {
1 << Self::CH
}
#[doc(hidden)]
fn input_signal(&self) -> InputSignal {
for_each_dedicated_gpio!(
(signals $( ($cpu:literal, $signal_idx:literal, $signal_name:ident) ),*) => {
const CHANNEL_COUNT: usize = property!("dedicated_gpio.channel_count");
const SIGNALS: [[InputSignal; CHANNEL_COUNT]; Cpu::COUNT] = const {
let mut signals = [[core::mem::MaybeUninit::uninit(); CHANNEL_COUNT]; Cpu::COUNT];
$(
signals[$cpu][$signal_idx].write(InputSignal::$signal_name);
)*
unsafe { core::mem::transmute(signals) }
};
};
);
SIGNALS[Cpu::current() as usize][Self::CH as usize]
}
}
pub trait OutputChannel: Sealed {
#[doc(hidden)]
const CH: u8;
#[doc(hidden)]
fn mask(&self) -> u32 {
1 << Self::CH
}
#[doc(hidden)]
fn output_signal(&self) -> OutputSignal {
for_each_dedicated_gpio!(
(signals $( ($cpu:literal, $signal_idx:literal, $signal_name:ident) ),* ) => {
const CHANNEL_COUNT: usize = property!("dedicated_gpio.channel_count");
const SIGNALS: [[OutputSignal; CHANNEL_COUNT]; Cpu::COUNT] = const {
let mut signals = [[core::mem::MaybeUninit::uninit(); CHANNEL_COUNT]; Cpu::COUNT];
$(
signals[$cpu][$signal_idx].write(OutputSignal::$signal_name);
)*
unsafe { core::mem::transmute(signals) }
};
};
);
SIGNALS[Cpu::current() as usize][Self::CH as usize]
}
}
pub trait InputDriver: Sealed {
#[doc(hidden)]
fn pin(&self) -> u8;
#[doc(hidden)]
fn set_input_connection(&self, signal: InputSignal);
}
#[instability::unstable]
impl InputDriver for super::Input<'_> {
fn pin(&self) -> u8 {
self.pin.pin.pin
}
fn set_input_connection(&self, signal: InputSignal) {
self.connect_input_to_peripheral(signal);
}
}
#[instability::unstable]
impl InputDriver for &mut super::Input<'_> {
fn pin(&self) -> u8 {
self.pin.pin.pin
}
fn set_input_connection(&self, signal: InputSignal) {
self.connect_input_to_peripheral(signal);
}
}
#[instability::unstable]
impl InputDriver for super::Flex<'_> {
fn pin(&self) -> u8 {
self.pin.pin
}
fn set_input_connection(&self, signal: InputSignal) {
self.connect_input_to_peripheral(signal);
}
}
#[instability::unstable]
impl InputDriver for &mut super::Flex<'_> {
fn pin(&self) -> u8 {
self.pin.pin
}
fn set_input_connection(&self, signal: InputSignal) {
self.connect_input_to_peripheral(signal);
}
}
pub trait OutputDriver: Sealed {
#[doc(hidden)]
fn pin(&self) -> u8;
#[doc(hidden)]
fn set_output_connection(&self, signal: OutputSignal);
}
#[instability::unstable]
impl OutputDriver for super::Output<'_> {
fn pin(&self) -> u8 {
self.pin.pin.pin
}
fn set_output_connection(&self, signal: OutputSignal) {
self.pin.pin.connect_peripheral_to_output(signal);
}
}
#[instability::unstable]
impl OutputDriver for &mut super::Output<'_> {
fn pin(&self) -> u8 {
self.pin.pin.pin
}
fn set_output_connection(&self, signal: OutputSignal) {
self.pin.pin.connect_peripheral_to_output(signal);
}
}
#[instability::unstable]
impl OutputDriver for super::Flex<'_> {
fn pin(&self) -> u8 {
self.pin.pin
}
fn set_output_connection(&self, signal: OutputSignal) {
self.pin.connect_peripheral_to_output(signal);
}
}
#[instability::unstable]
impl OutputDriver for &mut super::Flex<'_> {
fn pin(&self) -> u8 {
self.pin.pin
}
fn set_output_connection(&self, signal: OutputSignal) {
self.pin.connect_peripheral_to_output(signal);
}
}
pub struct DedicatedGpioChannel<'lt, const CH: u8> {
pub input: DedicatedGpioInputChannel<'lt, CH>,
pub output: DedicatedGpioOutputChannel<'lt, CH>,
}
impl<const CH: u8> Sealed for DedicatedGpioChannel<'_, CH> {}
impl<const CH: u8> Sealed for &mut DedicatedGpioChannel<'_, CH> {}
impl<const CH: u8> DedicatedGpioChannel<'_, CH> {
pub unsafe fn steal() -> Self {
unsafe {
Self {
input: DedicatedGpioInputChannel::steal(),
output: DedicatedGpioOutputChannel::steal(),
}
}
}
}
impl<const CH: u8> InputChannel for DedicatedGpioChannel<'_, CH> {
const CH: u8 = CH;
}
impl<const CH: u8> OutputChannel for DedicatedGpioChannel<'_, CH> {
const CH: u8 = CH;
}
impl<const CH: u8> InputChannel for &mut DedicatedGpioChannel<'_, CH> {
const CH: u8 = CH;
}
impl<const CH: u8> OutputChannel for &mut DedicatedGpioChannel<'_, CH> {
const CH: u8 = CH;
}
pub struct DedicatedGpioInputChannel<'lt, const CH: u8> {
_marker: PhantomData<&'lt mut ()>,
}
impl<const CH: u8> Sealed for DedicatedGpioInputChannel<'_, CH> {}
impl<const CH: u8> Sealed for &mut DedicatedGpioInputChannel<'_, CH> {}
impl<const CH: u8> DedicatedGpioInputChannel<'_, CH> {
pub unsafe fn steal() -> Self {
Self {
_marker: PhantomData,
}
}
}
impl<const CH: u8> InputChannel for DedicatedGpioInputChannel<'_, CH> {
const CH: u8 = CH;
}
impl<const CH: u8> InputChannel for &mut DedicatedGpioInputChannel<'_, CH> {
const CH: u8 = CH;
}
pub struct DedicatedGpioOutputChannel<'lt, const CH: u8> {
_marker: PhantomData<&'lt mut ()>,
}
impl<const CH: u8> Sealed for DedicatedGpioOutputChannel<'_, CH> {}
impl<const CH: u8> Sealed for &mut DedicatedGpioOutputChannel<'_, CH> {}
impl<const CH: u8> DedicatedGpioOutputChannel<'_, CH> {
pub unsafe fn steal() -> Self {
Self {
_marker: PhantomData,
}
}
}
impl<const CH: u8> OutputChannel for DedicatedGpioOutputChannel<'_, CH> {
const CH: u8 = CH;
}
impl<const CH: u8> OutputChannel for &mut DedicatedGpioOutputChannel<'_, CH> {
const CH: u8 = CH;
}
#[doc_replace]
#[cfg_attr(
multi_core,
doc = r#"
<section class="warning">
Note that the driver must only be used on the core that has created it. Do not send the driver to
another core, either directly, or indirectly via a thread that is not pinned to a core.
</section>
"#
)]
#[doc = ""]
pub struct DedicatedGpioInput<'lt> {
_marker: PhantomData<&'lt mut ()>,
mask: u32,
#[cfg(all(debug_assertions, multi_core))]
core: Cpu,
}
impl<'lt> DedicatedGpioInput<'lt> {
pub fn new<CH, P>(channel: CH, pin: P) -> Self
where
CH: InputChannel + 'lt,
P: InputDriver + 'lt,
{
pin.set_input_connection(channel.input_signal());
Self {
mask: channel.mask(),
_marker: PhantomData,
#[cfg(all(debug_assertions, multi_core))]
core: Cpu::current(),
}
}
#[inline(always)]
pub fn level(&self) -> Level {
#[cfg(all(debug_assertions, multi_core))]
debug_assert_eq!(
self.core,
Cpu::current(),
"Dedicated GPIO used on a different CPU core than it was created on"
);
let bits = ll::read_in();
Level::from(bits & self.mask != 0)
}
}
impl embedded_hal::digital::ErrorType for DedicatedGpioInput<'_> {
type Error = Infallible;
}
impl embedded_hal::digital::InputPin for DedicatedGpioInput<'_> {
#[inline(always)]
fn is_high(&mut self) -> Result<bool, Self::Error> {
Ok(self.level() == Level::High)
}
#[inline(always)]
fn is_low(&mut self) -> Result<bool, Self::Error> {
Ok(self.level() == Level::Low)
}
}
#[doc_replace]
#[cfg_attr(
esp32s3,
doc = r#"
<section class="warning">
The method <code>output_level</code> is currently not available on ESP32-S3 due to
an LLVM bug. See <a href=https://github.com/espressif/llvm-project/issues/120>https://github.com/espressif/llvm-project/issues/120</a> for details.
</section>
"#
)]
#[cfg_attr(
multi_core,
doc = r#"
<section class="warning">
Note that the driver must only be used on the core that has created it. Do not send the driver to
another core, either directly, or indirectly via a thread that is not pinned to a core.
</section>
"#
)]
#[doc = ""]
pub struct DedicatedGpioOutput<'lt> {
_marker: PhantomData<&'lt mut ()>,
mask: u32,
signal: OutputSignal,
contained_gpios: [u32; GpioBank::COUNT],
#[cfg(all(debug_assertions, multi_core))]
core: Cpu,
}
impl<'lt> DedicatedGpioOutput<'lt> {
pub fn new<CH>(channel: CH) -> Self
where
CH: OutputChannel + 'lt,
{
ll::set_output_enabled(channel.mask(), true);
Self {
mask: channel.mask(),
signal: channel.output_signal(),
contained_gpios: [0; GpioBank::COUNT],
_marker: PhantomData,
#[cfg(all(debug_assertions, multi_core))]
core: Cpu::current(),
}
}
pub fn with_pin(mut self, pin: impl OutputDriver + 'lt) -> Self {
pin.set_output_connection(self.signal);
let bank = pin.pin() / 32;
let pin_in_bank = pin.pin() % 32;
self.contained_gpios[bank as usize] |= 1 << pin_in_bank;
self
}
#[inline(always)]
pub fn set_level(&mut self, level: Level) {
#[cfg(all(debug_assertions, multi_core))]
debug_assert_eq!(
self.core,
Cpu::current(),
"Dedicated GPIO used on a different CPU core than it was created on"
);
if level == Level::High {
ll::write(self.mask, self.mask);
} else {
ll::write(self.mask, 0);
}
}
#[cfg(not(esp32s3))]
#[inline(always)]
pub fn output_level(&self) -> Level {
#[cfg(all(debug_assertions, multi_core))]
debug_assert_eq!(
self.core,
Cpu::current(),
"Dedicated GPIO used on a different CPU core than it was created on"
);
Level::from(ll::read_out() & self.mask != 0)
}
}
impl Drop for DedicatedGpioOutput<'_> {
fn drop(&mut self) {
#[cfg(all(debug_assertions, multi_core))]
debug_assert_eq!(
self.core,
Cpu::current(),
"Dedicated GPIO used on a different CPU core than it was created on"
);
for (bank, mut pins) in self.contained_gpios.into_iter().enumerate() {
let bank_offset = bank as u8 * 32;
while pins != 0 {
let pin = pins.trailing_zeros() as u8;
pins &= !(1 << pin);
let gpio = unsafe { AnyPin::steal(bank_offset + pin) };
gpio.disconnect_from_peripheral_output();
gpio.connect_peripheral_to_output(OutputSignal::GPIO);
}
}
}
}
impl embedded_hal::digital::ErrorType for DedicatedGpioOutput<'_> {
type Error = Infallible;
}
impl embedded_hal::digital::OutputPin for DedicatedGpioOutput<'_> {
#[inline(always)]
fn set_low(&mut self) -> Result<(), Self::Error> {
self.set_level(Level::Low);
Ok(())
}
#[inline(always)]
fn set_high(&mut self) -> Result<(), Self::Error> {
self.set_level(Level::High);
Ok(())
}
}
#[doc_replace]
#[cfg_attr(
xtensa,
doc = r#"
On ESP32-S2 and ESP32-S3, the GPIO's output is always enabled.
"#
)]
#[cfg_attr(
multi_core,
doc = r#"
<section class="warning">
Note that the driver must only be used on the core that has created it. Do not send the driver to
another core, either directly, or indirectly via a thread that is not pinned to a core.
</section>
"#
)]
#[doc = ""]
pub struct DedicatedGpioFlex<'lt> {
_marker: PhantomData<&'lt mut ()>,
mask: u32,
pin: u8,
#[cfg(all(debug_assertions, multi_core))]
core: Cpu,
}
impl<'lt> DedicatedGpioFlex<'lt> {
pub fn new<CH, P>(channel: CH, pin: P) -> Self
where
CH: InputChannel + OutputChannel + 'lt,
P: InputDriver + OutputDriver + 'lt,
{
pin.set_input_connection(channel.input_signal());
pin.set_output_connection(channel.output_signal());
Self {
mask: <CH as OutputChannel>::mask(&channel),
pin: <P as OutputDriver>::pin(&pin),
_marker: PhantomData,
#[cfg(all(debug_assertions, multi_core))]
core: Cpu::current(),
}
}
#[cfg(riscv)] pub fn set_output_enabled(&mut self, enabled: bool) {
#[cfg(all(debug_assertions, multi_core))]
debug_assert_eq!(
self.core,
Cpu::current(),
"Dedicated GPIO used on a different CPU core than it was created on"
);
ll::set_output_enabled(self.mask, enabled);
}
#[inline(always)]
pub fn set_level(&mut self, level: Level) {
#[cfg(all(debug_assertions, multi_core))]
debug_assert_eq!(
self.core,
Cpu::current(),
"Dedicated GPIO used on a different CPU core than it was created on"
);
if level == Level::High {
ll::write(self.mask, self.mask);
} else {
ll::write(self.mask, 0);
}
}
#[cfg(not(esp32s3))]
#[inline(always)]
pub fn output_level(&self) -> Level {
#[cfg(all(debug_assertions, multi_core))]
debug_assert_eq!(
self.core,
Cpu::current(),
"Dedicated GPIO used on a different CPU core than it was created on"
);
Level::from(ll::read_out() & self.mask != 0)
}
#[inline(always)]
pub fn level(&self) -> Level {
#[cfg(all(debug_assertions, multi_core))]
debug_assert_eq!(
self.core,
Cpu::current(),
"Dedicated GPIO used on a different CPU core than it was created on"
);
let bits = ll::read_in();
Level::from(bits & self.mask != 0)
}
}
impl Drop for DedicatedGpioFlex<'_> {
fn drop(&mut self) {
#[cfg(all(debug_assertions, multi_core))]
debug_assert_eq!(
self.core,
Cpu::current(),
"Dedicated GPIO used on a different CPU core than it was created on"
);
let gpio = unsafe { AnyPin::steal(self.pin) };
gpio.disconnect_from_peripheral_output();
gpio.connect_peripheral_to_output(OutputSignal::GPIO);
}
}
#[doc_replace]
#[cfg_attr(
multi_core,
doc = r#"
<section class="warning">
Only channels configured on the current CPU core can be used.
</section>
"#
)]
#[inline(always)]
pub fn write_ll(mask: u32, value: u32) {
ll::write(mask, value);
}
#[inline(always)]
pub fn read_all_ll() -> u32 {
ll::read_in()
}
#[cfg(not(esp32s3))]
#[inline(always)]
pub fn output_levels_ll() -> u32 {
ll::read_out()
}
#[doc_replace]
#[cfg_attr(
esp32s3,
doc = r#"
<section class="warning">
The method <code>output_levels</code> is currently not available on ESP32-S3 due to
an LLVM bug. See <a href=https://github.com/espressif/llvm-project/issues/120>https://github.com/espressif/llvm-project/issues/120</a> for details.
</section>
"#
)]
#[cfg_attr(
multi_core,
doc = r#"
<section class="warning">
Dedicated GPIO channels are tied to CPU cores. All output drivers attached to a bundle must be
configured on the same core, and the bundle must only be used on the core that created it.
</section>
"#
)]
#[doc = ""]
pub struct DedicatedGpioOutputBundle<'lt> {
_marker: PhantomData<&'lt ()>,
mask: u32,
#[cfg(all(debug_assertions, multi_core))]
core: Cpu,
}
impl<'lt> DedicatedGpioOutputBundle<'lt> {
pub fn new() -> Self {
Self {
_marker: PhantomData,
mask: 0,
#[cfg(all(debug_assertions, multi_core))]
core: Cpu::current(),
}
}
#[doc_replace]
#[inline(always)]
pub fn mask(&self) -> u32 {
self.mask
}
#[cfg_attr(
multi_core,
doc = r#"
<section class="warning">
All dedicated GPIO drivers in a bundle must be configured on the same core as the bundle itself
</section>
"#
)]
pub fn enable_output<'d>(&mut self, out: &'lt DedicatedGpioOutput<'d>) -> &mut Self {
#[cfg(all(debug_assertions, multi_core))]
debug_assert_eq!(
out.core, self.core,
"Trying to enable a dedicated GPIO driver configured on a different core from the bundle."
);
self.mask |= out.mask;
self
}
#[cfg_attr(
multi_core,
doc = r#"
<section class="warning">
You should only disable dedicated GPIO drivers that were configured on the same core as the bundle itself.
</section>
"#
)]
pub fn disable_output<'d>(&mut self, out: &'lt DedicatedGpioOutput<'d>) -> &mut Self {
#[cfg(all(debug_assertions, multi_core))]
debug_assert_eq!(
out.core, self.core,
"Trying to disable a dedicated GPIO driver configured on a different core from the bundle."
);
self.mask &= !out.mask;
self
}
#[inline(always)]
pub fn set_high(&mut self, bits: u32) {
#[cfg(all(debug_assertions, multi_core))]
debug_assert_eq!(
self.core,
Cpu::current(),
"Dedicated GPIO used on a different CPU core than it was created on"
);
debug_assert!(
(bits & !self.mask) == 0,
"Trying to set bits outside of the bundle mask"
);
ll::write(bits, bits); }
#[inline(always)]
pub fn set_low(&mut self, bits: u32) {
#[cfg(all(debug_assertions, multi_core))]
debug_assert_eq!(
self.core,
Cpu::current(),
"Dedicated GPIO used on a different CPU core than it was created on"
);
debug_assert!(
(bits & !self.mask) == 0,
"Trying to clear bits outside of the bundle mask"
);
ll::write(bits, 0);
}
#[inline(always)]
pub fn write_bits(&mut self, bits: u32) {
#[cfg(all(debug_assertions, multi_core))]
debug_assert_eq!(
self.core,
Cpu::current(),
"Dedicated GPIO used on a different CPU core than it was created on"
);
ll::write(self.mask, bits);
}
#[cfg(not(esp32s3))]
#[inline(always)]
pub fn output_levels(&self) -> u32 {
#[cfg(all(debug_assertions, multi_core))]
debug_assert_eq!(
self.core,
Cpu::current(),
"Dedicated GPIO used on a different CPU core than it was created on"
);
ll::read_out() & self.mask
}
}
impl<'lt> Default for DedicatedGpioOutputBundle<'lt> {
fn default() -> Self {
Self::new()
}
}
#[doc_replace]
#[cfg_attr(
multi_core,
doc = r#"
<section class="warning">
Dedicated GPIO channels are tied to CPU cores. All input drivers attached to a bundle must be
configured on the same core, and the bundle must only be used on the core that created it.
</section>
"#
)]
#[doc = ""]
pub struct DedicatedGpioInputBundle<'lt> {
_marker: PhantomData<&'lt ()>,
mask: u32,
#[cfg(all(debug_assertions, multi_core))]
core: Cpu,
}
impl<'lt> DedicatedGpioInputBundle<'lt> {
pub fn new() -> Self {
Self {
_marker: PhantomData,
mask: 0,
#[cfg(all(debug_assertions, multi_core))]
core: Cpu::current(),
}
}
#[doc_replace]
#[inline(always)]
pub fn mask(&self) -> u32 {
self.mask
}
#[cfg_attr(
multi_core,
doc = r#"
<section class="warning">
All dedicated GPIO drivers in a bundle must be configured on the same core as the bundle itself.
</section>
"#
)]
pub fn enable_input<'d>(&mut self, inp: &'lt DedicatedGpioInput<'d>) -> &mut Self {
#[cfg(all(debug_assertions, multi_core))]
debug_assert_eq!(
inp.core, self.core,
"Trying to enable a dedicated GPIO driver configured on a different core from the bundle."
);
self.mask |= inp.mask;
self
}
#[cfg_attr(
multi_core,
doc = r#"
<section class="warning">
You should only disable dedicated GPIO drivers that were configured on the same core as the bundle itself.
</section>
"#
)]
pub fn disable_input<'d>(&mut self, inp: &'lt DedicatedGpioInput<'d>) -> &mut Self {
#[cfg(all(debug_assertions, multi_core))]
debug_assert_eq!(
inp.core, self.core,
"Trying to disable a dedicated GPIO driver configured on a different core from the bundle."
);
self.mask &= !inp.mask;
self
}
#[inline(always)]
pub fn levels(&self) -> u32 {
#[cfg(all(debug_assertions, multi_core))]
debug_assert_eq!(
self.core,
Cpu::current(),
"Dedicated GPIO used on a different CPU core than it was created on"
);
ll::read_in() & self.mask
}
#[inline(always)]
pub fn all_high(&self) -> bool {
#[cfg(all(debug_assertions, multi_core))]
debug_assert_eq!(
self.core,
Cpu::current(),
"Dedicated GPIO used on a different CPU core than it was created on"
);
(ll::read_in() & self.mask) == self.mask
}
#[inline(always)]
pub fn all_low(&self) -> bool {
#[cfg(all(debug_assertions, multi_core))]
debug_assert_eq!(
self.core,
Cpu::current(),
"Dedicated GPIO used on a different CPU core than it was created on"
);
(ll::read_in() & self.mask) == 0
}
}
impl<'lt> Default for DedicatedGpioInputBundle<'lt> {
fn default() -> Self {
Self::new()
}
}
#[doc_replace]
#[cfg_attr(
esp32s3,
doc = r#"
<section class="warning">
The method <code>output_levels</code> is currently not available on ESP32-S3 due to
an LLVM bug. See <a href=https://github.com/espressif/llvm-project/issues/120>https://github.com/espressif/llvm-project/issues/120</a> for details.
</section>
"#
)]
#[cfg_attr(
multi_core,
doc = r#"
<section class="warning">
Dedicated GPIO channels are tied to CPU cores. All flex drivers attached to a bundle must be
configured on the current and same core, and the bundle must only be used on the core that created it.
For example, do not create the bundle on core 0 and then call <code>write_bits</code> from a task
running on core 1.
</section>
"#
)]
#[doc = ""]
pub struct DedicatedGpioFlexBundle<'lt> {
_marker: PhantomData<&'lt ()>,
mask: u32,
#[cfg(all(debug_assertions, multi_core))]
core: Cpu,
}
impl<'lt> DedicatedGpioFlexBundle<'lt> {
pub fn new() -> Self {
Self {
_marker: PhantomData,
mask: 0,
#[cfg(all(debug_assertions, multi_core))]
core: Cpu::current(),
}
}
#[doc_replace]
#[inline(always)]
pub fn mask(&self) -> u32 {
self.mask
}
#[cfg_attr(
multi_core,
doc = r#"
<section class="warning">
All dedicated GPIO drivers in a bundle must be configured on the same core as the bundle itself.
</section>
"#
)]
pub fn enable_flex<'d>(&mut self, flex: &'lt DedicatedGpioFlex<'d>) -> &mut Self {
#[cfg(all(debug_assertions, multi_core))]
debug_assert_eq!(
flex.core, self.core,
"Trying to enable a dedicated GPIO driver configured on a different core from the bundle."
);
self.mask |= flex.mask;
self
}
#[cfg_attr(
multi_core,
doc = r#"
<section class="warning">
You should only disable dedicated GPIO drivers that were configured on the same core as the bundle itself.
</section>
"#
)]
pub fn disable_flex<'d>(&mut self, flex: &'lt DedicatedGpioFlex<'d>) -> &mut Self {
#[cfg(all(debug_assertions, multi_core))]
debug_assert_eq!(
flex.core, self.core,
"Trying to disable a dedicated GPIO driver configured on a different core from the bundle."
);
self.mask &= !flex.mask;
self
}
#[inline(always)]
pub fn set_high(&mut self, bits: u32) {
#[cfg(all(debug_assertions, multi_core))]
debug_assert_eq!(
self.core,
Cpu::current(),
"Dedicated GPIO used on a different CPU core than it was created on"
);
debug_assert!(
(bits & !self.mask) == 0,
"Trying to set bits outside of the bundle mask"
);
ll::write(bits, bits); }
#[inline(always)]
pub fn set_low(&mut self, bits: u32) {
#[cfg(all(debug_assertions, multi_core))]
debug_assert_eq!(
self.core,
Cpu::current(),
"Dedicated GPIO used on a different CPU core than it was created on"
);
debug_assert!(
(bits & !self.mask) == 0,
"Trying to clear bits outside of the bundle mask"
);
ll::write(bits, 0); }
#[inline(always)]
pub fn write_bits(&mut self, bits: u32) {
#[cfg(all(debug_assertions, multi_core))]
debug_assert_eq!(
self.core,
Cpu::current(),
"Dedicated GPIO used on a different CPU core than it was created on"
);
ll::write(self.mask, bits);
}
#[cfg(not(esp32s3))]
#[inline(always)]
pub fn output_levels(&self) -> u32 {
#[cfg(all(debug_assertions, multi_core))]
debug_assert_eq!(
self.core,
Cpu::current(),
"Dedicated GPIO used on a different CPU core than it was created on"
);
ll::read_out() & self.mask
}
#[inline(always)]
pub fn levels(&self) -> u32 {
#[cfg(all(debug_assertions, multi_core))]
debug_assert_eq!(
self.core,
Cpu::current(),
"Dedicated GPIO used on a different CPU core than it was created on"
);
ll::read_in() & self.mask
}
#[inline(always)]
pub fn all_high(&self) -> bool {
#[cfg(all(debug_assertions, multi_core))]
debug_assert_eq!(
self.core,
Cpu::current(),
"Dedicated GPIO used on a different CPU core than it was created on"
);
(ll::read_in() & self.mask) == self.mask
}
#[inline(always)]
pub fn all_low(&self) -> bool {
#[cfg(all(debug_assertions, multi_core))]
debug_assert_eq!(
self.core,
Cpu::current(),
"Dedicated GPIO used on a different CPU core than it was created on"
);
(ll::read_in() & self.mask) == 0
}
}
impl<'lt> Default for DedicatedGpioFlexBundle<'lt> {
fn default() -> Self {
Self::new()
}
}
#[cfg(esp32s2)]
mod ll {
#[inline(always)]
pub(super) fn set_output_enabled(_mask: u32, _en: bool) {
}
#[inline(always)]
pub(super) fn read_in() -> u32 {
let val;
unsafe { core::arch::asm!("get_gpio_in {0}", out(reg) val) };
val
}
#[inline(always)]
pub(super) fn read_out() -> u32 {
let val;
unsafe { core::arch::asm!("rur.gpio_out {0}", out(reg) val) };
val
}
#[inline(always)]
pub(super) fn write(mask: u32, value: u32) {
unsafe { core::arch::asm!("wr_mask_gpio_out {0}, {1}", in(reg) mask, in(reg) value) }
}
}
#[cfg(esp32s3)]
mod ll {
#[inline(always)]
pub(super) fn set_output_enabled(_mask: u32, _en: bool) {
}
#[inline(always)]
pub(super) fn read_in() -> u32 {
let val;
unsafe { core::arch::asm!("ee.get_gpio_in {0}", out(reg) val) };
val
}
#[cfg(not(esp32s3))]
#[inline(always)]
pub(super) fn read_out() -> u32 {
let val;
unsafe { core::arch::asm!("rur.gpio_out {0}", out(reg) val) };
val
}
#[inline(always)]
pub(super) fn write(mask: u32, value: u32) {
unsafe { core::arch::asm!("ee.wr_mask_gpio_out {0}, {1}", in(reg) mask, in(reg) value) }
}
}
#[cfg(riscv)]
mod ll {
#[inline(always)]
pub(super) fn set_output_enabled(mask: u32, en: bool) {
riscv::read_csr!(0x803);
riscv::write_csr!(0x803);
unsafe {
let bits = _read();
if en {
_write(bits | mask as usize)
} else {
_write(bits & !mask as usize)
}
}
}
#[inline(always)]
pub(super) fn read_in() -> u32 {
riscv::read_csr!(0x804);
unsafe { _read() as u32 }
}
#[inline(always)]
pub(super) fn read_out() -> u32 {
riscv::read_csr!(0x805);
unsafe { _read() as u32 }
}
#[inline(always)]
pub(super) fn write(mask: u32, value: u32) {
riscv::set!(0x805);
riscv::clear!(0x805);
unsafe {
_set((mask & value) as usize);
_clear((mask & !value) as usize);
}
}
}