#include <linux/of.h>
 #include <linux/phy.h>
 #include <linux/netdevice.h>
+#include <linux/crc16.h>
+#include <linux/etherdevice.h>
 #include <linux/smscphy.h>
 
 /* Vendor-specific PHY Definitions */
        unsigned int edpd_enable:1;
        unsigned int edpd_mode_set_by_user:1;
        unsigned int edpd_max_wait_ms;
+       bool wol_arp;
 };
 
 static int smsc_phy_ack_interrupt(struct phy_device *phydev)
 }
 EXPORT_SYMBOL_GPL(lan87xx_read_status);
 
+static int lan874x_phy_config_init(struct phy_device *phydev)
+{
+       u16 val;
+       int rc;
+
+       /* Setup LED2/nINT/nPME pin to function as nPME.  May need user option
+        * to use LED1/nINT/nPME.
+        */
+       val = MII_LAN874X_PHY_PME2_SET;
+
+       /* The bits MII_LAN874X_PHY_WOL_PFDA_FR, MII_LAN874X_PHY_WOL_WUFR,
+        * MII_LAN874X_PHY_WOL_MPR, and MII_LAN874X_PHY_WOL_BCAST_FR need to
+        * be cleared to de-assert PME signal after a WoL event happens, but
+        * using PME auto clear gets around that.
+        */
+       val |= 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;
+
+       /* 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;
+
+       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;
+       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;
+
+       if (rc & MII_LAN874X_PHY_WOL_PFDAEN)
+               wol->wolopts |= WAKE_UCAST;
+
+       if (rc & MII_LAN874X_PHY_WOL_BCSTEN)
+               wol->wolopts |= WAKE_BCAST;
+
+       if (rc & MII_LAN874X_PHY_WOL_MPEN)
+               wol->wolopts |= WAKE_MAGIC;
+
+       if (rc & MII_LAN874X_PHY_WOL_WUEN) {
+               if (priv->wol_arp)
+                       wol->wolopts |= WAKE_ARP;
+               else
+                       wol->wolopts |= WAKE_MCAST;
+       }
+}
+
+static u16 smsc_crc16(const u8 *buffer, size_t len)
+{
+       return bitrev16(crc16(0xFFFF, buffer, len));
+}
+
+static int lan874x_chk_wol_pattern(const u8 pattern[], const u16 *mask,
+                                  u8 len, u8 *data, u8 *datalen)
+{
+       size_t i, j, k;
+       int ret = 0;
+       u16 bits;
+
+       /* Pattern filtering can match up to 128 bytes of frame data.  There
+        * are 8 registers to program the 16-bit masks, where each bit means
+        * the byte will be compared.  The frame data will then go through a
+        * CRC16 calculation for hardware comparison.  This helper function
+        * makes sure only relevant frame data are included in this
+        * calculation.  It provides a warning when the masks and expected
+        * data size do not match.
+        */
+       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)
+                                       ret = i + 1;
+                               break;
+                       }
+                       if (bits & 1)
+                               data[k++] = pattern[i];
+                       bits >>= 1;
+               }
+               mask++;
+       }
+       *datalen = k;
+       return ret;
+}
+
+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;
+
+       /* Starting pattern offset is set before calling this function. */
+       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;
+       u8 data[128];
+       u8 datalen;
+       int rc;
+
+       /* lan874x has only one WoL filter pattern */
+       if ((wol->wolopts & (WAKE_ARP | WAKE_MCAST)) ==
+           (WAKE_ARP | WAKE_MCAST)) {
+               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 = 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 (wol->wolopts & (WAKE_ARP | WAKE_MCAST))
+               val_wucsr |= MII_LAN874X_PHY_WOL_WUEN;
+       else
+               val_wucsr &= ~MII_LAN874X_PHY_WOL_WUEN;
+
+       if (wol->wolopts & WAKE_ARP) {
+               const u8 pattern[2] = { 0x08, 0x06 };
+               const u16 mask[1] = { 0x0003 };
+
+               rc = lan874x_chk_wol_pattern(pattern, mask, 2, data,
+                                            &datalen);
+               if (rc)
+                       phydev_dbg(phydev, "pattern not valid at %d\n", rc);
+
+               /* Need to match broadcast destination address and provided
+                * data pattern at offset 12.
+                */
+               val = 12 | MII_LAN874X_PHY_WOL_FILTER_BCSTEN;
+               rc = lan874x_set_wol_pattern(phydev, val, data, datalen, mask,
+                                            2);
+               if (rc < 0)
+                       return rc;
+               priv->wol_arp = true;
+       }
+
+       if (wol->wolopts & WAKE_MCAST) {
+               /* Need to match multicast destination address. */
+               val = MII_LAN874X_PHY_WOL_FILTER_MCASTTEN;
+               rc = lan874x_set_wol_pattern(phydev, val, data, 0, NULL, 0);
+               if (rc < 0)
+                       return rc;
+               priv->wol_arp = false;
+       }
+
+       if (wol->wolopts & (WAKE_MAGIC | WAKE_UCAST)) {
+               const u8 *mac = (const u8 *)ndev->dev_addr;
+               int i, reg;
+
+               reg = MII_LAN874X_PHY_MMD_WOL_RX_ADDRC;
+               for (i = 0; i < 6; i += 2, reg--) {
+                       rc = phy_write_mmd(phydev, MDIO_MMD_PCS, reg,
+                                          ((mac[i + 1] << 8) | mac[i]));
+                       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;
+
+       return 0;
+}
+
 static int smsc_get_sset_count(struct phy_device *phydev)
 {
        return ARRAY_SIZE(smsc_hw_stats);
 
        /* 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 */
        .get_tunable    = smsc_phy_get_tunable,
        .set_tunable    = smsc_phy_set_tunable,
 
+       /* WoL */
+       .set_wol        = lan874x_set_wol,
+       .get_wol        = lan874x_get_wol,
+
        .suspend        = genphy_suspend,
        .resume         = genphy_resume,
 }, {
 
        /* 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 */
        .get_tunable    = smsc_phy_get_tunable,
        .set_tunable    = smsc_phy_set_tunable,
 
+       /* WoL */
+       .set_wol        = lan874x_set_wol,
+       .get_wol        = lan874x_get_wol,
+
        .suspend        = genphy_suspend,
        .resume         = genphy_resume,
 } };
 
                         struct ethtool_tunable *tuna, const void *data);
 int smsc_phy_probe(struct phy_device *phydev);
 
+#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 */
+
 #endif /* __LINUX_SMSCPHY_H__ */