smsc95xx: add wol support for more frame types
authorSteve Glendinning <steve.glendinning@shawell.net>
Fri, 26 Oct 2012 03:43:56 +0000 (03:43 +0000)
committerDavid S. Miller <davem@davemloft.net>
Wed, 31 Oct 2012 17:32:36 +0000 (13:32 -0400)
This patch adds support for wol wakeup on unicast, broadcast,
multicast and arp frames.

The wakeup filter code isn't pretty, but it works.

Signed-off-by: Steve Glendinning <steve.glendinning@shawell.net>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/usb/Kconfig
drivers/net/usb/smsc95xx.c
drivers/net/usb/smsc95xx.h

index 2ab8043e1e288a871bdd496a2c014290127f5ade..e62882c68dd787a092d36ba1c73073dc1ddd37f4 100644 (file)
@@ -256,6 +256,8 @@ config USB_NET_SMSC75XX
 config USB_NET_SMSC95XX
        tristate "SMSC LAN95XX based USB 2.0 10/100 ethernet devices"
        depends on USB_USBNET
+       select BITREVERSE
+       select CRC16
        select CRC32
        help
          This option adds support for SMSC LAN95XX based USB 2.0
index 1730f753d062c8cce6127a9ea040b8862d08198e..46cd784467d588cd5a0a9caead3f5591da2d8c10 100644 (file)
@@ -26,6 +26,8 @@
 #include <linux/ethtool.h>
 #include <linux/mii.h>
 #include <linux/usb.h>
+#include <linux/bitrev.h>
+#include <linux/crc16.h>
 #include <linux/crc32.h>
 #include <linux/usb/usbnet.h>
 #include <linux/slab.h>
@@ -46,7 +48,8 @@
 #define SMSC95XX_INTERNAL_PHY_ID       (1)
 #define SMSC95XX_TX_OVERHEAD           (8)
 #define SMSC95XX_TX_OVERHEAD_CSUM      (12)
-#define SUPPORTED_WAKE                 (WAKE_MAGIC)
+#define SUPPORTED_WAKE                 (WAKE_UCAST | WAKE_BCAST | \
+                                        WAKE_MCAST | WAKE_ARP | WAKE_MAGIC)
 
 #define check_warn(ret, fmt, args...) \
        ({ if (ret < 0) netdev_warn(dev->net, fmt, ##args); })
@@ -63,6 +66,7 @@ struct smsc95xx_priv {
        u32 hash_lo;
        u32 wolopts;
        spinlock_t mac_cr_lock;
+       int wuff_filter_count;
 };
 
 static bool turbo_mode = true;
@@ -956,6 +960,7 @@ static const struct net_device_ops smsc95xx_netdev_ops = {
 static int smsc95xx_bind(struct usbnet *dev, struct usb_interface *intf)
 {
        struct smsc95xx_priv *pdata = NULL;
+       u32 val;
        int ret;
 
        printk(KERN_INFO SMSC_CHIPNAME " v" SMSC_DRIVER_VERSION "\n");
@@ -986,6 +991,15 @@ static int smsc95xx_bind(struct usbnet *dev, struct usb_interface *intf)
        /* Init all registers */
        ret = smsc95xx_reset(dev);
 
+       /* detect device revision as different features may be available */
+       ret = smsc95xx_read_reg(dev, ID_REV, &val);
+       check_warn_return(ret, "Failed to read ID_REV: %d\n", ret);
+       val >>= 16;
+       if ((val == ID_REV_CHIP_ID_9500A_) || (val == ID_REV_CHIP_ID_9512_))
+               pdata->wuff_filter_count = LAN9500A_WUFF_NUM;
+       else
+               pdata->wuff_filter_count = LAN9500_WUFF_NUM;
+
        dev->net->netdev_ops = &smsc95xx_netdev_ops;
        dev->net->ethtool_ops = &smsc95xx_ethtool_ops;
        dev->net->flags |= IFF_MULTICAST;
@@ -1005,6 +1019,11 @@ static void smsc95xx_unbind(struct usbnet *dev, struct usb_interface *intf)
        }
 }
 
+static u16 smsc_crc(const u8 *buffer, size_t len, int filter)
+{
+       return bitrev16(crc16(0xFFFF, buffer, len)) << ((filter % 2) * 16);
+}
+
 static int smsc95xx_suspend(struct usb_interface *intf, pm_message_t message)
 {
        struct usbnet *dev = usb_get_intfdata(intf);
@@ -1049,6 +1068,94 @@ static int smsc95xx_suspend(struct usb_interface *intf, pm_message_t message)
                return 0;
        }
 
+       if (pdata->wolopts & (WAKE_BCAST | WAKE_MCAST | WAKE_ARP | WAKE_UCAST)) {
+               u32 *filter_mask = kzalloc(32, GFP_KERNEL);
+               u32 *command = kzalloc(2, GFP_KERNEL);
+               u32 *offset = kzalloc(2, GFP_KERNEL);
+               u32 *crc = kzalloc(4, GFP_KERNEL);
+               int i, filter = 0;
+
+               if (pdata->wolopts & WAKE_BCAST) {
+                       const u8 bcast[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+                       netdev_info(dev->net, "enabling broadcast detection");
+                       filter_mask[filter * 4] = 0x003F;
+                       filter_mask[filter * 4 + 1] = 0x00;
+                       filter_mask[filter * 4 + 2] = 0x00;
+                       filter_mask[filter * 4 + 3] = 0x00;
+                       command[filter/4] |= 0x05UL << ((filter % 4) * 8);
+                       offset[filter/4] |= 0x00 << ((filter % 4) * 8);
+                       crc[filter/2] |= smsc_crc(bcast, 6, filter);
+                       filter++;
+               }
+
+               if (pdata->wolopts & WAKE_MCAST) {
+                       const u8 mcast[] = {0x01, 0x00, 0x5E};
+                       netdev_info(dev->net, "enabling multicast detection");
+                       filter_mask[filter * 4] = 0x0007;
+                       filter_mask[filter * 4 + 1] = 0x00;
+                       filter_mask[filter * 4 + 2] = 0x00;
+                       filter_mask[filter * 4 + 3] = 0x00;
+                       command[filter/4] |= 0x09UL << ((filter % 4) * 8);
+                       offset[filter/4] |= 0x00  << ((filter % 4) * 8);
+                       crc[filter/2] |= smsc_crc(mcast, 3, filter);
+                       filter++;
+               }
+
+               if (pdata->wolopts & WAKE_ARP) {
+                       const u8 arp[] = {0x08, 0x06};
+                       netdev_info(dev->net, "enabling ARP detection");
+                       filter_mask[filter * 4] = 0x0003;
+                       filter_mask[filter * 4 + 1] = 0x00;
+                       filter_mask[filter * 4 + 2] = 0x00;
+                       filter_mask[filter * 4 + 3] = 0x00;
+                       command[filter/4] |= 0x05UL << ((filter % 4) * 8);
+                       offset[filter/4] |= 0x0C << ((filter % 4) * 8);
+                       crc[filter/2] |= smsc_crc(arp, 2, filter);
+                       filter++;
+               }
+
+               if (pdata->wolopts & WAKE_UCAST) {
+                       netdev_info(dev->net, "enabling unicast detection");
+                       filter_mask[filter * 4] = 0x003F;
+                       filter_mask[filter * 4 + 1] = 0x00;
+                       filter_mask[filter * 4 + 2] = 0x00;
+                       filter_mask[filter * 4 + 3] = 0x00;
+                       command[filter/4] |= 0x01UL << ((filter % 4) * 8);
+                       offset[filter/4] |= 0x00 << ((filter % 4) * 8);
+                       crc[filter/2] |= smsc_crc(dev->net->dev_addr, ETH_ALEN, filter);
+                       filter++;
+               }
+
+               for (i = 0; i < (pdata->wuff_filter_count * 4); i++) {
+                       ret = smsc95xx_write_reg(dev, WUFF, filter_mask[i]);
+                       check_warn_return(ret, "Error writing WUFF");
+               }
+
+               for (i = 0; i < (pdata->wuff_filter_count / 4); i++) {
+                       ret = smsc95xx_write_reg(dev, WUFF, command[i]);
+                       check_warn_return(ret, "Error writing WUFF");
+               }
+
+               for (i = 0; i < (pdata->wuff_filter_count / 4); i++) {
+                       ret = smsc95xx_write_reg(dev, WUFF, offset[i]);
+                       check_warn_return(ret, "Error writing WUFF");
+               }
+
+               for (i = 0; i < (pdata->wuff_filter_count / 2); i++) {
+                       ret = smsc95xx_write_reg(dev, WUFF, crc[i]);
+                       check_warn_return(ret, "Error writing WUFF");
+               }
+
+               /* clear any pending pattern match packet status */
+               ret = smsc95xx_read_reg(dev, WUCSR, &val);
+               check_warn_return(ret, "Error reading WUCSR");
+
+               val |= WUCSR_WUFR_;
+
+               ret = smsc95xx_write_reg(dev, WUCSR, val);
+               check_warn_return(ret, "Error writing WUCSR");
+       }
+
        if (pdata->wolopts & WAKE_MAGIC) {
                /* clear any pending magic packet status */
                ret = smsc95xx_read_reg(dev, WUCSR, &val);
@@ -1060,10 +1167,18 @@ static int smsc95xx_suspend(struct usb_interface *intf, pm_message_t message)
                check_warn_return(ret, "Error writing WUCSR");
        }
 
-       /* enable/disable magic packup wake */
+       /* enable/disable wakeup sources */
        ret = smsc95xx_read_reg(dev, WUCSR, &val);
        check_warn_return(ret, "Error reading WUCSR");
 
+       if (pdata->wolopts & (WAKE_BCAST | WAKE_MCAST | WAKE_ARP | WAKE_UCAST)) {
+               netdev_info(dev->net, "enabling pattern match wakeup");
+               val |= WUCSR_WAKE_EN_;
+       } else {
+               netdev_info(dev->net, "disabling pattern match wakeup");
+               val &= ~WUCSR_WAKE_EN_;
+       }
+
        if (pdata->wolopts & WAKE_MAGIC) {
                netdev_info(dev->net, "enabling magic packet wakeup");
                val |= WUCSR_MPEN_;
@@ -1084,7 +1199,7 @@ static int smsc95xx_suspend(struct usb_interface *intf, pm_message_t message)
        ret = smsc95xx_write_reg(dev, PM_CTRL, val);
        check_warn_return(ret, "Error writing PM_CTRL");
 
-       /* enable receiver */
+       /* enable receiver to enable frame reception */
        smsc95xx_start_rx_path(dev);
 
        /* some wol options are enabled, so enter SUSPEND0 */
@@ -1123,14 +1238,14 @@ static int smsc95xx_resume(struct usb_interface *intf)
 
        BUG_ON(!dev);
 
-       if (pdata->wolopts & WAKE_MAGIC) {
+       if (pdata->wolopts) {
                smsc95xx_clear_feature(dev, USB_DEVICE_REMOTE_WAKEUP);
 
-               /* Disable magic packup wake */
+               /* clear wake-up sources */
                ret = smsc95xx_read_reg(dev, WUCSR, &val);
                check_warn_return(ret, "Error reading WUCSR");
 
-               val &= ~WUCSR_MPEN_;
+               val &= ~(WUCSR_WAKE_EN_ | WUCSR_MPEN_);
 
                ret = smsc95xx_write_reg(dev, WUCSR, val);
                check_warn_return(ret, "Error writing WUCSR");
index 2ff9815aa27c5e9e098dd587e8291398f2dce25f..1f862693dd7e7174ea8675a29ceb696b43f02295 100644 (file)
@@ -53,6 +53,8 @@
 #define ID_REV_CHIP_ID_MASK_           (0xFFFF0000)
 #define ID_REV_CHIP_REV_MASK_          (0x0000FFFF)
 #define ID_REV_CHIP_ID_9500_           (0x9500)
+#define ID_REV_CHIP_ID_9500A_          (0x9E00)
+#define ID_REV_CHIP_ID_9512_           (0xEC00)
 
 #define INT_STS                                (0x08)
 #define INT_STS_TX_STOP_               (0x00020000)
 #define VLAN2                          (0x124)
 
 #define WUFF                           (0x128)
+#define LAN9500_WUFF_NUM               (4)
+#define LAN9500A_WUFF_NUM              (8)
 
 #define WUCSR                          (0x12C)
+#define WUCSR_WFF_PTR_RST_             (0x80000000)
 #define WUCSR_GUE_                     (0x00000200)
 #define WUCSR_WUFR_                    (0x00000040)
 #define WUCSR_MPR_                     (0x00000020)