From 16e277aaa9bf1808795471b3a901f73d6c8d868d Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sat, 28 Jun 2025 22:30:57 +0200 Subject: [PATCH] phy stuff --- examples/zedboard/Cargo.toml | 1 + examples/zedboard/src/bin/ethernet.rs | 167 +++++++++++++++++++++++++- zynq7000-embassy/Cargo.toml | 9 +- zynq7000-hal/src/eth/mdio.rs | 12 ++ zynq7000-hal/src/eth/mod.rs | 63 ++++++++-- zynq7000/src/lib.rs | 4 + 6 files changed, 236 insertions(+), 20 deletions(-) diff --git a/examples/zedboard/Cargo.toml b/examples/zedboard/Cargo.toml index 4678716..653bbc5 100644 --- a/examples/zedboard/Cargo.toml +++ b/examples/zedboard/Cargo.toml @@ -20,6 +20,7 @@ zynq7000-hal = { path = "../../zynq7000-hal" } zynq7000-embassy = { path = "../../zynq7000-embassy" } l3gd20 = { git = "https://github.com/us-irs/l3gd20.git", branch = "add-async-if" } embedded-io = "0.6" +bitbybit = "1.3" arbitrary-int = "1.3" embedded-io-async = "0.6" critical-section = "1" diff --git a/examples/zedboard/src/bin/ethernet.rs b/examples/zedboard/src/bin/ethernet.rs index 2d03084..123df28 100644 --- a/examples/zedboard/src/bin/ethernet.rs +++ b/examples/zedboard/src/bin/ethernet.rs @@ -1,6 +1,8 @@ #![no_std] #![no_main] +use arbitrary_int::{u2, u4, u5}; +use bitbybit::bitfield; use core::{mem::MaybeUninit, panic::PanicInfo}; use cortex_ar::asm::nop; use embassy_executor::Spawner; @@ -10,19 +12,21 @@ use embedded_io::Write; use log::{error, info}; use zedboard::PS_CLOCK_FREQUENCY; use zynq7000_hal::{ - BootMode, clocks::Clocks, configure_level_shifter, + eth::{EthernetConfig, EthernetLowLevel}, gic::{GicConfigurator, GicInterruptHelper, Interrupt}, gpio::{GpioPins, Output, PinState}, gtc::Gtc, uart::{ClkConfigRaw, Uart, UartConfig}, + BootMode, }; -use zynq7000::{PsPeripherals, slcr::LevelShifterConfig}; +use zynq7000::{eth::MdcClkDiv, slcr::LevelShifterConfig, PsPeripherals}; use zynq7000_rt::{self as _, mmu::section_attrs::SHAREABLE_DEVICE, mmu_l1_table_mut}; const INIT_STRING: &str = "-- Zynq 7000 Zedboard Ethernet Example --\n\r"; +const MAC_ADDRESS: [u8; 6] = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06]; #[unsafe(link_section = ".uncached")] static RX_DESCRIPTORS: static_cell::ConstStaticCell< @@ -59,11 +63,117 @@ pub extern "C" fn boot_core(cpu_id: u32) -> ! { main(); } +#[derive(Clone, Debug)] +pub struct PhyIdentifier { + pub oui: u32, + pub model: u8, + pub rev: u8, +} + +// Organizational Unique Identifier for Marvell 88E1518 PHY +const MARVELL_88E1518_OUI: u32 = 0x005043; +const MARVELL_88E1518_MODELL_NUMBER: u8 = 0b011101; + +#[bitbybit::bitenum(u5, exhaustive = false)] +pub enum MarvellRegistersPage0 { + CopperControl = 0x00, + CopperStatus = 0x01, + IdReg1 = 0x02, + IdReg2 = 0x03, +} + +pub struct Marvell88E1518Phy { + mdio: zynq7000_hal::eth::mdio::Mdio, + addr: u5, +} + +impl<'mdio> Marvell88E1518Phy<'mdio> { + pub fn new_autoprobe_addr( + mdio: &zynq7000_hal::eth::mdio::Mdio, + ) -> Option<(Self, u4)> { + for addr in 0..32 { + let phy_id_1 = + mdio.read_blocking(u5::new(addr), MarvellRegistersPage0::IdReg1.raw_value()); + let phy_id_2 = + mdio.read_blocking(u5::new(addr), MarvellRegistersPage0::IdReg2.raw_value()); + let oui = (((phy_id_2 as u32) >> 10) << 19) | ((phy_id_1 as u32) << 3); + let model_number = ((phy_id_2 >> 4) & 0b111111) as u8; + let revision_number = u4::new((phy_id_2 & 0b1111) as u8); + if oui == MARVELL_88E1518_OUI && model_number == MARVELL_88E1518_MODELL_NUMBER { + return Some(( + Self { + mdio: unsafe { mdio.clone() }, + addr: u5::new(addr), + }, + revision_number, + )); + } + } + None + } + + pub fn new(mdio: zynq7000_hal::eth::mdio::Mdio, addr: u5) -> Self { + Self { mdio, addr } + } +} + +#[bitbybit::bitfield(u16)] +pub struct CopperControlRegister { + #[bit(15, rw)] + copper_reset: bool, + #[bit(14, rw)] + loopback: bool, + #[bit(12, rw)] + auto_negotiation_enable: bool, + #[bit(11, rw)] + power_down: bool, + #[bit(10, rw)] + isolate: bool, + #[bit(9, rw)] + restart_auto_negotiation: bool, + /// 1: Full-duplex, 0: Half-duplex + #[bit(8, rw)] + copper_duplex_mode: bool, + #[bits([13, 6], rw)] + speed_selection: u2, +} + +impl Marvell88E1518Phy<'_> { + pub fn reset(&mut self) { + let mut ctrl = CopperControlRegister::new_with_raw_value( + self.mdio + .read_blocking(self.addr, MarvellRegistersPage0::CopperControl.raw_value()), + ); + ctrl.set_copper_reset(true); + self.mdio.write_blocking( + self.addr, + MarvellRegistersPage0::CopperControl.raw_value(), + ctrl.raw_value(), + ); + } + + pub fn restart_auto_negotiation(&mut self) { + let mut ctrl = CopperControlRegister::new_with_raw_value( + self.mdio + .read_blocking(self.addr, MarvellRegistersPage0::CopperControl.raw_value()), + ); + ctrl.set_auto_negotiation_enable(true); + ctrl.set_restart_auto_negotiation(true); + self.mdio.write_blocking( + self.addr, + MarvellRegistersPage0::CopperControl.raw_value(), + ctrl.raw_value(), + ); + } +} + #[embassy_executor::main] #[unsafe(export_name = "main")] async fn main(_spawner: Spawner) -> ! { // Configure the uncached memory region using the MMU. - mmu_l1_table_mut().update(UNCACHED_ADDR, SHAREABLE_DEVICE); + mmu_l1_table_mut() + .update(UNCACHED_ADDR, SHAREABLE_DEVICE) + .expect("configuring uncached memory section failed"); // Enable PS-PL level shifters. configure_level_shifter(LevelShifterConfig::EnableAll); @@ -121,6 +231,57 @@ async fn main(_spawner: Spawner) -> ! { rx_descr_ref.set_rx_buf_address(index, rx_buf.0.as_ptr() as u32); } + // 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, + ); + info!( + "Calculated RGMII clock configuration: {:?}, error: {}", + clk_config, clk_error + ); + // Unwrap okay, we use a standard clock config, and the clock config should never fail. + let eth_cfg = EthernetConfig::new( + clk_config, + zynq7000_hal::eth::calculate_mdc_clk_div(clocks.arm_clocks()).unwrap(), + MAC_ADDRESS, + ); + // Configures all the physical pins for ethernet operation and sets up the + // ethernet peripheral. + let mut eth = zynq7000_hal::eth::Ethernet::new_with_mio( + eth_ll, + eth_cfg, + gpio_pins.mio.mio16, + gpio_pins.mio.mio21, + ( + gpio_pins.mio.mio17, + gpio_pins.mio.mio18, + gpio_pins.mio.mio19, + gpio_pins.mio.mio20, + ), + gpio_pins.mio.mio22, + gpio_pins.mio.mio27, + ( + gpio_pins.mio.mio23, + gpio_pins.mio.mio24, + gpio_pins.mio.mio25, + gpio_pins.mio.mio26, + ), + Some((gpio_pins.mio.mio52, gpio_pins.mio.mio53)), + ); + eth.set_rx_buf_descriptor_base_address(rx_descr_ref.base_addr()); + eth.set_tx_buf_descriptor_base_address(tx_descr_ref.base_addr()); + let (mut phy, phy_rev) = Marvell88E1518Phy::new_autoprobe_addr(eth.mdio_mut()).unwrap(); + info!("Detected Marvell 88E1518 PHY with revision number: {:?}", phy_rev); + phy.reset(); + phy.restart_auto_negotiation(); + + // TODO: + // 1. PHY configuration. + // 2. Interrupt handler for ethernet RX and TX. Need to pass the buffers and descriptors + // to the interrupt handler. + // info!("Boot mode: {:?}", boot_mode); let mut ticker = Ticker::every(Duration::from_millis(200)); diff --git a/zynq7000-embassy/Cargo.toml b/zynq7000-embassy/Cargo.toml index fc6c052..593ebc1 100644 --- a/zynq7000-embassy/Cargo.toml +++ b/zynq7000-embassy/Cargo.toml @@ -15,12 +15,7 @@ critical-section = "1" once_cell = { version = "1", default-features = false, features = ["critical-section"] } zynq7000-hal = { path = "../zynq7000-hal" } -<<<<<<< HEAD -embassy-time-driver = { git = "https://github.com/embassy-rs/embassy.git", branch = "main", version = "0.2" } -embassy-time-queue-utils = { git = "https://github.com/embassy-rs/embassy.git", branch = "main", version = "0.1" } -======= -# embassy-time-driver = { git = "https://github.com/us-irs/embassy", branch = "add-cortex-ar-support", version = "0.2" } -# embassy-time-queue-utils = { git = "https://github.com/us-irs/embassy", branch = "add-cortex-ar-support", version = "0.2" } +# embassy-time-driver = { git = "https://github.com/embassy-rs/embassy.git", branch = "main", version = "0.2" } +# embassy-time-queue-utils = { git = "https://github.com/embassy-rs/embassy.git", branch = "main", version = "0.1" } embassy-time-driver = { path = "../../../Rust/embassy/embassy-time-driver", version = "0.2" } embassy-time-queue-utils = { path = "../../../Rust/embassy/embassy-time-queue-utils", version = "0.1" } ->>>>>>> 807d9df (start adding ethernet support) diff --git a/zynq7000-hal/src/eth/mdio.rs b/zynq7000-hal/src/eth/mdio.rs index bcb1df6..9087b74 100644 --- a/zynq7000-hal/src/eth/mdio.rs +++ b/zynq7000-hal/src/eth/mdio.rs @@ -26,6 +26,18 @@ impl Mdio { } } + /// Steals the MDIO handle from the given Ethernet low-level interface. + /// + /// # Safety + /// + /// Circumvents ownership and safety guarantees of the HAL. + pub unsafe fn clone(&self) -> Self { + Self { + regs: unsafe { self.regs.clone() }, + clause22: self.clause22, + } + } + #[inline] pub fn configure_clock_div(&mut self, clk_div: MdcClkDiv) { self.regs.modify_net_cfg(|mut val| { diff --git a/zynq7000-hal/src/eth/mod.rs b/zynq7000-hal/src/eth/mod.rs index 9649ae2..299196c 100644 --- a/zynq7000-hal/src/eth/mod.rs +++ b/zynq7000-hal/src/eth/mod.rs @@ -1,11 +1,11 @@ use arbitrary_int::{u2, u3}; pub use zynq7000::eth::MdcClkDiv; use zynq7000::eth::{ - BurstLength, DmaRxBufSize, GEM_0_BASE_ADDR, GEM_1_BASE_ADDR, MmioEthernet, NetworkConfig, - SpeedMode, + BurstLength, DmaRxBufSize, MmioEthernet, NetworkConfig, SpeedMode, GEM_0_BASE_ADDR, + GEM_1_BASE_ADDR, }; -pub use ll::{ClkConfig, EthernetLowLevel}; +pub use ll::{ClkConfig, EthernetLowLevel, Speed}; pub mod ll; pub mod mdio; @@ -13,17 +13,22 @@ pub mod rx_descr; pub mod tx_descr; pub const MTU: usize = 1536; +pub const MAX_MDC_SPEED: Hertz = Hertz::from_raw(2_500_000); #[cfg(not(feature = "7z010-7z007s-clg225"))] use crate::gpio::mio::{ Mio16, Mio17, Mio18, Mio19, Mio20, Mio21, Mio22, Mio23, Mio24, Mio25, Mio26, Mio27, }; -use crate::gpio::{ - IoPeriphPin, - mio::{ - Mio28, Mio29, Mio30, Mio31, Mio32, Mio33, Mio34, Mio35, Mio36, Mio37, Mio38, Mio39, Mio52, - Mio53, MioPinMarker, MuxConf, Pin, +use crate::{ + clocks::ArmClocks, + gpio::{ + mio::{ + Mio28, Mio29, Mio30, Mio31, Mio32, Mio33, Mio34, Mio35, Mio36, Mio37, Mio38, Mio39, + Mio52, Mio53, MioPinMarker, MuxConf, Pin, + }, + IoPeriphPin, }, + time::Hertz, }; pub const MUX_CONF_PHY: MuxConf = MuxConf::new_with_l0(); @@ -203,6 +208,23 @@ impl RxData3 for Pin { const ETH_ID: EthernetId = EthernetId::Eth1; } +/// Calculate the CPU 1x clock divisor required to achieve a clock speed which is below +/// 2.5 MHz, as specified by the 802.3 standard. +pub fn calculate_mdc_clk_div(arm_clks: &ArmClocks) -> Option { + let div = arm_clks.cpu_1x_clk().raw().div_ceil(MAX_MDC_SPEED.raw()); + match div { + 0..8 => Some(MdcClkDiv::Div8), + 8..16 => Some(MdcClkDiv::Div16), + 16..32 => Some(MdcClkDiv::Div32), + 32..64 => Some(MdcClkDiv::Div64), + 64..128 => Some(MdcClkDiv::Div128), + 128..224 => Some(MdcClkDiv::Div224), + // MDC clock divisor is too high for the maximum speed. + // This is not a valid configuration. + _ => None, + } +} + #[derive(Debug, Clone, Copy)] pub struct EthernetConfig { pub clk_config: ClkConfig, @@ -372,7 +394,10 @@ impl Ethernet { ll.configure_clock(config.clk_config); let mut mdio = mdio::Mdio::new(&ll, true); mdio.configure_clock_div(config.mdc_clk_div); - Ethernet { ll, mdio } + let eth = Ethernet { ll, mdio } + eth.set_rx_buf_descriptor_base_address(0); + eth.set_tx_buf_descriptor_base_address(0); + eth } pub fn new(mut ll: EthernetLowLevel, config: EthernetConfig) -> Self { @@ -405,7 +430,6 @@ impl Ethernet { // to disable the other addresses here. ll.regs.write_addr1_low(macaddr_lsbs); ll.regs.write_addr1_high(macaddr_msbs); - // TODO ll.regs.modify_dma_cfg(|mut val| { val.set_rx_packet_buf_size_sel(u2::new(0b11)); val.set_tx_packet_buf_size_sel(true); @@ -428,10 +452,29 @@ impl Ethernet { &self.ll.regs } + #[inline] + pub fn mdio(&mut self) -> &mdio::Mdio { + &self.mdio + } + + pub fn mdio_mut(&mut self) -> &mut mdio::Mdio { + &mut self.mdio + } + #[inline] pub fn regs_mut(&mut self) -> &mut MmioEthernet<'static> { &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); + } + + #[inline] + pub fn set_tx_buf_descriptor_base_address(&mut self, addr: u32) { + self.ll.set_rx_buf_descriptor_base_address(addr); + } } mod shared { diff --git a/zynq7000/src/lib.rs b/zynq7000/src/lib.rs index 51ad44a..431c8e6 100644 --- a/zynq7000/src/lib.rs +++ b/zynq7000/src/lib.rs @@ -48,6 +48,8 @@ pub struct PsPeripherals { pub slcr: slcr::MmioSlcr<'static>, pub ttc_0: ttc::MmioTtc<'static>, pub ttc_1: ttc::MmioTtc<'static>, + pub eth_0: eth::MmioEthernet<'static>, + pub eth_1: eth::MmioEthernet<'static> } impl PsPeripherals { @@ -81,6 +83,8 @@ impl PsPeripherals { i2c_1: i2c::I2c::new_mmio_fixed_1(), ttc_0: ttc::Ttc::new_mmio_fixed_0(), ttc_1: ttc::Ttc::new_mmio_fixed_1(), + eth_0: eth::Ethernet::new_mmio_fixed_0(), + eth_1: eth::Ethernet::new_mmio_fixed_1(), } } }