eth: Add lan8742a PHY

This commit is contained in:
Thales Fragoso 2021-06-10 02:38:59 -03:00 committed by Dario Nieuwenhuis
parent 46e1bae9e3
commit 4cffa200bd
3 changed files with 204 additions and 20 deletions

View 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);
}
}

View File

@ -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;
}

View File

@ -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