summaryrefslogtreecommitdiff
path: root/drivers/net/usb/smsc75xx.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/usb/smsc75xx.c')
-rw-r--r--drivers/net/usb/smsc75xx.c297
1 files changed, 204 insertions, 93 deletions
diff --git a/drivers/net/usb/smsc75xx.c b/drivers/net/usb/smsc75xx.c
index b77ae76f4aa8..c5353cfc9c8c 100644
--- a/drivers/net/usb/smsc75xx.c
+++ b/drivers/net/usb/smsc75xx.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>
@@ -52,7 +54,8 @@
#define USB_PRODUCT_ID_LAN7500 (0x7500)
#define USB_PRODUCT_ID_LAN7505 (0x7505)
#define RXW_PADDING 2
-#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); })
@@ -82,71 +85,92 @@ static bool turbo_mode = true;
module_param(turbo_mode, bool, 0644);
MODULE_PARM_DESC(turbo_mode, "Enable multiple frames per Rx transaction");
-static int __must_check smsc75xx_read_reg(struct usbnet *dev, u32 index,
- u32 *data)
+static int __must_check __smsc75xx_read_reg(struct usbnet *dev, u32 index,
+ u32 *data, int in_pm)
{
- u32 *buf = kmalloc(4, GFP_KERNEL);
+ u32 buf;
int ret;
+ int (*fn)(struct usbnet *, u8, u8, u16, u16, void *, u16);
BUG_ON(!dev);
- if (!buf)
- return -ENOMEM;
-
- ret = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0),
- USB_VENDOR_REQUEST_READ_REGISTER,
- USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
- 00, index, buf, 4, USB_CTRL_GET_TIMEOUT);
+ if (!in_pm)
+ fn = usbnet_read_cmd;
+ else
+ fn = usbnet_read_cmd_nopm;
+ ret = fn(dev, USB_VENDOR_REQUEST_READ_REGISTER, USB_DIR_IN
+ | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ 0, index, &buf, 4);
if (unlikely(ret < 0))
netdev_warn(dev->net,
"Failed to read reg index 0x%08x: %d", index, ret);
- le32_to_cpus(buf);
- *data = *buf;
- kfree(buf);
+ le32_to_cpus(&buf);
+ *data = buf;
return ret;
}
-static int __must_check smsc75xx_write_reg(struct usbnet *dev, u32 index,
- u32 data)
+static int __must_check __smsc75xx_write_reg(struct usbnet *dev, u32 index,
+ u32 data, int in_pm)
{
- u32 *buf = kmalloc(4, GFP_KERNEL);
+ u32 buf;
int ret;
+ int (*fn)(struct usbnet *, u8, u8, u16, u16, const void *, u16);
BUG_ON(!dev);
- if (!buf)
- return -ENOMEM;
-
- *buf = data;
- cpu_to_le32s(buf);
+ if (!in_pm)
+ fn = usbnet_write_cmd;
+ else
+ fn = usbnet_write_cmd_nopm;
- ret = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0),
- USB_VENDOR_REQUEST_WRITE_REGISTER,
- USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
- 00, index, buf, 4, USB_CTRL_SET_TIMEOUT);
+ buf = data;
+ cpu_to_le32s(&buf);
+ ret = fn(dev, USB_VENDOR_REQUEST_WRITE_REGISTER, USB_DIR_OUT
+ | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ 0, index, &buf, 4);
if (unlikely(ret < 0))
netdev_warn(dev->net,
"Failed to write reg index 0x%08x: %d", index, ret);
- kfree(buf);
-
return ret;
}
+static int __must_check smsc75xx_read_reg_nopm(struct usbnet *dev, u32 index,
+ u32 *data)
+{
+ return __smsc75xx_read_reg(dev, index, data, 1);
+}
+
+static int __must_check smsc75xx_write_reg_nopm(struct usbnet *dev, u32 index,
+ u32 data)
+{
+ return __smsc75xx_write_reg(dev, index, data, 1);
+}
+
+static int __must_check smsc75xx_read_reg(struct usbnet *dev, u32 index,
+ u32 *data)
+{
+ return __smsc75xx_read_reg(dev, index, data, 0);
+}
+
+static int __must_check smsc75xx_write_reg(struct usbnet *dev, u32 index,
+ u32 data)
+{
+ return __smsc75xx_write_reg(dev, index, data, 0);
+}
+
static int smsc75xx_set_feature(struct usbnet *dev, u32 feature)
{
if (WARN_ON_ONCE(!dev))
return -EINVAL;
- cpu_to_le32s(&feature);
-
- return usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0),
- USB_REQ_SET_FEATURE, USB_RECIP_DEVICE, feature, 0, NULL, 0,
- USB_CTRL_SET_TIMEOUT);
+ return usbnet_write_cmd_nopm(dev, USB_REQ_SET_FEATURE,
+ USB_DIR_OUT | USB_RECIP_DEVICE,
+ feature, 0, NULL, 0);
}
static int smsc75xx_clear_feature(struct usbnet *dev, u32 feature)
@@ -154,11 +178,9 @@ static int smsc75xx_clear_feature(struct usbnet *dev, u32 feature)
if (WARN_ON_ONCE(!dev))
return -EINVAL;
- cpu_to_le32s(&feature);
-
- return usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0),
- USB_REQ_CLEAR_FEATURE, USB_RECIP_DEVICE, feature, 0, NULL, 0,
- USB_CTRL_SET_TIMEOUT);
+ return usbnet_write_cmd_nopm(dev, USB_REQ_CLEAR_FEATURE,
+ USB_DIR_OUT | USB_RECIP_DEVICE,
+ feature, 0, NULL, 0);
}
/* Loop until the read is completed with timeout
@@ -804,13 +826,16 @@ static int smsc75xx_set_features(struct net_device *netdev,
return 0;
}
-static int smsc75xx_wait_ready(struct usbnet *dev)
+static int smsc75xx_wait_ready(struct usbnet *dev, int in_pm)
{
int timeout = 0;
do {
u32 buf;
- int ret = smsc75xx_read_reg(dev, PMT_CTL, &buf);
+ int ret;
+
+ ret = __smsc75xx_read_reg(dev, PMT_CTL, &buf, in_pm);
+
check_warn_return(ret, "Failed to read PMT_CTL: %d", ret);
if (buf & PMT_CTL_DEV_RDY)
@@ -832,7 +857,7 @@ static int smsc75xx_reset(struct usbnet *dev)
netif_dbg(dev, ifup, dev->net, "entering smsc75xx_reset");
- ret = smsc75xx_wait_ready(dev);
+ ret = smsc75xx_wait_ready(dev, 0);
check_warn_return(ret, "device not ready in smsc75xx_reset");
ret = smsc75xx_read_reg(dev, HW_CFG, &buf);
@@ -1154,6 +1179,36 @@ static void smsc75xx_unbind(struct usbnet *dev, struct usb_interface *intf)
}
}
+static u16 smsc_crc(const u8 *buffer, size_t len)
+{
+ return bitrev16(crc16(0xFFFF, buffer, len));
+}
+
+static int smsc75xx_write_wuff(struct usbnet *dev, int filter, u32 wuf_cfg,
+ u32 wuf_mask1)
+{
+ int cfg_base = WUF_CFGX + filter * 4;
+ int mask_base = WUF_MASKX + filter * 16;
+ int ret;
+
+ ret = smsc75xx_write_reg(dev, cfg_base, wuf_cfg);
+ check_warn_return(ret, "Error writing WUF_CFGX");
+
+ ret = smsc75xx_write_reg(dev, mask_base, wuf_mask1);
+ check_warn_return(ret, "Error writing WUF_MASKX");
+
+ ret = smsc75xx_write_reg(dev, mask_base + 4, 0);
+ check_warn_return(ret, "Error writing WUF_MASKX");
+
+ ret = smsc75xx_write_reg(dev, mask_base + 8, 0);
+ check_warn_return(ret, "Error writing WUF_MASKX");
+
+ ret = smsc75xx_write_reg(dev, mask_base + 12, 0);
+ check_warn_return(ret, "Error writing WUF_MASKX");
+
+ return 0;
+}
+
static int smsc75xx_suspend(struct usb_interface *intf, pm_message_t message)
{
struct usbnet *dev = usb_get_intfdata(intf);
@@ -1169,101 +1224,156 @@ static int smsc75xx_suspend(struct usb_interface *intf, pm_message_t message)
netdev_info(dev->net, "entering SUSPEND2 mode");
/* disable energy detect (link up) & wake up events */
- ret = smsc75xx_read_reg(dev, WUCSR, &val);
+ ret = smsc75xx_read_reg_nopm(dev, WUCSR, &val);
check_warn_return(ret, "Error reading WUCSR");
val &= ~(WUCSR_MPEN | WUCSR_WUEN);
- ret = smsc75xx_write_reg(dev, WUCSR, val);
+ ret = smsc75xx_write_reg_nopm(dev, WUCSR, val);
check_warn_return(ret, "Error writing WUCSR");
- ret = smsc75xx_read_reg(dev, PMT_CTL, &val);
+ ret = smsc75xx_read_reg_nopm(dev, PMT_CTL, &val);
check_warn_return(ret, "Error reading PMT_CTL");
val &= ~(PMT_CTL_ED_EN | PMT_CTL_WOL_EN);
- ret = smsc75xx_write_reg(dev, PMT_CTL, val);
+ ret = smsc75xx_write_reg_nopm(dev, PMT_CTL, val);
check_warn_return(ret, "Error writing PMT_CTL");
/* enter suspend2 mode */
- ret = smsc75xx_read_reg(dev, PMT_CTL, &val);
+ ret = smsc75xx_read_reg_nopm(dev, PMT_CTL, &val);
check_warn_return(ret, "Error reading PMT_CTL");
val &= ~(PMT_CTL_SUS_MODE | PMT_CTL_WUPS | PMT_CTL_PHY_RST);
val |= PMT_CTL_SUS_MODE_2;
- ret = smsc75xx_write_reg(dev, PMT_CTL, val);
+ ret = smsc75xx_write_reg_nopm(dev, PMT_CTL, val);
check_warn_return(ret, "Error writing PMT_CTL");
return 0;
}
- if (pdata->wolopts & WAKE_MAGIC) {
- /* clear any pending magic packet status */
- ret = smsc75xx_read_reg(dev, WUCSR, &val);
+ if (pdata->wolopts & (WAKE_MCAST | WAKE_ARP)) {
+ int i, filter = 0;
+
+ /* disable all filters */
+ for (i = 0; i < WUF_NUM; i++) {
+ ret = smsc75xx_write_reg_nopm(dev, WUF_CFGX + i * 4, 0);
+ check_warn_return(ret, "Error writing WUF_CFGX");
+ }
+
+ if (pdata->wolopts & WAKE_MCAST) {
+ const u8 mcast[] = {0x01, 0x00, 0x5E};
+ netdev_info(dev->net, "enabling multicast detection");
+
+ val = WUF_CFGX_EN | WUF_CFGX_ATYPE_MULTICAST
+ | smsc_crc(mcast, 3);
+ ret = smsc75xx_write_wuff(dev, filter++, val, 0x0007);
+ check_warn_return(ret, "Error writing wakeup filter");
+ }
+
+ if (pdata->wolopts & WAKE_ARP) {
+ const u8 arp[] = {0x08, 0x06};
+ netdev_info(dev->net, "enabling ARP detection");
+
+ val = WUF_CFGX_EN | WUF_CFGX_ATYPE_ALL | (0x0C << 16)
+ | smsc_crc(arp, 2);
+ ret = smsc75xx_write_wuff(dev, filter++, val, 0x0003);
+ check_warn_return(ret, "Error writing wakeup filter");
+ }
+
+ /* clear any pending pattern match packet status */
+ ret = smsc75xx_read_reg_nopm(dev, WUCSR, &val);
check_warn_return(ret, "Error reading WUCSR");
- val |= WUCSR_MPR;
+ val |= WUCSR_WUFR;
- ret = smsc75xx_write_reg(dev, WUCSR, val);
+ ret = smsc75xx_write_reg_nopm(dev, WUCSR, val);
+ check_warn_return(ret, "Error writing WUCSR");
+
+ netdev_info(dev->net, "enabling packet match detection");
+ ret = smsc75xx_read_reg_nopm(dev, WUCSR, &val);
+ check_warn_return(ret, "Error reading WUCSR");
+
+ val |= WUCSR_WUEN;
+
+ ret = smsc75xx_write_reg_nopm(dev, WUCSR, val);
+ check_warn_return(ret, "Error writing WUCSR");
+ } else {
+ netdev_info(dev->net, "disabling packet match detection");
+ ret = smsc75xx_read_reg_nopm(dev, WUCSR, &val);
+ check_warn_return(ret, "Error reading WUCSR");
+
+ val &= ~WUCSR_WUEN;
+
+ ret = smsc75xx_write_reg_nopm(dev, WUCSR, val);
check_warn_return(ret, "Error writing WUCSR");
}
- /* enable/disable magic packup wake */
- ret = smsc75xx_read_reg(dev, WUCSR, &val);
+ /* disable magic, bcast & unicast wakeup sources */
+ ret = smsc75xx_read_reg_nopm(dev, WUCSR, &val);
check_warn_return(ret, "Error reading WUCSR");
+ val &= ~(WUCSR_MPEN | WUCSR_BCST_EN | WUCSR_PFDA_EN);
+
+ ret = smsc75xx_write_reg_nopm(dev, WUCSR, val);
+ check_warn_return(ret, "Error writing WUCSR");
+
if (pdata->wolopts & WAKE_MAGIC) {
netdev_info(dev->net, "enabling magic packet wakeup");
- val |= WUCSR_MPEN;
- } else {
- netdev_info(dev->net, "disabling magic packet wakeup");
- val &= ~WUCSR_MPEN;
+ ret = smsc75xx_read_reg_nopm(dev, WUCSR, &val);
+ check_warn_return(ret, "Error reading WUCSR");
+
+ /* clear any pending magic packet status */
+ val |= WUCSR_MPR | WUCSR_MPEN;
+
+ ret = smsc75xx_write_reg_nopm(dev, WUCSR, val);
+ check_warn_return(ret, "Error writing WUCSR");
}
- ret = smsc75xx_write_reg(dev, WUCSR, val);
- check_warn_return(ret, "Error writing WUCSR");
+ if (pdata->wolopts & WAKE_BCAST) {
+ netdev_info(dev->net, "enabling broadcast detection");
+ ret = smsc75xx_read_reg_nopm(dev, WUCSR, &val);
+ check_warn_return(ret, "Error reading WUCSR");
- /* enable wol wakeup source */
- ret = smsc75xx_read_reg(dev, PMT_CTL, &val);
- check_warn_return(ret, "Error reading PMT_CTL");
+ val |= WUCSR_BCAST_FR | WUCSR_BCST_EN;
- val |= PMT_CTL_WOL_EN;
+ ret = smsc75xx_write_reg_nopm(dev, WUCSR, val);
+ check_warn_return(ret, "Error writing WUCSR");
+ }
- ret = smsc75xx_write_reg(dev, PMT_CTL, val);
- check_warn_return(ret, "Error writing PMT_CTL");
+ if (pdata->wolopts & WAKE_UCAST) {
+ netdev_info(dev->net, "enabling unicast detection");
+ ret = smsc75xx_read_reg_nopm(dev, WUCSR, &val);
+ check_warn_return(ret, "Error reading WUCSR");
+
+ val |= WUCSR_WUFR | WUCSR_PFDA_EN;
+
+ ret = smsc75xx_write_reg_nopm(dev, WUCSR, val);
+ check_warn_return(ret, "Error writing WUCSR");
+ }
- /* enable receiver */
- ret = smsc75xx_read_reg(dev, MAC_RX, &val);
+ /* enable receiver to enable frame reception */
+ ret = smsc75xx_read_reg_nopm(dev, MAC_RX, &val);
check_warn_return(ret, "Failed to read MAC_RX: %d", ret);
val |= MAC_RX_RXEN;
- ret = smsc75xx_write_reg(dev, MAC_RX, val);
+ ret = smsc75xx_write_reg_nopm(dev, MAC_RX, val);
check_warn_return(ret, "Failed to write MAC_RX: %d", ret);
/* some wol options are enabled, so enter SUSPEND0 */
netdev_info(dev->net, "entering SUSPEND0 mode");
- ret = smsc75xx_read_reg(dev, PMT_CTL, &val);
+ ret = smsc75xx_read_reg_nopm(dev, PMT_CTL, &val);
check_warn_return(ret, "Error reading PMT_CTL");
- val &= (~(PMT_CTL_SUS_MODE | PMT_CTL_WUPS | PMT_CTL_PHY_RST));
- val |= PMT_CTL_SUS_MODE_0;
+ val &= (~(PMT_CTL_SUS_MODE | PMT_CTL_PHY_RST));
+ val |= PMT_CTL_SUS_MODE_0 | PMT_CTL_WOL_EN | PMT_CTL_WUPS;
- ret = smsc75xx_write_reg(dev, PMT_CTL, val);
+ ret = smsc75xx_write_reg_nopm(dev, PMT_CTL, val);
check_warn_return(ret, "Error writing PMT_CTL");
- /* clear wol status */
- val &= ~PMT_CTL_WUPS;
- val |= PMT_CTL_WUPS_WOL;
- ret = smsc75xx_write_reg(dev, PMT_CTL, val);
- check_warn_return(ret, "Error writing PMT_CTL");
-
- /* read back PMT_CTL */
- ret = smsc75xx_read_reg(dev, PMT_CTL, &val);
- check_warn_return(ret, "Error reading PMT_CTL");
-
smsc75xx_set_feature(dev, USB_DEVICE_REMOTE_WAKEUP);
return 0;
@@ -1276,42 +1386,43 @@ static int smsc75xx_resume(struct usb_interface *intf)
int ret;
u32 val;
- if (pdata->wolopts & WAKE_MAGIC) {
+ if (pdata->wolopts) {
netdev_info(dev->net, "resuming from SUSPEND0");
smsc75xx_clear_feature(dev, USB_DEVICE_REMOTE_WAKEUP);
- /* Disable magic packup wake */
- ret = smsc75xx_read_reg(dev, WUCSR, &val);
+ /* Disable wakeup sources */
+ ret = smsc75xx_read_reg_nopm(dev, WUCSR, &val);
check_warn_return(ret, "Error reading WUCSR");
- val &= ~WUCSR_MPEN;
+ val &= ~(WUCSR_WUEN | WUCSR_MPEN | WUCSR_PFDA_EN
+ | WUCSR_BCST_EN);
- ret = smsc75xx_write_reg(dev, WUCSR, val);
+ ret = smsc75xx_write_reg_nopm(dev, WUCSR, val);
check_warn_return(ret, "Error writing WUCSR");
/* clear wake-up status */
- ret = smsc75xx_read_reg(dev, PMT_CTL, &val);
+ ret = smsc75xx_read_reg_nopm(dev, PMT_CTL, &val);
check_warn_return(ret, "Error reading PMT_CTL");
val &= ~PMT_CTL_WOL_EN;
val |= PMT_CTL_WUPS;
- ret = smsc75xx_write_reg(dev, PMT_CTL, val);
+ ret = smsc75xx_write_reg_nopm(dev, PMT_CTL, val);
check_warn_return(ret, "Error writing PMT_CTL");
} else {
netdev_info(dev->net, "resuming from SUSPEND2");
- ret = smsc75xx_read_reg(dev, PMT_CTL, &val);
+ ret = smsc75xx_read_reg_nopm(dev, PMT_CTL, &val);
check_warn_return(ret, "Error reading PMT_CTL");
val |= PMT_CTL_PHY_PWRUP;
- ret = smsc75xx_write_reg(dev, PMT_CTL, val);
+ ret = smsc75xx_write_reg_nopm(dev, PMT_CTL, val);
check_warn_return(ret, "Error writing PMT_CTL");
}
- ret = smsc75xx_wait_ready(dev);
+ ret = smsc75xx_wait_ready(dev, 1);
check_warn_return(ret, "device not ready in smsc75xx_resume");
return usbnet_resume(intf);