1#![no_std]
60
61mod regs;
62
63use eth_mdio_phy::ieee802_3;
64use eth_mdio_phy::{Duplex, LinkStatus, MdioBus, PhyCapabilities, PhyDriver, PhyError, Speed};
65
66pub struct PhyLan87xx {
68 addr: u8,
69 link_up: bool,
70}
71
72impl PhyLan87xx {
73 pub fn new(addr: u8) -> Self {
75 Self {
76 addr,
77 link_up: false,
78 }
79 }
80
81 fn parse_pscsr(pscsr_val: u16) -> Option<LinkStatus> {
85 match pscsr_val & regs::pscsr::SPEED_DUPLEX_MASK {
86 regs::pscsr::SPEED_10_HD => Some(LinkStatus::new(Speed::Mbps10, Duplex::Half)),
87 regs::pscsr::SPEED_10_FD => Some(LinkStatus::new(Speed::Mbps10, Duplex::Full)),
88 regs::pscsr::SPEED_100_HD => Some(LinkStatus::new(Speed::Mbps100, Duplex::Half)),
89 regs::pscsr::SPEED_100_FD => Some(LinkStatus::new(Speed::Mbps100, Duplex::Full)),
90 _ => None,
91 }
92 }
93}
94
95impl PhyDriver for PhyLan87xx {
96 fn phy_addr(&self) -> u8 {
97 self.addr
98 }
99
100 fn init<M: MdioBus>(&mut self, mdio: &mut M) -> Result<(), PhyError<M::Error>> {
101 let cleared = ieee802_3::soft_reset(mdio, self.addr, 500).map_err(PhyError::Mdio)?;
103 if !cleared {
104 return Err(PhyError::ResetTimeout);
105 }
106
107 let id = ieee802_3::read_phy_id(mdio, self.addr).map_err(PhyError::Mdio)?;
109 if id & regs::PHY_OUI_MASK != regs::PHY_OUI {
110 return Err(PhyError::UnsupportedChip { id });
111 }
112
113 let mcsr = mdio
115 .read(self.addr, regs::mcsr::ADDR)
116 .map_err(PhyError::Mdio)?;
117 mdio.write(self.addr, regs::mcsr::ADDR, mcsr & !regs::mcsr::EDPD_EN)
118 .map_err(PhyError::Mdio)?;
119
120 let anar = ieee802_3::anar::TX_FD
132 | ieee802_3::anar::TX_HD
133 | ieee802_3::anar::T10_FD
134 | ieee802_3::anar::T10_HD
135 | ieee802_3::anar::SELECTOR_IEEE802_3;
136 mdio.write(self.addr, ieee802_3::regs::ANAR, anar)
137 .map_err(PhyError::Mdio)?;
138
139 ieee802_3::enable_auto_negotiation(mdio, self.addr).map_err(PhyError::Mdio)?;
141
142 self.link_up = false;
143 Ok(())
144 }
145
146 fn poll_link<M: MdioBus>(
147 &mut self,
148 mdio: &mut M,
149 ) -> Result<Option<LinkStatus>, PhyError<M::Error>> {
150 let up = ieee802_3::is_link_up(mdio, self.addr).map_err(PhyError::Mdio)?;
151 if !up {
152 self.link_up = false;
153 return Ok(None);
154 }
155
156 let bmcr = mdio
160 .read(self.addr, ieee802_3::regs::BMCR)
161 .map_err(PhyError::Mdio)?;
162
163 let status = if bmcr & ieee802_3::bmcr::AN_ENABLE != 0 {
164 let pscsr = mdio
171 .read(self.addr, regs::pscsr::ADDR)
172 .map_err(PhyError::Mdio)?;
173 if pscsr & regs::pscsr::AUTODONE == 0 {
174 self.link_up = false;
175 return Ok(None);
176 }
177 Self::parse_pscsr(pscsr)
178 } else {
179 let speed = if bmcr & ieee802_3::bmcr::SPEED_100 != 0 {
185 Speed::Mbps100
186 } else {
187 Speed::Mbps10
188 };
189 let duplex = if bmcr & ieee802_3::bmcr::DUPLEX_FULL != 0 {
190 Duplex::Full
191 } else {
192 Duplex::Half
193 };
194 Some(LinkStatus::new(speed, duplex))
195 };
196
197 self.link_up = status.is_some();
198 Ok(status)
199 }
200
201 fn capabilities<M: MdioBus>(
202 &self,
203 mdio: &mut M,
204 ) -> Result<PhyCapabilities, PhyError<M::Error>> {
205 ieee802_3::read_capabilities(mdio, self.addr).map_err(PhyError::Mdio)
206 }
207
208 fn phy_id<M: MdioBus>(&self, mdio: &mut M) -> Result<u32, PhyError<M::Error>> {
209 ieee802_3::read_phy_id(mdio, self.addr).map_err(PhyError::Mdio)
210 }
211}
212
213pub struct PhyLan87xxWithReset<P: embedded_hal::digital::OutputPin> {
218 inner: PhyLan87xx,
219 reset_pin: P,
220}
221
222impl<P: embedded_hal::digital::OutputPin> PhyLan87xxWithReset<P> {
223 pub fn new(addr: u8, pin: P) -> Self {
225 Self {
226 inner: PhyLan87xx::new(addr),
227 reset_pin: pin,
228 }
229 }
230
231 pub fn hardware_reset<D: embedded_hal::delay::DelayNs>(
237 &mut self,
238 delay: &mut D,
239 ) -> Result<(), P::Error> {
240 self.reset_pin.set_low()?;
241 delay.delay_ms(2);
242 self.reset_pin.set_high()?;
243 delay.delay_ms(25);
244 Ok(())
245 }
246}
247
248impl<P: embedded_hal::digital::OutputPin> PhyDriver for PhyLan87xxWithReset<P> {
249 fn phy_addr(&self) -> u8 {
250 self.inner.phy_addr()
251 }
252
253 fn init<M: MdioBus>(&mut self, mdio: &mut M) -> Result<(), PhyError<M::Error>> {
254 self.inner.init(mdio)
255 }
256
257 fn poll_link<M: MdioBus>(
258 &mut self,
259 mdio: &mut M,
260 ) -> Result<Option<LinkStatus>, PhyError<M::Error>> {
261 self.inner.poll_link(mdio)
262 }
263
264 fn capabilities<M: MdioBus>(
265 &self,
266 mdio: &mut M,
267 ) -> Result<PhyCapabilities, PhyError<M::Error>> {
268 self.inner.capabilities(mdio)
269 }
270
271 fn phy_id<M: MdioBus>(&self, mdio: &mut M) -> Result<u32, PhyError<M::Error>> {
272 self.inner.phy_id(mdio)
273 }
274}
275
276#[cfg(test)]
277mod tests {
278 extern crate alloc;
279
280 use super::*;
281 use alloc::vec;
282 use alloc::vec::Vec;
283 use eth_mdio_phy::ieee802_3::{bmcr, bmsr};
284
285 #[derive(Debug, PartialEq)]
288 struct MockError;
289
290 struct MockMdio {
291 reads: Vec<u16>,
292 read_idx: usize,
293 writes: Vec<(u8, u8, u16)>,
294 fail_at: Option<usize>,
295 call_count: usize,
296 }
297
298 impl MockMdio {
299 fn new(reads: Vec<u16>) -> Self {
300 Self {
301 reads,
302 read_idx: 0,
303 writes: Vec::new(),
304 fail_at: None,
305 call_count: 0,
306 }
307 }
308
309 fn with_failure(reads: Vec<u16>, fail_at: usize) -> Self {
310 Self {
311 reads,
312 read_idx: 0,
313 writes: Vec::new(),
314 fail_at: Some(fail_at),
315 call_count: 0,
316 }
317 }
318 }
319
320 impl MdioBus for MockMdio {
321 type Error = MockError;
322
323 fn read(&mut self, _phy_addr: u8, _reg_addr: u8) -> Result<u16, Self::Error> {
324 if self.fail_at == Some(self.call_count) {
325 self.call_count += 1;
326 return Err(MockError);
327 }
328 self.call_count += 1;
329 let val = *self
330 .reads
331 .get(self.read_idx)
332 .expect("MockMdio: reads vector exhausted — test needs more entries");
333 self.read_idx += 1;
334 Ok(val)
335 }
336
337 fn write(&mut self, phy_addr: u8, reg_addr: u8, value: u16) -> Result<(), Self::Error> {
338 if self.fail_at == Some(self.call_count) {
339 self.call_count += 1;
340 return Err(MockError);
341 }
342 self.call_count += 1;
343 self.writes.push((phy_addr, reg_addr, value));
344 Ok(())
345 }
346 }
347
348 #[test]
351 fn new_sets_address() {
352 let phy = PhyLan87xx::new(3);
353 assert_eq!(phy.phy_addr(), 3);
354 }
355
356 #[test]
357 fn new_link_starts_down() {
358 let phy = PhyLan87xx::new(0);
359 assert!(!phy.link_up);
360 }
361
362 #[test]
365 fn init_success() {
366 let mut mdio = MockMdio::new(vec![
373 0x0000, 0x0007, 0xC0F0, regs::mcsr::EDPD_EN, 0x0000, ]);
379 let mut phy = PhyLan87xx::new(1);
380 phy.init(&mut mdio).unwrap();
381 }
382
383 #[test]
384 fn init_rejects_wrong_phy_id() {
385 let mut mdio = MockMdio::new(vec![
387 0x0000, 0x0022, 0x1619, ]);
391 let mut phy = PhyLan87xx::new(1);
392 let err = phy.init(&mut mdio).unwrap_err();
393 match err {
394 PhyError::UnsupportedChip { id } => assert_eq!(id, 0x0022_1619),
395 _ => panic!("expected UnsupportedChip, got {:?}", err),
396 }
397 }
398
399 #[test]
400 fn init_reset_timeout() {
401 let mut mdio = MockMdio::new(vec![bmcr::RESET; 1000]);
404 let mut phy = PhyLan87xx::new(1);
405 let err = phy.init(&mut mdio).unwrap_err();
406 match err {
407 PhyError::ResetTimeout => {}
408 _ => panic!("expected ResetTimeout, got {:?}", err),
409 }
410 }
411
412 #[test]
413 fn init_writes_anar_standard_advertisement() {
414 let mut mdio = MockMdio::new(vec![
418 0x0000, 0x0007, 0xC0F0, regs::mcsr::EDPD_EN, 0x0000, ]);
424 let mut phy = PhyLan87xx::new(1);
425 phy.init(&mut mdio).unwrap();
426
427 let anar_idx = mdio
428 .writes
429 .iter()
430 .position(|&(_, reg, _)| reg == eth_mdio_phy::ieee802_3::regs::ANAR)
431 .expect("expected a write to ANAR");
432 let expected = eth_mdio_phy::ieee802_3::anar::TX_FD
433 | eth_mdio_phy::ieee802_3::anar::TX_HD
434 | eth_mdio_phy::ieee802_3::anar::T10_FD
435 | eth_mdio_phy::ieee802_3::anar::T10_HD
436 | eth_mdio_phy::ieee802_3::anar::SELECTOR_IEEE802_3;
437 assert_eq!(
438 mdio.writes[anar_idx].2, expected,
439 "ANAR must advertise standard 10/100 full+half + 802.3 selector"
440 );
441
442 let bmcr_an_idx = mdio
448 .writes
449 .iter()
450 .rposition(|&(_, reg, val)| {
451 reg == eth_mdio_phy::ieee802_3::regs::BMCR
452 && (val
453 & (eth_mdio_phy::ieee802_3::bmcr::AN_ENABLE
454 | eth_mdio_phy::ieee802_3::bmcr::AN_RESTART))
455 != 0
456 })
457 .expect("expected a BMCR write that enables/restarts auto-neg");
458 assert!(
459 anar_idx < bmcr_an_idx,
460 "ANAR (write #{anar_idx}) must be programmed BEFORE BMCR.AN_ENABLE/AN_RESTART (write #{bmcr_an_idx})",
461 );
462
463 let pre_anar_an_restart = mdio.writes[..anar_idx].iter().any(|&(_, reg, val)| {
471 reg == eth_mdio_phy::ieee802_3::regs::BMCR
472 && (val
473 & (eth_mdio_phy::ieee802_3::bmcr::AN_ENABLE
474 | eth_mdio_phy::ieee802_3::bmcr::AN_RESTART))
475 != 0
476 });
477 assert!(
478 !pre_anar_an_restart,
479 "BMCR.AN_ENABLE/AN_RESTART must not be issued before the ANAR write",
480 );
481 }
482
483 #[test]
484 fn init_disables_edpd() {
485 let mcsr_initial: u16 = regs::mcsr::EDPD_EN | regs::mcsr::ENERGYON;
487 let mut mdio = MockMdio::new(vec![
488 0x0000, 0x0007, 0xC0F0, mcsr_initial, 0x0000, ]);
494 let mut phy = PhyLan87xx::new(1);
495 phy.init(&mut mdio).unwrap();
496
497 let mcsr_write = mdio
499 .writes
500 .iter()
501 .find(|&&(_, reg, _)| reg == regs::mcsr::ADDR)
502 .expect("expected a write to MCSR");
503 assert_eq!(
505 mcsr_write.2 & regs::mcsr::EDPD_EN,
506 0,
507 "EDPD_EN should be cleared"
508 );
509 assert_ne!(
510 mcsr_write.2 & regs::mcsr::ENERGYON,
511 0,
512 "other MCSR bits should be preserved"
513 );
514 }
515
516 #[test]
517 fn init_mdio_error_propagates() {
518 let mut mdio = MockMdio::with_failure(vec![], 0);
520 let mut phy = PhyLan87xx::new(1);
521 let err = phy.init(&mut mdio).unwrap_err();
522 match err {
523 PhyError::Mdio(MockError) => {}
524 _ => panic!("expected Mdio error, got {:?}", err),
525 }
526 }
527
528 #[test]
531 fn poll_link_down() {
532 let mut mdio = MockMdio::new(vec![0x0000]);
534 let mut phy = PhyLan87xx::new(1);
535 let result = phy.poll_link(&mut mdio).unwrap();
536 assert!(result.is_none());
537 assert!(!phy.link_up);
538 }
539
540 #[test]
541 fn poll_link_100_full() {
542 let mut mdio = MockMdio::new(vec![
543 bmsr::LINK_STATUS, ieee802_3::bmcr::AN_ENABLE, regs::pscsr::AUTODONE | regs::pscsr::SPEED_100_FD, ]);
547 let mut phy = PhyLan87xx::new(1);
548 let result = phy.poll_link(&mut mdio).unwrap();
549 assert_eq!(result, Some(LinkStatus::new(Speed::Mbps100, Duplex::Full)));
550 assert!(phy.link_up);
551 }
552
553 #[test]
554 fn poll_link_10_half() {
555 let mut mdio = MockMdio::new(vec![
556 bmsr::LINK_STATUS,
557 ieee802_3::bmcr::AN_ENABLE,
558 regs::pscsr::AUTODONE | regs::pscsr::SPEED_10_HD,
559 ]);
560 let mut phy = PhyLan87xx::new(1);
561 let result = phy.poll_link(&mut mdio).unwrap();
562 assert_eq!(result, Some(LinkStatus::new(Speed::Mbps10, Duplex::Half)));
563 }
564
565 #[test]
566 fn poll_link_100_half() {
567 let mut mdio = MockMdio::new(vec![
568 bmsr::LINK_STATUS,
569 ieee802_3::bmcr::AN_ENABLE,
570 regs::pscsr::AUTODONE | regs::pscsr::SPEED_100_HD,
571 ]);
572 let mut phy = PhyLan87xx::new(1);
573 let result = phy.poll_link(&mut mdio).unwrap();
574 assert_eq!(result, Some(LinkStatus::new(Speed::Mbps100, Duplex::Half)));
575 }
576
577 #[test]
578 fn poll_link_10_full() {
579 let mut mdio = MockMdio::new(vec![
580 bmsr::LINK_STATUS,
581 ieee802_3::bmcr::AN_ENABLE,
582 regs::pscsr::AUTODONE | regs::pscsr::SPEED_10_FD,
583 ]);
584 let mut phy = PhyLan87xx::new(1);
585 let result = phy.poll_link(&mut mdio).unwrap();
586 assert_eq!(result, Some(LinkStatus::new(Speed::Mbps10, Duplex::Full)));
587 }
588
589 #[test]
590 fn poll_link_unknown_speed_returns_none() {
591 let mut mdio = MockMdio::new(vec![
593 bmsr::LINK_STATUS,
594 ieee802_3::bmcr::AN_ENABLE,
595 regs::pscsr::AUTODONE, ]);
597 let mut phy = PhyLan87xx::new(1);
598 let result = phy.poll_link(&mut mdio).unwrap();
599 assert!(result.is_none());
600 assert!(!phy.link_up);
601 }
602
603 #[test]
604 fn poll_link_returns_none_when_autodone_clear() {
605 let mut mdio = MockMdio::new(vec![
610 bmsr::LINK_STATUS,
611 ieee802_3::bmcr::AN_ENABLE,
612 regs::pscsr::SPEED_100_FD, ]);
614 let mut phy = PhyLan87xx::new(1);
615 let result = phy.poll_link(&mut mdio).unwrap();
616 assert!(
617 result.is_none(),
618 "must wait for AUTODONE before decoding speed"
619 );
620 assert!(!phy.link_up);
621 }
622
623 #[test]
624 fn poll_link_forced_100_full() {
625 let mut mdio = MockMdio::new(vec![
629 bmsr::LINK_STATUS,
630 ieee802_3::bmcr::SPEED_100 | ieee802_3::bmcr::DUPLEX_FULL,
631 ]);
632 let mut phy = PhyLan87xx::new(1);
633 let result = phy.poll_link(&mut mdio).unwrap();
634 assert_eq!(result, Some(LinkStatus::new(Speed::Mbps100, Duplex::Full)));
635 assert!(phy.link_up);
636 }
637
638 #[test]
639 fn poll_link_forced_10_half() {
640 let mut mdio = MockMdio::new(vec![bmsr::LINK_STATUS, 0x0000]);
642 let mut phy = PhyLan87xx::new(1);
643 let result = phy.poll_link(&mut mdio).unwrap();
644 assert_eq!(result, Some(LinkStatus::new(Speed::Mbps10, Duplex::Half)));
645 }
646
647 #[test]
648 fn poll_link_forced_skips_pscsr_read() {
649 let mut mdio = MockMdio::new(vec![
653 bmsr::LINK_STATUS,
654 ieee802_3::bmcr::SPEED_100, ]);
656 let mut phy = PhyLan87xx::new(1);
657 let result = phy.poll_link(&mut mdio).unwrap();
658 assert_eq!(result, Some(LinkStatus::new(Speed::Mbps100, Duplex::Half)));
659 }
660
661 #[test]
662 fn poll_link_mdio_error() {
663 let mut mdio = MockMdio::with_failure(vec![], 0);
665 let mut phy = PhyLan87xx::new(1);
666 let err = phy.poll_link(&mut mdio).unwrap_err();
667 match err {
668 PhyError::Mdio(MockError) => {}
669 _ => panic!("expected Mdio error"),
670 }
671 }
672
673 #[test]
676 fn capabilities_reads_bmsr() {
677 let bmsr_val = bmsr::TX_FD_CAPABLE
678 | bmsr::TX_HD_CAPABLE
679 | bmsr::T10_FD_CAPABLE
680 | bmsr::T10_HD_CAPABLE
681 | bmsr::AN_ABILITY;
682 let mut mdio = MockMdio::new(vec![bmsr_val]);
683 let phy = PhyLan87xx::new(1);
684 let caps = phy.capabilities(&mut mdio).unwrap();
685 assert!(caps.speed_100_fd);
686 assert!(caps.speed_100_hd);
687 assert!(caps.speed_10_fd);
688 assert!(caps.speed_10_hd);
689 assert!(caps.auto_negotiation);
690 }
691
692 #[test]
695 fn phy_id_reads_registers() {
696 let mut mdio = MockMdio::new(vec![0x0007, 0xC0F0]);
697 let phy = PhyLan87xx::new(1);
698 let id = phy.phy_id(&mut mdio).unwrap();
699 assert_eq!(id, 0x0007_C0F0);
700 }
701
702 #[test]
705 fn parse_pscsr_all_modes() {
706 assert_eq!(
707 PhyLan87xx::parse_pscsr(regs::pscsr::SPEED_10_HD),
708 Some(LinkStatus::new(Speed::Mbps10, Duplex::Half))
709 );
710 assert_eq!(
711 PhyLan87xx::parse_pscsr(regs::pscsr::SPEED_10_FD),
712 Some(LinkStatus::new(Speed::Mbps10, Duplex::Full))
713 );
714 assert_eq!(
715 PhyLan87xx::parse_pscsr(regs::pscsr::SPEED_100_HD),
716 Some(LinkStatus::new(Speed::Mbps100, Duplex::Half))
717 );
718 assert_eq!(
719 PhyLan87xx::parse_pscsr(regs::pscsr::SPEED_100_FD),
720 Some(LinkStatus::new(Speed::Mbps100, Duplex::Full))
721 );
722 assert_eq!(PhyLan87xx::parse_pscsr(0x0000), None);
724 }
725
726 #[test]
727 fn parse_pscsr_ignores_other_bits() {
728 let val = regs::pscsr::SPEED_100_FD | regs::pscsr::AUTODONE | 0x0003 | 0x8000;
730 assert_eq!(
731 PhyLan87xx::parse_pscsr(val),
732 Some(LinkStatus::new(Speed::Mbps100, Duplex::Full))
733 );
734 }
735}