| From af32eba20f34e1e9161e190398b03df6b76b3913 Mon Sep 17 00:00:00 2001 |
| From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= |
| <niklas.soderlund+renesas@ragnatech.se> |
| Date: Mon, 9 Jan 2017 16:34:05 +0100 |
| Subject: [PATCH 130/255] sh_eth: add generic wake-on-lan support via magic |
| packet |
| MIME-Version: 1.0 |
| Content-Type: text/plain; charset=UTF-8 |
| Content-Transfer-Encoding: 8bit |
| |
| 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> |
| (cherry picked from commit d8981d029da9d230955dabe596dbb30e7971b7b9) |
| Signed-off-by: Simon Horman <horms+renesas@verge.net.au> |
| --- |
| drivers/net/ethernet/renesas/sh_eth.c | 114 +++++++++++++++++++++++++++++++--- |
| drivers/net/ethernet/renesas/sh_eth.h | 3 |
| 2 files changed, 109 insertions(+), 8 deletions(-) |
| |
| --- a/drivers/net/ethernet/renesas/sh_eth.c |
| +++ b/drivers/net/ethernet/renesas/sh_eth.c |
| @@ -1562,6 +1562,8 @@ static void sh_eth_emac_interrupt(struct |
| sh_eth_rcv_snd_enable(ndev); |
| } |
| } |
| + if (felic_stat & ECSR_MPD) |
| + pm_wakeup_event(&mdp->pdev->dev, 0); |
| } |
| |
| /* error control function */ |
| @@ -2184,6 +2186,33 @@ static int sh_eth_set_ringparam(struct n |
| 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, |
| @@ -2198,6 +2227,8 @@ static const struct ethtool_ops sh_eth_e |
| .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 */ |
| @@ -3002,6 +3033,11 @@ static int sh_eth_drv_probe(struct platf |
| 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); |
| @@ -3115,6 +3151,9 @@ static int sh_eth_drv_probe(struct platf |
| 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); |
| @@ -3154,15 +3193,67 @@ static int sh_eth_drv_remove(struct plat |
| |
| #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; |
| } |
| @@ -3170,14 +3261,21 @@ static int sh_eth_suspend(struct device |
| 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; |
| } |
| --- a/drivers/net/ethernet/renesas/sh_eth.h |
| +++ b/drivers/net/ethernet/renesas/sh_eth.h |
| @@ -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) |