Files
novaOS/src/peripherals/gpio.rs

206 lines
5.7 KiB
Rust

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