not quite done, but getting there

This commit is contained in:
2025-07-08 22:19:18 +02:00
parent c5a734210a
commit d2cc61f28f
6 changed files with 311 additions and 71 deletions

View File

@@ -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
}
}

View File

@@ -104,12 +104,34 @@ pub enum PhySpeedBits {
Mbps10 = 0b00,
}
impl PhySpeedBits {
#[inline]
pub fn as_zynq7000_eth_speed(&self) -> Option<zynq7000_hal::eth::Speed> {
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)]

View File

@@ -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]

View File

@@ -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<MdcClkDiv> {
#[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);
}
}
}

View File

@@ -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 {

View File

@@ -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,