zynq7000_hal/eth/
ll.rs

1use arbitrary_int::{prelude::*, u6};
2use zynq7000::{
3    eth::{InterruptControl, NetworkControl, RxStatus, TxStatus},
4    slcr::reset::EthernetReset,
5};
6
7use crate::{clocks::IoClocks, enable_amba_peripheral_clock, slcr::Slcr, time::Hertz};
8
9use super::{EthernetId, PsEthernet as _};
10
11pub struct EthernetLowLevel {
12    id: EthernetId,
13    pub regs: zynq7000::eth::MmioEthernet<'static>,
14}
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum Speed {
18    Mbps10,
19    Mbps100,
20    Mbps1000,
21}
22
23impl Speed {
24    pub const fn rgmii_clk_rate(&self) -> Hertz {
25        match self {
26            Speed::Mbps10 => Hertz::from_raw(2_500_000),
27            Speed::Mbps100 => Hertz::from_raw(25_000_000),
28            Speed::Mbps1000 => Hertz::from_raw(125_000_000),
29        }
30    }
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum Duplex {
35    Half,
36    Full,
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub struct ClockDivisors {
41    pub divisor_0: u6,
42    pub divisor_1: u6,
43}
44
45impl ClockDivisors {
46    pub const fn new(divisor_0: u6, divisor_1: u6) -> Self {
47        Self {
48            divisor_0,
49            divisor_1,
50        }
51    }
52
53    /// Calls [Self::calculate_for_rgmii], assuming that the IO clock is the reference clock,
54    /// which is the default clock for the Ethernet module.
55    pub fn calculate_for_rgmii_and_io_clock(io_clks: IoClocks, target_speed: Speed) -> (Self, u32) {
56        Self::calculate_for_rgmii(io_clks.ref_clk(), target_speed)
57    }
58
59    /// Calculate the best clock configuration (divisors) for the given reference clock
60    /// and desired target speed when using a RGMII interface.
61    ///
62    /// Usually, the reference clock will be the IO clock.
63    ///
64    /// Returns a tuple where the first entry is the calcualted clock configuration
65    /// and the second entry is the difference between calculated clock speed for the divisors
66    /// and the target speed. Ideally, this difference should be 0.
67    pub fn calculate_for_rgmii(ref_clk: Hertz, target_speed: Speed) -> (Self, u32) {
68        let mut smallest_diff = u32::MAX;
69        let target_speed = target_speed.rgmii_clk_rate();
70        let mut best_div_0 = u6::new(0);
71        let mut best_div_1 = u6::new(0);
72        for div_1 in 1..=u6::MAX.as_usize() {
73            for div_0 in 1..=u6::MAX.as_usize() {
74                let clk_rate = ref_clk.raw() / div_0 as u32 / div_1 as u32;
75                let diff = (target_speed.raw() as i64 - clk_rate as i64).unsigned_abs() as u32;
76                if diff < smallest_diff {
77                    smallest_diff = diff;
78                    best_div_0 = u6::new(div_0 as u8);
79                    best_div_1 = u6::new(div_1 as u8);
80                }
81                // We found a perfect match. No need to continue.
82                if diff == 0 {
83                    break;
84                }
85            }
86        }
87        (Self::new(best_div_0, best_div_1), smallest_diff)
88    }
89}
90
91/// Full clock configuration for the ethernet peripheral.
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub struct ClockConfig {
94    pub src_sel: zynq7000::slcr::clocks::SrcSelIo,
95    pub use_emio_tx_clk: bool,
96    pub divs: ClockDivisors,
97    /// Enable the clock.
98    pub enable: bool,
99}
100
101impl ClockConfig {
102    pub const fn new(divs: ClockDivisors) -> Self {
103        Self {
104            src_sel: zynq7000::slcr::clocks::SrcSelIo::IoPll,
105            use_emio_tx_clk: false,
106            divs,
107            enable: true,
108        }
109    }
110}
111
112/// This is a set of clock configuration for all relevant speed settings.
113///
114/// Generally, the clock need to be re-configured each time the speed settings change, for example
115/// after a completed auto-negotiation process. The necessary clock configurations for each speed
116/// setting can be pre-calculated and stored using this data structure.
117#[derive(Debug, Clone, Copy)]
118pub struct ClockDivSet {
119    pub cfg_10_mbps: ClockDivisors,
120    pub cfg_100_mbps: ClockDivisors,
121    pub cfg_1000_mbps: ClockDivisors,
122}
123
124impl ClockDivSet {
125    pub const fn new(
126        cfg_10_mbps: ClockDivisors,
127        cfg_100_mbps: ClockDivisors,
128        cfg_1000_mbps: ClockDivisors,
129    ) -> Self {
130        Self {
131            cfg_10_mbps,
132            cfg_100_mbps,
133            cfg_1000_mbps,
134        }
135    }
136
137    #[inline]
138    pub fn clk_divs_for_speed(&self, speed: Speed) -> &ClockDivisors {
139        match speed {
140            Speed::Mbps10 => &self.cfg_10_mbps,
141            Speed::Mbps100 => &self.cfg_100_mbps,
142            Speed::Mbps1000 => &self.cfg_1000_mbps,
143        }
144    }
145
146    /// Calls [Self::calculate_for_rgmii], assuming that the IO clock is the reference clock,
147    /// which is the default clock for the Ethernet module.
148    pub fn calculate_for_rgmii_and_io_clock(io_clks: &IoClocks) -> (Self, [u32; 3]) {
149        Self::calculate_for_rgmii(io_clks.ref_clk())
150    }
151
152    /// Calculate the best clock configuration (divisors) for the given reference clock
153    /// and desired target speed when using a RGMII interface.
154    ///
155    /// Usually, the reference clock will be the IO clock.
156    ///
157    /// Returns a tuple where the first entry is the calcualted clock configuration
158    /// and the second entry is the difference between calculated clock speed for the divisors
159    /// and the target speed. Ideally, this difference should be 0.
160    pub fn calculate_for_rgmii(ref_clk: Hertz) -> (Self, [u32; 3]) {
161        let (cfg_10_mbps, error_10_mbps) =
162            ClockDivisors::calculate_for_rgmii(ref_clk, Speed::Mbps10);
163        let (cfg_100_mbps, error_100_mbps) =
164            ClockDivisors::calculate_for_rgmii(ref_clk, Speed::Mbps100);
165        let (cfg_1000_mbps, error_1000_mbps) =
166            ClockDivisors::calculate_for_rgmii(ref_clk, Speed::Mbps1000);
167        (
168            Self::new(cfg_10_mbps, cfg_100_mbps, cfg_1000_mbps),
169            [error_10_mbps, error_100_mbps, error_1000_mbps],
170        )
171    }
172}
173
174/// Ethernet low-level interface.
175///
176/// Basic building block for higher-level abstraction.
177impl EthernetLowLevel {
178    /// Creates a new instance of the Ethernet low-level interface.
179    #[inline]
180    pub fn new(regs: zynq7000::eth::MmioEthernet<'static>) -> Option<Self> {
181        regs.id()?;
182        Some(EthernetLowLevel {
183            id: regs.id().unwrap(),
184            regs,
185        })
186    }
187
188    /// Create a low-level instance for the given [EthernetId].
189    ///
190    /// # Safety
191    ///
192    /// Circumvents ownership and safety guarantees of the HAL.
193    #[inline]
194    pub const unsafe fn steal(id: EthernetId) -> Self {
195        Self {
196            id,
197            regs: unsafe {
198                match id {
199                    EthernetId::Eth0 => zynq7000::eth::Ethernet::new_mmio_fixed_0(),
200                    EthernetId::Eth1 => zynq7000::eth::Ethernet::new_mmio_fixed_1(),
201                }
202            },
203        }
204    }
205
206    pub fn reset(&mut self, cycles: usize) {
207        let assert_reset = match self.id {
208            EthernetId::Eth0 => EthernetReset::builder()
209                .with_gem1_ref_rst(false)
210                .with_gem0_ref_rst(true)
211                .with_gem1_rx_rst(false)
212                .with_gem0_rx_rst(true)
213                .with_gem1_cpu1x_rst(false)
214                .with_gem0_cpu1x_rst(true)
215                .build(),
216            EthernetId::Eth1 => EthernetReset::builder()
217                .with_gem1_ref_rst(true)
218                .with_gem0_ref_rst(false)
219                .with_gem1_rx_rst(true)
220                .with_gem0_rx_rst(false)
221                .with_gem1_cpu1x_rst(true)
222                .with_gem0_cpu1x_rst(false)
223                .build(),
224        };
225        unsafe {
226            Slcr::with(|regs| {
227                regs.reset_ctrl().write_eth(assert_reset);
228                for _ in 0..cycles {
229                    cortex_ar::asm::nop();
230                }
231                regs.reset_ctrl().write_eth(EthernetReset::DEFAULT);
232            });
233        }
234    }
235
236    #[inline]
237    pub fn enable_peripheral_clock(&mut self) {
238        let periph_sel = match self.id {
239            EthernetId::Eth0 => crate::PeriphSelect::Gem0,
240            EthernetId::Eth1 => crate::PeriphSelect::Gem1,
241        };
242        enable_amba_peripheral_clock(periph_sel);
243    }
244
245    /// Completely configures the clock based on the provided [ClockConfig].
246    ///
247    /// This should be called once when initializing the peripheral.
248    pub fn configure_clock(&mut self, cfg: ClockConfig, enable_rx_clock: bool) {
249        unsafe {
250            Slcr::with(|regs| {
251                let (ptr_gig_eth_clk_ctrl, ptr_gig_eth_rclk_ctrl) = self.id().clk_config_regs(regs);
252                let mut gig_eth_clk_ctrl_val = core::ptr::read_volatile(ptr_gig_eth_clk_ctrl);
253                gig_eth_clk_ctrl_val.set_srcsel(cfg.src_sel);
254                gig_eth_clk_ctrl_val.set_divisor_0(cfg.divs.divisor_0);
255                gig_eth_clk_ctrl_val.set_divisor_1(cfg.divs.divisor_1);
256                gig_eth_clk_ctrl_val.set_use_emio_tx_clk(cfg.use_emio_tx_clk);
257                gig_eth_clk_ctrl_val.set_clk_act(cfg.enable);
258                core::ptr::write_volatile(ptr_gig_eth_clk_ctrl, gig_eth_clk_ctrl_val);
259
260                if enable_rx_clock {
261                    let mut gig_eth_rclk_ctrl_val = core::ptr::read_volatile(ptr_gig_eth_rclk_ctrl);
262                    gig_eth_rclk_ctrl_val.set_clk_enable(true);
263                    core::ptr::write_volatile(ptr_gig_eth_rclk_ctrl, gig_eth_rclk_ctrl_val);
264                }
265            })
266        }
267    }
268
269    /// Re-configures the clock divisors for the Ethernet peripheral.
270    ///
271    /// This might be necessary after auto-negotiation of speed settings.
272    pub fn configure_clock_divs(&mut self, cfg: ClockDivisors) {
273        unsafe {
274            Slcr::with(|regs| {
275                let (ptr_gig_eth_clk_ctrl, _ptr_gig_eth_rclk_ctrl) =
276                    self.id().clk_config_regs(regs);
277                let mut gig_eth_clk_ctrl_val = core::ptr::read_volatile(ptr_gig_eth_clk_ctrl);
278                gig_eth_clk_ctrl_val.set_divisor_0(cfg.divisor_0);
279                gig_eth_clk_ctrl_val.set_divisor_1(cfg.divisor_1);
280                core::ptr::write_volatile(ptr_gig_eth_clk_ctrl, gig_eth_clk_ctrl_val);
281            })
282        }
283    }
284
285    /// Can be used after auto-negotiation to update all relevant speed and duplex
286    /// parameter of the ethernet peripheral.
287    ///
288    /// It is probably a good idea to disable the receiver and transmitter while doing this.
289    /// This function calls [Self::configure_clock_for_speed] and [Self::set_speed_and_duplex].
290    pub fn configure_clock_and_speed_duplex(
291        &mut self,
292        speed: Speed,
293        duplex: Duplex,
294        clk_collection: &ClockDivSet,
295    ) {
296        self.configure_clock_for_speed(speed, clk_collection);
297        self.set_speed_and_duplex(speed, duplex);
298    }
299
300    pub fn configure_clock_for_speed(&mut self, speed: Speed, clk_collection: &ClockDivSet) {
301        match speed {
302            Speed::Mbps10 => self.configure_clock_divs(clk_collection.cfg_10_mbps),
303            Speed::Mbps100 => self.configure_clock_divs(clk_collection.cfg_100_mbps),
304            Speed::Mbps1000 => self.configure_clock_divs(clk_collection.cfg_1000_mbps),
305        }
306    }
307
308    #[inline]
309    pub fn set_promiscous_mode(&mut self, enable: bool) {
310        self.regs.modify_net_cfg(|mut val| {
311            val.set_copy_all_frames(enable);
312            val
313        });
314    }
315
316    #[inline]
317    pub fn set_rx_buf_descriptor_base_address(&mut self, addr: u32) {
318        self.regs.write_rx_buf_queue_base_addr(addr);
319    }
320
321    #[inline]
322    pub fn set_tx_buf_descriptor_base_address(&mut self, addr: u32) {
323        self.regs.write_tx_buf_queue_base_addr(addr);
324    }
325
326    /// This function sets the speed and duplex mode of the Ethernet interface.
327    ///
328    /// This should be called after a completed auto-negotiation process with the negotiated
329    /// settings.
330    pub fn set_speed_and_duplex(&mut self, speed: Speed, duplex: Duplex) {
331        self.regs.modify_net_cfg(|mut val| {
332            val.set_full_duplex(duplex == Duplex::Full);
333            match speed {
334                Speed::Mbps10 => {
335                    val.set_speed_mode(zynq7000::eth::SpeedMode::Low10Mbps);
336                    val.set_gigabit_enable(false);
337                    val
338                }
339                Speed::Mbps100 => {
340                    val.set_speed_mode(zynq7000::eth::SpeedMode::High100Mbps);
341                    val.set_gigabit_enable(false);
342                    val
343                }
344                Speed::Mbps1000 => {
345                    val.set_gigabit_enable(true);
346                    val
347                }
348            }
349        });
350    }
351
352    /// Allows enabling/disabling ethernet receiver and transmitter respectively.
353    #[inline]
354    pub fn set_tx_rx_enable(&mut self, tx_enable: bool, rx_enable: bool) {
355        self.regs.modify_net_ctrl(|mut val| {
356            val.set_rx_enable(rx_enable);
357            val.set_tx_enable(tx_enable);
358            val
359        });
360    }
361
362    /// Performs initialization according to TRM p.541.
363    ///
364    /// These steps do not include any resets or clock configuration.
365    pub fn initialize(&mut self, reset_rx_tx_queue_base_addr: bool) {
366        let mut ctrl_val = NetworkControl::new_with_raw_value(0);
367        self.regs.write_net_ctrl(ctrl_val);
368        // Now clear statistics.
369        ctrl_val.set_clear_statistics(true);
370        self.regs.write_net_ctrl(ctrl_val);
371        self.regs.write_tx_status(TxStatus::new_clear_all());
372        self.regs.write_rx_status(RxStatus::new_clear_all());
373        self.regs
374            .write_interrupt_disable(InterruptControl::new_clear_all());
375        if reset_rx_tx_queue_base_addr {
376            self.regs.write_rx_buf_queue_base_addr(0);
377            self.regs.write_tx_buf_queue_base_addr(0);
378        }
379    }
380
381    #[inline]
382    pub const fn id(&self) -> EthernetId {
383        self.id
384    }
385}