eth_mdio_phy/phy.rs
1// SPDX-License-Identifier: GPL-2.0-or-later OR Apache-2.0
2// Copyright (c) Viacheslav Bocharov <v@baodeep.com> and JetHome (r)
3
4//! PHY driver trait and common error type.
5
6use crate::mdio::MdioBus;
7use crate::types::{LinkStatus, PhyCapabilities};
8
9/// Common error type for PHY driver operations.
10#[derive(Debug)]
11#[non_exhaustive]
12#[cfg_attr(feature = "defmt", derive(defmt::Format))]
13pub enum PhyError<E> {
14 /// MDIO bus error (passthrough).
15 Mdio(E),
16 /// PHY soft reset did not complete within allowed attempts.
17 ResetTimeout,
18 /// PHY ID does not match expected chip family.
19 UnsupportedChip {
20 /// The actual PHY ID read from registers.
21 id: u32,
22 },
23 /// PHY ID matched the expected family, but a chip-specific package
24 /// or variant strap did not. Reported by drivers whose family
25 /// covers more than one silicon SKU and which discriminate the
26 /// concrete part at runtime (e.g. LAN867x via `STRAP_CTRL0.PKGTYP`).
27 UnsupportedPackage {
28 /// Raw strap-register value the driver could not decode,
29 /// zero-extended to 32 bits for forward-compatibility with
30 /// chips that expose wider strap windows.
31 strap: u32,
32 },
33}
34
35impl<E> From<E> for PhyError<E> {
36 fn from(e: E) -> Self {
37 PhyError::Mdio(e)
38 }
39}
40
41/// Ethernet PHY driver — one implementation per chip family.
42///
43/// Every method except [`phy_addr`](Self::phy_addr) takes the bus
44/// generically (`<M: MdioBus>`) so a single driver instance can talk
45/// to different concrete buses across a session — useful for unit
46/// tests against a mock bus and for diagnostic passthroughs.
47///
48/// **Object-safety.** Because of those generic methods the trait is
49/// **not** object-safe — `dyn PhyDriver` is a compile error. If you
50/// need polymorphic storage of multiple PHYs (a switch driver, a
51/// link-state watchdog) keep them as concrete types behind an enum,
52/// or write your own object-safe wrapper that fixes a single
53/// `MdioBus` impl.
54pub trait PhyDriver {
55 /// PHY address on the MDIO bus (0-31).
56 fn phy_addr(&self) -> u8;
57
58 /// Initialize PHY: reset, configure, enable auto-negotiation.
59 fn init<M: MdioBus>(&mut self, mdio: &mut M) -> Result<(), PhyError<M::Error>>;
60
61 /// Poll link status. Returns `Some(LinkStatus)` when up, `None` when down.
62 fn poll_link<M: MdioBus>(
63 &mut self,
64 mdio: &mut M,
65 ) -> Result<Option<LinkStatus>, PhyError<M::Error>>;
66
67 /// Query hardware capabilities.
68 fn capabilities<M: MdioBus>(&self, mdio: &mut M)
69 -> Result<PhyCapabilities, PhyError<M::Error>>;
70
71 /// Read the 32-bit PHY identifier composed of the two ID
72 /// registers at Clause 22 addresses 0x02 and 0x03:
73 /// `(reg_0x02 << 16) | reg_0x03`.
74 ///
75 /// The bit-layout of the result is **chip-family-specific** —
76 /// IEEE 802.3 Clause 22.2.4.3.1 defines OUI/model/revision packing
77 /// for 100BASE-TX (e.g. LAN87xx via `PHYIDR1`/`PHYIDR2`), while
78 /// 10BASE-T1S chips (LAN867x) use the same registers as a flat
79 /// `PHY_ID0`/`PHY_ID1` pair with their own field widths. Drivers
80 /// validate the result against a chip-specific mask; downstream
81 /// consumers should treat it as opaque or consult the relevant
82 /// datasheet.
83 fn phy_id<M: MdioBus>(&self, mdio: &mut M) -> Result<u32, PhyError<M::Error>>;
84}
85
86#[cfg(test)]
87mod tests {
88 extern crate alloc;
89
90 use super::*;
91 use alloc::format;
92
93 /// A trivial bus error type for testing.
94 #[derive(Debug, PartialEq)]
95 struct BusError(u8);
96
97 #[test]
98 fn phy_error_from_bus_error() {
99 let bus_err = BusError(42);
100 let phy_err: PhyError<BusError> = PhyError::from(bus_err);
101 match phy_err {
102 PhyError::Mdio(e) => assert_eq!(e, BusError(42)),
103 _ => panic!("expected Mdio variant"),
104 }
105 }
106
107 #[test]
108 fn phy_error_reset_timeout() {
109 let err: PhyError<BusError> = PhyError::ResetTimeout;
110 match err {
111 PhyError::ResetTimeout => {}
112 _ => panic!("expected ResetTimeout variant"),
113 }
114 }
115
116 #[test]
117 fn phy_error_unsupported_chip() {
118 let err: PhyError<BusError> = PhyError::UnsupportedChip { id: 0x0007_C0F1 };
119 match err {
120 PhyError::UnsupportedChip { id } => assert_eq!(id, 0x0007_C0F1),
121 _ => panic!("expected UnsupportedChip variant"),
122 }
123 }
124
125 #[test]
126 fn phy_error_debug() {
127 let err: PhyError<BusError> = PhyError::Mdio(BusError(1));
128 let dbg = format!("{:?}", err);
129 assert!(dbg.contains("Mdio"), "debug missing 'Mdio': {dbg}");
130 }
131
132 #[test]
133 fn phy_error_into_from_bus() {
134 fn fallible() -> Result<(), BusError> {
135 Err(BusError(7))
136 }
137
138 let result: Result<(), PhyError<BusError>> = fallible().map_err(PhyError::from);
139 match result {
140 Err(PhyError::Mdio(e)) => assert_eq!(e, BusError(7)),
141 _ => panic!("expected Mdio error"),
142 }
143 }
144}