1use crate::error::EmacError;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15#[cfg_attr(feature = "defmt", derive(defmt::Format))]
16pub enum MdcClockDivider {
17 Div16 = 2,
19 Div26 = 3,
21 Div42 = 0,
23 Div62 = 1,
25 Div102 = 4,
27 Div124 = 5,
29}
30
31impl MdcClockDivider {
32 pub const fn from_sys_clock_hz(hz: u32) -> Self {
34 if hz < 35_000_000 {
35 Self::Div16
36 } else if hz < 60_000_000 {
37 Self::Div26
38 } else if hz < 100_000_000 {
39 Self::Div42
40 } else if hz < 150_000_000 {
41 Self::Div62
42 } else if hz < 250_000_000 {
43 Self::Div102
44 } else {
45 Self::Div124
46 }
47 }
48
49 pub const fn reg_value(self) -> u32 {
51 self as u32
52 }
53}
54
55impl Default for MdcClockDivider {
56 fn default() -> Self {
58 Self::Div42
59 }
60}
61
62pub const MAX_PHY_ADDR: u8 = 31;
64
65pub const MAX_REG_ADDR: u8 = 31;
67
68const MDIO_TIMEOUT_ITERS: u32 = 1000;
70
71const MAC_BASE: usize = 0x3FF6_A000;
75
76const GMACMIIADDR_OFFSET: usize = 0x10;
78const GMACMIIDATA_OFFSET: usize = 0x14;
80
81const GMACMIIADDR_PA_SHIFT: u32 = 11;
83const GMACMIIADDR_PA_MASK: u32 = 0x1F << 11;
84const GMACMIIADDR_GR_SHIFT: u32 = 6;
85const GMACMIIADDR_GR_MASK: u32 = 0x1F << 6;
86const GMACMIIADDR_CR_SHIFT: u32 = 2;
87const GMACMIIADDR_CR_MASK: u32 = 0x0F << 2;
88const GMACMIIADDR_GW: u32 = 1 << 1;
89const GMACMIIADDR_GB: u32 = 1 << 0;
90
91pub struct EspMdio {
101 clock_divider: MdcClockDivider,
102}
103
104impl EspMdio {
105 pub fn new() -> Self {
107 Self {
108 clock_divider: MdcClockDivider::default(),
109 }
110 }
111
112 pub fn with_clock_divider(divider: MdcClockDivider) -> Self {
114 Self {
115 clock_divider: divider,
116 }
117 }
118
119 pub fn read(&mut self, phy_addr: u8, reg_addr: u8) -> Result<u16, EmacError> {
121 if phy_addr > MAX_PHY_ADDR {
122 return Err(EmacError::InvalidPhyAddress);
123 }
124 if reg_addr > MAX_REG_ADDR {
125 return Err(EmacError::InvalidConfig);
126 }
127
128 self.wait_not_busy()?;
129
130 let addr = self.build_mii_addr(phy_addr, reg_addr, false);
131 self.write_mii_addr(addr);
132
133 self.wait_not_busy()?;
134
135 Ok((self.read_mii_data() & 0xFFFF) as u16)
136 }
137
138 pub fn write(&mut self, phy_addr: u8, reg_addr: u8, value: u16) -> Result<(), EmacError> {
140 if phy_addr > MAX_PHY_ADDR {
141 return Err(EmacError::InvalidPhyAddress);
142 }
143 if reg_addr > MAX_REG_ADDR {
144 return Err(EmacError::InvalidConfig);
145 }
146
147 self.wait_not_busy()?;
148
149 self.write_mii_data(value as u32);
150
151 let addr = self.build_mii_addr(phy_addr, reg_addr, true);
152 self.write_mii_addr(addr);
153
154 self.wait_not_busy()
155 }
156
157 pub(crate) fn build_mii_addr(&self, phy_addr: u8, reg_addr: u8, is_write: bool) -> u32 {
161 let mut addr = 0u32;
162 addr |= ((phy_addr as u32) << GMACMIIADDR_PA_SHIFT) & GMACMIIADDR_PA_MASK;
163 addr |= ((reg_addr as u32) << GMACMIIADDR_GR_SHIFT) & GMACMIIADDR_GR_MASK;
164 addr |= (self.clock_divider.reg_value() << GMACMIIADDR_CR_SHIFT) & GMACMIIADDR_CR_MASK;
165 if is_write {
166 addr |= GMACMIIADDR_GW;
167 }
168 addr |= GMACMIIADDR_GB;
169 addr
170 }
171
172 fn wait_not_busy(&self) -> Result<(), EmacError> {
174 for _ in 0..MDIO_TIMEOUT_ITERS {
175 if self.read_mii_addr() & GMACMIIADDR_GB == 0 {
176 return Ok(());
177 }
178 }
179 Err(EmacError::Timeout)
180 }
181
182 #[inline(always)]
185 fn read_mii_addr(&self) -> u32 {
186 unsafe { core::ptr::read_volatile((MAC_BASE + GMACMIIADDR_OFFSET) as *const u32) }
187 }
188
189 #[inline(always)]
190 fn write_mii_addr(&self, val: u32) {
191 unsafe { core::ptr::write_volatile((MAC_BASE + GMACMIIADDR_OFFSET) as *mut u32, val) }
192 }
193
194 #[inline(always)]
195 fn read_mii_data(&self) -> u32 {
196 unsafe { core::ptr::read_volatile((MAC_BASE + GMACMIIDATA_OFFSET) as *const u32) }
197 }
198
199 #[inline(always)]
200 fn write_mii_data(&self, val: u32) {
201 unsafe { core::ptr::write_volatile((MAC_BASE + GMACMIIDATA_OFFSET) as *mut u32, val) }
202 }
203}
204
205impl Default for EspMdio {
206 fn default() -> Self {
207 Self::new()
208 }
209}
210
211#[cfg(feature = "mdio-phy")]
214impl eth_mdio_phy::MdioBus for EspMdio {
215 type Error = EmacError;
216
217 fn read(&mut self, phy_addr: u8, reg_addr: u8) -> Result<u16, EmacError> {
218 EspMdio::read(self, phy_addr, reg_addr)
219 }
220
221 fn write(&mut self, phy_addr: u8, reg_addr: u8, value: u16) -> Result<(), EmacError> {
222 EspMdio::write(self, phy_addr, reg_addr, value)
223 }
224}
225
226#[cfg(test)]
229mod tests {
230 use super::*;
231
232 #[test]
235 fn mdc_divider_from_sys_clock() {
236 assert_eq!(
237 MdcClockDivider::from_sys_clock_hz(20_000_000),
238 MdcClockDivider::Div16
239 );
240 assert_eq!(
241 MdcClockDivider::from_sys_clock_hz(40_000_000),
242 MdcClockDivider::Div26
243 );
244 assert_eq!(
245 MdcClockDivider::from_sys_clock_hz(80_000_000),
246 MdcClockDivider::Div42
247 );
248 assert_eq!(
249 MdcClockDivider::from_sys_clock_hz(120_000_000),
250 MdcClockDivider::Div62
251 );
252 assert_eq!(
253 MdcClockDivider::from_sys_clock_hz(160_000_000),
254 MdcClockDivider::Div102
255 );
256 assert_eq!(
257 MdcClockDivider::from_sys_clock_hz(280_000_000),
258 MdcClockDivider::Div124
259 );
260 }
261
262 #[test]
263 fn mdc_divider_default_is_div42() {
264 assert_eq!(MdcClockDivider::default(), MdcClockDivider::Div42);
265 }
266
267 #[test]
268 fn mdc_divider_reg_values() {
269 assert_eq!(MdcClockDivider::Div42.reg_value(), 0);
270 assert_eq!(MdcClockDivider::Div62.reg_value(), 1);
271 assert_eq!(MdcClockDivider::Div16.reg_value(), 2);
272 assert_eq!(MdcClockDivider::Div26.reg_value(), 3);
273 assert_eq!(MdcClockDivider::Div102.reg_value(), 4);
274 assert_eq!(MdcClockDivider::Div124.reg_value(), 5);
275 }
276
277 #[test]
278 fn mdc_divider_boundary_35mhz() {
279 assert_eq!(
280 MdcClockDivider::from_sys_clock_hz(34_999_999),
281 MdcClockDivider::Div16
282 );
283 assert_eq!(
284 MdcClockDivider::from_sys_clock_hz(35_000_000),
285 MdcClockDivider::Div26
286 );
287 }
288
289 #[test]
290 fn mdc_divider_boundary_60mhz() {
291 assert_eq!(
292 MdcClockDivider::from_sys_clock_hz(59_999_999),
293 MdcClockDivider::Div26
294 );
295 assert_eq!(
296 MdcClockDivider::from_sys_clock_hz(60_000_000),
297 MdcClockDivider::Div42
298 );
299 }
300
301 #[test]
302 fn mdc_divider_boundary_100mhz() {
303 assert_eq!(
304 MdcClockDivider::from_sys_clock_hz(99_999_999),
305 MdcClockDivider::Div42
306 );
307 assert_eq!(
308 MdcClockDivider::from_sys_clock_hz(100_000_000),
309 MdcClockDivider::Div62
310 );
311 }
312
313 #[test]
314 fn mdc_divider_boundary_150mhz() {
315 assert_eq!(
316 MdcClockDivider::from_sys_clock_hz(149_999_999),
317 MdcClockDivider::Div62
318 );
319 assert_eq!(
320 MdcClockDivider::from_sys_clock_hz(150_000_000),
321 MdcClockDivider::Div102
322 );
323 }
324
325 #[test]
326 fn mdc_divider_boundary_250mhz() {
327 assert_eq!(
328 MdcClockDivider::from_sys_clock_hz(249_999_999),
329 MdcClockDivider::Div102
330 );
331 assert_eq!(
332 MdcClockDivider::from_sys_clock_hz(250_000_000),
333 MdcClockDivider::Div124
334 );
335 }
336
337 #[test]
340 fn build_mii_addr_read() {
341 let mdio = EspMdio::new(); let addr = mdio.build_mii_addr(1, 0, false);
343 assert_eq!(addr, 0x0800 | GMACMIIADDR_GB);
349 }
350
351 #[test]
352 fn build_mii_addr_write() {
353 let mdio = EspMdio::new();
354 let addr = mdio.build_mii_addr(1, 0, true);
355 assert_eq!(addr, 0x0800 | GMACMIIADDR_GW | GMACMIIADDR_GB);
357 }
358
359 #[test]
360 fn build_mii_addr_phy_and_reg() {
361 let mdio = EspMdio::new();
362 let addr = mdio.build_mii_addr(31, 31, false);
363 assert_eq!(addr & GMACMIIADDR_PA_MASK, 31 << 11);
366 assert_eq!(addr & GMACMIIADDR_GR_MASK, 31 << 6);
367 }
368
369 #[test]
370 fn build_mii_addr_clock_divider() {
371 let mdio = EspMdio::with_clock_divider(MdcClockDivider::Div102);
372 let addr = mdio.build_mii_addr(0, 0, false);
373 let cr_field = (addr & GMACMIIADDR_CR_MASK) >> GMACMIIADDR_CR_SHIFT;
375 assert_eq!(cr_field, 4);
376 }
377
378 #[test]
383 fn max_phy_addr_constant() {
384 assert_eq!(MAX_PHY_ADDR, 31);
385 }
386
387 #[test]
388 fn max_reg_addr_constant() {
389 assert_eq!(MAX_REG_ADDR, 31);
390 }
391}