tulip: implement wake-on-lan support
authorSteven Walter <stevenrwalter@gmail.com>
Mon, 31 May 2010 12:34:43 +0000 (12:34 +0000)
committerDavid S. Miller <davem@davemloft.net>
Tue, 1 Jun 2010 07:15:51 +0000 (00:15 -0700)
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>
drivers/net/tulip/tulip.h
drivers/net/tulip/tulip_core.c

index 0afa2d4..e525875 100644 (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];
index bd3b41d..03e96b9 100644 (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))