Skip to content

Commit

Permalink
DM9000: Wake on LAN support
Browse files Browse the repository at this point in the history
Add support for Wake on LAN (WOL) reception and waking the device up from
this signal via the ethtool interface. Currently we are only supporting
the magic-packet variant of wakeup.

WOL is enabled by specifying a second interrupt resource to the driver
which indicates where the interrupt for the WOL is being signalled. This
then enables the necessary ethtool calls to leave the device in a state
to receive WOL frames when going into suspend.

Signed-off-by: Ben Dooks <ben@simtec.co.uk>
Signed-off-by: Simtec Linux Team <linux@simtec.co.uk>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
Ben Dooks authored and David S. Miller committed Nov 12, 2009
1 parent f9254ed commit c029f44
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 8 deletions.
143 changes: 135 additions & 8 deletions drivers/net/dm9000.c
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ typedef struct board_info {

unsigned int flags;
unsigned int in_suspend :1;
unsigned int wake_supported :1;
int debug_level;

enum dm9000_type type;
Expand All @@ -116,6 +117,8 @@ typedef struct board_info {
struct resource *data_req;
struct resource *irq_res;

int irq_wake;

struct mutex addr_lock; /* phy and eeprom access lock */

struct delayed_work phy_poll;
Expand All @@ -125,6 +128,7 @@ typedef struct board_info {

struct mii_if_info mii;
u32 msg_enable;
u32 wake_state;

int rx_csum;
int can_csum;
Expand Down Expand Up @@ -568,6 +572,54 @@ static int dm9000_set_eeprom(struct net_device *dev,
return 0;
}

static void dm9000_get_wol(struct net_device *dev, struct ethtool_wolinfo *w)
{
board_info_t *dm = to_dm9000_board(dev);

memset(w, 0, sizeof(struct ethtool_wolinfo));

/* note, we could probably support wake-phy too */
w->supported = dm->wake_supported ? WAKE_MAGIC : 0;
w->wolopts = dm->wake_state;
}

static int dm9000_set_wol(struct net_device *dev, struct ethtool_wolinfo *w)
{
board_info_t *dm = to_dm9000_board(dev);
unsigned long flags;
u32 opts = w->wolopts;
u32 wcr = 0;

if (!dm->wake_supported)
return -EOPNOTSUPP;

if (opts & ~WAKE_MAGIC)
return -EINVAL;

if (opts & WAKE_MAGIC)
wcr |= WCR_MAGICEN;

mutex_lock(&dm->addr_lock);

spin_lock_irqsave(&dm->lock, flags);
iow(dm, DM9000_WCR, wcr);
spin_unlock_irqrestore(&dm->lock, flags);

mutex_unlock(&dm->addr_lock);

if (dm->wake_state != opts) {
/* change in wol state, update IRQ state */

if (!dm->wake_state)
set_irq_wake(dm->irq_wake, 1);
else if (dm->wake_state & !opts)
set_irq_wake(dm->irq_wake, 0);
}

dm->wake_state = opts;
return 0;
}

static const struct ethtool_ops dm9000_ethtool_ops = {
.get_drvinfo = dm9000_get_drvinfo,
.get_settings = dm9000_get_settings,
Expand All @@ -576,6 +628,8 @@ static const struct ethtool_ops dm9000_ethtool_ops = {
.set_msglevel = dm9000_set_msglevel,
.nway_reset = dm9000_nway_reset,
.get_link = dm9000_get_link,
.get_wol = dm9000_get_wol,
.set_wol = dm9000_set_wol,
.get_eeprom_len = dm9000_get_eeprom_len,
.get_eeprom = dm9000_get_eeprom,
.set_eeprom = dm9000_set_eeprom,
Expand Down Expand Up @@ -722,6 +776,7 @@ dm9000_init_dm9000(struct net_device *dev)
{
board_info_t *db = netdev_priv(dev);
unsigned int imr;
unsigned int ncr;

dm9000_dbg(db, 1, "entering %s\n", __func__);

Expand All @@ -736,8 +791,15 @@ dm9000_init_dm9000(struct net_device *dev)
iow(db, DM9000_GPCR, GPCR_GEP_CNTL); /* Let GPIO0 output */
iow(db, DM9000_GPR, 0); /* Enable PHY */

if (db->flags & DM9000_PLATF_EXT_PHY)
iow(db, DM9000_NCR, NCR_EXT_PHY);
ncr = (db->flags & DM9000_PLATF_EXT_PHY) ? NCR_EXT_PHY : 0;

/* if wol is needed, then always set NCR_WAKEEN otherwise we end
* up dumping the wake events if we disable this. There is already
* a wake-mask in DM9000_WCR */
if (db->wake_supported)
ncr |= NCR_WAKEEN;

iow(db, DM9000_NCR, ncr);

/* Program operating register */
iow(db, DM9000_TCR, 0); /* TX Polling clear */
Expand Down Expand Up @@ -1045,6 +1107,41 @@ static irqreturn_t dm9000_interrupt(int irq, void *dev_id)
return IRQ_HANDLED;
}

static irqreturn_t dm9000_wol_interrupt(int irq, void *dev_id)
{
struct net_device *dev = dev_id;
board_info_t *db = netdev_priv(dev);
unsigned long flags;
unsigned nsr, wcr;

spin_lock_irqsave(&db->lock, flags);

nsr = ior(db, DM9000_NSR);
wcr = ior(db, DM9000_WCR);

dev_dbg(db->dev, "%s: NSR=0x%02x, WCR=0x%02x\n", __func__, nsr, wcr);

if (nsr & NSR_WAKEST) {
/* clear, so we can avoid */
iow(db, DM9000_NSR, NSR_WAKEST);

if (wcr & WCR_LINKST)
dev_info(db->dev, "wake by link status change\n");
if (wcr & WCR_SAMPLEST)
dev_info(db->dev, "wake by sample packet\n");
if (wcr & WCR_MAGICST )
dev_info(db->dev, "wake by magic packet\n");
if (!(wcr & (WCR_LINKST | WCR_SAMPLEST | WCR_MAGICST)))
dev_err(db->dev, "wake signalled with no reason? "
"NSR=0x%02x, WSR=0x%02x\n", nsr, wcr);

}

spin_unlock_irqrestore(&db->lock, flags);

return (nsr & NSR_WAKEST) ? IRQ_HANDLED : IRQ_NONE;
}

#ifdef CONFIG_NET_POLL_CONTROLLER
/*
*Used by netconsole
Expand Down Expand Up @@ -1299,6 +1396,29 @@ dm9000_probe(struct platform_device *pdev)
goto out;
}

db->irq_wake = platform_get_irq(pdev, 1);
if (db->irq_wake >= 0) {
dev_dbg(db->dev, "wakeup irq %d\n", db->irq_wake);

ret = request_irq(db->irq_wake, dm9000_wol_interrupt,
IRQF_SHARED, dev_name(db->dev), ndev);
if (ret) {
dev_err(db->dev, "cannot get wakeup irq (%d)\n", ret);
} else {

/* test to see if irq is really wakeup capable */
ret = set_irq_wake(db->irq_wake, 1);
if (ret) {
dev_err(db->dev, "irq %d cannot set wakeup (%d)\n",
db->irq_wake, ret);
ret = 0;
} else {
set_irq_wake(db->irq_wake, 0);
db->wake_supported = 1;
}
}
}

iosize = resource_size(db->addr_res);
db->addr_req = request_mem_region(db->addr_res->start, iosize,
pdev->name);
Expand Down Expand Up @@ -1490,10 +1610,14 @@ dm9000_drv_suspend(struct device *dev)
db = netdev_priv(ndev);
db->in_suspend = 1;

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

netif_device_detach(ndev);

/* only shutdown if not using WoL */
if (!db->wake_state)
dm9000_shutdown(ndev);
}
}
return 0;
}
Expand All @@ -1506,10 +1630,13 @@ dm9000_drv_resume(struct device *dev)
board_info_t *db = netdev_priv(ndev);

if (ndev) {

if (netif_running(ndev)) {
dm9000_reset(db);
dm9000_init_dm9000(ndev);
/* reset if we were not in wake mode to ensure if
* the device was powered off it is in a known state */
if (!db->wake_state) {
dm9000_reset(db);
dm9000_init_dm9000(ndev);
}

netif_device_attach(ndev);
}
Expand Down
7 changes: 7 additions & 0 deletions drivers/net/dm9000.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,13 @@
#define RSR_CE (1<<1)
#define RSR_FOE (1<<0)

#define WCR_LINKEN (1 << 5)
#define WCR_SAMPLEEN (1 << 4)
#define WCR_MAGICEN (1 << 3)
#define WCR_LINKST (1 << 2)
#define WCR_SAMPLEST (1 << 1)
#define WCR_MAGICST (1 << 0)

#define FCTR_HWOT(ot) (( ot & 0xf ) << 4 )
#define FCTR_LWOT(ot) ( ot & 0xf )

Expand Down

0 comments on commit c029f44

Please sign in to comment.