diff options
author | Steve Glendinning <steve.glendinning@shawell.net> | 2012-10-26 05:43:56 +0200 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2012-10-31 18:32:36 +0100 |
commit | bbd9f9ee69242f23c6063f0102bbb98f5bd23521 (patch) | |
tree | b75904a46c0ea31a1e1863375536012ae580a0fa /drivers/net/usb/smsc95xx.c | |
parent | net/ipv4/ipconfig: add device address to a KERN_INFO message (diff) | |
download | linux-bbd9f9ee69242f23c6063f0102bbb98f5bd23521.tar.xz linux-bbd9f9ee69242f23c6063f0102bbb98f5bd23521.zip |
smsc95xx: add wol support for more frame types
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>
Diffstat (limited to 'drivers/net/usb/smsc95xx.c')
-rw-r--r-- | drivers/net/usb/smsc95xx.c | 127 |
1 files changed, 121 insertions, 6 deletions
diff --git a/drivers/net/usb/smsc95xx.c b/drivers/net/usb/smsc95xx.c index 1730f753d062..46cd784467d5 100644 --- a/drivers/net/usb/smsc95xx.c +++ b/drivers/net/usb/smsc95xx.c @@ -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"); |