eth: Add lan8742a PHY
This commit is contained in:
parent
46e1bae9e3
commit
4cffa200bd
103
embassy-stm32/src/eth/lan8742a.rs
Normal file
103
embassy-stm32/src/eth/lan8742a.rs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
//! SMSC LAN8742A Ethernet PHY
|
||||||
|
|
||||||
|
use super::{StationManagement, PHY};
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
mod phy_consts {
|
||||||
|
pub const PHY_REG_BCR: u8 = 0x00;
|
||||||
|
pub const PHY_REG_BSR: u8 = 0x01;
|
||||||
|
pub const PHY_REG_ID1: u8 = 0x02;
|
||||||
|
pub const PHY_REG_ID2: u8 = 0x03;
|
||||||
|
pub const PHY_REG_ANTX: u8 = 0x04;
|
||||||
|
pub const PHY_REG_ANRX: u8 = 0x05;
|
||||||
|
pub const PHY_REG_ANEXP: u8 = 0x06;
|
||||||
|
pub const PHY_REG_ANNPTX: u8 = 0x07;
|
||||||
|
pub const PHY_REG_ANNPRX: u8 = 0x08;
|
||||||
|
pub const PHY_REG_SSR: u8 = 0x1F; // Special Status Register
|
||||||
|
pub const PHY_REG_CTL: u8 = 0x0D; // Ethernet PHY Register Control
|
||||||
|
pub const PHY_REG_ADDAR: u8 = 0x0E; // Ethernet PHY Address or Data
|
||||||
|
|
||||||
|
pub const PHY_REG_WUCSR: u16 = 0x8010;
|
||||||
|
|
||||||
|
pub const PHY_REG_BCR_COLTEST: u16 = 1 << 7;
|
||||||
|
pub const PHY_REG_BCR_FD: u16 = 1 << 8;
|
||||||
|
pub const PHY_REG_BCR_ANRST: u16 = 1 << 9;
|
||||||
|
pub const PHY_REG_BCR_ISOLATE: u16 = 1 << 10;
|
||||||
|
pub const PHY_REG_BCR_POWERDN: u16 = 1 << 11;
|
||||||
|
pub const PHY_REG_BCR_AN: u16 = 1 << 12;
|
||||||
|
pub const PHY_REG_BCR_100M: u16 = 1 << 13;
|
||||||
|
pub const PHY_REG_BCR_LOOPBACK: u16 = 1 << 14;
|
||||||
|
pub const PHY_REG_BCR_RESET: u16 = 1 << 15;
|
||||||
|
|
||||||
|
pub const PHY_REG_BSR_JABBER: u16 = 1 << 1;
|
||||||
|
pub const PHY_REG_BSR_UP: u16 = 1 << 2;
|
||||||
|
pub const PHY_REG_BSR_FAULT: u16 = 1 << 4;
|
||||||
|
pub const PHY_REG_BSR_ANDONE: u16 = 1 << 5;
|
||||||
|
|
||||||
|
pub const PHY_REG_SSR_ANDONE: u16 = 1 << 12;
|
||||||
|
pub const PHY_REG_SSR_SPEED: u16 = 0b111 << 2;
|
||||||
|
pub const PHY_REG_SSR_10BASE_HD: u16 = 0b001 << 2;
|
||||||
|
pub const PHY_REG_SSR_10BASE_FD: u16 = 0b101 << 2;
|
||||||
|
pub const PHY_REG_SSR_100BASE_HD: u16 = 0b010 << 2;
|
||||||
|
pub const PHY_REG_SSR_100BASE_FD: u16 = 0b110 << 2;
|
||||||
|
}
|
||||||
|
use self::phy_consts::*;
|
||||||
|
|
||||||
|
/// SMSC LAN8742A Ethernet PHY
|
||||||
|
pub struct LAN8742A;
|
||||||
|
|
||||||
|
unsafe impl PHY for LAN8742A {
|
||||||
|
/// Reset PHY and wait for it to come out of reset.
|
||||||
|
fn phy_reset<S: StationManagement>(sm: &mut S) {
|
||||||
|
sm.smi_write(PHY_REG_BCR, PHY_REG_BCR_RESET);
|
||||||
|
while sm.smi_read(PHY_REG_BCR) & PHY_REG_BCR_RESET == PHY_REG_BCR_RESET {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// PHY initialisation.
|
||||||
|
fn phy_init<S: StationManagement>(sm: &mut S) {
|
||||||
|
// Clear WU CSR
|
||||||
|
Self::smi_write_ext(sm, PHY_REG_WUCSR, 0);
|
||||||
|
|
||||||
|
// Enable auto-negotiation
|
||||||
|
sm.smi_write(
|
||||||
|
PHY_REG_BCR,
|
||||||
|
PHY_REG_BCR_AN | PHY_REG_BCR_ANRST | PHY_REG_BCR_100M,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_link<S: StationManagement>(sm: &mut S) -> bool {
|
||||||
|
let bsr = sm.smi_read(PHY_REG_BSR);
|
||||||
|
let ssr = sm.smi_read(PHY_REG_SSR);
|
||||||
|
|
||||||
|
// No link without autonegotiate
|
||||||
|
if bsr & PHY_REG_BSR_ANDONE == 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// No link if link is down
|
||||||
|
if bsr & PHY_REG_BSR_UP == 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// No link if autonegotiate incomplete
|
||||||
|
if ssr & PHY_REG_SSR_ANDONE == 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// No link if other side isn't 100Mbps full duplex
|
||||||
|
if ssr & PHY_REG_SSR_SPEED != PHY_REG_SSR_100BASE_FD {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Got link
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Public functions for the LAN8742A
|
||||||
|
impl LAN8742A {
|
||||||
|
// Writes a value to an extended PHY register in MMD address space
|
||||||
|
fn smi_write_ext<S: StationManagement>(sm: &mut S, reg_addr: u16, reg_data: u16) {
|
||||||
|
sm.smi_write(PHY_REG_CTL, 0x0003); // set address
|
||||||
|
sm.smi_write(PHY_REG_ADDAR, reg_addr);
|
||||||
|
sm.smi_write(PHY_REG_CTL, 0x4003); // set data
|
||||||
|
sm.smi_write(PHY_REG_ADDAR, reg_data);
|
||||||
|
}
|
||||||
|
}
|
@ -3,5 +3,32 @@
|
|||||||
#[cfg_attr(eth_v1, path = "v1.rs")]
|
#[cfg_attr(eth_v1, path = "v1.rs")]
|
||||||
#[cfg_attr(eth_v2, path = "v2/mod.rs")]
|
#[cfg_attr(eth_v2, path = "v2/mod.rs")]
|
||||||
mod _version;
|
mod _version;
|
||||||
|
pub mod lan8742a;
|
||||||
|
|
||||||
pub use _version::*;
|
pub use _version::*;
|
||||||
|
|
||||||
|
/// Station Management Interface (SMI) on an ethernet PHY
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The methods cannot move out of self
|
||||||
|
pub unsafe trait StationManagement {
|
||||||
|
/// Read a register over SMI.
|
||||||
|
fn smi_read(&mut self, reg: u8) -> u16;
|
||||||
|
/// Write a register over SMI.
|
||||||
|
fn smi_write(&mut self, reg: u8, val: u16);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Traits for an Ethernet PHY
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The methods cannot move S
|
||||||
|
pub unsafe trait PHY {
|
||||||
|
/// Reset PHY and wait for it to come out of reset.
|
||||||
|
fn phy_reset<S: StationManagement>(sm: &mut S);
|
||||||
|
/// PHY initialisation.
|
||||||
|
fn phy_init<S: StationManagement>(sm: &mut S);
|
||||||
|
/// Poll link to see if it is up and FD with 100Mbps
|
||||||
|
fn poll_link<S: StationManagement>(sm: &mut S) -> bool;
|
||||||
|
}
|
||||||
|
@ -14,32 +14,21 @@ use crate::interrupt::Interrupt;
|
|||||||
use crate::pac::gpio::vals::Ospeedr;
|
use crate::pac::gpio::vals::Ospeedr;
|
||||||
use crate::pac::ETH;
|
use crate::pac::ETH;
|
||||||
use crate::peripherals;
|
use crate::peripherals;
|
||||||
|
use crate::time::Hertz;
|
||||||
|
|
||||||
mod descriptors;
|
mod descriptors;
|
||||||
|
use super::{StationManagement, PHY};
|
||||||
use descriptors::DescriptorRing;
|
use descriptors::DescriptorRing;
|
||||||
|
|
||||||
/// Station Management Interface (SMI) on an ethernet PHY
|
pub struct Ethernet<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> {
|
||||||
pub trait StationManagement {
|
|
||||||
/// Read a register over SMI.
|
|
||||||
fn smi_read(&mut self, reg: u8) -> u16;
|
|
||||||
/// Write a register over SMI.
|
|
||||||
fn smi_write(&mut self, reg: u8, val: u16);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Traits for an Ethernet PHY
|
|
||||||
pub trait PHY {
|
|
||||||
/// Reset PHY and wait for it to come out of reset.
|
|
||||||
fn phy_reset(&mut self);
|
|
||||||
/// PHY initialisation.
|
|
||||||
fn phy_init(&mut self);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Ethernet<'d, T: Instance, const TX: usize, const RX: usize> {
|
|
||||||
state: PeripheralMutex<Inner<'d, T, TX, RX>>,
|
state: PeripheralMutex<Inner<'d, T, TX, RX>>,
|
||||||
pins: [AnyPin; 9],
|
pins: [AnyPin; 9],
|
||||||
|
_phy: P,
|
||||||
|
clock_range: u8,
|
||||||
|
phy_addr: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'d, T: Instance, const TX: usize, const RX: usize> Ethernet<'d, T, TX, RX> {
|
impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Ethernet<'d, T, P, TX, RX> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
peri: impl Unborrow<Target = T> + 'd,
|
peri: impl Unborrow<Target = T> + 'd,
|
||||||
interrupt: impl Unborrow<Target = T::Interrupt> + 'd,
|
interrupt: impl Unborrow<Target = T::Interrupt> + 'd,
|
||||||
@ -52,7 +41,10 @@ impl<'d, T: Instance, const TX: usize, const RX: usize> Ethernet<'d, T, TX, RX>
|
|||||||
tx_d0: impl Unborrow<Target = impl TXD0Pin<T>> + 'd,
|
tx_d0: impl Unborrow<Target = impl TXD0Pin<T>> + 'd,
|
||||||
tx_d1: impl Unborrow<Target = impl TXD1Pin<T>> + 'd,
|
tx_d1: impl Unborrow<Target = impl TXD1Pin<T>> + 'd,
|
||||||
tx_en: impl Unborrow<Target = impl TXEnPin<T>> + 'd,
|
tx_en: impl Unborrow<Target = impl TXEnPin<T>> + 'd,
|
||||||
|
phy: P,
|
||||||
mac_addr: [u8; 6],
|
mac_addr: [u8; 6],
|
||||||
|
hclk: Hertz,
|
||||||
|
phy_addr: u8,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
unborrow!(interrupt, ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en);
|
unborrow!(interrupt, ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en);
|
||||||
|
|
||||||
@ -116,6 +108,20 @@ impl<'d, T: Instance, const TX: usize, const RX: usize> Ethernet<'d, T, TX, RX>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the MDC clock frequency in the range 1MHz - 2.5MHz
|
||||||
|
let hclk_mhz = hclk.0 / 1_000_000;
|
||||||
|
let clock_range = match hclk_mhz {
|
||||||
|
0..=34 => 2, // Divide by 16
|
||||||
|
35..=59 => 3, // Divide by 26
|
||||||
|
60..=99 => 0, // Divide by 42
|
||||||
|
100..=149 => 1, // Divide by 62
|
||||||
|
150..=249 => 4, // Divide by 102
|
||||||
|
250..=310 => 5, // Divide by 124
|
||||||
|
_ => {
|
||||||
|
panic!("HCLK results in MDC clock > 2.5MHz even for the highest CSR clock divider")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let pins = [
|
let pins = [
|
||||||
ref_clk.degrade(),
|
ref_clk.degrade(),
|
||||||
mdio.degrade(),
|
mdio.degrade(),
|
||||||
@ -128,7 +134,13 @@ impl<'d, T: Instance, const TX: usize, const RX: usize> Ethernet<'d, T, TX, RX>
|
|||||||
tx_en.degrade(),
|
tx_en.degrade(),
|
||||||
];
|
];
|
||||||
|
|
||||||
Self { state, pins }
|
Self {
|
||||||
|
state,
|
||||||
|
pins,
|
||||||
|
_phy: phy,
|
||||||
|
clock_range,
|
||||||
|
phy_addr,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(self: Pin<&mut Self>) {
|
pub fn init(self: Pin<&mut Self>) {
|
||||||
@ -163,10 +175,52 @@ impl<'d, T: Instance, const TX: usize, const RX: usize> Ethernet<'d, T, TX, RX>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
P::phy_reset(this);
|
||||||
|
P::phy_init(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'d, T: Instance, const TX: usize, const RX: usize> Drop for Ethernet<'d, T, TX, RX> {
|
unsafe impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> StationManagement
|
||||||
|
for Ethernet<'d, T, P, TX, RX>
|
||||||
|
{
|
||||||
|
fn smi_read(&mut self, reg: u8) -> u16 {
|
||||||
|
// NOTE(unsafe) These registers aren't used in the interrupt and we have `&mut self`
|
||||||
|
unsafe {
|
||||||
|
let mac = ETH.ethernet_mac();
|
||||||
|
|
||||||
|
mac.macmdioar().modify(|w| {
|
||||||
|
w.set_pa(self.phy_addr);
|
||||||
|
w.set_rda(reg);
|
||||||
|
w.set_goc(0b11); // read
|
||||||
|
w.set_cr(self.clock_range);
|
||||||
|
w.set_mb(true);
|
||||||
|
});
|
||||||
|
while mac.macmdioar().read().mb() {}
|
||||||
|
mac.macmdiodr().read().md()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn smi_write(&mut self, reg: u8, val: u16) {
|
||||||
|
// NOTE(unsafe) These registers aren't used in the interrupt and we have `&mut self`
|
||||||
|
unsafe {
|
||||||
|
let mac = ETH.ethernet_mac();
|
||||||
|
|
||||||
|
mac.macmdiodr().write(|w| w.set_md(val));
|
||||||
|
mac.macmdioar().modify(|w| {
|
||||||
|
w.set_pa(self.phy_addr);
|
||||||
|
w.set_rda(reg);
|
||||||
|
w.set_goc(0b01); // write
|
||||||
|
w.set_cr(self.clock_range);
|
||||||
|
w.set_mb(true);
|
||||||
|
});
|
||||||
|
while mac.macmdioar().read().mb() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Drop
|
||||||
|
for Ethernet<'d, T, P, TX, RX>
|
||||||
|
{
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
for pin in self.pins.iter_mut() {
|
for pin in self.pins.iter_mut() {
|
||||||
// NOTE(unsafe) Exclusive access to the regs
|
// NOTE(unsafe) Exclusive access to the regs
|
||||||
|
Loading…
Reference in New Issue
Block a user