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}