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_v2, path = "v2/mod.rs")]
|
||||
mod _version;
|
||||
pub mod lan8742a;
|
||||
|
||||
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::ETH;
|
||||
use crate::peripherals;
|
||||
use crate::time::Hertz;
|
||||
|
||||
mod descriptors;
|
||||
use super::{StationManagement, PHY};
|
||||
use descriptors::DescriptorRing;
|
||||
|
||||
/// Station Management Interface (SMI) on an ethernet PHY
|
||||
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> {
|
||||
pub struct Ethernet<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> {
|
||||
state: PeripheralMutex<Inner<'d, T, TX, RX>>,
|
||||
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(
|
||||
peri: impl Unborrow<Target = T> + '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_d1: impl Unborrow<Target = impl TXD1Pin<T>> + 'd,
|
||||
tx_en: impl Unborrow<Target = impl TXEnPin<T>> + 'd,
|
||||
phy: P,
|
||||
mac_addr: [u8; 6],
|
||||
hclk: Hertz,
|
||||
phy_addr: u8,
|
||||
) -> Self {
|
||||
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 = [
|
||||
ref_clk.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(),
|
||||
];
|
||||
|
||||
Self { state, pins }
|
||||
Self {
|
||||
state,
|
||||
pins,
|
||||
_phy: phy,
|
||||
clock_range,
|
||||
phy_addr,
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
for pin in self.pins.iter_mut() {
|
||||
// NOTE(unsafe) Exclusive access to the regs
|
||||
|
Loading…
Reference in New Issue
Block a user