From 54126cf58748bd129c25889a07da1f944539319f Mon Sep 17 00:00:00 2001 From: Patricio Whittingslow Date: Sun, 9 Nov 2025 20:03:02 -0300 Subject: [PATCH 1/7] begin adding Reduced Media Independent Interface (RMII) implementation for ethernet Co-authored-by: Claude --- rp2-pio/examples/rmii/main.go | 196 ++++++++++++++++ rp2-pio/piolib/rmii.go | 405 ++++++++++++++++++++++++++++++++++ rp2-pio/piolib/rmiitxrx.go | 296 +++++++++++++++++++++++++ 3 files changed, 897 insertions(+) create mode 100644 rp2-pio/examples/rmii/main.go create mode 100644 rp2-pio/piolib/rmii.go create mode 100644 rp2-pio/piolib/rmiitxrx.go diff --git a/rp2-pio/examples/rmii/main.go b/rp2-pio/examples/rmii/main.go new file mode 100644 index 0000000..53442ff --- /dev/null +++ b/rp2-pio/examples/rmii/main.go @@ -0,0 +1,196 @@ +package main + +import ( + "machine" + "time" + + pio "github.com/tinygo-org/pio/rp2-pio" + "github.com/tinygo-org/pio/rp2-pio/piolib" +) + +// Pin configuration matching reference implementation +// Reference: https://github.com/sandeepmistry/pico-rmii-ethernet/blob/main/examples/httpd/main.c +const ( + // TX pins: GPIO 0, 1, 2 (TXD0, TXD1, TX_EN) + pinTxBase = machine.GPIO0 + + // RX pins: GPIO 3, 4, 5 (RXD0, RXD1, CRS_DV) + pinRxBase = machine.GPIO3 + pinCRSDV = machine.GPIO5 + + // MDIO pins: + pinMDC = machine.GPIO6 + pinMDIO = machine.GPIO7 + + // Reference clock: (50MHz from PHY) + pinRefClk = machine.GPIO6 +) + +// Network configuration +var ( + // MAC address (locally administered) + macAddr = [6]byte{0x02, 0x00, 0x00, 0x12, 0x34, 0x56} + + // Static IP configuration (before DHCP) + ipAddr = [4]byte{192, 168, 1, 100} + netmask = [4]byte{255, 255, 255, 0} + gateway = [4]byte{192, 168, 1, 1} +) + +func main() { + // Sleep to allow serial monitor to connect + time.Sleep(2 * time.Second) + println("RMII Ethernet HTTP Server") + println("========================") + + // Initialize RMII interface + rmii, err := initRMII(pio.PIO0) + if err != nil { + panic("Failed to initialize RMII: " + err.Error()) + } + + // Discover and initialize PHY + println("\nDiscovering PHY...") + if err := rmii.DiscoverPHY(); err != nil { + panic("PHY discovery failed: " + err.Error()) + } + println("PHY found at address:", rmii.PHYAddr()) + + // Initialize PHY with auto-negotiation + println("Initializing PHY...") + if err := rmii.InitPHY(); err != nil { + panic("PHY initialization failed: " + err.Error()) + } + + // Wait for link to come up + println("Waiting for link...") + waitForLink(rmii) + println("Link is UP!") + + // Print network configuration + println("\nNetwork Configuration:") + println(" MAC:", formatMAC(macAddr[:])) + println(" IP:", formatIP(ipAddr[:])) + println(" Netmask:", formatIP(netmask[:])) + println(" Gateway:", formatIP(gateway[:])) + println("\nHTTP server listening on port 80") + + // Enable RX DMA with interrupt handling + err = rmii.EnableDMA(true) + if err != nil { + panic("failed to enabled DMA:" + err.Error()) + } + + // Set up RX interrupt for frame detection + if err := rmii.EnableRxInterrupt(func(pin machine.Pin) { + rmii.OnRxComplete() + }); err != nil { + panic("Failed to enable RX interrupt: " + err.Error()) + } + + // Start RX DMA + go func() { + for { + if err := rmii.StartRxDMA(); err != nil { + println("RX DMA error:", err.Error()) + } + time.Sleep(10 * time.Millisecond) + } + }() + + // Initialize network stack + + // Main network processing loop + println("\nStarting network stack...") + for { + time.Sleep(1 * time.Millisecond) + } +} + +// initRMII initializes the RMII interface with PIO and DMA +// Reference: netif_rmii_ethernet_low_init() from rmii_ethernet.c +func initRMII(Pio *pio.PIO) (*piolib.RMII, error) { + smTx, err := Pio.ClaimStateMachine() + if err != nil { + return nil, err + } + smRx, err := Pio.ClaimStateMachine() + if err != nil { + return nil, err + } + // Configure RMII + cfg := piolib.RMIIConfig{ + TxRx: piolib.RMIITxRxConfig{ + TxPin: pinTxBase, + RxPin: pinRxBase, + CRSDVPin: pinCRSDV, + RefClkPin: pinRefClk, + }, + MDIO: pinMDIO, + MDC: pinMDC, + RxBufferSize: 2048, + TxBufferSize: 2048, + } + rmii, err := piolib.NewRMII(smTx, smRx, cfg) + if err != nil { + return nil, err + } + return rmii, nil +} + +// waitForLink waits for the PHY link to come up +func waitForLink(rmii *piolib.RMII) { + for { + // Read PHY Basic Status Register (register 1) + status, err := rmii.MDIORead(rmii.PHYAddr(), 1) + if err != nil { + println("Error reading PHY status:", err.Error()) + time.Sleep(100 * time.Millisecond) + continue + } + + // Check link status bit (bit 2) + if status&0x04 != 0 { + return + } + + time.Sleep(100 * time.Millisecond) + } +} + +// Utility functions for formatting + +func formatMAC(mac []byte) string { + if len(mac) != 6 { + return "invalid" + } + return formatHex(mac[0]) + ":" + formatHex(mac[1]) + ":" + formatHex(mac[2]) + ":" + + formatHex(mac[3]) + ":" + formatHex(mac[4]) + ":" + formatHex(mac[5]) +} + +func formatIP(ip []byte) string { + if len(ip) != 4 { + return "invalid" + } + return formatDec(ip[0]) + "." + formatDec(ip[1]) + "." + formatDec(ip[2]) + "." + formatDec(ip[3]) +} + +func formatHex(b byte) string { + const hexChars = "0123456789abcdef" + return string([]byte{hexChars[b>>4], hexChars[b&0x0f]}) +} + +func formatDec(b byte) string { + if b == 0 { + return "0" + } + + var buf [3]byte + i := 2 + for b > 0 && i >= 0 { + buf[i] = '0' + (b % 10) + b /= 10 + i-- + } + return string(buf[i+1:]) +} diff --git a/rp2-pio/piolib/rmii.go b/rp2-pio/piolib/rmii.go new file mode 100644 index 0000000..7228d61 --- /dev/null +++ b/rp2-pio/piolib/rmii.go @@ -0,0 +1,405 @@ +//go:build rp2040 || rp2350 + +package piolib + +import ( + "errors" + "machine" + "time" + + pio "github.com/tinygo-org/pio/rp2-pio" +) + +// RMII provides a complete Reduced Media Independent Interface implementation +// with MDIO/MDC management interface for PHY register access. +// Inspired by Sandeep Mistry's implementation at https://github.com/sandeepmistry/pico-rmii-ethernet +type RMII struct { + rxtx RMIITxRx + mdio machine.Pin + mdc machine.Pin + phyAddr uint8 + rxDVPin machine.Pin + rxBuffer []byte + txBuffer []byte +} + +// RMIIConfig configures the complete RMII interface including MDIO/MDC pins. +type RMIIConfig struct { + // TxRx contains the configuration for the PIO-based TX/RX interface + TxRx RMIITxRxConfig + // MDIO is the Management Data Input/Output pin for PHY register access + MDIO machine.Pin + // MDC is the Management Data Clock pin + MDC machine.Pin + // RxBufferSize is the size of the receive buffer (default 2048 if 0) + RxBufferSize int + // TxBufferSize is the size of the transmit buffer (default 2048 if 0) + TxBufferSize int +} + +// NewRMII creates a new complete RMII interface with MDIO/MDC management. +func NewRMII(smTx, smRx pio.StateMachine, cfg RMIIConfig) (*RMII, error) { + // Create the low-level TX/RX interface + rxtx, err := NewRMIITxRx(smTx, smRx, cfg.TxRx) + if err != nil { + return nil, err + } + + // Configure MDIO/MDC pins + cfg.MDIO.Configure(machine.PinConfig{Mode: machine.PinOutput}) + cfg.MDC.Configure(machine.PinConfig{Mode: machine.PinOutput}) + cfg.MDIO.High() + cfg.MDC.Low() + + // Set default buffer sizes + rxBufSize := cfg.RxBufferSize + if rxBufSize == 0 { + rxBufSize = 2048 + } + txBufSize := cfg.TxBufferSize + if txBufSize == 0 { + txBufSize = 2048 + } + + rmii := &RMII{ + rxtx: *rxtx, + mdio: cfg.MDIO, + mdc: cfg.MDC, + rxDVPin: cfg.TxRx.CRSDVPin, + rxBuffer: make([]byte, rxBufSize), + txBuffer: make([]byte, txBufSize), + } + + return rmii, nil +} + +// DiscoverPHY scans MDIO addresses 0-31 to find a connected PHY. +// Returns the PHY address or an error if no PHY is found. +func (r *RMII) DiscoverPHY() error { + for addr := uint8(0); addr < 32; addr++ { + val, err := r.MDIORead(addr, 0) + if err != nil { + continue + } + if val != 0xffff && val != 0x0000 { + r.phyAddr = addr + return nil + } + } + return errors.New("no PHY found on MDIO bus") +} + +// InitPHY initializes the PHY with auto-negotiation settings. +// Must be called after DiscoverPHY(). +func (r *RMII) InitPHY() error { + // Write to register 4 (Advertisement): 0x61 + // This typically enables 10/100 half/full duplex advertisement + if err := r.MDIOWrite(r.phyAddr, 4, 0x61); err != nil { + return err + } + + // Write to register 0 (Control): 0x1000 + // Enable auto-negotiation and restart it + if err := r.MDIOWrite(r.phyAddr, 0, 0x1000); err != nil { + return err + } + + return nil +} + +// PHYAddr returns the discovered PHY address. +func (r *RMII) PHYAddr() uint8 { + return r.phyAddr +} + +// MDIO low-level clock operations +// Reference: netif_rmii_ethernet_mdio_clock_out() and netif_rmii_ethernet_mdio_clock_in() +// from rmii_ethernet.c + +// mdioClockOut outputs a bit on MDIO while pulsing MDC clock. +func (r *RMII) mdioClockOut(bit bool) { + if bit { + r.mdio.High() + } else { + r.mdio.Low() + } + time.Sleep(time.Microsecond) + r.mdc.High() + time.Sleep(time.Microsecond) + r.mdc.Low() +} + +// mdioClockIn reads a bit from MDIO while pulsing MDC clock. +func (r *RMII) mdioClockIn() bool { + time.Sleep(time.Microsecond) + r.mdc.High() + time.Sleep(time.Microsecond) + bit := r.mdio.Get() + r.mdc.Low() + return bit +} + +// MDIORead reads a 16-bit register from the PHY via MDIO. +// Implements IEEE 802.3 MDIO frame format for read operation. +// Reference: netif_rmii_ethernet_mdio_read() from rmii_ethernet.c +func (r *RMII) MDIORead(phyAddr uint8, regAddr uint8) (uint16, error) { + if phyAddr > 31 || regAddr > 31 { + return 0, errors.New("MDIO address out of range") + } + + // Preamble: 32 bits of '1' + for i := 0; i < 32; i++ { + r.mdioClockOut(true) + } + + // Start of frame: 01 + r.mdioClockOut(false) + r.mdioClockOut(true) + + // Opcode: 10 (read) + r.mdioClockOut(true) + r.mdioClockOut(false) + + // PHY address: 5 bits MSB first + for i := 4; i >= 0; i-- { + r.mdioClockOut((phyAddr>>uint(i))&0x01 != 0) + } + + // Register address: 5 bits MSB first + for i := 4; i >= 0; i-- { + r.mdioClockOut((regAddr>>uint(i))&0x01 != 0) + } + + // Turnaround: switch MDIO to input, read 2 bits (should be Z0) + r.mdio.Configure(machine.PinConfig{Mode: machine.PinInput}) + r.mdioClockIn() // Z bit + r.mdioClockIn() // 0 bit + + // Read 16 data bits MSB first + var data uint16 + for i := 15; i >= 0; i-- { + if r.mdioClockIn() { + data |= 1 << uint(i) + } + } + + // Release MDIO bus + r.mdio.Configure(machine.PinConfig{Mode: machine.PinOutput}) + r.mdio.High() + + return data, nil +} + +// MDIOWrite writes a 16-bit value to a PHY register via MDIO. +// Implements IEEE 802.3 MDIO frame format for write operation. +// Reference: netif_rmii_ethernet_mdio_write() from rmii_ethernet.c +func (r *RMII) MDIOWrite(phyAddr uint8, regAddr uint8, value uint16) error { + if phyAddr > 31 || regAddr > 31 { + return errors.New("MDIO address out of range") + } + + // Ensure MDIO is output + r.mdio.Configure(machine.PinConfig{Mode: machine.PinOutput}) + + // Preamble: 32 bits of '1' + for i := 0; i < 32; i++ { + r.mdioClockOut(true) + } + + // Start of frame: 01 + r.mdioClockOut(false) + r.mdioClockOut(true) + + // Opcode: 01 (write) + r.mdioClockOut(false) + r.mdioClockOut(true) + + // PHY address: 5 bits MSB first + for i := 4; i >= 0; i-- { + r.mdioClockOut((phyAddr>>uint(i))&0x01 != 0) + } + + // Register address: 5 bits MSB first + for i := 4; i >= 0; i-- { + r.mdioClockOut((regAddr>>uint(i))&0x01 != 0) + } + + // Turnaround: 10 + r.mdioClockOut(true) + r.mdioClockOut(false) + + // Write 16 data bits MSB first + for i := 15; i >= 0; i-- { + r.mdioClockOut((value>>uint(i))&0x01 != 0) + } + + // Release MDIO to high impedance + r.mdio.High() + + return nil +} + +// CRC32 computes the Ethernet CRC32 for the given data. +// Uses the polynomial 0xedb88320 (reversed representation). +// Reference: netif_rmii_ethernet_crc() from rmii_ethernet.c +func (r *RMII) CRC32(data []byte) uint32 { + crc := uint32(0xffffffff) + for _, b := range data { + crc ^= uint32(b) + for bit := 0; bit < 8; bit++ { + if crc&1 != 0 { + crc = (crc >> 1) ^ 0xedb88320 + } else { + crc = crc >> 1 + } + } + } + return ^crc +} + +// Pass-through methods to underlying rxtx + +// SetEnabled enables or disables both TX and RX state machines. +func (r *RMII) SetEnabled(enabled bool) { + r.rxtx.SetEnabled(enabled) +} + +func (r *RMII) EnableDMA(enabled bool) error { + err := r.rxtx.EnableRxDMA(enabled) + if err != nil { + return err + } + err = r.rxtx.EnableTxDMA(enabled) + return err +} + +// SetTimeout sets the read/write timeout for both TX and RX operations. +func (r *RMII) SetTimeout(timeout time.Duration) { + r.rxtx.SetTimeout(timeout) +} + +// RxTx returns a reference to the underlying RMIItxrx for low-level access. +func (r *RMII) RxTx() *RMIITxRx { + return &r.rxtx +} + +// Frame transmission and reception +// Reference: netif_rmii_ethernet_output() from rmii_ethernet.c + +// TxFrame transmits an Ethernet frame with preamble, SFD, data, and CRC. +// The data should be the complete Ethernet frame (destination MAC, source MAC, type, payload). +// Minimum frame size is 60 bytes (excluding preamble/SFD/CRC). +func (r *RMII) TxFrame(frame []byte) error { + if len(frame) < 60 { + return errors.New("frame too small (minimum 60 bytes)") + } + if len(frame) > 1518 { + return errors.New("frame too large (maximum 1518 bytes)") + } + + // Compute CRC32 + crc := r.CRC32(frame) + + // Encode frame: preamble + SFD + data + CRC + IPG + // Each byte is encoded as 4 nibbles with TX_EN asserted + const preambleNibbles = 31 + const sfdNibbles = 1 + dataAndCrcLen := len(frame) + 4 // frame + 4 byte CRC + const ipgNibbles = 12 + + totalNibbles := preambleNibbles + sfdNibbles + (dataAndCrcLen * 4) + ipgNibbles + + // Ensure we don't overflow the tx buffer + if totalNibbles > len(r.txBuffer) { + return errors.New("frame too large for TX buffer") + } + + idx := 0 + + // Preamble: 31 × 0x05 (alternating 01 pattern with TX_EN) + for i := 0; i < preambleNibbles; i++ { + r.txBuffer[idx] = 0x05 + idx++ + } + + // SFD: 1 × 0x07 (10101011 start frame delimiter) + r.txBuffer[idx] = 0x07 + idx++ + + // Encode frame data: each byte as 4 nibbles with 0x04 prefix (TX_EN bit) + for _, b := range frame { + r.txBuffer[idx] = 0x04 | ((b >> 0) & 0x03) // bits [1:0] + idx++ + r.txBuffer[idx] = 0x04 | ((b >> 2) & 0x03) // bits [3:2] + idx++ + r.txBuffer[idx] = 0x04 | ((b >> 4) & 0x03) // bits [5:4] + idx++ + r.txBuffer[idx] = 0x04 | ((b >> 6) & 0x03) // bits [7:6] + idx++ + } + + // Encode CRC: 4 bytes as nibbles + for i := 0; i < 4; i++ { + crcByte := byte(crc >> uint(i*8)) + r.txBuffer[idx] = 0x04 | ((crcByte >> 0) & 0x03) + idx++ + r.txBuffer[idx] = 0x04 | ((crcByte >> 2) & 0x03) + idx++ + r.txBuffer[idx] = 0x04 | ((crcByte >> 4) & 0x03) + idx++ + r.txBuffer[idx] = 0x04 | ((crcByte >> 6) & 0x03) + idx++ + } + + // Inter-packet gap: 12 × 0x00 (idle, TX_EN low) + for i := 0; i < ipgNibbles; i++ { + r.txBuffer[idx] = 0x00 + idx++ + } + + // Transmit via the underlying rxtx + return r.rxtx.Tx8(r.txBuffer[:idx]) +} + +// EnableRxInterrupt enables GPIO interrupt on RX_DV falling edge for frame detection. +// The callback will be invoked when a frame reception completes (RX_DV goes low). +// Reference: netif_rmii_ethernet_rx_dv_falling_callback() from rmii_ethernet.c +func (r *RMII) EnableRxInterrupt(callback func(machine.Pin)) error { + if callback == nil { + return errors.New("callback cannot be nil") + } + return r.rxDVPin.SetInterrupt(machine.PinFalling, callback) +} + +// DisableRxInterrupt disables the RX_DV falling edge interrupt. +func (r *RMII) DisableRxInterrupt() error { + return r.rxDVPin.SetInterrupt(machine.PinFalling, nil) +} + +// OnRxComplete is called when RX_DV falling edge is detected. +// This stops the RX state machine and aborts DMA, signaling frame completion. +// Users should call this from their interrupt handler. +// Reference: netif_rmii_ethernet_rx_dv_falling_callback() from rmii_ethernet.c +func (r *RMII) OnRxComplete() { + r.rxtx.smRx.SetEnabled(false) + // Note: DMA abort is internal to dmaChannel, happens automatically when disabled +} + +// StartRxDMA starts continuous DMA reception into the internal buffer. +// This should be combined with interrupt handling on RX_DV for frame detection. +// Call EnableRxInterrupt() with a callback that invokes OnRxComplete(). +func (r *RMII) StartRxDMA() error { + r.rxtx.smRx.SetEnabled(true) + return r.rxtx.Rx8(r.rxBuffer) +} + +// RxBuffer returns a reference to the internal RX buffer for direct access. +// Useful for interrupt-driven reception where you need to inspect the buffer. +func (r *RMII) RxBuffer() []byte { + return r.rxBuffer +} + +// TxBuffer returns a reference to the internal TX buffer for direct access. +func (r *RMII) TxBuffer() []byte { + return r.txBuffer +} diff --git a/rp2-pio/piolib/rmiitxrx.go b/rp2-pio/piolib/rmiitxrx.go new file mode 100644 index 0000000..f8ae5ca --- /dev/null +++ b/rp2-pio/piolib/rmiitxrx.go @@ -0,0 +1,296 @@ +//go:build rp2040 || rp2350 + +package piolib + +import ( + "errors" + "machine" + "time" + "unsafe" + + pio "github.com/tinygo-org/pio/rp2-pio" +) + +// RMIITxRx is the Reduced Media Independent Interface for 100Mbps Ethernet PHY communication. +// It uses two state machines: one for TX and one for RX. +// Inspired by Sandeep Mistry's implementation at https://github.com/sandeepmistry/pico-rmii-ethernet/tree/main/src +type RMIITxRx struct { + smTx pio.StateMachine + smRx pio.StateMachine + programOffTx uint8 + programOffRx uint8 + dmaTx dmaChannel + dmaRx dmaChannel +} + +// RMIITxRxConfig configures the RMII interface pins and parameters. +type RMIITxRxConfig struct { + Baud uint32 + // TxPin is the base pin for RMII TX (TXD0, TXD1, TX_EN). + // Requires 3 consecutive pins. + TxPin machine.Pin + // RxPin is the base pin for RMII RX (RXD0, RXD1). + // Requires 2 consecutive pins. + RxPin machine.Pin + // CRSDVPin is the Carrier Sense/Data Valid pin (also called RX_DV). + CRSDVPin machine.Pin + // RefClkPin is the 50MHz reference clock input from PHY. + RefClkPin machine.Pin +} + +// NewRMIITxRx creates a new RMII interface using two state machines (TX and RX). +// The TX and RX state machines should be from the same PIO block. +func NewRMIITxRx(smTx, smRx pio.StateMachine, cfg RMIITxRxConfig) (*RMIITxRx, error) { + if smTx.PIO().BlockIndex() != smRx.PIO().BlockIndex() { + return nil, errors.New("TX and RX state machines must be from the same PIO block") + } + + // Claim state machines + smTx.TryClaim() + smRx.TryClaim() + + Pio := smTx.PIO() + var asm pio.AssemblerV0 + + // RX Program: Wait for sync pattern, then continuously read 2 bits + // .program rmii_ethernet_phy_rx_data + // wait 0 pin 2 ; Wait for CRSDV low + // wait 0 pin 0 ; Wait for RXD0 low + // wait 0 pin 1 ; Wait for RXD1 low + // wait 1 pin 2 ; Wait for CRSDV high + // wait 1 pin 0 ; Wait for RXD0 high + // wait 1 pin 1 ; Wait for RXD1 high + // .wrap_target + // in pins, 2 ; Continuously read 2 bits + // .wrap + // if program changes wrap target must be changed manually. + // Worry not, will fail to compile if wrap target is invalid. + const rxWrapTarget = 6 + rxProgram := [7]uint16{ + asm.WaitPin(false, 2).Encode(), // wait 0 pin 2 (CRSDV) + asm.WaitPin(false, 0).Encode(), // wait 0 pin 0 (RXD0) + asm.WaitPin(false, 1).Encode(), // wait 0 pin 1 (RXD1) + asm.WaitPin(true, 2).Encode(), // wait 1 pin 2 (CRSDV) + asm.WaitPin(true, 0).Encode(), // wait 1 pin 0 (RXD0) + asm.WaitPin(true, 1).Encode(), // wait 1 pin 1 (RXD1) + rxWrapTarget:// .wrap_target Once all pins have waited we receive all data in loop. + asm.In(pio.InSrcPins, 2).Encode(), // in pins, 2 + } + + // Add RX program first (longer, more likely to fail if PIO memory full) + rxOffset, err := Pio.AddProgram(rxProgram[:], -1) + if err != nil { + return nil, err + } + + // TX Program: Simple output of 3 pins (TXD0, TXD1, TX_EN) + // .program rmii_ethernet_phy_tx_data + // .wrap_target + // out pins, 3 + // .wrap + txProgram := [1]uint16{ + asm.Out(pio.OutDestPins, 3).Encode(), // out pins, 3 + } + + txOffset, err := Pio.AddProgram(txProgram[:], -1) + if err != nil { + Pio.ClearProgramSection(rxOffset, uint8(len(rxProgram))) + return nil, err + } + + // Configure RX state machine + rxcfg := pio.DefaultStateMachineConfig() + rxcfg.SetWrap(rxOffset+rxWrapTarget, rxOffset+uint8(len(rxProgram))-1) + rxcfg.SetInPins(cfg.RxPin, 2) // RXD0, RXD1 + // Shift left, autopush enabled, push threshold 32 bits + rxcfg.SetInShift(false, true, 32) + rxcfg.SetClkDivIntFrac(10, 0) + rxcfg.SetFIFOJoin(pio.FifoJoinRx) + + // Configure TX state machine + txcfg := pio.DefaultStateMachineConfig() + txcfg.SetWrap(txOffset, txOffset+uint8(len(txProgram))-1) + txcfg.SetOutPins(cfg.TxPin, 3) + // Shift right, autopull enabled, pull threshold 32 bits + txcfg.SetOutShift(true, true, 32) + // Clock divider: run at 50MHz / 4 = 12.5MHz for RMII (2 bits per clock at 50MHz = 4 clocks per 2-bit dibit) + // Reference implementation uses divider of 10 to get effective rate + txcfg.SetClkDivIntFrac(10, 0) + txcfg.SetFIFOJoin(pio.FifoJoinTx) + + // Configure pins + pinCfg := machine.PinConfig{Mode: Pio.PinMode()} + + // Configure TX pins (TXD0, TXD1, TX_EN) + for i := 0; i < 3; i++ { + pin := cfg.TxPin + machine.Pin(i) + pin.Configure(pinCfg) + } + + // Configure RX pins (RXD0, RXD1, CRSDV) + cfg.RxPin.Configure(pinCfg) + (cfg.RxPin + 1).Configure(pinCfg) + cfg.CRSDVPin.Configure(pinCfg) + + // RefClk is input from PHY, configure as input + cfg.RefClkPin.Configure(machine.PinConfig{Mode: machine.PinInput}) + + // Set TX pins as output, initially low + txPinMask := uint32(0b111 << cfg.TxPin) + smTx.SetPindirsMasked(txPinMask, txPinMask) + smTx.SetPinsMasked(0, txPinMask) + + // Set RX pins as input + rxPinMask := uint32(0b11< Date: Mon, 10 Nov 2025 00:15:34 -0300 Subject: [PATCH 2/7] keep vibing Co-authored-by: Claude --- rp2-pio/examples/rmii/main.go | 99 ++++++++++++++++++++++++----------- rp2-pio/piolib/rmii.go | 24 ++++----- 2 files changed, 79 insertions(+), 44 deletions(-) diff --git a/rp2-pio/examples/rmii/main.go b/rp2-pio/examples/rmii/main.go index 53442ff..5c5d718 100644 --- a/rp2-pio/examples/rmii/main.go +++ b/rp2-pio/examples/rmii/main.go @@ -2,7 +2,9 @@ package main import ( "machine" + "strconv" "time" + "unsafe" pio "github.com/tinygo-org/pio/rp2-pio" "github.com/tinygo-org/pio/rp2-pio/piolib" @@ -69,10 +71,10 @@ func main() { // Print network configuration println("\nNetwork Configuration:") - println(" MAC:", formatMAC(macAddr[:])) - println(" IP:", formatIP(ipAddr[:])) - println(" Netmask:", formatIP(netmask[:])) - println(" Gateway:", formatIP(gateway[:])) + println(" MAC:", string(appendHexSep(nil, macAddr[:], ':'))) + println(" IP:", string(appendDecSep(nil, ipAddr[:], '.'))) + println(" Netmask:", string(appendDecSep(nil, netmask[:], '.'))) + println(" Gateway:", string(appendDecSep(nil, gateway[:], '.'))) println("\nHTTP server listening on port 80") // Enable RX DMA with interrupt handling @@ -138,8 +140,34 @@ func initRMII(Pio *pio.PIO) (*piolib.RMII, error) { return rmii, nil } +// Utility functions for formatting + +func formatHex16(val uint16) string { + fwd := []byte{'0', 'x'} + fwd = appendHex(fwd, byte(val>>8)) + fwd = appendHex(fwd, byte(val)) + return unsafe.String(&fwd[0], len(fwd)) +} + // waitForLink waits for the PHY link to come up func waitForLink(rmii *piolib.RMII) { + println("Reading PHY registers for diagnostics...") + + // Read PHY ID registers + id1, err1 := rmii.MDIORead(rmii.PHYAddr(), 2) + id2, err2 := rmii.MDIORead(rmii.PHYAddr(), 3) + if err1 == nil && err2 == nil { + println(" PHY ID1:", formatHex16(id1)) + println(" PHY ID2:", formatHex16(id2)) + } + + // Read control register + ctrl, errCtrl := rmii.MDIORead(rmii.PHYAddr(), 0) + if errCtrl == nil { + println(" Control Reg (0):", formatHex16(ctrl)) + } + + attempt := 0 for { // Read PHY Basic Status Register (register 1) status, err := rmii.MDIORead(rmii.PHYAddr(), 1) @@ -149,8 +177,25 @@ func waitForLink(rmii *piolib.RMII) { continue } + attempt++ + if attempt%10 == 0 { + println(" Status Reg (1):", formatHex16(status), "- Link bit:", (status>>2)&1) + + // Read more diagnostic info + if attempt%30 == 0 { + // Read auto-neg advertisement (reg 4) + anar, _ := rmii.MDIORead(rmii.PHYAddr(), 4) + println(" Auto-Neg Adv (4):", formatHex16(anar)) + + // Read auto-neg link partner (reg 5) + anlpar, _ := rmii.MDIORead(rmii.PHYAddr(), 5) + println(" Link Partner (5):", formatHex16(anlpar)) + } + } + // Check link status bit (bit 2) if status&0x04 != 0 { + println(" Link established! Final status:", formatHex16(status)) return } @@ -158,39 +203,31 @@ func waitForLink(rmii *piolib.RMII) { } } -// Utility functions for formatting - -func formatMAC(mac []byte) string { - if len(mac) != 6 { - return "invalid" - } - return formatHex(mac[0]) + ":" + formatHex(mac[1]) + ":" + formatHex(mac[2]) + ":" + - formatHex(mac[3]) + ":" + formatHex(mac[4]) + ":" + formatHex(mac[5]) -} - -func formatIP(ip []byte) string { - if len(ip) != 4 { - return "invalid" +func appendHexSep(dst, mac []byte, sep byte) []byte { + for i := range mac { + dst = appendHex(dst, mac[i]) + if sep != 0 && i != len(mac)-1 { + dst = append(dst, sep) + } } - return formatDec(ip[0]) + "." + formatDec(ip[1]) + "." + formatDec(ip[2]) + "." + formatDec(ip[3]) + return dst } -func formatHex(b byte) string { +func appendHex(dst []byte, b byte) []byte { const hexChars = "0123456789abcdef" - return string([]byte{hexChars[b>>4], hexChars[b&0x0f]}) + return append(dst, hexChars[b>>4], hexChars[b&0xf]) } -func formatDec(b byte) string { - if b == 0 { - return "0" - } +func appendDec(dst []byte, b byte) []byte { + return strconv.AppendInt(dst, int64(b), 10) +} - var buf [3]byte - i := 2 - for b > 0 && i >= 0 { - buf[i] = '0' + (b % 10) - b /= 10 - i-- +func appendDecSep(dst []byte, data []byte, sep byte) []byte { + for i := range data { + dst = appendDec(dst, data[i]) + if sep != 0 && i != len(data)-1 { + dst = append(dst, sep) + } } - return string(buf[i+1:]) + return dst } diff --git a/rp2-pio/piolib/rmii.go b/rp2-pio/piolib/rmii.go index 7228d61..999a543 100644 --- a/rp2-pio/piolib/rmii.go +++ b/rp2-pio/piolib/rmii.go @@ -91,15 +91,16 @@ func (r *RMII) DiscoverPHY() error { // InitPHY initializes the PHY with auto-negotiation settings. // Must be called after DiscoverPHY(). +// Reference: netif_rmii_ethernet_low_init() from rmii_ethernet.c func (r *RMII) InitPHY() error { // Write to register 4 (Advertisement): 0x61 - // This typically enables 10/100 half/full duplex advertisement + // Configure advertised capabilities (10/100 Mbps support) if err := r.MDIOWrite(r.phyAddr, 4, 0x61); err != nil { return err } // Write to register 0 (Control): 0x1000 - // Enable auto-negotiation and restart it + // Enable auto-negotiation (bit 12) if err := r.MDIOWrite(r.phyAddr, 0, 0x1000); err != nil { return err } @@ -107,7 +108,7 @@ func (r *RMII) InitPHY() error { return nil } -// PHYAddr returns the discovered PHY address. +// PHYAddr returns the discovered PHY address. Set after [RMII.DiscoverPHY] success. func (r *RMII) PHYAddr() uint8 { return r.phyAddr } @@ -118,24 +119,20 @@ func (r *RMII) PHYAddr() uint8 { // mdioClockOut outputs a bit on MDIO while pulsing MDC clock. func (r *RMII) mdioClockOut(bit bool) { - if bit { - r.mdio.High() - } else { - r.mdio.Low() - } + r.mdc.Low() time.Sleep(time.Microsecond) + r.mdio.Set(bit) r.mdc.High() time.Sleep(time.Microsecond) - r.mdc.Low() } // mdioClockIn reads a bit from MDIO while pulsing MDC clock. func (r *RMII) mdioClockIn() bool { + r.mdc.Low() time.Sleep(time.Microsecond) r.mdc.High() - time.Sleep(time.Microsecond) bit := r.mdio.Get() - r.mdc.Low() + time.Sleep(time.Microsecond) return bit } @@ -243,12 +240,13 @@ func (r *RMII) MDIOWrite(phyAddr uint8, regAddr uint8, value uint16) error { // Uses the polynomial 0xedb88320 (reversed representation). // Reference: netif_rmii_ethernet_crc() from rmii_ethernet.c func (r *RMII) CRC32(data []byte) uint32 { + const polynomial = 0xedb88320 crc := uint32(0xffffffff) for _, b := range data { crc ^= uint32(b) for bit := 0; bit < 8; bit++ { if crc&1 != 0 { - crc = (crc >> 1) ^ 0xedb88320 + crc = (crc >> 1) ^ polynomial } else { crc = crc >> 1 } @@ -305,7 +303,7 @@ func (r *RMII) TxFrame(frame []byte) error { const preambleNibbles = 31 const sfdNibbles = 1 dataAndCrcLen := len(frame) + 4 // frame + 4 byte CRC - const ipgNibbles = 12 + const ipgNibbles = 12 * 4 // 12 bytes * 4 nibbles per byte = 48 totalNibbles := preambleNibbles + sfdNibbles + (dataAndCrcLen * 4) + ipgNibbles From 46b45d171b60fd2860ec7df3a8b875f8c8413365 Mon Sep 17 00:00:00 2001 From: Patricio Whittingslow Date: Mon, 10 Nov 2025 01:19:45 -0300 Subject: [PATCH 3/7] keep on vibin 2 --- rp2-pio/piolib/rmii.go | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/rp2-pio/piolib/rmii.go b/rp2-pio/piolib/rmii.go index 999a543..ec59ba7 100644 --- a/rp2-pio/piolib/rmii.go +++ b/rp2-pio/piolib/rmii.go @@ -122,6 +122,7 @@ func (r *RMII) mdioClockOut(bit bool) { r.mdc.Low() time.Sleep(time.Microsecond) r.mdio.Set(bit) + time.Sleep(time.Microsecond) // Allow data line to settle r.mdc.High() time.Sleep(time.Microsecond) } @@ -136,6 +137,13 @@ func (r *RMII) mdioClockIn() bool { return bit } +func (r *RMII) mdCfg() { + r.mdc.High() + r.mdio.High() + r.mdio.Configure(machine.PinConfig{Mode: machine.PinOutput}) + r.mdc.Configure(machine.PinConfig{Mode: machine.PinOutput}) +} + // MDIORead reads a 16-bit register from the PHY via MDIO. // Implements IEEE 802.3 MDIO frame format for read operation. // Reference: netif_rmii_ethernet_mdio_read() from rmii_ethernet.c @@ -143,6 +151,7 @@ func (r *RMII) MDIORead(phyAddr uint8, regAddr uint8) (uint16, error) { if phyAddr > 31 || regAddr > 31 { return 0, errors.New("MDIO address out of range") } + r.mdCfg() // Preamble: 32 bits of '1' for i := 0; i < 32; i++ { @@ -169,21 +178,16 @@ func (r *RMII) MDIORead(phyAddr uint8, regAddr uint8) (uint16, error) { // Turnaround: switch MDIO to input, read 2 bits (should be Z0) r.mdio.Configure(machine.PinConfig{Mode: machine.PinInput}) - r.mdioClockIn() // Z bit - r.mdioClockIn() // 0 bit + r.mdioClockOut(false) // Z bit + r.mdioClockOut(false) // 0 bit // Read 16 data bits MSB first var data uint16 for i := 15; i >= 0; i-- { - if r.mdioClockIn() { - data |= 1 << uint(i) - } + data <<= 1 + data |= uint16(b2u8(r.mdioClockIn())) } - // Release MDIO bus - r.mdio.Configure(machine.PinConfig{Mode: machine.PinOutput}) - r.mdio.High() - return data, nil } @@ -194,9 +198,7 @@ func (r *RMII) MDIOWrite(phyAddr uint8, regAddr uint8, value uint16) error { if phyAddr > 31 || regAddr > 31 { return errors.New("MDIO address out of range") } - - // Ensure MDIO is output - r.mdio.Configure(machine.PinConfig{Mode: machine.PinOutput}) + r.mdCfg() // Preamble: 32 bits of '1' for i := 0; i < 32; i++ { @@ -230,8 +232,8 @@ func (r *RMII) MDIOWrite(phyAddr uint8, regAddr uint8, value uint16) error { r.mdioClockOut((value>>uint(i))&0x01 != 0) } - // Release MDIO to high impedance - r.mdio.High() + // Release MDIO bus to high impedance + r.mdio.Configure(machine.PinConfig{Mode: machine.PinInput}) return nil } @@ -401,3 +403,10 @@ func (r *RMII) RxBuffer() []byte { func (r *RMII) TxBuffer() []byte { return r.txBuffer } + +func b2u8(b bool) uint8 { + if b { + return 1 + } + return 0 +} From 428ffd71034f97e1ba221a60d05b6ae7696878b0 Mon Sep 17 00:00:00 2001 From: Patricio Whittingslow Date: Mon, 10 Nov 2025 01:47:47 -0300 Subject: [PATCH 4/7] result still shifted by one --- rp2-pio/piolib/rmii.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/rp2-pio/piolib/rmii.go b/rp2-pio/piolib/rmii.go index ec59ba7..8693a54 100644 --- a/rp2-pio/piolib/rmii.go +++ b/rp2-pio/piolib/rmii.go @@ -45,12 +45,6 @@ func NewRMII(smTx, smRx pio.StateMachine, cfg RMIIConfig) (*RMII, error) { return nil, err } - // Configure MDIO/MDC pins - cfg.MDIO.Configure(machine.PinConfig{Mode: machine.PinOutput}) - cfg.MDC.Configure(machine.PinConfig{Mode: machine.PinOutput}) - cfg.MDIO.High() - cfg.MDC.Low() - // Set default buffer sizes rxBufSize := cfg.RxBufferSize if rxBufSize == 0 { @@ -70,6 +64,8 @@ func NewRMII(smTx, smRx pio.StateMachine, cfg RMIIConfig) (*RMII, error) { txBuffer: make([]byte, txBufSize), } + // Configure MDIO/MDC pins + // rmii.mdCfg() return rmii, nil } @@ -138,8 +134,8 @@ func (r *RMII) mdioClockIn() bool { } func (r *RMII) mdCfg() { - r.mdc.High() - r.mdio.High() + // r.mdc.High() + // r.mdio.High() r.mdio.Configure(machine.PinConfig{Mode: machine.PinOutput}) r.mdc.Configure(machine.PinConfig{Mode: machine.PinOutput}) } From 8027ec8914c9c9e2956295cecb426a9292e0cfba Mon Sep 17 00:00:00 2001 From: Patricio Whittingslow Date: Tue, 11 Nov 2025 21:18:30 -0300 Subject: [PATCH 5/7] try z mdio --- rp2-pio/examples/rmii/main.go | 15 ++++++++------- rp2-pio/piolib/rmii.go | 32 +++++++++++++++++++++++++++++--- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/rp2-pio/examples/rmii/main.go b/rp2-pio/examples/rmii/main.go index 5c5d718..7a8c191 100644 --- a/rp2-pio/examples/rmii/main.go +++ b/rp2-pio/examples/rmii/main.go @@ -128,6 +128,7 @@ func initRMII(Pio *pio.PIO) (*piolib.RMII, error) { CRSDVPin: pinCRSDV, RefClkPin: pinRefClk, }, + NoZMDIO: false, MDIO: pinMDIO, MDC: pinMDC, RxBufferSize: 2048, @@ -142,13 +143,6 @@ func initRMII(Pio *pio.PIO) (*piolib.RMII, error) { // Utility functions for formatting -func formatHex16(val uint16) string { - fwd := []byte{'0', 'x'} - fwd = appendHex(fwd, byte(val>>8)) - fwd = appendHex(fwd, byte(val)) - return unsafe.String(&fwd[0], len(fwd)) -} - // waitForLink waits for the PHY link to come up func waitForLink(rmii *piolib.RMII) { println("Reading PHY registers for diagnostics...") @@ -203,6 +197,13 @@ func waitForLink(rmii *piolib.RMII) { } } +func formatHex16(val uint16) string { + fwd := []byte{'0', 'x'} + fwd = appendHex(fwd, byte(val>>8)) + fwd = appendHex(fwd, byte(val)) + return unsafe.String(&fwd[0], len(fwd)) +} + func appendHexSep(dst, mac []byte, sep byte) []byte { for i := range mac { dst = appendHex(dst, mac[i]) diff --git a/rp2-pio/piolib/rmii.go b/rp2-pio/piolib/rmii.go index 8693a54..f7b830e 100644 --- a/rp2-pio/piolib/rmii.go +++ b/rp2-pio/piolib/rmii.go @@ -15,6 +15,7 @@ import ( // Inspired by Sandeep Mistry's implementation at https://github.com/sandeepmistry/pico-rmii-ethernet type RMII struct { rxtx RMIITxRx + zmdio bool mdio machine.Pin mdc machine.Pin phyAddr uint8 @@ -35,6 +36,8 @@ type RMIIConfig struct { RxBufferSize int // TxBufferSize is the size of the transmit buffer (default 2048 if 0) TxBufferSize int + // NoZMDIO avoids using high impedance Z level for HIGH pin state on MDIO as stated by RMII specification. + NoZMDIO bool } // NewRMII creates a new complete RMII interface with MDIO/MDC management. @@ -62,6 +65,7 @@ func NewRMII(smTx, smRx pio.StateMachine, cfg RMIIConfig) (*RMII, error) { rxDVPin: cfg.TxRx.CRSDVPin, rxBuffer: make([]byte, rxBufSize), txBuffer: make([]byte, txBufSize), + zmdio: cfg.NoZMDIO, } // Configure MDIO/MDC pins @@ -117,12 +121,35 @@ func (r *RMII) PHYAddr() uint8 { func (r *RMII) mdioClockOut(bit bool) { r.mdc.Low() time.Sleep(time.Microsecond) - r.mdio.Set(bit) - time.Sleep(time.Microsecond) // Allow data line to settle + r.mdioSet(bit) + time.Sleep(time.Microsecond) r.mdc.High() time.Sleep(time.Microsecond) } +func (r *RMII) mdioSet(b bool) { + if r.zmdio { + if b { + r.mdioZHigh() + } else { + r.mdioLow() + } + } else { + r.mdio.Set(b) + } +} + +func (r *RMII) mdioZHigh() { + // RMII z pin level means high impedance, pull up resistor. + r.mdio.Configure(machine.PinConfig{Mode: machine.PinInputPullup}) +} + +func (r *RMII) mdioLow() { + // RMII 0 pin level sets as output + r.mdio.Low() + r.mdio.Configure(machine.PinConfig{Mode: machine.PinOutput}) +} + // mdioClockIn reads a bit from MDIO while pulsing MDC clock. func (r *RMII) mdioClockIn() bool { r.mdc.Low() @@ -135,7 +162,6 @@ func (r *RMII) mdioClockIn() bool { func (r *RMII) mdCfg() { // r.mdc.High() - // r.mdio.High() r.mdio.Configure(machine.PinConfig{Mode: machine.PinOutput}) r.mdc.Configure(machine.PinConfig{Mode: machine.PinOutput}) } From 35205264acbccc94186a8298788159d692fda555 Mon Sep 17 00:00:00 2001 From: Patricio Whittingslow Date: Wed, 12 Nov 2025 00:12:38 -0300 Subject: [PATCH 6/7] add lan8720 driver --- rp2-pio/examples/rmii/main.go | 242 ++++++++++++++++++++-------------- rp2-pio/piolib/rmii.go | 30 ++--- 2 files changed, 157 insertions(+), 115 deletions(-) diff --git a/rp2-pio/examples/rmii/main.go b/rp2-pio/examples/rmii/main.go index 7a8c191..4b6f686 100644 --- a/rp2-pio/examples/rmii/main.go +++ b/rp2-pio/examples/rmii/main.go @@ -1,6 +1,7 @@ package main import ( + "errors" "machine" "strconv" "time" @@ -40,73 +41,45 @@ var ( ) func main() { + cfg := piolib.RMIIConfig{ + TxRx: piolib.RMIITxRxConfig{ + TxPin: pinTxBase, + RxPin: pinRxBase, + CRSDVPin: pinCRSDV, + RefClkPin: pinRefClk, + }, + NoZMDIO: false, + MDIO: pinMDIO, + MDC: pinMDC, + RxBufferSize: 2048, + TxBufferSize: 2048, + } // Sleep to allow serial monitor to connect time.Sleep(2 * time.Second) - println("RMII Ethernet HTTP Server") - println("========================") - - // Initialize RMII interface - rmii, err := initRMII(pio.PIO0) + println("=== LAN 8720 RMII ===") + device, err := NewLAN8270(pio.PIO0, cfg) if err != nil { - panic("Failed to initialize RMII: " + err.Error()) - } - - // Discover and initialize PHY - println("\nDiscovering PHY...") - if err := rmii.DiscoverPHY(); err != nil { - panic("PHY discovery failed: " + err.Error()) + panic(err) } - println("PHY found at address:", rmii.PHYAddr()) - - // Initialize PHY with auto-negotiation - println("Initializing PHY...") - if err := rmii.InitPHY(); err != nil { - panic("PHY initialization failed: " + err.Error()) + // Init Loop: + for { + err = device.Init() + if err == nil { + break + } + println("init failed:", err.Error()) + println("retrying soon...") + time.Sleep(6 * time.Second) } - - // Wait for link to come up - println("Waiting for link...") - waitForLink(rmii) - println("Link is UP!") - - // Print network configuration - println("\nNetwork Configuration:") - println(" MAC:", string(appendHexSep(nil, macAddr[:], ':'))) - println(" IP:", string(appendDecSep(nil, ipAddr[:], '.'))) - println(" Netmask:", string(appendDecSep(nil, netmask[:], '.'))) - println(" Gateway:", string(appendDecSep(nil, gateway[:], '.'))) - println("\nHTTP server listening on port 80") - - // Enable RX DMA with interrupt handling - err = rmii.EnableDMA(true) + status, err := device.Status() if err != nil { - panic("failed to enabled DMA:" + err.Error()) + panic("status: " + err.Error()) } + ctl, _ := device.BasicControl() + println("status", formatHex16(uint16(status)), "islinked", status.IsLinked()) + println("regctl", formatHex16(uint16(ctl)), "isenabled", ctl.IsEnabled()) + println("PHY ID1:", device.id1, "ID2:", device.id2) - // Set up RX interrupt for frame detection - if err := rmii.EnableRxInterrupt(func(pin machine.Pin) { - rmii.OnRxComplete() - }); err != nil { - panic("Failed to enable RX interrupt: " + err.Error()) - } - - // Start RX DMA - go func() { - for { - if err := rmii.StartRxDMA(); err != nil { - println("RX DMA error:", err.Error()) - } - time.Sleep(10 * time.Millisecond) - } - }() - - // Initialize network stack - - // Main network processing loop - println("\nStarting network stack...") - for { - time.Sleep(1 * time.Millisecond) - } } // initRMII initializes the RMII interface with PIO and DMA @@ -141,62 +114,131 @@ func initRMII(Pio *pio.PIO) (*piolib.RMII, error) { return rmii, nil } -// Utility functions for formatting +const ( + regBasicControl = 0x00 + regBasicStatus = 0x01 + regPhyId1 = 0x02 + regPhyId2 = 0x03 + + regAutoNegotiationAdvertisement = 0x04 + regAutoNegotiationLinkPartnerAbility = 0x05 + regAutoNegotiationExpansion = 0x05 + regModeControlStatus = 0x11 + regSpecialModes = 0x12 + regSymbolErorCounter = 0x1a + regSpecialControlStatusIndications = 0x1b + regIRQSourceFlag = 0x1d + regIRQMask = 0x1e + regPhySpecialScontrolStatus = 0x1f +) -// waitForLink waits for the PHY link to come up -func waitForLink(rmii *piolib.RMII) { - println("Reading PHY registers for diagnostics...") +type LAN8720 struct { + bus *piolib.RMII + smiaddr uint8 + id1, id2 uint16 +} - // Read PHY ID registers - id1, err1 := rmii.MDIORead(rmii.PHYAddr(), 2) - id2, err2 := rmii.MDIORead(rmii.PHYAddr(), 3) - if err1 == nil && err2 == nil { - println(" PHY ID1:", formatHex16(id1)) - println(" PHY ID2:", formatHex16(id2)) +func NewLAN8270(Pio *pio.PIO, cfg piolib.RMIIConfig) (*LAN8720, error) { + smTx, err := Pio.ClaimStateMachine() + if err != nil { + return nil, err } + smRx, err := Pio.ClaimStateMachine() + if err != nil { + return nil, err + } + // Configure RMII - // Read control register - ctrl, errCtrl := rmii.MDIORead(rmii.PHYAddr(), 0) - if errCtrl == nil { - println(" Control Reg (0):", formatHex16(ctrl)) + rmii, err := piolib.NewRMII(smTx, smRx, cfg) + if err != nil { + return nil, err } + return &LAN8720{bus: rmii}, nil +} - attempt := 0 - for { - // Read PHY Basic Status Register (register 1) - status, err := rmii.MDIORead(rmii.PHYAddr(), 1) - if err != nil { - println("Error reading PHY status:", err.Error()) - time.Sleep(100 * time.Millisecond) - continue - } +type status uint16 +type control uint16 - attempt++ - if attempt%10 == 0 { - println(" Status Reg (1):", formatHex16(status), "- Link bit:", (status>>2)&1) +func (c *control) SetEnabled(b bool) { + *c &^= 1 << 15 + if b { + *c |= 1 << 15 + } +} +func (c control) IsEnabled() bool { + return c&(1<<15) != 0 +} - // Read more diagnostic info - if attempt%30 == 0 { - // Read auto-neg advertisement (reg 4) - anar, _ := rmii.MDIORead(rmii.PHYAddr(), 4) - println(" Auto-Neg Adv (4):", formatHex16(anar)) +func (s status) IsLinked() bool { + return s&(1<<2) != 0 +} - // Read auto-neg link partner (reg 5) - anlpar, _ := rmii.MDIORead(rmii.PHYAddr(), 5) - println(" Link Partner (5):", formatHex16(anlpar)) - } - } +func (lan *LAN8720) Status() (status, error) { + stat, err := lan.readReg(regBasicStatus) + return status(stat), err +} + +func (lan *LAN8720) BasicControl() (control, error) { + ct, err := lan.readReg(regBasicControl) + return control(ct), err +} - // Check link status bit (bit 2) - if status&0x04 != 0 { - println(" Link established! Final status:", formatHex16(status)) - return +func (lan *LAN8720) Init() error { + const maxAddr = 31 + lan.smiaddr = 255 + for addr := uint8(0); addr <= maxAddr; addr++ { + val, err := lan.bus.MDIORead(addr, 0) + if err != nil { + continue + } + if val != 0xffff && val != 0x0000 { + lan.smiaddr = addr + break } + time.Sleep(150 * time.Microsecond) + } + if lan.smiaddr > maxAddr { + return errors.New("no PHY found via addr scanning") + } + ctl, err := lan.BasicControl() + if err != nil { + return errors.New("failed reading basic control: " + err.Error()) + } + ctl.SetEnabled(true) + err = lan.writeReg(regBasicControl, uint16(ctl)) + if err != nil { + return err + } + time.Sleep(50 * time.Millisecond) + ctl, err = lan.BasicControl() - time.Sleep(100 * time.Millisecond) + if err != nil { + return err + } else if ctl.IsEnabled() { + println("want ctl bit 16, got:", formatHex16(uint16(ctl))) + return errors.New("lan8720 reset failed") } + lan.id1, err = lan.readReg(regPhyId1) + if err != nil { + return err + } + lan.id2, err = lan.readReg(regPhyId2) + if err != nil { + return err + } + return nil +} + +func (lan *LAN8720) readReg(reg uint8) (uint16, error) { + return lan.bus.MDIORead(lan.smiaddr, reg) } +func (lan *LAN8720) writeReg(reg uint8, value uint16) error { + return lan.bus.MDIOWrite(lan.smiaddr, reg, value) +} + +// Utility functions for formatting + func formatHex16(val uint16) string { fwd := []byte{'0', 'x'} fwd = appendHex(fwd, byte(val>>8)) diff --git a/rp2-pio/piolib/rmii.go b/rp2-pio/piolib/rmii.go index f7b830e..96b7e74 100644 --- a/rp2-pio/piolib/rmii.go +++ b/rp2-pio/piolib/rmii.go @@ -73,21 +73,21 @@ func NewRMII(smTx, smRx pio.StateMachine, cfg RMIIConfig) (*RMII, error) { return rmii, nil } -// DiscoverPHY scans MDIO addresses 0-31 to find a connected PHY. -// Returns the PHY address or an error if no PHY is found. -func (r *RMII) DiscoverPHY() error { - for addr := uint8(0); addr < 32; addr++ { - val, err := r.MDIORead(addr, 0) - if err != nil { - continue - } - if val != 0xffff && val != 0x0000 { - r.phyAddr = addr - return nil - } - } - return errors.New("no PHY found on MDIO bus") -} +// // DiscoverPHY scans MDIO addresses 0-31 to find a connected PHY. +// // Returns the PHY address or an error if no PHY is found. +// func (r *RMII) DiscoverPHY() error { +// for addr := uint8(0); addr < 32; addr++ { +// val, err := r.MDIORead(addr, 0) +// if err != nil { +// continue +// } +// if val != 0xffff && val != 0x0000 { +// r.phyAddr = addr +// return nil +// } +// } +// return errors.New("no PHY found on MDIO bus") +// } // InitPHY initializes the PHY with auto-negotiation settings. // Must be called after DiscoverPHY(). From 30c095eb87d719212ae665b1093393b5c60e4a1b Mon Sep 17 00:00:00 2001 From: Patricio Whittingslow Date: Fri, 14 Nov 2025 08:00:19 -0300 Subject: [PATCH 7/7] fix zmdio bug, read from reg 1 instead of 0 for discovery, move around mdio pin edges --- rp2-pio/examples/rmii/main.go | 5 ++++- rp2-pio/piolib/rmii.go | 13 +++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/rp2-pio/examples/rmii/main.go b/rp2-pio/examples/rmii/main.go index 4b6f686..bdbd601 100644 --- a/rp2-pio/examples/rmii/main.go +++ b/rp2-pio/examples/rmii/main.go @@ -54,6 +54,8 @@ func main() { RxBufferSize: 2048, TxBufferSize: 2048, } + pinMDC.Configure(machine.PinConfig{Mode: machine.PinOutput}) + pinMDC.Low() // Sleep to allow serial monitor to connect time.Sleep(2 * time.Second) println("=== LAN 8720 RMII ===") @@ -187,7 +189,7 @@ func (lan *LAN8720) Init() error { const maxAddr = 31 lan.smiaddr = 255 for addr := uint8(0); addr <= maxAddr; addr++ { - val, err := lan.bus.MDIORead(addr, 0) + val, err := lan.bus.MDIORead(addr, regBasicStatus) if err != nil { continue } @@ -200,6 +202,7 @@ func (lan *LAN8720) Init() error { if lan.smiaddr > maxAddr { return errors.New("no PHY found via addr scanning") } + ctl, err := lan.BasicControl() if err != nil { return errors.New("failed reading basic control: " + err.Error()) diff --git a/rp2-pio/piolib/rmii.go b/rp2-pio/piolib/rmii.go index 96b7e74..6301918 100644 --- a/rp2-pio/piolib/rmii.go +++ b/rp2-pio/piolib/rmii.go @@ -65,7 +65,7 @@ func NewRMII(smTx, smRx pio.StateMachine, cfg RMIIConfig) (*RMII, error) { rxDVPin: cfg.TxRx.CRSDVPin, rxBuffer: make([]byte, rxBufSize), txBuffer: make([]byte, txBufSize), - zmdio: cfg.NoZMDIO, + zmdio: !cfg.NoZMDIO, } // Configure MDIO/MDC pins @@ -119,12 +119,12 @@ func (r *RMII) PHYAddr() uint8 { // mdioClockOut outputs a bit on MDIO while pulsing MDC clock. func (r *RMII) mdioClockOut(bit bool) { - r.mdc.Low() - time.Sleep(time.Microsecond) r.mdioSet(bit) time.Sleep(time.Microsecond) r.mdc.High() time.Sleep(time.Microsecond) + r.mdc.Low() + time.Sleep(time.Microsecond) } func (r *RMII) mdioSet(b bool) { @@ -152,16 +152,17 @@ func (r *RMII) mdioLow() { // mdioClockIn reads a bit from MDIO while pulsing MDC clock. func (r *RMII) mdioClockIn() bool { - r.mdc.Low() - time.Sleep(time.Microsecond) r.mdc.High() + time.Sleep(time.Microsecond) bit := r.mdio.Get() time.Sleep(time.Microsecond) + r.mdc.Low() + time.Sleep(time.Microsecond) return bit } func (r *RMII) mdCfg() { - // r.mdc.High() + r.mdc.Low() r.mdio.Configure(machine.PinConfig{Mode: machine.PinOutput}) r.mdc.Configure(machine.PinConfig{Mode: machine.PinOutput}) }