Bitbang I2C
This commit is contained in:
70
usb4/Cargo.lock
generated
70
usb4/Cargo.lock
generated
@@ -1,29 +1,5 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95752358c8f7552394baf48cd82695b345628ad3f170d607de3ca03b8dacca15"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
|
||||
[[package]]
|
||||
name = "coreboot-collector"
|
||||
version = "0.1.0"
|
||||
@@ -47,37 +23,12 @@ dependencies = [
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "i2cdev"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c0eb3d9b6b02dc2508ee23439170004e44344bab9d53a490eb1f64c885b5003"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
"libc",
|
||||
"nix",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"void",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numtoa"
|
||||
version = "0.2.3"
|
||||
@@ -169,22 +120,15 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
||||
|
||||
[[package]]
|
||||
name = "usb4"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"coreboot-collector",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
|
||||
|
||||
[[package]]
|
||||
name = "void"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||
|
||||
[[package]]
|
||||
name = "you-ass-bee-see"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"coreboot-collector",
|
||||
"i2cdev",
|
||||
]
|
||||
|
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "you-ass-bee-see"
|
||||
name = "usb4"
|
||||
version = "0.1.0"
|
||||
authors = ["Jeremy Soller <jeremy@system76.com>"]
|
||||
edition = "2018"
|
||||
@@ -8,4 +8,3 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
coreboot-collector = { path = "../tools/coreboot-collector" }
|
||||
i2cdev = "0.4.4"
|
||||
|
399
usb4/src/main.rs
399
usb4/src/main.rs
@@ -1,7 +1,11 @@
|
||||
use coreboot_collector::sideband::Sideband;
|
||||
use i2cdev::core::I2CDevice;
|
||||
use i2cdev::linux::LinuxI2CDevice;
|
||||
use std::{fs, thread, time};
|
||||
use std::{
|
||||
fs,
|
||||
rc::Rc,
|
||||
process,
|
||||
thread,
|
||||
time
|
||||
};
|
||||
|
||||
const IECS_CMD: u8 = 8;
|
||||
const IECS_DATA: u8 = 9;
|
||||
@@ -13,71 +17,319 @@ const CMD_BLKW: u32 = 0x574b4c42;
|
||||
const CMD_BOPS: u32 = 0x53504f42;
|
||||
const CMD_PCYC: u32 = 0x43594350;
|
||||
|
||||
const GPIO_FORCE_POWER: (u8, u8) = (0x6E, 0x82); // GPP_A23
|
||||
|
||||
fn read<I: I2CDevice>(dev: &mut I, reg: u8) -> Result<u32, I::Error> {
|
||||
let bytes = dev.smbus_read_block_data(reg)?;
|
||||
//TODO: return error on bytes.len() != 4
|
||||
#[repr(u64)]
|
||||
pub enum GpioPadMode {
|
||||
Gpio = 0 << 10,
|
||||
Nf1 = 1 << 10,
|
||||
Nf2 = 2 << 10,
|
||||
Nf3 = 3 << 10,
|
||||
Nf4 = 4 << 10,
|
||||
Nf5 = 5 << 10,
|
||||
Nf6 = 6 << 10,
|
||||
Nf7 = 7 << 10,
|
||||
}
|
||||
|
||||
pub struct Gpio {
|
||||
//TODO: this should probably be locked
|
||||
sideband: Rc<Sideband>,
|
||||
port: u8,
|
||||
pad: u8,
|
||||
}
|
||||
|
||||
impl Gpio {
|
||||
const PAD_MODE: u64 = 0b111 << 10;
|
||||
const RX_DIS: u64 = 1 << 9;
|
||||
const TX_DIS: u64 = 1 << 8;
|
||||
const RX: u64 = 1 << 1;
|
||||
const TX: u64 = 1 << 0;
|
||||
|
||||
pub fn new(sideband: Rc<Sideband>, port: u8, pad: u8) -> Self {
|
||||
Self { sideband, port, pad }
|
||||
}
|
||||
|
||||
unsafe fn get_config(&self) -> u64 {
|
||||
self.sideband.gpio(self.port, self.pad)
|
||||
}
|
||||
|
||||
unsafe fn set_config(&mut self, config: u64) {
|
||||
self.sideband.set_gpio(self.port, self.pad, config);
|
||||
}
|
||||
|
||||
unsafe fn get_mask(&self, mask: u64) -> bool {
|
||||
self.get_config() & mask == mask
|
||||
}
|
||||
|
||||
unsafe fn set_mask(&mut self, mask: u64, value: bool) {
|
||||
let mut config = self.get_config();
|
||||
if value {
|
||||
config |= mask;
|
||||
} else {
|
||||
config &= !mask;
|
||||
}
|
||||
self.set_config(config);
|
||||
}
|
||||
|
||||
pub unsafe fn set_pad_mode(&mut self, mode: GpioPadMode) {
|
||||
let mut config = self.get_config();
|
||||
config &= !Self::PAD_MODE;
|
||||
config |= mode as u64;
|
||||
self.set_config(config);
|
||||
|
||||
}
|
||||
|
||||
pub unsafe fn enable_rx(&mut self, value: bool) {
|
||||
self.set_mask(Self::RX_DIS, !value);
|
||||
}
|
||||
|
||||
pub unsafe fn enable_tx(&mut self, value: bool) {
|
||||
self.set_mask(Self::TX_DIS, !value);
|
||||
}
|
||||
|
||||
pub unsafe fn get_rx(&self) -> bool {
|
||||
self.get_mask(Self::RX)
|
||||
}
|
||||
|
||||
pub unsafe fn get_tx(&self) -> bool {
|
||||
self.get_mask(Self::TX)
|
||||
}
|
||||
|
||||
pub unsafe fn set_tx(&mut self, value: bool) {
|
||||
self.set_mask(Self::TX, value);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct I2CBitbang {
|
||||
scl: Gpio,
|
||||
scl_config: u64,
|
||||
sda: Gpio,
|
||||
sda_config: u64,
|
||||
}
|
||||
|
||||
impl I2CBitbang {
|
||||
pub unsafe fn new(mut scl: Gpio, mut sda: Gpio) -> Self {
|
||||
//TODO: will this transmit something invalid?
|
||||
|
||||
let scl_config = scl.get_config();
|
||||
scl.enable_rx(true);
|
||||
scl.enable_tx(false);
|
||||
scl.set_tx(false);
|
||||
scl.set_pad_mode(GpioPadMode::Gpio);
|
||||
|
||||
let sda_config = sda.get_config();
|
||||
sda.enable_rx(true);
|
||||
sda.enable_tx(false);
|
||||
sda.set_tx(false);
|
||||
sda.set_pad_mode(GpioPadMode::Gpio);
|
||||
|
||||
Self { scl, scl_config, sda, sda_config, }
|
||||
}
|
||||
|
||||
// Delay half half of period
|
||||
fn delay(&self) {
|
||||
// Hard coded to 5 us, which is half of the period 10 us for a frequency of 100 KHz
|
||||
thread::sleep(time::Duration::from_micros(5));
|
||||
}
|
||||
|
||||
// Pull SCL low
|
||||
unsafe fn clr_scl(&mut self) {
|
||||
self.scl.enable_tx(true);
|
||||
}
|
||||
|
||||
// Release SCL, bus pulls it high
|
||||
unsafe fn set_scl(&mut self) {
|
||||
self.scl.enable_tx(false);
|
||||
}
|
||||
|
||||
// Pull SDA low
|
||||
unsafe fn clr_sda(&mut self) {
|
||||
self.sda.enable_tx(true);
|
||||
}
|
||||
|
||||
// Release SDA, bus pulls it high
|
||||
unsafe fn set_sda(&mut self) {
|
||||
self.sda.enable_tx(false);
|
||||
}
|
||||
|
||||
unsafe fn start(&mut self) {
|
||||
self.set_scl();
|
||||
self.set_sda();
|
||||
self.delay();
|
||||
self.clr_sda();
|
||||
self.delay();
|
||||
self.clr_scl();
|
||||
self.delay();
|
||||
}
|
||||
|
||||
unsafe fn stop(&mut self) {
|
||||
self.clr_sda();
|
||||
self.delay();
|
||||
self.set_scl();
|
||||
self.delay();
|
||||
self.set_sda();
|
||||
self.delay();
|
||||
}
|
||||
|
||||
unsafe fn write_bit(&mut self, bit: bool) {
|
||||
if bit {
|
||||
self.set_sda();
|
||||
} else {
|
||||
self.clr_sda();
|
||||
}
|
||||
self.delay();
|
||||
self.set_scl();
|
||||
self.delay();
|
||||
self.clr_scl();
|
||||
}
|
||||
|
||||
unsafe fn read_bit(&mut self) -> bool {
|
||||
self.set_sda();
|
||||
self.delay();
|
||||
self.set_scl();
|
||||
self.delay();
|
||||
let bit = self.sda.get_rx();
|
||||
self.clr_scl();
|
||||
bit
|
||||
}
|
||||
|
||||
unsafe fn write_byte(&mut self, byte: u8, start: bool) -> bool {
|
||||
if start {
|
||||
self.start();
|
||||
}
|
||||
for i in (0..8).rev() {
|
||||
self.write_bit(byte & (1 << i) != 0);
|
||||
}
|
||||
self.read_bit()
|
||||
}
|
||||
|
||||
unsafe fn read_byte(&mut self, ack: bool) -> u8 {
|
||||
let mut byte = 0;
|
||||
for i in (0..8).rev() {
|
||||
if self.read_bit() {
|
||||
byte |= 1 << i;
|
||||
}
|
||||
}
|
||||
self.write_bit(!ack);
|
||||
byte
|
||||
}
|
||||
|
||||
pub unsafe fn smbus_block_write(&mut self, address: u8, command: u8, bytes: &[u8]) -> usize {
|
||||
// Only 32 bytes can be processed at a time
|
||||
if bytes.len() > 32 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let mut count = 0;
|
||||
if self.write_byte(address << 1, true) {
|
||||
if self.write_byte(command, false) {
|
||||
if self.write_byte(bytes.len() as u8, false) {
|
||||
for byte in bytes.iter() {
|
||||
if self.write_byte(*byte, false) {
|
||||
count += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.stop();
|
||||
count
|
||||
}
|
||||
|
||||
pub unsafe fn smbus_block_read(&mut self, address: u8, command: u8) -> Vec<u8> {
|
||||
//TODO: use static buffer?
|
||||
let mut bytes = Vec::new();
|
||||
if self.write_byte(address << 1, true) {
|
||||
if self.write_byte(command, false) {
|
||||
if self.write_byte(address << 1 | 1, true) {
|
||||
let count = self.read_byte(true);
|
||||
for _i in 0..count {
|
||||
bytes.push(self.read_byte(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.stop();
|
||||
bytes
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for I2CBitbang {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
//TODO: will this transmit something invalid?
|
||||
self.scl.set_config(self.scl_config);
|
||||
self.sda.set_config(self.sda_config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Retimer {
|
||||
i2c: I2CBitbang,
|
||||
address: u8,
|
||||
}
|
||||
|
||||
impl Retimer {
|
||||
pub fn new(i2c: I2CBitbang, address: u8) -> Self {
|
||||
Self { i2c, address }
|
||||
}
|
||||
|
||||
pub unsafe fn read(&mut self, reg: u8) -> Result<u32, String> {
|
||||
let bytes = self.i2c.smbus_block_read(self.address, reg);
|
||||
if bytes.len() == 4 {
|
||||
Ok(
|
||||
bytes[0] as u32 |
|
||||
(bytes[1] as u32) << 8 |
|
||||
(bytes[2] as u32) << 16 |
|
||||
(bytes[3] as u32) << 24
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Err(format!("Retimer::read: read {} bytes instead of 4", bytes.len()))
|
||||
}
|
||||
}
|
||||
|
||||
fn write<I: I2CDevice>(dev: &mut I, reg: u8, data: u32) -> Result<(), I::Error> {
|
||||
pub unsafe fn write(&mut self, reg: u8, data: u32) -> Result<(), String> {
|
||||
let bytes = [
|
||||
data as u8,
|
||||
(data >> 8) as u8,
|
||||
(data >> 16) as u8,
|
||||
(data >> 24) as u8,
|
||||
];
|
||||
dev.smbus_write_block_data(reg, &bytes)
|
||||
}
|
||||
let count = self.i2c.smbus_block_write(self.address, reg, &bytes);
|
||||
if count == 4 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("Retimer::write: wrote {} bytes instead of 4", count))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn command<I: I2CDevice>(dev: &mut I, cmd: u32) -> Result<u32, I::Error> {
|
||||
write(dev, IECS_CMD, cmd)?;
|
||||
loop {
|
||||
let status = read(dev, IECS_CMD)?;
|
||||
pub unsafe fn command(&mut self, cmd: u32) -> Result<(), String> {
|
||||
self.write(IECS_CMD, cmd)?;
|
||||
//TODO: is this the right number of retries?
|
||||
let retries = 1000;
|
||||
for _i in 0..retries {
|
||||
let status = self.read(IECS_CMD)?;
|
||||
if status != cmd {
|
||||
//TODO: perform error checking here
|
||||
return Ok(status);
|
||||
if status == 0 {
|
||||
return Ok(());
|
||||
} else {
|
||||
return Err(format!("Retimer::command: read 0x{:X} instead of 0", status));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(format!("Retimer::command: timed out after {} retries", retries))
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
//TODO: check model
|
||||
|
||||
let sideband = unsafe { Sideband::new(0xFD00_0000).unwrap() };
|
||||
|
||||
// Set FORCE_POWER high
|
||||
unsafe {
|
||||
let (port, pad) = GPIO_FORCE_POWER;
|
||||
let mut value = sideband.gpio(port, pad);
|
||||
value |= 1;
|
||||
sideband.set_gpio(port, pad, value);
|
||||
}
|
||||
|
||||
// Sleep 40ms
|
||||
thread::sleep(time::Duration::from_millis(40));
|
||||
|
||||
let mut dev = LinuxI2CDevice::new("/dev/i2c-11", 0x40).unwrap();
|
||||
eprintln!("Vendor: {:X}", read(&mut dev, 0).unwrap());
|
||||
eprintln!("Device: {:X}", read(&mut dev, 1).unwrap());
|
||||
|
||||
let image = fs::read("../models/galp5/usb4-retimer.rom").unwrap();
|
||||
|
||||
for i in 2..=32 {
|
||||
println!("{}: {:X}", i, read(&mut dev, i).unwrap());
|
||||
}
|
||||
|
||||
write(&mut dev, IECS_DATA, 0).unwrap();
|
||||
println!("IECS_DATA: {:X}", read(&mut dev, IECS_DATA).unwrap());
|
||||
unsafe fn flash_retimer(retimer: &mut Retimer) -> Result<(), String> {
|
||||
eprintln!("Vendor: {:X}", retimer.read(0)?);
|
||||
eprintln!("Device: {:X}", retimer.read(1)?);
|
||||
|
||||
/*
|
||||
let image = fs::read("../models/galp5/usb4-retimer.rom").unwrap();
|
||||
|
||||
eprintln!("Set offset to 0");
|
||||
write(&mut dev, IECS_DATA, 0).unwrap();
|
||||
let status = command(&mut dev, CMD_BOPS).unwrap();
|
||||
@@ -125,11 +377,56 @@ fn main() {
|
||||
eprintln!("Successfully flashed retimer");
|
||||
*/
|
||||
|
||||
// Set FORCE_POWER low
|
||||
unsafe {
|
||||
let (port, pad) = GPIO_FORCE_POWER;
|
||||
let mut value = sideband.gpio(port, pad);
|
||||
value &= !1;
|
||||
sideband.set_gpio(port, pad, value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn retimer_access(i2c: I2CBitbang) -> i32 {
|
||||
let mut retimer = Retimer::new(i2c, 0x40);
|
||||
match flash_retimer(&mut retimer) {
|
||||
Ok(()) => 0,
|
||||
Err(err) => {
|
||||
eprintln!("Failed to flash retimer: {}", err);
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn i2c_access(sideband: Rc<Sideband>) -> i32 {
|
||||
let scl = Gpio::new(sideband.clone(), 0x6A, 0x06); // GPP_C3
|
||||
let sda = Gpio::new(sideband.clone(), 0x6A, 0x08); // GPP_C4
|
||||
let i2c = I2CBitbang::new(scl, sda);
|
||||
retimer_access(i2c)
|
||||
}
|
||||
|
||||
unsafe fn force_power(sideband: Rc<Sideband>) -> i32 {
|
||||
let mut force_power = Gpio::new(sideband.clone(), 0x6E, 0x82); // GPP_A23
|
||||
|
||||
println!("Set FORCE_POWER high");
|
||||
force_power.set_tx(true);
|
||||
|
||||
println!("Sleep 40 ms");
|
||||
thread::sleep(time::Duration::from_millis(40));
|
||||
|
||||
let exit_status = i2c_access(sideband);
|
||||
|
||||
eprintln!("Set FORCE_POWER low");
|
||||
force_power.set_tx(false);
|
||||
|
||||
exit_status
|
||||
}
|
||||
|
||||
fn main() {
|
||||
//TODO: check model
|
||||
|
||||
unsafe {
|
||||
let sideband = match Sideband::new(0xFD00_0000) {
|
||||
Ok(ok) => Rc::new(ok),
|
||||
Err(err) => {
|
||||
eprintln!("Failed to access sideband: {}", err);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
process::exit(force_power(sideband));
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user