use embedded_hal::delay::DelayNs;
use crate::driver::error::{ConfigError, IoError, Result};
use crate::internal::register::mac::{
GMACMIIADDR_CR_MASK, GMACMIIADDR_CR_SHIFT, GMACMIIADDR_GB, GMACMIIADDR_GR_MASK,
GMACMIIADDR_GR_SHIFT, GMACMIIADDR_GW, GMACMIIADDR_PA_MASK, GMACMIIADDR_PA_SHIFT, MacRegs,
};
use crate::internal::phy_regs::standard::{bmcr, bmsr, phy_reg};
pub const MDIO_TIMEOUT_US: u32 = 1_000;
pub const MAX_PHY_ADDR: u8 = 31;
pub const MAX_REG_ADDR: u8 = 31;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum MdcClockDivider {
Div42 = 0,
Div62 = 1,
Div16 = 2,
Div26 = 3,
#[default]
Div102 = 4,
Div124 = 5,
}
impl MdcClockDivider {
pub const fn from_sys_clock_hz(sys_clk_hz: u32) -> Self {
if sys_clk_hz < 35_000_000 {
Self::Div16
} else if sys_clk_hz < 60_000_000 {
Self::Div26
} else if sys_clk_hz < 100_000_000 {
Self::Div42
} else if sys_clk_hz < 150_000_000 {
Self::Div62
} else if sys_clk_hz < 250_000_000 {
Self::Div102
} else {
Self::Div124
}
}
pub const fn to_reg_value(self) -> u32 {
self as u32
}
}
pub trait MdioBus {
fn read(&mut self, phy_addr: u8, reg_addr: u8) -> Result<u16>;
fn write(&mut self, phy_addr: u8, reg_addr: u8, value: u16) -> Result<()>;
fn is_busy(&self) -> bool;
}
#[derive(Debug)]
pub struct MdioController<D: DelayNs> {
clock_divider: MdcClockDivider,
delay: D,
timeout_us: u32,
}
impl<D: DelayNs> MdioController<D> {
pub fn new(delay: D) -> Self {
Self {
clock_divider: MdcClockDivider::Div102,
timeout_us: MDIO_TIMEOUT_US,
delay,
}
}
pub fn with_clock_divider(delay: D, divider: MdcClockDivider) -> Self {
Self {
clock_divider: divider,
timeout_us: MDIO_TIMEOUT_US,
delay,
}
}
pub fn configure_for_sys_clock(&mut self, sys_clk_hz: u32) {
self.clock_divider = MdcClockDivider::from_sys_clock_hz(sys_clk_hz);
}
pub fn set_timeout_us(&mut self, timeout_us: u32) {
self.timeout_us = timeout_us;
}
fn wait_not_busy(&mut self) -> Result<()> {
let mut elapsed = 0u32;
while MacRegs::mii_address() & GMACMIIADDR_GB != 0 {
if elapsed >= self.timeout_us {
return Err(IoError::Timeout.into());
}
self.delay.delay_us(10);
elapsed += 10;
}
Ok(())
}
fn build_mii_addr(&self, phy_addr: u8, reg_addr: u8, is_write: bool) -> u32 {
let mut addr = 0u32;
addr |= ((phy_addr as u32) << GMACMIIADDR_PA_SHIFT) & GMACMIIADDR_PA_MASK;
addr |= ((reg_addr as u32) << GMACMIIADDR_GR_SHIFT) & GMACMIIADDR_GR_MASK;
addr |= ((self.clock_divider.to_reg_value()) << GMACMIIADDR_CR_SHIFT) & GMACMIIADDR_CR_MASK;
if is_write {
addr |= GMACMIIADDR_GW;
}
addr |= GMACMIIADDR_GB;
addr
}
}
impl<D: DelayNs> MdioBus for MdioController<D> {
fn read(&mut self, phy_addr: u8, reg_addr: u8) -> Result<u16> {
if phy_addr > MAX_PHY_ADDR {
return Err(ConfigError::InvalidPhyAddress.into());
}
if reg_addr > MAX_REG_ADDR {
return Err(ConfigError::InvalidConfig.into());
}
self.wait_not_busy()?;
let addr = self.build_mii_addr(phy_addr, reg_addr, false);
MacRegs::set_mii_address(addr);
self.wait_not_busy()?;
let data = MacRegs::mii_data() & 0xFFFF;
Ok(data as u16)
}
fn write(&mut self, phy_addr: u8, reg_addr: u8, value: u16) -> Result<()> {
if phy_addr > MAX_PHY_ADDR {
return Err(ConfigError::InvalidPhyAddress.into());
}
if reg_addr > MAX_REG_ADDR {
return Err(ConfigError::InvalidConfig.into());
}
self.wait_not_busy()?;
MacRegs::set_mii_data(value as u32);
let addr = self.build_mii_addr(phy_addr, reg_addr, true);
MacRegs::set_mii_address(addr);
self.wait_not_busy()
}
fn is_busy(&self) -> bool {
(MacRegs::mii_address() & GMACMIIADDR_GB) != 0
}
}
#[derive(Debug, Clone, Copy, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PhyStatus {
pub link_up: bool,
pub an_complete: bool,
pub speed_100: bool,
pub full_duplex: bool,
}
pub fn read_phy_status<M: MdioBus>(mdio: &mut M, phy_addr: u8) -> Result<PhyStatus> {
let bmsr_val = mdio.read(phy_addr, phy_reg::BMSR)?;
let bmcr_val = mdio.read(phy_addr, phy_reg::BMCR)?;
Ok(PhyStatus {
link_up: (bmsr_val & bmsr::LINK_STATUS) != 0,
an_complete: (bmsr_val & bmsr::AN_COMPLETE) != 0,
speed_100: (bmcr_val & bmcr::SPEED_100) != 0,
full_duplex: (bmcr_val & bmcr::DUPLEX_FULL) != 0,
})
}
pub fn reset_phy<M: MdioBus>(mdio: &mut M, phy_addr: u8) -> Result<()> {
mdio.write(phy_addr, phy_reg::BMCR, bmcr::RESET)
}
pub fn read_phy_id<M: MdioBus>(mdio: &mut M, phy_addr: u8) -> Result<u32> {
let id1 = mdio.read(phy_addr, phy_reg::PHYIDR1)? as u32;
let id2 = mdio.read(phy_addr, phy_reg::PHYIDR2)? as u32;
Ok((id1 << 16) | id2)
}
pub fn enable_auto_negotiation<M: MdioBus>(mdio: &mut M, phy_addr: u8) -> Result<()> {
let bmcr_val = mdio.read(phy_addr, phy_reg::BMCR)?;
mdio.write(
phy_addr,
phy_reg::BMCR,
(bmcr_val | bmcr::AN_ENABLE | bmcr::AN_RESTART) & !bmcr::ISOLATE,
)
}
pub fn force_speed_duplex<M: MdioBus>(
mdio: &mut M,
phy_addr: u8,
speed_100: bool,
full_duplex: bool,
) -> Result<()> {
let mut bmcr_val = mdio.read(phy_addr, phy_reg::BMCR)?;
bmcr_val &= !bmcr::AN_ENABLE;
bmcr_val &= !bmcr::ISOLATE;
if speed_100 {
bmcr_val |= bmcr::SPEED_100;
} else {
bmcr_val &= !bmcr::SPEED_100;
}
if full_duplex {
bmcr_val |= bmcr::DUPLEX_FULL;
} else {
bmcr_val &= !bmcr::DUPLEX_FULL;
}
mdio.write(phy_addr, phy_reg::BMCR, bmcr_val)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::internal::phy_regs::standard::{anar, anlpar, bmcr, bmsr, phy_reg};
#[test]
fn clock_divider_from_sys_clock() {
assert_eq!(
MdcClockDivider::from_sys_clock_hz(20_000_000),
MdcClockDivider::Div16
);
assert_eq!(
MdcClockDivider::from_sys_clock_hz(40_000_000),
MdcClockDivider::Div26
);
assert_eq!(
MdcClockDivider::from_sys_clock_hz(80_000_000),
MdcClockDivider::Div42
);
assert_eq!(
MdcClockDivider::from_sys_clock_hz(160_000_000),
MdcClockDivider::Div102
);
assert_eq!(
MdcClockDivider::from_sys_clock_hz(280_000_000),
MdcClockDivider::Div124
);
}
#[test]
fn clock_divider_to_reg_value() {
assert_eq!(MdcClockDivider::Div42.to_reg_value(), 0);
assert_eq!(MdcClockDivider::Div62.to_reg_value(), 1);
assert_eq!(MdcClockDivider::Div16.to_reg_value(), 2);
assert_eq!(MdcClockDivider::Div26.to_reg_value(), 3);
assert_eq!(MdcClockDivider::Div102.to_reg_value(), 4);
assert_eq!(MdcClockDivider::Div124.to_reg_value(), 5);
}
#[test]
fn clock_divider_default() {
assert_eq!(MdcClockDivider::default(), MdcClockDivider::Div102);
}
#[test]
fn clock_divider_boundary_35mhz() {
assert_eq!(
MdcClockDivider::from_sys_clock_hz(34_999_999),
MdcClockDivider::Div16
);
assert_eq!(
MdcClockDivider::from_sys_clock_hz(35_000_000),
MdcClockDivider::Div26
);
}
#[test]
fn clock_divider_boundary_60mhz() {
assert_eq!(
MdcClockDivider::from_sys_clock_hz(59_999_999),
MdcClockDivider::Div26
);
assert_eq!(
MdcClockDivider::from_sys_clock_hz(60_000_000),
MdcClockDivider::Div42
);
}
#[test]
fn clock_divider_boundary_100mhz() {
assert_eq!(
MdcClockDivider::from_sys_clock_hz(99_999_999),
MdcClockDivider::Div42
);
assert_eq!(
MdcClockDivider::from_sys_clock_hz(100_000_000),
MdcClockDivider::Div62
);
}
#[test]
fn clock_divider_boundary_150mhz() {
assert_eq!(
MdcClockDivider::from_sys_clock_hz(149_999_999),
MdcClockDivider::Div62
);
assert_eq!(
MdcClockDivider::from_sys_clock_hz(150_000_000),
MdcClockDivider::Div102
);
}
#[test]
fn clock_divider_boundary_250mhz() {
assert_eq!(
MdcClockDivider::from_sys_clock_hz(249_999_999),
MdcClockDivider::Div102
);
assert_eq!(
MdcClockDivider::from_sys_clock_hz(250_000_000),
MdcClockDivider::Div124
);
}
#[test]
fn mdio_timeout_is_reasonable() {
assert!(MDIO_TIMEOUT_US > 0);
assert!(MDIO_TIMEOUT_US <= 10_000); }
#[test]
fn max_phy_addr_is_5_bits() {
assert_eq!(MAX_PHY_ADDR, 31);
assert!(MAX_PHY_ADDR < 32); }
#[test]
fn max_reg_addr_is_5_bits() {
assert_eq!(MAX_REG_ADDR, 31);
assert!(MAX_REG_ADDR < 32); }
#[test]
fn phy_reg_standard_addresses() {
assert_eq!(phy_reg::BMCR, 0);
assert_eq!(phy_reg::BMSR, 1);
assert_eq!(phy_reg::PHYIDR1, 2);
assert_eq!(phy_reg::PHYIDR2, 3);
assert_eq!(phy_reg::ANAR, 4);
assert_eq!(phy_reg::ANLPAR, 5);
}
#[test]
fn phy_reg_extended_addresses() {
assert_eq!(phy_reg::ANER, 6);
assert_eq!(phy_reg::ANNPTR, 7);
assert_eq!(phy_reg::ANNPRR, 8);
assert_eq!(phy_reg::MMD_CTRL, 13);
assert_eq!(phy_reg::MMD_DATA, 14);
assert_eq!(phy_reg::ESTATUS, 15);
}
#[test]
fn phy_reg_all_valid() {
assert!(phy_reg::BMCR < 32);
assert!(phy_reg::BMSR < 32);
assert!(phy_reg::PHYIDR1 < 32);
assert!(phy_reg::PHYIDR2 < 32);
assert!(phy_reg::ANAR < 32);
assert!(phy_reg::ANLPAR < 32);
assert!(phy_reg::ESTATUS < 32);
}
#[test]
fn bmcr_reset_bit() {
assert_eq!(bmcr::RESET, 0x8000);
}
#[test]
fn bmcr_bit_positions() {
assert_eq!(bmcr::RESET, 1 << 15);
assert_eq!(bmcr::LOOPBACK, 1 << 14);
assert_eq!(bmcr::SPEED_100, 1 << 13);
assert_eq!(bmcr::AN_ENABLE, 1 << 12);
assert_eq!(bmcr::POWER_DOWN, 1 << 11);
assert_eq!(bmcr::ISOLATE, 1 << 10);
assert_eq!(bmcr::AN_RESTART, 1 << 9);
assert_eq!(bmcr::DUPLEX_FULL, 1 << 8);
}
#[test]
fn bmcr_speed_duplex_bits() {
let bmcr_100fd = bmcr::SPEED_100 | bmcr::DUPLEX_FULL;
assert!(bmcr_100fd & bmcr::SPEED_100 != 0);
assert!(bmcr_100fd & bmcr::DUPLEX_FULL != 0);
let bmcr_10hd = 0u16;
assert!(bmcr_10hd & bmcr::SPEED_100 == 0);
assert!(bmcr_10hd & bmcr::DUPLEX_FULL == 0);
}
#[test]
fn bmcr_auto_neg_bits() {
let bmcr_an = bmcr::AN_ENABLE | bmcr::AN_RESTART;
assert!(bmcr_an & bmcr::AN_ENABLE != 0);
assert!(bmcr_an & bmcr::AN_RESTART != 0);
}
#[test]
fn bmcr_bits_are_distinct() {
let all_bits = bmcr::RESET
| bmcr::LOOPBACK
| bmcr::SPEED_100
| bmcr::AN_ENABLE
| bmcr::POWER_DOWN
| bmcr::ISOLATE
| bmcr::AN_RESTART
| bmcr::DUPLEX_FULL;
assert_eq!(all_bits.count_ones(), 8);
}
#[test]
fn bmsr_link_status_bit() {
let bmsr_up = 0x786D;
assert!(bmsr_up & bmsr::LINK_STATUS != 0);
let bmsr_down = 0x7869;
assert!(bmsr_down & bmsr::LINK_STATUS == 0);
}
#[test]
fn bmsr_auto_neg_complete_bit() {
let bmsr_complete = 0x0024;
assert!(bmsr_complete & bmsr::AN_COMPLETE != 0);
let bmsr_pending = 0x0004;
assert!(bmsr_pending & bmsr::AN_COMPLETE == 0);
}
#[test]
fn bmsr_capability_bits() {
let bmsr = bmsr::TX_FD_CAPABLE
| bmsr::TX_HD_CAPABLE
| bmsr::T10_FD_CAPABLE
| bmsr::T10_HD_CAPABLE
| bmsr::AN_ABILITY;
assert!(bmsr & bmsr::TX_FD_CAPABLE != 0);
assert!(bmsr & bmsr::TX_HD_CAPABLE != 0);
assert!(bmsr & bmsr::T10_FD_CAPABLE != 0);
assert!(bmsr & bmsr::T10_HD_CAPABLE != 0);
assert!(bmsr & bmsr::AN_ABILITY != 0);
}
#[test]
fn bmsr_bit_positions() {
assert_eq!(bmsr::T4_CAPABLE, 1 << 15);
assert_eq!(bmsr::TX_FD_CAPABLE, 1 << 14);
assert_eq!(bmsr::TX_HD_CAPABLE, 1 << 13);
assert_eq!(bmsr::T10_FD_CAPABLE, 1 << 12);
assert_eq!(bmsr::T10_HD_CAPABLE, 1 << 11);
assert_eq!(bmsr::ESTATUS, 1 << 8);
assert_eq!(bmsr::AN_COMPLETE, 1 << 5);
assert_eq!(bmsr::REMOTE_FAULT, 1 << 4);
assert_eq!(bmsr::AN_ABILITY, 1 << 3);
assert_eq!(bmsr::LINK_STATUS, 1 << 2);
assert_eq!(bmsr::JABBER_DETECT, 1 << 1);
assert_eq!(bmsr::EXT_CAPABLE, 1 << 0);
}
#[test]
fn anar_bit_positions() {
assert_eq!(anar::NEXT_PAGE, 1 << 15);
assert_eq!(anar::ACK, 1 << 14);
assert_eq!(anar::REMOTE_FAULT, 1 << 13);
assert_eq!(anar::PAUSE, 1 << 10);
assert_eq!(anar::T4, 1 << 9);
assert_eq!(anar::TX_FD, 1 << 8);
assert_eq!(anar::TX_HD, 1 << 7);
assert_eq!(anar::T10_FD, 1 << 6);
assert_eq!(anar::T10_HD, 1 << 5);
}
#[test]
fn anar_selector_field() {
assert_eq!(anar::SELECTOR, 0x001F);
assert_eq!(anar::SELECTOR_IEEE802_3, 0x0001);
}
#[test]
fn anar_full_advertisement() {
let anar_val =
anar::TX_FD | anar::TX_HD | anar::T10_FD | anar::T10_HD | anar::SELECTOR_IEEE802_3;
assert!(anar_val & anar::TX_FD != 0);
assert!(anar_val & anar::TX_HD != 0);
assert!(anar_val & anar::T10_FD != 0);
assert!(anar_val & anar::T10_HD != 0);
assert_eq!(anar_val & anar::SELECTOR, anar::SELECTOR_IEEE802_3);
}
#[test]
fn anlpar_100_fd_parsing() {
let anlpar_val = anlpar::CAN_100_FD | anlpar::SELECTOR_802_3;
assert!(anlpar_val & anlpar::CAN_100_FD != 0);
assert!(anlpar_val & anlpar::CAN_100_HD == 0);
}
#[test]
fn anlpar_10_hd_parsing() {
let anlpar_val = anlpar::CAN_10_HD | anlpar::SELECTOR_802_3;
assert!(anlpar_val & anlpar::CAN_10_HD != 0);
assert!(anlpar_val & anlpar::CAN_100_FD == 0);
assert!(anlpar_val & anlpar::CAN_100_HD == 0);
assert!(anlpar_val & anlpar::CAN_10_FD == 0);
}
#[test]
fn anlpar_full_capability() {
let anlpar_val = anlpar::CAN_100_FD
| anlpar::CAN_100_HD
| anlpar::CAN_10_FD
| anlpar::CAN_10_HD
| anlpar::PAUSE
| anlpar::SELECTOR_802_3;
let can_100_fd = anlpar_val & anlpar::CAN_100_FD != 0;
let can_100_hd = anlpar_val & anlpar::CAN_100_HD != 0;
let can_10_fd = anlpar_val & anlpar::CAN_10_FD != 0;
let can_10_hd = anlpar_val & anlpar::CAN_10_HD != 0;
assert!(can_100_fd);
assert!(can_100_hd);
assert!(can_10_fd);
assert!(can_10_hd);
}
#[test]
fn anlpar_pause_capability() {
let with_pause = anlpar::PAUSE | anlpar::SELECTOR_802_3;
let without_pause = anlpar::SELECTOR_802_3;
assert!(with_pause & anlpar::PAUSE != 0);
assert!(without_pause & anlpar::PAUSE == 0);
}
#[test]
fn anlpar_bit_positions() {
assert_eq!(anlpar::NEXT_PAGE, 1 << 15);
assert_eq!(anlpar::ACK, 1 << 14);
assert_eq!(anlpar::REMOTE_FAULT, 1 << 13);
assert_eq!(anlpar::PAUSE_ASYM, 1 << 11);
assert_eq!(anlpar::PAUSE, 1 << 10);
assert_eq!(anlpar::CAN_100_T4, 1 << 9);
assert_eq!(anlpar::CAN_100_FD, 1 << 8);
assert_eq!(anlpar::CAN_100_HD, 1 << 7);
assert_eq!(anlpar::CAN_10_FD, 1 << 6);
assert_eq!(anlpar::CAN_10_HD, 1 << 5);
}
#[test]
fn anlpar_selector_field() {
assert_eq!(anlpar::SELECTOR_MASK, 0x001F);
assert_eq!(anlpar::SELECTOR_802_3, 0x0001);
}
#[test]
fn phy_status_default() {
let status = PhyStatus::default();
assert!(!status.link_up);
assert!(!status.an_complete);
assert!(!status.speed_100);
assert!(!status.full_duplex);
}
#[test]
fn phy_status_all_true() {
let status = PhyStatus {
link_up: true,
an_complete: true,
speed_100: true,
full_duplex: true,
};
assert!(status.link_up);
assert!(status.an_complete);
assert!(status.speed_100);
assert!(status.full_duplex);
}
#[test]
fn phy_status_partial() {
let status = PhyStatus {
link_up: true,
an_complete: false,
speed_100: true,
full_duplex: false,
};
assert!(status.link_up);
assert!(!status.an_complete);
assert!(status.speed_100);
assert!(!status.full_duplex);
}
#[test]
fn phy_status_clone() {
let status = PhyStatus {
link_up: true,
an_complete: true,
speed_100: true,
full_duplex: true,
};
let cloned = status;
assert_eq!(cloned.link_up, status.link_up);
assert_eq!(cloned.an_complete, status.an_complete);
assert_eq!(cloned.speed_100, status.speed_100);
assert_eq!(cloned.full_duplex, status.full_duplex);
}
#[test]
fn phy_status_10_half() {
let status = PhyStatus {
link_up: true,
an_complete: true,
speed_100: false,
full_duplex: false,
};
assert!(status.link_up);
assert!(!status.speed_100);
assert!(!status.full_duplex);
}
#[test]
fn phy_status_100_half() {
let status = PhyStatus {
link_up: true,
an_complete: true,
speed_100: true,
full_duplex: false,
};
assert!(status.link_up);
assert!(status.speed_100);
assert!(!status.full_duplex);
}
#[test]
fn phy_status_10_full() {
let status = PhyStatus {
link_up: true,
an_complete: true,
speed_100: false,
full_duplex: true,
};
assert!(status.link_up);
assert!(!status.speed_100);
assert!(status.full_duplex);
}
}