//! 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;

fn analog_input() {
    #[allow(non_snake_case)]
    let GPIOC = unsafe { &*stm32f40x::GPIOC::ptr() };
    #[allow(non_snake_case)]
    let GPIOD = unsafe { &*stm32f40x::GPIOD::ptr() };
    #[allow(non_snake_case)]
    let GPIOE = unsafe { &*stm32f40x::GPIOE::ptr() };
    // #[allow(non_snake_case)]
    // let GPIOF = unsafe { &*stm32f40x::GPIOF::ptr() };
    // #[allow(non_snake_case)]
    // let GPIOG = unsafe { &*stm32f40x::GPIOG::ptr() };
    // #[allow(non_snake_case)]
    // let GPIOH = unsafe { &*stm32f40x::GPIOH::ptr() };
    // #[allow(non_snake_case)]
    // let GPIOI = unsafe { &*stm32f40x::GPIOI::ptr() };
    let r1 = GPIOC.moder.read().bits();
    GPIOC.moder.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); // analog mode
    GPIOD.moder.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); // analog mode
    GPIOE.moder.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); // analog mode
                                                           // GPIOF.moder.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); // analog mode
                                                           // GPIOG.moder.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); // analog mode
                                                           // GPIOH.moder.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); // analog mode
                                                           // GPIOI.moder.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); // analog mode

    let r2 = GPIOC.moder.read().bits();
    // asm::bkpt();
}

// 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() };
    #[allow(non_snake_case)]
    let GPIOA = unsafe { &*stm32f40x::GPIOA::ptr() };
    #[allow(non_snake_case)]
    let GPIOB = unsafe { &*stm32f40x::GPIOB::ptr() };
    dbg.dbgmcu_cr.modify(|_, w| {
        w.dbg_sleep()
            .set_bit()
            .dbg_stop()
            .set_bit()
            .dbg_standby()
            .set_bit()
            .trace_ioen()
            .set_bit()
    });
    GPIOA.moder.reset();
    GPIOA.pupdr.reset();

    GPIOB.moder.reset();
    GPIOB.pupdr.reset();
}

// set the MCU in true sleepdeep mode on WFI/WFE
// debugging is disabled (until re-enabled)

fn dbg_disable() {
    #[allow(non_snake_case)]
    let DBG = unsafe { &*stm32f40x::DBG::ptr() };
    #[allow(non_snake_case)]
    let GPIOA = unsafe { &*stm32f40x::GPIOA::ptr() };
    #[allow(non_snake_case)]
    let GPIOB = unsafe { &*stm32f40x::GPIOB::ptr() };
    asm::nop();
    DBG.dbgmcu_cr.modify(|_, w| {
        w.dbg_sleep()
            .clear_bit()
            .dbg_stop()
            .clear_bit()
            .dbg_standby()
            .clear_bit()
            .trace_ioen()
            .clear_bit()
    });
    // set all gpio to analog input
    // let _a_moder_ = GPIOA.moder.read().bits();
    // let _b_moder_ = GPIOB.moder.read().bits();
    // let _a_pupdr_ = GPIOA.pupdr.read().bits();
    // let _b_pupdr_ = GPIOB.pupdr.read().bits();

    // 0.142 mA without manipulating GPIO/GPIOB

    GPIOA.moder.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); // PA, analog
    GPIOA.moder.modify(|_, w| w.moder1().input_mode()); // PA1, input_mode
    GPIOB.pupdr.write(|w| unsafe { w.bits(0) }); // PA, floating
    GPIOA.pupdr.modify(|_, w| w.pupdr1().pull_up()); // PA1, pull up

    GPIOB.moder.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); // PB, analog
    GPIOB.pupdr.write(|w| unsafe { w.bits(0) }); // PB, floating

    // let _a_moder = GPIOA.moder.read().bits();
    // let _b_moder = GPIOB.moder.read().bits();
    // let _a_pupdr = GPIOA.pupdr.read().bits();
    // let _b_pupdr = GPIOB.pupdr.read().bits();
    asm::nop();
}

// 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, gpiob
    r.RCC.ahb1enr.modify(|_, w| {
        w.gpioaen()
            .set_bit()
            .gpioben()
            .set_bit()
            .gpiocen()
            .set_bit()
            .gpioden()
            .set_bit()
            .gpioeen()
            .set_bit()
            .gpiofen()
            .set_bit()
    });
    // asm::bkpt();
    analog_input();
    // asm::bkpt();

    let mut read: u32 = 0;

    // PA1 pull up
    r.GPIOA.pupdr.modify(|r, w| w.pupdr1().pull_up());
    #[allow(non_snake_case)]
    let GPIOA = unsafe { &*stm32f40x::GPIOA::ptr() };

    asm::nop();

    // SYSCFG.exti1 is PAx (0000) by reset, so we don't change it

    // 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());
    // clear triggering event
    r.EXTI.pr.modify(|_, w| w.pr1().set_bit());

    p.SCB.set_sleepdeep();
    // enable pwr
    r.RCC.apb1enr.modify(|_, w| w.pwren().set_bit());
    // set standby mode
    // r.PWR
    //     .cr
    //     .write(|w| unsafe { w.bits((1 << 10) | 0x0000_8000) });
    r.PWR
        .cr
        .modify(|_, w| w.pdds().clear_bit().lpds().set_bit().fpds().set_bit());

    // r.PWR
    //     .cr
    //     .modify(|_, w| w.pdds().clear_bit().lpds().clear_bit());
    // blink();

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

    asm::bkpt();
    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

// 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();
        }

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

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

// 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();
}