diff --git a/drivers/net/usb/Kconfig b/drivers/net/usb/Kconfig index 2ab8043e1e288a871bdd496a2c014290127f5ade..e62882c68dd787a092d36ba1c73073dc1ddd37f4 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 1730f753d062c8cce6127a9ea040b8862d08198e..46cd784467d588cd5a0a9caead3f5591da2d8c10 100644 --- a/drivers/net/usb/smsc95xx.c +++ b/drivers/net/usb/smsc95xx.c @@ -26,6 +26,8 @@ #include #include #include +#include +#include #include #include #include @@ -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 2ff9815aa27c5e9e098dd587e8291398f2dce25f..1f862693dd7e7174ea8675a29ceb696b43f02295 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)