use embedded_hal::delay::DelayNs;
use super::config::{Duplex, EmacConfig, PhyInterface, RmiiClockMode, Speed, State};
use super::error::{ConfigError, IoError, Result};
use super::interrupt::InterruptStatus;
use crate::hal::reset::ResetController;
use crate::internal::constants::{
CSR_CLOCK_DIV_42, FLUSH_TIMEOUT, MII_BUSY_TIMEOUT, TX_DMA_STATE_MASK, TX_DMA_STATE_SHIFT,
};
use crate::internal::dma::DmaEngine;
use crate::internal::register::dma::{
DMABUSMODE_AAL, DMABUSMODE_ATDS, DMABUSMODE_FB, DMABUSMODE_PBL_MASK, DMABUSMODE_PBL_SHIFT,
DMABUSMODE_USP, DMAOPERATION_RSF, DMAOPERATION_TSF, DmaRegs,
};
use crate::internal::register::ext::ExtRegs;
use crate::internal::register::gpio::GpioMatrix;
use crate::internal::register::mac::{
GMACCONFIG_ACS, GMACCONFIG_DM, GMACCONFIG_FES, GMACCONFIG_IPC, GMACCONFIG_JD, GMACCONFIG_PS,
GMACCONFIG_WD, GMACFF_PM, GMACFF_PR, GMACMIIADDR_CR_MASK, GMACMIIADDR_CR_SHIFT, GMACMIIADDR_GB,
GMACMIIADDR_GR_SHIFT, GMACMIIADDR_GW, GMACMIIADDR_PA_SHIFT, MacRegs,
};
struct BorrowedDelay<'a, D: DelayNs>(&'a mut D);
impl<D: DelayNs> DelayNs for BorrowedDelay<'_, D> {
fn delay_ns(&mut self, ns: u32) {
self.0.delay_ns(ns);
}
}
pub struct Emac<const RX_BUFS: usize, const TX_BUFS: usize, const BUF_SIZE: usize> {
pub(super) dma: DmaEngine<RX_BUFS, TX_BUFS, BUF_SIZE>,
pub(super) config: EmacConfig,
state: State,
mac_addr: [u8; 6],
speed: Speed,
duplex: Duplex,
pub(super) peer_pause_ability: bool,
pub(super) flow_control_active: bool,
}
impl<const RX_BUFS: usize, const TX_BUFS: usize, const BUF_SIZE: usize>
Emac<RX_BUFS, TX_BUFS, BUF_SIZE>
{
pub const fn new() -> Self {
Self {
dma: DmaEngine::new(),
config: EmacConfig::new(),
state: State::Uninitialized,
mac_addr: [0u8; 6],
speed: Speed::Mbps100,
duplex: Duplex::Full,
peer_pause_ability: false,
flow_control_active: false,
}
}
#[inline(always)]
pub fn state(&self) -> State {
self.state
}
#[inline(always)]
pub fn mac_address(&self) -> &[u8; 6] {
&self.mac_addr
}
#[inline(always)]
pub fn speed(&self) -> Speed {
self.speed
}
#[inline(always)]
pub fn duplex(&self) -> Duplex {
self.duplex
}
pub fn init<D: DelayNs>(&mut self, config: EmacConfig, mut delay: D) -> Result<()> {
if self.state != State::Uninitialized {
return Err(ConfigError::AlreadyInitialized.into());
}
self.config = config;
if matches!(self.config.rmii_clock, RmiiClockMode::ExternalInput { .. }) {
ExtRegs::configure_gpio0_rmii_clock_input();
#[cfg(feature = "defmt")]
defmt::info!("GPIO0 configured for external RMII clock input");
}
GpioMatrix::configure_smi_pins();
#[cfg(feature = "defmt")]
defmt::info!("SMI pins configured: GPIO23=MDC, GPIO18=MDIO");
GpioMatrix::configure_rmii_pins();
#[cfg(feature = "defmt")]
defmt::info!("RMII data pins configured via IO_MUX");
ExtRegs::enable_peripheral_clock();
#[cfg(feature = "defmt")]
defmt::info!("EMAC peripheral clock enabled via DPORT");
self.configure_phy_interface_regs();
ExtRegs::enable_clocks();
ExtRegs::power_up_ram();
self.software_reset(&mut delay)?;
self.configure_mac_defaults();
self.configure_dma_defaults();
self.dma.init();
self.mac_addr = self.config.mac_address;
MacRegs::set_mac_address(&self.mac_addr);
self.state = State::Initialized;
Ok(())
}
fn configure_phy_interface_regs(&self) {
match self.config.phy_interface {
PhyInterface::Rmii => {
ExtRegs::set_rmii_mode();
match self.config.rmii_clock {
RmiiClockMode::ExternalInput { .. } => {
ExtRegs::set_rmii_clock_external();
}
RmiiClockMode::InternalOutput { .. } => {
ExtRegs::set_rmii_clock_internal();
}
}
}
PhyInterface::Mii => {
ExtRegs::set_mii_mode();
}
}
}
fn software_reset<D: DelayNs>(&self, delay: &mut D) -> Result<()> {
let mut reset_ctrl = ResetController::new(BorrowedDelay(delay));
reset_ctrl
.soft_reset()
.map_err(|_| ConfigError::ResetFailed.into())
}
fn configure_mac_defaults(&self) {
let mut cfg = 0u32;
cfg |= GMACCONFIG_PS;
cfg |= GMACCONFIG_FES;
cfg |= GMACCONFIG_DM;
cfg |= GMACCONFIG_ACS;
cfg |= GMACCONFIG_JD;
cfg |= GMACCONFIG_WD;
if self.config.checksum.rx_checksum {
cfg |= GMACCONFIG_IPC;
}
MacRegs::set_config(cfg);
let mut filter = 0u32;
if self.config.promiscuous {
filter |= GMACFF_PR;
}
filter |= GMACFF_PM;
MacRegs::set_frame_filter(filter);
MacRegs::set_hash_table_high(0);
MacRegs::set_hash_table_low(0);
}
fn configure_dma_defaults(&self) {
let pbl = self.config.dma_burst_len.to_pbl();
let bus_mode = DMABUSMODE_FB | DMABUSMODE_AAL | DMABUSMODE_USP | DMABUSMODE_ATDS | ((pbl << DMABUSMODE_PBL_SHIFT) & DMABUSMODE_PBL_MASK);
DmaRegs::set_bus_mode(bus_mode);
let op_mode = DMAOPERATION_TSF | DMAOPERATION_RSF;
DmaRegs::set_operation_mode(op_mode);
DmaRegs::disable_all_interrupts();
DmaRegs::clear_all_interrupts();
}
pub fn start(&mut self) -> Result<()> {
match self.state {
State::Initialized | State::Stopped => {}
State::Running => return Ok(()), State::Uninitialized => return Err(IoError::InvalidState.into()),
}
self.dma.reset();
DmaRegs::clear_all_interrupts();
DmaRegs::enable_default_interrupts();
self.mac_tx_enable(true);
DmaRegs::start_tx();
DmaRegs::start_rx();
self.mac_rx_enable(true);
DmaRegs::rx_poll_demand();
self.state = State::Running;
Ok(())
}
pub fn stop(&mut self) -> Result<()> {
if self.state != State::Running {
return Err(IoError::InvalidState.into());
}
DmaRegs::stop_tx();
self.wait_tx_idle()?;
DmaRegs::stop_rx();
self.mac_tx_enable(false);
self.mac_rx_enable(false);
self.flush_tx_fifo()?;
DmaRegs::disable_all_interrupts();
DmaRegs::clear_all_interrupts();
self.state = State::Stopped;
Ok(())
}
fn mac_tx_enable(&self, enable: bool) {
if enable {
MacRegs::enable_tx();
} else {
MacRegs::disable_tx();
}
}
fn mac_rx_enable(&self, enable: bool) {
if enable {
MacRegs::enable_rx();
} else {
MacRegs::disable_rx();
}
}
fn wait_tx_idle(&self) -> Result<()> {
for _ in 0..FLUSH_TIMEOUT {
let status = DmaRegs::status();
let tx_state = (status >> TX_DMA_STATE_SHIFT) & TX_DMA_STATE_MASK;
if tx_state == 0 {
return Ok(());
}
core::hint::spin_loop();
}
Err(IoError::Timeout.into())
}
fn flush_tx_fifo(&self) -> Result<()> {
DmaRegs::flush_tx_fifo();
for _ in 0..FLUSH_TIMEOUT {
if DmaRegs::is_tx_fifo_flush_complete() {
return Ok(());
}
core::hint::spin_loop();
}
Err(IoError::Timeout.into())
}
pub fn transmit(&mut self, data: &[u8]) -> Result<usize> {
if self.state != State::Running {
return Err(IoError::InvalidState.into());
}
self.dma.transmit(data)
}
#[inline(always)]
pub fn rx_available(&self) -> bool {
self.dma.rx_available()
}
pub fn peek_rx_length(&self) -> Option<usize> {
self.dma.peek_frame_length()
}
pub fn receive(&mut self, buffer: &mut [u8]) -> Result<usize> {
if self.state != State::Running {
return Err(IoError::InvalidState.into());
}
self.dma.receive(buffer)
}
pub fn tx_ready(&self) -> bool {
self.dma.tx_available() > 0
}
pub fn can_transmit(&self, len: usize) -> bool {
self.dma.can_transmit(len)
}
pub fn set_mac_address(&mut self, addr: &[u8; 6]) {
self.mac_addr = *addr;
self.config.mac_address = *addr;
MacRegs::set_mac_address(addr);
}
pub fn set_speed(&mut self, speed: Speed) {
self.speed = speed;
MacRegs::set_speed_100mbps(matches!(speed, Speed::Mbps100));
}
pub fn set_duplex(&mut self, duplex: Duplex) {
self.duplex = duplex;
MacRegs::set_duplex_full(matches!(duplex, Duplex::Full));
}
pub fn update_link(&mut self, speed: Speed, duplex: Duplex) {
self.set_speed(speed);
self.set_duplex(duplex);
}
pub fn set_promiscuous(&mut self, enable: bool) {
self.config.promiscuous = enable;
MacRegs::set_promiscuous(enable);
}
pub fn set_pass_all_multicast(&mut self, enable: bool) {
MacRegs::set_pass_all_multicast(enable);
}
pub fn set_broadcast_enabled(&mut self, enable: bool) {
MacRegs::set_broadcast_enabled(enable);
}
pub fn write_phy_reg(&self, phy_addr: u8, reg: u8, value: u16) -> Result<()> {
self.wait_mii_not_busy()?;
MacRegs::set_mii_data(value as u32);
let cmd = GMACMIIADDR_GB
| GMACMIIADDR_GW
| ((phy_addr as u32) << GMACMIIADDR_PA_SHIFT)
| ((reg as u32) << GMACMIIADDR_GR_SHIFT)
| ((CSR_CLOCK_DIV_42 << GMACMIIADDR_CR_SHIFT) & GMACMIIADDR_CR_MASK);
MacRegs::set_mii_address(cmd);
self.wait_mii_not_busy()
}
pub fn read_phy_reg(&self, phy_addr: u8, reg: u8) -> Result<u16> {
self.wait_mii_not_busy()?;
let cmd = GMACMIIADDR_GB
| ((phy_addr as u32) << GMACMIIADDR_PA_SHIFT)
| ((reg as u32) << GMACMIIADDR_GR_SHIFT)
| ((CSR_CLOCK_DIV_42 << GMACMIIADDR_CR_SHIFT) & GMACMIIADDR_CR_MASK);
MacRegs::set_mii_address(cmd);
self.wait_mii_not_busy()?;
let value = MacRegs::mii_data() & 0xFFFF;
Ok(value as u16)
}
fn wait_mii_not_busy(&self) -> Result<()> {
for _ in 0..MII_BUSY_TIMEOUT {
if !MacRegs::is_mii_busy() {
return Ok(());
}
core::hint::spin_loop();
}
Err(IoError::PhyError.into())
}
pub fn interrupt_status(&self) -> InterruptStatus {
InterruptStatus::from_raw(DmaRegs::status())
}
pub fn clear_interrupts(&self, status: InterruptStatus) {
DmaRegs::set_status(status.to_raw());
}
pub fn clear_all_interrupts(&self) {
DmaRegs::clear_all_interrupts();
}
pub fn handle_interrupt(&self) -> InterruptStatus {
let status = self.interrupt_status();
self.clear_interrupts(status);
status
}
pub fn enable_tx_interrupt(&self, enable: bool) {
let mut int_en = DmaRegs::interrupt_enable();
if enable {
int_en |= 1 << 0; } else {
int_en &= !(1 << 0);
}
DmaRegs::set_interrupt_enable(int_en);
}
pub fn enable_rx_interrupt(&self, enable: bool) {
let mut int_en = DmaRegs::interrupt_enable();
if enable {
int_en |= 1 << 6; } else {
int_en &= !(1 << 6);
}
DmaRegs::set_interrupt_enable(int_en);
}
pub fn tx_descriptors_available(&self) -> usize {
self.dma.tx_available()
}
pub fn rx_frames_waiting(&self) -> usize {
self.dma.rx_frame_count()
}
pub const fn memory_usage() -> usize {
DmaEngine::<RX_BUFS, TX_BUFS, BUF_SIZE>::memory_usage()
+ core::mem::size_of::<EmacConfig>()
+ core::mem::size_of::<State>()
+ 6 + core::mem::size_of::<Speed>()
+ core::mem::size_of::<Duplex>()
}
}
impl<const RX_BUFS: usize, const TX_BUFS: usize, const BUF_SIZE: usize> Default
for Emac<RX_BUFS, TX_BUFS, BUF_SIZE>
{
fn default() -> Self {
Self::new()
}
}
unsafe impl<const RX_BUFS: usize, const TX_BUFS: usize, const BUF_SIZE: usize> Sync
for Emac<RX_BUFS, TX_BUFS, BUF_SIZE>
{
}
unsafe impl<const RX_BUFS: usize, const TX_BUFS: usize, const BUF_SIZE: usize> Send
for Emac<RX_BUFS, TX_BUFS, BUF_SIZE>
{
}
pub type EmacDefault = Emac<10, 10, 1600>;
pub type EmacSmall = Emac<4, 4, 1600>;
pub type EmacLarge = Emac<16, 16, 1600>;