Skip to content

Commit 8baddaa

Browse files
ffainellidavem330
authored andcommitted
net: phy: broadcom: Add support for Wake-on-LAN
Add support for WAKE_UCAST, WAKE_MCAST, WAKE_BCAST, WAKE_MAGIC and WAKE_MAGICSECURE. This is only supported with the BCM54210E and compatible Ethernet PHYs. Using the in-band interrupt or an out of band GPIO interrupts are supported. Broadcom PHYs will generate a Wake-on-LAN level low interrupt on LED4 as soon as one of the supported patterns is being matched. That includes generating such an interrupt even if the PHY is operated during normal modes. If WAKE_UCAST is selected, this could lead to the LED4 interrupt firing up for every packet being received which is absolutely undesirable from a performance point of view. Because the Wake-on-LAN configuration can be set long before the system is actually put to sleep, we cannot have an interrupt service routine to clear on read the interrupt status register and ensure that new packet matches will be detected. It is desirable to enable the Wake-on-LAN interrupt as late as possible during the system suspend process such that we limit the number of interrupts to be handled by the system, but also conversely feed into the Linux's system suspend way of dealing with interrupts in and around the points of no return. Reviewed-by: Simon Horman <[email protected]> Signed-off-by: Florian Fainelli <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent a7e3448 commit 8baddaa

File tree

4 files changed

+395
-3
lines changed

4 files changed

+395
-3
lines changed

drivers/net/phy/bcm-phy-lib.c

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@
66
#include "bcm-phy-lib.h"
77
#include <linux/bitfield.h>
88
#include <linux/brcmphy.h>
9+
#include <linux/etherdevice.h>
910
#include <linux/export.h>
1011
#include <linux/mdio.h>
1112
#include <linux/module.h>
1213
#include <linux/phy.h>
1314
#include <linux/ethtool.h>
1415
#include <linux/ethtool_netlink.h>
16+
#include <linux/netdevice.h>
1517

1618
#define MII_BCM_CHANNEL_WIDTH 0x2000
1719
#define BCM_CL45VEN_EEE_ADV 0x3c
@@ -816,6 +818,216 @@ int bcm_phy_cable_test_get_status_rdb(struct phy_device *phydev,
816818
}
817819
EXPORT_SYMBOL_GPL(bcm_phy_cable_test_get_status_rdb);
818820

821+
#define BCM54XX_WOL_SUPPORTED_MASK (WAKE_UCAST | \
822+
WAKE_MCAST | \
823+
WAKE_BCAST | \
824+
WAKE_MAGIC | \
825+
WAKE_MAGICSECURE)
826+
827+
int bcm_phy_set_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol)
828+
{
829+
struct net_device *ndev = phydev->attached_dev;
830+
u8 da[ETH_ALEN], mask[ETH_ALEN];
831+
unsigned int i;
832+
u16 ctl;
833+
int ret;
834+
835+
/* Allow a MAC driver to play through its own Wake-on-LAN
836+
* implementation
837+
*/
838+
if (wol->wolopts & ~BCM54XX_WOL_SUPPORTED_MASK)
839+
return -EOPNOTSUPP;
840+
841+
/* The PHY supports passwords of 4, 6 and 8 bytes in size, but Linux's
842+
* ethtool only supports 6, for now.
843+
*/
844+
BUILD_BUG_ON(sizeof(wol->sopass) != ETH_ALEN);
845+
846+
/* Clear previous interrupts */
847+
ret = bcm_phy_read_exp(phydev, BCM54XX_WOL_INT_STATUS);
848+
if (ret < 0)
849+
return ret;
850+
851+
ret = bcm_phy_read_exp(phydev, BCM54XX_WOL_MAIN_CTL);
852+
if (ret < 0)
853+
return ret;
854+
855+
ctl = ret;
856+
857+
if (!wol->wolopts) {
858+
if (phy_interrupt_is_valid(phydev))
859+
disable_irq_wake(phydev->irq);
860+
861+
/* Leave all interrupts disabled */
862+
ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_INT_MASK,
863+
BCM54XX_WOL_ALL_INTRS);
864+
if (ret < 0)
865+
return ret;
866+
867+
/* Disable the global Wake-on-LAN enable bit */
868+
ctl &= ~BCM54XX_WOL_EN;
869+
870+
return bcm_phy_write_exp(phydev, BCM54XX_WOL_MAIN_CTL, ctl);
871+
}
872+
873+
/* Clear the previously configured mode and mask mode for Wake-on-LAN */
874+
ctl &= ~(BCM54XX_WOL_MODE_MASK << BCM54XX_WOL_MODE_SHIFT);
875+
ctl &= ~(BCM54XX_WOL_MASK_MODE_MASK << BCM54XX_WOL_MASK_MODE_SHIFT);
876+
ctl &= ~BCM54XX_WOL_DIR_PKT_EN;
877+
ctl &= ~(BCM54XX_WOL_SECKEY_OPT_MASK << BCM54XX_WOL_SECKEY_OPT_SHIFT);
878+
879+
/* When using WAKE_MAGIC, we program the magic pattern filter to match
880+
* the device's MAC address and we accept any MAC DA in the Ethernet
881+
* frame.
882+
*
883+
* When using WAKE_UCAST, WAKE_BCAST or WAKE_MCAST, we program the
884+
* following:
885+
* - WAKE_UCAST -> MAC DA is the device's MAC with a perfect match
886+
* - WAKE_MCAST -> MAC DA is X1:XX:XX:XX:XX:XX where XX is don't care
887+
* - WAKE_BCAST -> MAC DA is FF:FF:FF:FF:FF:FF with a perfect match
888+
*
889+
* Note that the Broadcast MAC DA is inherently going to match the
890+
* multicast pattern being matched.
891+
*/
892+
memset(mask, 0, sizeof(mask));
893+
894+
if (wol->wolopts & WAKE_MCAST) {
895+
memset(da, 0, sizeof(da));
896+
memset(mask, 0xff, sizeof(mask));
897+
da[0] = 0x01;
898+
mask[0] = ~da[0];
899+
} else {
900+
if (wol->wolopts & WAKE_UCAST) {
901+
ether_addr_copy(da, ndev->dev_addr);
902+
} else if (wol->wolopts & WAKE_BCAST) {
903+
eth_broadcast_addr(da);
904+
} else if (wol->wolopts & WAKE_MAGICSECURE) {
905+
ether_addr_copy(da, wol->sopass);
906+
} else if (wol->wolopts & WAKE_MAGIC) {
907+
memset(da, 0, sizeof(da));
908+
memset(mask, 0xff, sizeof(mask));
909+
}
910+
}
911+
912+
for (i = 0; i < ETH_ALEN / 2; i++) {
913+
if (wol->wolopts & (WAKE_MAGIC | WAKE_MAGICSECURE)) {
914+
ret = bcm_phy_write_exp(phydev,
915+
BCM54XX_WOL_MPD_DATA1(2 - i),
916+
ndev->dev_addr[i * 2] << 8 |
917+
ndev->dev_addr[i * 2 + 1]);
918+
if (ret < 0)
919+
return ret;
920+
}
921+
922+
ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_MPD_DATA2(2 - i),
923+
da[i * 2] << 8 | da[i * 2 + 1]);
924+
if (ret < 0)
925+
return ret;
926+
927+
ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_MASK(2 - i),
928+
mask[i * 2] << 8 | mask[i * 2 + 1]);
929+
if (ret)
930+
return ret;
931+
}
932+
933+
if (wol->wolopts & WAKE_MAGICSECURE) {
934+
ctl |= BCM54XX_WOL_SECKEY_OPT_6B <<
935+
BCM54XX_WOL_SECKEY_OPT_SHIFT;
936+
ctl |= BCM54XX_WOL_MODE_SINGLE_MPDSEC << BCM54XX_WOL_MODE_SHIFT;
937+
ctl |= BCM54XX_WOL_MASK_MODE_DA_FF <<
938+
BCM54XX_WOL_MASK_MODE_SHIFT;
939+
} else {
940+
if (wol->wolopts & WAKE_MAGIC)
941+
ctl |= BCM54XX_WOL_MODE_SINGLE_MPD;
942+
else
943+
ctl |= BCM54XX_WOL_DIR_PKT_EN;
944+
ctl |= BCM54XX_WOL_MASK_MODE_DA_ONLY <<
945+
BCM54XX_WOL_MASK_MODE_SHIFT;
946+
}
947+
948+
/* Globally enable Wake-on-LAN */
949+
ctl |= BCM54XX_WOL_EN | BCM54XX_WOL_CRC_CHK;
950+
951+
ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_MAIN_CTL, ctl);
952+
if (ret < 0)
953+
return ret;
954+
955+
/* Enable WOL interrupt on LED4 */
956+
ret = bcm_phy_read_exp(phydev, BCM54XX_TOP_MISC_LED_CTL);
957+
if (ret < 0)
958+
return ret;
959+
960+
ret |= BCM54XX_LED4_SEL_INTR;
961+
ret = bcm_phy_write_exp(phydev, BCM54XX_TOP_MISC_LED_CTL, ret);
962+
if (ret < 0)
963+
return ret;
964+
965+
/* Enable all Wake-on-LAN interrupt sources */
966+
ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_INT_MASK, 0);
967+
if (ret < 0)
968+
return ret;
969+
970+
if (phy_interrupt_is_valid(phydev))
971+
enable_irq_wake(phydev->irq);
972+
973+
return 0;
974+
}
975+
EXPORT_SYMBOL_GPL(bcm_phy_set_wol);
976+
977+
void bcm_phy_get_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol)
978+
{
979+
struct net_device *ndev = phydev->attached_dev;
980+
u8 da[ETH_ALEN];
981+
unsigned int i;
982+
int ret;
983+
u16 ctl;
984+
985+
wol->supported = BCM54XX_WOL_SUPPORTED_MASK;
986+
wol->wolopts = 0;
987+
988+
ret = bcm_phy_read_exp(phydev, BCM54XX_WOL_MAIN_CTL);
989+
if (ret < 0)
990+
return;
991+
992+
ctl = ret;
993+
994+
if (!(ctl & BCM54XX_WOL_EN))
995+
return;
996+
997+
for (i = 0; i < sizeof(da) / 2; i++) {
998+
ret = bcm_phy_read_exp(phydev,
999+
BCM54XX_WOL_MPD_DATA2(2 - i));
1000+
if (ret < 0)
1001+
return;
1002+
1003+
da[i * 2] = ret >> 8;
1004+
da[i * 2 + 1] = ret & 0xff;
1005+
}
1006+
1007+
if (ctl & BCM54XX_WOL_DIR_PKT_EN) {
1008+
if (is_broadcast_ether_addr(da))
1009+
wol->wolopts |= WAKE_BCAST;
1010+
else if (is_multicast_ether_addr(da))
1011+
wol->wolopts |= WAKE_MCAST;
1012+
else if (ether_addr_equal(da, ndev->dev_addr))
1013+
wol->wolopts |= WAKE_UCAST;
1014+
} else {
1015+
ctl = (ctl >> BCM54XX_WOL_MODE_SHIFT) & BCM54XX_WOL_MODE_MASK;
1016+
switch (ctl) {
1017+
case BCM54XX_WOL_MODE_SINGLE_MPD:
1018+
wol->wolopts |= WAKE_MAGIC;
1019+
break;
1020+
case BCM54XX_WOL_MODE_SINGLE_MPDSEC:
1021+
wol->wolopts |= WAKE_MAGICSECURE;
1022+
memcpy(wol->sopass, da, sizeof(da));
1023+
break;
1024+
default:
1025+
break;
1026+
}
1027+
}
1028+
}
1029+
EXPORT_SYMBOL_GPL(bcm_phy_get_wol);
1030+
8191031
MODULE_DESCRIPTION("Broadcom PHY Library");
8201032
MODULE_LICENSE("GPL v2");
8211033
MODULE_AUTHOR("Broadcom Corporation");

drivers/net/phy/bcm-phy-lib.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
#include <linux/brcmphy.h>
1010
#include <linux/phy.h>
1111

12+
struct ethtool_wolinfo;
13+
1214
/* 28nm only register definitions */
1315
#define MISC_ADDR(base, channel) base, channel
1416

@@ -111,4 +113,7 @@ static inline void bcm_ptp_stop(struct bcm_ptp_private *priv)
111113
}
112114
#endif
113115

116+
int bcm_phy_set_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol);
117+
void bcm_phy_get_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol);
118+
114119
#endif /* _LINUX_BCM_PHY_LIB_H */

0 commit comments

Comments
 (0)