i2c: improve i2c architecture, add sifive i2c driver
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
use device_api::{ResetDevice, device::Device};
|
||||
use yggdrasil_abi::{error::Error, primitive_enum};
|
||||
|
||||
const EXT_HSM: u64 = 0x48534D;
|
||||
@@ -6,6 +7,7 @@ const EXT_DBCN: u64 = 0x4442434E;
|
||||
const EXT_SPI: u64 = 0x735049;
|
||||
const EXT_SYSTEM_SHUTDOWN: u64 = 0x53525354;
|
||||
const EXT_SYSTEM_SHUTDOWN_LEGACY: u64 = 0x08;
|
||||
const EXT_SYSTEM_RESET: u64 = 0x53525354;
|
||||
|
||||
primitive_enum! {
|
||||
pub enum Status: i64 {
|
||||
@@ -52,6 +54,20 @@ impl From<i64> for SbiError {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SbiResetMethod;
|
||||
|
||||
impl Device for SbiResetMethod {
|
||||
fn display_name(&self) -> &str {
|
||||
"SBI reset"
|
||||
}
|
||||
}
|
||||
|
||||
impl ResetDevice for SbiResetMethod {
|
||||
unsafe fn reset(&self) -> ! {
|
||||
sbi_system_reset()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[inline(always)]
|
||||
unsafe fn sbi_do_call(
|
||||
@@ -107,6 +123,11 @@ pub fn sbi_set_timer(next_event: u64) {
|
||||
unsafe { sbi_do_call(EXT_TIME, 0x00, next_event, 0, 0, 0, 0, 0) }.ok();
|
||||
}
|
||||
|
||||
pub fn sbi_system_reset() -> ! {
|
||||
unsafe { sbi_do_call(EXT_SYSTEM_RESET, 0x00, 0x01, 0x00, 0, 0, 0, 0) }.ok();
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
pub fn sbi_system_shutdown() -> ! {
|
||||
unsafe { sbi_do_call(EXT_SYSTEM_SHUTDOWN, 0x00, 0, 0, 0, 0, 0, 0) }.ok();
|
||||
unsafe { sbi_do_call(EXT_SYSTEM_SHUTDOWN_LEGACY, 0x00, 0, 0, 0, 0, 0, 0) }.ok();
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
use alloc::sync::Arc;
|
||||
use alloc::{boxed::Box, sync::Arc};
|
||||
use async_trait::async_trait;
|
||||
use device_api::{
|
||||
clock::{ClockHandle, Hertz},
|
||||
device::{Device, DeviceInitContext},
|
||||
i2c::{I2CAddress, I2CController},
|
||||
i2c::{I2CAddress, I2CController, I2CMessage, I2CTransfer},
|
||||
interrupt::{InterruptHandler, IrqHandle, IrqVector},
|
||||
};
|
||||
use device_tree::driver::{Node, ProbeContext, device_tree_driver};
|
||||
use futures_util::task::AtomicWaker;
|
||||
use libk::{device::manager::DEVICE_REGISTRY, error::Error};
|
||||
use libk_mm::device::DeviceMemoryIo;
|
||||
use libk_util::{OneTimeInit, sync::spin_rwlock::IrqSafeRwLock};
|
||||
use libk_util::{OneTimeInit, event::BitmapEvent, sync::spin_rwlock::IrqSafeRwLock};
|
||||
use tock_registers::{
|
||||
LocalRegisterCopy,
|
||||
fields::FieldValue,
|
||||
interfaces::{ReadWriteable, Readable, Writeable},
|
||||
register_bitfields, register_structs,
|
||||
registers::ReadWrite,
|
||||
@@ -56,6 +60,10 @@ register_structs! {
|
||||
}
|
||||
}
|
||||
|
||||
struct I2CState {
|
||||
status: BitmapEvent<AtomicWaker>,
|
||||
}
|
||||
|
||||
struct I2C {
|
||||
name: &'static str,
|
||||
clock_frequency: Option<Hertz>,
|
||||
@@ -63,87 +71,22 @@ struct I2C {
|
||||
clock: Option<ClockHandle>,
|
||||
regs: IrqSafeRwLock<DeviceMemoryIo<'static, Regs>>,
|
||||
index: OneTimeInit<u32>,
|
||||
state: I2CState,
|
||||
}
|
||||
|
||||
impl Regs {
|
||||
fn start_transfer(&self, name: &str, buflen: u16, address: I2CAddress, read: bool) {
|
||||
log::debug!(
|
||||
"{}: start address={}, read={}, len={}",
|
||||
name,
|
||||
address,
|
||||
read,
|
||||
buflen
|
||||
);
|
||||
|
||||
let address = address.as_8_bit().unwrap();
|
||||
let read = if read { C::READ::SET } else { C::READ::CLEAR };
|
||||
|
||||
self.S.write(S::ERR::SET + S::DONE::SET);
|
||||
self.DLEN.set(buflen as u32);
|
||||
self.C.modify(C::ST::CLEAR + C::I2CEN::CLEAR);
|
||||
self.A.set(address as u32);
|
||||
self.C.modify(read + C::ST::SET + C::I2CEN::SET);
|
||||
}
|
||||
|
||||
fn finish_transfer(&self, name: &str) -> Result<(), Error> {
|
||||
log::debug!("{}: finish transfer", name);
|
||||
let status = self.S.extract();
|
||||
self.C.set(0);
|
||||
if status.matches_all(S::ERR::SET) {
|
||||
self.S.write(S::DONE::SET + S::ERR::SET);
|
||||
return Err(Error::HostUnreachable);
|
||||
}
|
||||
if status.matches_all(S::DONE::SET) {
|
||||
self.S.write(S::DONE::SET);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_byte(&self, byte: u8) -> Result<bool, Error> {
|
||||
loop {
|
||||
let status = self.S.extract();
|
||||
if status.matches_all(S::ERR::SET) {
|
||||
self.C.write(C::CLEAR.val(1));
|
||||
self.S.write(S::ERR::SET + S::DONE::SET);
|
||||
// TODO better code
|
||||
return Err(Error::HostUnreachable);
|
||||
}
|
||||
if status.matches_all(S::DONE::SET) {
|
||||
self.C.set(0);
|
||||
self.S.write(S::DONE::SET);
|
||||
return Ok(false);
|
||||
}
|
||||
if status.matches_all(S::TXD::SET) {
|
||||
self.FIFO.set(byte as u32);
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
core::hint::spin_loop();
|
||||
impl I2CState {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
status: BitmapEvent::new(AtomicWaker::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_byte(&self) -> Result<Option<u8>, Error> {
|
||||
loop {
|
||||
let status = self.S.extract();
|
||||
if status.matches_all(S::ERR::SET) {
|
||||
self.C.write(C::CLEAR.val(1));
|
||||
self.S.write(S::ERR::SET + S::DONE::SET);
|
||||
// TODO better code
|
||||
return Err(Error::HostUnreachable);
|
||||
}
|
||||
if status.matches_all(S::DONE::SET) {
|
||||
self.C.set(0);
|
||||
self.S.write(S::DONE::SET);
|
||||
return Ok(None);
|
||||
}
|
||||
if status.matches_all(S::RXD::SET) {
|
||||
let val = self.FIFO.get() as u8;
|
||||
return Ok(Some(val));
|
||||
}
|
||||
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
// All of these are thread-unsafe, but I2C subsystem locks the peripheral by process
|
||||
impl I2C {
|
||||
async fn wait_for_event(&self) -> LocalRegisterCopy<u32, S::Register> {
|
||||
let event = self.state.status.wait().await;
|
||||
LocalRegisterCopy::new(event as u32)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,16 +101,13 @@ impl Device for I2C {
|
||||
|
||||
let index = DEVICE_REGISTRY.i2c.register_bus(self.clone())?;
|
||||
self.index.init(index);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn init_irq(self: Arc<Self>) -> Result<(), Error> {
|
||||
// TODO
|
||||
let _ = &self.irq;
|
||||
// self.irq.register(self.clone())?;
|
||||
// self.irq.enable()?;
|
||||
// let regs = self.regs.write();
|
||||
// regs.C.modify(C::INTD::SET);
|
||||
self.irq.register(self.clone())?;
|
||||
self.irq.enable()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -178,10 +118,26 @@ impl Device for I2C {
|
||||
|
||||
impl InterruptHandler for I2C {
|
||||
fn handle_irq(self: Arc<Self>, _vector: IrqVector) -> bool {
|
||||
todo!()
|
||||
let regs = self.regs.write();
|
||||
let event = regs.S.extract();
|
||||
// log::info!("I2C irq {:#x}", event.get());
|
||||
let mut clear = FieldValue::none();
|
||||
if event.matches_all(S::RXR::SET) {
|
||||
clear += C::INTR::CLEAR;
|
||||
}
|
||||
if event.matches_all(S::TXW::SET) {
|
||||
clear += C::INTT::CLEAR;
|
||||
}
|
||||
if event.matches_all(S::DONE::SET) {
|
||||
clear += C::INTD::CLEAR;
|
||||
}
|
||||
regs.C.modify(clear);
|
||||
self.state.status.signal(event.get() as u64);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl I2CController for I2C {
|
||||
fn bus_number(&self) -> u32 {
|
||||
*self.index.get()
|
||||
@@ -217,47 +173,98 @@ impl I2CController for I2C {
|
||||
}
|
||||
}
|
||||
|
||||
fn i2c_write(&self, address: I2CAddress, buffer: &[u8]) -> Result<usize, Error> {
|
||||
let buflen: u16 = buffer
|
||||
.len()
|
||||
.try_into()
|
||||
.map_err(|_| Error::InvalidArgument)?;
|
||||
let regs = self.regs.write();
|
||||
// TODO DMA/interrupts
|
||||
regs.start_transfer(self.name, buflen, address, false);
|
||||
let mut bytes_written = 0;
|
||||
// let mut done = false;
|
||||
for &byte in buffer {
|
||||
if regs.write_byte(byte)? {
|
||||
bytes_written += 1;
|
||||
} else {
|
||||
break;
|
||||
async fn i2c_transfer(
|
||||
&self,
|
||||
address: I2CAddress,
|
||||
transfer: &mut I2CTransfer,
|
||||
) -> Result<usize, Error> {
|
||||
// TODO validate transfer
|
||||
let address = address.as_8_bit().ok_or(Error::NotImplemented)?;
|
||||
let repeat_start = transfer.repeat_start;
|
||||
let mut nbytes = 0;
|
||||
let mut error = None;
|
||||
for (i, message) in transfer.messages.iter_mut().enumerate() {
|
||||
if i == 0 || repeat_start {
|
||||
let (read, len) = match message {
|
||||
I2CMessage::Read(buffer) => (true, buffer.len()),
|
||||
I2CMessage::Write(buffer) => (false, buffer.len()),
|
||||
};
|
||||
// log::info!("{}: START {:#x} read={}", self.name, address, read);
|
||||
let read = if read { C::READ::SET } else { C::READ::CLEAR };
|
||||
let regs = self.regs.write();
|
||||
regs.S.write(S::ERR::SET + S::DONE::SET);
|
||||
regs.DLEN.set(len as u32);
|
||||
regs.C.modify(C::ST::CLEAR);
|
||||
regs.A.set(address as u32);
|
||||
regs.C.modify(
|
||||
read + C::ST::SET + C::I2CEN::SET + C::INTD::SET + C::INTR::SET + C::INTT::SET,
|
||||
);
|
||||
}
|
||||
}
|
||||
regs.finish_transfer(self.name)?;
|
||||
Ok(bytes_written)
|
||||
}
|
||||
|
||||
fn i2c_read(&self, address: I2CAddress, buffer: &mut [u8]) -> Result<usize, Error> {
|
||||
let buflen: u16 = buffer
|
||||
.len()
|
||||
.try_into()
|
||||
.map_err(|_| Error::InvalidArgument)?;
|
||||
let regs = self.regs.write();
|
||||
// TODO DMA/interrupts
|
||||
regs.start_transfer(self.name, buflen, address, true);
|
||||
let mut bytes_read = 0;
|
||||
// let mut done = false;
|
||||
for byte in buffer {
|
||||
if let Some(val) = regs.read_byte()? {
|
||||
*byte = val;
|
||||
bytes_read += 1;
|
||||
} else {
|
||||
let mut pos = 0;
|
||||
let status = loop {
|
||||
let s = self.wait_for_event().await;
|
||||
let regs = self.regs.write();
|
||||
if s.matches_all(S::ERR::SET) || s.matches_all(S::DONE::SET) {
|
||||
break s;
|
||||
}
|
||||
match message {
|
||||
I2CMessage::Write(buffer) => {
|
||||
if s.matches_all(S::TXW::SET) {
|
||||
while regs.S.matches_all(S::TXD::SET) && pos < buffer.len() {
|
||||
let byte = buffer[pos];
|
||||
// log::info!("{}: tx {:#x}", self.name, byte);
|
||||
regs.FIFO.set(byte as u32);
|
||||
pos += 1;
|
||||
nbytes += 1;
|
||||
}
|
||||
}
|
||||
regs.C.modify(C::INTT::SET);
|
||||
}
|
||||
I2CMessage::Read(buffer) => {
|
||||
if s.matches_all(S::RXR::SET) {
|
||||
while regs.S.matches_all(S::RXD::SET) && pos < buffer.len() {
|
||||
let byte = regs.FIFO.get() as u8;
|
||||
// log::info!("{}: rx {:#x}", self.name, byte);
|
||||
buffer[pos] = byte;
|
||||
pos += 1;
|
||||
nbytes += 1;
|
||||
}
|
||||
}
|
||||
regs.C.modify(C::INTR::SET);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Finalize message
|
||||
let regs = self.regs.write();
|
||||
if status.matches_all(S::ERR::SET) {
|
||||
regs.S.write(S::DONE::SET + S::ERR::SET);
|
||||
error = Some(Error::HostUnreachable);
|
||||
break;
|
||||
} else if status.matches_all(S::DONE::SET) {
|
||||
regs.S.write(S::DONE::SET);
|
||||
} else {
|
||||
log::error!("{}: transfer finished without DONE or ERR set", self.name);
|
||||
log::error!("{}: S = {:#x}", self.name, status.get());
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
regs.finish_transfer(self.name)?;
|
||||
Ok(bytes_read)
|
||||
|
||||
// Finish transfer
|
||||
// log::info!(
|
||||
// "{}: finish xfer, error={:?}, nbytes={}",
|
||||
// self.name,
|
||||
// error,
|
||||
// nbytes
|
||||
// );
|
||||
let regs = self.regs.write();
|
||||
regs.C.set(0);
|
||||
|
||||
match error {
|
||||
Some(error) => Err(error),
|
||||
None => Ok(nbytes),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,7 +286,8 @@ device_tree_driver! {
|
||||
irq,
|
||||
clock,
|
||||
index: OneTimeInit::new(),
|
||||
regs: IrqSafeRwLock::new(regs)
|
||||
regs: IrqSafeRwLock::new(regs),
|
||||
state: I2CState::new()
|
||||
});
|
||||
|
||||
node.make_i2c_controller(i2c.clone());
|
||||
|
||||
@@ -17,3 +17,4 @@ tock-registers.workspace = true
|
||||
log.workspace = true
|
||||
bytemuck.workspace = true
|
||||
futures-util.workspace = true
|
||||
async-trait.workspace = true
|
||||
|
||||
@@ -0,0 +1,523 @@
|
||||
use alloc::{boxed::Box, sync::Arc};
|
||||
use async_trait::async_trait;
|
||||
use device_api::{
|
||||
clock::{ClockHandle, Hertz, IntoHertz},
|
||||
device::{Device, DeviceInitContext},
|
||||
i2c::{I2CAddress, I2CController, I2CMessage, I2CTransfer},
|
||||
interrupt::{InterruptHandler, IrqHandle, IrqVector},
|
||||
};
|
||||
use device_tree::driver::{Node, ProbeContext, device_tree_driver};
|
||||
use futures_util::task::AtomicWaker;
|
||||
use libk::{block, device::manager::DEVICE_REGISTRY, error::Error};
|
||||
use libk_mm::device::DeviceMemoryIo;
|
||||
use libk_util::{OneTimeInit, event::BitmapEvent, sync::IrqSafeSpinlock};
|
||||
use tock_registers::{
|
||||
LocalRegisterCopy,
|
||||
interfaces::{ReadWriteable, Readable, Writeable},
|
||||
register_bitfields, register_structs,
|
||||
registers::{Aliased, ReadWrite},
|
||||
};
|
||||
use yggdrasil_abi::io::device::i2c::I2CMasterInfo;
|
||||
|
||||
const CMD_START: u32 = 0x91;
|
||||
const CMD_STOP: u32 = 0x41;
|
||||
const CMD_WRITE: u32 = 0x11;
|
||||
const CMD_READ_ACK: u32 = 0x21;
|
||||
const CMD_READ_NACK: u32 = 0x29;
|
||||
const CMD_IACK: u32 = 0x01;
|
||||
|
||||
register_bitfields! {
|
||||
u32,
|
||||
CTR [
|
||||
EN OFFSET(7) NUMBITS(1) [],
|
||||
IEN OFFSET(6) NUMBITS(1) [],
|
||||
],
|
||||
SR [
|
||||
RXACK OFFSET(7) NUMBITS(1) [],
|
||||
BUSY OFFSET(6) NUMBITS(1) [],
|
||||
AL OFFSET(5) NUMBITS(1) [],
|
||||
TIP OFFSET(1) NUMBITS(1) [],
|
||||
IF OFFSET(0) NUMBITS(1) [],
|
||||
],
|
||||
}
|
||||
|
||||
register_structs! {
|
||||
#[allow(non_snake_case)]
|
||||
Regs {
|
||||
(0x000 => PRER_LO: ReadWrite<u32>),
|
||||
(0x004 => PRER_HI: ReadWrite<u32>),
|
||||
(0x008 => CTR: ReadWrite<u32, CTR::Register>),
|
||||
(0x00C => TXR_RXR: ReadWrite<u32>),
|
||||
(0x010 => CR_SR: Aliased<u32, SR::Register, ()>),
|
||||
(0x014 => _0),
|
||||
(0x020 => @END),
|
||||
}
|
||||
}
|
||||
|
||||
struct AbortGuard<'a> {
|
||||
i2c: &'a I2C,
|
||||
abort: bool,
|
||||
}
|
||||
|
||||
impl<'a> AbortGuard<'a> {
|
||||
pub fn new(i2c: &'a I2C) -> Self {
|
||||
Self { i2c, abort: true }
|
||||
}
|
||||
|
||||
pub fn consume(mut self) {
|
||||
self.abort = false;
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AbortGuard<'_> {
|
||||
fn drop(&mut self) {
|
||||
if self.abort {
|
||||
block!(self.i2c.finish_transfer(Err(Error::Interrupted)).await).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct I2C {
|
||||
name: &'static str,
|
||||
clock: ClockHandle,
|
||||
irq: IrqHandle,
|
||||
regs: IrqSafeSpinlock<DeviceMemoryIo<'static, Regs>>,
|
||||
index: OneTimeInit<u32>,
|
||||
sr: BitmapEvent<AtomicWaker>,
|
||||
}
|
||||
|
||||
impl Regs {
|
||||
fn set_clock_rate(&self, input_rate: Hertz, clock_rate: Hertz) -> Result<Hertz, Error> {
|
||||
let divider = Hertz::divider(input_rate / 5, clock_rate).ok_or(Error::InvalidArgument)?;
|
||||
if divider == 0 || divider > 0xFFFF {
|
||||
return Err(Error::InvalidArgument);
|
||||
}
|
||||
log::info!("input_rate = {input_rate}, desired = {clock_rate}, divider = {divider}");
|
||||
let divider = divider - 1;
|
||||
self.PRER_LO.set(divider & 0xFF);
|
||||
self.PRER_HI.set(divider >> 8);
|
||||
let real_rate = input_rate / (5 * (divider + 1));
|
||||
Ok(real_rate)
|
||||
}
|
||||
}
|
||||
|
||||
impl I2C {
|
||||
async fn wait_for_event(&self) -> LocalRegisterCopy<u32, SR::Register> {
|
||||
let value = self.sr.wait().await;
|
||||
LocalRegisterCopy::new(value as u32)
|
||||
}
|
||||
|
||||
async fn finish_transfer(&self, status: Result<usize, Error>) -> Result<usize, Error> {
|
||||
if status == Err(Error::Interrupted) {
|
||||
log::warn!("{}: transfer aborted", self.name);
|
||||
|
||||
let regs = self.regs.lock();
|
||||
let stopped = {
|
||||
if regs.CR_SR.matches_all(SR::TIP::CLEAR) {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if !stopped {
|
||||
regs.CTR.modify(CTR::IEN::SET);
|
||||
drop(regs);
|
||||
|
||||
loop {
|
||||
let sr = self.wait_for_event().await;
|
||||
if sr.matches_all(SR::TIP::CLEAR) {
|
||||
break;
|
||||
}
|
||||
let regs = self.regs.lock();
|
||||
regs.CTR.modify(CTR::IEN::SET); // Reenable IRQs
|
||||
}
|
||||
}
|
||||
}
|
||||
// Generate stop condition
|
||||
{
|
||||
let regs = self.regs.lock();
|
||||
regs.CR_SR.set(CMD_STOP);
|
||||
regs.CTR.modify(CTR::IEN::SET); // Reenable IRQs
|
||||
}
|
||||
|
||||
loop {
|
||||
let sr = self.wait_for_event().await;
|
||||
if sr.matches_all(SR::BUSY::CLEAR) {
|
||||
break;
|
||||
}
|
||||
let regs = self.regs.lock();
|
||||
regs.CTR.modify(CTR::IEN::SET); // Reenable IRQs
|
||||
}
|
||||
|
||||
let regs = self.regs.lock();
|
||||
regs.CTR.modify(CTR::IEN::CLEAR); // Mask IRQs
|
||||
regs.CR_SR.set(CMD_IACK);
|
||||
|
||||
match status {
|
||||
Ok(count) => Ok(count),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Device for I2C {
|
||||
unsafe fn init(self: Arc<Self>, _cx: DeviceInitContext) -> Result<(), Error> {
|
||||
let input_clk = self.clock.rate()?;
|
||||
let regs = self.regs.lock();
|
||||
regs.CTR.set(0);
|
||||
regs.set_clock_rate(input_clk, 100u64.khz())?;
|
||||
regs.CTR.write(CTR::EN::SET);
|
||||
regs.CR_SR.set(CMD_IACK);
|
||||
let index = DEVICE_REGISTRY.i2c.register_bus(self.clone())?;
|
||||
self.index.init(index);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn init_irq(self: Arc<Self>) -> Result<(), Error> {
|
||||
self.irq.register(self.clone())?;
|
||||
self.irq.enable()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn display_name(&self) -> &str {
|
||||
self.name
|
||||
}
|
||||
}
|
||||
|
||||
impl InterruptHandler for I2C {
|
||||
fn handle_irq(self: Arc<Self>, _vector: IrqVector) -> bool {
|
||||
let regs = self.regs.lock();
|
||||
let status = regs.CR_SR.extract();
|
||||
self.sr.signal(status.get() as u64);
|
||||
regs.CTR.modify(CTR::IEN::CLEAR);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl I2CController for I2C {
|
||||
fn bus_number(&self) -> u32 {
|
||||
*self.index.get()
|
||||
}
|
||||
|
||||
fn child_number(&self) -> Option<u32> {
|
||||
None
|
||||
}
|
||||
|
||||
fn capabilities(&self) -> I2CMasterInfo {
|
||||
let scl_max = self.clock.rate().expect("Couldn't get input clock rate") / 5;
|
||||
I2CMasterInfo {
|
||||
max_speed_hz: scl_max.0 as u32,
|
||||
supports_10bit_addresses: true,
|
||||
}
|
||||
}
|
||||
|
||||
async fn i2c_transfer(
|
||||
&self,
|
||||
address: I2CAddress,
|
||||
transfer: &mut I2CTransfer,
|
||||
) -> Result<usize, Error> {
|
||||
// TODO validate transfer
|
||||
let address = address.as_8_bit().ok_or(Error::NotImplemented)?;
|
||||
let repeat_start = transfer.repeat_start;
|
||||
let mut nbytes = 0;
|
||||
let mut error = None;
|
||||
let mut abort = None;
|
||||
for (i, message) in transfer.messages.iter_mut().enumerate() {
|
||||
let (read, buffer_len) = match message {
|
||||
I2CMessage::Read(buffer) => (true, buffer.len()),
|
||||
I2CMessage::Write(buffer) => (false, buffer.len()),
|
||||
};
|
||||
|
||||
if i == 0 || repeat_start {
|
||||
//log::info!("{}: START {:#x} read={}", self.name, address, read);
|
||||
let read_bit = if read { 1 << 0 } else { 0 << 0 };
|
||||
let regs = self.regs.lock();
|
||||
regs.TXR_RXR.set(((address as u32) << 1) | read_bit);
|
||||
regs.CR_SR.set(CMD_START);
|
||||
regs.CTR.modify(CTR::IEN::SET);
|
||||
if abort.is_none() {
|
||||
abort = Some(AbortGuard::new(self));
|
||||
}
|
||||
}
|
||||
|
||||
let mut pos = 0;
|
||||
let mut start = true;
|
||||
loop {
|
||||
if pos == buffer_len {
|
||||
break;
|
||||
}
|
||||
let sr = self.wait_for_event().await;
|
||||
let regs = self.regs.lock();
|
||||
if sr.matches_all(SR::AL::SET) {
|
||||
error = Some(Error::HostUnreachable);
|
||||
break;
|
||||
}
|
||||
|
||||
match message {
|
||||
I2CMessage::Read(buffer) => {
|
||||
if pos == 0 && sr.matches_all(SR::RXACK::SET) {
|
||||
error = Some(Error::HostUnreachable);
|
||||
break;
|
||||
}
|
||||
if start {
|
||||
regs.CR_SR.set(CMD_READ_ACK);
|
||||
start = false;
|
||||
} else if sr.matches_all(SR::TIP::CLEAR) {
|
||||
let byte = regs.TXR_RXR.get() as u8;
|
||||
//log::info!("{}: rx {:#x}", self.name, byte);
|
||||
buffer[pos] = byte;
|
||||
pos += 1;
|
||||
nbytes += 1;
|
||||
if pos >= buffer.len() {
|
||||
regs.CR_SR.set(CMD_READ_NACK);
|
||||
} else {
|
||||
regs.CR_SR.set(CMD_READ_ACK);
|
||||
}
|
||||
}
|
||||
}
|
||||
I2CMessage::Write(buffer) => {
|
||||
if sr.matches_all(SR::RXACK::SET) {
|
||||
error = Some(Error::HostUnreachable);
|
||||
break;
|
||||
}
|
||||
regs.TXR_RXR.set(buffer[pos] as u32);
|
||||
//log::info!("{}: tx {:#x}", self.name, buffer[pos]);
|
||||
pos += 1;
|
||||
nbytes += 1;
|
||||
regs.CR_SR.set(CMD_WRITE);
|
||||
}
|
||||
}
|
||||
regs.CTR.modify(CTR::IEN::SET);
|
||||
}
|
||||
|
||||
if error.is_some() {
|
||||
break;
|
||||
}
|
||||
|
||||
{
|
||||
let regs = self.regs.lock();
|
||||
if regs.CR_SR.matches_all(SR::TIP::CLEAR) {
|
||||
regs.CTR.modify(CTR::IEN::SET); // Reenable IRQs
|
||||
continue;
|
||||
}
|
||||
|
||||
regs.CTR.modify(CTR::IEN::SET); // Reenable IRQs
|
||||
}
|
||||
|
||||
loop {
|
||||
let sr = self.wait_for_event().await;
|
||||
if sr.matches_all(SR::TIP::CLEAR) {
|
||||
break;
|
||||
}
|
||||
let regs = self.regs.lock();
|
||||
regs.CTR.modify(CTR::IEN::SET); // Reenable IRQs
|
||||
}
|
||||
}
|
||||
|
||||
// Generate stop condition
|
||||
{
|
||||
//log::info!("{}: STOP", self.name);
|
||||
let regs = self.regs.lock();
|
||||
regs.CR_SR.set(CMD_STOP);
|
||||
regs.CTR.modify(CTR::IEN::SET); // Reenable IRQs
|
||||
}
|
||||
|
||||
loop {
|
||||
let sr = self.wait_for_event().await;
|
||||
if sr.matches_all(SR::BUSY::CLEAR) {
|
||||
break;
|
||||
}
|
||||
let regs = self.regs.lock();
|
||||
regs.CTR.modify(CTR::IEN::SET); // Reenable IRQs
|
||||
}
|
||||
|
||||
if let Some(abort) = abort.take() {
|
||||
abort.consume();
|
||||
}
|
||||
|
||||
let regs = self.regs.lock();
|
||||
regs.CTR.modify(CTR::IEN::CLEAR); // Mask IRQs
|
||||
regs.CR_SR.set(CMD_IACK);
|
||||
|
||||
// log::info!(
|
||||
// "{}: finish xfer, error={:?}, nbytes={}",
|
||||
// self.name,
|
||||
// error,
|
||||
// nbytes
|
||||
// );
|
||||
match error {
|
||||
Some(err) => Err(err),
|
||||
None => Ok(nbytes),
|
||||
}
|
||||
}
|
||||
// async fn i2c_read(&self, address: I2CAddress, buffer: &mut [u8]) -> Result<usize, Error> {
|
||||
// self.start_transfer(address, true)?;
|
||||
// let mut pos = 0;
|
||||
// let status = loop {
|
||||
// if pos >= buffer.len() {
|
||||
// break Ok(pos);
|
||||
// }
|
||||
// let sr = self.wait_for_event().await;
|
||||
// let regs = self.regs.lock();
|
||||
// if sr.matches_all(SR::RXACK::SET) || sr.matches_all(SR::AL::SET) {
|
||||
// break Err(sr);
|
||||
// }
|
||||
// };
|
||||
// self.finish_transfer(status).await
|
||||
// }
|
||||
|
||||
// async fn i2c_write(&self, address: I2CAddress, buffer: &[u8]) -> Result<usize, Error> {
|
||||
// self.start_transfer(address, false)?;
|
||||
// let mut pos = 0;
|
||||
// let status = loop {
|
||||
// if pos >= buffer.len() {
|
||||
// break Ok(pos);
|
||||
// }
|
||||
// let sr = self.wait_for_event().await;
|
||||
// let regs = self.regs.lock();
|
||||
// if sr.matches_all(SR::RXACK::SET) || sr.matches_all(SR::AL::SET) {
|
||||
// break Err(sr);
|
||||
// }
|
||||
// };
|
||||
// self.finish_transfer(status).await
|
||||
// }
|
||||
|
||||
// fn i2c_read(&self, address: I2CAddress, buffer: &mut [u8]) -> Result<usize, Error> {
|
||||
// let address = address.as_8_bit().ok_or(Error::NotImplemented)?;
|
||||
// let regs = self.regs.lock();
|
||||
// log::info!(":::: start {:#x} read", address);
|
||||
// regs.poll_wait(SR::BUSY::SET);
|
||||
// regs.TXR_RXR.set(((address as u32) << 1) | 1);
|
||||
// regs.CR_SR.set(CMD_START);
|
||||
|
||||
// let mut read = 0;
|
||||
// let mut error = None;
|
||||
// let mut start = true;
|
||||
// loop {
|
||||
// let sr = regs.poll_wait(SR::TIP::SET);
|
||||
// if read == buffer.len() {
|
||||
// break;
|
||||
// }
|
||||
// if sr.matches_all(SR::AL::SET) {
|
||||
// error = Some(Error::WouldBlock);
|
||||
// break;
|
||||
// }
|
||||
// if sr.matches_all(SR::RXACK::SET) {
|
||||
// error = Some(Error::DoesNotExist);
|
||||
// break;
|
||||
// }
|
||||
|
||||
// if !start {
|
||||
// let val = regs.TXR_RXR.get() as u8;
|
||||
// buffer[read] = val;
|
||||
// read += 1;
|
||||
// if read >= buffer.len() {
|
||||
// log::info!(":::: read_ack {:02x}", val);
|
||||
// regs.CR_SR.set(CMD_READ_NACK);
|
||||
// } else {
|
||||
// log::info!(":::: read {:02x}", val);
|
||||
// regs.CR_SR.set(CMD_READ_ACK);
|
||||
// }
|
||||
// } else {
|
||||
// log::info!(":::: begin read");
|
||||
// regs.CR_SR.set(CMD_READ);
|
||||
// start = false;
|
||||
// }
|
||||
// }
|
||||
|
||||
// log::info!(":::: stop {:#x}", address);
|
||||
// // Generate stop condition
|
||||
// regs.CR_SR.set(CMD_STOP);
|
||||
// regs.poll_wait(SR::BUSY::SET);
|
||||
// regs.CR_SR.set(CMD_IACK);
|
||||
|
||||
// log::info!(":::: xfer finish {error:?}, read {read}");
|
||||
// if let Some(error) = error {
|
||||
// Err(error)
|
||||
// } else {
|
||||
// Ok(read)
|
||||
// }
|
||||
// }
|
||||
|
||||
// fn i2c_write(&self, address: I2CAddress, buffer: &[u8]) -> Result<usize, Error> {
|
||||
// let address = address.as_8_bit().ok_or(Error::NotImplemented)?;
|
||||
// let regs = self.regs.lock();
|
||||
// log::info!(":::: start {:#x} write", address);
|
||||
// regs.poll_wait(SR::BUSY::SET);
|
||||
// regs.TXR_RXR.set((address as u32) << 1);
|
||||
// regs.CR_SR.set(CMD_START);
|
||||
|
||||
// let mut sent = 0;
|
||||
// let mut error = None;
|
||||
// loop {
|
||||
// let sr = regs.poll_wait(SR::TIP::SET);
|
||||
// if sent == buffer.len() {
|
||||
// // Generate stop condition
|
||||
// regs.CR_SR.set(CMD_STOP);
|
||||
// break;
|
||||
// }
|
||||
// if sr.matches_all(SR::AL::SET) {
|
||||
// regs.CR_SR.set(CMD_STOP);
|
||||
// error = Some(Error::WouldBlock);
|
||||
// break;
|
||||
// }
|
||||
// if sr.matches_all(SR::RXACK::SET) {
|
||||
// // Generate stop condition
|
||||
// regs.CR_SR.set(CMD_STOP);
|
||||
// error = Some(Error::DoesNotExist);
|
||||
// break;
|
||||
// }
|
||||
|
||||
// log::info!(":::: send {:#x}", buffer[sent]);
|
||||
// regs.TXR_RXR.set(buffer[sent] as u32);
|
||||
// regs.CR_SR.set(CMD_WRITE);
|
||||
// sent += 1;
|
||||
// }
|
||||
|
||||
// log::info!(":::: stop {:#x}", address);
|
||||
// regs.poll_wait(SR::BUSY::SET);
|
||||
// regs.CR_SR.set(CMD_IACK);
|
||||
|
||||
// log::info!(":::: xfer finish {error:?}, sent {sent}");
|
||||
// if let Some(error) = error {
|
||||
// Err(error)
|
||||
// } else {
|
||||
// Ok(sent)
|
||||
// }
|
||||
// }
|
||||
|
||||
fn set_speed(&self, speed: Hertz) -> Result<Hertz, Error> {
|
||||
let input_rate = self.clock.rate()?;
|
||||
let regs = self.regs.lock();
|
||||
regs.set_clock_rate(input_rate, speed)
|
||||
}
|
||||
}
|
||||
|
||||
device_tree_driver! {
|
||||
compatible: ["sifive,i2c0"],
|
||||
driver: {
|
||||
fn probe(&self, node: &Arc<Node>, context: &mut ProbeContext) -> Option<Arc<dyn Device>> {
|
||||
let name = node.name().unwrap_or("i2c");
|
||||
let base = node.map_base(context, 0)?;
|
||||
let clock = node.clock(0)?;
|
||||
let irq = node.interrupt(0)?;
|
||||
|
||||
let regs = unsafe { DeviceMemoryIo::map(base, Default::default()) }.ok()?;
|
||||
|
||||
let i2c = Arc::new(I2C {
|
||||
name,
|
||||
regs: IrqSafeSpinlock::new(regs),
|
||||
clock,
|
||||
irq,
|
||||
index: OneTimeInit::new(),
|
||||
sr: BitmapEvent::new(AtomicWaker::new()),
|
||||
});
|
||||
|
||||
node.make_i2c_controller(i2c.clone());
|
||||
|
||||
Some(i2c)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ extern crate alloc;
|
||||
mod clock_fu540;
|
||||
mod clock_fu740;
|
||||
mod ethernet;
|
||||
mod i2c;
|
||||
mod pll;
|
||||
mod pwm;
|
||||
mod uart;
|
||||
|
||||
@@ -4,7 +4,8 @@ use core::{
|
||||
sync::atomic::{AtomicU16, AtomicU32, Ordering},
|
||||
};
|
||||
|
||||
use alloc::sync::Arc;
|
||||
use alloc::{boxed::Box, sync::Arc};
|
||||
use async_trait::async_trait;
|
||||
use yggdrasil_abi::{error::Error, io::device::i2c::I2CMasterInfo, process::ProcessId};
|
||||
|
||||
use crate::{
|
||||
@@ -20,6 +21,17 @@ pub struct I2CDevice {
|
||||
user: AtomicU32,
|
||||
}
|
||||
|
||||
pub enum I2CMessage<'a> {
|
||||
Read(&'a mut [u8]),
|
||||
Write(&'a [u8]),
|
||||
}
|
||||
|
||||
pub struct I2CTransfer<'a> {
|
||||
pub repeat_start: bool,
|
||||
pub messages: &'a mut [I2CMessage<'a>],
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait I2CController: Device {
|
||||
fn bus_number(&self) -> u32;
|
||||
fn child_number(&self) -> Option<u32>;
|
||||
@@ -27,8 +39,13 @@ pub trait I2CController: Device {
|
||||
fn set_speed(&self, speed: Hertz) -> Result<Hertz, Error>;
|
||||
fn capabilities(&self) -> I2CMasterInfo;
|
||||
|
||||
fn i2c_write(&self, address: I2CAddress, buffer: &[u8]) -> Result<usize, Error>;
|
||||
fn i2c_read(&self, address: I2CAddress, buffer: &mut [u8]) -> Result<usize, Error>;
|
||||
async fn i2c_transfer(
|
||||
&self,
|
||||
address: I2CAddress,
|
||||
transfer: &mut I2CTransfer,
|
||||
) -> Result<usize, Error>;
|
||||
// async fn i2c_write(&self, address: I2CAddress, buffer: &[u8]) -> Result<usize, Error>;
|
||||
// async fn i2c_read(&self, address: I2CAddress, buffer: &mut [u8]) -> Result<usize, Error>;
|
||||
}
|
||||
|
||||
impl I2CAddress {
|
||||
|
||||
@@ -4,7 +4,7 @@ use alloc::boxed::Box;
|
||||
use async_trait::async_trait;
|
||||
use device_api::{
|
||||
clock::Hertz,
|
||||
i2c::{I2CAddress, I2CDevice},
|
||||
i2c::{I2CAddress, I2CDevice, I2CMessage, I2CTransfer},
|
||||
};
|
||||
use yggdrasil_abi::{
|
||||
error::Error,
|
||||
@@ -13,26 +13,42 @@ use yggdrasil_abi::{
|
||||
process::ProcessId,
|
||||
};
|
||||
|
||||
use crate::{device::char::CharDevice, task::thread::Thread, vfs::FileReadiness};
|
||||
use crate::{
|
||||
device::char::CharDevice,
|
||||
task::{mem::ForeignPointer, thread::Thread},
|
||||
vfs::FileReadiness,
|
||||
};
|
||||
|
||||
// TODO timeouts and better access
|
||||
#[async_trait]
|
||||
impl CharDevice for I2CDevice {
|
||||
async fn read(&self, buffer: &mut [u8]) -> Result<usize, Error> {
|
||||
self.read_nonblocking(buffer)
|
||||
let pid = Thread::current().process_id();
|
||||
self.check_lock(pid)?;
|
||||
let mut message = [I2CMessage::Read(buffer)];
|
||||
let mut xfer = I2CTransfer {
|
||||
messages: &mut message,
|
||||
repeat_start: false,
|
||||
};
|
||||
self.i2c_transfer(self.slave_address(), &mut xfer).await
|
||||
}
|
||||
async fn write(&self, buffer: &[u8]) -> Result<usize, Error> {
|
||||
self.write_nonblocking(buffer)
|
||||
let pid = Thread::current().process_id();
|
||||
self.check_lock(pid)?;
|
||||
let mut message = [I2CMessage::Write(buffer)];
|
||||
let mut xfer = I2CTransfer {
|
||||
messages: &mut message,
|
||||
repeat_start: false,
|
||||
};
|
||||
self.i2c_transfer(self.slave_address(), &mut xfer).await
|
||||
}
|
||||
fn read_nonblocking(&self, buffer: &mut [u8]) -> Result<usize, Error> {
|
||||
let pid = Thread::current().process_id();
|
||||
self.check_lock(pid)?;
|
||||
self.i2c_read(self.slave_address(), buffer)
|
||||
let _ = buffer;
|
||||
Err(Error::InvalidOperation)
|
||||
}
|
||||
fn write_nonblocking(&self, buffer: &[u8]) -> Result<usize, Error> {
|
||||
let pid = Thread::current().process_id();
|
||||
self.check_lock(pid)?;
|
||||
self.i2c_write(self.slave_address(), buffer)
|
||||
let _ = buffer;
|
||||
Err(Error::InvalidOperation)
|
||||
}
|
||||
fn device_request(&self, option: u32, buffer: &mut [u8], len: usize) -> Result<usize, Error> {
|
||||
if let Ok(req) = I2CRequestVariant::try_from(option) {
|
||||
@@ -55,6 +71,27 @@ impl CharDevice for I2CDevice {
|
||||
let len = i2c::GetMasterInfo::store_response(&info, buffer)?;
|
||||
Ok(len)
|
||||
}
|
||||
I2CRequestVariant::SmbusReadReg => {
|
||||
let thread = Thread::current();
|
||||
let pid = thread.process_id();
|
||||
self.check_lock(pid)?;
|
||||
let read_reg = i2c::SmbusReadReg::load_request(&buffer[..len])?;
|
||||
let space = thread.address_space();
|
||||
let rx_buffer = unsafe {
|
||||
read_reg
|
||||
.value_buffer
|
||||
.validate_user_slice_mut(read_reg.value_buffer_len, space)
|
||||
}?;
|
||||
let tx_buffer = [read_reg.reg];
|
||||
let mut messages = [I2CMessage::Write(&tx_buffer), I2CMessage::Read(rx_buffer)];
|
||||
let mut xfer = I2CTransfer {
|
||||
messages: &mut messages,
|
||||
repeat_start: true,
|
||||
};
|
||||
let len = block!(self.i2c_transfer(self.slave_address(), &mut xfer).await)??;
|
||||
let len = i2c::SmbusReadReg::store_response(&len.saturating_sub(1), buffer)?;
|
||||
Ok(len)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(Error::InvalidArgument)
|
||||
|
||||
@@ -7,7 +7,6 @@ use device_tree::{
|
||||
DeviceTree, DeviceTreeNodeExt,
|
||||
driver::{InitSequence, unflatten_device_tree},
|
||||
};
|
||||
use kernel_arch::{Architecture, ArchitectureImpl};
|
||||
use kernel_arch_riscv64::{
|
||||
PerCpuData, mem,
|
||||
registers::{SIE, SSTATUS},
|
||||
@@ -48,10 +47,12 @@ pub struct Riscv64 {
|
||||
|
||||
impl Platform for Riscv64 {
|
||||
unsafe fn reset(&self) -> ! {
|
||||
ArchitectureImpl::halt();
|
||||
log::warn!("Performing system reset via SBI");
|
||||
sbi::sbi_system_reset()
|
||||
}
|
||||
|
||||
unsafe fn power_off(&self) -> Result<!, Error> {
|
||||
log::warn!("Performing system shutdown via SBI");
|
||||
sbi::sbi_system_shutdown()
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,12 @@ use abi::{
|
||||
error::Error,
|
||||
io::{FileMode, device::i2c::I2CMasterInfo},
|
||||
};
|
||||
use alloc::{string::String, sync::Arc};
|
||||
use alloc::{boxed::Box, string::String, sync::Arc};
|
||||
use async_trait::async_trait;
|
||||
use device_api::{
|
||||
clock::Hertz,
|
||||
device::{Device, DeviceInitContext},
|
||||
i2c::{I2CAddress, I2CController, I2CDevice},
|
||||
i2c::{I2CAddress, I2CController, I2CDevice, I2CTransfer},
|
||||
};
|
||||
use device_tree::driver::{Node, ProbeContext, device_tree_driver, lookup_phandle};
|
||||
use libk::fs::devfs;
|
||||
@@ -71,6 +72,7 @@ impl Device for I2CMuxChild {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl I2CController for I2CMuxChild {
|
||||
fn bus_number(&self) -> u32 {
|
||||
self.parent.bus_number()
|
||||
@@ -88,14 +90,12 @@ impl I2CController for I2CMuxChild {
|
||||
self.parent.capabilities()
|
||||
}
|
||||
|
||||
fn i2c_write(&self, address: I2CAddress, buffer: &[u8]) -> Result<usize, Error> {
|
||||
// TODO channel select?
|
||||
self.parent.i2c_write(address, buffer)
|
||||
}
|
||||
|
||||
fn i2c_read(&self, address: I2CAddress, buffer: &mut [u8]) -> Result<usize, Error> {
|
||||
// TODO channel select?
|
||||
self.parent.i2c_read(address, buffer)
|
||||
async fn i2c_transfer(
|
||||
&self,
|
||||
address: I2CAddress,
|
||||
transfer: &mut I2CTransfer,
|
||||
) -> Result<usize, Error> {
|
||||
self.parent.i2c_transfer(address, transfer).await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ fn dump_panic_info(cpu: &LocalCpu, pi: &core::panic::PanicInfo) {
|
||||
debug::panic_log!(sink, "--- END PANIC ---\n");
|
||||
}
|
||||
|
||||
#[cfg_attr(any(rust_analyzer, target_arch = "riscv64"), allow(unreachable_code))]
|
||||
pub(crate) fn panic_handler(pi: &core::panic::PanicInfo) -> ! {
|
||||
let cpu = Cpu::local();
|
||||
|
||||
@@ -80,6 +81,11 @@ pub(crate) fn panic_handler(pi: &core::panic::PanicInfo) -> ! {
|
||||
|
||||
dump_panic_info(&cpu, pi);
|
||||
|
||||
#[cfg(any(rust_analyzer, target_arch = "riscv64"))]
|
||||
unsafe {
|
||||
PLATFORM.reset();
|
||||
}
|
||||
|
||||
PANIC_FINISHED_FENCE.signal();
|
||||
while PANIC_SEQUENCE.load(Ordering::Acquire) != cpu.id() {
|
||||
core::hint::spin_loop();
|
||||
|
||||
@@ -96,6 +96,7 @@ pub(crate) fn system_control(option: u32, value: &mut [u8], size: usize) -> Resu
|
||||
SystemControlVariant::PowerOff => {
|
||||
unsafe { PLATFORM.power_off() }?;
|
||||
}
|
||||
SystemControlVariant::Reboot => unsafe { PLATFORM.reset() },
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user