Skip to main content

esp_hal/ethernet/phy/
generic.rs

1//! Generic PHY driver.
2//!
3//! Standard IEEE 802.3 Clause 22 PHYs with auto-negotiation support.
4
5use core::task::Context;
6
7use crate::ethernet::{
8    mac::{Duplex, LinkState, Speed},
9    phy::{
10        ANAR,
11        ANLPAR,
12        BMCR,
13        BMSR,
14        EXSR,
15        GBCR,
16        GBSR,
17        MdioBus,
18        PHYIDR1,
19        PHYIDR2,
20        Phy,
21        PhyError,
22        an,
23        bmcr,
24        bmsr,
25        exsr,
26        gbcr,
27        gbsr,
28    },
29};
30
31/// Maximum iterations to wait for the PHY reset bit to self-clear.
32const RESET_POLL_LIMIT: u32 = 500_000; // us
33
34/// Motorcomm YT8531 identifier (`PHYIDR1`/`PHYIDR2`).
35const YT8531_ID: (u16, u16) = (0x4F51, 0xE91B);
36
37/// Extended-register address window (Motorcomm).
38const REG_EXT_ADDR: u8 = 0x1E;
39/// Extended-register data window (Motorcomm).
40const REG_EXT_DATA: u8 = 0x1F;
41
42/// Chip config (extended). Bit 8 adds a coarse ~1.9 ns RXC delay.
43const EXT_CHIP_CFG: u16 = 0xA001;
44const RXC_DLY_EN: u16 = 1 << 8;
45
46/// RGMII delay config (extended).
47/// `[13:10]` RX, `[7:4]` 100M TX, `[3:0]` 1000M TX. Unit 150 ps; 13 ≈ 1.95 ns.
48const EXT_RGMII_CFG1: u16 = 0xA003;
49const RGMII_TX_DLY_MASK: u16 = 0x00FF;
50const RGMII_DLY_2NS: u16 = 13;
51
52fn ext_read<M: MdioBus>(mdio: &mut M, addr: u8, ext: u16) -> u16 {
53    mdio.write(addr, REG_EXT_ADDR, ext);
54    mdio.read(addr, REG_EXT_DATA)
55}
56
57fn ext_write<M: MdioBus>(mdio: &mut M, addr: u8, ext: u16, val: u16) {
58    mdio.write(addr, REG_EXT_ADDR, ext);
59    mdio.write(addr, REG_EXT_DATA, val);
60}
61
62/// Generic PHY driver.
63///
64/// Initialization and link handling follow IEEE 802.3 Clause 22, matching
65/// esp-idf `esp_eth_phy_802_3`. PHYs that need chip-specific setup get it
66/// applied from [`Phy::init`] based on their identifier.
67///
68/// Can be constructed with a fixed address ([`GenericPhy::new`]) or with
69/// automatic address discovery ([`GenericPhy::new_auto`]).  When using
70/// `new_auto`, the MDIO bus is scanned during [`Phy::init`] and the first
71/// responding PHY address is adopted.
72#[derive(Clone, Copy, Debug)]
73pub struct GenericPhy {
74    /// `Some(addr)` for a fixed address; `None` until auto-discovery runs.
75    addr: Option<u8>,
76}
77
78impl GenericPhy {
79    /// Creates a new driver instance for the given MDIO bus address.
80    pub const fn new(addr: u8) -> Self {
81        Self { addr: Some(addr) }
82    }
83
84    /// Creates a new driver instance that discovers the PHY address automatically
85    /// by scanning the MDIO bus during [`Phy::init`].
86    ///
87    /// Returns [`PhyError::NotFound`] from `init` if no PHY responds.
88    pub const fn new_auto() -> Self {
89        Self { addr: None }
90    }
91
92    /// Scans all 32 Clause 22 addresses and returns the first one that holds a
93    /// valid `PHYIDR1` value (not 0x0000 or 0xFFFF).
94    ///
95    /// The scan is repeated up to 3 times to handle transient bus noise,
96    /// matching the esp-idf `esp_eth_phy_802_3_detect_phy_addr` strategy.
97    fn discover<M: MdioBus>(mdio: &mut M) -> Option<u8> {
98        for _ in 0..3 {
99            for addr in 0..32_u8 {
100                let id = mdio.read(addr, PHYIDR1);
101                if id != 0x0000 && id != 0xFFFF {
102                    debug!("phy found at addr {} - id: {:x}", addr, id);
103                    return Some(addr);
104                }
105            }
106        }
107        None
108    }
109
110    /// Removes advertisements faster than `max_speed`.
111    ///
112    /// A gigabit PHY wired to an RMII or MII MAC would otherwise negotiate 1000
113    /// Mbps with the link partner and the MAC could not carry it. Returns
114    /// whether an advertisement was withdrawn, in which case auto-negotiation
115    /// has to be restarted to take effect.
116    fn limit_advertisement<M: MdioBus>(mdio: &mut M, addr: u8, max_speed: Speed) -> bool {
117        let mut changed = false;
118
119        if max_speed != Speed::_1000M {
120            let gbcr_val = mdio.read(addr, GBCR);
121            if gbcr_val != 0xFFFF {
122                let limited = gbcr_val & !(gbcr::ADV_1000_FULL | gbcr::ADV_1000_HALF);
123                if limited != gbcr_val {
124                    mdio.write(addr, GBCR, limited);
125                    changed = true;
126                }
127            }
128        }
129
130        if max_speed == Speed::_10M {
131            let anar_val = mdio.read(addr, ANAR);
132            let limited = anar_val & !(an::BASE_100_FULL | an::BASE_100_HALF);
133            if limited != anar_val {
134                mdio.write(addr, ANAR, limited);
135                changed = true;
136            }
137        }
138
139        changed
140    }
141
142    /// Programs the Motorcomm YT8531 RGMII clock delays.
143    ///
144    /// RGMII needs roughly 2 ns of delay on both paths. Do not write the YT8521 `0xA000` UTP/fiber
145    /// page select: the YT8531 is UTP-only and that write maps the Clause 22 registers to dead
146    /// space.
147    fn init_yt8531<M: MdioBus>(mdio: &mut M, addr: u8) {
148        let chip = ext_read(mdio, addr, EXT_CHIP_CFG);
149        ext_write(mdio, addr, EXT_CHIP_CFG, chip | RXC_DLY_EN);
150
151        let mut rgmii = ext_read(mdio, addr, EXT_RGMII_CFG1);
152        rgmii = (rgmii & !RGMII_TX_DLY_MASK) | (RGMII_DLY_2NS << 4) | RGMII_DLY_2NS;
153        ext_write(mdio, addr, EXT_RGMII_CFG1, rgmii);
154
155        // Restart auto-negotiation so the delays are in force before it resolves.
156        let ctrl = mdio.read(addr, BMCR);
157        mdio.write(addr, BMCR, ctrl | bmcr::RESTART_AN);
158    }
159}
160
161impl Phy for GenericPhy {
162    fn address(&self) -> u8 {
163        self.addr
164            .expect("GenericPhy address not yet resolved — call init() first")
165    }
166
167    fn init<M: MdioBus>(&mut self, mdio: &mut M, max_speed: Speed) -> Result<(), PhyError> {
168        // Resolve address if auto-discovery was requested.
169        if self.addr.is_none() {
170            self.addr = Some(Self::discover(mdio).ok_or(PhyError::NotFound)?);
171        }
172        let addr = self.addr.unwrap();
173
174        // Match esp-idf `esp_eth_phy_802_3_basic_phy_init`: power up, then
175        // software reset. ANAR/GBCR keep their post-reset defaults except for
176        // abilities the MAC interface cannot carry.
177        let ctrl = mdio.read(addr, BMCR);
178        if ctrl & bmcr::POWER_DOWN != 0 {
179            mdio.write(addr, BMCR, ctrl & !bmcr::POWER_DOWN);
180            for _ in 0..RESET_POLL_LIMIT {
181                if mdio.read(addr, BMCR) & bmcr::POWER_DOWN == 0 {
182                    break;
183                }
184                crate::rom::ets_delay_us(1);
185            }
186        }
187
188        mdio.write(addr, BMCR, bmcr::RESET);
189        for _ in 0..RESET_POLL_LIMIT {
190            if mdio.read(addr, BMCR) & bmcr::RESET == 0 {
191                // Some PHYs clear ANEN across a reset. Only restore the bit;
192                // advertisements stay at their post-reset defaults.
193                let ctrl = mdio.read(addr, BMCR);
194                if ctrl & bmcr::ANEN == 0 {
195                    mdio.write(addr, BMCR, (ctrl & !bmcr::POWER_DOWN) | bmcr::ANEN);
196                }
197
198                let restart_an = Self::limit_advertisement(mdio, addr, max_speed);
199
200                let id = (mdio.read(addr, PHYIDR1), mdio.read(addr, PHYIDR2));
201                if id == YT8531_ID {
202                    // Also restarts auto-negotiation.
203                    Self::init_yt8531(mdio, addr);
204                } else if restart_an {
205                    let ctrl = mdio.read(addr, BMCR);
206                    mdio.write(addr, BMCR, ctrl | bmcr::RESTART_AN);
207                }
208
209                return Ok(());
210            }
211            crate::rom::ets_delay_us(1);
212        }
213
214        Err(PhyError::Timeout)
215    }
216
217    fn poll_link<M: MdioBus>(&mut self, mdio: &mut M, cx: Option<&mut Context<'_>>) -> LinkState {
218        const LINK_STATE_DOWN: LinkState = LinkState {
219            up: false,
220            speed: Speed::_100M,
221            duplex: Duplex::Full,
222        };
223
224        // No PHY interrupt is available, and esp-hal has no timer to defer to,
225        // so wake immediately to keep being polled. Wrap this driver to
226        // rate-limit the polling (see `Phy::poll_link`).
227        if let Some(cx) = cx {
228            cx.waker().wake_by_ref();
229        }
230
231        let Some(addr) = self.addr else {
232            debug!("poll_link called before init");
233            return LINK_STATE_DOWN;
234        };
235
236        // Read BMSR twice: first read clears the latch-low LINK_STATUS bit on
237        // some PHYs; the second read gives the real state.
238        let _ = mdio.read(addr, BMSR);
239        let bmsr_val = mdio.read(addr, BMSR);
240
241        trace!("bmsr_val: {:x}", bmsr_val);
242
243        // Link is BMSR.LINK_STATUS alone, as in IDF
244        // `esp_eth_phy_802_3_updt_link_dup_spd`. AN_COMPLETE may still be clear
245        // while Clause 40 finishes even though copper is already up.
246        if bmsr_val & bmsr::LINK_STATUS == 0 {
247            return LINK_STATE_DOWN;
248        }
249
250        let bmcr_val = mdio.read(addr, BMCR);
251        if bmcr_val & bmcr::ANEN == 0 {
252            let speed = if bmcr_val & bmcr::SPEED_1000 != 0 {
253                Speed::_1000M
254            } else if bmcr_val & bmcr::SPEED_100 != 0 {
255                Speed::_100M
256            } else {
257                Speed::_10M
258            };
259            let duplex = if bmcr_val & bmcr::FULL_DUPLEX != 0 {
260                Duplex::Full
261            } else {
262                Duplex::Half
263            };
264            return LinkState {
265                up: true,
266                speed,
267                duplex,
268            };
269        }
270
271        if bmsr_val & bmsr::EXTENDED_STATUS != 0 {
272            let exsr_val = mdio.read(addr, EXSR);
273            if exsr_val != 0xFFFF && (exsr_val & (exsr::BASE1000_T_HD | exsr::BASE1000_T_FD) != 0) {
274                let gbcr_val = mdio.read(addr, GBCR);
275                let gbsr_val = mdio.read(addr, GBSR);
276                if gbcr_val != 0xFFFF && gbsr_val != 0xFFFF {
277                    if gbcr_val & gbcr::ADV_1000_FULL != 0 && gbsr_val & gbsr::LP_1000_FULL != 0 {
278                        return LinkState {
279                            up: true,
280                            speed: Speed::_1000M,
281                            duplex: Duplex::Full,
282                        };
283                    }
284                    if gbcr_val & gbcr::ADV_1000_HALF != 0 && gbsr_val & gbsr::LP_1000_HALF != 0 {
285                        return LinkState {
286                            up: true,
287                            speed: Speed::_1000M,
288                            duplex: Duplex::Half,
289                        };
290                    }
291                }
292            }
293        }
294
295        // IDF: resolved mode is ANAR ∩ ANLPAR, highest common ability first.
296        let anar_val = mdio.read(addr, ANAR);
297        let anlpar_val = mdio.read(addr, ANLPAR);
298        let common = anar_val & anlpar_val;
299        let (speed, duplex) = if common & an::BASE_100_FULL != 0 {
300            (Speed::_100M, Duplex::Full)
301        } else if common & an::BASE_100_HALF != 0 {
302            (Speed::_100M, Duplex::Half)
303        } else if common & an::BASE_10_FULL != 0 {
304            (Speed::_10M, Duplex::Full)
305        } else if common & an::BASE_10_HALF != 0 {
306            (Speed::_10M, Duplex::Half)
307        } else {
308            (Speed::_100M, Duplex::Full)
309        };
310
311        LinkState {
312            up: true,
313            speed,
314            duplex,
315        }
316    }
317}