mirror of
https://github.com/soypat/lneto.git
synced 2026-08-10 09:53:44 +00:00
add MDIOBitBang
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
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
|
||||
// Below is a TinyGo oriented HAL needed to use MDIOBitBang. MDC is clock line, MDIO is 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})
|
||||
// } 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})
|
||||
// }
|
||||
// })
|
||||
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.
|
||||
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.
|
||||
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
|
||||
}
|
||||
+30
@@ -40,6 +40,36 @@ All RMII signals are synchronous to a continuous 50 MHz reference clock. Two mod
|
||||
|
||||
|
||||
## Tx0/Tx1/TxEN and CLKREF/RETCLK interface
|
||||
|
||||
### Frame Transmission Sequence
|
||||
First 8 bytes in transmission are preamble and start of frame delimiter (SFD).
|
||||
The preamble is composed of 7 bytes, all dibits valued 0b01, so TX0=1, TX1=0.
|
||||
The SFD is composed of 3 0b01 dibits and a 0b11 dibit where both TX0 and TX1 are high for a single CLKREF cycle. After the final SFD(0b11) dibit the frame data is presented of the wire.
|
||||
|
||||
TX_EN is asserted synchronously with the first dibit of preamble and remains HIGH throughout the entire frame (preamble, SFD, payload, CRC).
|
||||
|
||||
Example at 100M link mode:
|
||||
```
|
||||
REF_CLK: _|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_ ...
|
||||
TX_EN: __|‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ ... (HIGH until end of frame)
|
||||
TX0: __|‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾...‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾|.D ...
|
||||
TX1: _______________________________...____________|‾‾‾|.A ...
|
||||
TXD[1:0]: 00|01 |01 |01 |01 |01 |01 |01 |...|01 |01 |01 |11 |DA|TA|...
|
||||
└───────────── Preamble (28 dibits) ─────────┘└SFD┘└─ Frame data ─...
|
||||
```
|
||||
|
||||
|
||||
**Inter-Packet Gap (IPG)**: After TX_EN deasserts, TXD[1:0] must be held at 00 for a minimum of 96 bit times (48 dibits = 12 bytes at 100M). This is the minimum gap required between consecutive frame transmissions.
|
||||
|
||||
```
|
||||
End of frame with IPG at 100M link mode:
|
||||
|
||||
REF_CLK: _|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_ ...
|
||||
TX_EN: ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾|_____________________________ ...
|
||||
TXD[1:0]: ...data...|CR|C |00|00|00|00|00|00|00|00|...
|
||||
└CRC┘ └──── IPG (≥48 dibits) ────...
|
||||
```
|
||||
|
||||
### Practical Tx example:
|
||||
To illustrate programatically we'll suppose we have a hardware which requires a byte for every clock. Each byte contains 3 bits to be sent out: Tx0,Tx1,TxEn bits.
|
||||
This is not a contrived example, it is how Sandeep Mistry's and Rob Scott's LAN8720 drivers work.
|
||||
|
||||
Reference in New Issue
Block a user