DM9620 is an USB2.0 network adapter rather than DM9601 USB1.1. This
driver processed the RX data 4 bytes header, TX data 2 bytes header,
make the control bit exactly right in PHY write function, and optional
IFF_ALLMUTLI bit for RX control.
Tested good for many platforms, include X86 desktop and ARM embedded.
Signed-off-by: Joseph CHANG <[email protected]>
---
drivers/net/usb/Kconfig | 13 +
drivers/net/usb/Makefile | 1 +
drivers/net/usb/dm9620.c | 796 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 810 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/usb/dm9620.c
diff --git a/drivers/net/usb/Kconfig b/drivers/net/usb/Kconfig
index 287cc62..e9c9e2a 100644
--- a/drivers/net/usb/Kconfig
+++ b/drivers/net/usb/Kconfig
@@ -272,6 +272,19 @@ config USB_NET_DM9601
This option adds support for Davicom DM9601 based USB 1.1
10/100 Ethernet adapters.
+config USB_NET_DM9620
+ tristate "Davicom DM9620/21 based USB 2.0 10/100 ethernet devices"
+ depends on USB_USBNET
+ select CRC32
+ help
+ This option adds support for Davicom DM9620 based USB 2.0
+ 10/100 Ethernet adapters.
+
+ This driver creates an interface named "ethX", where X depends on
+ what other networking devices you have in use.
+
+ To compile this driver as a module, choose M here.
+
config USB_NET_SMSC75XX
tristate "SMSC LAN75XX based USB 2.0 gigabit ethernet devices"
depends on USB_USBNET
diff --git a/drivers/net/usb/Makefile b/drivers/net/usb/Makefile
index 9ab5c9d..7d95e5c 100644
--- a/drivers/net/usb/Makefile
+++ b/drivers/net/usb/Makefile
@@ -14,6 +14,7 @@ obj-$(CONFIG_USB_NET_AX88179_178A) += ax88179_178a.o
obj-$(CONFIG_USB_NET_CDCETHER) += cdc_ether.o
obj-$(CONFIG_USB_NET_CDC_EEM) += cdc_eem.o
obj-$(CONFIG_USB_NET_DM9601) += dm9601.o
+obj-$(CONFIG_USB_NET_DM9620) += dm9620.o
obj-$(CONFIG_USB_NET_SMSC75XX) += smsc75xx.o
obj-$(CONFIG_USB_NET_SMSC95XX) += smsc95xx.o
obj-$(CONFIG_USB_NET_GL620A) += gl620a.o
diff --git a/drivers/net/usb/dm9620.c b/drivers/net/usb/dm9620.c
new file mode 100644
index 0000000..aa4226b
--- /dev/null
+++ b/drivers/net/usb/dm9620.c
@@ -0,0 +1,796 @@
+/*
+ * Davicom DM9620 USB 2.0 10/100Mbps ethernet devices
+ *
+ * Peter Korsgaard <[email protected]>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+/* #define DEBUG */
+
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/stddef.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/mii.h>
+#include <linux/usb.h>
+#include <linux/crc32.h>
+#include <linux/usb/usbnet.h>
+#include <linux/slab.h>
+
+/* control requests */
+#define DM_READ_REGS 0x00
+#define DM_WRITE_REGS 0x01
+#define DM_READ_MEMS 0x02
+#define DM_WRITE_REG 0x03
+#define DM_WRITE_MEMS 0x05
+#define DM_WRITE_MEM 0x07
+
+/* registers */
+#define DM_NET_CTRL 0x00
+#define DM_RX_CTRL 0x05
+#define DM_FCR 0x0a
+#define DM_SHARED_CTRL 0x0b
+#define DM_SHARED_ADDR 0x0c
+#define DM_SHARED_DATA 0x0d /* low + high */
+#define DM_SHARED_DL 0x0d
+#define DM_SHARED_DH 0x0e
+#define DM_WAKEUP_CTRL 0x0f
+#define DM_PHY_ADDR 0x10 /* 6 bytes */
+#define DM_MCAST_ADDR 0x16 /* 8 bytes */
+#define DM_GPR_CTRL 0x1e
+#define DM_GPR_DATA 0x1f
+#define DM_PID 0x2a
+#define DM_CHIP_ID 0x2c
+#define DM_XPHY_CTRL 0x2e /* reserved */
+#define DM_TX_CRC_CTRL 0x31
+#define DM_RX_CRC_CTRL 0x32
+#define DM_AZR 0x3f /* reserved */
+#define DM_USB_CTRL 0xf4
+#define DM_MODE_CTRL 0x91 /* only on dm9620 */
+#define DM_CHIP_ID_EX 0x5C
+
+#define MD96XX_EEPROM_MAGIC 0x9620
+#define DM_MAX_MCAST 64
+#define DM_MCAST_SIZE 8
+#define DM_EEPROM_LEN 256
+#define DM_TX_OVERHEAD 2 /* 2 byte header */
+#define DM_RX_OVERHEAD 8 /* 4 byte header + 4 byte crc tail */
+#define DM_TIMEOUT 1000
+
+#define DM_NCR_RST (1<<0)
+#define DM_NCR_WAKEEN (1<<6)
+
+#define DM_FCR_TXPEN (1<<5)
+#define DM_FCR_BKPM (1<<3)
+#define DM_FCR_FLCE (1<<0)
+
+#define DMSC_WEP (1<<4)
+#define DMSC_ERPRW (1<<1)
+#define DMSC_ERRE (1<<0)
+
+#define DM_LINKEN (1<<5)
+#define DM_MAGICEN (1<<3)
+
+#define DM_TX_UDPCSE (1<<2)
+#define DM_TX_TCPCSE (1<<1)
+#define DM_TX_IPCSE (1<<0)
+#define DM_RX_RCSEN (1<<1)
+#define DM_MODE_DM_TXRX (0<<4)
+#define DM_MODE_CDC_TRX (1<<4)
+#define DM_MODE_DM_DESC (0<<5)
+#define DM_MODE_CDC_DES (1<<5)
+
+#define DM_USB_EP3ACK (1<<5)
+
+#define DM_MODE9601 (0<<7)
+#define DM_MODE9620 (1<<7)
+#define DM9620_PHY_ID 1 /* Stone add For kernel read phy register */
+
+#define VID_DAVICOM 0x0a46
+
+#define PID_DM9620 0x9620
+#define PID_DM9621 0x9621
+#define PID_DM9622 0x9622
+#define PID_DM9620A 0x0269
+#define PID_DM9621A 0x1269
+
+static int dm_read(struct usbnet *dev, u8 reg, u16 length, void *data)
+{
+ int err;
+ err = usbnet_read_cmd(dev, DM_READ_REGS,
+ USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ 0, reg, data, length);
+ if (err != length && err >= 0)
+ err = -EINVAL;
+ return err;
+}
+
+static int dm_read_reg(struct usbnet *dev, u8 reg, u8 *value)
+{
+ return dm_read(dev, reg, 1, value);
+}
+
+static int dm_write(struct usbnet *dev, u8 reg, u16 length, void *data)
+{
+ int err;
+ err = usbnet_write_cmd(dev, DM_WRITE_REGS,
+ USB_DIR_OUT | USB_TYPE_VENDOR |
+ USB_RECIP_DEVICE,
+ 0, reg, data, length);
+
+ if (err >= 0 && err < length)
+ err = -EINVAL;
+ return err;
+}
+
+static int dm_write_reg(struct usbnet *dev, u8 reg, u8 value)
+{
+ return usbnet_write_cmd(dev, DM_WRITE_REG,
+ USB_DIR_OUT | USB_TYPE_VENDOR |
+ USB_RECIP_DEVICE,
+ value, reg, NULL, 0);
+}
+
+static void dm_write_async(struct usbnet *dev, u8 reg, u16 length, void *data)
+{
+ usbnet_write_cmd_async(dev, DM_WRITE_REGS,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ 0, reg, data, length);
+}
+
+static void dm_write_reg_async(struct usbnet *dev, u8 reg, u8 value)
+{
+ usbnet_write_cmd_async(dev, DM_WRITE_REG,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ value, reg, NULL, 0);
+}
+
+static int dm_read_shared_word(struct usbnet *dev, int phy, u8 reg,
+ __le16 *value)
+{
+ int ret, i;
+
+ mutex_lock(&dev->phy_mutex);
+
+ dm_write_reg(dev, DM_SHARED_ADDR, phy ? (reg | 0x40) : reg);
+ dm_write_reg(dev, DM_SHARED_CTRL, phy ? 0xc : 0x4);
+
+ for (i = 0; i < DM_TIMEOUT; i++) {
+ u8 tmp = 0;
+
+ udelay(1);
+ ret = dm_read_reg(dev, DM_SHARED_CTRL, &tmp);
+ if (ret < 0)
+ goto out;
+
+ /* ready */
+ if ((tmp & 1) == 0)
+ break;
+ }
+
+ if (i == DM_TIMEOUT) {
+ netdev_err(dev->net, "%s read timed out!\n",
+ phy ? "phy" : "eeprom");
+ ret = -EIO;
+ goto out;
+ }
+
+ dm_write_reg(dev, DM_SHARED_CTRL, 0x0);
+ ret = dm_read(dev, DM_SHARED_DATA, 2, value);
+
+ netdev_dbg(dev->net, "read shared %d 0x%02x returned 0x%04x, %d\n",
+ phy, reg, *value, ret);
+
+ out:
+ mutex_unlock(&dev->phy_mutex);
+ return ret;
+}
+
+static int dm_write_shared_word(struct usbnet *dev, int phy, u8 reg,
+ __le16 value)
+{
+ int ret, i;
+
+ mutex_lock(&dev->phy_mutex);
+
+ ret = dm_write(dev, DM_SHARED_DATA, 2, &value);
+ if (ret < 0)
+ goto out;
+
+ dm_write_reg(dev, DM_SHARED_ADDR, phy ? (reg | 0x40) : reg);
+ dm_write_reg(dev, DM_SHARED_CTRL, phy ? 0x0a : 0x12);
+
+ for (i = 0; i < DM_TIMEOUT; i++) {
+ u8 tmp = 0;
+
+ udelay(1);
+ ret = dm_read_reg(dev, DM_SHARED_CTRL, &tmp);
+ if (ret < 0)
+ goto out;
+
+ /* ready */
+ if ((tmp & 1) == 0)
+ break;
+ }
+
+ if (i == DM_TIMEOUT) {
+ netdev_err(dev->net, "%s write timed out!\n",
+ phy ? "phy" : "eeprom");
+ ret = -EIO;
+ goto out;
+ }
+
+ dm_write_reg(dev, DM_SHARED_CTRL, 0x0);
+
+out:
+ mutex_unlock(&dev->phy_mutex);
+ return ret;
+}
+
+static void device_polling(struct usbnet *dev, u8 reg, u8 dmsc_bit,
+ u8 uexpected)
+{
+ int i, ret;
+ u8 tmp = 0;
+ for (i = 0; i < DM_TIMEOUT; i++) {
+ udelay(1);
+ ret = dm_read_reg(dev, reg, &tmp);
+ if (ret < 0) {
+ netdev_err(dev->net,
+ "[dm962 read reg] (reg: 0x%02x) error!\n",
+ reg);
+ break;
+ }
+ if ((tmp & dmsc_bit) == uexpected) /* ready */
+ break;
+ }
+ if (i == DM_TIMEOUT)
+ netdev_err(dev->net, "[dm962 time out] on polling bit:0x%x\n",
+ dmsc_bit);
+}
+
+static void dm_write_eeprom_word(struct usbnet *dev, u8 offset, u8 *data)
+{
+ mutex_lock(&dev->phy_mutex);
+
+ dm_write_reg(dev, DM_SHARED_ADDR, offset);
+ dm_write_reg(dev, DM_SHARED_DH, data[1]);
+ dm_write_reg(dev, DM_SHARED_DL, data[0]);
+ dm_write_reg(dev, DM_SHARED_CTRL, DMSC_WEP | DMSC_ERPRW);
+ device_polling(dev, DM_SHARED_CTRL, DMSC_ERRE, 0x00);
+ dm_write_reg(dev, DM_SHARED_CTRL, 0);
+
+ mutex_unlock(&dev->phy_mutex);
+}
+
+static int dm_read_eeprom_word(struct usbnet *dev, u8 offset, void *value)
+{
+ return dm_read_shared_word(dev, 0, offset, value);
+}
+
+static int dm9620_set_eeprom(struct net_device *net,
+ struct ethtool_eeprom *eeprom, u8 *data)
+{
+ struct usbnet *dev = netdev_priv(net);
+ int offset = eeprom->offset;
+ int len = eeprom->len;
+ int done;
+
+ if (eeprom->magic != MD96XX_EEPROM_MAGIC) {
+ netdev_err(dev->net, "EEPROM: magic value mismatch, magic = 0x%x\n",
+ eeprom->magic);
+ return -EINVAL;
+ }
+
+ while (len > 0) {
+ if (len & 1 || offset & 1) {
+ int which = offset & 1;
+ u8 tmp[2];
+ dm_read_eeprom_word(dev, offset / 2, tmp);
+ tmp[which] = *data;
+ dm_write_eeprom_word(dev, offset / 2, tmp);
+ usleep_range(10000, 15000);
+ done = 1;
+ } else {
+ dm_write_eeprom_word(dev, offset / 2, data);
+ done = 2;
+ }
+ data += done;
+ offset += done;
+ len -= done;
+ }
+ return 0;
+}
+
+static int dm9620_get_eeprom_len(struct net_device *dev)
+{
+ return DM_EEPROM_LEN;
+}
+
+static int dm9620_get_eeprom(struct net_device *net,
+ struct ethtool_eeprom *eeprom, u8 *data)
+{
+ struct usbnet *dev = netdev_priv(net);
+ __le16 *ebuf = (__le16 *) data;
+ int i;
+
+ /* access is 16bit */
+ if ((eeprom->offset % 2) || (eeprom->len % 2))
+ return -EINVAL;
+
+ for (i = 0; i < eeprom->len / 2; i++) {
+ if (dm_read_eeprom_word(dev, eeprom->offset / 2 + i,
+ &ebuf[i]) < 0)
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int dm9620_mdio_read(struct net_device *netdev, int phy_id, int loc)
+{
+ struct usbnet *dev = netdev_priv(netdev);
+
+ __le16 res;
+
+ dm_read_shared_word(dev, 1, loc, &res);
+
+ netdev_dbg(dev->net,
+ "dm9620_mdio_read() phy_id=0x%02x, loc=0x%02x, returns=0x%04x\n",
+ phy_id, loc, le16_to_cpu(res));
+
+ return le16_to_cpu(res);
+}
+
+static void dm9620_mdio_write(struct net_device *netdev, int phy_id, int loc,
+ int val)
+{
+ struct usbnet *dev = netdev_priv(netdev);
+ __le16 res = cpu_to_le16(val);
+
+
+ netdev_dbg(dev->net, "dm9620_mdio_write() phy_id=0x%02x, loc=0x%02x, val=0x%04x\n",
+ phy_id, loc, val);
+
+ dm_write_shared_word(dev, 1, loc, res);
+}
+
+static void dm9620_get_drvinfo(struct net_device *net,
+ struct ethtool_drvinfo *info)
+{
+ /* Inherit standard device info */
+ usbnet_get_drvinfo(net, info);
+ info->eedump_len = DM_EEPROM_LEN;
+}
+
+static u32 dm9620_get_link(struct net_device *net)
+{
+ struct usbnet *dev = netdev_priv(net);
+
+ return mii_link_ok(&dev->mii);
+}
+
+static int dm9620_ioctl(struct net_device *net, struct ifreq *rq, int cmd)
+{
+ struct usbnet *dev = netdev_priv(net);
+
+ return generic_mii_ioctl(&dev->mii, if_mii(rq), cmd, NULL);
+}
+
+static void
+dm9620_get_wol(struct net_device *net, struct ethtool_wolinfo *wolinfo)
+{
+ struct usbnet *dev = netdev_priv(net);
+ u8 opt;
+
+ if (dm_read_reg(dev, DM_WAKEUP_CTRL, &opt) < 0) {
+ wolinfo->supported = 0;
+ wolinfo->wolopts = 0;
+ return;
+ }
+ wolinfo->supported = WAKE_PHY | WAKE_MAGIC;
+ wolinfo->wolopts = 0;
+
+ if (opt & DM_LINKEN)
+ wolinfo->wolopts |= WAKE_PHY;
+ if (opt & DM_MAGICEN)
+ wolinfo->wolopts |= WAKE_MAGIC;
+}
+
+static int
+dm9620_set_wol(struct net_device *net, struct ethtool_wolinfo *wolinfo)
+{
+ struct usbnet *dev = netdev_priv(net);
+ u8 opt = 0;
+
+ if (wolinfo->wolopts & WAKE_PHY)
+ opt |= DM_LINKEN;
+ if (wolinfo->wolopts & WAKE_MAGIC)
+ opt |= DM_MAGICEN;
+
+ dm_write_reg(dev, DM_WAKEUP_CTRL, opt);
+ /* enable WAKEEN */
+ return dm_write_reg(dev, DM_NET_CTRL, DM_NCR_WAKEEN);
+}
+
+static const struct ethtool_ops dm9620_ethtool_ops = {
+ .get_drvinfo = dm9620_get_drvinfo,
+ .get_link = dm9620_get_link,
+ .get_msglevel = usbnet_get_msglevel,
+ .set_msglevel = usbnet_set_msglevel,
+ .get_eeprom_len = dm9620_get_eeprom_len,
+ .get_eeprom = dm9620_get_eeprom,
+ .set_eeprom = dm9620_set_eeprom,
+ .get_settings = usbnet_get_settings,
+ .set_settings = usbnet_set_settings,
+ .nway_reset = usbnet_nway_reset,
+ .get_wol = dm9620_get_wol,
+ .set_wol = dm9620_set_wol,
+};
+
+static void dm9620_set_multicast(struct net_device *net)
+{
+ struct usbnet *dev = netdev_priv(net);
+ /* We use the 20 byte dev->data for our 8 byte filter buffer
+ * to avoid allocating memory that is tricky to free later
+ */
+ u8 *hashes = (u8 *) &dev->data;
+ u8 rx_ctl = 0x31;
+
+ memset(hashes, 0, DM_MCAST_SIZE);
+ hashes[DM_MCAST_SIZE - 1] |= 0x80; /* broadcast address */
+
+ if (net->flags & IFF_PROMISC) {
+ rx_ctl |= 0x02;
+ } else if (net->flags & IFF_ALLMULTI ||
+ netdev_mc_count(net) > DM_MAX_MCAST) {
+ rx_ctl |= 0x08;
+ } else if (!netdev_mc_empty(net)) {
+ struct netdev_hw_addr *ha;
+
+ netdev_for_each_mc_addr(ha, net) {
+ u32 crc = ether_crc(ETH_ALEN, ha->addr) >> 26;
+ hashes[crc >> 3] |= 1 << (crc & 0x7);
+ }
+ }
+
+ dm_write_async(dev, DM_MCAST_ADDR, DM_MCAST_SIZE, hashes);
+ dm_write_reg_async(dev, DM_RX_CTRL, rx_ctl);
+}
+
+static void __dm9620_set_mac_address(struct usbnet *dev)
+{
+ dm_write_async(dev, DM_PHY_ADDR, ETH_ALEN, dev->net->dev_addr);
+}
+
+static int dm9620_set_mac_address(struct net_device *net, void *p)
+{
+ struct sockaddr *addr = p;
+ struct usbnet *dev = netdev_priv(net);
+
+ if (!is_valid_ether_addr(addr->sa_data)) {
+ dev_err(&net->dev, "not setting invalid mac address %pM\n",
+ addr->sa_data);
+ return -EINVAL;
+ }
+
+ memcpy(net->dev_addr, addr->sa_data, net->addr_len);
+ __dm9620_set_mac_address(dev);
+
+ return 0;
+}
+
+static const struct net_device_ops dm9620_netdev_ops = {
+ .ndo_open = usbnet_open,
+ .ndo_stop = usbnet_stop,
+ .ndo_start_xmit = usbnet_start_xmit,
+ .ndo_tx_timeout = usbnet_tx_timeout,
+ .ndo_change_mtu = usbnet_change_mtu,
+ .ndo_validate_addr = eth_validate_addr,
+ .ndo_do_ioctl = dm9620_ioctl,
+ .ndo_set_rx_mode = dm9620_set_multicast,
+ .ndo_set_mac_address = dm9620_set_mac_address,
+};
+
+static int dm9620_bind(struct usbnet *dev, struct usb_interface *intf)
+{
+ int ret;
+ u8 mac[ETH_ALEN], id;
+ u16 value;
+
+ ret = usbnet_get_endpoints(dev, intf);
+ if (ret)
+ return ret;
+
+ dev->net->netdev_ops = &dm9620_netdev_ops;
+ dev->net->ethtool_ops = &dm9620_ethtool_ops;
+ dev->net->hard_header_len += DM_TX_OVERHEAD;
+ dev->hard_mtu = dev->net->mtu + dev->net->hard_header_len;
+
+ /* ftp fail fixed */
+ dev->rx_urb_size = dev->net->mtu + ETH_HLEN + DM_RX_OVERHEAD+1;
+
+ dev->mii.dev = dev->net;
+ dev->mii.mdio_read = dm9620_mdio_read;
+ dev->mii.mdio_write = dm9620_mdio_write;
+ dev->mii.phy_id_mask = 0x1f;
+ dev->mii.reg_num_mask = 0x1f;
+ dev->mii.phy_id = DM9620_PHY_ID;
+
+ /* reset */
+ dm_write_reg(dev, DM_NET_CTRL, 1);
+ usleep_range(20, 30);
+
+ /* read MAC */
+ if (dm_read(dev, DM_PHY_ADDR, ETH_ALEN, mac) < 0) {
+ netdev_err(dev->net, "Error reading MAC address\n");
+ return -ENODEV;
+ }
+
+ /* Overwrite the auto-generated address only with good ones */
+ if (is_valid_ether_addr(mac))
+ memcpy(dev->net->dev_addr, mac, ETH_ALEN);
+ else {
+ netdev_warn(dev->net,
+ "dm9620: No valid MAC address in EEPROM, using %pM\n",
+ dev->net->dev_addr);
+ __dm9620_set_mac_address(dev);
+ }
+
+ if (dm_read_reg(dev, DM_CHIP_ID, &id) < 0) {
+ netdev_err(dev->net, "Error reading chip ID\n");
+ return -ENODEV;
+ }
+
+ dm_read(dev, DM_PID, 2, &value);
+
+ /* Add for check Product dm9620a/21a */
+ if (value == PID_DM9620A || value == PID_DM9621A)
+ dm_write_reg(dev, DM_MODE_CTRL, DM_MODE9620 | DM_MODE_CDC_DES);
+ else
+ dm_write_reg(dev, DM_MODE_CTRL, DM_MODE9620);
+
+ dm_write_reg(dev, DM_RX_CRC_CTRL, DM_RX_RCSEN);
+
+ /* power up phy */
+ dm_write_reg(dev, DM_GPR_CTRL, 1);
+ dm_write_reg(dev, DM_GPR_DATA, 0);
+
+ /* receive broadcast packets */
+ dm9620_set_multicast(dev->net);
+
+ dm9620_mdio_write(dev->net, dev->mii.phy_id, MII_BMCR, BMCR_RESET);
+
+ /* TX Pause Packet Enable */
+ dm_write_reg(dev, DM_FCR, DM_FCR_TXPEN | DM_FCR_BKPM | DM_FCR_FLCE);
+
+ /* Pause Capability on */
+ dm9620_mdio_write(dev->net, dev->mii.phy_id, MII_ADVERTISE,
+ ADVERTISE_ALL | ADVERTISE_CSMA | ADVERTISE_PAUSE_CAP);
+
+ dm_write_reg(dev, DM_USB_CTRL, DM_USB_EP3ACK);
+
+ mii_nway_restart(&dev->mii);
+
+ return 0;
+}
+
+static void dm9620_unbind(struct usbnet *dev, struct usb_interface *intf)
+{
+ netdev_warn(dev->net, "[dm962] Linux Driver = V2.6 - unbind\n");
+}
+
+static int dm9620_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
+{
+ u8 status;
+ int len;
+
+ /* format:
+ * b0: rx_flag
+ * b1: rx status
+ * b2: packet length (incl crc) low
+ * b3: packet length (incl crc) high
+ * b4..n-4: packet data
+ * bn-3..bn: ethernet crc
+ */
+ if (unlikely(skb->len < DM_RX_OVERHEAD)) {
+ dev_err(&dev->udev->dev, "unexpected tiny rx frame\n");
+ return 0;
+ }
+
+ status = skb->data[1];
+ len = (skb->data[2] | (skb->data[3] << 8)) - 4;
+
+ if (unlikely(status & 0xbf)) {
+ if (status & 0x01)
+ dev->net->stats.rx_fifo_errors++;
+ if (status & 0x02)
+ dev->net->stats.rx_crc_errors++;
+ if (status & 0x04)
+ dev->net->stats.rx_frame_errors++;
+ if (status & 0x20)
+ dev->net->stats.rx_missed_errors++;
+ if (status & 0x90)
+ dev->net->stats.rx_length_errors++;
+ return 0;
+ }
+
+ skb_pull(skb, 4);
+ skb_trim(skb, len);
+
+ return 1;
+}
+
+static bool davicom_bulkout_fix(int len, unsigned fullp)
+{
+ len = ((len+1)/2)*2;
+ len += 2;
+ return !(len % fullp) ? true : false;
+}
+
+static int dm9620_tx_oddadd_len(int len)
+{
+ return len & 1;
+}
+
+static int dm9620_tx_fulladd_len(int len, unsigned full_pld)
+{
+ if (davicom_bulkout_fix(len, full_pld))
+ return 2;
+ return 0;
+}
+
+static struct sk_buff *dm9620_tx_fixup(struct usbnet *dev, struct sk_buff *skb,
+ gfp_t flags)
+{
+ int len;
+ int oddadd_len, fulladd_len, newtailroom;
+
+ /* format:
+ * b1: packet length low
+ * b2: packet length high
+ * b3..n: packet data
+ */
+ len = skb->len;
+
+ oddadd_len = dm9620_tx_oddadd_len(len);
+ fulladd_len = dm9620_tx_fulladd_len(len, dev->maxpacket);
+ newtailroom = oddadd_len + fulladd_len;
+
+ if (skb_headroom(skb) < DM_TX_OVERHEAD ||
+ skb_tailroom(skb) < newtailroom) {
+ struct sk_buff *skb2;
+
+ if (skb_headroom(skb) < DM_TX_OVERHEAD &&
+ skb_tailroom(skb) < newtailroom)
+ skb2 = skb_copy_expand(skb, DM_TX_OVERHEAD, newtailroom,
+ flags);
+ else if (skb_headroom(skb) < DM_TX_OVERHEAD)
+ skb2 = skb_copy_expand(skb, DM_TX_OVERHEAD, 0, flags);
+ else
+ skb2 = skb_copy_expand(skb, 0, newtailroom, flags);
+ dev_kfree_skb_any(skb);
+ skb = skb2;
+ if (!skb)
+ return NULL;
+ }
+
+ /* add 2 bytes header which is data[0], data[1]
+ * with extra 2 bytes bulkout len
+ */
+ __skb_push(skb, DM_TX_OVERHEAD);
+
+ /* add 1, 2, or 3 bytes for tailer
+ * with different variate bulkout len
+ */
+ if (newtailroom)
+ __skb_put(skb, newtailroom);
+
+ /* add 2 bytes in length if a multiple of packet size */
+ len += fulladd_len;
+
+ skb->data[0] = len;
+ skb->data[1] = len >> 8;
+
+ return skb;
+}
+
+static void dm9620_status(struct usbnet *dev, struct urb *urb)
+{
+ int link;
+ u8 *buf;
+
+ /* format:
+ * b0: net status
+ * b1: tx status 1
+ * b2: tx status 2
+ * b3: rx status
+ * b4: rx overflow
+ * b5: rx count
+ * b6: tx count
+ * b7: gpr
+ */
+ if (urb->actual_length < 8)
+ return;
+
+ buf = urb->transfer_buffer;
+
+ link = !!(buf[0] & 0x40);
+ if (netif_carrier_ok(dev->net) != link) {
+ usbnet_link_change(dev, link, 1);
+ netdev_dbg(dev->net, "Link Status is: %d\n", link);
+ }
+}
+
+static int dm9620_link_reset(struct usbnet *dev)
+{
+ struct ethtool_cmd ecmd = { .cmd = ETHTOOL_GSET };
+
+ mii_check_media(&dev->mii, 1, 1);
+ mii_ethtool_gset(&dev->mii, &ecmd);
+ dm_write_reg(dev, DM_USB_CTRL, DM_USB_EP3ACK);
+
+ netdev_dbg(dev->net, "link_reset() speed: %u duplex: %d\n",
+ ethtool_cmd_speed(&ecmd), ecmd.duplex);
+
+ return 0;
+}
+
+static const struct driver_info dm9620_info = {
+ .description = "Davicom DM9620 USB Ethernet",
+ .flags = FLAG_ETHER | FLAG_LINK_INTR,
+ .bind = dm9620_bind,
+ .rx_fixup = dm9620_rx_fixup,
+ .tx_fixup = dm9620_tx_fixup,
+ .status = dm9620_status,
+ .link_reset = dm9620_link_reset,
+ .reset = dm9620_link_reset,
+ .unbind = dm9620_unbind,
+};
+
+static const struct usb_device_id products[] = {
+ {
+ USB_DEVICE(VID_DAVICOM, PID_DM9620), /* dm9620 */
+ .driver_info = (unsigned long)&dm9620_info,
+ },
+ {
+ USB_DEVICE(VID_DAVICOM, PID_DM9621), /* dm9621 */
+ .driver_info = (unsigned long)&dm9620_info,
+ },
+ {
+ USB_DEVICE(VID_DAVICOM, PID_DM9622), /* dm9622 */
+ .driver_info = (unsigned long)&dm9620_info,
+ },
+ {
+ USB_DEVICE(VID_DAVICOM, PID_DM9620A), /* dm9620a */
+ .driver_info = (unsigned long)&dm9620_info,
+ },
+ {
+ USB_DEVICE(VID_DAVICOM, PID_DM9621A), /* dm9621a */
+ .driver_info = (unsigned long)&dm9620_info,
+ },
+ {},
+};
+
+MODULE_DEVICE_TABLE(usb, products);
+
+static struct usb_driver dm9620_driver = {
+ .name = "dm9620",
+ .id_table = products,
+ .probe = usbnet_probe,
+ .disconnect = usbnet_disconnect,
+ .suspend = usbnet_suspend,
+ .resume = usbnet_resume,
+ .disable_hub_initiated_lpm = 1,
+};
+
+module_usb_driver(dm9620_driver);
+
+MODULE_AUTHOR("Peter Korsgaard <[email protected]>");
+MODULE_DESCRIPTION("Davicom DM9620 USB 2.0 ethernet devices");
+MODULE_LICENSE("GPL");
--
1.7.1
>>>>> "Joseph" == Joseph CHANG <[email protected]> writes:
Joseph> DM9620 is an USB2.0 network adapter rather than DM9601 USB1.1. This
Joseph> driver processed the RX data 4 bytes header, TX data 2 bytes header,
Joseph> make the control bit exactly right in PHY write function, and optional
Joseph> IFF_ALLMUTLI bit for RX control.
But dm9601.c already supports the dm9620 based devices. Why another
driver for the same hardware?
Please CC me on dm9601 related patches.
Joseph> Tested good for many platforms, include X86 desktop and ARM embedded.
Joseph> +static struct usb_driver dm9620_driver = {
Joseph> + .name = "dm9620",
Joseph> + .id_table = products,
Joseph> + .probe = usbnet_probe,
Joseph> + .disconnect = usbnet_disconnect,
Joseph> + .suspend = usbnet_suspend,
Joseph> + .resume = usbnet_resume,
Joseph> + .disable_hub_initiated_lpm = 1,
Joseph> +};
Joseph> +
Joseph> +module_usb_driver(dm9620_driver);
Joseph> +
Joseph> +MODULE_AUTHOR("Peter Korsgaard <[email protected]>");
I'm not the author of this file.
--
Bye, Peter Korsgaard
Dear Peter,
This is Joseph CHANG, I am glade to contact and work with you.
I am busy in some affairs else these days, and will like to be back soon.
For the begin, We copy the source code 'dm9601.c' which was by you,
In order to work for our new chips (DM9620, DM9621, DM9621A... series),
We did modify to get 'dm9620.c' base on 'dm0601.c',
Since you are the author and also the owner, so keep MODULE_AUTHOR() as you.
To generate DM9620.c is a way to distinguish the different generation chips:
(as below)
DM9601: old revision: USB1.1, Ethernet 10/100, Phase out
DM9620: current revision: USB2.0, Ethernet 10/100, Mass production state
DM9633: next revision: USB3.0, Ethernet 10/100/1000, Will to release on
2013/E
So want to make it from the configuration by "make menuconfig".
Can you help give further advices, we hope you can go ahead be the owner
(author) of the
'dm9620.c', And then we make patch to it. Would you agree this idea? If so we
will be happy
because we did not ever create such a code.
* Think about DM9633 driver to be, It will be is total different compare to
DM9601 &
DM9620. And will get the same issue.
ThankYou and Best Regards,
Joseph CHANG
System Application Engineering Division
Davicom Semiconductor, Inc.
No. 6 Li-Hsin Rd. VI, Science-Based Park,
Hsin-Chu, Taiwan.
Tel: 886-3-5798797 Ex 8534
Fax: 886-3-5646929
Web: http://www.davicom.com.tw
-----Original Message-----
From: Peter Korsgaard [mailto:[email protected]] On Behalf Of Peter Korsgaard
Sent: Monday, June 24, 2013 4:01 AM
To: Joseph CHANG
Cc: Greg Kroah-Hartman; [email protected]; [email protected];
[email protected]; [email protected]; [email protected]
Subject: Re: [PATCH 1/1] net: add dm9620 net usb driver
>>>>> "Joseph" == Joseph CHANG <[email protected]> writes:
Joseph> DM9620 is an USB2.0 network adapter rather than DM9601 USB1.1. This
Joseph> driver processed the RX data 4 bytes header, TX data 2 bytes header,
Joseph> make the control bit exactly right in PHY write function, and optional
Joseph> IFF_ALLMUTLI bit for RX control.
But dm9601.c already supports the dm9620 based devices. Why another
driver for the same hardware?
Please CC me on dm9601 related patches.
Joseph> Tested good for many platforms, include X86 desktop and ARM embedded.
Joseph> +static struct usb_driver dm9620_driver = {
Joseph> + .name = "dm9620",
Joseph> + .id_table = products,
Joseph> + .probe = usbnet_probe,
Joseph> + .disconnect = usbnet_disconnect,
Joseph> + .suspend = usbnet_suspend,
Joseph> + .resume = usbnet_resume,
Joseph> + .disable_hub_initiated_lpm = 1,
Joseph> +};
Joseph> +
Joseph> +module_usb_driver(dm9620_driver);
Joseph> +
Joseph> +MODULE_AUTHOR("Peter Korsgaard <[email protected]>");
I'm not the author of this file.
--
Bye, Peter Korsgaard
--
This message has been scanned for viruses and
dangerous content by MailScanner, and is
believed to be clean.
>>>>> "Joseph" == Joseph Chang <[email protected]> writes:
Hi,
Joseph> This is Joseph CHANG, I am glade to contact and work with
Joseph> you. I am busy in some affairs else these days, and will
Joseph> like to be back soon.
Joseph> For the begin, We copy the source code 'dm9601.c' which was
Joseph> by you, In order to work for our new chips (DM9620, DM9621,
Joseph> DM9621A... series), We did modify to get 'dm9620.c' base on
Joseph> 'dm0601.c', Since you are the author and also the owner, so
Joseph> keep MODULE_AUTHOR() as you.
Yes, that's what I learned from a number of bug reports. It would have
been good if you would have worked with me to get your modifications
integrated instead.
Joseph> To generate DM9620.c is a way to distinguish the different
Joseph> generation chips: (as below)
Joseph> DM9601: old revision: USB1.1, Ethernet 10/100, Phase out
Joseph> DM9620: current revision: USB2.0, Ethernet 10/100, Mass production state
Joseph> DM9633: next revision: USB3.0, Ethernet 10/100/1000, Will to
Joseph> release on 2013/E
dm9620 is basically backwards compatible with dm9601, so I added dm9620
support to the dm9601 driver earlier this year:
https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=6642f91c92da0736
Is there anything missing from that?
I don't see any dm9633 datasheet on the davicom website, so I cannot say
if it makes sense to add support for it in the existing driver. Is the
datasheet available somewhere?
Joseph> So want to make it from the configuration by "make
Joseph> menuconfig".
Joseph> Can you help give further advices, we hope you can go ahead
Joseph> be the owner (author) of the 'dm9620.c', And then we make patch
Joseph> to it. Would you agree this idea? If so we will be happy
Joseph> because we did not ever create such a code.
Joseph> * Think about DM9633 driver to be, It will be is total
Joseph> different compare to DM9601 & DM9620. And will get the same
Joseph> issue.
If you can provide me with a dm9633 datasheet and a sample device then I
can look into writing a driver for it / extending dm9601.c. Please
contact me off list for that.
Thanks!
--
Bye, Peter Korsgaard