Add minimal RPi power management

* Turn the board off
* Reboot the board
This commit is contained in:
Berkus Decker 2020-11-15 23:51:58 +02:00
parent 6127b5d940
commit 2ac804793b
3 changed files with 139 additions and 0 deletions

View File

@ -463,6 +463,17 @@ impl Mailbox {
buf[index + 5] = 0; // skip turbo setting
index + 6
}
#[inline]
pub fn set_device_power(&mut self, index: usize, device_id: u32, power_flags: u32) -> usize {
let buf = unsafe { self.buffer.as_mut() };
buf[index] = tag::SetPowerState;
buf[index + 1] = 8; // Buffer size // val buf size
buf[index + 2] = 8; // Response size // val size
buf[index + 3] = device_id;
buf[index + 4] = power_flags; // bit 0: off, bit 1: no wait
index + 5
}
}
impl MailboxOps for PreparedMailbox {

View File

@ -10,6 +10,7 @@ pub mod gpio;
pub mod mailbox;
pub mod mini_uart;
pub mod pl011_uart;
pub mod power;
/// See BCM2835-ARM-Peripherals.pdf
/// See https://www.raspberrypi.org/forums/viewtopic.php?t=186090 for more details.

View File

@ -0,0 +1,127 @@
/*
* SPDX-License-Identifier: MIT OR BlueOak-1.0.0
* Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
* Original code distributed under MIT, additional changes are under BlueOak-1.0.0
*/
use {
super::{
gpio,
mailbox::{channel, Mailbox, MailboxOps},
BcmHost,
},
crate::arch::loop_delay,
core::ops,
register::mmio::*,
snafu::Snafu,
};
const POWER_BASE: usize = BcmHost::get_peripheral_address() + 0x0010_001C;
#[allow(non_snake_case)]
#[repr(C)]
pub struct RegisterBlock {
PM_RSTC: ReadWrite<u32>, // 0x1C
PM_RSTS: ReadWrite<u32>, // 0x20
PM_WDOG: ReadWrite<u32>, // 0x24
}
const PM_PASSWORD: u32 = 0x5a00_0000;
const PM_RSTC_WRCFG_CLR: u32 = 0xffff_ffcf;
const PM_RSTC_WRCFG_FULL_RESET: u32 = 0x0000_0020;
// The Raspberry Pi firmware uses the RSTS register to know which
// partition to boot from. The partition value is spread into bits 0, 2,
// 4, 6, 8, 10. Partition 63 is a special partition used by the
// firmware to indicate halt.
const PM_RSTS_RASPBERRYPI_HALT: u32 = 0x555;
const POWER_STATE_OFF: u32 = 0;
const POWER_STATE_ON: u32 = 1;
const POWER_STATE_DO_NOT_WAIT: u32 = 0;
const POWER_STATE_WAIT: u32 = 2;
#[derive(Debug, Snafu)]
pub enum PowerError {
#[snafu(display("Power setup failed in mailbox operation"))]
MailboxError,
}
pub type Result<T> = ::core::result::Result<T, PowerError>;
/// Public interface to the Power subsystem
pub struct Power;
impl ops::Deref for Power {
type Target = RegisterBlock;
fn deref(&self) -> &Self::Target {
unsafe { &*Self::ptr() }
}
}
impl Power {
pub fn new() -> Power {
Power
}
/// Returns a pointer to the register block
fn ptr() -> *const RegisterBlock {
POWER_BASE as *const _
}
/// Shutdown the board
pub fn off(&self, gpio: &gpio::GPIO) -> Result<()> {
// power off devices one by one
for dev_id in 0..16 {
let mut mbox = Mailbox::default();
let index = mbox.request();
let index =
mbox.set_device_power(index, dev_id, POWER_STATE_OFF | POWER_STATE_DO_NOT_WAIT);
let mbox = mbox.end(index);
mbox.call(channel::PropertyTagsArmToVc)
.map_err(|_| PowerError::MailboxError)?;
}
// power off gpio pins (but not VCC pins)
for bank in 0..5 {
gpio.FSEL[bank].set(0);
}
gpio.PUD.set(0);
loop_delay(150);
gpio.PUDCLK[0].set(0xffff_ffff);
gpio.PUDCLK[1].set(0xffff_ffff);
loop_delay(150);
// flush GPIO setup
gpio.PUDCLK[0].set(0);
gpio.PUDCLK[1].set(0);
// We set the watchdog hard reset bit here to distinguish this
// reset from the normal (full) reset. bootcode.bin will not
// reboot after a hard reset.
let mut val = self.PM_RSTS.get();
val |= PM_PASSWORD | PM_RSTS_RASPBERRYPI_HALT;
self.PM_RSTS.set(val);
// Continue with normal reset mechanism
self.reset();
}
/// Reboot
pub fn reset(&self) -> ! {
// use a timeout of 10 ticks (~150us)
self.PM_WDOG.set(PM_PASSWORD | 10);
let mut val = self.PM_RSTC.get();
val &= PM_RSTC_WRCFG_CLR;
val |= PM_PASSWORD | PM_RSTC_WRCFG_FULL_RESET;
self.PM_RSTC.set(val);
loop {}
}
}