@@ -20,6 +20,11 @@
#include <linux/of.h>
#include <linux/phy.h>
#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/crc16.h>
+#include <linux/inetdevice.h>
+#include <net/ipv6.h>
+#include <net/if_inet6.h>
#include <linux/smscphy.h>
/* Vendor-specific PHY Definitions */
@@ -46,6 +51,7 @@ struct smsc_hw_stat {
struct smsc_phy_priv {
u16 intmask;
bool energy_enable;
+ struct ethtool_wolinfo wol;
struct clk *refclk;
};
@@ -246,6 +252,312 @@ static int lan87xx_read_status(struct phy_device *phydev)
return err;
}
+static int lan874x_phy_config_init(struct phy_device *phydev)
+{
+ u16 val;
+ int rc;
+
+ /* set nPME self clear delay time */
+ rc = phy_write_mmd(phydev, MDIO_MMD_PCS, MII_LAN874X_PHY_MMD_MCFGR,
+ MII_LAN874X_PHY_PME_SELF_CLEAR_DELAY);
+ if (rc < 0)
+ return rc;
+
+ /* MII_LAN874X_PHY_PME1_SET set LED1/nINT/nPME pin function as nPME */
+ /* MII_LAN874X_PHY_PME2_SET set LED2/nINT/nPME pin function as nPME */
+ /* set nPME auto clear */
+ val = (MII_LAN874X_PHY_PME2_SET | MII_LAN874X_PHY_PME_SELF_CLEAR);
+ rc = phy_write_mmd(phydev, MDIO_MMD_PCS, MII_LAN874X_PHY_MMD_WOL_WUCSR,
+ val);
+ if (rc < 0)
+ return rc;
+
+ return smsc_phy_config_init(phydev);
+}
+
+static void lan874x_get_wol(struct phy_device *phydev,
+ struct ethtool_wolinfo *wol)
+{
+ struct smsc_phy_priv *priv = phydev->priv;
+ u16 val_wucsr;
+ int rc;
+
+ wol->supported = (WAKE_UCAST | WAKE_BCAST | WAKE_MAGIC |
+ WAKE_ARP | WAKE_MCAST);
+ wol->wolopts = 0;
+
+ rc = phy_read_mmd(phydev, MDIO_MMD_PCS, MII_LAN874X_PHY_MMD_WOL_WUCSR);
+ if (rc < 0)
+ return;
+
+ val_wucsr = (u16)rc;
+
+ if (val_wucsr & MII_LAN874X_PHY_WOL_PFDAEN)
+ wol->wolopts |= WAKE_UCAST;
+
+ if (val_wucsr & MII_LAN874X_PHY_WOL_BCSTEN)
+ wol->wolopts |= WAKE_BCAST;
+
+ if (val_wucsr & MII_LAN874X_PHY_WOL_MPEN)
+ wol->wolopts |= WAKE_MAGIC;
+
+ if (val_wucsr & MII_LAN874X_PHY_WOL_WUEN) {
+ if (priv->wol.wolopts & WAKE_ARP)
+ wol->wolopts |= WAKE_ARP;
+ if (priv->wol.wolopts & WAKE_MCAST)
+ wol->wolopts |= WAKE_MCAST;
+ }
+
+ /* WoL event notification */
+ if (val_wucsr & MII_LAN874X_PHY_WOL_WUFR) {
+ if (wol->wolopts & WAKE_ARP)
+ phydev_info(phydev, "ARP WoL event received\n");
+ if (wol->wolopts & WAKE_MCAST)
+ phydev_info(phydev, "MCAST WoL event received\n");
+ }
+
+ if (val_wucsr & MII_LAN874X_PHY_WOL_PFDA_FR)
+ phydev_info(phydev, "UCAST WoL event received\n");
+
+ if (val_wucsr & MII_LAN874X_PHY_WOL_BCAST_FR)
+ phydev_info(phydev, "BCAST WoL event received\n");
+
+ if (val_wucsr & MII_LAN874X_PHY_WOL_MPR)
+ phydev_info(phydev, "Magic WoL event received\n");
+
+ /* clear WoL event */
+ rc = phy_write_mmd(phydev, MDIO_MMD_PCS, MII_LAN874X_PHY_MMD_WOL_WUCSR,
+ val_wucsr);
+ if (rc < 0)
+ return;
+}
+
+static u16 smsc_crc16(const u8 *buffer, size_t len)
+{
+ u16 crc = bitrev16(crc16(0xFFFF, buffer, len));
+ return crc;
+}
+
+static int lan874x_chk_wol_pattern(const u8 pattern[], const u16 *mask,
+ u8 len, u8 *data, u8 *datalen)
+{
+ size_t i, j, k;
+ u16 bits;
+
+ i = 0;
+ k = 0;
+ while (len > 0) {
+ bits = *mask;
+ for (j = 0; j < 16; j++, i++, len--) {
+ /* No more pattern. */
+ if (!len) {
+ /* The rest of bitmap is not empty. */
+ if (bits)
+ return i + 1;
+ break;
+ }
+ if (bits & 1)
+ data[k++] = pattern[i];
+ bits >>= 1;
+ }
+ mask++;
+ }
+ *datalen = k;
+ return 0;
+}
+
+static int lan874x_set_wol_pattern(struct phy_device *phydev, u16 val,
+ const u8 data[], u8 datalen,
+ const u16 *mask, u8 masklen)
+{
+ u16 crc, reg;
+ int rc;
+
+ val |= MII_LAN874X_PHY_WOL_FILTER_EN;
+ rc = phy_write_mmd(phydev, MDIO_MMD_PCS,
+ MII_LAN874X_PHY_MMD_WOL_WUF_CFGA, val);
+ if (rc < 0)
+ return rc;
+
+ crc = smsc_crc16(data, datalen);
+ rc = phy_write_mmd(phydev, MDIO_MMD_PCS,
+ MII_LAN874X_PHY_MMD_WOL_WUF_CFGB, crc);
+ if (rc < 0)
+ return rc;
+
+ masklen = (masklen + 15) & ~0xf;
+ reg = MII_LAN874X_PHY_MMD_WOL_WUF_MASK7;
+ while (masklen >= 16) {
+ rc = phy_write_mmd(phydev, MDIO_MMD_PCS, reg, *mask);
+ if (rc < 0)
+ return rc;
+ reg--;
+ mask++;
+ masklen -= 16;
+ }
+
+ /* Clear out the rest of mask registers. */
+ while (reg != MII_LAN874X_PHY_MMD_WOL_WUF_MASK0) {
+ phy_write_mmd(phydev, MDIO_MMD_PCS, reg, 0);
+ reg--;
+ }
+ return rc;
+}
+
+static int lan874x_set_wol(struct phy_device *phydev,
+ struct ethtool_wolinfo *wol)
+{
+ struct net_device *ndev = phydev->attached_dev;
+ struct smsc_phy_priv *priv = phydev->priv;
+ u16 val, val_wucsr;
+ int i = 0, rc;
+ u8 data[128];
+ u8 datalen;
+
+ if (wol->wolopts & WAKE_PHY)
+ return -EOPNOTSUPP;
+
+ /* lan874x has only one WoL filter pattern */
+ if (wol->wolopts & (WAKE_ARP | WAKE_MCAST)) {
+ if (wol->wolopts & WAKE_ARP)
+ i++;
+ if (wol->wolopts & WAKE_MCAST)
+ i++;
+ if (i >= 2) {
+ phydev_info(phydev,
+ "lan874x WoL supports one of ARP|MCAST at a time\n");
+ return -EOPNOTSUPP;
+ }
+ }
+
+ rc = phy_read_mmd(phydev, MDIO_MMD_PCS, MII_LAN874X_PHY_MMD_WOL_WUCSR);
+ if (rc < 0)
+ return rc;
+
+ val_wucsr = (u16)rc;
+
+ if (wol->wolopts & WAKE_UCAST)
+ val_wucsr |= MII_LAN874X_PHY_WOL_PFDAEN;
+ else
+ val_wucsr &= ~MII_LAN874X_PHY_WOL_PFDAEN;
+
+ if (wol->wolopts & WAKE_BCAST)
+ val_wucsr |= MII_LAN874X_PHY_WOL_BCSTEN;
+ else
+ val_wucsr &= ~MII_LAN874X_PHY_WOL_BCSTEN;
+
+ if (wol->wolopts & WAKE_MAGIC)
+ val_wucsr |= MII_LAN874X_PHY_WOL_MPEN;
+ else
+ val_wucsr &= ~MII_LAN874X_PHY_WOL_MPEN;
+
+ /* Need to use pattern matching */
+ if (i > 0)
+ val_wucsr |= MII_LAN874X_PHY_WOL_WUEN;
+ else
+ val_wucsr &= ~MII_LAN874X_PHY_WOL_WUEN;
+
+ if (wol->wolopts & WAKE_ARP) {
+ const u8 *ip_addr =
+ ((const u8 *)&((ndev->ip_ptr)->ifa_list)->ifa_address);
+ u8 pattern[42] = {
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x08, 0x06,
+ 0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00 };
+ const u16 mask[3] = { 0xF03F, 0x003F, 0x03C0 };
+ u16 offset = 0;
+ u8 len = 42;
+
+ memcpy(&pattern[38], ip_addr, 4);
+ rc = lan874x_chk_wol_pattern(&pattern[offset], mask, len,
+ data, &datalen);
+ if (rc)
+ phydev_info(phydev, "pattern not valid at %d\n", rc);
+
+ /* Need to match broadcast destination address. */
+ val = offset |
+ MII_LAN874X_PHY_WOL_FILTER_BCSTEN;
+ rc = lan874x_set_wol_pattern(phydev, val, data, datalen, mask,
+ len);
+ if (rc < 0)
+ return rc;
+ }
+
+ if (wol->wolopts & WAKE_MCAST) {
+ u8 pattern[6] = { 0x33, 0x33, 0xFF, 0x00, 0x00, 0x00 };
+ u16 mask[1] = { 0x0007 };
+ u8 len = 3;
+
+ /* Try to match IPv6 Neighbor Solicitation. */
+ if (ndev->ip6_ptr) {
+ struct list_head *addr_list =
+ &ndev->ip6_ptr->addr_list;
+ struct inet6_ifaddr *ifa;
+
+ list_for_each_entry(ifa, addr_list, if_list) {
+ if (ifa->scope == IFA_LINK) {
+ memcpy(&pattern[3],
+ &ifa->addr.in6_u.u6_addr8[13],
+ 3);
+ mask[0] = 0x003F;
+ len = 6;
+ break;
+ }
+ }
+ }
+ rc = lan874x_chk_wol_pattern(pattern, mask, len,
+ data, &datalen);
+ if (rc)
+ phydev_info(phydev, "pattern not valid at %d\n", rc);
+
+ /* Need to match multicast destination address. */
+ val = 0 |
+ MII_LAN874X_PHY_WOL_FILTER_MCASTTEN;
+ rc = lan874x_set_wol_pattern(phydev, val, data, datalen, mask,
+ len);
+ if (rc < 0)
+ return rc;
+ }
+
+ if (wol->wolopts & (WAKE_MAGIC | WAKE_UCAST)) {
+ const u8 *mac = (const u8 *)ndev->dev_addr;
+
+ if (!is_valid_ether_addr(mac))
+ return -EINVAL;
+
+ rc = phy_write_mmd(phydev, MDIO_MMD_PCS,
+ MII_LAN874X_PHY_MMD_WOL_RX_ADDRC,
+ ((mac[1] << 8) | mac[0]));
+ if (rc < 0)
+ return rc;
+
+ rc = phy_write_mmd(phydev, MDIO_MMD_PCS,
+ MII_LAN874X_PHY_MMD_WOL_RX_ADDRB,
+ ((mac[3] << 8) | mac[2]));
+ if (rc < 0)
+ return rc;
+
+ rc = phy_write_mmd(phydev, MDIO_MMD_PCS,
+ MII_LAN874X_PHY_MMD_WOL_RX_ADDRA,
+ ((mac[5] << 8) | mac[4]));
+ if (rc < 0)
+ return rc;
+ }
+
+ rc = phy_write_mmd(phydev, MDIO_MMD_PCS, MII_LAN874X_PHY_MMD_WOL_WUCSR,
+ val_wucsr);
+ if (rc < 0)
+ return rc;
+
+ priv->wol = *wol;
+ return 0;
+}
+
static int smsc_get_sset_count(struct phy_device *phydev)
{
return ARRAY_SIZE(smsc_hw_stats);
@@ -460,7 +772,7 @@ static int smsc_phy_probe(struct phy_device *phydev)
/* basic functions */
.read_status = lan87xx_read_status,
- .config_init = smsc_phy_config_init,
+ .config_init = lan874x_phy_config_init,
.soft_reset = smsc_phy_reset,
/* IRQ related */
@@ -472,6 +784,10 @@ static int smsc_phy_probe(struct phy_device *phydev)
.get_strings = smsc_get_strings,
.get_stats = smsc_get_stats,
+ /* WoL */
+ .set_wol = lan874x_set_wol,
+ .get_wol = lan874x_get_wol,
+
.suspend = genphy_suspend,
.resume = genphy_resume,
}, {
@@ -490,7 +806,7 @@ static int smsc_phy_probe(struct phy_device *phydev)
/* basic functions */
.read_status = lan87xx_read_status,
- .config_init = smsc_phy_config_init,
+ .config_init = lan874x_phy_config_init,
.soft_reset = smsc_phy_reset,
/* IRQ related */
@@ -502,6 +818,10 @@ static int smsc_phy_probe(struct phy_device *phydev)
.get_strings = smsc_get_strings,
.get_stats = smsc_get_stats,
+ /* WoL */
+ .set_wol = lan874x_set_wol,
+ .get_wol = lan874x_get_wol,
+
.suspend = genphy_suspend,
.resume = genphy_resume,
} };
@@ -28,4 +28,40 @@
#define MII_LAN83C185_MODE_POWERDOWN 0xC0 /* Power Down mode */
#define MII_LAN83C185_MODE_ALL 0xE0 /* All capable mode */
+#define MII_LAN874X_PHY_MMD_WOL_WUCSR 0x8010
+#define MII_LAN874X_PHY_MMD_WOL_WUF_CFGA 0x8011
+#define MII_LAN874X_PHY_MMD_WOL_WUF_CFGB 0x8012
+#define MII_LAN874X_PHY_MMD_WOL_WUF_MASK0 0x8021
+#define MII_LAN874X_PHY_MMD_WOL_WUF_MASK1 0x8022
+#define MII_LAN874X_PHY_MMD_WOL_WUF_MASK2 0x8023
+#define MII_LAN874X_PHY_MMD_WOL_WUF_MASK3 0x8024
+#define MII_LAN874X_PHY_MMD_WOL_WUF_MASK4 0x8025
+#define MII_LAN874X_PHY_MMD_WOL_WUF_MASK5 0x8026
+#define MII_LAN874X_PHY_MMD_WOL_WUF_MASK6 0x8027
+#define MII_LAN874X_PHY_MMD_WOL_WUF_MASK7 0x8028
+#define MII_LAN874X_PHY_MMD_WOL_RX_ADDRA 0x8061
+#define MII_LAN874X_PHY_MMD_WOL_RX_ADDRB 0x8062
+#define MII_LAN874X_PHY_MMD_WOL_RX_ADDRC 0x8063
+#define MII_LAN874X_PHY_MMD_MCFGR 0x8064
+
+#define MII_LAN874X_PHY_PME1_SET (2<<13)
+#define MII_LAN874X_PHY_PME2_SET (2<<11)
+#define MII_LAN874X_PHY_PME_SELF_CLEAR BIT(9)
+#define MII_LAN874X_PHY_WOL_PFDA_FR BIT(7)
+#define MII_LAN874X_PHY_WOL_WUFR BIT(6)
+#define MII_LAN874X_PHY_WOL_MPR BIT(5)
+#define MII_LAN874X_PHY_WOL_BCAST_FR BIT(4)
+#define MII_LAN874X_PHY_WOL_PFDAEN BIT(3)
+#define MII_LAN874X_PHY_WOL_WUEN BIT(2)
+#define MII_LAN874X_PHY_WOL_MPEN BIT(1)
+#define MII_LAN874X_PHY_WOL_BCSTEN BIT(0)
+
+#define MII_LAN874X_PHY_WOL_FILTER_EN BIT(15)
+#define MII_LAN874X_PHY_WOL_FILTER_MCASTTEN BIT(9)
+#define MII_LAN874X_PHY_WOL_FILTER_BCSTEN BIT(8)
+
+#define MII_LAN874X_PHY_PME_SELF_CLEAR_DELAY 0x1000 /* 81 milliseconds */
+
+#define MII_LAN874X_PHY_WOL_IMEN BIT(8) /* WoL intr enable */
+
#endif /* __LINUX_SMSCPHY_H__ */