//! Sleep mode example

#![no_main]
#![no_std]

extern crate cortex_m;

#[macro_use(interrupt)]
extern crate stm32f40x;
use stm32f40x::Interrupt;

#[macro_use(entry, exception)]
extern crate cortex_m_rt as rt;

extern crate panic_abort;
use cortex_m::asm;
use rt::ExceptionFrame;

use cortex_m::peripheral::Peripherals;

// set the MCU in debug sleepdeep mode on WFI/WFE
// debugging is possible even if sleeping
fn dbg_enable() {
    let dbg = unsafe { &*stm32f40x::DBG::ptr() };
    dbg.dbgmcu_cr.modify(|_, w| {
        w.dbg_sleep()
            .set_bit()
            .dbg_stop()
            .set_bit()
            .dbg_standby()
            .set_bit()
            .trace_ioen()
            .set_bit()
    });
}

// set the MCU in true sleepdeep mode on WFI/WFE
// debugging is disabled (until re-enabled)
fn dbg_disable() {
    let dbg = unsafe { &*stm32f40x::DBG::ptr() };
    dbg.dbgmcu_cr.modify(|_, w| {
        w.dbg_sleep()
            .clear_bit()
            .dbg_stop()
            .clear_bit()
            .dbg_standby()
            .clear_bit()
            .trace_ioen()
            .clear_bit()
    });
}

// the program entry point is ...
entry!(main);
// ... this never ending function
fn main() -> ! {
    let r = stm32f40x::Peripherals::take().unwrap();
    let mut p = Peripherals::take().unwrap();

    // enable the EXTI1 interrupt
    p.NVIC.enable(Interrupt::EXTI1);
    // enable gpioa (input mode on reset)
    r.RCC.ahb1enr.modify(|_, w| w.gpioaen().set_bit());
    r.GPIOA.pupdr.modify(|_, w| w.pupdr1().pull_up());

    // SYSCFG.exti1 is PAx (0000) by reset
    // enbable masking of EXTI line 1
    r.EXTI.imr.modify(|_, w| w.mr1().set_bit());
    // trigger on falling edge
    r.EXTI.ftsr.modify(|_, w| w.tr1().set_bit());

    asm::bkpt();
    blink();

    // p.NVIC.set_pending(Interrupt::EXTI1);

    loop {
        asm::nop(); // put gdb breakpoint here, the MCU and gdb will detect breakpoint
        dbg_disable();
        asm::wfi();
        dbg_enable();
        asm::nop(); // put gdb breakpoint here, in debug mode its caught twice
                    // first occation (on wakeup), gdb does NOT report span correctly,
                    // second occation, gdb DEOS report span correctly
        asm::bkpt(); // here the MCU halt, and gdb detects the asm breakpoint
                     // gdb continue, will catch the gdb breakpoint at "dbg_disable"
                     // this is good as it shows that disabling and re-enabling gdb debugging works
                     // even if the gdb communictation has been disrputed and re-estabished, nice!
    }
}
// the dbg_sleep, and dbg_stop both set deos not prohibit sleep mode debugging (error?)
// the bdg_standby set further reduces power, and prohibits debugging
// the behavior seems not to comply with the documentation

// as expected debugging release code puts the gdb breakpoint at the wrong position
// as expected the initial breakpoint after wakeup deos not register in gdb correctly
// also the blink loop becomes to fast to observe

// bind the EXTI1 handler
interrupt!(EXTI1, exti1);

// unsafe version where we access GPIOA through the (raw) pointer
fn blink() {
    #[allow(non_snake_case)]
    let GPIOA = unsafe { &*stm32f40x::GPIOA::ptr() };
    #[allow(non_snake_case)]
    let RCC = unsafe { &*stm32f40x::RCC::ptr() };
    RCC.ahb1enr.modify(|_, w| w.gpioaen().set_bit());
    GPIOA.moder.modify(|_, w| w.moder5().output_mode());
    for _ in 0..10 {
        GPIOA.bsrr.write(|w| w.br5().set_bit());
        for _ in 0..1000 {
            asm::nop();
        }
        // wait_cycles(p, 1000_000);

        GPIOA.bsrr.write(|w| w.bs5().set_bit());
        for _ in 0..1000 {
            asm::nop();
        }
        // wait_cycles(p, 1000_000);
    }
    GPIOA.moder.modify(|_, w| w.moder5().input_mode());
    RCC.ahb1enr.modify(|_, w| w.gpioaen().clear_bit());
}

// the exti1 interrupt implementation
fn exti1() {
    blink(); // it will take some time so bounces are likely gone

    // // let's try to "fake" access to GPIOA
    // // let g = stm32f40x::GPIOA {
    // //     _marker: core::marker::PhantomData, <- the field is private, so we cannot
    // // };
    // asm::bkpt();

    // clear pending state
    let exti = unsafe { &*stm32f40x::EXTI::ptr() };
    exti.pr.write(|w| w.pr1().set_bit());
}

// define the hard fault handler
exception!(HardFault, hard_fault);

fn hard_fault(_ef: &ExceptionFrame) -> ! {
    asm::bkpt();
    loop {}
}

// define the default exception handler
exception!(*, default_handler);

fn default_handler(_irqn: i16) {
    asm::bkpt();
}