esp-p4-eth 0.1.0

no_std async Ethernet MAC driver for ESP32-P4 RMII designs (embassy-net compatible)
Documentation
#![no_std]
#![no_main]

//! Bring-up probe for ESP32-P4 SYSTIMER (base 0x500E_2000).
//!
//! Validates that we can read the 64-bit free-running counter UNIT0 via the
//! snapshot dance (WT bit 30 in UNIT0_OP, poll bit 29, then read VAL_HI/LO).
//! Prints DATE register, CONF, several timed snapshots, and an estimated tick
//! rate by comparing two reads spaced by a long busy-wait.
//!
//! Prerequisites assumed by HW reset state:
//! - SYSTIMER bus clock: HP_SYS_CLKRST.SOC_CLK_CTRL2 bit 23 default = 1.
//! - SYSTIMER reset:     HP_SYS_CLKRST.HP_RST_EN1   bit 5  default = 0 (not in reset).
//! - SYSTIMER UNIT0:     CONF.timer_unit0_work_en (bit 30) default = 1.
//! Expected source: XTAL 40 MHz / 2.5 = 16 MHz tick (62.5 ns/tick).

use core::fmt::{self, Write};
use core::hint::spin_loop;
use core::ptr::{read_volatile, write_volatile};

use panic_halt as _;
use riscv_rt::entry;

const UART0_BASE: usize = 0x500C_A000;
const UART_FIFO_REG: *mut u32 = UART0_BASE as *mut u32;
const UART_STATUS_REG: *const u32 = (UART0_BASE + 0x1C) as *const u32;
const UART_TXFIFO_CNT_SHIFT: u32 = 16;
const UART_TXFIFO_CNT_MASK: u32 = 0xFF << UART_TXFIFO_CNT_SHIFT;
const UART_TXFIFO_CAPACITY: u32 = 128;

const SYSTIMER_BASE: usize = 0x500E_2000;
const SYSTIMER_CONF: *mut u32 = SYSTIMER_BASE as *mut u32;
const SYSTIMER_UNIT0_OP: *mut u32 = (SYSTIMER_BASE + 0x04) as *mut u32;
const SYSTIMER_UNIT0_VAL_HI: *const u32 = (SYSTIMER_BASE + 0x40) as *const u32;
const SYSTIMER_UNIT0_VAL_LO: *const u32 = (SYSTIMER_BASE + 0x44) as *const u32;
const SYSTIMER_DATE: *const u32 = (SYSTIMER_BASE + 0xFC) as *const u32;

const HP_CLKRST_SOC_CLK_CTRL2: *const u32 = 0x500E_601C as *const u32;
const HP_CLKRST_PERI_CLK_CTRL21: *const u32 = 0x500E_6098 as *const u32;
const HP_CLKRST_HP_RST_EN1: *const u32 = 0x500E_60C4 as *const u32;

const UNIT_OP_UPDATE_BIT: u32 = 1 << 30;
const UNIT_OP_VALID_BIT: u32 = 1 << 29;

struct Uart0;

impl Uart0 {
    fn write_byte(&mut self, byte: u8) {
        while txfifo_count() >= UART_TXFIFO_CAPACITY {
            spin_loop();
        }
        unsafe {
            write_volatile(UART_FIFO_REG, byte as u32);
        }
    }
}

impl Write for Uart0 {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        for byte in s.bytes() {
            if byte == b'\n' {
                self.write_byte(b'\r');
            }
            self.write_byte(byte);
        }
        Ok(())
    }
}

fn txfifo_count() -> u32 {
    let status = unsafe { read_volatile(UART_STATUS_REG) };
    (status & UART_TXFIFO_CNT_MASK) >> UART_TXFIFO_CNT_SHIFT
}

fn delay_cycles(spins: usize) {
    for _ in 0..spins {
        spin_loop();
    }
}

fn flush_uart() {
    while txfifo_count() != 0 {
        spin_loop();
    }
}

/// Atomic 64-bit read of UNIT0 via the snapshot dance.
fn read_unit0() -> u64 {
    unsafe {
        write_volatile(SYSTIMER_UNIT0_OP, UNIT_OP_UPDATE_BIT);
        // Poll value_valid; it is set once the snapshot is committed.
        while read_volatile(SYSTIMER_UNIT0_OP) & UNIT_OP_VALID_BIT == 0 {
            spin_loop();
        }
        let hi = read_volatile(SYSTIMER_UNIT0_VAL_HI) as u64;
        let lo = read_volatile(SYSTIMER_UNIT0_VAL_LO) as u64;
        (hi << 32) | lo
    }
}

#[entry]
fn main() -> ! {
    let mut uart = Uart0;
    let _ = writeln!(uart, "\n=== systimer_probe alive ===");
    flush_uart();

    let date = unsafe { read_volatile(SYSTIMER_DATE) };
    let conf = unsafe { read_volatile(SYSTIMER_CONF) };
    let soc_clk_ctrl2 = unsafe { read_volatile(HP_CLKRST_SOC_CLK_CTRL2) };
    let peri_clk_ctrl21 = unsafe { read_volatile(HP_CLKRST_PERI_CLK_CTRL21) };
    let hp_rst_en1 = unsafe { read_volatile(HP_CLKRST_HP_RST_EN1) };

    let _ = writeln!(uart, "SYSTIMER_DATE         = 0x{:08X}", date);
    let _ = writeln!(uart, "SYSTIMER_CONF         = 0x{:08X}", conf);
    let _ = writeln!(
        uart,
        "  decoded: clk_en={} unit0_work_en={} unit1_work_en={} target0_en={}",
        (conf >> 31) & 1,
        (conf >> 30) & 1,
        (conf >> 29) & 1,
        (conf >> 24) & 1,
    );
    let _ = writeln!(
        uart,
        "SOC_CLK_CTRL2          = 0x{:08X}  (bit23 SYSTIMER_APB_CLK_EN={})",
        soc_clk_ctrl2,
        (soc_clk_ctrl2 >> 23) & 1,
    );
    let _ = writeln!(
        uart,
        "PERI_CLK_CTRL21        = 0x{:08X}  (bit29 CLK_SRC_SEL={} 0=XTAL,1=RC_FAST)",
        peri_clk_ctrl21,
        (peri_clk_ctrl21 >> 29) & 1,
    );
    let _ = writeln!(
        uart,
        "HP_RST_EN1             = 0x{:08X}  (bit5  RST_EN_STIMER={})",
        hp_rst_en1,
        (hp_rst_en1 >> 5) & 1,
    );
    flush_uart();

    let _ = writeln!(uart, "--- 5 snapshots, ~5ms apart ---");
    flush_uart();
    let mut prev = 0u64;
    for i in 0..5 {
        let v = read_unit0();
        let _ = writeln!(uart, "  t[{}] = {:>14}  (delta = {})", i, v, v - prev);
        flush_uart();
        prev = v;
        delay_cycles(50_000);
    }

    // Linearity sweep — three different busy-wait lengths. If counter rate is
    // constant, ticks/spin should match across all three. The previous run
    // measured ~44 ticks per spin_loop iter at espflash --ram boot (CPU not
    // boosted past XTAL by IDF system_init).
    for spins in [50_000usize, 200_000, 1_000_000] {
        let t0 = read_unit0();
        delay_cycles(spins);
        let t1 = read_unit0();
        let dt = t1 - t0;
        let per_spin = dt / spins as u64;
        let _ = writeln!(
            uart,
            "  spins={:>8}  delta_ticks={:>10}  ticks/spin={}",
            spins, dt, per_spin
        );
        flush_uart();
    }
    let _ = writeln!(
        uart,
        "  expected systimer rate = XTAL 40 MHz / 2.5 = 16 MHz (62.5 ns/tick)"
    );
    flush_uart();

    let _ = writeln!(uart, "=== systimer_probe done ===");
    flush_uart();
    loop {
        spin_loop();
    }
}