Commit d8981d02 authored by Niklas Söderlund's avatar Niklas Söderlund Committed by David S. Miller

sh_eth: add generic wake-on-lan support via magic packet

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: default avatarNiklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 6dcf45e5
...@@ -1550,6 +1550,8 @@ static void sh_eth_emac_interrupt(struct net_device *ndev) ...@@ -1550,6 +1550,8 @@ static void sh_eth_emac_interrupt(struct net_device *ndev)
sh_eth_rcv_snd_enable(ndev); sh_eth_rcv_snd_enable(ndev);
} }
} }
if (felic_stat & ECSR_MPD)
pm_wakeup_event(&mdp->pdev->dev, 0);
} }
/* error control function */ /* error control function */
...@@ -2199,6 +2201,33 @@ static int sh_eth_set_ringparam(struct net_device *ndev, ...@@ -2199,6 +2201,33 @@ static int sh_eth_set_ringparam(struct net_device *ndev,
return 0; 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 = { static const struct ethtool_ops sh_eth_ethtool_ops = {
.get_regs_len = sh_eth_get_regs_len, .get_regs_len = sh_eth_get_regs_len,
.get_regs = sh_eth_get_regs, .get_regs = sh_eth_get_regs,
...@@ -2213,6 +2242,8 @@ static const struct ethtool_ops sh_eth_ethtool_ops = { ...@@ -2213,6 +2242,8 @@ static const struct ethtool_ops sh_eth_ethtool_ops = {
.set_ringparam = sh_eth_set_ringparam, .set_ringparam = sh_eth_set_ringparam,
.get_link_ksettings = sh_eth_get_link_ksettings, .get_link_ksettings = sh_eth_get_link_ksettings,
.set_link_ksettings = sh_eth_set_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 */ /* network device open function */
...@@ -3015,6 +3046,11 @@ static int sh_eth_drv_probe(struct platform_device *pdev) ...@@ -3015,6 +3046,11 @@ static int sh_eth_drv_probe(struct platform_device *pdev)
goto out_release; 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; ndev->base_addr = res->start;
spin_lock_init(&mdp->lock); spin_lock_init(&mdp->lock);
...@@ -3109,6 +3145,9 @@ static int sh_eth_drv_probe(struct platform_device *pdev) ...@@ -3109,6 +3145,9 @@ static int sh_eth_drv_probe(struct platform_device *pdev)
if (ret) if (ret)
goto out_napi_del; goto out_napi_del;
if (mdp->cd->magic && mdp->clk)
device_set_wakeup_capable(&pdev->dev, 1);
/* print device information */ /* print device information */
netdev_info(ndev, "Base address at 0x%x, %pM, IRQ %d.\n", netdev_info(ndev, "Base address at 0x%x, %pM, IRQ %d.\n",
(u32)ndev->base_addr, ndev->dev_addr, ndev->irq); (u32)ndev->base_addr, ndev->dev_addr, ndev->irq);
...@@ -3148,15 +3187,67 @@ static int sh_eth_drv_remove(struct platform_device *pdev) ...@@ -3148,15 +3187,67 @@ static int sh_eth_drv_remove(struct platform_device *pdev)
#ifdef CONFIG_PM #ifdef CONFIG_PM
#ifdef CONFIG_PM_SLEEP #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) static int sh_eth_suspend(struct device *dev)
{ {
struct net_device *ndev = dev_get_drvdata(dev); struct net_device *ndev = dev_get_drvdata(dev);
struct sh_eth_private *mdp = netdev_priv(ndev);
int ret = 0; int ret = 0;
if (netif_running(ndev)) { if (!netif_running(ndev))
return 0;
netif_device_detach(ndev); netif_device_detach(ndev);
if (mdp->wol_enabled)
ret = sh_eth_wol_setup(ndev);
else
ret = sh_eth_close(ndev); ret = sh_eth_close(ndev);
}
return ret; return ret;
} }
...@@ -3164,14 +3255,21 @@ static int sh_eth_suspend(struct device *dev) ...@@ -3164,14 +3255,21 @@ static int sh_eth_suspend(struct device *dev)
static int sh_eth_resume(struct device *dev) static int sh_eth_resume(struct device *dev)
{ {
struct net_device *ndev = dev_get_drvdata(dev); struct net_device *ndev = dev_get_drvdata(dev);
struct sh_eth_private *mdp = netdev_priv(ndev);
int ret = 0; 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); ret = sh_eth_open(ndev);
if (ret < 0) if (ret < 0)
return ret; return ret;
netif_device_attach(ndev); netif_device_attach(ndev);
}
return ret; return ret;
} }
......
...@@ -492,6 +492,7 @@ struct sh_eth_cpu_data { ...@@ -492,6 +492,7 @@ struct sh_eth_cpu_data {
unsigned select_mii:1; /* EtherC have RMII_MII (MII select register) */ unsigned select_mii:1; /* EtherC have RMII_MII (MII select register) */
unsigned rmiimode:1; /* EtherC has RMIIMODE register */ unsigned rmiimode:1; /* EtherC has RMIIMODE register */
unsigned rtrate:1; /* EtherC has RTRATE register */ unsigned rtrate:1; /* EtherC has RTRATE register */
unsigned magic:1; /* EtherC has ECMR.MPDE and ECSR.MPD */
}; };
struct sh_eth_private { struct sh_eth_private {
...@@ -500,6 +501,7 @@ struct sh_eth_private { ...@@ -500,6 +501,7 @@ struct sh_eth_private {
const u16 *reg_offset; const u16 *reg_offset;
void __iomem *addr; void __iomem *addr;
void __iomem *tsu_addr; void __iomem *tsu_addr;
struct clk *clk;
u32 num_rx_ring; u32 num_rx_ring;
u32 num_tx_ring; u32 num_tx_ring;
dma_addr_t rx_desc_dma; dma_addr_t rx_desc_dma;
...@@ -528,6 +530,7 @@ struct sh_eth_private { ...@@ -528,6 +530,7 @@ struct sh_eth_private {
unsigned no_ether_link:1; unsigned no_ether_link:1;
unsigned ether_link_active_low:1; unsigned ether_link_active_low:1;
unsigned is_opened:1; unsigned is_opened:1;
unsigned wol_enabled:1;
}; };
static inline void sh_eth_soft_swap(char *src, int len) static inline void sh_eth_soft_swap(char *src, int len)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment