eth-phy-lan87xx
#![no_std] MDIO driver for the Microchip LAN87xx family of 10/100
Ethernet PHYs:
- LAN8710A
- LAN8720A
- LAN8740A
- LAN8741A
- LAN8742A
Implements eth_mdio_phy::PhyDriver,
so any MAC that exposes eth_mdio_phy::MdioBus can drive the chip —
typical case is the ESP32 built-in EMAC SMI controller via
esp_emac::mdio::EspMdio.
Installation
[]
= "0.2"
= "0.2"
| Feature | Default | Pulls in |
|---|---|---|
defmt |
off | defmt::Format derives via eth-mdio-phy/defmt |
MSRV: 1.75. Pure #![no_std]. Works on any target — picking the
target is the MAC layer's problem, not this crate's.
Pre-1.0 SemVer note. Cargo's caret on
^0.1will not pick up0.2.x— both digits behave as the major axis below 1.0. Bump the minor in your manifest explicitly when a new release lands.
Compatibility
| Crate | Version |
|---|---|
eth-mdio-phy |
0.2.x |
For ESP32: esp-emac |
0.2.x |
Quick start
Driving a LAN8720A on an ESP32 board (PHY at MDIO addr 1):
use EspMdio;
use PhyLan87xx;
use ;
# # where EspMdio:
For the full embassy-net + DHCP example see
esp-emac/examples/embassy_net_lan8720a.rs.
Boards with a PHY reset pin
If your board exposes a GPIO-driven PHY nRST line, use
PhyLan87xxWithReset<P> instead of PhyLan87xx. It wraps the same
driver and adds a hardware_reset() method that drives nRST low
for 2 ms, then deasserts and waits 25 ms before MDIO becomes
accessible (LAN8720A datasheet Table 4-2). Most JXD modules do
not route nRST to the MCU; for those use plain PhyLan87xx.
use ;
use ;
use PhyLan87xxWithReset;
# # # where
# P: OutputPin,
# D: DelayNs,
# M: MdioBus,
#
#
Bypassing auto-negotiation
Auto-neg covers the common case. If a board needs a forced link (e.g.
a fixed-speed back-to-back connection) call
eth_mdio_phy::ieee802_3::force_link
directly with the chosen Speed / Duplex after PhyLan87xx::init
returns — that helper clears AN_ENABLE and sets the SPEED_100 /
DUPLEX_FULL bits in BMCR for you.
poll_link automatically detects the BMCR mode (auto-neg vs forced)
and decodes the link state appropriately.
What init does
- Issues
BMCR.RESET(soft reset) and waits for the bit to self-clear. - Reads
PHYIDR1/2and rejects anything that doesn't decode to a known LAN87xx OUI / model. - Disables Energy-Detect Power-Down by clearing
MCSR.EDPD_EN. With EDPD on, the LAN87xx silently drops 10 Mbps frames during the wake-up window after auto-neg — turning it off keeps the RX path active at all times. - Writes
ANAR = 0x01E1explicitly — both the 10BASE-T / 10BASE-T-FD / 100BASE-TX / 100BASE-TX-FD ability bits and the IEEE 802.3 selector field. This step is crucial; see the troubleshooting note below. - Sets
BMCR.AN_ENABLE | BMCR.AN_RESTARTto kick auto-negotiation.
What poll_link does
- Reads
BMSRfor the link bit. - If the PHY is in auto-neg mode (
BMCR.AN_ENABLE = 1), waits forPSCSR.AUTODONEthen decodes the negotiated speed / duplex from the LAN87xx-specific PSCSR register (faster and more reliable than readingANLPARbecause it reflects the actual result rather than the partner's advertisement). - If auto-neg is disabled (forced mode), decodes speed / duplex
directly from
BMCR.SPEED_100/BMCR.DUPLEX_FULL.
Troubleshooting
Link comes up but unicast RX is dead (cold boot only)
Symptoms: ARP requests get replies, but ICMP/TCP times out. Reproduces on cold boot; goes away after a re-flash without power-cycle.
Root cause: on a cold boot of the LAN8720A (and confirmed on its
siblings), issuing BMCR.RESET does NOT restore ANAR to the default
0x01E1. Whatever the PHY has in non-volatile state survives, and
that's typically a subset of the full 10/100 + half/full advertisement.
Auto-neg then converges on the partial subset and the link comes up
at the lowest common denominator — or, worse, succeeds on a speed
that the MAC isn't ready for, so unicast RX wedges and only
broadcast / multicast survive.
This driver handles the case by writing ANAR = 0x01E1 explicitly
between the soft reset and AN_RESTART. If you reimplement this
PHY init elsewhere, do the same — it is the single most common cold-
boot Ethernet failure on LAN87xx.
PhyError::UnsupportedChip { id: 0 } on init
The PHY is on a different MDIO address than the one passed to new().
Typical strap-pin variants put LAN8720A at addr 0 or 1. Try both. On
ESP32 modules, the strap-pin assignment depends on PCB-level pull-up
configuration — check the schematic.
PhyError::UnsupportedChip { id: 0xFFFF_FFFF } on init
MDIO bus reads are floating high — typical signs:
- MDIO line not pulled up (LAN87xx datasheet requires 1.5 kΩ pull-up on MDIO).
- RMII reference clock is not running. The MDIO state machine inside
the PHY needs the 25 MHz clock to be alive even though MDIO is
electrically asynchronous; on LAN8720A, no REF_CLK = no MDIO ACK.
Verify
Emac::init(or your equivalent) brings up the clock before the first MDIO transaction.
Link reports 10 Mbps despite a 100 Mbps switch
ANAR didn't get programmed correctly — see the cold-boot section above. Other possibilities:
- MDIO writes aren't actually reaching the PHY (verify with a scope or a software MDIO trace).
- The PHY's
BMCR.SPEED_100strap pin is tied low and overrides the software value at reset (LAN8720A datasheet table 7-1).
Hardware verified on
- JXD-PM3-80-E1ETH and JXD-R6-E1ETH-LCD (LAN8720A on RMII; ESP32 APLL drives the 50 MHz reference into the PHY through GPIO17). Cold boot, soft reset and USB power-cycle all converge on link up 100 Mbps full duplex within a few hundred milliseconds.
License
Licensed under either of:
- GNU General Public License, Version 2.0 or later
- Apache License, Version 2.0
at your option.