tulip: implement wake-on-lan support

Based on a patch from http://simon.baatz.info/wol-support-for-an983b/

Tested to resume from suspend by magic packet.

Signed-off-by: Steven Walter <stevenrwalter@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Steven Walter 2010-05-31 12:34:43 +00:00 committed by David S. Miller
parent 7a1d7f01b5
commit db6f30078d
2 changed files with 156 additions and 23 deletions

View File

@ -20,6 +20,7 @@
#include <linux/types.h>
#include <linux/spinlock.h>
#include <linux/netdevice.h>
#include <linux/ethtool.h>
#include <linux/timer.h>
#include <linux/delay.h>
#include <linux/pci.h>
@ -51,22 +52,23 @@ struct tulip_chip_table {
enum tbl_flag {
HAS_MII = 0x0001,
HAS_MEDIA_TABLE = 0x0002,
CSR12_IN_SROM = 0x0004,
ALWAYS_CHECK_MII = 0x0008,
HAS_ACPI = 0x0010,
MC_HASH_ONLY = 0x0020, /* Hash-only multicast filter. */
HAS_PNICNWAY = 0x0080,
HAS_NWAY = 0x0040, /* Uses internal NWay xcvr. */
HAS_INTR_MITIGATION = 0x0100,
IS_ASIX = 0x0200,
HAS_8023X = 0x0400,
COMET_MAC_ADDR = 0x0800,
HAS_PCI_MWI = 0x1000,
HAS_PHY_IRQ = 0x2000,
HAS_SWAPPED_SEEPROM = 0x4000,
NEEDS_FAKE_MEDIA_TABLE = 0x8000,
HAS_MII = 0x00001,
HAS_MEDIA_TABLE = 0x00002,
CSR12_IN_SROM = 0x00004,
ALWAYS_CHECK_MII = 0x00008,
HAS_ACPI = 0x00010,
MC_HASH_ONLY = 0x00020, /* Hash-only multicast filter. */
HAS_PNICNWAY = 0x00080,
HAS_NWAY = 0x00040, /* Uses internal NWay xcvr. */
HAS_INTR_MITIGATION = 0x00100,
IS_ASIX = 0x00200,
HAS_8023X = 0x00400,
COMET_MAC_ADDR = 0x00800,
HAS_PCI_MWI = 0x01000,
HAS_PHY_IRQ = 0x02000,
HAS_SWAPPED_SEEPROM = 0x04000,
NEEDS_FAKE_MEDIA_TABLE = 0x08000,
COMET_PM = 0x10000,
};
@ -120,6 +122,11 @@ enum tulip_offsets {
CSR13 = 0x68,
CSR14 = 0x70,
CSR15 = 0x78,
CSR18 = 0x88,
CSR19 = 0x8c,
CSR20 = 0x90,
CSR27 = 0xAC,
CSR28 = 0xB0,
};
/* register offset and bits for CFDD PCI config reg */
@ -289,6 +296,30 @@ enum t21143_csr6_bits {
csr6_mask_100bt = (csr6_scr | csr6_pcs | csr6_hbd),
};
enum tulip_comet_csr13_bits {
/* The LINKOFFE and LINKONE work in conjunction with LSCE, i.e. they
* determine which link status transition wakes up if LSCE is
* enabled */
comet_csr13_linkoffe = (1 << 17),
comet_csr13_linkone = (1 << 16),
comet_csr13_wfre = (1 << 10),
comet_csr13_mpre = (1 << 9),
comet_csr13_lsce = (1 << 8),
comet_csr13_wfr = (1 << 2),
comet_csr13_mpr = (1 << 1),
comet_csr13_lsc = (1 << 0),
};
enum tulip_comet_csr18_bits {
comet_csr18_pmes_sticky = (1 << 24),
comet_csr18_pm_mode = (1 << 19),
comet_csr18_apm_mode = (1 << 18),
comet_csr18_d3a = (1 << 7)
};
enum tulip_comet_csr20_bits {
comet_csr20_pmes = (1 << 15),
};
/* Keep the ring sizes a power of two for efficiency.
Making the Tx ring too large decreases the effectiveness of channel
@ -411,6 +442,7 @@ struct tulip_private {
unsigned int csr6; /* Current CSR6 control settings. */
unsigned char eeprom[EEPROM_SIZE]; /* Serial EEPROM contents. */
void (*link_change) (struct net_device * dev, int csr5);
struct ethtool_wolinfo wolinfo; /* WOL settings */
u16 sym_advertise, mii_advertise; /* NWay capabilities advertised. */
u16 lpar; /* 21143 Link partner ability. */
u16 advertising[4];

View File

@ -30,7 +30,6 @@
#include <linux/etherdevice.h>
#include <linux/delay.h>
#include <linux/mii.h>
#include <linux/ethtool.h>
#include <linux/crc32.h>
#include <asm/unaligned.h>
#include <asm/uaccess.h>
@ -272,6 +271,7 @@ static void tulip_down(struct net_device *dev);
static struct net_device_stats *tulip_get_stats(struct net_device *dev);
static int private_ioctl(struct net_device *dev, struct ifreq *rq, int cmd);
static void set_rx_mode(struct net_device *dev);
static void tulip_set_wolopts(struct pci_dev *pdev, u32 wolopts);
#ifdef CONFIG_NET_POLL_CONTROLLER
static void poll_tulip(struct net_device *dev);
#endif
@ -309,6 +309,11 @@ static void tulip_up(struct net_device *dev)
/* Wake the chip from sleep/snooze mode. */
tulip_set_power_state (tp, 0, 0);
/* Disable all WOL events */
pci_enable_wake(tp->pdev, PCI_D3hot, 0);
pci_enable_wake(tp->pdev, PCI_D3cold, 0);
tulip_set_wolopts(tp->pdev, 0);
/* On some chip revs we must set the MII/SYM port before the reset!? */
if (tp->mii_cnt || (tp->mtable && tp->mtable->has_mii))
iowrite32(0x00040000, ioaddr + CSR6);
@ -345,8 +350,8 @@ static void tulip_up(struct net_device *dev)
} else if (tp->flags & COMET_MAC_ADDR) {
iowrite32(addr_low, ioaddr + 0xA4);
iowrite32(addr_high, ioaddr + 0xA8);
iowrite32(0, ioaddr + 0xAC);
iowrite32(0, ioaddr + 0xB0);
iowrite32(0, ioaddr + CSR27);
iowrite32(0, ioaddr + CSR28);
}
} else {
/* This is set_rx_mode(), but without starting the transmitter. */
@ -876,8 +881,35 @@ static void tulip_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *in
strcpy(info->bus_info, pci_name(np->pdev));
}
static int tulip_ethtool_set_wol(struct net_device *dev,
struct ethtool_wolinfo *wolinfo)
{
struct tulip_private *tp = netdev_priv(dev);
if (wolinfo->wolopts & (~tp->wolinfo.supported))
return -EOPNOTSUPP;
tp->wolinfo.wolopts = wolinfo->wolopts;
device_set_wakeup_enable(&tp->pdev->dev, tp->wolinfo.wolopts);
return 0;
}
static void tulip_ethtool_get_wol(struct net_device *dev,
struct ethtool_wolinfo *wolinfo)
{
struct tulip_private *tp = netdev_priv(dev);
wolinfo->supported = tp->wolinfo.supported;
wolinfo->wolopts = tp->wolinfo.wolopts;
return;
}
static const struct ethtool_ops ops = {
.get_drvinfo = tulip_get_drvinfo
.get_drvinfo = tulip_get_drvinfo,
.set_wol = tulip_ethtool_set_wol,
.get_wol = tulip_ethtool_get_wol,
};
/* Provide ioctl() calls to examine the MII xcvr state. */
@ -1093,8 +1125,8 @@ static void set_rx_mode(struct net_device *dev)
iowrite32(3, ioaddr + CSR13);
iowrite32(mc_filter[1], ioaddr + CSR14);
} else if (tp->flags & COMET_MAC_ADDR) {
iowrite32(mc_filter[0], ioaddr + 0xAC);
iowrite32(mc_filter[1], ioaddr + 0xB0);
iowrite32(mc_filter[0], ioaddr + CSR27);
iowrite32(mc_filter[1], ioaddr + CSR28);
}
tp->mc_filter[0] = mc_filter[0];
tp->mc_filter[1] = mc_filter[1];
@ -1434,6 +1466,19 @@ static int __devinit tulip_init_one (struct pci_dev *pdev,
tp->chip_id = chip_idx;
tp->flags = tulip_tbl[chip_idx].flags;
tp->wolinfo.supported = 0;
tp->wolinfo.wolopts = 0;
/* COMET: Enable power management only for AN983B */
if (chip_idx == COMET ) {
u32 sig;
pci_read_config_dword (pdev, 0x80, &sig);
if (sig == 0x09811317) {
tp->flags |= COMET_PM;
tp->wolinfo.supported = WAKE_PHY | WAKE_MAGIC;
printk(KERN_INFO "tulip_init_one: Enabled WOL support for AN983B\n");
}
}
tp->pdev = pdev;
tp->base_addr = ioaddr;
tp->revision = pdev->revision;
@ -1766,11 +1811,43 @@ err_out_free_netdev:
}
/* set the registers according to the given wolopts */
static void tulip_set_wolopts (struct pci_dev *pdev, u32 wolopts)
{
struct net_device *dev = pci_get_drvdata(pdev);
struct tulip_private *tp = netdev_priv(dev);
void __iomem *ioaddr = tp->base_addr;
if (tp->flags & COMET_PM) {
unsigned int tmp;
tmp = ioread32(ioaddr + CSR18);
tmp &= ~(comet_csr18_pmes_sticky | comet_csr18_apm_mode | comet_csr18_d3a);
tmp |= comet_csr18_pm_mode;
iowrite32(tmp, ioaddr + CSR18);
/* Set the Wake-up Control/Status Register to the given WOL options*/
tmp = ioread32(ioaddr + CSR13);
tmp &= ~(comet_csr13_linkoffe | comet_csr13_linkone | comet_csr13_wfre | comet_csr13_lsce | comet_csr13_mpre);
if (wolopts & WAKE_MAGIC)
tmp |= comet_csr13_mpre;
if (wolopts & WAKE_PHY)
tmp |= comet_csr13_linkoffe | comet_csr13_linkone | comet_csr13_lsce;
/* Clear the event flags */
tmp |= comet_csr13_wfr | comet_csr13_mpr | comet_csr13_lsc;
iowrite32(tmp, ioaddr + CSR13);
}
}
#ifdef CONFIG_PM
static int tulip_suspend (struct pci_dev *pdev, pm_message_t state)
{
pci_power_t pstate;
struct net_device *dev = pci_get_drvdata(pdev);
struct tulip_private *tp = netdev_priv(dev);
if (!dev)
return -EINVAL;
@ -1786,7 +1863,16 @@ static int tulip_suspend (struct pci_dev *pdev, pm_message_t state)
save_state:
pci_save_state(pdev);
pci_disable_device(pdev);
pci_set_power_state(pdev, pci_choose_state(pdev, state));
pstate = pci_choose_state(pdev, state);
if (state.event == PM_EVENT_SUSPEND && pstate != PCI_D0) {
int rc;
tulip_set_wolopts(pdev, tp->wolinfo.wolopts);
rc = pci_enable_wake(pdev, pstate, tp->wolinfo.wolopts);
if (rc)
printk("tulip: pci_enable_wake failed (%d)\n", rc);
}
pci_set_power_state(pdev, pstate);
return 0;
}
@ -1795,7 +1881,10 @@ save_state:
static int tulip_resume(struct pci_dev *pdev)
{
struct net_device *dev = pci_get_drvdata(pdev);
struct tulip_private *tp = netdev_priv(dev);
void __iomem *ioaddr = tp->base_addr;
int retval;
unsigned int tmp;
if (!dev)
return -EINVAL;
@ -1816,6 +1905,18 @@ static int tulip_resume(struct pci_dev *pdev)
return retval;
}
if (tp->flags & COMET_PM) {
pci_enable_wake(pdev, PCI_D3hot, 0);
pci_enable_wake(pdev, PCI_D3cold, 0);
/* Clear the PMES flag */
tmp = ioread32(ioaddr + CSR20);
tmp |= comet_csr20_pmes;
iowrite32(tmp, ioaddr + CSR20);
/* Disable all wake-up events */
tulip_set_wolopts(pdev, 0);
}
netif_device_attach(dev);
if (netif_running(dev))