// Copyright (c) 2026 Rigless Labs.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! The general-purpose I/O port of RM0090 rev 22 chapter 8, written against the `gpioa` layout,
//! which is the layout of all eleven ports. One of these answers for each of them.
//!
//! Every register of section 8.4 and every configuration of section 8.3 is implemented, by the model
//! RM0433 rev 9 gives the STM32H743 too, in [`crate::soc::st::gpio`]. A pin's electrical level is what
//! the outside world would see on it. The outside world drives a pin through [`Gpio::drive`] and a
//! peripheral drives its alternate function through [`Gpio::drive_function`], each announced at the
//! instant it's driven. Nothing here is register-only.
//!
//! | Register or section | Behavior | Source | Test |
//! |---|---|---|---|
//! | Reset state | Every pin an input, floating, except port A's debug pins, in their alternate function with their pulls, and port B's one of them. What a port wakes up as is its own, held in the part's corrections rather than here | 8.3.1, 8.4.1 to 8.4.4 | `port_a_resets_with_the_debug_pins_pulled`, `every_port_comes_out_of_reset_as_the_manual_gives_it` |
//! | `MODER` | Input, output, alternate function, or analog per pin | 8.4.1, table 36 | `a_pin_is_what_its_mode_says` |
//! | `OTYPER` | Push-pull drives both levels, and open-drain drives low and releases high | 8.3.10, 8.4.2 | `open_drain_drives_low_and_releases_high` |
//! | `OSPEEDR` | Slew rate, with no observable value in the emulator. It's stored | 8.4.3 | `speed_is_stored_and_changes_nothing` |
//! | `PUPDR` | A released pin reads its pull, and a floating pin keeps its last level. Analog mode disables the pulls | 8.3.9, 8.3.12, 8.4.4 | `a_released_pin_reads_its_pull`, `analog_mode_disables_the_pull` |
//! | `IDR` | Every pin's level, sampled at the read. Zero in analog mode | 8.3.9, 8.3.12, 8.4.5 | `idr_reads_the_pins_and_zero_for_analog` |
//! | `ODR` | The level an output drives. A push-pull output beats what drives the pin | 8.3.10, 8.4.6 | `odr_drives_a_push_pull_output` |
//! | `BSRR` | Sets the low half's bits and clears the high half's, the set winning. Reads zero | 8.3.5, 8.4.7 | `bsrr_sets_and_clears_and_the_set_wins` |
//! | `LCKR` | The lock key sequence freezes the six configuration registers for the locked pins until reset | 8.3.6, 8.4.8 | `the_lock_sequence_freezes_the_configuration`, `a_broken_lock_sequence_aborts` |
//! | Wire | A pin whose level on the wire changes announces it as the port's request line, numbered by the pin: what a chip select on a serial interface reads. Announced after the write that changed it or at the instant the outside world or a peripheral drives it, never on a read, and only while something holds the pin (an output stage, a function's driver, the outside world, or a pull). A floating pin announces nothing, because the level it keeps is section 8.4.5's convention rather than a wire. Every pin but the debug ones comes out of reset floating, so a pin's first level is announced, low or high. An analog pin's input stage is forced to zero, so it announces low and nothing else | 8.3.9, 8.3.12, 8.4.1, 8.4.4 | `a_pin_announces_its_level_when_it_changes`, `nothing_is_announced_at_reset`, `a_pin_announces_the_level_it_becomes_an_output_at`, `a_read_announces_nothing`, `a_floating_pin_announces_nothing_and_a_pulled_one_its_pull`, `the_outside_world_driving_a_pin_announces_it_at_once`, `an_analog_pin_announces_low_and_nothing_else` |
//! | `AFRL`, `AFRH` | The alternate function a pin carries. Its peripheral drives the pin | 8.3.7, 8.3.11, 8.4.9, 8.4.10 | `an_alternate_function_pin_is_driven_by_its_peripheral` |
//! | Input | An input follows what drives it and keeps the last level when released | 8.3.9 | `an_input_follows_what_drives_it` |
//! | Narrow access | Byte, half-word, and word access lands in its lane. On the SoC `IDR` is read and `LCKR` written by word only, which the model can't see, the width staying on the bus | 8.4, 8.3.6, 8.4.5, 8.4.8 | `the_registers_accept_narrow_access`, with the word-only rule pending |
//! | 8.3.2, 8.3.7 | The pin multiplexer: `AFRL` and `AFRH` above choose the function, and the board wires the peripheral to the pin | 8.3.2, 8.3.7 | `an_alternate_function_pin_is_driven_by_its_peripheral` |
//! | The reset bit | A write that leaves the port's bit of `RCC_AHB1RSTR` set puts the port back as it came out of reset: its registers, and the lock, which lasts "until the next MCU or peripheral reset". What the outside world drives stays | 6.3.5, 8.3.6, 8.4.8 | `a_reset_bit_releases_its_ports_lock_and_leaves_the_others_alone` |
//!
//! **What the model leaves out, and why.**
//!
//! | Behavior | Source | Reason |
//! |---|---|---|
//! | External interrupt and wake-up lines | 8.3.8 | Not applicable. The EXTI is its own instance, and the part has no model for it yet |
//! | The oscillator pins as GPIO, PC14, PC15, PH0, PH1 | 8.3.13, 8.3.14 | Not modeled. The oscillator takes the pins while `LSEON` or `HSEON` stands, and the port here never gives them up. Ports C and H are bound, so the pins exist. What's on the other end of them is the RCC's |
//! | A register write within the clock-enable delay is lost | ES0206 rev 24, 2.2.7 | Not modeled, as `rcc.rs` records for the same erratum: a gate opens at the instant its bit is written |
//! | Flash reads corrupt while PA12 is a GPIO under dual-bank conditions | ES0206 rev 24, 2.2.14 | Not applicable. Table 3 marks it for revisions A, Y and 1 only, and `memory_map.rs` gives `DBGMCU_IDCODE` a `REV_ID` of revision 4, 5 or B with its reason |
//! | PC13 as the RTC alarm open-drain output | ES0206 rev 24, 2.2.16 | Pending. The RTC's alarm isn't modeled |
//! | PC13 transitions disturb the LSE | ES0206 rev 24, 2.2.10 | Not modeled. It's an analog effect with no register behavior, as `rcc.rs` records |
//! | PB4 as a GPIO breaking a four-wire JTAG debug session | ES0206 rev 24, 2.2.5 | Unreachable here, though Table 3 marks it on every revision. The debugger reaches the core through the emulator's stub and not a JTAG port |
//! | What reaches a port's pins from elsewhere, across the port's own reset | 6.3.5, 8.3.11 | The model's reset is the one a reset of the whole part needs: what a peripheral drives on an alternate function goes, and every pin something holds is announced again at the next write, as if its readers had restarted too. Across the port's own reset the peripheral goes on driving and the readers go on listening, so a reader may hear a level it already had. No model on this part drives a function yet |
//!
//! Where the manual leaves a value undefined, an input pin that floats with nothing driving it, section 8.4.5's
//! reset value, the model keeps the last level the pin had, low at reset, and says so here.
use crate::device::Level;
use crate::soc::st::gpio::Port;
use crate::soc::stm32f427::generated::gpioa::Register;
pub use crate::soc::st::gpio::Pin;
/// A port of sixteen pins.
pub type Gpio = crate::soc::st::gpio::Gpio<Register>;
impl Port for Register {
const MODER: Self = Self::Moder;
const OTYPER: Self = Self::Otyper;
const OSPEEDR: Self = Self::Ospeedr;
const PUPDR: Self = Self::Pupdr;
const IDR: Self = Self::Idr;
const ODR: Self = Self::Odr;
const BSRR: Self = Self::Bsrr;
const LCKR: Self = Self::Lckr;
const AFRL: Self = Self::Afrl;
const AFRH: Self = Self::Afrh;
// Every pin but the debug ones comes out of reset a floating input, 8.4.1, which carries no level.
const AT_RESET: Option<Level> = None;
}