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::{ANAR, ANLPAR, BMCR, BMSR, MdioBus, PHYIDR1, Phy, PhyError, an, bmcr, bmsr},
10};
11
12/// Maximum iterations to wait for the PHY reset bit to self-clear.
13const RESET_POLL_LIMIT: u32 = 500_000; // us
14
15/// Generic PHY driver.
16///
17/// Can be constructed with a fixed address ([`GenericPhy::new`]) or with
18/// automatic address discovery ([`GenericPhy::new_auto`]).  When using
19/// `new_auto`, the MDIO bus is scanned during [`Phy::init`] and the first
20/// responding PHY address is adopted.
21#[derive(Clone, Copy, Debug)]
22pub struct GenericPhy {
23    /// `Some(addr)` for a fixed address; `None` until auto-discovery runs.
24    addr: Option<u8>,
25}
26
27impl GenericPhy {
28    /// Creates a driver instance for the given MDIO bus address.
29    pub const fn new(addr: u8) -> Self {
30        Self { addr: Some(addr) }
31    }
32
33    /// Creates a driver instance that discovers the PHY address automatically
34    /// by scanning the MDIO bus during [`Phy::init`].
35    ///
36    /// Returns [`PhyError::NotFound`] from `init` if no PHY responds.
37    pub const fn new_auto() -> Self {
38        Self { addr: None }
39    }
40
41    /// Scans all 32 Clause 22 addresses and returns the first one that holds a
42    /// valid `PHYIDR1` value (not 0x0000 or 0xFFFF).
43    ///
44    /// The scan is repeated up to 3 times to handle transient bus noise,
45    /// matching the esp-idf `esp_eth_phy_802_3_detect_phy_addr` strategy.
46    fn discover<M: MdioBus>(mdio: &mut M) -> Option<u8> {
47        for _ in 0..3 {
48            for addr in 0..32_u8 {
49                let id = mdio.read(addr, PHYIDR1);
50                if id != 0x0000 && id != 0xFFFF {
51                    debug!("phy found at addr {} - id: {:x}", addr, id);
52                    return Some(addr);
53                }
54            }
55        }
56        None
57    }
58}
59
60impl Phy for GenericPhy {
61    fn address(&self) -> u8 {
62        self.addr
63            .expect("GenericPhy address not yet resolved — call init() first")
64    }
65
66    fn init<M: MdioBus>(&mut self, mdio: &mut M) -> Result<(), PhyError> {
67        // Resolve address if auto-discovery was requested.
68        if self.addr.is_none() {
69            self.addr = Some(Self::discover(mdio).ok_or(PhyError::NotFound)?);
70        }
71        let addr = self.addr.unwrap();
72
73        mdio.write(addr, BMCR, bmcr::RESET);
74
75        // Wait for the RESET bit to self-clear (≤ ~0.5 ms per IEEE 802.3).
76        for _ in 0..RESET_POLL_LIMIT {
77            if mdio.read(addr, BMCR) & bmcr::RESET == 0 {
78                // Reset complete. Configure advertisement then restart AN.
79                let adv =
80                    an::BASE_10_HALF | an::BASE_10_FULL | an::BASE_100_HALF | an::BASE_100_FULL;
81                mdio.write(addr, ANAR, adv | 0x0001); // selector = IEEE 802.3
82
83                // RMW so we don't disturb speed/duplex bits restored by reset.
84                let ctrl = mdio.read(addr, BMCR);
85                mdio.write(addr, BMCR, ctrl | bmcr::ANEN | bmcr::RESTART_AN);
86
87                return Ok(());
88            }
89            crate::rom::ets_delay_us(1);
90        }
91
92        Err(PhyError::Timeout)
93    }
94
95    fn poll_link<M: MdioBus>(&mut self, mdio: &mut M, cx: Option<&mut Context<'_>>) -> LinkState {
96        const LINK_STATE_DOWN: LinkState = LinkState {
97            up: false,
98            speed: Speed::_100M,
99            duplex: Duplex::Full,
100        };
101
102        if let Some(cx) = cx {
103            cx.waker().wake_by_ref();
104        }
105
106        let Some(addr) = self.addr else {
107            debug!("poll_link called before init");
108            return LINK_STATE_DOWN;
109        };
110
111        // Read BMSR twice: first read clears the latch-low LINK_STATUS bit on
112        // some PHYs; the second read gives the real state.
113        let _ = mdio.read(addr, BMSR);
114        let bmsr_val = mdio.read(addr, BMSR);
115
116        trace!("bmsr_val: {:x}", bmsr_val);
117
118        if bmsr_val & bmsr::LINK_STATUS == 0 {
119            return LINK_STATE_DOWN;
120        }
121
122        if bmsr_val & bmsr::AN_COMPLETE == 0 {
123            return LINK_STATE_DOWN;
124        }
125
126        // Link is up and auto-negotiation is complete. read ANLPAR to
127        // determine the negotiated speed/duplex.
128
129        let anlpar_val = mdio.read(addr, ANLPAR);
130        let speed = if anlpar_val & an::BASE_100_FULL != 0 || anlpar_val & an::BASE_100_HALF != 0 {
131            Speed::_100M
132        } else {
133            Speed::_10M
134        };
135        let duplex = if anlpar_val & an::BASE_10_FULL != 0 || anlpar_val & an::BASE_100_FULL != 0 {
136            Duplex::Full
137        } else {
138            Duplex::Half
139        };
140
141        LinkState {
142            up: true,
143            speed,
144            duplex,
145        }
146    }
147}