Skip to content

Commit

Permalink
ravb: add wake-on-lan support via magic packet
Browse files Browse the repository at this point in the history
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 ravb was not suspended to reduce resume time. To reset the
hardware the driver closes the device, sets it in reset mode 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 reset 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>
Acked-by: Sergei Shtylyov <sergei.shtylyov@cogentembedded.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
Niklas Söderlund authored and David S. Miller committed Aug 2, 2017
1 parent 906a039 commit 3e3d647
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 4 deletions.
2 changes: 2 additions & 0 deletions drivers/net/ethernet/renesas/ravb.h
Original file line number Diff line number Diff line change
Expand Up @@ -991,6 +991,7 @@ struct ravb_private {
struct net_device *ndev;
struct platform_device *pdev;
void __iomem *addr;
struct clk *clk;
struct mdiobb_ctrl mdiobb;
u32 num_rx_ring[NUM_RX_QUEUE];
u32 num_tx_ring[NUM_TX_QUEUE];
Expand Down Expand Up @@ -1033,6 +1034,7 @@ struct ravb_private {

unsigned no_avb_link:1;
unsigned avb_link_active_low:1;
unsigned wol_enabled:1;
};

static inline u32 ravb_read(struct net_device *ndev, enum ravb_reg reg)
Expand Down
108 changes: 104 additions & 4 deletions drivers/net/ethernet/renesas/ravb_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,9 @@ static void ravb_emac_interrupt_unlocked(struct net_device *ndev)

ecsr = ravb_read(ndev, ECSR);
ravb_write(ndev, ecsr, ECSR); /* clear interrupt */

if (ecsr & ECSR_MPD)
pm_wakeup_event(&priv->pdev->dev, 0);
if (ecsr & ECSR_ICD)
ndev->stats.tx_carrier_errors++;
if (ecsr & ECSR_LCHNG) {
Expand Down Expand Up @@ -1330,6 +1333,33 @@ static int ravb_get_ts_info(struct net_device *ndev,
return 0;
}

static void ravb_get_wol(struct net_device *ndev, struct ethtool_wolinfo *wol)
{
struct ravb_private *priv = netdev_priv(ndev);

wol->supported = 0;
wol->wolopts = 0;

if (priv->clk) {
wol->supported = WAKE_MAGIC;
wol->wolopts = priv->wol_enabled ? WAKE_MAGIC : 0;
}
}

static int ravb_set_wol(struct net_device *ndev, struct ethtool_wolinfo *wol)
{
struct ravb_private *priv = netdev_priv(ndev);

if (!priv->clk || wol->wolopts & ~WAKE_MAGIC)
return -EOPNOTSUPP;

priv->wol_enabled = !!(wol->wolopts & WAKE_MAGIC);

device_set_wakeup_enable(&priv->pdev->dev, priv->wol_enabled);

return 0;
}

static const struct ethtool_ops ravb_ethtool_ops = {
.nway_reset = ravb_nway_reset,
.get_msglevel = ravb_get_msglevel,
Expand All @@ -1343,6 +1373,8 @@ static const struct ethtool_ops ravb_ethtool_ops = {
.get_ts_info = ravb_get_ts_info,
.get_link_ksettings = ravb_get_link_ksettings,
.set_link_ksettings = ravb_set_link_ksettings,
.get_wol = ravb_get_wol,
.set_wol = ravb_set_wol,
};

static inline int ravb_hook_irq(unsigned int irq, irq_handler_t handler,
Expand Down Expand Up @@ -2041,6 +2073,11 @@ static int ravb_probe(struct platform_device *pdev)

priv->chip_id = chip_id;

/* Get clock, if not found that's OK but Wake-On-Lan is unavailable */
priv->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(priv->clk))
priv->clk = NULL;

/* Set function */
ndev->netdev_ops = &ravb_netdev_ops;
ndev->ethtool_ops = &ravb_ethtool_ops;
Expand Down Expand Up @@ -2107,6 +2144,9 @@ static int ravb_probe(struct platform_device *pdev)
if (error)
goto out_napi_del;

if (priv->clk)
device_set_wakeup_capable(&pdev->dev, 1);

/* Print device information */
netdev_info(ndev, "Base address at %#x, %pM, IRQ %d.\n",
(u32)ndev->base_addr, ndev->dev_addr, ndev->irq);
Expand Down Expand Up @@ -2160,15 +2200,66 @@ static int ravb_remove(struct platform_device *pdev)
return 0;
}

static int ravb_wol_setup(struct net_device *ndev)
{
struct ravb_private *priv = netdev_priv(ndev);

/* Disable interrupts by clearing the interrupt masks. */
ravb_write(ndev, 0, RIC0);
ravb_write(ndev, 0, RIC2);
ravb_write(ndev, 0, TIC);

/* Only allow ECI interrupts */
synchronize_irq(priv->emac_irq);
napi_disable(&priv->napi[RAVB_NC]);
napi_disable(&priv->napi[RAVB_BE]);
ravb_write(ndev, ECSIPR_MPDIP, ECSIPR);

/* Enable MagicPacket */
ravb_modify(ndev, ECMR, ECMR_MPDE, ECMR_MPDE);

/* Increased clock usage so device won't be suspended */
clk_enable(priv->clk);

return enable_irq_wake(priv->emac_irq);
}

static int ravb_wol_restore(struct net_device *ndev)
{
struct ravb_private *priv = netdev_priv(ndev);
int ret;

napi_enable(&priv->napi[RAVB_NC]);
napi_enable(&priv->napi[RAVB_BE]);

/* Disable MagicPacket */
ravb_modify(ndev, ECMR, ECMR_MPDE, 0);

ret = ravb_close(ndev);
if (ret < 0)
return ret;

/* Restore clock usage count */
clk_disable(priv->clk);

return disable_irq_wake(priv->emac_irq);
}

static int __maybe_unused ravb_suspend(struct device *dev)
{
struct net_device *ndev = dev_get_drvdata(dev);
int ret = 0;
struct ravb_private *priv = netdev_priv(ndev);
int ret;

if (netif_running(ndev)) {
netif_device_detach(ndev);
if (!netif_running(ndev))
return 0;

netif_device_detach(ndev);

if (priv->wol_enabled)
ret = ravb_wol_setup(ndev);
else
ret = ravb_close(ndev);
}

return ret;
}
Expand All @@ -2179,6 +2270,10 @@ static int __maybe_unused ravb_resume(struct device *dev)
struct ravb_private *priv = netdev_priv(ndev);
int ret = 0;

/* If WoL is enabled set reset mode to rearm the WoL logic */
if (priv->wol_enabled)
ravb_write(ndev, CCC_OPC_RESET, CCC);

/* All register have been reset to default values.
* Restore all registers which where setup at probe time and
* reopen device if it was running before system suspended.
Expand All @@ -2202,6 +2297,11 @@ static int __maybe_unused ravb_resume(struct device *dev)
ravb_write(ndev, priv->desc_bat_dma, DBAT);

if (netif_running(ndev)) {
if (priv->wol_enabled) {
ret = ravb_wol_restore(ndev);
if (ret)
return ret;
}
ret = ravb_open(ndev);
if (ret < 0)
return ret;
Expand Down

0 comments on commit 3e3d647

Please sign in to comment.