use crate::regs;
const REG_IO_MUX_BASE: usize = 0x500E_1000;
const fn iomux_reg(pin: u32) -> usize {
REG_IO_MUX_BASE + 0x4 + (pin as usize) * 4
}
const FUN_IE: u32 = 1 << 9;
const FUN_PU: u32 = 1 << 8;
const FUN_PD: u32 = 1 << 7;
const MCU_SEL_SHIFT: u32 = 12;
const MCU_SEL_MASK: u32 = 0x7 << MCU_SEL_SHIFT;
const FUN_DRV_SHIFT: u32 = 0;
const FUN_DRV_MASK: u32 = 0x3 << FUN_DRV_SHIFT;
const FUN_DRV_MEDIUM: u32 = 0b10; const EMAC_PAD_FUNC: u32 = 3;
const GPIO_PAD_FUNC: u32 = 1;
const MII_MDI_PAD_IN_IDX: u32 = 107;
const MII_MDC_PAD_OUT_IDX: u32 = 108;
const MII_MDO_PAD_OUT_IDX: u32 = 109;
const REG_GPIO_BASE: usize = 0x500E_0000;
const GPIO_FUNC_OUT_SEL_CFG_BASE: usize = REG_GPIO_BASE + 0x558;
const GPIO_FUNC_IN_SEL_CFG_BASE: usize = REG_GPIO_BASE + 0x158;
const GPIO_FUNC_IN_SEL_BIT: u32 = 1 << 7;
const GPIO_OUT_W1TS: usize = REG_GPIO_BASE + 0x08;
const GPIO_OUT_W1TC: usize = REG_GPIO_BASE + 0x0C;
const GPIO_OUT1_W1TS: usize = REG_GPIO_BASE + 0x14;
const GPIO_OUT1_W1TC: usize = REG_GPIO_BASE + 0x18;
const GPIO_ENABLE_W1TS: usize = REG_GPIO_BASE + 0x24;
const GPIO_ENABLE1_W1TS: usize = REG_GPIO_BASE + 0x30;
const GPIO_MATRIX_OUT_SIMPLE: u32 = 256;
#[derive(Clone, Copy, Debug)]
pub struct RmiiPinConfig {
pub txd0: u32,
pub txd1: u32,
pub tx_en: u32,
pub rxd0: u32,
pub rxd1: u32,
pub crs_dv: u32,
}
impl RmiiPinConfig {
pub const WAVESHARE_P4_ETH: Self = Self {
txd0: 34,
txd1: 35,
tx_en: 49,
rxd0: 30,
rxd1: 29,
crs_dv: 28,
};
}
#[derive(Clone, Copy, Debug)]
pub struct MdioPinConfig {
pub mdc: u32,
pub mdio: u32,
}
impl MdioPinConfig {
pub const WAVESHARE_P4_ETH: Self = Self { mdc: 31, mdio: 52 };
}
#[derive(Clone, Copy, Debug)]
pub struct PhyResetPinConfig {
pub pin: u32,
pub active_low: bool,
}
impl PhyResetPinConfig {
pub const WAVESHARE_P4_ETH: Self = Self {
pin: 51,
active_low: true,
};
}
pub fn configure_rmii_pins() {
configure_rmii_pin_set(RmiiPinConfig::WAVESHARE_P4_ETH);
}
pub fn configure_rmii_pin_set(cfg: RmiiPinConfig) {
configure_input_pin(iomux_reg(cfg.crs_dv));
configure_input_pin(iomux_reg(cfg.rxd0));
configure_input_pin(iomux_reg(cfg.rxd1));
configure_output_pin(iomux_reg(cfg.txd0));
configure_output_pin(iomux_reg(cfg.txd1));
configure_output_pin(iomux_reg(cfg.tx_en));
}
pub fn configure_mdio_pins() {
configure_mdio_pin_set(MdioPinConfig::WAVESHARE_P4_ETH);
}
pub fn configure_mdio_pin_set(cfg: MdioPinConfig) {
configure_gpio_pad_with_drive(cfg.mdc, false);
matrix_out(cfg.mdc, MII_MDC_PAD_OUT_IDX);
gpio_enable_output(cfg.mdc);
configure_gpio_pad_with_drive(cfg.mdio, true);
matrix_out(cfg.mdio, MII_MDO_PAD_OUT_IDX);
matrix_in(cfg.mdio, MII_MDI_PAD_IN_IDX);
gpio_enable_output(cfg.mdio);
}
pub fn release_waveshare_phy_reset() {
release_phy_reset_pin(PhyResetPinConfig::WAVESHARE_P4_ETH);
}
pub fn release_phy_reset_pin(cfg: PhyResetPinConfig) {
configure_gpio_pad(cfg.pin, false);
matrix_out(cfg.pin, GPIO_MATRIX_OUT_SIMPLE);
if cfg.active_low {
gpio_set_high(cfg.pin);
} else {
gpio_set_low(cfg.pin);
}
gpio_enable_output(cfg.pin);
}
fn configure_input_pin(iomux: usize) {
regs::write(iomux, pad_config_value(regs::read(iomux), true, EMAC_PAD_FUNC));
}
fn configure_output_pin(iomux: usize) {
regs::write(
iomux,
pad_config_value(regs::read(iomux), false, EMAC_PAD_FUNC),
);
}
fn configure_gpio_pad(pin: u32, input_enabled: bool) {
let iomux = iomux_reg(pin);
regs::write(
iomux,
pad_config_value(regs::read(iomux), input_enabled, GPIO_PAD_FUNC),
);
}
fn configure_gpio_pad_with_drive(pin: u32, input_enabled: bool) {
let iomux = iomux_reg(pin);
let mut value = pad_config_value(regs::read(iomux), input_enabled, GPIO_PAD_FUNC);
value = (value & !FUN_DRV_MASK) | ((FUN_DRV_MEDIUM << FUN_DRV_SHIFT) & FUN_DRV_MASK);
regs::write(iomux, value);
}
fn matrix_out(pin: u32, signal_idx: u32) {
let reg = GPIO_FUNC_OUT_SEL_CFG_BASE + (pin as usize) * 4;
regs::write(reg, signal_idx & 0x1FF);
}
fn matrix_in(pin: u32, signal_idx: u32) {
let reg = GPIO_FUNC_IN_SEL_CFG_BASE + (signal_idx as usize) * 4;
regs::write(reg, (pin & 0x3F) | GPIO_FUNC_IN_SEL_BIT);
}
fn gpio_set_high(pin: u32) {
if pin < 32 {
regs::write(GPIO_OUT_W1TS, 1u32 << pin);
} else {
regs::write(GPIO_OUT1_W1TS, 1u32 << (pin - 32));
}
}
fn gpio_set_low(pin: u32) {
if pin < 32 {
regs::write(GPIO_OUT_W1TC, 1u32 << pin);
} else {
regs::write(GPIO_OUT1_W1TC, 1u32 << (pin - 32));
}
}
fn gpio_enable_output(pin: u32) {
if pin < 32 {
regs::write(GPIO_ENABLE_W1TS, 1u32 << pin);
} else {
regs::write(GPIO_ENABLE1_W1TS, 1u32 << (pin - 32));
}
}
fn pad_config_value(previous: u32, input_enabled: bool, func: u32) -> u32 {
let mut value =
(previous & !(MCU_SEL_MASK | FUN_PU | FUN_PD)) | ((func << MCU_SEL_SHIFT) & MCU_SEL_MASK);
if input_enabled {
value |= FUN_IE;
} else {
value &= !FUN_IE;
}
value
}
#[cfg(test)]
mod tests {
use super::*;
use crate::regs;
#[test]
fn iomux_reg_matches_idf_addresses() {
assert_eq!(iomux_reg(0), 0x500E_1004);
assert_eq!(iomux_reg(28), 0x500E_1074);
assert_eq!(iomux_reg(50), 0x500E_10CC);
assert_eq!(iomux_reg(52), 0x500E_10D4);
}
#[test]
fn input_pad_config_enables_input_and_selects_emac_function() {
let value = pad_config_value(u32::MAX, true, EMAC_PAD_FUNC);
assert_eq!(value & MCU_SEL_MASK, EMAC_PAD_FUNC << MCU_SEL_SHIFT);
assert_ne!(value & FUN_IE, 0);
assert_eq!(value & FUN_PU, 0);
assert_eq!(value & FUN_PD, 0);
}
#[test]
fn output_pad_config_disables_input_and_selects_emac_function() {
let value = pad_config_value(u32::MAX, false, EMAC_PAD_FUNC);
assert_eq!(value & MCU_SEL_MASK, EMAC_PAD_FUNC << MCU_SEL_SHIFT);
assert_eq!(value & FUN_IE, 0);
assert_eq!(value & FUN_PU, 0);
assert_eq!(value & FUN_PD, 0);
}
#[test]
fn waveshare_rmii_pins_program_iomux_function_3() {
regs::reset_test_registers();
let cfg = RmiiPinConfig::WAVESHARE_P4_ETH;
let pins_in = [cfg.crs_dv, cfg.rxd0, cfg.rxd1];
let pins_out = [cfg.txd0, cfg.txd1, cfg.tx_en];
for &p in pins_in.iter().chain(pins_out.iter()) {
regs::write(iomux_reg(p), u32::MAX);
}
configure_rmii_pin_set(cfg);
for &p in &pins_in {
let v = regs::read(iomux_reg(p));
assert_eq!(v & MCU_SEL_MASK, EMAC_PAD_FUNC << MCU_SEL_SHIFT);
assert_ne!(v & FUN_IE, 0);
}
for &p in &pins_out {
let v = regs::read(iomux_reg(p));
assert_eq!(v & MCU_SEL_MASK, EMAC_PAD_FUNC << MCU_SEL_SHIFT);
assert_eq!(v & FUN_IE, 0);
}
}
#[test]
fn waveshare_mdio_pins_set_iomux_to_gpio_function() {
regs::reset_test_registers();
let cfg = MdioPinConfig::WAVESHARE_P4_ETH;
configure_mdio_pin_set(cfg);
let mdc = regs::read(iomux_reg(cfg.mdc));
assert_eq!(mdc & MCU_SEL_MASK, GPIO_PAD_FUNC << MCU_SEL_SHIFT);
assert_eq!(mdc & FUN_IE, 0);
let mdio = regs::read(iomux_reg(cfg.mdio));
assert_eq!(mdio & MCU_SEL_MASK, GPIO_PAD_FUNC << MCU_SEL_SHIFT);
assert_ne!(mdio & FUN_IE, 0);
}
#[test]
fn waveshare_phy_reset_deasserts_gpio51_high() {
regs::reset_test_registers();
release_waveshare_phy_reset();
let cfg = PhyResetPinConfig::WAVESHARE_P4_ETH;
let iomux = regs::read(iomux_reg(cfg.pin));
assert_eq!(iomux & MCU_SEL_MASK, GPIO_PAD_FUNC << MCU_SEL_SHIFT);
assert_eq!(iomux & FUN_IE, 0);
assert_eq!(
regs::read(GPIO_FUNC_OUT_SEL_CFG_BASE + cfg.pin as usize * 4),
GPIO_MATRIX_OUT_SIMPLE
);
assert_eq!(regs::read(GPIO_OUT1_W1TS), 1u32 << (cfg.pin - 32));
assert_eq!(regs::read(GPIO_ENABLE1_W1TS), 1u32 << (cfg.pin - 32));
}
}