From d2cc61f28fd605c136ca31af0336c0ed0fa8f687 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 8 Jul 2025 22:19:18 +0200 Subject: [PATCH] not quite done, but getting there --- examples/zedboard/src/bin/ethernet.rs | 78 +++++++---- examples/zedboard/src/phy_marvell.rs | 22 ++++ zynq7000-hal/src/eth/ll.rs | 182 ++++++++++++++++++++++---- zynq7000-hal/src/eth/mod.rs | 78 ++++++++--- zynq7000/src/eth.rs | 1 + zynq7000/src/slcr/clocks.rs | 21 ++- 6 files changed, 311 insertions(+), 71 deletions(-) diff --git a/examples/zedboard/src/bin/ethernet.rs b/examples/zedboard/src/bin/ethernet.rs index 0765379..c49589a 100644 --- a/examples/zedboard/src/bin/ethernet.rs +++ b/examples/zedboard/src/bin/ethernet.rs @@ -4,7 +4,7 @@ use core::{mem::MaybeUninit, panic::PanicInfo}; use cortex_ar::asm::nop; use embassy_executor::Spawner; -use embassy_time::{Duration, Ticker}; +use embassy_time::{Duration, Ticker, WithTimeout}; use embedded_hal::digital::StatefulOutputPin; use embedded_io::Write; use log::{error, info}; @@ -14,7 +14,7 @@ use zynq7000_hal::{ BootMode, clocks::Clocks, configure_level_shifter, - eth::{AlignedBuffer, EthernetConfig, EthernetLowLevel}, + eth::{AlignedBuffer, ClkConfigCollection, EthernetConfig, EthernetLowLevel}, gic::{GicConfigurator, GicInterruptHelper, Interrupt}, gpio::{GpioPins, Output, PinState}, gtc::Gtc, @@ -59,12 +59,21 @@ pub extern "C" fn boot_core(cpu_id: u32) -> ! { } #[embassy_executor::task] -async fn net_task( +async fn embassy_net_task( mut runner: embassy_net::Runner<'static, zynq7000_hal::eth::embassy_net::Driver>, ) -> ! { runner.run().await } +#[embassy_executor::task] +async fn led_task(mut mio_led: Output) -> ! { + let mut ticker = Ticker::every(Duration::from_millis(200)); + loop { + mio_led.toggle().unwrap(); + ticker.next().await; // Wait for the next cycle of the ticker + } +} + #[embassy_executor::main] #[unsafe(export_name = "main")] async fn main(spawner: Spawner) -> ! { @@ -136,17 +145,15 @@ async fn main(spawner: Spawner) -> ! { // Unwrap okay, this is a valid peripheral. let eth_ll = EthernetLowLevel::new(dp.eth_0).unwrap(); - let (clk_config, clk_error) = zynq7000_hal::eth::ClkConfig::calculate_for_rgmii( - clocks.io_clocks().ref_clk(), - zynq7000_hal::eth::Speed::Mbps1000, - ); + let (clk_collection, clk_errors) = + ClkConfigCollection::calculate_for_rgmii_and_io_clock(clocks.io_clocks()); info!( - "Calculated RGMII clock configuration: {:?}, error: {}", - clk_config, clk_error + "Calculated RGMII clock configuration: {:?}, errors (missmatch from ideal rate in hertz): {:?}", + clk_collection, clk_errors ); // Unwrap okay, we use a standard clock config, and the clock config should never fail. let eth_cfg = EthernetConfig::new( - clk_config, + clk_collection.cfg_100_mbps, zynq7000_hal::eth::calculate_mdc_clk_div(clocks.arm_clocks()).unwrap(), MAC_ADDRESS, ); @@ -208,19 +215,13 @@ async fn main(spawner: Spawner) -> ! { RESOURCES.init(embassy_net::StackResources::new()), rng.next_u64(), ); - spawner.spawn(net_task(runner)).unwrap(); - - stack.wait_config_up().await; - let network_config = stack.config_v4(); - info!( - "Network configuration is up: DHCP config: {:?}!", - network_config - ); - - let mut ticker = Ticker::every(Duration::from_millis(200)); + spawner.spawn(embassy_net_task(runner)).unwrap(); let mut mio_led = Output::new_for_mio(gpio_pins.mio.mio7, PinState::Low); - let mut emio_leds: [Output; 8] = [ + + spawner.spawn(led_task(mio_led)).unwrap(); + + let mut _emio_leds: [Output; 8] = [ Output::new_for_emio(gpio_pins.emio.take(0).unwrap(), PinState::Low), Output::new_for_emio(gpio_pins.emio.take(1).unwrap(), PinState::Low), Output::new_for_emio(gpio_pins.emio.take(2).unwrap(), PinState::Low), @@ -231,15 +232,34 @@ async fn main(spawner: Spawner) -> ! { Output::new_for_emio(gpio_pins.emio.take(7).unwrap(), PinState::Low), ]; loop { - mio_led.toggle().unwrap(); - - // Create a wave pattern for emio_leds - for led in emio_leds.iter_mut() { - led.toggle().unwrap(); - ticker.next().await; // Wait for the next ticker for each toggle + if !stack.is_link_up() { + loop { + let config_up_fut = stack.wait_config_up(); + let status = phy.read_copper_status(); + // TODO: How is this related to the ethernet stack link state? Should we only + // update the link state to up once auto-negotiation is complete and the clock + // was configured correctly? + if status.auto_negotiation_complete() { + let extended_status = phy.read_copper_specific_status_register_1(); + eth.configure_clock_and_speed_duplex( + // If this has the reserved bits, what do we even do? For this example app, + // I am going to assume this never happens.. + extended_status.speed().as_zynq7000_eth_speed().unwrap(), + extended_status.duplex().as_zynq7000_eth_duplex(), + &clk_collection, + ); + eth.set_tx_rx_enable(true, true); + } + match config_up_fut.with_timeout(Duration::from_millis(100)).await { + Ok(_) => break, + Err(e) => todo!(), + } + } } - - ticker.next().await; // Wait for the next cycle of the ticker + let network_config = stack.config_v4(); + info!("Network configuration is up: DHCP config: {network_config:?}!",); + //mio_led.toggle().unwrap(); + //ticker.next().await; // Wait for the next cycle of the ticker } } diff --git a/examples/zedboard/src/phy_marvell.rs b/examples/zedboard/src/phy_marvell.rs index 4f86b78..1ddb890 100644 --- a/examples/zedboard/src/phy_marvell.rs +++ b/examples/zedboard/src/phy_marvell.rs @@ -104,12 +104,34 @@ pub enum PhySpeedBits { Mbps10 = 0b00, } +impl PhySpeedBits { + #[inline] + pub fn as_zynq7000_eth_speed(&self) -> Option { + match self { + PhySpeedBits::Reserved => None, + PhySpeedBits::Mbps1000 => Some(zynq7000_hal::eth::Speed::Mbps1000), + PhySpeedBits::Mbps100 => Some(zynq7000_hal::eth::Speed::Mbps100), + PhySpeedBits::Mbps10 => Some(zynq7000_hal::eth::Speed::Mbps10), + } + } +} + #[bitbybit::bitenum(u1, exhaustive = true)] pub enum PhyDuplexBit { Full = 1, Half = 0, } +impl PhyDuplexBit { + #[inline] + pub fn as_zynq7000_eth_duplex(&self) -> zynq7000_hal::eth::Duplex { + match self { + PhyDuplexBit::Full => zynq7000_hal::eth::Duplex::Full, + PhyDuplexBit::Half => zynq7000_hal::eth::Duplex::Half, + } + } +} + #[bitbybit::bitfield(u16)] pub struct CopperSpecificStatusRegister { #[bits(14..=15, r)] diff --git a/zynq7000-hal/src/eth/ll.rs b/zynq7000-hal/src/eth/ll.rs index ff44502..f1b3b26 100644 --- a/zynq7000-hal/src/eth/ll.rs +++ b/zynq7000-hal/src/eth/ll.rs @@ -4,7 +4,7 @@ use zynq7000::{ slcr::reset::EthernetReset, }; -use crate::{enable_amba_peripheral_clock, slcr::Slcr, time::Hertz}; +use crate::{clocks::IoClocks, enable_amba_peripheral_clock, slcr::Slcr, time::Hertz}; use super::{EthernetId, PsEthernet as _}; @@ -13,16 +13,6 @@ pub struct EthernetLowLevel { pub regs: zynq7000::eth::MmioEthernet<'static>, } -#[derive(Debug, Clone, Copy)] -pub struct ClkConfig { - pub src_sel: zynq7000::slcr::clocks::SrcSelIo, - pub use_emio_tx_clk: bool, - pub divisor_0: u6, - pub divisor_1: u6, - /// Enable the clock. - pub enable: bool, -} - #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Speed { Mbps10, @@ -40,6 +30,22 @@ impl Speed { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Duplex { + Half, + Full, +} + +#[derive(Debug, Clone, Copy)] +pub struct ClkConfig { + pub src_sel: zynq7000::slcr::clocks::SrcSelIo, + pub use_emio_tx_clk: bool, + pub divisor_0: u6, + pub divisor_1: u6, + /// Enable the clock. + pub enable: bool, +} + impl ClkConfig { pub const fn new(divisor_0: u6, divisor_1: u6) -> Self { Self { @@ -51,6 +57,12 @@ impl ClkConfig { } } + /// Calls [Self::calculate_for_rgmii], assuming that the IO clock is the reference clock, + /// which is the default clock for the Ethernet module. + pub fn calculate_for_rgmii_and_io_clock(io_clks: IoClocks, target_speed: Speed) -> (Self, u32) { + Self::calculate_for_rgmii(io_clks.ref_clk(), target_speed) + } + /// Calculate the best clock configuration (divisors) for the given reference clock /// and desired target speed when using a RGMII interface. /// @@ -83,6 +95,58 @@ impl ClkConfig { } } +/// This is a collection of clock configuration for all relevant speed settings. +/// +/// Generally, the clock need to be re-configured each time the speed settings change, for example +/// after a completed auto-negotiation process. The necessary clock configurations for each speed +/// setting can be pre-calculated and stored using this data structure. +#[derive(Debug, Clone, Copy)] +pub struct ClkConfigCollection { + pub cfg_10_mbps: ClkConfig, + pub cfg_100_mbps: ClkConfig, + pub cfg_1000_mbps: ClkConfig, +} + +impl ClkConfigCollection { + pub const fn new( + cfg_10_mbps: ClkConfig, + cfg_100_mbps: ClkConfig, + cfg_1000_mbps: ClkConfig, + ) -> Self { + Self { + cfg_10_mbps, + cfg_100_mbps, + cfg_1000_mbps, + } + } + + /// Calls [Self::calculate_for_rgmii], assuming that the IO clock is the reference clock, + /// which is the default clock for the Ethernet module. + pub fn calculate_for_rgmii_and_io_clock(io_clks: &IoClocks) -> (Self, [u32; 3]) { + Self::calculate_for_rgmii(io_clks.ref_clk()) + } + + /// Calculate the best clock configuration (divisors) for the given reference clock + /// and desired target speed when using a RGMII interface. + /// + /// Usually, the reference clock will be the IO clock. + /// + /// Returns a tuple where the first entry is the calcualted clock configuration + /// and the second entry is the difference between calculated clock speed for the divisors + /// and the target speed. Ideally, this difference should be 0. + pub fn calculate_for_rgmii(ref_clk: Hertz) -> (Self, [u32; 3]) { + let (cfg_10_mbps, error_10_mbps) = ClkConfig::calculate_for_rgmii(ref_clk, Speed::Mbps10); + let (cfg_100_mbps, error_100_mbps) = + ClkConfig::calculate_for_rgmii(ref_clk, Speed::Mbps100); + let (cfg_1000_mbps, error_1000_mbps) = + ClkConfig::calculate_for_rgmii(ref_clk, Speed::Mbps1000); + ( + Self::new(cfg_10_mbps, cfg_100_mbps, cfg_1000_mbps), + [error_10_mbps, error_100_mbps, error_1000_mbps], + ) + } +} + /// Ethernet low-level interface. impl EthernetLowLevel { /// Creates a new instance of the Ethernet low-level interface. @@ -152,22 +216,54 @@ impl EthernetLowLevel { enable_amba_peripheral_clock(periph_sel); } - #[inline] - pub fn configure_clock(&mut self, cfg: ClkConfig) { + pub fn configure_clock(&mut self, cfg: ClkConfig, enable_rx_clock: bool) { unsafe { Slcr::with(|regs| { - regs.clk_ctrl().modify_gem_0_clk_ctrl(|mut val| { - val.set_srcsel(cfg.src_sel); - val.set_divisor_0(cfg.divisor_0); - val.set_divisor_1(cfg.divisor_1); - val.set_use_emio_tx_clk(cfg.use_emio_tx_clk); - val.set_clk_act(cfg.enable); - val - }); + let (ptr_gig_eth_clk_ctrl, ptr_gig_eth_rclk_ctrl) = self.id().clk_config_regs(regs); + let mut gig_eth_clk_ctrl_val = core::ptr::read_volatile(ptr_gig_eth_clk_ctrl); + gig_eth_clk_ctrl_val.set_srcsel(cfg.src_sel); + gig_eth_clk_ctrl_val.set_divisor_0(cfg.divisor_0); + gig_eth_clk_ctrl_val.set_divisor_1(cfg.divisor_1); + gig_eth_clk_ctrl_val.set_use_emio_tx_clk(cfg.use_emio_tx_clk); + gig_eth_clk_ctrl_val.set_clk_act(cfg.enable); + core::ptr::write_volatile(ptr_gig_eth_clk_ctrl, gig_eth_clk_ctrl_val); + + if enable_rx_clock { + let mut gig_eth_rclk_ctrl_val = core::ptr::read_volatile(ptr_gig_eth_rclk_ctrl); + gig_eth_rclk_ctrl_val.set_clk_enable(true); + core::ptr::write_volatile(ptr_gig_eth_rclk_ctrl, gig_eth_rclk_ctrl_val); + } }) } } + /// Can be used after auto-negotiation to update all relevant speed and duplex + /// parameter of the ethernet peripheral. + /// + /// It is probably a good idea to disable the receiver and transmitter while doing this. + /// This function calls [Self::configure_clock_for_speed] and [Self::set_speed_and_duplex]. + pub fn configure_clock_and_speed_duplex( + &mut self, + speed: Speed, + duplex: Duplex, + clk_collection: &ClkConfigCollection, + ) { + self.configure_clock_for_speed(speed, clk_collection); + self.set_speed_and_duplex(speed, duplex); + } + + pub fn configure_clock_for_speed( + &mut self, + speed: Speed, + clk_collection: &ClkConfigCollection, + ) { + match speed { + Speed::Mbps10 => self.configure_clock(clk_collection.cfg_10_mbps, false), + Speed::Mbps100 => self.configure_clock(clk_collection.cfg_100_mbps, false), + Speed::Mbps1000 => self.configure_clock(clk_collection.cfg_1000_mbps, false), + } + } + #[inline] pub fn set_promiscous_mode(&mut self, enable: bool) { self.regs.modify_net_cfg(|mut val| { @@ -186,10 +282,46 @@ impl EthernetLowLevel { self.regs.write_tx_buf_queue_base_addr(addr); } + /// This function sets the speed and duplex mode of the Ethernet interface. + /// + /// This should be called after a completed auto-negotiation process with the negotiated + /// settings. + pub fn set_speed_and_duplex(&mut self, speed: Speed, duplex: Duplex) { + self.regs.modify_net_cfg(|mut val| { + val.set_full_duplex(duplex == Duplex::Full); + match speed { + Speed::Mbps10 => { + val.set_speed_mode(zynq7000::eth::SpeedMode::Low10Mbps); + val.set_gigabit_enable(false); + val + } + Speed::Mbps100 => { + val.set_speed_mode(zynq7000::eth::SpeedMode::High100Mbps); + val.set_gigabit_enable(false); + val + } + Speed::Mbps1000 => { + val.set_gigabit_enable(true); + val + } + } + }); + } + + /// Allows enabling/disabling ethernet receiver and transmitter respectively. + #[inline] + pub fn set_tx_rx_enable(&mut self, tx_enable: bool, rx_enable: bool) { + self.regs.modify_net_ctrl(|mut val| { + val.set_rx_enable(rx_enable); + val.set_tx_enable(tx_enable); + val + }); + } + /// Performs initialization according to TRM p.541. /// /// These steps do not include any resets or clock configuration. - pub fn initialize(&mut self) { + pub fn initialize(&mut self, reset_rx_tx_queue_base_addr: bool) { let mut ctrl_val = NetworkControl::new_with_raw_value(0); self.regs.write_net_ctrl(ctrl_val); // Now clear statistics. @@ -199,8 +331,10 @@ impl EthernetLowLevel { self.regs.write_rx_status(RxStatus::new_clear_all()); self.regs .write_interrupt_disable(InterruptControl::new_clear_all()); - self.regs.write_rx_buf_queue_base_addr(0); - self.regs.write_tx_buf_queue_base_addr(0); + if reset_rx_tx_queue_base_addr { + self.regs.write_rx_buf_queue_base_addr(0); + self.regs.write_tx_buf_queue_base_addr(0); + } } #[inline] diff --git a/zynq7000-hal/src/eth/mod.rs b/zynq7000-hal/src/eth/mod.rs index 8e78037..6444e7d 100644 --- a/zynq7000-hal/src/eth/mod.rs +++ b/zynq7000-hal/src/eth/mod.rs @@ -5,7 +5,7 @@ use zynq7000::eth::{ SpeedMode, }; -pub use ll::{ClkConfig, EthernetLowLevel, Speed}; +pub use ll::{ClkConfig, ClkConfigCollection, Duplex, EthernetLowLevel, Speed}; pub mod embassy_net; pub mod ll; @@ -59,6 +59,25 @@ impl EthernetId { } } } + + pub fn clk_config_regs( + &self, + slcr: &mut zynq7000::slcr::MmioSlcr<'static>, + ) -> ( + *mut zynq7000::slcr::clocks::GigEthClkCtrl, + *mut zynq7000::slcr::clocks::GigEthRclkCtrl, + ) { + match self { + EthernetId::Eth0 => ( + slcr.clk_ctrl().pointer_to_gem_0_clk_ctrl(), + slcr.clk_ctrl().pointer_to_gem_0_rclk_ctrl(), + ), + EthernetId::Eth1 => ( + slcr.clk_ctrl().pointer_to_gem_1_clk_ctrl(), + slcr.clk_ctrl().pointer_to_gem_1_rclk_ctrl(), + ), + } + } } pub trait PsEthernet { @@ -232,15 +251,15 @@ pub fn calculate_mdc_clk_div(arm_clks: &ArmClocks) -> Option { #[derive(Debug, Clone, Copy)] pub struct EthernetConfig { - pub clk_config: ClkConfig, + pub init_clk_config: ClkConfig, pub mdc_clk_div: MdcClkDiv, pub mac_address: [u8; 6], } impl EthernetConfig { - pub fn new(clk_config: ClkConfig, mdc_clk_div: MdcClkDiv, mac_address: [u8; 6]) -> Self { + pub fn new(init_clk_config: ClkConfig, mdc_clk_div: MdcClkDiv, mac_address: [u8; 6]) -> Self { Self { - clk_config, + init_clk_config, mdc_clk_div, mac_address, } @@ -416,7 +435,7 @@ impl Ethernet { }); }); } - ll.configure_clock(config.clk_config); + ll.configure_clock(config.init_clk_config, true); let mut mdio = mdio::Mdio::new(&ll, true); mdio.configure_clock_div(config.mdc_clk_div); let mut eth = Ethernet { ll, mdio }; @@ -427,18 +446,24 @@ impl Ethernet { pub fn new(mut ll: EthernetLowLevel, config: EthernetConfig) -> Self { Self::common_init(&mut ll, config.mac_address); - ll.configure_clock(config.clk_config); + ll.configure_clock(config.init_clk_config, true); let mut mdio = mdio::Mdio::new(&ll, true); mdio.configure_clock_div(config.mdc_clk_div); Ethernet { ll, mdio } } + pub fn reset(&mut self) { + self.ll.reset(3); + // Do not set RX and TX queue base address. + self.ll.initialize(false); + } + fn common_init(ll: &mut EthernetLowLevel, mac_address: [u8; 6]) { ll.enable_peripheral_clock(); ll.reset(3); - ll.initialize(); - // By default, only modify critical network control bits to retain user configuration - // like the MDC clock divisor. + ll.initialize(true); + // Set these to sensible values, but these probably need to be set again after + // auto-negotiation has completed. ll.regs.modify_net_cfg(|mut net_cfg| { net_cfg.set_full_duplex(true); net_cfg.set_gigabit_enable(true); @@ -501,14 +526,35 @@ impl Ethernet { &mut self.ll.regs } - #[inline] - pub fn set_rx_buf_descriptor_base_address(&mut self, addr: u32) { - self.ll.set_rx_buf_descriptor_base_address(addr); - } + delegate::delegate! { + to self.ll { + #[inline] + pub const fn id(&self) -> EthernetId; - #[inline] - pub fn set_tx_buf_descriptor_base_address(&mut self, addr: u32) { - self.ll.set_rx_buf_descriptor_base_address(addr); + #[inline] + pub fn set_rx_buf_descriptor_base_address(&mut self, addr: u32); + + #[inline] + pub fn set_tx_buf_descriptor_base_address(&mut self, addr: u32); + + pub fn configure_clock_and_speed_duplex( + &mut self, + speed: Speed, + duplex: Duplex, + clk_collection: &ClkConfigCollection + ); + + pub fn configure_clock_for_speed( + &mut self, + speed: Speed, + clk_collection: &ClkConfigCollection, + ); + + pub fn set_speed_and_duplex(&mut self, speed: Speed, duplex: Duplex); + + #[inline] + pub fn set_tx_rx_enable(&mut self, tx_enable: bool, rx_enable: bool); + } } } diff --git a/zynq7000/src/eth.rs b/zynq7000/src/eth.rs index 8c815fc..cab3e4a 100644 --- a/zynq7000/src/eth.rs +++ b/zynq7000/src/eth.rs @@ -38,6 +38,7 @@ pub struct NetworkControl { loopback_local: bool, } +/// The speed mode selects between 10 Mbps and 100 Mbps if the Gigabit enable bit is cleared. #[bitbybit::bitenum(u1, exhaustive = true)] #[derive(Debug, PartialEq, Eq)] pub enum SpeedMode { diff --git a/zynq7000/src/slcr/clocks.rs b/zynq7000/src/slcr/clocks.rs index 822c154..4dbf158 100644 --- a/zynq7000/src/slcr/clocks.rs +++ b/zynq7000/src/slcr/clocks.rs @@ -195,6 +195,23 @@ pub struct GigEthClkCtrl { clk_act: bool, } +#[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Debug)] +pub enum SrcSelGigEthRclk { + Mio = 0, + Emio = 1, +} + +#[bitbybit::bitfield(u32)] +#[derive(Debug)] +pub struct GigEthRclkCtrl { + #[bit(4, rw)] + srcsel: SrcSelGigEthRclk, + // Enable the ethernet controller RX clock. + #[bit(0, rw)] + clk_enable: bool, +} + #[bitbybit::bitfield(u32)] #[derive(Debug)] pub struct CanClkCtrl { @@ -320,8 +337,8 @@ pub struct ClockControl { aper_clk_ctrl: AperClkCtrl, usb_0_clk_ctrl: u32, usb_1_clk_ctrl: u32, - gem_0_rclk_ctrl: u32, - gem_1_rclk_ctrl: u32, + gem_0_rclk_ctrl: GigEthRclkCtrl, + gem_1_rclk_ctrl: GigEthRclkCtrl, gem_0_clk_ctrl: GigEthClkCtrl, gem_1_clk_ctrl: GigEthClkCtrl, smc_clk_ctrl: SingleCommonPeriphIoClkCtrl,