use core::result::Result; use core::result::Result::Ok; use core::sync::atomic::{compiler_fence, Ordering}; use crate::timer::{delay_nops, sleep_ms}; use crate::{read_address, write_address}; const GPFSEL_BASE: u32 = 0x3F20_0000; const GPSET_BASE: u32 = 0x3F20_001C; const GPCLR_BASE: u32 = 0x3F20_0028; const GPLEV_BASE: u32 = 0x3F20_0034; const GPEDS_BASE: u32 = 0x3F20_0040; const GPFEN_BASE: u32 = 0x3F20_0058; const GPPUD: u32 = 0x3F20_0094; const GPPUDCLK_BASE: u32 = 0x3F20_0098; const GPREN_BASE: u32 = 0x3F20_004C; #[repr(u8)] pub enum SpecificGpio { OnboardLed = 29, } #[repr(u32)] pub enum GPIOFunction { Input = 0b000, Output = 0b001, Alternative0 = 0b100, Alternative1 = 0b101, Alternative2 = 0b110, Alternative3 = 0b111, Alternative4 = 0b011, Alternative5 = 0b010, } /// Set the function of the GPIO pin pub fn set_gpio_function(gpio: u8, state: GPIOFunction) -> Result<(), &'static str> { let register_index = gpio / 10; let register_offset = (gpio % 10) * 3; let register_addr = GPFSEL_BASE + (register_index as u32 * 4); let current = unsafe { read_address(register_addr) }; let mask = !(0b111 << register_offset); let cleared = current & mask; let new_val = cleared | ((state as u32) << register_offset); unsafe { write_address(register_addr, new_val) }; Ok(()) } /// Set the GPIO to high /// /// Should be used when GPIO function is set to `OUTPUT` via `set_gpio_function`, /// otherwise setting is ignored pub fn gpio_high(gpio: u8) -> Result<(), &'static str> { let register_index = gpio / 32; let register_offset = gpio % 32; let register_addr = GPSET_BASE + (register_index as u32 * 4); unsafe { write_address(register_addr, 1 << register_offset) }; Ok(()) } /// Set the GPIO to low /// /// Should be used when GPIO function is set to `OUTPUT` via `set_gpio_function` pub fn gpio_low(gpio: u8) -> Result<(), &'static str> { let register_index = gpio / 32; let register_offset = gpio % 32; let register_addr = GPCLR_BASE + (register_index as u32 * 4); unsafe { write_address(register_addr, 1 << register_offset) }; Ok(()) } /// Read the current GPIO power state pub fn gpio_get_state(gpio: u8) -> u8 { let register_index = gpio / 32; let register_offset = gpio % 32; let register_addr = GPLEV_BASE + (register_index as u32 * 4); let state = unsafe { read_address(register_addr) }; ((state >> register_offset) & 0b1) as u8 } /// Pull GPIO up /// /// Should be used when GPIO function is set to `INPUT` via `set_gpio_function` pub fn gpio_pull_up(gpio: u8) { gpio_pull_up_down(gpio, 0b10); } /// Pull GPIO down /// /// Should be used when GPIO function is set to `INPUT` via `set_gpio_function` pub fn gpio_pull_down(gpio: u8) { gpio_pull_up_down(gpio, 0b01); } fn gpio_pull_up_down(gpio: u8, val: u32) { // Determine GPPUDCLK Register let register_addr = GPPUDCLK_BASE + 4 * (gpio as u32 / 32); let register_offset = gpio % 32; // 1. Write Pull up unsafe { write_address(GPPUD, val) }; // 2. Delay 150 cycles delay_nops(150); // 3. Write to clock let new_val = 0b1 << register_offset; unsafe { write_address(register_addr, new_val) }; // 4. Delay 150 cycles delay_nops(150); // 5. reset GPPUD unsafe { write_address(GPPUD, 0) }; // 6. reset clock unsafe { write_address(register_addr, 0) }; } /// Get the current status of the falling edge detection pub fn read_falling_edge_detect(gpio: u8) -> bool { let register_addr = GPFEN_BASE + 4 * (gpio as u32 / 32); let register_offset = gpio % 32; let current = unsafe { read_address(register_addr) }; ((current >> register_offset) & 0b1) != 0 } /// Get the current status of the rising edge detection pub fn read_rising_edge_detect(gpio: u8) -> bool { let register_addr = GPREN_BASE + 4 * (gpio as u32 / 32); let register_offset = gpio % 32; let current = unsafe { read_address(register_addr) }; ((current >> register_offset) & 0b1) != 0 } /// Enables falling edge detection pub fn set_falling_edge_detect(gpio: u8, enable: bool) { let register_addr = GPFEN_BASE + 4 * (gpio as u32 / 32); let register_offset = gpio % 32; let current = unsafe { read_address(register_addr) }; let mask = 0b1 << register_offset; let new_val = if enable { current | mask } else { current & !mask }; unsafe { write_address(register_addr, new_val) }; } /// Enables rising edge detection pub fn set_rising_edge_detect(gpio: u8, enable: bool) { let register_addr = GPREN_BASE + 4 * (gpio as u32 / 32); let register_offset = gpio % 32; let current = unsafe { read_address(register_addr) }; let mask = 0b1 << register_offset; let new_val = if enable { current | mask } else { current & !mask }; unsafe { write_address(register_addr, new_val) }; } /// Returns with the interrupt status of an GPIO. /// /// GPEDS register is used to record level and edge events on the GPIO pins. /// When an event is triggered by the GPIO, the corresponding bit will be set to 1. pub fn read_gpio_event_detect_status(id: u32) -> bool { let register = GPEDS_BASE + (id / 32) * 4; let register_offset = id % 32; let val = unsafe { read_address(register) } >> register_offset; (val & 0b1) != 0 } /// Resets current interrupt status of a GPIO pin. pub fn reset_gpio_event_detect_status(id: u32) { let register = GPEDS_BASE + (id / 32) * 4; let register_offset = id % 32; unsafe { write_address(register, 0b1 << register_offset) }; compiler_fence(Ordering::SeqCst); } // TODO: GPHEN,GPLEN,GPAREN,GPAFEN pub fn blink_gpio(gpio: u8, duration_ms: u64) { let _ = gpio_high(gpio); sleep_ms(duration_ms); let _ = gpio_low(gpio); sleep_ms(duration_ms); }