//! bare5.rs
//!
//! C Like Peripheral API
//!
//! What it covers:
//! - abstractions in Rust
//!

#![feature(uniform_paths)] // requires nightly
#![no_std]
#![no_main]

extern crate panic_halt;

extern crate cortex_m;
use cortex_m_rt::entry;

// C like API...
mod stm32f40x {
    #[allow(dead_code)]
    use core::{cell, ptr};

    #[rustfmt::skip]
    mod address {
        pub const PERIPH_BASE: u32      = 0x40000000;
        pub const AHB1PERIPH_BASE: u32  = PERIPH_BASE + 0x00020000;
        pub const RCC_BASE: u32         = AHB1PERIPH_BASE + 0x3800;
        pub const GPIOA_BASE: u32       = AHB1PERIPH_BASE + 0x0000;
    }
    use address::*;

    pub struct VolatileCell<T> {
        value: cell::UnsafeCell<T>,
    }

    impl<T> VolatileCell<T> {
        #[inline(always)]
        pub fn read(&self) -> T
        where
            T: Copy,
        {
            unsafe { ptr::read_volatile(self.value.get()) }
        }

        #[inline(always)]
        pub fn write(&self, value: T)
        where
            T: Copy,
        {
            unsafe { ptr::write_volatile(self.value.get(), value) }
        }
    }

    // impl VolatileCell<u32> {
    //     #[inline(always)]
    //     pub fn modify(&self, offset: u8, width: u8, value: u32) {
    //         // your code here
    //     }
    // }

    #[repr(C)]
    #[allow(non_snake_case)]
    #[rustfmt::skip]
    pub struct RCC {
        pub CR:         VolatileCell<u32>,      // < RCC clock control register,                                    Address offset: 0x00 
        pub PLLCFGR:    VolatileCell<u32>,      // < RCC PLL configuration register,                                Address offset: 0x04 
        pub CFGR:       VolatileCell<u32>,      // < RCC clock configuration register,                              Address offset: 0x08 
        pub CIR:        VolatileCell<u32>,      // < RCC clock interrupt register,                                  Address offset: 0x0C 
        pub AHB1RSTR:   VolatileCell<u32>,      // < RCC AHB1 peripheral reset register,                            Address offset: 0x10 
        pub AHB2RSTR:   VolatileCell<u32>,      // < RCC AHB2 peripheral reset register,                            Address offset: 0x14 
        pub AHB3RSTR:   VolatileCell<u32>,      // < RCC AHB3 peripheral reset register,                            Address offset: 0x18 
        pub RESERVED0:  VolatileCell<u32>,      // < Reserved, 0x1C                                                                      
        pub APB1RSTR:   VolatileCell<u32>,      // < RCC APB1 peripheral reset register,                            Address offset: 0x20 
        pub APB2RSTR:   VolatileCell<u32>,      // < RCC APB2 peripheral reset register,                            Address offset: 0x24 
        pub RESERVED1:  [VolatileCell<u32>; 2], // < Reserved, 0x28-0x2C                                                                 
        pub AHB1ENR:    VolatileCell<u32>,      // < RCC AHB1 peripheral clock register,                            Address offset: 0x30 
        pub AHB2ENR:    VolatileCell<u32>,      // < RCC AHB2 peripheral clock register,                            Address offset: 0x34 
        pub AHB3ENR:    VolatileCell<u32>,      // < RCC AHB3 peripheral clock register,                            Address offset: 0x38 
        pub RESERVED2:  VolatileCell<u32>,      // < Reserved, 0x3C                                                                      
        pub APB1ENR:    VolatileCell<u32>,      // < RCC APB1 peripheral clock enable register,                     Address offset: 0x40 
        pub APB2ENR:    VolatileCell<u32>,      // < RCC APB2 peripheral clock enable register,                     Address offset: 0x44 
        pub RESERVED3:  [VolatileCell<u32>; 2], // < Reserved, 0x48-0x4C                                                                 
        pub AHB1LPENR:  VolatileCell<u32>,      // < RCC AHB1 peripheral clock enable in low power mode register,   Address offset: 0x50 
        pub AHB2LPENR:  VolatileCell<u32>,      // < RCC AHB2 peripheral clock enable in low power mode register,   Address offset: 0x54 
        pub AHB3LPENR:  VolatileCell<u32>,      // < RCC AHB3 peripheral clock enable in low power mode register,   Address offset: 0x58 
        pub RESERVED4:  VolatileCell<u32>,      // < Reserved, 0x5C                                                                      
        pub APB1LPENR:  VolatileCell<u32>,      // < RCC APB1 peripheral clock enable in low power mode register,   Address offset: 0x60 
        pub APB2LPENR:  VolatileCell<u32>,      // < RCC APB2 peripheral clock enable in low power mode register,   Address offset: 0x64 
        pub RESERVED5:  [VolatileCell<u32>; 2], // < Reserved, 0x68-0x6C                                                                 
        pub BDCR:       VolatileCell<u32>,      // < RCC Backup domain control register,                            Address offset: 0x70 
        pub CSR:        VolatileCell<u32>,      // < RCC clock control & status register,                           Address offset: 0x74 
        pub RESERVED6:  [VolatileCell<u32>; 2], // < Reserved, 0x78-0x7C                                                                 
        pub SSCGR:      VolatileCell<u32>,      // < RCC spread spectrum clock generation register,                 Address offset: 0x80 
        pub PLLI2SCFGR: VolatileCell<u32>,      // < RCC PLLI2S configuration register,                             Address offset: 0x84 
    }

    impl RCC {
        pub fn get() -> *mut RCC {
            address::RCC_BASE as *mut RCC
        }
    }

    #[repr(C)]
    #[allow(non_snake_case)]
    #[rustfmt::skip]
    pub struct GPIOA {
        pub MODER:      VolatileCell<u32>,      // < GPIO port mode register,                                       Address offset: 0x00     
        pub OTYPER:     VolatileCell<u32>,      // < GPIO port output type register,                                Address offset: 0x04     
        pub OSPEEDR:    VolatileCell<u32>,      // < GPIO port output speed register,                               Address offset: 0x08     
        pub PUPDR:      VolatileCell<u32>,      // < GPIO port pull-up/pull-down register,                          Address offset: 0x0C     
        pub IDR:        VolatileCell<u32>,      // < GPIO port input data register,                                 Address offset: 0x10     
        pub ODR:        VolatileCell<u32>,      // < GPIO port output data register,                                Address offset: 0x14     
        pub BSRRL:      VolatileCell<u16>,      // < GPIO port bit set/reset low register,                          Address offset: 0x18     
        pub BSRRH:      VolatileCell<u16>,      // < GPIO port bit set/reset high register,                         Address offset: 0x1A     
        pub LCKR:       VolatileCell<u32>,      // < GPIO port configuration lock register,                         Address offset: 0x1C     
        pub AFR:        [VolatileCell<u32>;2],  // < GPIO alternate function registers,                             Address offset: 0x20-0x24
    }

    impl GPIOA {
        pub fn get() -> *mut GPIOA {
            GPIOA_BASE as *mut GPIOA
        }
    }
}
use stm32f40x::*;

// see the Reference Manual RM0368 (www.st.com/resource/en/reference_manual/dm00096844.pdf)
// rcc,     chapter 6
// gpio,    chapter 8

fn wait(i: u32) {
    for _ in 0..i {
        cortex_m::asm::nop(); // no operation (cannot be optimized out)
    }
}

// simple test of Your `modify`
//fn test() {
    // let t:VolatileCell<u32> = unsafe {  core::mem::uninitialized() };
    // t.write(0);
    // assert!(t.read() == 0);
    // t.modify(3, 3, 0b10101);
    // //
    // //     10101   
    // //    ..0111000
    // //    ---------
    // //    000101000
    // assert!(t.read() == 0b101 << 3);
    // t.modify(4, 3, 0b10001);
    // //    000101000
    // //      111
    // //      001
    // //    000011000
    // assert!(t.read() == 0b011 << 3);
    
    // if << is used, your code will panic in dev, but not in release mode
    // t.modify(32, 3, 1);
//}

// system startup, can be hidden from the user
#[entry]
fn main() -> ! {
    let rcc = unsafe { &mut *RCC::get() }; // get the reference to RCC in memory
    let gpioa = unsafe { &mut *GPIOA::get() }; // get the reference to GPIOA in memory


    // test(); // uncomment to run test
    idle(rcc, gpioa);
    loop {}
}

// user application
fn idle(rcc: &mut RCC, gpioa: &mut GPIOA) {
    // power on GPIOA
    let r = rcc.AHB1ENR.read(); // read
    rcc.AHB1ENR.write(r | 1 << (0)); // set enable

    // configure PA5 as output
    let r = gpioa.MODER.read() & !(0b11 << (5 * 2)); // read and mask
    gpioa.MODER.write(r | 0b01 << (5 * 2)); // set output mode

    // and alter the data output through the BSRR register
    // this is more efficient as the read register is not needed.

    loop {
        // set PA5 high
        //gpioa.BSRRH.write(1 << 5); // set bit, output hight (turn on led)
        gpioa.ODR.write(gpioa.ODR.read() | (1 << 5));

        wait(10_000);

        // set PA5 low
        // gpioa.BSRRL.write(1 << 5); // clear bit, output low (turn off led)
        gpioa.ODR.write(gpioa.ODR.read() & !(1 << 5));
        wait(10_000);
    }
}

// 1. C like API.
//    In C the .h files are used for defining interfaces, like function signatures (prototypes),
//    structs and macros (but usually not the functions themselves)
//    here is a peripheral abstraction quite similar to what you would find in the .h files
//    provided by ST (and other companies). Actually, the file presesnted here is mostly a
//    cut/paste/replace of the stm32f40x.h, just Rustified.
//
//    In this case we pass mutable pointers of the peripherals to the `idle`.
//
//    Rewrite the accesses in the loop to use the data register directly (and make a read/modify/write).
//
//    Run and see that the program behaves the same.
//
//    commit your answers (bare5_1)
//
// 2. Extend the read/write API with a modify for u32, taking the
//    - address (&mut u32),
//    - field offset (in bits, u8),
//    - field width (in bits, u8),
//    - and value (u32).
//
//    Implement and check that running `test` gives you expected behavior.
//
//    Change the code into using your new API.
//
//    Run and see that the program behaves the same.
//
//    commit your answers (bare5_2)
//
//    Discussion:
//    As with algebraic operations, defalut semantics differ in between
//    debug/dev and release builds. E.g., debug << rhs is checked, rhs must be less 
//    than 32 (for 32 bit datatypes).
// 
//    Notice, over-shifting (where bits are spilled) is always considered legal,
//    its just the shift amuount that is checked.
//    There are explicit unchecked versions available if so wanted.