Skip to main content

esp_hal/ethernet/phy/
mod.rs

1//! PHY abstraction layer.
2//!
3//! Defines the [`Phy`] trait, standard IEEE 802.3 Clause 22 MDIO register
4//! addresses, and a thin [`MdioDriver`] wrapper around the EMAC's built-in
5//! MDIO controller.
6
7use core::task::Context;
8
9use crate::ethernet::mac::{EmacRegs, LinkState, Speed};
10
11pub mod generic;
12
13// ── Standard Clause 22 register addresses ──────────────────────────────────
14
15/// Basic Mode Control Register.
16pub const BMCR: u8 = 0x00;
17/// Basic Mode Status Register.
18pub const BMSR: u8 = 0x01;
19/// PHY Identifier 1.
20pub const PHYIDR1: u8 = 0x02;
21/// PHY Identifier 2.
22pub const PHYIDR2: u8 = 0x03;
23/// Auto-Negotiation Advertisement Register.
24pub const ANAR: u8 = 0x04;
25/// Auto-Negotiation Link Partner Ability Register.
26pub const ANLPAR: u8 = 0x05;
27/// Auto-Negotiation Expansion Register.
28pub const ANER: u8 = 0x06;
29/// 1000BASE-T Control Register.
30pub const GBCR: u8 = 0x09;
31/// 1000BASE-T Status Register.
32pub const GBSR: u8 = 0x0A;
33/// Extended Status Register.
34pub const EXSR: u8 = 0x0F;
35
36/// Bit definitions for [`BMCR`].
37pub mod bmcr {
38    /// Software reset.
39    pub const RESET: u16 = 1 << 15;
40    /// Selects 100 Mbps (when auto-neg disabled).
41    pub const SPEED_100: u16 = 1 << 13;
42    /// Enables auto-negotiation.
43    pub const ANEN: u16 = 1 << 12;
44    /// IEEE power-down (all except MDIO).
45    pub const POWER_DOWN: u16 = 1 << 11;
46    /// Restarts auto-negotiation.
47    pub const RESTART_AN: u16 = 1 << 9;
48    /// Full-duplex (when auto-neg disabled).
49    pub const FULL_DUPLEX: u16 = 1 << 8;
50    /// 1000 Mbps MSB (with [`SPEED_100`] clear) when auto-neg is disabled.
51    pub const SPEED_1000: u16 = 1 << 6;
52}
53
54/// Bit definitions for [`BMSR`].
55pub mod bmsr {
56    /// Link status (latch-low on many PHYs — read twice to get current value).
57    pub const LINK_STATUS: u16 = 1 << 2;
58    /// Auto-negotiation complete.
59    pub const AN_COMPLETE: u16 = 1 << 5;
60    /// Auto-negotiation ability.
61    pub const AN_ABILITY: u16 = 1 << 3;
62    /// Extended status register is present (register 15).
63    pub const EXTENDED_STATUS: u16 = 1 << 8;
64}
65
66/// Bit definitions for [`ANAR`] and [`ANLPAR`].
67pub mod an {
68    /// 10BASE-T half-duplex.
69    pub const BASE_10_HALF: u16 = 1 << 5;
70    /// 10BASE-T full-duplex.
71    pub const BASE_10_FULL: u16 = 1 << 6;
72    /// 100BASE-TX half-duplex.
73    pub const BASE_100_HALF: u16 = 1 << 7;
74    /// 100BASE-TX full-duplex.
75    pub const BASE_100_FULL: u16 = 1 << 8;
76}
77
78/// Bit definitions for [`GBCR`].
79pub mod gbcr {
80    /// Advertise 1000BASE-T half-duplex.
81    pub const ADV_1000_HALF: u16 = 1 << 8;
82    /// Advertise 1000BASE-T full-duplex.
83    pub const ADV_1000_FULL: u16 = 1 << 9;
84    /// Prefer 1000BASE-T master (AN, not forced).
85    pub const PREFER_MASTER: u16 = 1 << 11;
86}
87
88/// Bit definitions for [`GBSR`].
89pub mod gbsr {
90    /// Link partner 1000BASE-T half-duplex.
91    pub const LP_1000_HALF: u16 = 1 << 10;
92    /// Link partner 1000BASE-T full-duplex.
93    pub const LP_1000_FULL: u16 = 1 << 11;
94}
95
96/// Bit definitions for [`EXSR`].
97pub mod exsr {
98    /// 1000BASE-T half-duplex ability.
99    pub const BASE1000_T_HD: u16 = 1 << 12;
100    /// 1000BASE-T full-duplex ability.
101    pub const BASE1000_T_FD: u16 = 1 << 13;
102}
103
104// ── MdioBus trait ───────────────────────────────────────────────────────────
105
106/// Generic MDIO bus interface for Clause 22 PHY register access.
107///
108/// PHY drivers are generic over this trait so they can live in external
109/// crates without depending on the concrete [`MdioDriver`]. PHY and
110/// register addresses are 5-bit Clause 22 fields (0-31).
111pub trait MdioBus {
112    /// Reads a 16-bit PHY register.
113    fn read(&mut self, phy_addr: u8, reg_addr: u8) -> u16;
114
115    /// Writes a 16-bit PHY register.
116    fn write(&mut self, phy_addr: u8, reg_addr: u8, value: u16);
117}
118
119// ── MdioDriver ──────────────────────────────────────────────────────────────
120
121/// Thin wrapper that exposes Clause 22 MDIO read/write over the EMAC MAC's
122/// built-in MDIO controller.
123///
124/// Implements [`MdioBus`], so PHY drivers generic over `MdioBus` can run
125/// directly on top of this MAC without any adapter.
126pub struct MdioDriver<'a> {
127    regs: &'a EmacRegs,
128}
129
130impl<'a> MdioDriver<'a> {
131    pub(super) fn new(regs: &'a EmacRegs) -> Self {
132        Self { regs }
133    }
134
135    /// Reads one PHY register (Clause 22).
136    pub fn read(&mut self, phy_addr: u8, reg: u8) -> u16 {
137        // Clause 22 frame fields are 5 bits — out-of-range values are silently
138        // truncated by the PAC writer (`FieldWriter<_, 5>`) and would hit a
139        // different PHY/register without any error. Catch this in debug builds.
140        debug_assert!(phy_addr < 32, "Clause 22 PHY address must be < 32");
141        debug_assert!(reg < 32, "Clause 22 register address must be < 32");
142        self.regs.mdio_read(phy_addr, reg)
143    }
144
145    /// Writes one PHY register (Clause 22).
146    pub fn write(&mut self, phy_addr: u8, reg: u8, data: u16) {
147        debug_assert!(phy_addr < 32, "Clause 22 PHY address must be < 32");
148        debug_assert!(reg < 32, "Clause 22 register address must be < 32");
149        self.regs.mdio_write(phy_addr, reg, data)
150    }
151}
152
153impl<'a> MdioBus for MdioDriver<'a> {
154    fn read(&mut self, phy_addr: u8, reg_addr: u8) -> u16 {
155        MdioDriver::read(self, phy_addr, reg_addr)
156    }
157
158    fn write(&mut self, phy_addr: u8, reg_addr: u8, value: u16) {
159        MdioDriver::write(self, phy_addr, reg_addr, value);
160    }
161}
162
163// ── Phy trait ───────────────────────────────────────────────────────────────
164
165/// PHY error.
166#[derive(Clone, Copy, Debug, Eq, PartialEq)]
167#[cfg_attr(feature = "defmt", derive(defmt::Format))]
168pub enum PhyError {
169    /// The PHY did not respond within the expected time.
170    Timeout,
171    /// No PHY was found on the MDIO bus during auto-address discovery.
172    NotFound,
173}
174
175/// Ethernet PHY driver.
176pub trait Phy {
177    /// One-time hardware initialization.
178    ///
179    /// Must reset the PHY, configure auto-negotiation, and wait until the
180    /// hardware is ready to respond to further MDIO transactions.
181    ///
182    /// `max_speed` is the fastest speed the wired MAC interface can carry (100
183    /// Mbps for MII/RMII, 1000 Mbps for RGMII). Faster abilities must not be
184    /// advertised, or the link partner may settle on a speed the MAC cannot
185    /// drive.
186    fn init<M: MdioBus>(&mut self, mdio: &mut M, max_speed: Speed) -> Result<(), PhyError>;
187
188    /// Polls the current link state.
189    ///
190    /// Returns [`LinkState`] with `up == true` once the link partner is
191    /// detected.
192    ///
193    /// In async mode the context must be used to schedule the next poll:
194    /// register a waker for a PHY interrupt or a timer, or wake immediately.
195    /// An implementation that does neither will never be polled again, so
196    /// drivers without an interrupt source wake immediately, which busy-polls.
197    /// Wrap them to rate-limit the polling if a timer is available.
198    fn poll_link<M: MdioBus>(&mut self, mdio: &mut M, _cx: Option<&mut Context<'_>>) -> LinkState;
199
200    /// Returns the Clause 22 PHY address on the MDIO bus.
201    fn address(&self) -> u8;
202}