Files
lneto/phy/mdio-bitbang.go
T
2026-01-25 09:11:08 -03:00

189 lines
4.9 KiB
Go

package phy
import (
"errors"
)
var _ MDIOBus = (*MDIOBitBang)(nil) // compile time guarantee of interface implementation.
const (
mdioRead = 0b10
mdioWrite = 0b01
miaddrc45 = 1 << 30
c45bit = 1 << 15
c45Addr = c45bit | 0b00
c45Read = c45bit | 0b11
c45Write = c45bit | 0b01
)
// MDIOBitBang provides a software defined(bitbang) MDIO/MDC management interface for PHY register access
// as the STA (Management station, this implementation) which communicates to the PHY (Physical layer device).
// Inspired by linux/v3.13.1/source/drivers/net/phy/mdio-bitbang.c
type MDIOBitBang struct {
_sendBit func(bit bool)
_getBit func() (inputBit bool)
_setDir func(output bool)
}
// Configure initializes the MDIO bit-bang interface with the given pin control callbacks.
// Below is a TinyGo oriented HAL needed to use MDIOBitBang. MDC is clock line, MDIO is data line.
// Example uses Z(high impedance) tristate level output encoding to avoid MAC/PHY simultaneous driving of data line.
//
// const mdioDelay = 340 * time.Nanosecond // MDIO spec max turnaround time
// pinMDIO.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
// pinMDC.Configure(machine.PinConfig{Mode: machine.PinOutput})
// pinMDC.Low()
// var mdio2 phy.MDIOBitBang
// mdio2.Configure(func(outBit bool) {
// // sendBit: set data, clock high, clock low
// if outBit {
// pinMDIO.Configure(machine.PinConfig{Mode: machine.PinInputPullup}) // Z high.
// } else {
// pinMDIO.Low()
// pinMDIO.Configure(machine.PinConfig{Mode: machine.PinOutput})
// }
// time.Sleep(mdioDelay)
// pinMDC.High()
// time.Sleep(mdioDelay)
// pinMDC.Low()
// }, func() (inBit bool) {
// // getBit: clock high, read, clock low
// time.Sleep(mdioDelay)
// pinMDC.High()
// time.Sleep(mdioDelay)
// pinMDC.Low()
// return pinMDIO.Get()
// }, func(setOut bool) {
// // setDir: configure pin direction
// if setOut {
// pinMDIO.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
// } else {
// pinMDIO.Configure(machine.PinConfig{Mode: machine.PinInput})
// }
// })
func (m *MDIOBitBang) Configure(sendBit func(bit bool), getBit func() bool, setDir func(setOut bool)) {
if sendBit == nil || getBit == nil || setDir == nil {
panic("nil callback")
}
m._getBit = getBit
m._sendBit = sendBit
m._setDir = setDir
m.reset()
}
func (m *MDIOBitBang) reset() {
// setting direction to output releases the bus. Pin idle at high impedance state when using Z mode.
m.setDir(true)
}
// Read reads a PHY register. Uses Clause 45 framing if devAddr is non-zero.
func (m *MDIOBitBang) Read(phyAddr, devAddr uint8, regAddr uint16) (uint16, error) {
isC45 := devAddr != 0
if isC45 {
m.cmdAddr2(phyAddr, devAddr, regAddr)
m.cmd(c45Read, phyAddr, devAddr)
} else {
m.cmd(mdioRead, phyAddr, uint8(regAddr))
}
m.setDir(false)
// Check turnaround bit, PHY should drive it to zero.
if m.getBit() {
// PHY did not drive low, as would be expected.
// Ensure flush:
for range 32 {
m.getBit()
}
return 0xffff, errors.New("PHY did not drive turnaround low")
}
ret := m.getNum(16)
m.getBit()
return ret, nil
}
// Write writes a value to a PHY register. Uses Clause 45 framing if devAddr is non-zero.
func (m *MDIOBitBang) Write(phyAddr, devAddr uint8, regAddr, value uint16) error {
isC45 := devAddr != 0
if isC45 {
m.cmdAddr2(phyAddr, devAddr, regAddr)
m.cmd(c45Write, phyAddr, devAddr)
} else {
m.cmd(mdioWrite, phyAddr, uint8(regAddr))
}
// send turnaround (10)
m.sendBit(true)
m.sendBit(false)
m.sendNum(value, 16)
m.setDir(false)
m.getBit()
return nil
}
func (m *MDIOBitBang) cmdAddr2(phy, dev uint8, reg uint16) {
m.cmd(c45Addr, phy, dev)
// turnaround 10.
m.sendBit(true)
m.sendBit(false)
m.sendNum(reg, 16)
m.setDir(false)
m.getBit()
}
func (m *MDIOBitBang) cmd(op uint16, phy uint8, reg uint8) {
const writeDir = true
m.setDir(writeDir)
// Preamble, 32 bits of 1.
for range 32 {
m.sendBit(true)
}
// Start of frame: 01
// Clause 45 op uses 00=start, 11=read, 10=write
m.sendBit(false)
m.sendBit(op&c45bit == 0)
m.sendBit((op>>1)&1 != 0)
m.sendBit((op>>0)&1 != 0)
m.sendNum(uint16(phy), 5)
m.sendNum(uint16(reg), 5)
}
func (m *MDIOBitBang) sendNum(val uint16, bits int) {
for i := bits - 1; i >= 0; i-- {
m.sendBit((val>>i)&1 != 0)
}
}
func (m *MDIOBitBang) getNum(bits int) (ret uint16) {
for i := bits - 1; i >= 0; i-- {
ret <<= 1
ret |= uint16(b2u8(m.getBit()))
}
return ret
}
// MDIO low-level clock operations
// Reference: https://github.com/sandeepmistry/pico-rmii-ethernet/blob/main/examples/httpd/main.c
// Reference: netif_rmii_ethernet_mdio_clock_out() and netif_rmii_ethernet_mdio_clock_in()
// from rmii_ethernet.c
// setDir configures pins preparing for write/read operations.
func (m *MDIOBitBang) setDir(outWrite bool) {
m._setDir(outWrite)
}
func (m *MDIOBitBang) sendBit(b bool) {
m._sendBit(b)
}
func (m *MDIOBitBang) getBit() bool {
return m._getBit()
}
func b2u8(b bool) uint8 {
if b {
return 1
}
return 0
}