use eth_mdio_phy::MdioBus;
use crate::regs;
pub(crate) fn mmd_read<M: MdioBus>(
mdio: &mut M,
phy_addr: u8,
devad: u8,
reg: u16,
) -> Result<u16, M::Error> {
let devad_field = u16::from(devad) & regs::MMDCTRL_DEVAD_MASK;
mdio.write(
phy_addr,
regs::REG_MMDCTRL,
regs::MMDCTRL_FNCTN_ADDR | devad_field,
)?;
mdio.write(phy_addr, regs::REG_MMDAD, reg)?;
mdio.write(
phy_addr,
regs::REG_MMDCTRL,
regs::MMDCTRL_FNCTN_DATA | devad_field,
)?;
mdio.read(phy_addr, regs::REG_MMDAD)
}
pub(crate) fn mmd_write<M: MdioBus>(
mdio: &mut M,
phy_addr: u8,
devad: u8,
reg: u16,
value: u16,
) -> Result<(), M::Error> {
let devad_field = u16::from(devad) & regs::MMDCTRL_DEVAD_MASK;
mdio.write(
phy_addr,
regs::REG_MMDCTRL,
regs::MMDCTRL_FNCTN_ADDR | devad_field,
)?;
mdio.write(phy_addr, regs::REG_MMDAD, reg)?;
mdio.write(
phy_addr,
regs::REG_MMDCTRL,
regs::MMDCTRL_FNCTN_DATA | devad_field,
)?;
mdio.write(phy_addr, regs::REG_MMDAD, value)
}
pub(crate) fn mmd_rmw<M: MdioBus>(
mdio: &mut M,
phy_addr: u8,
devad: u8,
reg: u16,
clear_mask: u16,
set_mask: u16,
) -> Result<(), M::Error> {
let current = mmd_read(mdio, phy_addr, devad, reg)?;
let next = (current & !clear_mask) | set_mask;
mmd_write(mdio, phy_addr, devad, reg, next)
}
#[cfg(test)]
mod tests {
extern crate alloc;
use super::*;
use alloc::vec;
use alloc::vec::Vec;
#[derive(Debug, PartialEq)]
struct MockError;
struct MockMdio {
reads: Vec<u16>,
read_idx: usize,
ops: Vec<Op>,
}
#[derive(Debug, PartialEq)]
enum Op {
Read { phy: u8, reg: u8 },
Write { phy: u8, reg: u8, value: u16 },
}
impl MockMdio {
fn new(reads: Vec<u16>) -> Self {
Self {
reads,
read_idx: 0,
ops: Vec::new(),
}
}
}
impl MdioBus for MockMdio {
type Error = MockError;
fn read(&mut self, phy_addr: u8, reg_addr: u8) -> Result<u16, MockError> {
self.ops.push(Op::Read {
phy: phy_addr,
reg: reg_addr,
});
let v = *self
.reads
.get(self.read_idx)
.expect("MockMdio: reads vector exhausted");
self.read_idx += 1;
Ok(v)
}
fn write(&mut self, phy_addr: u8, reg_addr: u8, value: u16) -> Result<(), MockError> {
self.ops.push(Op::Write {
phy: phy_addr,
reg: reg_addr,
value,
});
Ok(())
}
}
#[test]
fn mmd_read_drives_correct_indirection_sequence() {
let mut mdio = MockMdio::new(vec![0x0A10]);
let v = mmd_read(&mut mdio, 0, regs::MMD_VS2, regs::MMD_REG_MIDVER).unwrap();
assert_eq!(v, 0x0A10);
assert_eq!(
mdio.ops,
vec![
Op::Write {
phy: 0,
reg: regs::REG_MMDCTRL,
value: regs::MMDCTRL_FNCTN_ADDR | u16::from(regs::MMD_VS2),
},
Op::Write {
phy: 0,
reg: regs::REG_MMDAD,
value: regs::MMD_REG_MIDVER,
},
Op::Write {
phy: 0,
reg: regs::REG_MMDCTRL,
value: regs::MMDCTRL_FNCTN_DATA | u16::from(regs::MMD_VS2),
},
Op::Read {
phy: 0,
reg: regs::REG_MMDAD,
},
]
);
}
#[test]
fn mmd_write_drives_correct_indirection_sequence() {
let mut mdio = MockMdio::new(vec![]);
mmd_write(
&mut mdio,
3,
regs::MMD_VS2,
regs::MMD_REG_PLCA_CTRL1,
0x0801,
)
.unwrap();
assert_eq!(
mdio.ops,
vec![
Op::Write {
phy: 3,
reg: regs::REG_MMDCTRL,
value: regs::MMDCTRL_FNCTN_ADDR | u16::from(regs::MMD_VS2),
},
Op::Write {
phy: 3,
reg: regs::REG_MMDAD,
value: regs::MMD_REG_PLCA_CTRL1,
},
Op::Write {
phy: 3,
reg: regs::REG_MMDCTRL,
value: regs::MMDCTRL_FNCTN_DATA | u16::from(regs::MMD_VS2),
},
Op::Write {
phy: 3,
reg: regs::REG_MMDAD,
value: 0x0801,
},
]
);
}
#[test]
fn mmd_rmw_preserves_unrelated_bits() {
let pre = regs::T1SPMACTL_TXD;
let mut mdio = MockMdio::new(vec![pre]);
mmd_rmw(
&mut mdio,
0,
regs::MMD_PMA_PMD,
regs::MMD_REG_T1SPMACTL,
0,
regs::T1SPMACTL_MDE,
)
.unwrap();
let last_mmdad_write = mdio
.ops
.iter()
.rev()
.find_map(|op| match op {
Op::Write {
reg: regs::REG_MMDAD,
value,
..
} => Some(*value),
_ => None,
})
.expect("expected an MMDAD data write");
assert_eq!(
last_mmdad_write,
regs::T1SPMACTL_TXD | regs::T1SPMACTL_MDE,
"RMW must preserve the previously-set TXD bit"
);
}
#[test]
fn mmd_rmw_clears_then_sets() {
let pre = regs::PLCA_CTRL0_EN | regs::PLCA_CTRL0_RST;
let mut mdio = MockMdio::new(vec![pre]);
mmd_rmw(
&mut mdio,
0,
regs::MMD_VS2,
regs::MMD_REG_PLCA_CTRL0,
regs::PLCA_CTRL0_RST,
regs::PLCA_CTRL0_EN,
)
.unwrap();
let last_mmdad_write = mdio
.ops
.iter()
.rev()
.find_map(|op| match op {
Op::Write {
reg: regs::REG_MMDAD,
value,
..
} => Some(*value),
_ => None,
})
.unwrap();
assert_eq!(last_mmdad_write, regs::PLCA_CTRL0_EN);
}
}