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}