{# PAC BSP generation template for rlvgl-creator. #}
/* Auto-generated by rlvgl-creator
 * SPDX-FileCopyrightText: 2020 STMicroelectronics
 * SPDX-FileCopyrightText: 2025 Softoboros Technology, Inc.
 * SPDX-License-Identifier: BSD-3-Clause
 * Provenance: STM32_open_pin_data (commit <hash>), build <build-hash>.
{% if meta is defined %}
 * Board: {{ meta.board }} ({{ meta.chip }})
{% endif %}
*/
{% if meta is defined %}
//! PAC BSP for {{ meta.board }}.
{% else %}
//! PAC BSP for the generated board.
{% endif %}
#![allow(non_snake_case)]
#![allow(clippy::too_many_arguments)]
#![allow(dead_code)]
#![allow(unused_imports)]
#![allow(unused_macros)]
#![allow(unused_variables)]
#![allow(unused_unsafe)]
{% if mod_name %}#![cfg(feature = "{{ mod_name }}")]{% endif %}

{% if init_by is defined and this_core is defined %}
/// Clock initialization ownership: {{ init_by }}
#[doc(hidden)]
pub const CLOCKS_INIT_BY: &str = "{{ init_by }}";

{% if this_core != init_by %}
/// Secondary core helper: wait for system clocks initialized by the primary core.
pub fn wait_for_clocks() {
    // Mailbox: 1K reserved in D2 SRAM3 at 0x3004_7000..0x3004_73FF
    const MAILBOX_BASE: u32 = 0x3004_7000;
    // Semaphore at offset 0
    let flag = (MAILBOX_BASE + 0) as *const core::sync::atomic::AtomicU32;
    // Safety: fixed shared address in D2 SRAM
    let f = unsafe { &*flag };
    while f.load(core::sync::atomic::Ordering::Acquire) == 0 {
        cortex_m::asm::wfe();
    }
}
{% else %}
/// Primary core helper: optionally signal clocks ready to the secondary core.
pub fn signal_clocks_ready() {
    const MAILBOX_BASE: u32 = 0x3004_7000;
    let flag = (MAILBOX_BASE + 0) as *const core::sync::atomic::AtomicU32;
    // Safety: fixed shared address in D2 SRAM
    let f = unsafe { &*flag };
    f.store(1, core::sync::atomic::Ordering::Release);
    cortex_m::asm::sev();
}
{% endif %}
{% endif %}

{# collect unique GPIO port letters #}
{% set ns = namespace(ports=[]) %}
{% for pin in spec.pinctrl %}
    {% if pin.pin[1] not in ns.ports %}{% set ns.ports = ns.ports + [pin.pin[1]] %}{% endif %}
{% endfor %}

{# derive PAC crate and module from the MCU identifier #}
{% set crate = namespace(letters='', digits='', digits_started=false, stop=false) %}
{% for ch in spec.mcu[5:] %}
    {% if not crate.stop %}
        {% if ch >= 'A' and ch <= 'Z' %}
            {% if crate.digits_started %}
                {% set crate.stop = true %}
            {% else %}
                {% set crate.letters = crate.letters + ch %}
            {% endif %}
        {% elif ch >= '0' and ch <= '9' %}
            {% set crate.digits_started = true %}
            {% set crate.digits = crate.digits + ch %}
        {% else %}
            {% set crate.stop = true %}
        {% endif %}
    {% endif %}
{% endfor %}
{% set family = crate.letters | lower %}
{% set nums = crate.digits %}
{% if family == 'wb' %}
    {% set pac_crate = 'stm32wb' %}
    {% set pac_mod = pac_crate + nums %}
{% elif family == 'wl' %}
    {% set pac_crate = 'stm32wl' %}
    {% set pac_mod = pac_crate + nums %}
{% elif family == 'wba' %}
    {% set pac_crate = 'stm32wba' %}
    {% set pac_mod = pac_crate + nums %}
{% elif family == 'mp' %}
    {% set pac_crate = 'stm32mp1' %}
    {% set pac_mod = pac_crate + nums %}
{% else %}
    {% set pac_crate = 'stm32' + family + nums[0:1] %}
    {% set pac_mod = pac_crate + nums[1:] %}
{% endif %}
{# Adjust for STM32H7 dual-core PAC module naming #}
{% if spec.mcu[0:9] in ["STM32H745", "STM32H747", "STM32H755", "STM32H757"] %}
    {% if this_core is defined and this_core == 'cm4' %}
        {% set pac_mod = pac_mod + 'cm4' %}
    {% else %}
        {% set pac_mod = pac_mod + 'cm7' %}
    {% endif %}
{% endif %}
use {{ pac_crate }}::{{ pac_mod }} as pac;
// Optional lightweight logging hook. Enable with `--features bsp_log` and
// override `_bsp_log` in your binary to route output.
#[cfg(feature = "bsp_log")]
#[allow(unused)]
fn _bsp_log(_args: core::fmt::Arguments) { /* user-defined sink */ }
#[cfg(feature = "bsp_log")]
macro_rules! bsp_log { ($($arg:tt)*) => { _bsp_log(format_args!($($arg)*)); } }
#[cfg(not(feature = "bsp_log"))]
macro_rules! bsp_log { ($($arg:tt)*) => { let _ = ($(& $arg),*); } }
{# Provide board defaults inline when not part of a parent module tree #}
{% if mod_name is defined %}
use super::board::{Supply, Vos};
{% else %}
mod board {
    /// Supply mode for the PWR block.
    #[derive(Copy, Clone)]
    pub enum Supply { SMPS, LDO }
    /// Voltage scaling level (SDLEVEL/VOS).
    #[derive(Copy, Clone)]
    pub enum Vos { VOS0, VOS1, VOS2, VOS3 }
    /// User default: board reality on STM32H747I-DISCO is SMPS.
    pub const DEFAULT_PWR_SUPPLY: Supply = Supply::SMPS;
    /// User default: VOS1 is appropriate for high clocks on STM32H7.
    pub const DEFAULT_VOS: Vos = Vos::VOS1;
}
use board::{Supply, Vos, DEFAULT_PWR_SUPPLY, DEFAULT_VOS};
{% endif %}

{%- macro gpio_bus(family) -%}
{% if family[0:7] == "STM32H7" %}ahb4enr
{% elif family[0:7] == "STM32H5" or family[0:7] == "STM32L5" or family[0:7] == "STM32L4" or family[0:7] == "STM32G4" %}ahb2enr
{% elif family[0:7] == "STM32G0" or family[0:7] == "STM32L0" %}iopenr
{% elif family[0:7] == "STM32F1" %}apb2enr
{% elif family[0:7] == "STM32F0" or family[0:7] == "STM32F3" or family[0:7] == "STM32L1" %}ahbenr
{% else %}ahb1enr{% endif %}
{%- endmacro %}
{% macro port_bit(p) -%}
{% set map = {"A":0,"B":1,"C":2,"D":3,"E":4,"F":5,"G":6,"H":7,"I":8,"J":9,"K":10,"L":11,"M":12,"N":13,"O":14,"P":15,"Q":16,"R":17,"S":18,"T":19,"U":20,"V":21,"W":22,"X":23,"Y":24,"Z":25} %}
{{ map[p] }}
{%- endmacro %}
{%- macro port_u(pin) -%}{{ pin.pin[1] }}{%- endmacro %}
{% macro idx(pin) -%}{{ pin.pin[2:] | int }}{%- endmacro %}
{% macro kernel_clk(name, src) -%}
{%- if name == "usart1" -%}
    dp.RCC.d2ccip2r.modify(|_, w| w.usart1sel().{{ src }}());
{%- elif name == "uart8" -%}
    dp.RCC.d3ccipr.modify(|_, w| w.uart8sel().{{ src }}());
{%- elif name in ["spi1", "spi2", "spi3"] -%}
    dp.RCC.d2ccip1r.modify(|_, w| w.spi123sel().{{ src }}());
{%- elif name in ["spi4", "spi5"] -%}
    dp.RCC.d2ccip1r.modify(|_, w| w.spi45sel().{{ src }}());
{%- elif name == "i2c4" -%}
    dp.RCC.d3ccipr.modify(|_, w| w.i2c4sel().{{ src }}());
{%- else -%}
    // TODO: set {{ name }} kernel clock source to {{ src }}
{%- endif -%}
{%- endmacro %}

{% if meta is defined %}
/// Enables GPIO clocks required by {{ meta.board }}.
{% else %}
/// Enables GPIO clocks required by the generated board.
{% endif %}
pub fn enable_gpio_clocks(dp: &pac::Peripherals) {
    {% if mod_name %}
    #[cfg(feature = "c_hal")]
    {
        let _ = dp;
        unsafe { super::super::c_ffi::c_{{ mod_name }}_enable_gpio_clocks(); }
    }
    #[cfg(not(feature = "c_hal"))]
    {% endif %}
    {
    {% if ns.ports|length %}
    {% if grouped_writes %}
    const MASK: u32 = {% for p in ns.ports %}(1u32 << {{ port_bit(p) }}){% if not loop.last %} |{% endif %}{% endfor %};
    dp.RCC.{{ gpio_bus(spec.mcu) }}.modify(|r, w| unsafe { w.bits(r.bits() | MASK) });
    {% else %}
    {% for p in ns.ports %}
    dp.RCC.{{ gpio_bus(spec.mcu) }}.modify(|_, w| w.gpio{{ p|lower }}en().set_bit());
    {% endfor %}
    {% endif %}
    {% endif %}
    }
}

/// Configure power supply and voltage scaling.
pub fn init_power(dp: &pac::Peripherals) {
    // Resolve supply from .ioc or fallback default
    let supply = {
        {%- if spec.pwr and spec.pwr.supply is defined and spec.pwr.supply %}
        // From .ioc: PWR.Supply = {{ spec.pwr.supply }}
        Supply::{{ spec.pwr.supply | upper }}
        {%- else %}
        // No supply configuration in .ioc
        // Using DEFAULT_PWR_SUPPLY (user-editable)
        DEFAULT_PWR_SUPPLY
        {%- endif %}
    };
    let vos = {
        {%- if spec.pwr and spec.pwr.sdlevel is defined and spec.pwr.sdlevel %}
        // From .ioc: PWR.SDLEVEL = {{ spec.pwr.sdlevel }}
        Vos::{{ spec.pwr.sdlevel | upper }}
        {%- else %}
        // No VOS in .ioc; using DEFAULT_VOS (user-editable)
        DEFAULT_VOS
        {%- endif %}
    };

    {# Apply only for H7 family for now #}
    {%- if spec.mcu[0:7] == "STM32H7" %}
    // Allow supply/VOS configuration updates (SCUEN gates changes on H7)
    // Some PACs lack a typed accessor for SCUEN; set the bit position directly.
    dp.PWR.cr3.modify(|r, w| unsafe { w.bits(r.bits() | (1 << 2)) });
    match supply {
        Supply::SMPS => {
            // SMPS enable is SDEN in the PAC
            dp.PWR.cr3.modify(|_, w| w.sden().set_bit().ldoen().clear_bit());
            // SDLEVEL is only relevant under SMPS on H7
            match vos {
                Vos::VOS0 => dp.PWR.cr3.modify(|_, w| unsafe { w.sdlevel().bits(0b00) }),
                Vos::VOS1 => dp.PWR.cr3.modify(|_, w| unsafe { w.sdlevel().bits(0b01) }),
                Vos::VOS2 => dp.PWR.cr3.modify(|_, w| unsafe { w.sdlevel().bits(0b10) }),
                Vos::VOS3 => dp.PWR.cr3.modify(|_, w| unsafe { w.sdlevel().bits(0b11) }),
            }
        }
        Supply::LDO => {
            dp.PWR.cr3.modify(|_, w| w.ldoen().set_bit().sden().clear_bit());
        }
    }
    // Program VOS in D3CR using raw bits (VOS[15:14]). PACs differ on typed accessors.
    let vos_bits: u32 = match vos {
        Vos::VOS0 => 0b11,
        Vos::VOS1 => 0b10,
        Vos::VOS2 => 0b01,
        Vos::VOS3 => 0b00,
    };
    dp.PWR.d3cr.modify(|r, w| unsafe {
        let mut bits = r.bits() & !(0b11 << 14);
        bits |= vos_bits << 14;
        w.bits(bits)
    });
    // And active VOS achieved
    while dp.PWR.csr1.read().actvosrdy().bit_is_clear() {}
    // Lock supply/VOS configuration updates (optional hardening)
    dp.PWR.cr3.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << 2)) });
    {%- else %}
    // TODO: Power init for non-H7 families
    let _ = (supply, vos);
    {%- endif %}
}

{% if init_by is defined and this_core is defined and this_core == init_by %}
/// Configure system clocks from CubeMX `.ioc` settings (CM7 owner).
pub fn init_clocks(dp: &pac::Peripherals) {
    // Parsed from .ioc (raw tokens): SYSCLK={{ spec.clocks.sys_src | default("unknown") }}, PLLSRC={{ spec.clocks.pll_src | default("unknown") }}
    // Prescalers: D1CPU={{ spec.clocks.d1cpu | default("-") }}, D1PPRE={{ spec.clocks.d1ppre | default("-") }}, D2PPRE1={{ spec.clocks.d2ppre1 | default("-") }}, D2PPRE2={{ spec.clocks.d2ppre2 | default("-") }}, D3PPRE={{ spec.clocks.d3ppre | default("-") }}
    // HSE: {{ spec.clocks.hse_hz | default(0) }} Hz
    bsp_log!("[bsp] SYS={} PLLSRC={} HSE={}Hz D1CPU={} D1PPRE={} D2PPRE1={} D2PPRE2={} D3PPRE={}\n",
        "{{ spec.clocks.sys_src | default("unknown") }}",
        "{{ spec.clocks.pll_src | default("unknown") }}",
        {{ spec.clocks.hse_hz | default(0) }},
        "{{ spec.clocks.d1cpu | default("-") }}",
        "{{ spec.clocks.d1ppre | default("-") }}",
        "{{ spec.clocks.d2ppre1 | default("-") }}",
        "{{ spec.clocks.d2ppre2 | default("-") }}",
        "{{ spec.clocks.d3ppre | default("-") }}"
    );
    {% if spec.clocks.sys_src and spec.clocks.sys_src | upper in ["HSI","HSE"] %}
    // Handle simple SYSCLK sources without PLL
    {% if spec.clocks.sys_src | upper == "HSE" %}
    // Enable HSE and wait ready
    dp.RCC.cr.modify(|r, w| unsafe { w.bits(r.bits() | (1 << 16)) }); // HSEON
    while dp.RCC.cr.read().bits() & (1 << 17) == 0 {} // HSERDY
    // Switch SYSCLK to HSE (CFGR.SW = 0b10)
    dp.RCC.cfgr.modify(|r, w| unsafe { w.bits((r.bits() & !(0b11)) | (0b10)) });
    {% else %}
    // HSI is enabled by default after reset; ensure it remains on and select it (SW=0b00)
    dp.RCC.cfgr.modify(|r, w| unsafe { w.bits(r.bits() & !(0b11)) });
    {% endif %}
    // NOTE: Prescalers are left at reset defaults (no division). Add mappings as needed.
    {% else %}
    // Attempt PLL1 system clock configuration for STM32H7 using parsed `.ioc` values.
    // This sequence follows the reference manual at a high level and may need
    // refinement per sub-family. Values are encoded per H7 PAC layout.
    // Preconditions: Voltage scaling and supply have been set in init_power().
    // 1) Ensure PLL1 is off
    dp.RCC.cr.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << 24)) }); // PLL1ON=0
    while dp.RCC.cr.read().bits() & (1 << 25) != 0 {} // wait PLL1RDY=0
    // 2) Select PLL source and DIVM1
    // PLLSRC: 00=HSI, 01=CSI, 10=HSE
    let pllsrc_str: &str = "{{ spec.clocks.pll_src | default("HSI") | upper }}";
    let pllsrc_bits: u32 = match pllsrc_str {
        "HSE" | "RCC_PLLSOURCE_HSE" => 0b10,
        "CSI" | "RCC_PLLSOURCE_CSI" => 0b01,
        _ => 0b00, // HSI default
    };
    // DIVM1 from `.ioc` PLL1
    let divm1: u32 = {{ (spec.clocks.pll["pll1"].m | default(1)) }} as u32 & 0x3F;
    // PLLCKSELR: set PLLSRC and DIVM1; keep other DIVM at reset
    dp.RCC.pllckselr.modify(|_, w| unsafe { w.bits((pllsrc_bits & 0b11) | (divm1 << 4)) });
    // 3) Program PLL1DIVR (minus-one encoding)
    let n1: u32 = ({{ (spec.clocks.pll["pll1"].n | default(8)) }} as u32).saturating_sub(1) & 0x1FF;
    let p1: u32 = ({{ (spec.clocks.pll["pll1"].p | default(2)) }} as u32).saturating_sub(1) & 0x7F;
    let q1: u32 = ({{ (spec.clocks.pll["pll1"].q | default(2)) }} as u32).saturating_sub(1) & 0x7F;
    let r1: u32 = ({{ (spec.clocks.pll["pll1"].r | default(2)) }} as u32).saturating_sub(1) & 0x7F;
    let pll1divr: u32 = (n1 << 0) | (p1 << 9) | (q1 << 16) | (r1 << 24);
    dp.RCC.pll1divr.write(|w| unsafe { w.bits(pll1divr) });
    // 4) Enable PLL1 P/Q/R outputs as needed; here we enable P for SYSCLK
    // DIVP1EN=1 (bit 16), others optional
    dp.RCC.pllcfgr.modify(|r, w| unsafe { w.bits(r.bits() | (1 << 16)) });
    // 5) Enable PLL1 and wait ready
    dp.RCC.cr.modify(|r, w| unsafe { w.bits(r.bits() | (1 << 24)) }); // PLL1ON=1
    while dp.RCC.cr.read().bits() & (1 << 25) == 0 {} // PLL1RDY
    // 6) Switch SYSCLK to PLL1 (CFGR.SW=0b11) and wait SWS==0b11
    dp.RCC.cfgr.modify(|r, w| unsafe { w.bits((r.bits() & !0b11) | 0b11) });
    while (dp.RCC.cfgr.read().bits() & (0b11 << 3)) != (0b11 << 3) {}
    // 7) Apply CPU prescaler (D1CFGR.D1CPRE[3:0]) if specified
    let d1cpre_bits: u32 = match "{{ spec.clocks.d1cpu | default("") }}" {
        "RCC_D1CPRE_DIV1" | "RCC_SYSCLK_NOT_DIV1" | "RCC_HCLK_DIV1" => 0b0000,
        "RCC_D1CPRE_DIV2" | "RCC_HCLK_DIV2" => 0b1000,
        "RCC_D1CPRE_DIV4" | "RCC_HCLK_DIV4" => 0b1001,
        "RCC_D1CPRE_DIV8" | "RCC_HCLK_DIV8" => 0b1010,
        "RCC_D1CPRE_DIV16"| "RCC_HCLK_DIV16"=> 0b1011,
        "RCC_D1CPRE_DIV64"| "RCC_HCLK_DIV64"=> 0b1100,
        "RCC_D1CPRE_DIV128"|"RCC_HCLK_DIV128"=>0b1101,
        "RCC_D1CPRE_DIV256"|"RCC_HCLK_DIV256"=>0b1110,
        "RCC_D1CPRE_DIV512"|"RCC_HCLK_DIV512"=>0b1111,
        _ => 0b0000,
    };
    dp.RCC.d1cfgr.modify(|r, w| unsafe { w.bits((r.bits() & !0xF) | d1cpre_bits) });

    // 8) Apply APB prescalers if specified
    // D1PPRE (APB3) at D1CFGR[10:8]
    let d1ppre_bits: u32 = match "{{ spec.clocks.d1ppre | default("") }}" {
        "RCC_APB3_DIV1" | "RCC_D1PPRE_DIV1" | "RCC_HCLK_DIV1" => 0b000,
        "RCC_APB3_DIV2" | "RCC_D1PPRE_DIV2" | "RCC_HCLK_DIV2" => 0b100,
        "RCC_APB3_DIV4" | "RCC_D1PPRE_DIV4" | "RCC_HCLK_DIV4" => 0b101,
        "RCC_APB3_DIV8" | "RCC_D1PPRE_DIV8" | "RCC_HCLK_DIV8" => 0b110,
        "RCC_APB3_DIV16"| "RCC_D1PPRE_DIV16"| "RCC_HCLK_DIV16"=> 0b111,
        _ => 0b000,
    };
    dp.RCC.d1cfgr.modify(|r, w| unsafe { w.bits((r.bits() & !(0x7 << 8)) | (d1ppre_bits << 8)) });
    // D2PPRE1 (APB1) at D2CFGR[10:8]
    let d2ppre1_bits: u32 = match "{{ spec.clocks.d2ppre1 | default("") }}" {
        "RCC_APB1_DIV1" | "RCC_D2PPRE1_DIV1" | "RCC_HCLK_DIV1" => 0b000,
        "RCC_APB1_DIV2" | "RCC_D2PPRE1_DIV2" | "RCC_HCLK_DIV2" => 0b100,
        "RCC_APB1_DIV4" | "RCC_D2PPRE1_DIV4" | "RCC_HCLK_DIV4" => 0b101,
        "RCC_APB1_DIV8" | "RCC_D2PPRE1_DIV8" | "RCC_HCLK_DIV8" => 0b110,
        "RCC_APB1_DIV16"| "RCC_D2PPRE1_DIV16"| "RCC_HCLK_DIV16"=> 0b111,
        _ => 0b000,
    };
    dp.RCC.d2cfgr.modify(|r, w| unsafe { w.bits((r.bits() & !(0x7 << 8)) | (d2ppre1_bits << 8)) });
    // D2PPRE2 (APB2) at D2CFGR[13:11]
    let d2ppre2_bits: u32 = match "{{ spec.clocks.d2ppre2 | default("") }}" {
        "RCC_APB2_DIV1" | "RCC_D2PPRE2_DIV1" | "RCC_HCLK_DIV1" => 0b000,
        "RCC_APB2_DIV2" | "RCC_D2PPRE2_DIV2" | "RCC_HCLK_DIV2" => 0b100,
        "RCC_APB2_DIV4" | "RCC_D2PPRE2_DIV4" | "RCC_HCLK_DIV4" => 0b101,
        "RCC_APB2_DIV8" | "RCC_D2PPRE2_DIV8" | "RCC_HCLK_DIV8" => 0b110,
        "RCC_APB2_DIV16"| "RCC_D2PPRE2_DIV16"| "RCC_HCLK_DIV16"=> 0b111,
        _ => 0b000,
    };
    dp.RCC.d2cfgr.modify(|r, w| unsafe { w.bits((r.bits() & !(0x7 << 11)) | (d2ppre2_bits << 11)) });
    // D3PPRE (APB4) at D3CFGR[10:8]
    let d3ppre_bits: u32 = match "{{ spec.clocks.d3ppre | default("") }}" {
        "RCC_APB4_DIV1" | "RCC_D3PPRE_DIV1" | "RCC_HCLK_DIV1" => 0b000,
        "RCC_APB4_DIV2" | "RCC_D3PPRE_DIV2" | "RCC_HCLK_DIV2" => 0b100,
        "RCC_APB4_DIV4" | "RCC_D3PPRE_DIV4" | "RCC_HCLK_DIV4" => 0b101,
        "RCC_APB4_DIV8" | "RCC_D3PPRE_DIV8" | "RCC_HCLK_DIV8" => 0b110,
        "RCC_APB4_DIV16"| "RCC_D3PPRE_DIV16"| "RCC_HCLK_DIV16"=> 0b111,
        _ => 0b000,
    };
    dp.RCC.d3cfgr.modify(|r, w| unsafe { w.bits((r.bits() & !(0x7 << 8)) | (d3ppre_bits << 8)) });
    {% endif %}
}
{% endif %}

{% if spec.pinctrl|length %}
{% if meta is defined %}
/// Configures pins for {{ meta.board }} using PAC registers.
{% else %}
/// Configures pins using PAC registers.
{% endif %}
pub fn configure_pins_pac(dp: &pac::Peripherals) {
    {% if mod_name %}
    #[cfg(feature = "c_hal")]
    {
        let _ = dp;
        unsafe { super::super::c_ffi::c_{{ mod_name }}_configure_pins(); }
    }
    #[cfg(not(feature = "c_hal"))]
    {% endif %}
    {
{% if grouped_writes %}
{% for p in ns.ports %}
    // GPIO{{ p }}
    dp.GPIO{{ p }}.pupdr.modify(|r, w| unsafe {
        let mut bits = r.bits();
        {%- for pin in spec.pinctrl if pin.pin[1] == p -%}
        let shift = {{ idx(pin) }} * 2;
        bits &= !(0b11 << shift);
        {%- if pin.func[0:3] == "I2C" %}bits |= 0b01 << shift;{%- else %}bits |= 0b00 << shift;{%- endif %}
        {%- endfor -%}
        w.bits(bits)
    });
    dp.GPIO{{ p }}.otyper.modify(|r, w| unsafe {
        let mut bits = r.bits();
        {%- for pin in spec.pinctrl if pin.pin[1] == p -%}
        bits &= !(1 << {{ idx(pin) }});
        {%- if pin.func[0:3] == "I2C" %}bits |= 1 << {{ idx(pin) }};{%- endif %}
        {%- endfor -%}
        w.bits(bits)
    });
    dp.GPIO{{ p }}.ospeedr.modify(|r, w| unsafe {
        let mut bits = r.bits();
        {%- for pin in spec.pinctrl if pin.pin[1] == p -%}
        let shift = {{ idx(pin) }} * 2;
        bits &= !(0b11 << shift);
        {%- if pin.func[0:18] == "USB_OTG_HS_ULPI_" or pin.func[0:5] == "SDMMC" or pin.func[0:3] == "SPI" %}bits |= 0b11 << shift;{%- endif %}
        {%- endfor -%}
        w.bits(bits)
    });
    {# Only emit AFRL/AFRH writes when there are pins in the half #}
    {% set have_low = namespace(v=false) %}
    {% for pin in spec.pinctrl %}
        {% if pin.pin[1] == p and idx(pin) < 8 %}{% set have_low.v = true %}{% endif %}
    {% endfor %}
    {% if have_low.v %}
    dp.GPIO{{ p }}.afrl.modify(|r, w| unsafe {
        let mut bits = r.bits();
        {%- for pin in spec.pinctrl -%}
            {%- if pin.pin[1] == p and idx(pin) < 8 -%}
        let afr_shift = {{ idx(pin) }} * 4;
        bits &= !(0xF << afr_shift);
        bits |= ({{ pin.af }}u32 & 0xF) << afr_shift;
            {%- endif -%}
        {%- endfor -%}
        w.bits(bits)
    });
    {% endif %}
    {% set have_high = namespace(v=false) %}
    {% for pin in spec.pinctrl %}
        {% if pin.pin[1] == p and idx(pin) >= 8 %}{% set have_high.v = true %}{% endif %}
    {% endfor %}
    {% if have_high.v %}
    dp.GPIO{{ p }}.afrh.modify(|r, w| unsafe {
        let mut bits = r.bits();
        {%- for pin in spec.pinctrl -%}
            {%- if pin.pin[1] == p and idx(pin) >= 8 -%}
        let afr_shift = ({{ idx(pin) }} % 8) * 4;
        bits &= !(0xF << afr_shift);
        bits |= ({{ pin.af }}u32 & 0xF) << afr_shift;
            {%- endif -%}
        {%- endfor -%}
        w.bits(bits)
    });
    {% endif %}
    dp.GPIO{{ p }}.moder.modify(|r, w| unsafe {
        let mut bits = r.bits();
        {%- for pin in spec.pinctrl if pin.pin[1] == p -%}
        let shift = {{ idx(pin) }} * 2;
        bits &= !(0b11 << shift);
        {%- if pin.func[0:5] == "GPIO_" -%}
            {%- if "Output" in pin.func %}bits |= 0b01 << shift;{%- elif "Input" in pin.func %}bits |= 0b00 << shift;{%- elif pin.func[0:3] == "ADC" or ("Analog" in pin.func) %}bits |= 0b11 << shift;{%- else %}bits |= 0b00 << shift;{%- endif %}
        {%- else -%}bits |= 0b10 << shift;{%- endif -%}
        {%- endfor -%}
        w.bits(bits)
    });
{% endfor %}
{% else %}
{% for pin in spec.pinctrl %}
    // {{ pin.pin }} {{ pin.func }} AF{{ pin.af }}{% if pin.label %} ({{ pin.label }}){% endif %}
    let shift = {{ idx(pin) }} * 2;
    dp.GPIO{{ port_u(pin) }}.pupdr.modify(|r, w| unsafe {
        let mut bits = r.bits() & !(0b11 << shift);
        {%- if pin.func[0:3] == "I2C" -%}
        bits |= 0b01 << shift; // PullUp
        {%- else -%}
        bits |= 0b00 << shift;
        {%- endif -%}
        w.bits(bits)
    });
    dp.GPIO{{ port_u(pin) }}.otyper.modify(|r, w| unsafe {
        let {{ 'mut ' if pin.func[0:3] == 'I2C' }}bits = r.bits() & !(1 << {{ idx(pin) }});
        {%- if pin.func[0:3] == "I2C" -%}
        bits |= 1 << {{ idx(pin) }}; // OpenDrain
        {%- endif -%}
        w.bits(bits)
    });
    dp.GPIO{{ port_u(pin) }}.ospeedr.modify(|r, w| unsafe {
        let {{ 'mut ' if pin.func[0:18] == 'USB_OTG_HS_ULPI_' or pin.func[0:5] == 'SDMMC' or pin.func[0:3] == 'SPI' }}bits = r.bits() & !(0b11 << shift);
        {%- if pin.func[0:18] == "USB_OTG_HS_ULPI_" or pin.func[0:5] == "SDMMC" or pin.func[0:3] == "SPI" -%}
        bits |= 0b11 << shift; // VeryHigh
        {%- endif -%}
        w.bits(bits)
    });
    dp.GPIO{{ port_u(pin) }}.afr{{ 'l' if idx(pin) < 8 else 'h' }}.modify(|r, w| unsafe {
        let afr_shift = ({{ idx(pin) }} % 8) * 4;
        let mut bits = r.bits() & !(0xF << afr_shift);
        bits |= ({{ pin.af }}u32 & 0xF) << afr_shift;
        w.bits(bits)
    });
    dp.GPIO{{ port_u(pin) }}.moder.modify(|r, w| unsafe {
        let mut bits = r.bits() & !(0b11 << shift);
        {%- if pin.func[0:5] == "GPIO_" -%}
        {%- if "Output" in pin.func -%}
        bits |= 0b01 << shift;
        {%- elif "Input" in pin.func -%}
        bits |= 0b00 << shift;
        {%- elif pin.func[0:3] == "ADC" or ("Analog" in pin.func) -%}
        bits |= 0b11 << shift;
        {%- else -%}
        bits |= 0b00 << shift;
        {%- endif -%}
        {%- else -%}
        bits |= 0b10 << shift; // Alternate
        {%- endif -%}
        w.bits(bits)
    });
{% endfor %}
{% endif %}
    }
}
{% endif %}

{% if meta is defined %}
/// Disables unused peripherals for {{ meta.board }} and masks their interrupts.
{% else %}
/// Disables unused peripherals and masks their interrupts.
{% endif %}
{% if meta is defined %}
/// Enables peripheral clocks for {{ meta.board }} using PAC registers.
{% else %}
/// Enables peripheral clocks for the generated board using PAC registers.
{% endif %}
{%- set pairs = [] -%}
{%- for name, _per in spec.peripherals | dictsort %}
    {%- if spec.mcu[0:7] == "STM32H7" %}
        {%- if name == "i2c4" %}{% set reg = "apb4enr" %}{% set field = "i2c4en" %}
        {%- elif name == "spi2" %}{% set reg = "apb1lenr" %}{% set field = "spi2en" %}
        {%- elif name == "spi5" %}{% set reg = "apb2enr" %}{% set field = "spi5en" %}
        {%- elif name == "uart8" %}{% set reg = "apb1henr" %}{% set field = "uart8en" %}
        {%- elif name == "usart1" %}{% set reg = "apb2enr" %}{% set field = "usart1en" %}
        {%- else %}{% set reg = None %}{% set field = None %}{%- endif %}
    {%- elif spec.mcu[0:7] == "STM32H5" %}
        {%- if name in ["usart1", "spi1"] %}{% set reg = "apb2enr" %}{% set field = name ~ "en" %}
        {%- else %}{% set reg = "apb1enr1" %}{% set field = name ~ "en" %}{%- endif %}
    {%- elif spec.mcu[0:6] == "STM32F" or spec.mcu[0:6] == "STM32L" or spec.mcu[0:6] == "STM32G" %}
        {%- if name in ["spi1", "spi4", "spi5", "spi6", "usart1", "usart6"] %}
            {% set reg = "apb2enr" %}
        {%- elif name[0:3] == "spi" or name[0:5] == "usart" or name[0:4] == "uart" or name[0:3] == "i2c" %}
            {% set reg = "apb1enr" %}
        {%- else %}{% set reg = None %}{% endif %}
        {%- if reg %}{% set field = name ~ "en" %}{% else %}{% set field = None %}{% endif %}
    {%- else %}
        {% set reg = None %}{% set field = None %}
    {%- endif %}
    {%- if reg and field %}{% set pairs = pairs + [{"reg": reg, "field": field}] %}{% endif %}
{%- endfor %}
pub fn enable_peripherals({{ 'dp' if pairs|length else '_dp' }}: &pac::Peripherals) {
    {% if mod_name %}
    #[cfg(feature = "c_hal")]
    unsafe { super::super::c_ffi::c_{{ mod_name }}_enable_peripherals(); }
    {% endif %}
{% if pairs|length %}
    {% if mod_name %}#[cfg(not(feature = "c_hal"))]{% endif %}
    {
{% if grouped_writes %}
{%- for group in pairs | groupby("reg") %}
    dp.RCC.{{ group.grouper }}.modify(|_, w| w{% for item in group.list %}.{{ item.field }}().set_bit(){% endfor %});
{%- endfor %}
{% else %}
{%- for item in pairs %}
    dp.RCC.{{ item.reg }}.modify(|_, w| w.{{ item.field }}().set_bit());
{%- endfor %}
{% endif %}
    }
{% endif %}
}

{% if with_deinit %}
{% if meta is defined %}
/// De-initializes {{ meta.board }} pins to their analog state.
{% else %}
/// De-initializes board pins to their analog state.
{% endif %}
{% if meta is defined %}
/// De-initializes {{ meta.board }} peripherals and clocks using PAC registers.
{% else %}
/// De-initializes board peripherals and clocks using PAC registers.
{% endif %}
pub fn deinit_board_pac(dp: &pac::Peripherals) {
    {% if mod_name %}
    #[cfg(feature = "c_hal")]
    {
        let _ = dp;
        unsafe { super::super::c_ffi::c_{{ mod_name }}_deinit(); }
    }
    #[cfg(not(feature = "c_hal"))]
    {% endif %}
    {
    // Return pins to analog and remove pulls/open-drain
{%- for pin in spec.pinctrl %}
    let shift = {{ idx(pin) }} * 2;
    dp.GPIO{{ port_u(pin) }}.moder.modify(|r, w| unsafe {
        let mut bits = r.bits() & !(0b11 << shift);
        bits |= 0b11 << shift;
        w.bits(bits)
    });
    dp.GPIO{{ port_u(pin) }}.pupdr.modify(|r, w| unsafe { w.bits(r.bits() & !(0b11 << shift)) });
    dp.GPIO{{ port_u(pin) }}.otyper.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << {{ idx(pin) }})) });
{%- endfor %}

    // Gate peripheral clocks
{%- set pairs = [] -%}
{%- for name, _per in spec.peripherals | dictsort %}
    {%- if spec.mcu[0:7] == "STM32H7" %}
        {%- if name == "i2c4" %}{% set reg = "apb4enr" %}{% set field = "i2c4en" %}
        {%- elif name == "spi2" %}{% set reg = "apb1lenr" %}{% set field = "spi2en" %}
        {%- elif name == "spi5" %}{% set reg = "apb2enr" %}{% set field = "spi5en" %}
        {%- elif name == "uart8" %}{% set reg = "apb1henr" %}{% set field = "uart8en" %}
        {%- elif name == "usart1" %}{% set reg = "apb2enr" %}{% set field = "usart1en" %}
        {%- else %}{% set reg = None %}{% set field = None %}{%- endif %}
    {%- elif spec.mcu[0:7] == "STM32H5" %}
        {%- if name in ["usart1", "spi1"] %}{% set reg = "apb2enr" %}{% set field = name ~ "en" %}
        {%- else %}{% set reg = "apb1enr1" %}{% set field = name ~ "en" %}{%- endif %}
    {%- elif spec.mcu[0:6] == "STM32F" or spec.mcu[0:6] == "STM32L" or spec.mcu[0:6] == "STM32G" %}
        {%- if name in ["spi1", "spi4", "spi5", "spi6", "usart1", "usart6"] %}
            {% set reg = "apb2enr" %}
        {%- elif name[0:3] == "spi" or name[0:5] == "usart" or name[0:4] == "uart" or name[0:3] == "i2c" %}
            {% set reg = "apb1enr" %}
        {%- else %}{% set reg = None %}{% endif %}
        {%- if reg %}{% set field = name ~ "en" %}{% else %}{% set field = None %}{% endif %}
    {%- else %}
        {% set reg = None %}{% set field = None %}
    {%- endif %}
    {%- if reg and field %}{% set pairs = pairs + [{"reg": reg, "field": field}] %}{% endif %}
{%- endfor %}
{%- for group in pairs | groupby("reg") %}
    dp.RCC.{{ group.grouper }}.modify(|_, w| w{% for item in group.list %}.{{ item.field }}().clear_bit(){% endfor %});
{%- endfor %}

    {%- set dmas = [] %}
    {%- set bdmas = [] %}
    {%- set mdmas = [] %}
    {%- for name, _per in spec.peripherals | dictsort %}
        {%- if name[0:3] == "dma" %}{% set dmas = dmas + [name|upper] %}
        {%- elif name[0:4] == "bdma" %}{% set bdmas = bdmas + [name|upper] %}
        {%- elif name[0:4] == "mdma" %}{% set mdmas = mdmas + [name|upper] %}
        {%- endif %}
    {%- endfor %}
    {%- set channel_dma = spec.mcu[0:7] == "STM32F0" or spec.mcu[0:7] == "STM32F1" or spec.mcu[0:7] == "STM32F3" or spec.mcu[0:7] == "STM32L0" or spec.mcu[0:7] == "STM32L1" or spec.mcu[0:7] == "STM32G0" %}

    // Disable DMA controllers and mask their interrupts
    {%- if dmas %}
    dp.RCC.{{ "ahbenr" if channel_dma else "ahb1enr" }}.modify(|_, w| w{% for dma in dmas %}.{{ dma|lower }}en().clear_bit(){% endfor %});
    {%- for dma in dmas %}
        {%- if channel_dma %}
            {%- for i in range(1,8) %}
    unsafe { pac::NVIC::mask(pac::Interrupt::{{ dma }}_CHANNEL{{ i }}); }
            {%- endfor %}
    dp.{{ dma }}.ifcr.write(|w| unsafe { w.bits(0xFFFF_FFFF) });
            {%- for i in range(1,8) %}
    dp.{{ dma }}.ch[{{ i }}].ccr.write(|w| unsafe { w.bits(0) });
    dp.{{ dma }}.ch[{{ i }}].cndtr.write(|w| unsafe { w.bits(0) });
    dp.{{ dma }}.ch[{{ i }}].cpar.write(|w| unsafe { w.bits(0) });
    dp.{{ dma }}.ch[{{ i }}].cmar.write(|w| unsafe { w.bits(0) });
            {%- endfor %}
        {%- else %}
            {%- for i in range(0,8) %}
    unsafe { pac::NVIC::mask(pac::Interrupt::{{ dma }}_STREAM{{ i }}); }
            {%- endfor %}
    dp.{{ dma }}.lifcr.write(|w| unsafe { w.bits(0xFFFF_FFFF) });
    dp.{{ dma }}.hifcr.write(|w| unsafe { w.bits(0xFFFF_FFFF) });
            {%- for i in range(0,8) %}
    dp.{{ dma }}.st[{{ i }}].cr.write(|w| unsafe { w.bits(0) });
    dp.{{ dma }}.st[{{ i }}].ndtr.write(|w| unsafe { w.bits(0) });
    dp.{{ dma }}.st[{{ i }}].par.write(|w| unsafe { w.bits(0) });
    dp.{{ dma }}.st[{{ i }}].m0ar.write(|w| unsafe { w.bits(0) });
    dp.{{ dma }}.st[{{ i }}].m1ar.write(|w| unsafe { w.bits(0) });
    dp.{{ dma }}.st[{{ i }}].fcr.write(|w| unsafe { w.bits(0) });
            {%- endfor %}
        {%- endif %}
    {%- endfor %}
    {%- endif %}

    {%- if bdmas %}
    dp.RCC.ahb4enr.modify(|_, w| w{% for bdma in bdmas %}.{{ bdma|lower }}en().clear_bit(){% endfor %});
    {%- for bdma in bdmas %}
        {%- for i in range(0,8) %}
    unsafe { pac::NVIC::mask(pac::Interrupt::{{ bdma }}_CHANNEL{{ i }}); }
    dp.{{ bdma }}.ifcr.write(|w| unsafe { w.bits(0xFFFF_FFFF) });
    dp.{{ bdma }}.ch[{{ i }}].cr.write(|w| unsafe { w.bits(0) });
    dp.{{ bdma }}.ch[{{ i }}].bndtr.write(|w| unsafe { w.bits(0) });
    dp.{{ bdma }}.ch[{{ i }}].sar.write(|w| unsafe { w.bits(0) });
    dp.{{ bdma }}.ch[{{ i }}].dar.write(|w| unsafe { w.bits(0) });
        {%- endfor %}
    {%- endfor %}
    {%- endif %}

    {%- if mdmas %}
    dp.RCC.ahb3enr.modify(|_, w| w{% for mdma in mdmas %}.{{ mdma|lower }}en().clear_bit(){% endfor %});
    {%- for mdma in mdmas %}
    unsafe { pac::NVIC::mask(pac::Interrupt::{{ mdma }}); }
        {%- for i in range(0,16) %}
    dp.{{ mdma }}.ch[{{ i }}].cr.write(|w| unsafe { w.bits(0) });
    dp.{{ mdma }}.ch[{{ i }}].tcr.write(|w| unsafe { w.bits(0) });
    dp.{{ mdma }}.ch[{{ i }}].bndtr.write(|w| unsafe { w.bits(0) });
    dp.{{ mdma }}.ch[{{ i }}].sar.write(|w| unsafe { w.bits(0) });
    dp.{{ mdma }}.ch[{{ i }}].dar.write(|w| unsafe { w.bits(0) });
    dp.{{ mdma }}.ch[{{ i }}].brur.write(|w| unsafe { w.bits(0) });
    dp.{{ mdma }}.ch[{{ i }}].lar.write(|w| unsafe { w.bits(0) });
    dp.{{ mdma }}.ch[{{ i }}].tbr.write(|w| unsafe { w.bits(0) });
    dp.{{ mdma }}.ch[{{ i }}].mar.write(|w| unsafe { w.bits(0) });
    dp.{{ mdma }}.ch[{{ i }}].mdr.write(|w| unsafe { w.bits(0) });
        {%- endfor %}
    {%- endfor %}
    {%- endif %}

    // Disable interrupts
    {%- set irq_map = {
        "i2c4": ["I2C4_EV", "I2C4_ER"],
        "spi2": ["SPI2"],
        "spi5": ["SPI5"],
        "uart8": ["UART8"],
        "usart1": ["USART1"],
    } -%}
    {%- for name, _per in spec.peripherals | dictsort %}
        {%- for irq in (irq_map[name] | default([])) %}
    unsafe { pac::NVIC::mask(pac::Interrupt::{{ irq }}); }
        {%- endfor %}
    {%- endfor %}
    }
}
{% endif %}

{% if mod_name is not defined %}
{% if meta is defined %}
/// Initializes {{ meta.board }} using PAC register access.
{% else %}
/// Initializes the board using PAC register access.
{% endif %}
{% if meta is defined %}
/// Initializes {{ meta.board }} using PAC register access.
{% else %}
/// Initializes the board using PAC register access.
{% endif %}
pub fn init_board_pac(dp: pac::Peripherals) {
    init_power(&dp);
    enable_gpio_clocks(&dp);
    configure_pins_pac(&dp);
    enable_peripherals(&dp);
}
{% endif %}

{# Emit label constants if requested #}
{% if emit_label_consts and idents and spec.pinctrl|length %}
/// Label-to-pin mapping constants derived from GPIO_Label entries.
#[allow(dead_code)]
pub struct PinLabel { pub pin: &'static str, pub func: &'static str, pub af: u8 }

/// Constants for user labels to ease discovery and mapping.
pub mod pins {
    use super::PinLabel;
  {# De-duplicate by physical pin: emit first entry for each pin having a label #}
  {% for group in spec.pinctrl | groupby("pin") %}
    {% set first = group.list[0] %}
    {% if first.label and idents[first.pin] is defined %}
    pub const {{ idents[first.pin] | upper }}: PinLabel = PinLabel { pin: "{{ first.pin }}", func: "{{ first.func }}", af: {{ first.af }} };
    {% endif %}
  {% endfor %}
}
{% endif %}
