Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

net: phy: realtek: fix RTL8211F wake-on-lan support

Implement Wake-on-Lan for RTL8211F correctly. The existing
implementation has multiple issues:

1. It assumes that Wake-on-Lan can always be used, whether or not the
interrupt is wired, and whether or not the interrupt is capable of
waking the system. This breaks the ability for MAC drivers to detect
whether the PHY WoL is functional.
2. switching the interrupt pin in the .set_wol() method to PMEB mode
immediately silences link-state interrupts, which breaks phylib
when interrupts are being used rather than polling mode.
3. the code claiming to "reset WOL status" was doing nothing of the
sort. Bit 15 in page 0xd8a register 17 controls WoL reset, and
needs to be pulsed low to reset the WoL state. This bit was always
written as '1', resulting in no reset.
4. not resetting WoL state results in the PMEB pin remaining asserted,
which in turn leads to an interrupt storm. Only resetting the WoL
state in .set_wol() is not sufficient.
5. PMEB mode does not allow software detection of the wake-up event as
there is no status bit to indicate we received the WoL packet.
6. across reboots of at least the Jetson Xavier NX system, the WoL
configuration is preserved.

Fix all of these issues by essentially rewriting the support. We:
1. clear the WoL event enable register at probe time.
2. detect whether we can support wake-up by having a valid interrupt,
and the "wakeup-source" property in DT. If we can, then we mark
the MDIO device as wakeup capable, and associate the interrupt
with the wakeup source.
3. arrange for the get_wol() and set_wol() implementations to handle
the case where the MDIO device has not been marked as wakeup
capable (thereby returning no WoL support, and refusing to enable
WoL support.)
4. avoid switching to PMEB mode, instead using INTB mode with the
interrupt enable, reconfiguring the interrupt enables at suspend
time, and restoring their original state at resume time (we track
the state of the interrupt enable register in .config_intr()
register.)
5. move WoL reset from .set_wol() to the suspend function to ensure
that WoL state is cleared prior to suspend. This is necessary
after the PME interrupt has been enabled as a second WoL packet
will not re-raise a previously cleared PME interrupt.
6. when a PME interrupt (for wakeup) is asserted, pass this to the
PM wakeup so it knows which device woke the system.

This fixes WoL support in the Realtek RTL8211F driver when used on the
nVidia Jetson Xavier NX platform, and needs to be applied before stmmac
patches which allow these platforms to forward the ethtool WoL commands
to the Realtek PHY.

Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Link: https://patch.msgid.link/E1um8Ld-008jxD-Mc@rmk-PC.armlinux.org.uk
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

Russell King (Oracle) and committed by
Jakub Kicinski
b826bf79 bab3ce40

+140 -32
+140 -32
drivers/net/phy/realtek/realtek_main.c
··· 10 10 #include <linux/bitops.h> 11 11 #include <linux/of.h> 12 12 #include <linux/phy.h> 13 + #include <linux/pm_wakeirq.h> 13 14 #include <linux/netdevice.h> 14 15 #include <linux/module.h> 15 16 #include <linux/delay.h> ··· 32 31 #define RTL821x_INER 0x12 33 32 #define RTL8211B_INER_INIT 0x6400 34 33 #define RTL8211E_INER_LINK_STATUS BIT(10) 34 + #define RTL8211F_INER_PME BIT(7) 35 35 #define RTL8211F_INER_LINK_STATUS BIT(4) 36 36 37 37 #define RTL821x_INSR 0x13 ··· 98 96 #define RTL8211F_RXCR 0x15 99 97 #define RTL8211F_RX_DELAY BIT(3) 100 98 101 - /* RTL8211F WOL interrupt configuration */ 102 - #define RTL8211F_INTBCR_PAGE 0xd40 103 - #define RTL8211F_INTBCR 0x16 104 - #define RTL8211F_INTBCR_INTB_PMEB BIT(5) 105 - 106 99 /* RTL8211F WOL settings */ 107 - #define RTL8211F_WOL_SETTINGS_PAGE 0xd8a 100 + #define RTL8211F_WOL_PAGE 0xd8a 108 101 #define RTL8211F_WOL_SETTINGS_EVENTS 16 109 102 #define RTL8211F_WOL_EVENT_MAGIC BIT(12) 110 - #define RTL8211F_WOL_SETTINGS_STATUS 17 111 - #define RTL8211F_WOL_STATUS_RESET (BIT(15) | 0x1fff) 103 + #define RTL8211F_WOL_RST_RMSQ 17 104 + #define RTL8211F_WOL_RG_RSTB BIT(15) 105 + #define RTL8211F_WOL_RMSQ 0x1fff 112 106 113 107 /* RTL8211F Unique phyiscal and multicast address (WOL) */ 114 108 #define RTL8211F_PHYSICAL_ADDR_PAGE 0xd8c ··· 170 172 u16 phycr2; 171 173 bool has_phycr2; 172 174 struct clk *clk; 173 - u32 saved_wolopts; 175 + /* rtl8211f */ 176 + u16 iner; 174 177 }; 175 178 176 179 static int rtl821x_read_page(struct phy_device *phydev) ··· 252 253 phydev->priv = priv; 253 254 254 255 return 0; 256 + } 257 + 258 + static int rtl8211f_probe(struct phy_device *phydev) 259 + { 260 + struct device *dev = &phydev->mdio.dev; 261 + int ret; 262 + 263 + ret = rtl821x_probe(phydev); 264 + if (ret < 0) 265 + return ret; 266 + 267 + /* Disable all PME events */ 268 + ret = phy_write_paged(phydev, RTL8211F_WOL_PAGE, 269 + RTL8211F_WOL_SETTINGS_EVENTS, 0); 270 + if (ret < 0) 271 + return ret; 272 + 273 + /* Mark this PHY as wakeup capable and register the interrupt as a 274 + * wakeup IRQ if the PHY is marked as a wakeup source in firmware, 275 + * and the interrupt is valid. 276 + */ 277 + if (device_property_read_bool(dev, "wakeup-source") && 278 + phy_interrupt_is_valid(phydev)) { 279 + device_set_wakeup_capable(dev, true); 280 + devm_pm_set_wake_irq(dev, phydev->irq); 281 + } 282 + 283 + return ret; 255 284 } 256 285 257 286 static int rtl8201_ack_interrupt(struct phy_device *phydev) ··· 379 352 380 353 static int rtl8211f_config_intr(struct phy_device *phydev) 381 354 { 355 + struct rtl821x_priv *priv = phydev->priv; 382 356 u16 val; 383 357 int err; 384 358 ··· 390 362 391 363 val = RTL8211F_INER_LINK_STATUS; 392 364 err = phy_write_paged(phydev, 0xa42, RTL821x_INER, val); 365 + if (err == 0) 366 + priv->iner = val; 393 367 } else { 394 - val = 0; 368 + priv->iner = val = 0; 395 369 err = phy_write_paged(phydev, 0xa42, RTL821x_INER, val); 396 370 if (err) 397 371 return err; ··· 456 426 return IRQ_NONE; 457 427 } 458 428 459 - if (!(irq_status & RTL8211F_INER_LINK_STATUS)) 460 - return IRQ_NONE; 429 + if (irq_status & RTL8211F_INER_LINK_STATUS) { 430 + phy_trigger_machine(phydev); 431 + return IRQ_HANDLED; 432 + } 461 433 462 - phy_trigger_machine(phydev); 434 + if (irq_status & RTL8211F_INER_PME) { 435 + pm_wakeup_event(&phydev->mdio.dev, 0); 436 + return IRQ_HANDLED; 437 + } 463 438 464 - return IRQ_HANDLED; 439 + return IRQ_NONE; 465 440 } 466 441 467 442 static void rtl8211f_get_wol(struct phy_device *dev, struct ethtool_wolinfo *wol) 468 443 { 469 444 int wol_events; 470 445 446 + /* If the PHY is not capable of waking the system, then WoL can not 447 + * be supported. 448 + */ 449 + if (!device_can_wakeup(&dev->mdio.dev)) { 450 + wol->supported = 0; 451 + return; 452 + } 453 + 471 454 wol->supported = WAKE_MAGIC; 472 455 473 - wol_events = phy_read_paged(dev, RTL8211F_WOL_SETTINGS_PAGE, RTL8211F_WOL_SETTINGS_EVENTS); 456 + wol_events = phy_read_paged(dev, RTL8211F_WOL_PAGE, RTL8211F_WOL_SETTINGS_EVENTS); 474 457 if (wol_events < 0) 475 458 return; 476 459 ··· 496 453 const u8 *mac_addr = dev->attached_dev->dev_addr; 497 454 int oldpage; 498 455 456 + if (!device_can_wakeup(&dev->mdio.dev)) 457 + return -EOPNOTSUPP; 458 + 499 459 oldpage = phy_save_page(dev); 500 460 if (oldpage < 0) 501 461 goto err; ··· 510 464 __phy_write(dev, RTL8211F_PHYSICAL_ADDR_WORD1, mac_addr[3] << 8 | (mac_addr[2])); 511 465 __phy_write(dev, RTL8211F_PHYSICAL_ADDR_WORD2, mac_addr[5] << 8 | (mac_addr[4])); 512 466 513 - /* Enable magic packet matching and reset WOL status */ 514 - rtl821x_write_page(dev, RTL8211F_WOL_SETTINGS_PAGE); 467 + /* Enable magic packet matching */ 468 + rtl821x_write_page(dev, RTL8211F_WOL_PAGE); 515 469 __phy_write(dev, RTL8211F_WOL_SETTINGS_EVENTS, RTL8211F_WOL_EVENT_MAGIC); 516 - __phy_write(dev, RTL8211F_WOL_SETTINGS_STATUS, RTL8211F_WOL_STATUS_RESET); 517 - 518 - /* Enable the WOL interrupt */ 519 - rtl821x_write_page(dev, RTL8211F_INTBCR_PAGE); 520 - __phy_set_bits(dev, RTL8211F_INTBCR, RTL8211F_INTBCR_INTB_PMEB); 470 + /* Set the maximum packet size, and assert WoL reset */ 471 + __phy_write(dev, RTL8211F_WOL_RST_RMSQ, RTL8211F_WOL_RMSQ); 521 472 } else { 522 - /* Disable the WOL interrupt */ 523 - rtl821x_write_page(dev, RTL8211F_INTBCR_PAGE); 524 - __phy_clear_bits(dev, RTL8211F_INTBCR, RTL8211F_INTBCR_INTB_PMEB); 525 - 526 - /* Disable magic packet matching and reset WOL status */ 527 - rtl821x_write_page(dev, RTL8211F_WOL_SETTINGS_PAGE); 473 + /* Disable magic packet matching */ 474 + rtl821x_write_page(dev, RTL8211F_WOL_PAGE); 528 475 __phy_write(dev, RTL8211F_WOL_SETTINGS_EVENTS, 0); 529 - __phy_write(dev, RTL8211F_WOL_SETTINGS_STATUS, RTL8211F_WOL_STATUS_RESET); 476 + 477 + /* Place WoL in reset */ 478 + __phy_clear_bits(dev, RTL8211F_WOL_RST_RMSQ, 479 + RTL8211F_WOL_RG_RSTB); 530 480 } 481 + 482 + device_set_wakeup_enable(&dev->mdio.dev, !!(wol->wolopts & WAKE_MAGIC)); 531 483 532 484 err: 533 485 return phy_restore_page(dev, oldpage, 0); ··· 672 628 return ret; 673 629 } 674 630 631 + static int rtl8211f_suspend(struct phy_device *phydev) 632 + { 633 + u16 wol_rst; 634 + int ret; 635 + 636 + ret = rtl821x_suspend(phydev); 637 + if (ret < 0) 638 + return ret; 639 + 640 + /* If a PME event is enabled, then configure the interrupt for 641 + * PME events only, disabling link interrupt. We avoid switching 642 + * to PMEB mode as we don't have a status bit for that. 643 + */ 644 + if (device_may_wakeup(&phydev->mdio.dev)) { 645 + ret = phy_write_paged(phydev, 0xa42, RTL821x_INER, 646 + RTL8211F_INER_PME); 647 + if (ret < 0) 648 + goto err; 649 + 650 + /* Read the INSR to clear any pending interrupt */ 651 + phy_read_paged(phydev, RTL8211F_INSR_PAGE, RTL8211F_INSR); 652 + 653 + /* Reset the WoL to ensure that an event is picked up. 654 + * Unless we do this, even if we receive another packet, 655 + * we may not have a PME interrupt raised. 656 + */ 657 + ret = phy_read_paged(phydev, RTL8211F_WOL_PAGE, 658 + RTL8211F_WOL_RST_RMSQ); 659 + if (ret < 0) 660 + goto err; 661 + 662 + wol_rst = ret & ~RTL8211F_WOL_RG_RSTB; 663 + ret = phy_write_paged(phydev, RTL8211F_WOL_PAGE, 664 + RTL8211F_WOL_RST_RMSQ, wol_rst); 665 + if (ret < 0) 666 + goto err; 667 + 668 + wol_rst |= RTL8211F_WOL_RG_RSTB; 669 + ret = phy_write_paged(phydev, RTL8211F_WOL_PAGE, 670 + RTL8211F_WOL_RST_RMSQ, wol_rst); 671 + } 672 + 673 + err: 674 + return ret; 675 + } 676 + 675 677 static int rtl821x_resume(struct phy_device *phydev) 676 678 { 677 679 struct rtl821x_priv *priv = phydev->priv; ··· 733 643 msleep(20); 734 644 735 645 return 0; 646 + } 647 + 648 + static int rtl8211f_resume(struct phy_device *phydev) 649 + { 650 + struct rtl821x_priv *priv = phydev->priv; 651 + int ret; 652 + 653 + ret = rtl821x_resume(phydev); 654 + if (ret < 0) 655 + return ret; 656 + 657 + /* If the device was programmed for a PME event, restore the interrupt 658 + * enable so phylib can receive link state interrupts. 659 + */ 660 + if (device_may_wakeup(&phydev->mdio.dev)) 661 + ret = phy_write_paged(phydev, 0xa42, RTL821x_INER, priv->iner); 662 + 663 + return ret; 736 664 } 737 665 738 666 static int rtl8211x_led_hw_is_supported(struct phy_device *phydev, u8 index, ··· 1735 1627 }, { 1736 1628 PHY_ID_MATCH_EXACT(0x001cc916), 1737 1629 .name = "RTL8211F Gigabit Ethernet", 1738 - .probe = rtl821x_probe, 1630 + .probe = rtl8211f_probe, 1739 1631 .config_init = &rtl8211f_config_init, 1740 1632 .read_status = rtlgen_read_status, 1741 1633 .config_intr = &rtl8211f_config_intr, 1742 1634 .handle_interrupt = rtl8211f_handle_interrupt, 1743 1635 .set_wol = rtl8211f_set_wol, 1744 1636 .get_wol = rtl8211f_get_wol, 1745 - .suspend = rtl821x_suspend, 1746 - .resume = rtl821x_resume, 1637 + .suspend = rtl8211f_suspend, 1638 + .resume = rtl8211f_resume, 1747 1639 .read_page = rtl821x_read_page, 1748 1640 .write_page = rtl821x_write_page, 1749 1641 .flags = PHY_ALWAYS_CALL_SUSPEND,