|
6 | 6 | #include "bcm-phy-lib.h" |
7 | 7 | #include <linux/bitfield.h> |
8 | 8 | #include <linux/brcmphy.h> |
| 9 | +#include <linux/etherdevice.h> |
9 | 10 | #include <linux/export.h> |
10 | 11 | #include <linux/mdio.h> |
11 | 12 | #include <linux/module.h> |
12 | 13 | #include <linux/phy.h> |
13 | 14 | #include <linux/ethtool.h> |
14 | 15 | #include <linux/ethtool_netlink.h> |
| 16 | +#include <linux/netdevice.h> |
15 | 17 |
|
16 | 18 | #define MII_BCM_CHANNEL_WIDTH 0x2000 |
17 | 19 | #define BCM_CL45VEN_EEE_ADV 0x3c |
@@ -816,6 +818,216 @@ int bcm_phy_cable_test_get_status_rdb(struct phy_device *phydev, |
816 | 818 | } |
817 | 819 | EXPORT_SYMBOL_GPL(bcm_phy_cable_test_get_status_rdb); |
818 | 820 |
|
| 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 | + |
819 | 1031 | MODULE_DESCRIPTION("Broadcom PHY Library"); |
820 | 1032 | MODULE_LICENSE("GPL v2"); |
821 | 1033 | MODULE_AUTHOR("Broadcom Corporation"); |
0 commit comments