use embedded_hal::delay::DelayNs;
use crate::regs::dma as dma_regs;
use crate::regs::ext as ext_regs;
use crate::regs::gpio as gpio_matrix;
use crate::regs::mac as mac_regs;
use crate::reset::ResetController;
use crate::config::{ClkGpio, EmacConfig, RmiiClockConfig};
use crate::dma::engine::DmaEngine;
use crate::error::EmacError;
use crate::interrupt::InterruptStatus;
use crate::regs::dma::{bus_mode, operation};
use crate::regs::mac::{config, frame_filter};
const TX_FIFO_FLUSH_TIMEOUT_US: u32 = 100_000;
#[cfg(feature = "mdio-phy")]
pub use eth_mdio_phy::{Duplex, Speed};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum EmacState {
Uninitialized,
Initialized,
Running,
}
pub struct Emac<const RX: usize = 10, const TX: usize = 10, const BUF: usize = 1600> {
dma: DmaEngine<RX, TX, BUF>,
config: EmacConfig,
state: EmacState,
mac_address: [u8; 6],
}
impl<const RX: usize, const TX: usize, const BUF: usize> Emac<RX, TX, BUF> {
pub const fn new(config: EmacConfig) -> Self {
Self {
dma: DmaEngine::new(),
config,
state: EmacState::Uninitialized,
mac_address: [0; 6],
}
}
#[inline(always)]
pub fn state(&self) -> EmacState {
self.state
}
#[inline(always)]
pub fn mac_address(&self) -> [u8; 6] {
self.mac_address
}
#[inline(always)]
pub fn config(&self) -> &EmacConfig {
&self.config
}
pub const fn memory_usage() -> usize {
DmaEngine::<RX, TX, BUF>::memory_usage()
}
pub fn set_mac_address(&mut self, mac: [u8; 6]) {
self.mac_address = mac;
if self.state != EmacState::Uninitialized {
crate::regs::mac::set_mac_address(&mac);
}
}
#[cfg(feature = "mdio-phy")]
pub fn set_speed(&mut self, speed: Speed) {
if self.state == EmacState::Uninitialized {
return;
}
let is_100 = match speed {
Speed::Mbps10 => false,
Speed::Mbps100 => true,
_ => {
#[cfg(feature = "defmt")]
defmt::warn!(
"esp-emac: unsupported Speed variant, clamping to 100 Mbps \
(ESP32 EMAC is 10/100 only)"
);
true
}
};
mac_regs::set_speed_100mbps(is_100);
}
#[cfg(feature = "mdio-phy")]
pub fn set_duplex(&mut self, duplex: Duplex) {
if self.state == EmacState::Uninitialized {
return;
}
let is_full = match duplex {
Duplex::Half => false,
Duplex::Full => true,
_ => {
#[cfg(feature = "defmt")]
defmt::warn!(
"esp-emac: unsupported Duplex variant, clamping to Full \
(ESP32 EMAC supports Half/Full only)"
);
true
}
};
mac_regs::set_duplex_full(is_full);
}
pub fn init(&mut self, delay: &mut impl DelayNs) -> Result<(), EmacError> {
if self.state != EmacState::Uninitialized {
return Err(EmacError::AlreadyInitialized);
}
if !gpio_matrix::is_valid_smi_pin(self.config.pins.mdc)
|| !gpio_matrix::is_valid_smi_pin(self.config.pins.mdio)
{
return Err(EmacError::InvalidConfig);
}
match self.config.clock {
RmiiClockConfig::External { gpio } if !matches!(gpio, ClkGpio::Gpio0) => {
return Err(EmacError::InvalidConfig);
}
RmiiClockConfig::InternalApll {
gpio: ClkGpio::Gpio0,
..
} => {
return Err(EmacError::InvalidConfig);
}
_ => {}
}
if let RmiiClockConfig::InternalApll { xtal, .. } = self.config.clock {
crate::clock::configure_apll_50mhz(xtal);
}
match self.config.clock {
RmiiClockConfig::External { gpio } => crate::clock::configure_emac_clk_in(gpio),
RmiiClockConfig::InternalApll { gpio, .. } => {
crate::clock::configure_emac_clk_out(gpio)
}
}
gpio_matrix::configure_smi_pins(self.config.pins.mdc, self.config.pins.mdio);
gpio_matrix::configure_rmii_pins();
ext_regs::enable_peripheral_clock();
ext_regs::set_rmii_mode();
match self.config.clock {
RmiiClockConfig::External { .. } => ext_regs::set_rmii_clock_external(),
RmiiClockConfig::InternalApll { .. } => ext_regs::set_rmii_clock_internal(),
}
ext_regs::enable_clocks();
ext_regs::power_up_ram();
let mut reset_ctrl = ResetController::new(BorrowedDelay(delay));
reset_ctrl.soft_reset()?;
let mac_cfg = config::PORT_SELECT
| config::SPEED_100
| config::DUPLEX_FULL
| config::AUTO_PAD_CRC_STRIP
| config::JABBER_DISABLE
| config::WATCHDOG_DISABLE;
mac_regs::set_config(mac_cfg);
mac_regs::set_frame_filter(frame_filter::PASS_ALL_MULTICAST);
mac_regs::set_hash_table(0);
let pbl = 32u32;
let bus = bus_mode::FIXED_BURST
| bus_mode::AAL
| bus_mode::USP
| bus_mode::ATDS
| ((pbl << bus_mode::PBL_SHIFT) & bus_mode::PBL_MASK);
dma_regs::set_bus_mode(bus);
dma_regs::set_operation_mode(operation::TSF | operation::RSF);
dma_regs::disable_all_interrupts();
dma_regs::clear_all_interrupts();
let (rx_base, tx_base) = self.dma.init();
dma_regs::set_rx_desc_list_addr(rx_base);
dma_regs::set_tx_desc_list_addr(tx_base);
crate::regs::mac::set_mac_address(&self.mac_address);
self.state = EmacState::Initialized;
Ok(())
}
pub fn start(&mut self) -> Result<(), EmacError> {
match self.state {
EmacState::Initialized => {}
EmacState::Running => return Ok(()),
EmacState::Uninitialized => return Err(EmacError::NotInitialized),
}
let (rx_base, tx_base) = self.dma.reset();
dma_regs::set_rx_desc_list_addr(rx_base);
dma_regs::set_tx_desc_list_addr(tx_base);
dma_regs::clear_all_interrupts();
dma_regs::enable_default_interrupts();
let cfg = mac_regs::config();
mac_regs::set_config(cfg | config::TX_ENABLE);
dma_regs::start_tx();
dma_regs::start_rx();
let cfg = mac_regs::config();
mac_regs::set_config(cfg | config::RX_ENABLE);
dma_regs::rx_poll_demand();
self.state = EmacState::Running;
Ok(())
}
pub fn stop(&mut self, delay: &mut impl DelayNs) -> Result<(), EmacError> {
match self.state {
EmacState::Running => {} EmacState::Initialized => return Ok(()),
EmacState::Uninitialized => return Err(EmacError::NotInitialized),
}
dma_regs::stop_tx();
dma_regs::flush_tx_fifo();
const POLL_STEP_US: u32 = 10;
let mut waited_us = 0u32;
let mut flush_timed_out = true;
while waited_us < TX_FIFO_FLUSH_TIMEOUT_US {
if (dma_regs::operation_mode() & operation::FTF) == 0 {
flush_timed_out = false;
break;
}
delay.delay_us(POLL_STEP_US);
waited_us += POLL_STEP_US;
}
let cfg = mac_regs::config();
mac_regs::set_config(cfg & !(config::TX_ENABLE | config::RX_ENABLE));
dma_regs::stop_rx();
dma_regs::disable_all_interrupts();
dma_regs::clear_all_interrupts();
self.state = EmacState::Initialized;
if flush_timed_out {
Err(EmacError::TxFlushTimeout)
} else {
Ok(())
}
}
pub fn transmit(&mut self, data: &[u8]) -> Result<usize, EmacError> {
if self.state != EmacState::Running {
return Err(EmacError::NotInitialized);
}
let n = self.dma.transmit(data)?;
dma_regs::tx_poll_demand();
Ok(n)
}
pub fn receive(&mut self, buffer: &mut [u8]) -> Result<Option<usize>, EmacError> {
if self.state != EmacState::Running {
return Err(EmacError::NotInitialized);
}
let result = self.dma.receive(buffer);
if !matches!(result, Ok(None)) {
dma_regs::rx_poll_demand();
}
result
}
#[inline(always)]
pub fn rx_available(&self) -> bool {
self.dma.rx_available()
}
#[inline(always)]
pub fn can_transmit(&self, len: usize) -> bool {
self.dma.can_transmit(len)
}
#[inline(always)]
pub fn tx_ready(&self) -> bool {
self.dma.tx_available() > 0
}
#[cfg(feature = "esp-hal")]
pub fn bind_interrupt(&mut self, handler: esp_hal::interrupt::InterruptHandler) {
use esp_hal::peripherals::Interrupt;
for core in esp_hal::system::Cpu::other() {
esp_hal::interrupt::disable(core, Interrupt::ETH_MAC);
}
esp_hal::interrupt::bind_handler(Interrupt::ETH_MAC, handler);
esp_hal::interrupt::enable(Interrupt::ETH_MAC, handler.priority());
}
#[cfg(feature = "esp-hal")]
pub fn disable_interrupt(&mut self) {
use esp_hal::peripherals::Interrupt;
esp_hal::interrupt::disable(esp_hal::system::Cpu::current(), Interrupt::ETH_MAC);
}
pub fn interrupt_status(&self) -> InterruptStatus {
let raw = unsafe {
core::ptr::read_volatile(
(crate::regs::dma::BASE + crate::regs::dma::DMASTATUS) as *const u32,
)
};
InterruptStatus::from_raw(raw)
}
pub fn clear_interrupts_raw(&self, raw: u32) {
unsafe {
core::ptr::write_volatile(
(crate::regs::dma::BASE + crate::regs::dma::DMASTATUS) as *mut u32,
raw & crate::regs::dma::status::ALL_INTERRUPTS,
);
}
}
pub fn handle_interrupt(&self) -> InterruptStatus {
let raw = unsafe {
core::ptr::read_volatile(
(crate::regs::dma::BASE + crate::regs::dma::DMASTATUS) as *const u32,
)
};
self.clear_interrupts_raw(raw);
InterruptStatus::from_raw(raw)
}
}
pub const DEFAULT_RX: usize = 10;
pub const DEFAULT_TX: usize = 10;
pub const DEFAULT_BUF: usize = 1600;
pub const SMALL_RX: usize = 4;
pub const SMALL_TX: usize = 4;
pub type EmacDefault = Emac<DEFAULT_RX, DEFAULT_TX, DEFAULT_BUF>;
pub type EmacSmall = Emac<SMALL_RX, SMALL_TX, DEFAULT_BUF>;
struct BorrowedDelay<'a, D: DelayNs + ?Sized>(&'a mut D);
impl<D: DelayNs + ?Sized> DelayNs for BorrowedDelay<'_, D> {
fn delay_ns(&mut self, ns: u32) {
self.0.delay_ns(ns);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_config() -> EmacConfig {
EmacConfig {
clock: RmiiClockConfig::InternalApll {
gpio: ClkGpio::Gpio17,
xtal: crate::config::XtalFreq::Mhz40,
},
pins: crate::config::RmiiPins::default(),
}
}
#[test]
fn new_is_uninitialized() {
let emac: EmacDefault = Emac::new(test_config());
assert_eq!(emac.state(), EmacState::Uninitialized);
assert_eq!(emac.mac_address(), [0u8; 6]);
}
#[test]
fn set_mac_before_init_only_caches() {
let mut emac: EmacDefault = Emac::new(test_config());
let mac = [0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
emac.set_mac_address(mac);
assert_eq!(emac.mac_address(), mac);
}
#[test]
fn memory_usage_matches_dma() {
assert_eq!(
EmacDefault::memory_usage(),
DmaEngine::<DEFAULT_RX, DEFAULT_TX, DEFAULT_BUF>::memory_usage()
);
}
}