271 lines
8.0 KiB
Rust
271 lines
8.0 KiB
Rust
use std::time::Duration;
|
|
|
|
use acpi::aml::{
|
|
op_region::{OpRegion, RegionHandler, RegionSpace},
|
|
AmlError,
|
|
};
|
|
use common::{
|
|
io::{Io, Pio},
|
|
timeout::Timeout,
|
|
};
|
|
use log::*;
|
|
|
|
const EC_DATA: u16 = 0x62;
|
|
const EC_SC: u16 = 0x66;
|
|
|
|
const OBF: u8 = 1 << 0; // output full / data ready for host <> empty
|
|
const IBF: u8 = 1 << 1; // input full / data ready for ec <> empty
|
|
const CMD: u8 = 1 << 3; // byte in data reg is command <> data
|
|
const BURST: u8 = 1 << 4; // burst mode <> normal mode
|
|
const SCI_EVT: u8 = 1 << 5; // sci event pending <> not
|
|
const SMI_EVT: u8 = 1 << 6; // smi event pending <> not
|
|
|
|
const RD_EC: u8 = 0x80;
|
|
const WR_EC: u8 = 0x81;
|
|
const BE_EC: u8 = 0x82;
|
|
const BD_EC: u8 = 0x83;
|
|
const QR_EC: u8 = 0x84;
|
|
|
|
const BURST_ACK: u8 = 0x90;
|
|
|
|
pub const DEFAULT_EC_TIMEOUT: Duration = Duration::from_millis(10);
|
|
|
|
#[repr(transparent)]
|
|
pub struct ScBits(u8);
|
|
#[allow(dead_code)]
|
|
impl ScBits {
|
|
const fn obf(&self) -> bool {
|
|
(self.0 & OBF) != 0
|
|
}
|
|
const fn ibf(&self) -> bool {
|
|
(self.0 & IBF) != 0
|
|
}
|
|
const fn cmd(&self) -> bool {
|
|
(self.0 & CMD) != 0
|
|
}
|
|
const fn burst(&self) -> bool {
|
|
(self.0 & BURST) != 0
|
|
}
|
|
const fn sci_evt(&self) -> bool {
|
|
(self.0 & SCI_EVT) != 0
|
|
}
|
|
const fn smi_evt(&self) -> bool {
|
|
(self.0 & SMI_EVT) != 0
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct Ec {
|
|
sc: u16,
|
|
data: u16,
|
|
|
|
timeout: Duration,
|
|
}
|
|
impl Ec {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
sc: EC_SC,
|
|
data: EC_DATA,
|
|
timeout: DEFAULT_EC_TIMEOUT,
|
|
}
|
|
}
|
|
#[allow(dead_code)]
|
|
pub fn with_address(sc: u16, data: u16, timeout: Duration) -> Self {
|
|
Self { sc, data, timeout }
|
|
}
|
|
#[inline]
|
|
fn read_reg_sc(&self) -> ScBits {
|
|
ScBits(Pio::<u8>::new(self.sc).read())
|
|
}
|
|
#[inline]
|
|
fn read_reg_data(&self) -> u8 {
|
|
Pio::<u8>::new(self.data).read()
|
|
}
|
|
#[inline]
|
|
fn write_reg_sc(&self, value: u8) {
|
|
Pio::<u8>::new(self.sc).write(value);
|
|
}
|
|
#[inline]
|
|
fn write_reg_data(&self, value: u8) {
|
|
Pio::<u8>::new(self.data).write(value);
|
|
}
|
|
#[inline]
|
|
fn wait_for_write_ready(&self) -> Option<()> {
|
|
let timeout = Timeout::new(self.timeout);
|
|
loop {
|
|
if !self.read_reg_sc().ibf() {
|
|
return Some(());
|
|
}
|
|
timeout.run().ok()?;
|
|
}
|
|
}
|
|
#[inline]
|
|
fn wait_for_read_ready(&self) -> Option<()> {
|
|
let timeout = Timeout::new(self.timeout);
|
|
loop {
|
|
if self.read_reg_sc().obf() {
|
|
return Some(());
|
|
}
|
|
timeout.run().ok()?;
|
|
}
|
|
}
|
|
|
|
//https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/12_ACPI_Embedded_Controller_Interface_Specification/embedded-controller-command-set.html
|
|
pub fn read(&self, address: u8) -> Option<u8> {
|
|
trace!("ec read addr: {:x}", address);
|
|
self.wait_for_write_ready()?;
|
|
|
|
self.write_reg_sc(RD_EC);
|
|
|
|
self.wait_for_write_ready()?;
|
|
|
|
self.write_reg_data(address);
|
|
|
|
self.wait_for_read_ready()?;
|
|
|
|
let val = self.read_reg_data();
|
|
trace!("got: {:x}", val);
|
|
Some(val)
|
|
}
|
|
pub fn write(&self, address: u8, value: u8) -> Option<()> {
|
|
trace!("ec write addr: {:x}, with: {:x}", address, value);
|
|
self.wait_for_write_ready()?;
|
|
|
|
self.write_reg_sc(WR_EC);
|
|
|
|
self.wait_for_write_ready()?;
|
|
|
|
self.write_reg_data(address);
|
|
|
|
self.wait_for_write_ready()?;
|
|
|
|
self.write_reg_data(value);
|
|
trace!("done");
|
|
Some(())
|
|
}
|
|
// disabled if not met
|
|
// First Access - 400 microseconds
|
|
// Subsequent Accesses - 50 microseconds each
|
|
// Total Burst Time - 1 millisecond
|
|
//Accesses should be responded to within 50 microseconds.
|
|
#[allow(dead_code)]
|
|
fn enable_burst(&self) -> bool {
|
|
trace!("ec burst enable");
|
|
self.wait_for_write_ready();
|
|
|
|
self.write_reg_sc(BE_EC);
|
|
|
|
self.wait_for_read_ready();
|
|
|
|
let res = self.read_reg_data() == BURST_ACK;
|
|
trace!("success: {}", res);
|
|
res
|
|
}
|
|
#[allow(dead_code)]
|
|
fn disable_burst(&self) {
|
|
trace!("ec burst disable");
|
|
self.wait_for_write_ready();
|
|
self.write_reg_sc(BD_EC);
|
|
trace!("done");
|
|
}
|
|
//OSPM driver sends this command when the SCI_EVT flag in the EC_SC register is set.
|
|
#[allow(dead_code)]
|
|
fn queue_query(&mut self) -> u8 {
|
|
trace!("ec query");
|
|
self.wait_for_write_ready();
|
|
|
|
self.write_reg_sc(QR_EC);
|
|
|
|
self.wait_for_read_ready();
|
|
|
|
let val = self.read_reg_data();
|
|
trace!("got: {}", val);
|
|
val
|
|
}
|
|
}
|
|
impl RegionHandler for Ec {
|
|
fn read_u8(
|
|
&self,
|
|
region: &acpi::aml::op_region::OpRegion,
|
|
offset: usize,
|
|
) -> Result<u8, acpi::aml::AmlError> {
|
|
assert_eq!(region.space, RegionSpace::EmbeddedControl);
|
|
self.read(offset as u8).ok_or(AmlError::MutexAcquireTimeout) // TODO proper error type
|
|
}
|
|
fn write_u8(
|
|
&self,
|
|
region: &OpRegion,
|
|
offset: usize,
|
|
value: u8,
|
|
) -> Result<(), acpi::aml::AmlError> {
|
|
assert_eq!(region.space, RegionSpace::EmbeddedControl);
|
|
self.write(offset as u8, value)
|
|
.ok_or(AmlError::MutexAcquireTimeout) // TODO proper error type
|
|
}
|
|
fn read_u16(&self, region: &OpRegion, offset: usize) -> Result<u16, acpi::aml::AmlError> {
|
|
assert_eq!(region.space, RegionSpace::EmbeddedControl);
|
|
// EC is 8-bit; compose 16-bit AML reads as little-endian 8-bit EC reads.
|
|
// Cross-referenced with Linux drivers/acpi/ec.c: acpi_ec_read() and
|
|
// AML acpi_extract_value() which handles the same byte-decomposition.
|
|
let lo = self.read_u8(region, offset)? as u16;
|
|
let hi = self.read_u8(region, offset + 1)? as u16;
|
|
Ok(lo | (hi << 8))
|
|
}
|
|
fn read_u32(&self, region: &OpRegion, offset: usize) -> Result<u32, acpi::aml::AmlError> {
|
|
assert_eq!(region.space, RegionSpace::EmbeddedControl);
|
|
let part = self.read_u16(region, offset)? as u32;
|
|
let part2 = self.read_u16(region, offset + 2)? as u32;
|
|
Ok(part | (part2 << 16))
|
|
}
|
|
fn read_u64(&self, region: &OpRegion, offset: usize) -> Result<u64, acpi::aml::AmlError> {
|
|
assert_eq!(region.space, RegionSpace::EmbeddedControl);
|
|
let part = self.read_u32(region, offset)? as u64;
|
|
let part2 = self.read_u32(region, offset + 4)? as u64;
|
|
Ok(part | (part2 << 32))
|
|
}
|
|
fn write_u16(
|
|
&self,
|
|
region: &OpRegion,
|
|
offset: usize,
|
|
value: u16,
|
|
) -> Result<(), acpi::aml::AmlError> {
|
|
assert_eq!(region.space, RegionSpace::EmbeddedControl);
|
|
let bytes = value.to_le_bytes();
|
|
self.write_u8(region, offset, bytes[0])?;
|
|
self.write_u8(region, offset + 1, bytes[1])?;
|
|
Ok(())
|
|
}
|
|
fn write_u32(
|
|
&self,
|
|
region: &OpRegion,
|
|
offset: usize,
|
|
value: u32,
|
|
) -> Result<(), acpi::aml::AmlError> {
|
|
assert_eq!(region.space, RegionSpace::EmbeddedControl);
|
|
let bytes = value.to_le_bytes();
|
|
self.write_u8(region, offset, bytes[0])?;
|
|
self.write_u8(region, offset + 1, bytes[1])?;
|
|
self.write_u8(region, offset + 2, bytes[2])?;
|
|
self.write_u8(region, offset + 3, bytes[3])?;
|
|
Ok(())
|
|
}
|
|
fn write_u64(
|
|
&self,
|
|
region: &OpRegion,
|
|
offset: usize,
|
|
value: u64,
|
|
) -> Result<(), acpi::aml::AmlError> {
|
|
assert_eq!(region.space, RegionSpace::EmbeddedControl);
|
|
let bytes = value.to_le_bytes();
|
|
self.write_u8(region, offset, bytes[0])?;
|
|
self.write_u8(region, offset + 1, bytes[1])?;
|
|
self.write_u8(region, offset + 2, bytes[2])?;
|
|
self.write_u8(region, offset + 3, bytes[3])?;
|
|
self.write_u8(region, offset + 4, bytes[4])?;
|
|
self.write_u8(region, offset + 5, bytes[5])?;
|
|
self.write_u8(region, offset + 6, bytes[6])?;
|
|
self.write_u8(region, offset + 7, bytes[7])?;
|
|
Ok(())
|
|
}
|
|
}
|