use crate::internal::constants::{
DEFAULT_FLOW_HIGH_WATER, DEFAULT_FLOW_LOW_WATER, DEFAULT_MAC_ADDR, MDC_MAX_FREQ_HZ,
PAUSE_TIME_MAX, SOFT_RESET_TIMEOUT_MS,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Speed {
Mbps10,
#[default]
Mbps100,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Duplex {
Half,
#[default]
Full,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PhyInterface {
Mii,
#[default]
Rmii,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum RmiiClockMode {
ExternalInput {
gpio: u8,
},
InternalOutput {
gpio: u8,
},
}
impl Default for RmiiClockMode {
fn default() -> Self {
RmiiClockMode::ExternalInput { gpio: 0 }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum DmaBurstLen {
Burst1 = 1,
Burst2 = 2,
Burst4 = 4,
Burst8 = 8,
Burst16 = 16,
#[default]
Burst32 = 32,
}
impl DmaBurstLen {
#[must_use]
pub const fn to_pbl(self) -> u32 {
self as u32
}
}
pub const MAC_FILTER_SLOTS: usize = 4;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum MacFilterType {
#[default]
Destination,
Source,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct MacAddressFilter {
pub address: [u8; 6],
pub filter_type: MacFilterType,
pub byte_mask: u8,
}
impl MacAddressFilter {
#[must_use]
pub const fn new(address: [u8; 6]) -> Self {
Self {
address,
filter_type: MacFilterType::Destination,
byte_mask: 0,
}
}
#[must_use]
pub const fn source(address: [u8; 6]) -> Self {
Self {
address,
filter_type: MacFilterType::Source,
byte_mask: 0,
}
}
#[must_use]
pub const fn with_mask(address: [u8; 6], byte_mask: u8) -> Self {
Self {
address,
filter_type: MacFilterType::Destination,
byte_mask,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ChecksumConfig {
pub rx_checksum: bool,
pub tx_checksum: TxChecksumMode,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct FlowControlConfig {
pub enabled: bool,
pub low_water_mark: usize,
pub high_water_mark: usize,
pub pause_time: u16,
pub pause_low_threshold: PauseLowThreshold,
pub unicast_pause_detect: bool,
}
impl Default for FlowControlConfig {
fn default() -> Self {
Self {
enabled: false,
low_water_mark: DEFAULT_FLOW_LOW_WATER,
high_water_mark: DEFAULT_FLOW_HIGH_WATER,
pause_time: PAUSE_TIME_MAX,
pause_low_threshold: PauseLowThreshold::Minus4,
unicast_pause_detect: false,
}
}
}
impl FlowControlConfig {
#[must_use]
pub const fn with_water_marks(low: usize, high: usize) -> Self {
Self {
enabled: true,
low_water_mark: low,
high_water_mark: high,
pause_time: PAUSE_TIME_MAX,
pause_low_threshold: PauseLowThreshold::Minus4,
unicast_pause_detect: false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum PauseLowThreshold {
#[default]
Minus4 = 0,
Minus28 = 1,
Minus144 = 2,
Minus256 = 3,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum TxChecksumMode {
#[default]
Disabled = 0,
IpHeaderOnly = 1,
IpAndPayload = 2,
Full = 3,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct EmacConfig {
pub phy_interface: PhyInterface,
pub rmii_clock: RmiiClockMode,
pub mac_address: [u8; 6],
pub dma_burst_len: DmaBurstLen,
pub sw_reset_timeout_ms: u32,
pub mdc_freq_hz: u32,
pub promiscuous: bool,
pub checksum: ChecksumConfig,
pub flow_control: FlowControlConfig,
}
impl Default for EmacConfig {
fn default() -> Self {
Self {
phy_interface: PhyInterface::default(),
rmii_clock: RmiiClockMode::default(),
mac_address: DEFAULT_MAC_ADDR,
dma_burst_len: DmaBurstLen::default(),
sw_reset_timeout_ms: SOFT_RESET_TIMEOUT_MS,
mdc_freq_hz: MDC_MAX_FREQ_HZ,
promiscuous: false,
checksum: ChecksumConfig::default(),
flow_control: FlowControlConfig::default(),
}
}
}
impl EmacConfig {
#[must_use]
pub const fn new() -> Self {
Self {
phy_interface: PhyInterface::Rmii,
rmii_clock: RmiiClockMode::ExternalInput { gpio: 0 },
mac_address: DEFAULT_MAC_ADDR,
dma_burst_len: DmaBurstLen::Burst32,
sw_reset_timeout_ms: SOFT_RESET_TIMEOUT_MS,
mdc_freq_hz: MDC_MAX_FREQ_HZ,
promiscuous: false,
checksum: ChecksumConfig {
rx_checksum: false,
tx_checksum: TxChecksumMode::Disabled,
},
flow_control: FlowControlConfig {
enabled: false,
low_water_mark: DEFAULT_FLOW_LOW_WATER,
high_water_mark: DEFAULT_FLOW_HIGH_WATER,
pause_time: PAUSE_TIME_MAX,
pause_low_threshold: PauseLowThreshold::Minus4,
unicast_pause_detect: false,
},
}
}
#[must_use]
pub const fn rmii_esp32_default() -> Self {
Self::new()
}
#[must_use]
pub const fn with_phy_interface(mut self, interface: PhyInterface) -> Self {
self.phy_interface = interface;
self
}
#[must_use]
pub const fn with_rmii_clock(mut self, clock: RmiiClockMode) -> Self {
self.rmii_clock = clock;
self
}
#[must_use]
pub const fn with_rmii_external_clock(mut self, gpio: u8) -> Self {
self.rmii_clock = RmiiClockMode::ExternalInput { gpio };
self
}
#[must_use]
pub const fn with_rmii_internal_clock(mut self, gpio: u8) -> Self {
self.rmii_clock = RmiiClockMode::InternalOutput { gpio };
self
}
#[must_use]
pub const fn with_mac_address(mut self, addr: [u8; 6]) -> Self {
self.mac_address = addr;
self
}
#[must_use]
pub const fn with_dma_burst_len(mut self, burst_len: DmaBurstLen) -> Self {
self.dma_burst_len = burst_len;
self
}
#[must_use]
pub const fn with_reset_timeout_ms(mut self, timeout_ms: u32) -> Self {
self.sw_reset_timeout_ms = timeout_ms;
self
}
#[must_use]
pub const fn with_mdc_freq_hz(mut self, freq_hz: u32) -> Self {
self.mdc_freq_hz = freq_hz;
self
}
#[must_use]
pub const fn with_promiscuous(mut self, enabled: bool) -> Self {
self.promiscuous = enabled;
self
}
#[must_use]
pub const fn with_checksum(mut self, checksum: ChecksumConfig) -> Self {
self.checksum = checksum;
self
}
#[must_use]
pub const fn with_rx_checksum(mut self, enabled: bool) -> Self {
self.checksum.rx_checksum = enabled;
self
}
#[must_use]
pub const fn with_tx_checksum(mut self, mode: TxChecksumMode) -> Self {
self.checksum.tx_checksum = mode;
self
}
#[must_use]
pub const fn with_flow_control(mut self, flow_control: FlowControlConfig) -> Self {
self.flow_control = flow_control;
self
}
#[must_use]
pub const fn with_flow_control_enabled(mut self, enabled: bool) -> Self {
self.flow_control.enabled = enabled;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum State {
#[default]
Uninitialized,
Initialized,
Running,
Stopped,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::internal::constants::DEFAULT_MAC_ADDR;
#[test]
fn config_default_values() {
let config = EmacConfig::new();
assert_eq!(config.mac_address, DEFAULT_MAC_ADDR);
assert_eq!(config.phy_interface, PhyInterface::Rmii);
assert_eq!(config.dma_burst_len, DmaBurstLen::Burst32);
assert!(!config.promiscuous);
assert!(!config.checksum.rx_checksum);
assert_eq!(config.checksum.tx_checksum, TxChecksumMode::Disabled);
assert!(!config.flow_control.enabled);
}
#[test]
fn config_default_trait_matches_new() {
let from_default = EmacConfig::default();
let from_new = EmacConfig::new();
assert_eq!(from_default.mac_address, from_new.mac_address);
assert_eq!(from_default.phy_interface, from_new.phy_interface);
assert_eq!(from_default.dma_burst_len, from_new.dma_burst_len);
}
#[test]
fn config_rmii_esp32_default_matches_new() {
let from_rmii = EmacConfig::rmii_esp32_default();
let from_new = EmacConfig::new();
assert_eq!(from_rmii.mac_address, from_new.mac_address);
assert_eq!(from_rmii.phy_interface, from_new.phy_interface);
assert_eq!(from_rmii.rmii_clock, from_new.rmii_clock);
}
#[test]
fn config_builder_mac_address() {
let mac = [0x02, 0x00, 0x00, 0x11, 0x22, 0x33];
let config = EmacConfig::new().with_mac_address(mac);
assert_eq!(config.mac_address, mac);
}
#[test]
fn config_builder_phy_interface() {
let config = EmacConfig::new().with_phy_interface(PhyInterface::Mii);
assert_eq!(config.phy_interface, PhyInterface::Mii);
let config = EmacConfig::new().with_phy_interface(PhyInterface::Rmii);
assert_eq!(config.phy_interface, PhyInterface::Rmii);
}
#[test]
fn config_builder_dma_burst_len() {
let config = EmacConfig::new().with_dma_burst_len(DmaBurstLen::Burst16);
assert_eq!(config.dma_burst_len, DmaBurstLen::Burst16);
let config = EmacConfig::new().with_dma_burst_len(DmaBurstLen::Burst1);
assert_eq!(config.dma_burst_len, DmaBurstLen::Burst1);
}
#[test]
fn config_builder_promiscuous() {
let config = EmacConfig::new().with_promiscuous(true);
assert!(config.promiscuous);
let config = EmacConfig::new().with_promiscuous(false);
assert!(!config.promiscuous);
}
#[test]
fn config_builder_chaining() {
let mac = [0x02, 0x00, 0x00, 0xAA, 0xBB, 0xCC];
let config = EmacConfig::new()
.with_mac_address(mac)
.with_phy_interface(PhyInterface::Mii)
.with_dma_burst_len(DmaBurstLen::Burst8)
.with_promiscuous(true)
.with_rx_checksum(true)
.with_tx_checksum(TxChecksumMode::Full)
.with_flow_control_enabled(true);
assert_eq!(config.mac_address, mac);
assert_eq!(config.phy_interface, PhyInterface::Mii);
assert_eq!(config.dma_burst_len, DmaBurstLen::Burst8);
assert!(config.promiscuous);
assert!(config.checksum.rx_checksum);
assert_eq!(config.checksum.tx_checksum, TxChecksumMode::Full);
assert!(config.flow_control.enabled);
}
#[test]
fn config_builder_checksum() {
let config = EmacConfig::new()
.with_rx_checksum(true)
.with_tx_checksum(TxChecksumMode::IpHeaderOnly);
assert!(config.checksum.rx_checksum);
assert_eq!(config.checksum.tx_checksum, TxChecksumMode::IpHeaderOnly);
}
#[test]
fn config_builder_rmii_clock() {
let config = EmacConfig::new().with_rmii_clock(RmiiClockMode::InternalOutput { gpio: 17 });
match config.rmii_clock {
RmiiClockMode::InternalOutput { gpio } => assert_eq!(gpio, 17),
_ => panic!("Expected InternalOutput"),
}
}
#[test]
fn config_builder_rmii_external_clock() {
let config = EmacConfig::new().with_rmii_external_clock(0);
match config.rmii_clock {
RmiiClockMode::ExternalInput { gpio } => assert_eq!(gpio, 0),
_ => panic!("Expected ExternalInput"),
}
}
#[test]
fn config_builder_rmii_internal_clock() {
let config = EmacConfig::new().with_rmii_internal_clock(16);
match config.rmii_clock {
RmiiClockMode::InternalOutput { gpio } => assert_eq!(gpio, 16),
_ => panic!("Expected InternalOutput"),
}
}
#[test]
fn dma_burst_len_to_pbl() {
assert_eq!(DmaBurstLen::Burst1.to_pbl(), 1);
assert_eq!(DmaBurstLen::Burst2.to_pbl(), 2);
assert_eq!(DmaBurstLen::Burst4.to_pbl(), 4);
assert_eq!(DmaBurstLen::Burst8.to_pbl(), 8);
assert_eq!(DmaBurstLen::Burst16.to_pbl(), 16);
assert_eq!(DmaBurstLen::Burst32.to_pbl(), 32);
}
#[test]
fn speed_default() {
assert_eq!(Speed::default(), Speed::Mbps100);
}
#[test]
fn duplex_default() {
assert_eq!(Duplex::default(), Duplex::Full);
}
#[test]
fn phy_interface_default() {
assert_eq!(PhyInterface::default(), PhyInterface::Rmii);
}
#[test]
fn state_default() {
assert_eq!(State::default(), State::Uninitialized);
}
#[test]
fn mac_filter_new() {
let addr = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06];
let filter = MacAddressFilter::new(addr);
assert_eq!(filter.address, addr);
assert_eq!(filter.filter_type, MacFilterType::Destination);
assert_eq!(filter.byte_mask, 0);
}
#[test]
fn mac_filter_source() {
let addr = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF];
let filter = MacAddressFilter::source(addr);
assert_eq!(filter.address, addr);
assert_eq!(filter.filter_type, MacFilterType::Source);
assert_eq!(filter.byte_mask, 0);
}
#[test]
fn mac_filter_with_mask() {
let addr = [0x01, 0x00, 0x5E, 0x00, 0x00, 0x00];
let filter = MacAddressFilter::with_mask(addr, 0b00_0111);
assert_eq!(filter.address, addr);
assert_eq!(filter.byte_mask, 0b00_0111);
}
#[test]
fn flow_control_default() {
let fc = FlowControlConfig::default();
assert!(!fc.enabled);
assert_eq!(fc.low_water_mark, DEFAULT_FLOW_LOW_WATER);
assert_eq!(fc.high_water_mark, DEFAULT_FLOW_HIGH_WATER);
assert_eq!(fc.pause_time, PAUSE_TIME_MAX);
}
#[test]
fn flow_control_with_water_marks() {
let fc = FlowControlConfig::with_water_marks(2, 8);
assert!(fc.enabled);
assert_eq!(fc.low_water_mark, 2);
assert_eq!(fc.high_water_mark, 8);
}
}