diff options
author | Steve Glendinning <steve.glendinning@shawell.net> | 2012-10-26 03:43:56 +0000 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2012-10-31 13:32:36 -0400 |
commit | bbd9f9ee69242f23c6063f0102bbb98f5bd23521 (patch) | |
tree | b75904a46c0ea31a1e1863375536012ae580a0fa | |
parent | 9ecd1c3d6cff05fcfd11517341cc22f61651ec3e (diff) | |
download | linux-3.10-bbd9f9ee69242f23c6063f0102bbb98f5bd23521.tar.gz linux-3.10-bbd9f9ee69242f23c6063f0102bbb98f5bd23521.tar.bz2 linux-3.10-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>
-rw-r--r-- | drivers/net/usb/Kconfig | 2 | ||||
-rw-r--r-- | drivers/net/usb/smsc95xx.c | 127 | ||||
-rw-r--r-- | drivers/net/usb/smsc95xx.h | 5 |
3 files changed, 128 insertions, 6 deletions
diff --git a/drivers/net/usb/Kconfig b/drivers/net/usb/Kconfig index 2ab8043e1e2..e62882c68dd 100644 --- a/drivers/net/usb/Kconfig +++ b/drivers/net/usb/Kconfig @@ -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 diff --git a/drivers/net/usb/smsc95xx.c b/drivers/net/usb/smsc95xx.c index 1730f753d06..46cd784467d 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"); diff --git a/drivers/net/usb/smsc95xx.h b/drivers/net/usb/smsc95xx.h index 2ff9815aa27..1f862693dd7 100644 --- a/drivers/net/usb/smsc95xx.h +++ b/drivers/net/usb/smsc95xx.h @@ -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) @@ -203,8 +205,11 @@ #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) |