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};
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
30/// Bit definitions for [`BMCR`].
31pub mod bmcr {
32    /// Software reset.
33    pub const RESET: u16 = 1 << 15;
34    /// Select 100 Mbps (when auto-neg disabled).
35    pub const SPEED_100: u16 = 1 << 13;
36    /// Enable auto-negotiation.
37    pub const ANEN: u16 = 1 << 12;
38    /// Restart auto-negotiation.
39    pub const RESTART_AN: u16 = 1 << 9;
40    /// Full-duplex (when auto-neg disabled).
41    pub const FULL_DUPLEX: u16 = 1 << 8;
42}
43
44/// Bit definitions for [`BMSR`].
45pub mod bmsr {
46    /// Link status (latch-low on many PHYs — read twice to get current value).
47    pub const LINK_STATUS: u16 = 1 << 2;
48    /// Auto-negotiation complete.
49    pub const AN_COMPLETE: u16 = 1 << 5;
50    /// Auto-negotiation ability.
51    pub const AN_ABILITY: u16 = 1 << 3;
52}
53
54/// Bit definitions for [`ANAR`] and [`ANLPAR`].
55pub mod an {
56    /// 10BASE-T half-duplex.
57    pub const BASE_10_HALF: u16 = 1 << 5;
58    /// 10BASE-T full-duplex.
59    pub const BASE_10_FULL: u16 = 1 << 6;
60    /// 100BASE-TX half-duplex.
61    pub const BASE_100_HALF: u16 = 1 << 7;
62    /// 100BASE-TX full-duplex.
63    pub const BASE_100_FULL: u16 = 1 << 8;
64}
65
66// ── MdioBus trait ───────────────────────────────────────────────────────────
67
68/// Generic MDIO bus interface for Clause 22 PHY register access.
69///
70/// PHY drivers are generic over this trait so they can live in external
71/// crates without depending on the concrete [`MdioDriver`]. PHY and
72/// register addresses are 5-bit Clause 22 fields (0-31).
73pub trait MdioBus {
74    /// Read a 16-bit PHY register.
75    fn read(&mut self, phy_addr: u8, reg_addr: u8) -> u16;
76
77    /// Write a 16-bit PHY register.
78    fn write(&mut self, phy_addr: u8, reg_addr: u8, value: u16);
79}
80
81// ── MdioDriver ──────────────────────────────────────────────────────────────
82
83/// Thin wrapper that exposes Clause 22 MDIO read/write over the EMAC MAC's
84/// built-in MDIO controller.
85///
86/// Implements [`MdioBus`], so PHY drivers generic over `MdioBus` can run
87/// directly on top of this MAC without any adapter.
88pub struct MdioDriver<'a> {
89    regs: &'a EmacRegs,
90}
91
92impl<'a> MdioDriver<'a> {
93    pub(super) fn new(regs: &'a EmacRegs) -> Self {
94        Self { regs }
95    }
96
97    /// Reads one PHY register (Clause 22).
98    pub fn read(&mut self, phy_addr: u8, reg: u8) -> u16 {
99        // Clause 22 frame fields are 5 bits — out-of-range values are silently
100        // truncated by the PAC writer (`FieldWriter<_, 5>`) and would hit a
101        // different PHY/register without any error. Catch this in debug builds.
102        debug_assert!(phy_addr < 32, "Clause 22 PHY address must be < 32");
103        debug_assert!(reg < 32, "Clause 22 register address must be < 32");
104        self.regs.mdio_read(phy_addr, reg)
105    }
106
107    /// Writes one PHY register (Clause 22).
108    pub fn write(&mut self, phy_addr: u8, reg: u8, data: u16) {
109        debug_assert!(phy_addr < 32, "Clause 22 PHY address must be < 32");
110        debug_assert!(reg < 32, "Clause 22 register address must be < 32");
111        self.regs.mdio_write(phy_addr, reg, data)
112    }
113}
114
115impl<'a> MdioBus for MdioDriver<'a> {
116    fn read(&mut self, phy_addr: u8, reg_addr: u8) -> u16 {
117        MdioDriver::read(self, phy_addr, reg_addr)
118    }
119
120    fn write(&mut self, phy_addr: u8, reg_addr: u8, value: u16) {
121        MdioDriver::write(self, phy_addr, reg_addr, value);
122    }
123}
124
125// ── Phy trait ───────────────────────────────────────────────────────────────
126
127/// PHY error.
128#[derive(Clone, Copy, Debug, Eq, PartialEq)]
129#[cfg_attr(feature = "defmt", derive(defmt::Format))]
130pub enum PhyError {
131    /// The PHY did not respond within the expected time.
132    Timeout,
133    /// No PHY was found on the MDIO bus during auto-address discovery.
134    NotFound,
135}
136
137/// Ethernet PHY driver.
138pub trait Phy {
139    /// One-time hardware initialization.
140    ///
141    /// Should reset the PHY, configure auto-negotiation, and wait until the
142    /// hardware is ready to respond to further MDIO transactions.
143    fn init<M: MdioBus>(&mut self, mdio: &mut M) -> Result<(), PhyError>;
144
145    /// Polls the current link state.
146    ///
147    /// Returns [`LinkState`] with `up == true` once auto-negotiation is
148    /// complete and the link partner is detected. The context can be used
149    /// to wake the caller when the link state should be re-polled, if the PHY chip
150    /// supports interrupt-driven notifications.
151    fn poll_link<M: MdioBus>(&mut self, mdio: &mut M, _cx: Option<&mut Context<'_>>) -> LinkState;
152
153    /// Returns the Clause 22 PHY address on the MDIO bus.
154    fn address(&self) -> u8;
155}