Skip to main content

esp_p4_eth/
board.rs

1//! Board-level configuration grouping pin maps, reference clock pin, and PHY
2//! address. A single [`BoardConfig`] value is enough to bring up the EMAC on
3//! a target hardware without per-call argument plumbing.
4//!
5//! Built-in board defaults live as `const` items, e.g. [`BoardConfig::WAVESHARE_P4_ETH`].
6//! For custom hardware, construct your own [`BoardConfig`] literal.
7
8use crate::clock::RefClockPin;
9use crate::pins::{MdioPinConfig, PhyResetPinConfig, RmiiPinConfig};
10
11/// Top-level hardware description used by the EMAC bring-up path.
12#[derive(Clone, Copy, Debug)]
13pub struct BoardConfig {
14    /// RMII data-plane pin assignment (TXD0/1, RXD0/1, TX_EN, CRS_DV).
15    pub rmii_pins: RmiiPinConfig,
16    /// MDIO management bus pin assignment (MDC, MDIO).
17    pub mdio_pins: MdioPinConfig,
18    /// PHY hardware reset pin (active level encoded in the struct).
19    pub phy_reset: PhyResetPinConfig,
20    /// Pad that receives the 50 MHz RMII reference clock from the PHY.
21    pub ref_clock: RefClockPin,
22    /// MDIO address strap for the PHY (0..31).
23    pub phy_addr: u8,
24}
25
26impl BoardConfig {
27    /// Layout used by the Waveshare ESP32-P4-ETH dev board (IP101GRI PHY).
28    ///
29    /// - RMII data: TXD0=GPIO34, TXD1=GPIO35, TX_EN=GPIO49,
30    ///   RXD0=GPIO30, RXD1=GPIO29, CRS_DV=GPIO28
31    /// - MDIO: MDC=GPIO31, MDIO=GPIO52
32    /// - PHY reset: GPIO51 (active low)
33    /// - Reference clock input: GPIO50
34    /// - PHY MDIO address: 1 (Waveshare strap)
35    pub const WAVESHARE_P4_ETH: Self = Self {
36        rmii_pins: RmiiPinConfig::WAVESHARE_P4_ETH,
37        mdio_pins: MdioPinConfig::WAVESHARE_P4_ETH,
38        phy_reset: PhyResetPinConfig::WAVESHARE_P4_ETH,
39        ref_clock: RefClockPin::Gpio50,
40        phy_addr: 1,
41    };
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47    use crate::clock::RefClockPin;
48
49    /// MDIO Clause-22 reserves 5 bits for the address — only 0..31 is legal.
50    /// A typo to e.g. 33 would silently pick `33 & 0x1F = 1` and address the
51    /// wrong PHY without compile-time complaint, so guard the range.
52    #[test]
53    fn waveshare_phy_addr_is_one_and_in_legal_range() {
54        let cfg = BoardConfig::WAVESHARE_P4_ETH;
55        assert_eq!(cfg.phy_addr, 1, "Waveshare straps the IP101 to MDIO addr 1");
56        assert!(cfg.phy_addr < 32, "MDIO addr must fit in 5 bits");
57    }
58
59    #[test]
60    fn waveshare_rmii_pin_map_matches_dev_board_silkscreen() {
61        let pins = BoardConfig::WAVESHARE_P4_ETH.rmii_pins;
62        assert_eq!(pins.txd0, 34);
63        assert_eq!(pins.txd1, 35);
64        assert_eq!(pins.tx_en, 49);
65        assert_eq!(pins.rxd0, 30);
66        assert_eq!(pins.rxd1, 29);
67        assert_eq!(pins.crs_dv, 28);
68    }
69
70    #[test]
71    fn waveshare_mdio_pins_match_dev_board_silkscreen() {
72        let pins = BoardConfig::WAVESHARE_P4_ETH.mdio_pins;
73        assert_eq!(pins.mdc, 31);
74        assert_eq!(pins.mdio, 52);
75    }
76
77    #[test]
78    fn waveshare_phy_reset_is_gpio51_active_low() {
79        let reset = BoardConfig::WAVESHARE_P4_ETH.phy_reset;
80        assert_eq!(reset.pin, 51);
81        assert!(
82            reset.active_low,
83            "IP101GRI RST_N is active low — flipping this strands the PHY in reset"
84        );
85    }
86
87    #[test]
88    fn waveshare_ref_clock_pad_is_gpio50() {
89        assert!(matches!(
90            BoardConfig::WAVESHARE_P4_ETH.ref_clock,
91            RefClockPin::Gpio50
92        ));
93    }
94
95    /// Compile-time check that `BoardConfig` is `Copy + Clone` so it can be
96    /// freely passed by value through the bring-up API without `&BoardConfig`
97    /// lifetime plumbing leaking into call sites.
98    #[test]
99    fn board_config_is_copy_clone() {
100        fn assert_copy<T: Copy>() {}
101        fn assert_clone<T: Clone>() {}
102        assert_copy::<BoardConfig>();
103        assert_clone::<BoardConfig>();
104
105        let a = BoardConfig::WAVESHARE_P4_ETH;
106        let b = a; // compiles only because Copy
107        let _c = a.clone();
108        assert_eq!(a.phy_addr, b.phy_addr);
109    }
110
111    /// All P4 GPIO numbers are < 56; anything higher is a typo. Catches
112    /// transposition errors like 35 ↔ 53.
113    #[test]
114    fn all_waveshare_gpio_numbers_fit_p4_pinout() {
115        let cfg = BoardConfig::WAVESHARE_P4_ETH;
116        let pins = [
117            cfg.rmii_pins.txd0,
118            cfg.rmii_pins.txd1,
119            cfg.rmii_pins.tx_en,
120            cfg.rmii_pins.rxd0,
121            cfg.rmii_pins.rxd1,
122            cfg.rmii_pins.crs_dv,
123            cfg.mdio_pins.mdc,
124            cfg.mdio_pins.mdio,
125            cfg.phy_reset.pin,
126        ];
127        for p in pins {
128            assert!(p < 56, "P4 has GPIO 0..55; got {}", p);
129        }
130    }
131
132    /// No two RMII / MDIO / reset pins may share a GPIO number — that would
133    /// either deadlock the IO_MUX matrix or silently re-route one signal.
134    #[test]
135    fn waveshare_pin_assignments_are_unique() {
136        let cfg = BoardConfig::WAVESHARE_P4_ETH;
137        let pins = [
138            cfg.rmii_pins.txd0,
139            cfg.rmii_pins.txd1,
140            cfg.rmii_pins.tx_en,
141            cfg.rmii_pins.rxd0,
142            cfg.rmii_pins.rxd1,
143            cfg.rmii_pins.crs_dv,
144            cfg.mdio_pins.mdc,
145            cfg.mdio_pins.mdio,
146            cfg.phy_reset.pin,
147        ];
148        let mut sorted = pins;
149        sorted.sort();
150        for w in sorted.windows(2) {
151            assert_ne!(w[0], w[1], "duplicate GPIO assignment in BoardConfig: {}", w[0]);
152        }
153    }
154}