Commit bbd9f9ee authored by Steve Glendinning's avatar Steve Glendinning Committed by David S. Miller

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: default avatarSteve Glendinning <steve.glendinning@shawell.net>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 9ecd1c3d
......@@ -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
......
......@@ -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");
......
......@@ -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)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment