mayio 0.2.0

A minimal no-std GPIO HAL for embedded systems
Documentation
//! Low-level GPIO building blocks used by the `mayio` crate.
//!
//! This module defines a small, platform-facing API that the higher-level
//! typed abstractions use. It intentionally exposes a minimal, unsafe
//! boundary (raw pointers to register blocks) so platform crates can provide
//! zero-cost wrappers over their hardware register maps.
//!
//! The documentation below includes short examples showing how a platform
//! crate might implement the required traits. The examples are illustrative
//! and marked `no_run` to avoid being executed as doctests.
//!  
//! Safety notes:
//! - The `Bank::addr()` pointer must be valid and point to the correct
//!   register block for the lifetime of operations. Dereferencing an invalid
//!   pointer is undefined behavior.
//! - Implementations should use volatile reads/writes (or generated accessors)
//!   to avoid compiler reordering and ensure side effects reach the hardware.
//! - Concurrent access (from interrupts or multiple cores) must be handled
//!   by the platform crate (e.g. with critical sections or atomic/lock
//!   mechanisms) if required by the hardware.

/// Abstraction representing a GPIO bank on the target platform.
///
/// A concrete bank type (provided by a platform-specific crate) must
/// implement `Bank<R>` for its register block `R`. The trait provides the
/// physical address of the registers and a convenience `get_handle()` that
/// returns an `io::Gpio<R>` wrapper around the register pointer.
pub trait Bank<R: register::GpioRegisters> {
    /// Base physical address of the GPIO bank registers.
    const BASE_ADDRESS: usize;

    /// Return a new `Gpio` handle pointing at the bank's register block.
    ///
    /// This default implementation calls `Self::addr()` and constructs the
    /// thin `io::Gpio` wrapper. Platform crates may rely on this helper
    /// to obtain a typed handle.
    fn get_handle() -> io::Gpio<R> {
        io::Gpio::new(Self::addr())
    }

    /// Return the base pointer to the register block for this bank.
    fn addr() -> *mut R {
        Self::BASE_ADDRESS as *mut R
    }
}

// Example: providing a `Bank` for `MyGpioRegs`.
//
// The `Bank` trait exposes a `const BASE_ADDRESS` and provides a
// default `addr()` implementation that converts the constant to a pointer.
// Platform crates can implement the bank by defining the `BASE_ADDRESS`.
//
// ```no_run
// pub struct MyBank;
//
// impl Bank<MyGpioRegs> for MyBank {
//     const BASE_ADDRESS: usize = 0x4002_0000; // platform-specific base address
// }
// ```

/// Register-level trait describing the operations a GPIO register block
/// must provide for `mayio to operate.
///
/// Platform-specific register types should implement `GpioRegisters` to
/// expose a small set of operations used by the higher-level API. The
/// methods are kept simple and raw (u32 masks, pin indices) so they map
/// directly onto common hardware register patterns.
pub mod register {

    use crate::{Interrupt, IoDir, Level};

    /// Represents the hardware register interface for a GPIO bank.
    ///
    /// Implementers must ensure that these functions perform the expected
    /// side effects on the hardware registers. The trait is intentionally
    /// small to make it easy to adapt to different MCUs or SoCs.
    pub unsafe trait GpioRegisters {
        /// Configure the direction of a single pin.
        fn set_dir(ptr: *mut Self, pin: u32, dir: IoDir);

        /// Configure the active state for a single pin.
        fn set_active_state(ptr: *mut Self, pin: u32, active_state: crate::Level);

        /// Configure the interrupt mode for a single pin.
        fn set_interrupt(ptr: *mut Self, pin: u32, interrupt: Interrupt);

        /// Read the input register(s) for the bank; returns the level of `pin`.
        fn read(ptr: *const Self, pin: u32) -> Level;

        /// Write `level` to `pin` for the bank.
        fn write(ptr: *mut Self, pin: u32, level: Level);

        /// Read whether an interrupt is pending for the given pin and acknoledge it.
        fn interrupt_pending(ptr: *mut Self, pin: u32) -> bool;
    }
}

/// Thin, unsafe wrapper around a raw pointer to a register block.
///
/// `io::Gpio<R>` stores a raw pointer to `R` and provides a small set of
/// safe(er) methods that call into the implementor's `GpioRegisters`
/// functions using `unsafe` internally. This isolates unsafe pointer
/// dereferencing in a single place.
pub mod io {
    use super::register::GpioRegisters;
    use crate::{Interrupt, IoDir, Level};

    /// Opaque handle to a GPIO register block.
    ///
    /// The handle contains a raw pointer to the register block type `R`.
    /// Callers must ensure the pointer is valid for the lifetime of the
    /// operations. All methods on `Gpio` use `unsafe` internally to access
    /// the pointer and forward to the `GpioRegisters` implementation.
    #[doc(hidden)]
    pub struct Gpio<R: GpioRegisters> {
        registers: *mut R,
    }

    impl<R> Gpio<R>
    where
        R: GpioRegisters,
    {
        /// Create a new `Gpio` wrapper from a raw register pointer.
        ///
        /// This is `pub(super)` because only the crate (and implementors)
        /// should construct `Gpio` handles from raw addresses; external
        /// crates should provide a `Bank` implementation instead.
        pub(super) fn new(registers: *mut R) -> Self {
            Self { registers }
        }

        /// Set the direction for `pin` by delegating to the register impl.
        #[inline]
        pub fn set_dir(&mut self, pin: u32, dir: IoDir) {
            <R as GpioRegisters>::set_dir(self.registers, pin, dir);
        }

        /// Configure the active state for `pin`.
        #[inline]
        pub fn set_active_state(&mut self, pin: u32, active_state: Level) {
            <R as GpioRegisters>::set_active_state(self.registers, pin, active_state);
        }

        /// Configure the interrupt mode for `pin`.
        #[inline]
        pub fn set_interrupt(&mut self, pin: u32, interrupt: Interrupt) {
            <R as GpioRegisters>::set_interrupt(self.registers, pin, interrupt);
        }

        /// Read the current input state bitmask for the bank.
        #[inline]
        pub fn read(&self, pin: u32) -> Level {
            <R as GpioRegisters>::read(self.registers, pin)
        }

        /// Write `level` to `pin` for the bank.
        #[inline]
        pub fn write(&mut self, pin: u32, level: Level) {
            <R as GpioRegisters>::write(self.registers, pin, level);
        }

        /// Read whether an interrupt is pending for the given pin and acknowledge it.
        pub fn interrupt_pending(&mut self, pin: u32) -> bool {
            <R as GpioRegisters>::interrupt_pending(self.registers, pin)
        }
    }
}