use embedded_hal::delay::DelayNs;
use embedded_hal::digital::OutputPin;
use crate::driver::error::Result;
use crate::hal::mdio::MdioBus;
use crate::internal::phy_regs::lan8720a as regs_int;
use super::generic::{LinkStatus, PhyCapabilities, PhyDriver, ieee802_3};
pub const LAN8720A_PHY_ID: u32 = regs_int::phy_id::ID;
pub const LAN8720A_PHY_ID_MASK: u32 = regs_int::phy_id::MASK;
use regs_int::timing::AN_MAX_ATTEMPTS;
use regs_int::timing::RESET_MAX_ATTEMPTS;
use regs_int::timing::RESET_PULSE_US;
use regs_int::timing::RESET_RECOVERY_US;
pub mod reg {
use super::regs_int::reg as reg_int;
pub const MCSR: u8 = reg_int::MCSR;
pub const SMR: u8 = reg_int::SMR;
pub const SECR: u8 = reg_int::SECR;
pub const SCSIR: u8 = reg_int::SCSIR;
pub const ISR: u8 = reg_int::ISR;
pub const IMR: u8 = reg_int::IMR;
pub const PSCSR: u8 = reg_int::PSCSR;
}
pub mod mcsr {
use super::regs_int::mcsr as mcsr_int;
pub const EDPWRDOWN: u16 = mcsr_int::EDPWRDOWN;
pub const FARLOOPBACK: u16 = mcsr_int::FARLOOPBACK;
pub const ALTINT: u16 = mcsr_int::ALTINT;
pub const ENERGYON: u16 = mcsr_int::ENERGYON;
}
pub mod smr {
use super::regs_int::smr as smr_int;
pub const MODE_MASK: u16 = smr_int::MODE_MASK;
pub const MODE_10HD: u16 = smr_int::MODE_10HD;
pub const MODE_10FD: u16 = smr_int::MODE_10FD;
pub const MODE_100HD: u16 = smr_int::MODE_100HD;
pub const MODE_100FD: u16 = smr_int::MODE_100FD;
pub const MODE_100HD_AN: u16 = smr_int::MODE_100HD_AN;
pub const MODE_REPEATER: u16 = smr_int::MODE_REPEATER;
pub const MODE_PWRDOWN: u16 = smr_int::MODE_PWRDOWN;
pub const MODE_ALL_AN: u16 = smr_int::MODE_ALL_AN;
pub const PHYAD_MASK: u16 = smr_int::PHYAD_MASK;
}
pub mod scsir {
use super::regs_int::scsir as scsir_int;
pub const AMDIXCTRL: u16 = scsir_int::AMDIXCTRL;
pub const CH_SELECT: u16 = scsir_int::CH_SELECT;
pub const SQEOFF: u16 = scsir_int::SQEOFF;
pub const XPOL: u16 = scsir_int::XPOL;
}
pub mod isr {
use super::regs_int::isr as isr_int;
pub const ENERGYON: u16 = isr_int::ENERGYON;
pub const AN_COMPLETE: u16 = isr_int::AN_COMPLETE;
pub const REMOTE_FAULT: u16 = isr_int::REMOTE_FAULT;
pub const LINK_DOWN: u16 = isr_int::LINK_DOWN;
pub const AN_LP_ACK: u16 = isr_int::AN_LP_ACK;
pub const PD_FAULT: u16 = isr_int::PD_FAULT;
pub const AN_PAGE_RX: u16 = isr_int::AN_PAGE_RX;
}
pub mod pscsr {
use super::regs_int::pscsr as pscsr_int;
pub const AUTODONE: u16 = pscsr_int::AUTODONE;
pub const HCDSPEED_MASK: u16 = pscsr_int::HCDSPEED_MASK;
pub const HCDSPEED_10HD: u16 = pscsr_int::HCDSPEED_10HD;
pub const HCDSPEED_10FD: u16 = pscsr_int::HCDSPEED_10FD;
pub const HCDSPEED_100HD: u16 = pscsr_int::HCDSPEED_100HD;
pub const HCDSPEED_100FD: u16 = pscsr_int::HCDSPEED_100FD;
}
#[derive(Debug)]
pub struct Lan8720a {
addr: u8,
last_link_up: bool,
}
impl Lan8720a {
pub const fn new(addr: u8) -> Self {
Self {
addr,
last_link_up: false,
}
}
pub fn verify_id<M: MdioBus>(&self, mdio: &mut M) -> Result<bool> {
let id = ieee802_3::read_phy_id(mdio, self.addr)?;
Ok((id & LAN8720A_PHY_ID_MASK) == LAN8720A_PHY_ID)
}
pub fn revision<M: MdioBus>(&self, mdio: &mut M) -> Result<u8> {
let id = ieee802_3::read_phy_id(mdio, self.addr)?;
Ok((id & 0x0F) as u8)
}
pub fn read_speed_indication<M: MdioBus>(&self, mdio: &mut M) -> Result<Option<LinkStatus>> {
let pscsr = mdio.read(self.addr, reg::PSCSR)?;
if (pscsr & pscsr::AUTODONE) == 0 {
return Ok(None);
}
let speed_bits = pscsr & pscsr::HCDSPEED_MASK;
let link = match speed_bits {
x if x == pscsr::HCDSPEED_100FD => LinkStatus::fast_full(),
x if x == pscsr::HCDSPEED_100HD => LinkStatus::fast_half(),
x if x == pscsr::HCDSPEED_10FD => LinkStatus::slow_full(),
x if x == pscsr::HCDSPEED_10HD => LinkStatus::slow_half(),
_ => return Ok(None), };
Ok(Some(link))
}
pub fn set_energy_detect_powerdown<M: MdioBus>(
&mut self,
mdio: &mut M,
enabled: bool,
) -> Result<()> {
let mut mcsr = mdio.read(self.addr, reg::MCSR)?;
if enabled {
mcsr |= mcsr::EDPWRDOWN;
} else {
mcsr &= !mcsr::EDPWRDOWN;
}
mdio.write(self.addr, reg::MCSR, mcsr)
}
pub fn is_energy_on<M: MdioBus>(&self, mdio: &mut M) -> Result<bool> {
let mcsr = mdio.read(self.addr, reg::MCSR)?;
Ok((mcsr & mcsr::ENERGYON) != 0)
}
pub fn read_interrupt_status<M: MdioBus>(&self, mdio: &mut M) -> Result<u16> {
mdio.read(self.addr, reg::ISR)
}
pub fn set_interrupt_mask<M: MdioBus>(&mut self, mdio: &mut M, mask: u16) -> Result<()> {
mdio.write(self.addr, reg::IMR, mask)
}
pub fn enable_link_interrupt<M: MdioBus>(&mut self, mdio: &mut M) -> Result<()> {
let mask = isr::LINK_DOWN | isr::AN_COMPLETE;
self.set_interrupt_mask(mdio, mask)
}
pub fn symbol_error_count<M: MdioBus>(&self, mdio: &mut M) -> Result<u16> {
mdio.read(self.addr, reg::SECR)
}
pub fn configure_advertisement<M: MdioBus>(
&mut self,
mdio: &mut M,
caps: &PhyCapabilities,
) -> Result<()> {
use crate::internal::phy_regs::standard::{anar, phy_reg};
let mut anar_val = anar::SELECTOR_IEEE802_3;
if caps.speed_100_fd {
anar_val |= anar::TX_FD;
}
if caps.speed_100_hd {
anar_val |= anar::TX_HD;
}
if caps.speed_10_fd {
anar_val |= anar::T10_FD;
}
if caps.speed_10_hd {
anar_val |= anar::T10_HD;
}
if caps.pause {
anar_val |= anar::PAUSE;
}
mdio.write(self.addr, phy_reg::ANAR, anar_val)
}
}
impl PhyDriver for Lan8720a {
fn address(&self) -> u8 {
self.addr
}
fn init<M: MdioBus>(&mut self, mdio: &mut M) -> Result<()> {
self.soft_reset(mdio)?;
self.set_energy_detect_powerdown(mdio, false)?;
self.enable_auto_negotiation(mdio)?;
self.last_link_up = false;
Ok(())
}
fn soft_reset<M: MdioBus>(&mut self, mdio: &mut M) -> Result<()> {
ieee802_3::soft_reset(mdio, self.addr, RESET_MAX_ATTEMPTS)
}
fn is_link_up<M: MdioBus>(&self, mdio: &mut M) -> Result<bool> {
ieee802_3::is_link_up(mdio, self.addr)
}
fn link_status<M: MdioBus>(&self, mdio: &mut M) -> Result<Option<LinkStatus>> {
if !self.is_link_up(mdio)? {
return Ok(None);
}
self.read_speed_indication(mdio)
}
fn poll_link<M: MdioBus>(&mut self, mdio: &mut M) -> Result<Option<LinkStatus>> {
let link_up = self.is_link_up(mdio)?;
if link_up && !self.last_link_up {
self.last_link_up = true;
return self.read_speed_indication(mdio);
}
if !link_up && self.last_link_up {
self.last_link_up = false;
}
Ok(None)
}
fn enable_auto_negotiation<M: MdioBus>(&mut self, mdio: &mut M) -> Result<()> {
let caps = PhyCapabilities::standard_10_100();
self.configure_advertisement(mdio, &caps)?;
ieee802_3::enable_auto_negotiation(mdio, self.addr)
}
fn force_link<M: MdioBus>(&mut self, mdio: &mut M, status: LinkStatus) -> Result<()> {
ieee802_3::force_link(mdio, self.addr, status)
}
fn capabilities<M: MdioBus>(&self, mdio: &mut M) -> Result<PhyCapabilities> {
ieee802_3::read_capabilities(mdio, self.addr)
}
fn phy_id<M: MdioBus>(&self, mdio: &mut M) -> Result<u32> {
ieee802_3::read_phy_id(mdio, self.addr)
}
fn is_auto_negotiation_complete<M: MdioBus>(&self, mdio: &mut M) -> Result<bool> {
ieee802_3::is_an_complete(mdio, self.addr)
}
fn link_partner_abilities<M: MdioBus>(&self, mdio: &mut M) -> Result<PhyCapabilities> {
ieee802_3::read_link_partner(mdio, self.addr)
}
}
#[derive(Debug)]
pub struct Lan8720aWithReset<RST: OutputPin> {
inner: Lan8720a,
reset_pin: RST,
}
impl<RST: OutputPin> Lan8720aWithReset<RST> {
pub fn new(addr: u8, mut reset_pin: RST) -> Self {
let _ = reset_pin.set_high();
Self {
inner: Lan8720a::new(addr),
reset_pin,
}
}
pub fn hardware_reset<D: DelayNs>(&mut self, delay: &mut D) -> Result<()> {
self.reset_pin
.set_low()
.map_err(|_| crate::driver::error::ConfigError::GpioError)?;
delay.delay_us(RESET_PULSE_US);
self.reset_pin
.set_high()
.map_err(|_| crate::driver::error::ConfigError::GpioError)?;
delay.delay_us(RESET_RECOVERY_US);
Ok(())
}
pub fn assert_reset(&mut self) -> Result<()> {
self.reset_pin
.set_low()
.map_err(|_| crate::driver::error::ConfigError::GpioError)?;
Ok(())
}
pub fn deassert_reset(&mut self) -> Result<()> {
self.reset_pin
.set_high()
.map_err(|_| crate::driver::error::ConfigError::GpioError)?;
Ok(())
}
pub fn reset_pin_mut(&mut self) -> &mut RST {
&mut self.reset_pin
}
pub fn into_reset_pin(self) -> RST {
self.reset_pin
}
pub fn verify_id<M: MdioBus>(&self, mdio: &mut M) -> Result<bool> {
self.inner.verify_id(mdio)
}
pub fn revision<M: MdioBus>(&self, mdio: &mut M) -> Result<u8> {
self.inner.revision(mdio)
}
pub fn read_speed_indication<M: MdioBus>(&self, mdio: &mut M) -> Result<Option<LinkStatus>> {
self.inner.read_speed_indication(mdio)
}
pub fn set_energy_detect_powerdown<M: MdioBus>(
&mut self,
mdio: &mut M,
enabled: bool,
) -> Result<()> {
self.inner.set_energy_detect_powerdown(mdio, enabled)
}
pub fn is_energy_on<M: MdioBus>(&self, mdio: &mut M) -> Result<bool> {
self.inner.is_energy_on(mdio)
}
pub fn read_interrupt_status<M: MdioBus>(&self, mdio: &mut M) -> Result<u16> {
self.inner.read_interrupt_status(mdio)
}
pub fn set_interrupt_mask<M: MdioBus>(&mut self, mdio: &mut M, mask: u16) -> Result<()> {
self.inner.set_interrupt_mask(mdio, mask)
}
pub fn enable_link_interrupt<M: MdioBus>(&mut self, mdio: &mut M) -> Result<()> {
self.inner.enable_link_interrupt(mdio)
}
pub fn symbol_error_count<M: MdioBus>(&self, mdio: &mut M) -> Result<u16> {
self.inner.symbol_error_count(mdio)
}
pub fn configure_advertisement<M: MdioBus>(
&mut self,
mdio: &mut M,
caps: &PhyCapabilities,
) -> Result<()> {
self.inner.configure_advertisement(mdio, caps)
}
}
impl<RST: OutputPin> PhyDriver for Lan8720aWithReset<RST> {
fn address(&self) -> u8 {
self.inner.address()
}
fn init<M: MdioBus>(&mut self, mdio: &mut M) -> Result<()> {
self.inner.init(mdio)
}
fn soft_reset<M: MdioBus>(&mut self, mdio: &mut M) -> Result<()> {
self.inner.soft_reset(mdio)
}
fn is_link_up<M: MdioBus>(&self, mdio: &mut M) -> Result<bool> {
self.inner.is_link_up(mdio)
}
fn link_status<M: MdioBus>(&self, mdio: &mut M) -> Result<Option<LinkStatus>> {
self.inner.link_status(mdio)
}
fn poll_link<M: MdioBus>(&mut self, mdio: &mut M) -> Result<Option<LinkStatus>> {
self.inner.poll_link(mdio)
}
fn enable_auto_negotiation<M: MdioBus>(&mut self, mdio: &mut M) -> Result<()> {
self.inner.enable_auto_negotiation(mdio)
}
fn force_link<M: MdioBus>(&mut self, mdio: &mut M, status: LinkStatus) -> Result<()> {
self.inner.force_link(mdio, status)
}
fn capabilities<M: MdioBus>(&self, mdio: &mut M) -> Result<PhyCapabilities> {
self.inner.capabilities(mdio)
}
fn phy_id<M: MdioBus>(&self, mdio: &mut M) -> Result<u32> {
self.inner.phy_id(mdio)
}
fn is_auto_negotiation_complete<M: MdioBus>(&self, mdio: &mut M) -> Result<bool> {
self.inner.is_auto_negotiation_complete(mdio)
}
fn link_partner_abilities<M: MdioBus>(&self, mdio: &mut M) -> Result<PhyCapabilities> {
self.inner.link_partner_abilities(mdio)
}
}
pub fn wait_for_link<M: MdioBus>(phy: &mut Lan8720a, mdio: &mut M) -> Result<Option<LinkStatus>> {
for _ in 0..AN_MAX_ATTEMPTS {
if let Some(link) = phy.poll_link(mdio)? {
return Ok(Some(link));
}
core::hint::spin_loop();
}
Ok(None)
}
pub fn scan_bus<M: MdioBus>(mdio: &mut M) -> Result<[Option<u8>; 32]> {
let mut found = [None; 32];
for addr in 0..32 {
let phy = Lan8720a::new(addr);
if phy.verify_id(mdio).unwrap_or(false) {
found[addr as usize] = Some(addr);
}
}
Ok(found)
}
#[cfg(test)]
#[allow(clippy::std_instead_of_alloc)]
mod tests {
extern crate std;
use super::*;
use crate::driver::config::{Duplex, Speed};
use crate::internal::phy_regs::standard::{bmcr, phy_reg};
use crate::testing::MockMdioBus;
use std::vec::Vec;
#[test]
fn test_phy_id_check() {
assert!((0x0007_C0F0 & LAN8720A_PHY_ID_MASK) == LAN8720A_PHY_ID);
assert!((0x0007_C0F1 & LAN8720A_PHY_ID_MASK) == LAN8720A_PHY_ID); assert!((0x0007_C0FF & LAN8720A_PHY_ID_MASK) == LAN8720A_PHY_ID);
assert!((0x0022_1555 & LAN8720A_PHY_ID_MASK) != LAN8720A_PHY_ID); assert!(LAN8720A_PHY_ID_MASK != LAN8720A_PHY_ID); assert!((0x0001_0000 & LAN8720A_PHY_ID_MASK) != LAN8720A_PHY_ID); }
#[test]
fn test_speed_indication() {
assert_eq!(pscsr::HCDSPEED_10HD, 0x04);
assert_eq!(pscsr::HCDSPEED_10FD, 0x14);
assert_eq!(pscsr::HCDSPEED_100HD, 0x08);
assert_eq!(pscsr::HCDSPEED_100FD, 0x18);
}
#[test]
fn test_init_performs_soft_reset() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
mdio.set_register(0, phy_reg::BMCR, 0x0000);
let mut phy = Lan8720a::new(0);
phy.init(&mut mdio).unwrap();
let writes = mdio.get_writes();
let reset_writes: Vec<_> = writes
.iter()
.filter(|(addr, reg, val)| {
*addr == 0 && *reg == phy_reg::BMCR && (*val & bmcr::RESET) != 0
})
.collect();
assert!(!reset_writes.is_empty(), "Expected BMCR.RESET write");
}
#[test]
fn test_init_disables_energy_detect_powerdown() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
mdio.set_register(0, reg::MCSR, mcsr::EDPWRDOWN);
let mut phy = Lan8720a::new(0);
phy.init(&mut mdio).unwrap();
let mcsr_val = mdio.get_register(0, reg::MCSR).unwrap();
assert_eq!(
mcsr_val & mcsr::EDPWRDOWN,
0,
"EDPWRDOWN should be disabled"
);
}
#[test]
fn test_init_enables_auto_negotiation() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
let mut phy = Lan8720a::new(0);
phy.init(&mut mdio).unwrap();
let bmcr_val = mdio.get_register(0, phy_reg::BMCR).unwrap();
assert!(bmcr_val & bmcr::AN_ENABLE != 0, "AN_ENABLE should be set");
}
#[test]
fn test_init_resets_link_state() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
mdio.simulate_link_up_100_fd(0);
let mut phy = Lan8720a::new(0);
phy.last_link_up = true;
phy.init(&mut mdio).unwrap();
assert!(!phy.last_link_up, "last_link_up should be reset after init");
}
#[test]
fn test_soft_reset_writes_reset_bit() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
mdio.set_register(0, phy_reg::BMCR, 0x0000);
let mut phy = Lan8720a::new(0);
phy.soft_reset(&mut mdio).unwrap();
let writes = mdio.get_writes();
let first_write = writes.first().unwrap();
assert_eq!(first_write.0, 0);
assert_eq!(first_write.1, phy_reg::BMCR);
assert!(first_write.2 & bmcr::RESET != 0, "Should write RESET bit");
}
#[test]
fn test_soft_reset_waits_for_reset_clear() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
mdio.set_register(0, phy_reg::BMCR, bmcr::RESET);
let mut phy = Lan8720a::new(0);
phy.soft_reset(&mut mdio).unwrap();
}
#[test]
fn test_is_link_up_when_link_down() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
let phy = Lan8720a::new(0);
assert!(!phy.is_link_up(&mut mdio).unwrap());
}
#[test]
fn test_is_link_up_when_link_up() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
mdio.simulate_link_up_100_fd(0);
let phy = Lan8720a::new(0);
assert!(phy.is_link_up(&mut mdio).unwrap());
}
#[test]
fn test_link_status_returns_none_when_down() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
let phy = Lan8720a::new(0);
assert!(phy.link_status(&mut mdio).unwrap().is_none());
}
#[test]
fn test_link_status_returns_speed_duplex_when_up() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
mdio.simulate_link_up_100_fd(0);
mdio.set_register(0, reg::PSCSR, pscsr::AUTODONE | pscsr::HCDSPEED_100FD);
let phy = Lan8720a::new(0);
let status = phy.link_status(&mut mdio).unwrap().unwrap();
assert_eq!(status.speed, Speed::Mbps100);
assert_eq!(status.duplex, Duplex::Full);
}
#[test]
fn test_poll_link_returns_none_when_link_stays_down() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
let mut phy = Lan8720a::new(0);
assert!(phy.poll_link(&mut mdio).unwrap().is_none());
assert!(phy.poll_link(&mut mdio).unwrap().is_none());
}
#[test]
fn test_poll_link_returns_status_on_link_up_transition() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
mdio.set_register(0, reg::PSCSR, pscsr::AUTODONE | pscsr::HCDSPEED_100FD);
let mut phy = Lan8720a::new(0);
assert!(phy.poll_link(&mut mdio).unwrap().is_none());
mdio.simulate_link_up_100_fd(0);
let status = phy.poll_link(&mut mdio).unwrap();
assert!(status.is_some());
let link = status.unwrap();
assert_eq!(link.speed, Speed::Mbps100);
assert_eq!(link.duplex, Duplex::Full);
}
#[test]
fn test_poll_link_returns_none_when_link_stays_up() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
mdio.simulate_link_up_100_fd(0);
mdio.set_register(0, reg::PSCSR, pscsr::AUTODONE | pscsr::HCDSPEED_100FD);
let mut phy = Lan8720a::new(0);
let first = phy.poll_link(&mut mdio).unwrap();
assert!(first.is_some());
let second = phy.poll_link(&mut mdio).unwrap();
assert!(second.is_none());
}
#[test]
fn test_poll_link_tracks_link_down_transition() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
mdio.simulate_link_up_100_fd(0);
mdio.set_register(0, reg::PSCSR, pscsr::AUTODONE | pscsr::HCDSPEED_100FD);
let mut phy = Lan8720a::new(0);
let _ = phy.poll_link(&mut mdio).unwrap();
mdio.simulate_link_down(0);
assert!(phy.poll_link(&mut mdio).unwrap().is_none());
assert!(!phy.last_link_up);
}
#[test]
fn test_poll_link_detects_link_flap() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
mdio.set_register(0, reg::PSCSR, pscsr::AUTODONE | pscsr::HCDSPEED_100FD);
let mut phy = Lan8720a::new(0);
mdio.simulate_link_up_100_fd(0);
assert!(phy.poll_link(&mut mdio).unwrap().is_some());
mdio.simulate_link_down(0);
assert!(phy.poll_link(&mut mdio).unwrap().is_none());
mdio.simulate_link_up_100_fd(0);
assert!(phy.poll_link(&mut mdio).unwrap().is_some());
}
#[test]
fn test_enable_auto_negotiation_writes_anar() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
let mut phy = Lan8720a::new(0);
phy.enable_auto_negotiation(&mut mdio).unwrap();
let writes = mdio.get_writes();
let anar_writes: Vec<_> = writes
.iter()
.filter(|(_, reg, _)| *reg == phy_reg::ANAR)
.collect();
assert!(!anar_writes.is_empty(), "Expected ANAR write");
}
#[test]
fn test_enable_auto_negotiation_restarts_an() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
let mut phy = Lan8720a::new(0);
phy.enable_auto_negotiation(&mut mdio).unwrap();
let bmcr_val = mdio.get_register(0, phy_reg::BMCR).unwrap();
assert!(bmcr_val & bmcr::AN_ENABLE != 0, "AN_ENABLE should be set");
assert!(bmcr_val & bmcr::AN_RESTART != 0, "AN_RESTART should be set");
}
#[test]
fn test_is_auto_negotiation_complete_when_not_done() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
let phy = Lan8720a::new(0);
assert!(!phy.is_auto_negotiation_complete(&mut mdio).unwrap());
}
#[test]
fn test_is_auto_negotiation_complete_when_done() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
mdio.simulate_link_up_100_fd(0);
let phy = Lan8720a::new(0);
assert!(phy.is_auto_negotiation_complete(&mut mdio).unwrap());
}
#[test]
fn test_force_link_disables_auto_negotiation() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
mdio.set_register(0, phy_reg::BMCR, bmcr::AN_ENABLE);
let mut phy = Lan8720a::new(0);
phy.force_link(&mut mdio, LinkStatus::fast_full()).unwrap();
let bmcr_val = mdio.get_register(0, phy_reg::BMCR).unwrap();
assert_eq!(bmcr_val & bmcr::AN_ENABLE, 0, "AN should be disabled");
}
#[test]
fn test_force_link_100_full() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
let mut phy = Lan8720a::new(0);
phy.force_link(&mut mdio, LinkStatus::fast_full()).unwrap();
let bmcr_val = mdio.get_register(0, phy_reg::BMCR).unwrap();
assert!(bmcr_val & bmcr::SPEED_100 != 0, "SPEED_100 should be set");
assert!(
bmcr_val & bmcr::DUPLEX_FULL != 0,
"DUPLEX_FULL should be set"
);
}
#[test]
fn test_force_link_10_half() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
let mut phy = Lan8720a::new(0);
phy.force_link(&mut mdio, LinkStatus::slow_half()).unwrap();
let bmcr_val = mdio.get_register(0, phy_reg::BMCR).unwrap();
assert_eq!(bmcr_val & bmcr::SPEED_100, 0, "SPEED_100 should be clear");
assert_eq!(
bmcr_val & bmcr::DUPLEX_FULL,
0,
"DUPLEX_FULL should be clear"
);
}
#[test]
fn test_force_link_100_half() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
let mut phy = Lan8720a::new(0);
phy.force_link(&mut mdio, LinkStatus::fast_half()).unwrap();
let bmcr_val = mdio.get_register(0, phy_reg::BMCR).unwrap();
assert!(bmcr_val & bmcr::SPEED_100 != 0, "SPEED_100 should be set");
assert_eq!(
bmcr_val & bmcr::DUPLEX_FULL,
0,
"DUPLEX_FULL should be clear"
);
}
#[test]
fn test_force_link_10_full() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
let mut phy = Lan8720a::new(0);
phy.force_link(&mut mdio, LinkStatus::slow_full()).unwrap();
let bmcr_val = mdio.get_register(0, phy_reg::BMCR).unwrap();
assert_eq!(bmcr_val & bmcr::SPEED_100, 0, "SPEED_100 should be clear");
assert!(
bmcr_val & bmcr::DUPLEX_FULL != 0,
"DUPLEX_FULL should be set"
);
}
#[test]
fn test_read_speed_indication_when_an_not_done() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
mdio.set_register(0, reg::PSCSR, 0x0000);
let phy = Lan8720a::new(0);
assert!(phy.read_speed_indication(&mut mdio).unwrap().is_none());
}
#[test]
fn test_read_speed_indication_100fd() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
mdio.set_register(0, reg::PSCSR, pscsr::AUTODONE | pscsr::HCDSPEED_100FD);
let phy = Lan8720a::new(0);
let status = phy.read_speed_indication(&mut mdio).unwrap().unwrap();
assert_eq!(status.speed, Speed::Mbps100);
assert_eq!(status.duplex, Duplex::Full);
}
#[test]
fn test_read_speed_indication_100hd() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
mdio.set_register(0, reg::PSCSR, pscsr::AUTODONE | pscsr::HCDSPEED_100HD);
let phy = Lan8720a::new(0);
let status = phy.read_speed_indication(&mut mdio).unwrap().unwrap();
assert_eq!(status.speed, Speed::Mbps100);
assert_eq!(status.duplex, Duplex::Half);
}
#[test]
fn test_read_speed_indication_10fd() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
mdio.set_register(0, reg::PSCSR, pscsr::AUTODONE | pscsr::HCDSPEED_10FD);
let phy = Lan8720a::new(0);
let status = phy.read_speed_indication(&mut mdio).unwrap().unwrap();
assert_eq!(status.speed, Speed::Mbps10);
assert_eq!(status.duplex, Duplex::Full);
}
#[test]
fn test_read_speed_indication_10hd() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
mdio.set_register(0, reg::PSCSR, pscsr::AUTODONE | pscsr::HCDSPEED_10HD);
let phy = Lan8720a::new(0);
let status = phy.read_speed_indication(&mut mdio).unwrap().unwrap();
assert_eq!(status.speed, Speed::Mbps10);
assert_eq!(status.duplex, Duplex::Half);
}
#[test]
fn test_verify_id_returns_true_for_lan8720a() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
let phy = Lan8720a::new(0);
assert!(phy.verify_id(&mut mdio).unwrap());
}
#[test]
fn test_verify_id_returns_false_for_other_phy() {
let mut mdio = MockMdioBus::new();
mdio.set_register(0, phy_reg::PHYIDR1, 0x0022);
mdio.set_register(0, phy_reg::PHYIDR2, 0x1555);
let phy = Lan8720a::new(0);
assert!(!phy.verify_id(&mut mdio).unwrap());
}
#[test]
fn test_phy_id_reads_both_registers() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
let phy = Lan8720a::new(0);
let id = phy.phy_id(&mut mdio).unwrap();
assert_eq!(id >> 16, 0x0007);
assert_eq!(id & 0xFFFF, 0xC0F1);
}
#[test]
fn test_revision_extracts_low_bits() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
let phy = Lan8720a::new(0);
let rev = phy.revision(&mut mdio).unwrap();
assert_eq!(rev, 1);
}
#[test]
fn test_capabilities_reads_from_bmsr() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
let phy = Lan8720a::new(0);
let caps = phy.capabilities(&mut mdio).unwrap();
assert!(caps.speed_100_fd);
assert!(caps.speed_100_hd);
assert!(caps.speed_10_fd);
assert!(caps.speed_10_hd);
assert!(caps.auto_negotiation);
}
#[test]
fn test_link_partner_abilities_after_an() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
mdio.simulate_link_up_100_fd(0);
let phy = Lan8720a::new(0);
let partner = phy.link_partner_abilities(&mut mdio).unwrap();
assert!(partner.speed_100_fd);
assert!(partner.speed_100_hd);
assert!(partner.speed_10_fd);
assert!(partner.speed_10_hd);
}
#[test]
fn test_energy_detect_powerdown_enable() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
mdio.set_register(0, reg::MCSR, 0x0000);
let mut phy = Lan8720a::new(0);
phy.set_energy_detect_powerdown(&mut mdio, true).unwrap();
let mcsr = mdio.get_register(0, reg::MCSR).unwrap();
assert!(mcsr & mcsr::EDPWRDOWN != 0);
}
#[test]
fn test_energy_detect_powerdown_disable() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
mdio.set_register(0, reg::MCSR, mcsr::EDPWRDOWN);
let mut phy = Lan8720a::new(0);
phy.set_energy_detect_powerdown(&mut mdio, false).unwrap();
let mcsr = mdio.get_register(0, reg::MCSR).unwrap();
assert_eq!(mcsr & mcsr::EDPWRDOWN, 0);
}
#[test]
fn test_is_energy_on() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
mdio.set_register(0, reg::MCSR, 0x0000);
let phy = Lan8720a::new(0);
assert!(!phy.is_energy_on(&mut mdio).unwrap());
mdio.set_register(0, reg::MCSR, mcsr::ENERGYON);
assert!(phy.is_energy_on(&mut mdio).unwrap());
}
#[test]
fn test_interrupt_mask() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
mdio.set_register(0, reg::IMR, 0x0000);
let mut phy = Lan8720a::new(0);
phy.set_interrupt_mask(&mut mdio, isr::LINK_DOWN | isr::AN_COMPLETE)
.unwrap();
let imr = mdio.get_register(0, reg::IMR).unwrap();
assert!(imr & isr::LINK_DOWN != 0);
assert!(imr & isr::AN_COMPLETE != 0);
}
#[test]
fn test_enable_link_interrupt() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
mdio.set_register(0, reg::IMR, 0x0000);
let mut phy = Lan8720a::new(0);
phy.enable_link_interrupt(&mut mdio).unwrap();
let imr = mdio.get_register(0, reg::IMR).unwrap();
assert!(imr & isr::LINK_DOWN != 0);
assert!(imr & isr::AN_COMPLETE != 0);
}
#[test]
fn test_symbol_error_count() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
mdio.set_register(0, reg::SECR, 42);
let phy = Lan8720a::new(0);
assert_eq!(phy.symbol_error_count(&mut mdio).unwrap(), 42);
}
#[test]
fn test_configure_advertisement() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
let mut phy = Lan8720a::new(0);
let caps = PhyCapabilities {
speed_100_fd: true,
speed_100_hd: false,
speed_10_fd: true,
speed_10_hd: false,
auto_negotiation: true,
pause: true,
pause_asymmetric: false,
};
phy.configure_advertisement(&mut mdio, &caps).unwrap();
let anar = mdio.get_register(0, phy_reg::ANAR).unwrap();
use crate::internal::phy_regs::standard::anar;
assert!(anar & anar::TX_FD != 0, "Should advertise 100FD");
assert_eq!(anar & anar::TX_HD, 0, "Should not advertise 100HD");
assert!(anar & anar::T10_FD != 0, "Should advertise 10FD");
assert_eq!(anar & anar::T10_HD, 0, "Should not advertise 10HD");
assert!(anar & anar::PAUSE != 0, "Should advertise PAUSE");
}
#[test]
fn test_phy_address() {
let phy0 = Lan8720a::new(0);
assert_eq!(phy0.address(), 0);
let phy1 = Lan8720a::new(1);
assert_eq!(phy1.address(), 1);
let phy31 = Lan8720a::new(31);
assert_eq!(phy31.address(), 31);
}
#[test]
fn test_operations_use_correct_address() {
let mut mdio = MockMdioBus::new();
mdio.set_register(5, phy_reg::PHYIDR1, 0x0007);
mdio.set_register(5, phy_reg::PHYIDR2, 0xC0F1);
mdio.set_register(5, phy_reg::BMSR, 0x7809); mdio.set_register(5, phy_reg::BMCR, 0x0000);
let phy = Lan8720a::new(5);
assert!(phy.verify_id(&mut mdio).unwrap());
let phy0 = Lan8720a::new(0);
assert!(!phy0.verify_id(&mut mdio).unwrap());
}
}