sh_eth: add generic wake-on-lan support via magic packet
authorNiklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
Mon, 9 Jan 2017 15:34:05 +0000 (16:34 +0100)
committerDavid S. Miller <davem@davemloft.net>
Mon, 9 Jan 2017 20:54:00 +0000 (15:54 -0500)
Add generic functionality to support Wake-on-LAN using MagicPacket which
are supported by at least a few versions of sh_eth. Only add
functionality for WoL, no specific sh_eth versions are marked to support
WoL yet.

WoL is enabled in the suspend callback by setting MagicPacket detection
and disabling all interrupts expect MagicPacket. In the resume path the
driver needs to reset the hardware to rearm the WoL logic, this prevents
the driver from simply restoring the registers and to take advantage of
that sh_eth was not suspended to reduce resume time. To reset the
hardware the driver closes and reopens the device just like it would do
in a normal suspend/resume scenario without WoL enabled, but it both
closes and opens the device in the resume callback since the device
needs to be open for WoL to work.

One quirk needed for WoL is that the module clock needs to be prevented
from being switched off by Runtime PM. To keep the clock alive the
suspend callback need to call clk_enable() directly to increase the
usage count of the clock. Then when Runtime PM decreases the clock usage
count it won't reach 0 and be switched off.

Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/renesas/sh_eth.c
drivers/net/ethernet/renesas/sh_eth.h

index 90fb0e9..4eae834 100644 (file)
@@ -1550,6 +1550,8 @@ static void sh_eth_emac_interrupt(struct net_device *ndev)
                        sh_eth_rcv_snd_enable(ndev);
                }
        }
+       if (felic_stat & ECSR_MPD)
+               pm_wakeup_event(&mdp->pdev->dev, 0);
 }
 
 /* error control function */
@@ -2199,6 +2201,33 @@ static int sh_eth_set_ringparam(struct net_device *ndev,
        return 0;
 }
 
+static void sh_eth_get_wol(struct net_device *ndev, struct ethtool_wolinfo *wol)
+{
+       struct sh_eth_private *mdp = netdev_priv(ndev);
+
+       wol->supported = 0;
+       wol->wolopts = 0;
+
+       if (mdp->cd->magic && mdp->clk) {
+               wol->supported = WAKE_MAGIC;
+               wol->wolopts = mdp->wol_enabled ? WAKE_MAGIC : 0;
+       }
+}
+
+static int sh_eth_set_wol(struct net_device *ndev, struct ethtool_wolinfo *wol)
+{
+       struct sh_eth_private *mdp = netdev_priv(ndev);
+
+       if (!mdp->cd->magic || !mdp->clk || wol->wolopts & ~WAKE_MAGIC)
+               return -EOPNOTSUPP;
+
+       mdp->wol_enabled = !!(wol->wolopts & WAKE_MAGIC);
+
+       device_set_wakeup_enable(&mdp->pdev->dev, mdp->wol_enabled);
+
+       return 0;
+}
+
 static const struct ethtool_ops sh_eth_ethtool_ops = {
        .get_regs_len   = sh_eth_get_regs_len,
        .get_regs       = sh_eth_get_regs,
@@ -2213,6 +2242,8 @@ static const struct ethtool_ops sh_eth_ethtool_ops = {
        .set_ringparam  = sh_eth_set_ringparam,
        .get_link_ksettings = sh_eth_get_link_ksettings,
        .set_link_ksettings = sh_eth_set_link_ksettings,
+       .get_wol        = sh_eth_get_wol,
+       .set_wol        = sh_eth_set_wol,
 };
 
 /* network device open function */
@@ -3015,6 +3046,11 @@ static int sh_eth_drv_probe(struct platform_device *pdev)
                goto out_release;
        }
 
+       /* Get clock, if not found that's OK but Wake-On-Lan is unavailable */
+       mdp->clk = devm_clk_get(&pdev->dev, NULL);
+       if (IS_ERR(mdp->clk))
+               mdp->clk = NULL;
+
        ndev->base_addr = res->start;
 
        spin_lock_init(&mdp->lock);
@@ -3109,6 +3145,9 @@ static int sh_eth_drv_probe(struct platform_device *pdev)
        if (ret)
                goto out_napi_del;
 
+       if (mdp->cd->magic && mdp->clk)
+               device_set_wakeup_capable(&pdev->dev, 1);
+
        /* print device information */
        netdev_info(ndev, "Base address at 0x%x, %pM, IRQ %d.\n",
                    (u32)ndev->base_addr, ndev->dev_addr, ndev->irq);
@@ -3148,15 +3187,67 @@ static int sh_eth_drv_remove(struct platform_device *pdev)
 
 #ifdef CONFIG_PM
 #ifdef CONFIG_PM_SLEEP
+static int sh_eth_wol_setup(struct net_device *ndev)
+{
+       struct sh_eth_private *mdp = netdev_priv(ndev);
+
+       /* Only allow ECI interrupts */
+       synchronize_irq(ndev->irq);
+       napi_disable(&mdp->napi);
+       sh_eth_write(ndev, DMAC_M_ECI, EESIPR);
+
+       /* Enable MagicPacket */
+       sh_eth_modify(ndev, ECMR, 0, ECMR_MPDE);
+
+       /* Increased clock usage so device won't be suspended */
+       clk_enable(mdp->clk);
+
+       return enable_irq_wake(ndev->irq);
+}
+
+static int sh_eth_wol_restore(struct net_device *ndev)
+{
+       struct sh_eth_private *mdp = netdev_priv(ndev);
+       int ret;
+
+       napi_enable(&mdp->napi);
+
+       /* Disable MagicPacket */
+       sh_eth_modify(ndev, ECMR, ECMR_MPDE, 0);
+
+       /* The device needs to be reset to restore MagicPacket logic
+        * for next wakeup. If we close and open the device it will
+        * both be reset and all registers restored. This is what
+        * happens during suspend and resume without WoL enabled.
+        */
+       ret = sh_eth_close(ndev);
+       if (ret < 0)
+               return ret;
+       ret = sh_eth_open(ndev);
+       if (ret < 0)
+               return ret;
+
+       /* Restore clock usage count */
+       clk_disable(mdp->clk);
+
+       return disable_irq_wake(ndev->irq);
+}
+
 static int sh_eth_suspend(struct device *dev)
 {
        struct net_device *ndev = dev_get_drvdata(dev);
+       struct sh_eth_private *mdp = netdev_priv(ndev);
        int ret = 0;
 
-       if (netif_running(ndev)) {
-               netif_device_detach(ndev);
+       if (!netif_running(ndev))
+               return 0;
+
+       netif_device_detach(ndev);
+
+       if (mdp->wol_enabled)
+               ret = sh_eth_wol_setup(ndev);
+       else
                ret = sh_eth_close(ndev);
-       }
 
        return ret;
 }
@@ -3164,14 +3255,21 @@ static int sh_eth_suspend(struct device *dev)
 static int sh_eth_resume(struct device *dev)
 {
        struct net_device *ndev = dev_get_drvdata(dev);
+       struct sh_eth_private *mdp = netdev_priv(ndev);
        int ret = 0;
 
-       if (netif_running(ndev)) {
+       if (!netif_running(ndev))
+               return 0;
+
+       if (mdp->wol_enabled)
+               ret = sh_eth_wol_restore(ndev);
+       else
                ret = sh_eth_open(ndev);
-               if (ret < 0)
-                       return ret;
-               netif_device_attach(ndev);
-       }
+
+       if (ret < 0)
+               return ret;
+
+       netif_device_attach(ndev);
 
        return ret;
 }
index 90b6943..a1bb8cc 100644 (file)
@@ -492,6 +492,7 @@ struct sh_eth_cpu_data {
        unsigned select_mii:1;  /* EtherC have RMII_MII (MII select register) */
        unsigned rmiimode:1;    /* EtherC has RMIIMODE register */
        unsigned rtrate:1;      /* EtherC has RTRATE register */
+       unsigned magic:1;       /* EtherC has ECMR.MPDE and ECSR.MPD */
 };
 
 struct sh_eth_private {
@@ -500,6 +501,7 @@ struct sh_eth_private {
        const u16 *reg_offset;
        void __iomem *addr;
        void __iomem *tsu_addr;
+       struct clk *clk;
        u32 num_rx_ring;
        u32 num_tx_ring;
        dma_addr_t rx_desc_dma;
@@ -528,6 +530,7 @@ struct sh_eth_private {
        unsigned no_ether_link:1;
        unsigned ether_link_active_low:1;
        unsigned is_opened:1;
+       unsigned wol_enabled:1;
 };
 
 static inline void sh_eth_soft_swap(char *src, int len)