Livt packages

Livt.IO

Version 1.8.1

Memory, UART, I2C, SPI, and Ethernet input/output components for Livt hardware designs.

Published files (126)

src/ethernet/phy/Dp83848Phy.lvt

namespace Livt.IO.Phy

using Livt.IO

/**
 * DP83848 Clause-22 management for MII, advertising 10/100 full duplex only.
 * Owns the borrowed MDIO controller exclusively. Board supplies the 25 MHz
 * reference, hardware reset, and startupReady. Restart is a toggle. Errors
 * revoke readiness and retry after POLL; half duplex is never admitted.
 */
component Dp83848Phy<PHY_ADDRESS: int = 1, POLL: Time = 10ms> : IEthernetLinkStatus
{
	static assert PHY_ADDRESS >= 0 && PHY_ADDRESS <= 31, "Clause-22 address required"
	static assert POLL >= 1us, "Positive polling interval required"

	// Basic control register.
	const BMCR: byte = 0

	// Basic status register; read twice for latched-low link status.
	const BMSR: byte = 1

	// First PHY identification register.
	const PHY_ID1: byte = 2

	// Second PHY identification register.
	const PHY_ID2: byte = 3

	// Autonegotiation advertisement register.
	const ANAR: byte = 4

	// Resolved physical link status register.
	const PHYSTS: byte = 16

	// RMII/bypass configuration register.
	const RBR: byte = 23

	// DP83848 manufacturer identification.
	const EXPECTED_ID1: logic[16] = 0x2000

	// DP83848 model identification without revision bits.
	const EXPECTED_ID2: logic[16] = 0x5C90

	// Ignore PHY revision when comparing model identity.
	const MODEL_MASK: logic[16] = 0xFFF0

	// Basic-control software reset bit.
	const SOFTWARE_RESET: logic[16] = 0x8000

	// Clear RMII mode while preserving unrelated configuration.
	const MII_MODE_MASK: logic[16] = 0xFFDF

	// Advertise IEEE selector plus 10-full and 100-full.
	const FULL_DUPLEX_ADVERTISEMENT: logic[16] = 0x0141

	// Enable and restart autonegotiation.
	const RESTART_AUTONEGOTIATION: logic[16] = 0x1200

	// Basic status requires link up and autonegotiation complete.
	const LINK_NEGOTIATED: logic[16] = 0x0024

	// Check link, duplex, loopback, and negotiation status.
	const PHYSICAL_STATUS_MASK: logic[16] = 0x001D

	// Require link, full duplex, negotiation complete, and no loopback.
	const PHYSICAL_READY: logic[16] = 0x0015

	// Resolved speed bit: set for 10 Mb/s.
	const SPEED_10: logic[16] = 2

	// Bound software-reset register polling.
	const RESET_POLL_LIMIT: uint = 10000

	// Borrowed exclusive management bus and board startup/restart controls.
	mdio: IMdioController
	startupReady: bool
	restart: bool

	// Negotiation state and last observed restart request.
	seenRestart: bool
	linked: bool
	rate: EthernetSpeed

	// Construction-time polling interval in the inherited context.
	POLL_TICKS: uint

	// Published management diagnostics; stage 5 denotes a resolved link.
	public failed: out bool
	public stage: out byte
	public identifier1: out logic[16]
	public identifier2: out logic[16]
	public bmsr: out logic[16]
	public physicalStatus: out logic[16]

	/**
	 * Binds the management bus and board startup/restart controls.
	 */
	new(mdio: IMdioController, startupReady: bool, restart: bool)
	{
		this.mdio = mdio
		this.startupReady = startupReady
		this.restart = restart
		POLL_TICKS = this.context.TicksFor(POLL)
	}

	/**
	 * Exposes only a resolved, full-duplex link after board startup.
	 */
	process Status[]()
	{
		this.ready = linked && startupReady && restart == seenRestart
		this.speed = EthernetSpeed.Unknown

		if (this.ready) {
			this.speed = rate
		}
	}

	/**
	 * Identifies the PHY, selects MII, and monitors negotiated speed and duplex.
	 * Explicit states retain the sampling and MDIO transaction sequence.
	 */
	process Manage()
	{
		var value: logic[16] = 0
		var status: logic[16] = 0
		var physical: logic[16] = 0
		var ticks: uint = 0
		var attempts: uint = 0
		var nextRate = EthernetSpeed.Unknown

		state Initialize {
			this.linked = false
			this.failed = false
			this.seenRestart = restart
			this.stage = 1

			if (!startupReady) {
				goto Initialize
			}
		}

		if (mdio.Read(PHY_ADDRESS as byte, PHY_ID1, value) != EthernetResult.Success) {
			goto Failed
		}

		this.identifier1 = value

		if (value != EXPECTED_ID1) {
			goto Failed
		}

		if (mdio.Read(PHY_ADDRESS as byte, PHY_ID2, value) != EthernetResult.Success) {
			goto Failed
		}

		this.identifier2 = value

		if ((value & MODEL_MASK) != EXPECTED_ID2) {
			goto Failed
		}

		this.stage = 2

		if (mdio.Write(PHY_ADDRESS as byte, BMCR, SOFTWARE_RESET) != EthernetResult.Success) {
			goto Failed
		}

		attempts = 0

		state ResetWait {}

		if (mdio.Read(PHY_ADDRESS as byte, BMCR, value) != EthernetResult.Success) {
			goto Failed
		}

		if ((value & SOFTWARE_RESET) != 0) {
			attempts++

			if (attempts >= RESET_POLL_LIMIT) {
				goto Failed
			}

			goto ResetWait
		}

		if (mdio.Read(PHY_ADDRESS as byte, RBR, value) != EthernetResult.Success) {
			goto Failed
		}

		// Clear RMII_MODE; preserve all other fields.
		if (mdio.Write(PHY_ADDRESS as byte, RBR, value & MII_MODE_MASK) != EthernetResult.Success) {
			goto Failed
		}

		if (mdio.Write(PHY_ADDRESS as byte, ANAR, FULL_DUPLEX_ADVERTISEMENT) != EthernetResult.Success
			|| mdio.Write(PHY_ADDRESS as byte, BMCR, RESTART_AUTONEGOTIATION) != EthernetResult.Success) {
			goto Failed
		}

		this.stage = 4

		state Poll {}

		if (!startupReady || restart != seenRestart) {
			goto Initialize
		}

		if (mdio.Read(PHY_ADDRESS as byte, BMSR, status) != EthernetResult.Success
			|| mdio.Read(PHY_ADDRESS as byte, BMSR, status) != EthernetResult.Success
			|| mdio.Read(PHY_ADDRESS as byte, PHYSTS, physical) != EthernetResult.Success) {
			goto Failed
		}

		this.bmsr = status
		this.physicalStatus = physical
		nextRate = EthernetSpeed.Mbps100

		if ((physical & SPEED_10) != 0) {
			nextRate = EthernetSpeed.Mbps10
		}

		// A speed transition revokes readiness for at least one edge.
		if (rate != nextRate) {
			this.linked = false
		}

		state {}

		this.rate = nextRate
		this.linked = (status & LINK_NEGOTIATED) == LINK_NEGOTIATED && (physical & PHYSICAL_STATUS_MASK) == PHYSICAL_READY
		this.stage = 4

		if (linked) {
			this.stage = 5
		}

		ticks = 0

		state Wait {}

		if (!startupReady || restart != seenRestart) {
			goto Initialize
		}

		ticks++

		if (ticks < POLL_TICKS) {
			goto Wait
		}

		goto Poll

		state Failed {
			this.failed = true
			this.linked = false
			this.stage = 6
			ticks = 0
		}

		state Retry {}

		ticks++

		if (ticks < POLL_TICKS && restart == seenRestart) {
			goto Retry
		}

		goto Initialize
	}
}