diff --git a/embassy-stm32/src/eth/lan8742a.rs b/embassy-stm32/src/eth/lan8742a.rs new file mode 100644 index 00000000..74d0ca5d --- /dev/null +++ b/embassy-stm32/src/eth/lan8742a.rs @@ -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(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(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(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(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); + } +} diff --git a/embassy-stm32/src/eth/mod.rs b/embassy-stm32/src/eth/mod.rs index 8791e155..e41ebf4d 100644 --- a/embassy-stm32/src/eth/mod.rs +++ b/embassy-stm32/src/eth/mod.rs @@ -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(sm: &mut S); + /// PHY initialisation. + fn phy_init(sm: &mut S); + /// Poll link to see if it is up and FD with 100Mbps + fn poll_link(sm: &mut S) -> bool; +} diff --git a/embassy-stm32/src/eth/v2/mod.rs b/embassy-stm32/src/eth/v2/mod.rs index d54c6e85..67d722d1 100644 --- a/embassy-stm32/src/eth/v2/mod.rs +++ b/embassy-stm32/src/eth/v2/mod.rs @@ -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>, 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 + 'd, interrupt: impl Unborrow + 'd, @@ -52,7 +41,10 @@ impl<'d, T: Instance, const TX: usize, const RX: usize> Ethernet<'d, T, TX, RX> tx_d0: impl Unborrow> + 'd, tx_d1: impl Unborrow> + 'd, tx_en: impl Unborrow> + '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