From 80b740827f0aff79326f3741f72baab5f1b9c5a0 Mon Sep 17 00:00:00 2001 From: soypat Date: Thu, 15 Jan 2026 18:31:17 -0300 Subject: [PATCH] begin adding phy package --- phy/mdio.go | 14 ++++++++ phy/phy.go | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++ phy/reg.go | 63 ++++++++++++++++++++++++++++++++++++ 3 files changed, 170 insertions(+) create mode 100644 phy/mdio.go create mode 100644 phy/phy.go create mode 100644 phy/reg.go diff --git a/phy/mdio.go b/phy/mdio.go new file mode 100644 index 0000000..cc5e63c --- /dev/null +++ b/phy/mdio.go @@ -0,0 +1,14 @@ +package phy + +// MDIOBus is a HAL for MDIO bus access supporting both Clause 22 and Clause 45 devices. +// Implementations should use devaddr to select the framing: +// - devaddr=0: Clause 22 framing (devaddr ignored in transaction) +// - devaddr>=1: Clause 45 framing (PMA/PMD=1, WIS=2, PCS=3, PHY XS=4, DTE XS=5, AN=7) +// +// Register address range: Clause 22 uses 0-31, Clause 45 uses 0-65535. +type MDIOBus interface { + // Read reads a 16-bit register from the PHY. + Read(phyAddr, devaddr uint8, regAddr uint16) (value uint16, err error) + // Write writes a 16-bit value to a PHY register. + Write(phyAddr, devaddr uint8, regAddr, value uint16) error +} diff --git a/phy/phy.go b/phy/phy.go new file mode 100644 index 0000000..61c8ce5 --- /dev/null +++ b/phy/phy.go @@ -0,0 +1,93 @@ +// Package phy provides Ethernet PHY management via MDIO. +// It supports IEEE 802.3 Clause 22 and Clause 45 register access +// for configuring and monitoring physical layer transceivers. +package phy + +import ( + "errors" + "time" +) + +// FindPHYs finds all regular non-clause45 PHYs on the MDIO bus and writes them to dst. +func FindPHYs(mdio MDIOBus, dst []uint8) (n int, err error) { + const maxAddr = 31 + const regBasicStatus = 0x01 + if len(dst) < 32 { + return -1, errors.New("require buffer length 32 for FindPHYs") + } + n = 0 + for addr := uint8(0); addr <= maxAddr; addr++ { + // Future proofing for supported clause 45. + // Check PMA/PMD device (DEVAD 1), register 0 (control) + const devAddr = 1 + var val uint16 + val, err = mdio.Read(addr, devAddr, BMCRAddr) + if err != nil { + continue + } + // Basic status has some bits that must be zero and one, so if this check fails then we know its a bad address. + if val != 0xffff && val != 0x0000 { + dst[n] = addr + n++ + } + time.Sleep(150 * time.Microsecond) + } + return n, err +} + +type Device struct { + mdio MDIOBus + phyaddr uint8 + // Is 1 for Clause 45 devices and 0 for clause 22 devices. + clause45 uint8 +} + +func (d *Device) BasicControl() (BMCR, error) { + ctl, err := d.rread(BMCRAddr) + return BMCR(ctl), err +} + +func (d *Device) BasicStatus(phyaddr uint8) (BMSR, error) { + stat, err := d.rread(BMSRAddr) + return BMSR(stat), err +} + +func (d *Device) ID1() (uint16, error) { + return d.rread(regPhyId1) +} + +func (d *Device) ID2() (uint16, error) { + return d.rread(regPhyId2) +} + +func (d *Device) Reset(phyaddr uint8) error { + err := d.rwrite(BMCRAddr, uint16(BMCRReset)) + if err != nil { + return err + } + // Wait for reset to complete (bit self-clears). + // IEEE 802.3 allows up to 500ms. + const maxPolls = 50 + const resetTimeout = 500 * time.Millisecond // As per standard. + for i := 0; i < maxPolls; i++ { + time.Sleep(resetTimeout / maxPolls) + ctl, err := d.BasicControl() + if err != nil { + continue + } + if ctl&BMCRReset == 0 { + return nil + } + } + return errors.New("PHY reset timeout") +} + +// rwrite mdio register write. +func (d *Device) rwrite(addr uint16, value uint16) error { + return d.mdio.Write(d.phyaddr, d.clause45, addr, value) +} + +// rread mdio register read. +func (d *Device) rread(addr uint16) (value uint16, _ error) { + return d.mdio.Read(d.phyaddr, d.clause45, addr) +} diff --git a/phy/reg.go b/phy/reg.go new file mode 100644 index 0000000..3267649 --- /dev/null +++ b/phy/reg.go @@ -0,0 +1,63 @@ +package phy + +// Registers 0..15 as defined by 802.3. +const ( + // First two registers are BMCR and BMSR. See below. + + regPhyId1 = 0x02 + regPhyId2 = 0x03 + + regAutoNegotiationAdvertisement = 0x04 + regAutoNegotiationLinkPartnerAbility = 0x05 + regAutoNegotiationExpansion = 0x05 + regModeControlStatus = 0x11 + regSpecialModes = 0x12 + regSymbolErorCounter = 0x1a + regSpecialControlStatusIndications = 0x1b + regIRQSourceFlag = 0x1d + regIRQMask = 0x1e + regPhySpecialScontrolStatus = 0x1f +) + +// BMCR represents the Basic Mode Control Register at address 0x00. +// Reference: IEEE 802.3 Clause 22.2.4.1 +type BMCR uint16 + +const ( + BMCRAddr = 0x00 + + BMCRSpeed1000 BMCR = 0x0040 // MSB of Speed (1000Mbps) + BMCRCollision BMCR = 0x0080 // Collision test + BMCRFullDuplex BMCR = 0x0100 // Full duplex mode + BMCRANRestart BMCR = 0x0200 // Restart auto-negotiation + BMCRIsolate BMCR = 0x0400 // Isolate PHY from MII + BMCRPowerDown BMCR = 0x0800 // Power down PHY + BMCRANEnable BMCR = 0x1000 // Enable auto-negotiation + BMCRSpeed100 BMCR = 0x2000 // Select 100Mbps + BMCRLoopback BMCR = 0x4000 // Enable TXD loopback + BMCRReset BMCR = 0x8000 // Software reset (self-clearing) +) + +// BMSR represents the Basic Mode Status Register at address 0x01. +// Reference: IEEE 802.3 Clause 22.2.4.2 +type BMSR uint16 + +const ( + BMSRAddr = 0x01 + + BMSRExtCap BMSR = 0x0001 // Extended register capability + BMSRJabber BMSR = 0x0002 // Jabber detected + BMSRLinkStatus BMSR = 0x0004 // Link status (1=up) + BMSRANCap BMSR = 0x0008 // Auto-negotiation capable + BMSRRemoteFault BMSR = 0x0010 // Remote fault detected + BMSRANComplete BMSR = 0x0020 // Auto-negotiation complete + BMSRNoPreamble BMSR = 0x0040 // Preamble suppression capable + BMSRExtStatus BMSR = 0x0100 // Extended status in register 15 + BMSR100Half2 BMSR = 0x0200 // 100BASE-T2 half-duplex capable + BMSR100Full2 BMSR = 0x0400 // 100BASE-T2 full-duplex capable + BMSR10Half BMSR = 0x0800 // 10Mbps half-duplex capable + BMSR10Full BMSR = 0x1000 // 10Mbps full-duplex capable + BMSR100Half BMSR = 0x2000 // 100Mbps half-duplex capable + BMSR100Full BMSR = 0x4000 // 100Mbps full-duplex capable + BMSR100Base4 BMSR = 0x8000 // 100BASE-T4 capable +)