From patchwork Sat Sep 12 15:39:20 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: He Yong X-Patchwork-Id: 1362884 X-Patchwork-Delegate: trini@ti.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.denx.de (client-ip=85.214.62.61; helo=phobos.denx.de; envelope-from=u-boot-bounces@lists.denx.de; receiver=) Authentication-Results: ozlabs.org; dmarc=pass (p=none dis=none) header.from=163.com Authentication-Results: ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=163.com header.i=@163.com header.a=rsa-sha256 header.s=s110527 header.b=k0lc18Xm; dkim-atps=neutral Received: from phobos.denx.de (phobos.denx.de [85.214.62.61]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 4BpcJT6hdjz9sTH for ; Sun, 13 Sep 2020 01:40:09 +1000 (AEST) Received: from h2850616.stratoserver.net (localhost [IPv6:::1]) by phobos.denx.de (Postfix) with ESMTP id 353EA821DB; Sat, 12 Sep 2020 17:40:06 +0200 (CEST) Authentication-Results: phobos.denx.de; dmarc=pass (p=none dis=none) header.from=163.com Authentication-Results: phobos.denx.de; spf=pass smtp.mailfrom=u-boot-bounces@lists.denx.de Authentication-Results: phobos.denx.de; dkim=pass (1024-bit key; unprotected) header.d=163.com header.i=@163.com header.b="k0lc18Xm"; dkim-atps=neutral Received: by phobos.denx.de (Postfix, from userid 109) id 4696582242; Sat, 12 Sep 2020 17:40:04 +0200 (CEST) X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on phobos.denx.de X-Spam-Level: X-Spam-Status: No, score=-2.0 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FROM,RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL,SPF_HELO_NONE autolearn=ham autolearn_force=no version=3.4.2 Received: from m12-17.163.com (m12-17.163.com [220.181.12.17]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) (No client certificate requested) by phobos.denx.de (Postfix) with ESMTPS id 5719480390 for ; Sat, 12 Sep 2020 17:39:53 +0200 (CEST) Authentication-Results: phobos.denx.de; dmarc=pass (p=none dis=none) header.from=163.com Authentication-Results: phobos.denx.de; spf=pass smtp.mailfrom=hyyoxhk@163.com DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=163.com; s=s110527; h=From:Subject:Date:Message-Id; bh=VinCKSonxLM703OD3W mHjIUUQ+cC5X8LcITCGAxI8I0=; b=k0lc18Xmfqvqpfkdft4nbRBskYn911llqD eTQHOiqNv0p7odD9MJy7TcSkeQ4Md+4P03kT0zBZ4OXnOAopFUnWrTtB4wdI2uRf 13LWmOOAnbRogOUR08eG3aHtwpL2sXaAMjxTozOPcLVWNb3nBACxzEHzAdyI3DCg rYeG85dos= Received: from hd.lan (unknown [125.69.92.37]) by smtp13 (Coremail) with SMTP id EcCowAAXtXmo61xfo3gkIA--.35585S2; Sat, 12 Sep 2020 23:39:20 +0800 (CST) From: hyyoxhk To: u-boot@lists.denx.de Cc: marex@denx.de Subject: [PATCH] Add support for Davicom DM96xx based USB 10/100 ethernet devices Date: Sat, 12 Sep 2020 23:39:20 +0800 Message-Id: <20200912153920.10861-1-hyyoxhk@163.com> X-Mailer: git-send-email 2.17.1 X-CM-TRANSID: EcCowAAXtXmo61xfo3gkIA--.35585S2 X-Coremail-Antispam: 1Uf129KBjvAXoW3ZF13Aw1rJr18GF4rurWrGrg_yoW8Gw47to Wxuanaqw1ft34UAr4v93WxCr1vqrn7uFy5Aw4fWrWkAa4q9wn0q3y7Ga98W34UJF4Ygrs5 Zws7Kw1Sqr4rt34rn29KB7ZKAUJUUUUU529EdanIXcx71UUUUU7v73VFW2AGmfu7bjvjm3 AaLaJ3UbIYCTnIWIevJa73UjIFyTuYvjxU0-erUUUUU X-Originating-IP: [125.69.92.37] X-CM-SenderInfo: pk1105lkn6il2tof0z/1tbiYwmd0laEB-y+8QAAsD X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.34 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: u-boot-bounces@lists.denx.de Sender: "U-Boot" X-Virus-Scanned: clamav-milter 0.102.3 at phobos.denx.de X-Virus-Status: Clean Ported from Linux driver - drivers/net/usb9601.c Signed-off-by: hyyoxhk --- drivers/usb/eth/Kconfig | 8 + drivers/usb/eth/Makefile | 1 + drivers/usb/eth/dm9601.c | 521 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 530 insertions(+) create mode 100644 drivers/usb/eth/dm9601.c diff --git a/drivers/usb/eth/Kconfig b/drivers/usb/eth/Kconfig index 2f6bfa8e71..8a47ca0ec4 100644 --- a/drivers/usb/eth/Kconfig +++ b/drivers/usb/eth/Kconfig @@ -62,4 +62,12 @@ config USB_ETHER_SMSC95XX Say Y here if you would like to support SMSC LAN95xx based USB 2.0 Ethernet Devices. +config USB_ETHER_DM9601 + bool "Davicom DM96xx based USB 10/100 ethernet devices" + depends on USB_HOST_ETHER + depends on DM_ETH + ---help--- + This option adds support for Davicom DM9601/DM9620/DM9621A + based USB 10/100 Ethernet adapters. + endif diff --git a/drivers/usb/eth/Makefile b/drivers/usb/eth/Makefile index 2e5d0782e8..044b12f028 100644 --- a/drivers/usb/eth/Makefile +++ b/drivers/usb/eth/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_USB_ETHER_ASIX) += asix.o obj-$(CONFIG_USB_ETHER_ASIX88179) += asix88179.o obj-$(CONFIG_USB_ETHER_MCS7830) += mcs7830.o obj-$(CONFIG_USB_ETHER_SMSC95XX) += smsc95xx.o +obj-$(CONFIG_USB_ETHER_DM9601) += dm9601.o obj-$(CONFIG_USB_ETHER_LAN75XX) += lan7x.o lan75xx.o obj-$(CONFIG_USB_ETHER_LAN78XX) += lan7x.o lan78xx.o obj-$(CONFIG_USB_ETHER_RTL8152) += r8152.o r8152_fw.o diff --git a/drivers/usb/eth/dm9601.c b/drivers/usb/eth/dm9601.c new file mode 100644 index 0000000000..f5956c7c73 --- /dev/null +++ b/drivers/usb/eth/dm9601.c @@ -0,0 +1,521 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Davicom DM96xx USB 10/100Mbps ethernet devices + * + * Ported from Linux driver - drivers/net/usb9601.c + * + * Copyright (C) 2020 hey + */ + +#include +#include +#include +#include +#include +#include +#include +#include "usb_ether.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_SHARED_CTRL 0x0b +#define DM_SHARED_ADDR 0x0c +#define DM_SHARED_DATA 0x0d /* low + high */ +#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_CHIP_ID 0x2c +#define DM_MODE_CTRL 0x91 /* only on dm9620 */ + +/* chip id values */ +#define ID_DM9601 0 +#define ID_DM9620 1 + +#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 7 /* 3 byte header + 4 byte crc tail */ +#define DM_TIMEOUT 1000 + +#define USB_CTRL_SET_TIMEOUT 5000 +#define PHY_CONNECT_TIMEOUT 5000 +#define USB_BULK_SEND_TIMEOUT 5000 +#define USB_BULK_RECV_TIMEOUT 5000 +#define RX_URB_SIZE 2048 + +/* driver private */ +struct dm9601_private { + struct ueth_data ueth; +}; + +static int dm_read(struct usb_device *udev, u8 reg, u16 length, void *data) +{ + ALLOC_CACHE_ALIGN_BUFFER(u8, v, length); + int err; + + err = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + DM_READ_REGS, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0, reg, v, length, + USB_CTRL_SET_TIMEOUT); + + memcpy(data, v, length); + + if (err != length && err >= 0) + err = -EINVAL; + + return err; +} + +static int dm_read_reg(struct usb_device *udev, u8 reg, u8 *value) +{ + return dm_read(udev, reg, 1, value); +} + +static int dm_write(struct usb_device *udev, u8 reg, u16 length, void *data) +{ + ALLOC_CACHE_ALIGN_BUFFER(u8, v, length); + int err; + + memcpy(v, data, length); + + err = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + DM_WRITE_REGS, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0, reg, v, length, + USB_CTRL_SET_TIMEOUT); + if (err >= 0 && err < length) + err = -EINVAL; + + return err; +} + +static int dm_write_reg(struct usb_device *udev, u8 reg, u8 value) +{ + ALLOC_CACHE_ALIGN_BUFFER(u8, v, 1); + v[0] = value; + + return usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + DM_WRITE_REG, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + v[0], reg, NULL, 0, + USB_CTRL_SET_TIMEOUT); +} + +static int dm_read_shared_word(struct usb_device *udev, int phy, u8 reg, __le16 *value) +{ + int ret, i; + + dm_write_reg(udev, DM_SHARED_ADDR, phy ? (reg | 0x40) : reg); + dm_write_reg(udev, DM_SHARED_CTRL, phy ? 0xc : 0x4); + + for (i = 0; i < DM_TIMEOUT; i++) { + u8 tmp = 0; + + udelay(1); + ret = dm_read_reg(udev, DM_SHARED_CTRL, &tmp); + if (ret < 0) + goto out; + + /* ready */ + if ((tmp & 1) == 0) + break; + } + + if (i == DM_TIMEOUT) { + printf("%s read timed out!\n", phy ? "phy" : "eeprom"); + ret = -EIO; + goto out; + } + + dm_write_reg(udev, DM_SHARED_CTRL, 0x0); + ret = dm_read(udev, DM_SHARED_DATA, 2, value); + +out: + return ret; +} + +static int dm_write_shared_word(struct usb_device *udev, int phy, u8 reg, __le16 value) +{ + int ret, i; + + ret = dm_write(udev, DM_SHARED_DATA, 2, &value); + if (ret < 0) + goto out; + + dm_write_reg(udev, DM_SHARED_ADDR, phy ? (reg | 0x40) : reg); + dm_write_reg(udev, DM_SHARED_CTRL, phy ? 0x1a : 0x12); + + for (i = 0; i < DM_TIMEOUT; i++) { + u8 tmp = 0; + + udelay(1); + ret = dm_read_reg(udev, DM_SHARED_CTRL, &tmp); + if (ret < 0) + goto out; + + /* ready */ + if ((tmp & 1) == 0) + break; + } + + if (i == DM_TIMEOUT) { + printf("%s write timed out!\n", phy ? "phy" : "eeprom"); + ret = -EIO; + goto out; + } + + dm_write_reg(udev, DM_SHARED_CTRL, 0x0); + +out: + return ret; +} + +static int dm9601_mdio_read(struct ueth_data *ueth, int phy_id, int loc) +{ + __le16 res; + + if (phy_id) { + printf("Only internal phy supported\n"); + return 0; + } + + dm_read_shared_word(ueth->pusb_dev, 1, loc, &res); + + return le16_to_cpu(res); +} + +static void dm9601_mdio_write(struct ueth_data *ueth, int phy_id, int loc, int val) +{ + __le16 res = cpu_to_le16(val); + + if (phy_id) { + printf("Only internal phy supported\n"); + return; + } + + dm_write_shared_word(ueth->pusb_dev, 1, loc, res); +} + +static int dm9601_set_mac_address(struct usb_device *udev, u8 *enetaddr) +{ + if (!is_valid_ethaddr(enetaddr)) { + printf("not setting invalid mac address %pM\n", enetaddr); + return -EINVAL; + } + + dm_write(udev, DM_PHY_ADDR, ETH_ALEN, enetaddr); + + return 0; +} + +/** + * mii_nway_restart - restart NWay (autonegotiation) for this interface + * @mii: the MII interface + * + * Returns 0 on success, negative on error. + */ +static int mii_nway_restart(struct ueth_data *ueth) +{ + int bmcr; + int r = -EINVAL; + + /* if autoneg is off, it's an error */ + bmcr = dm9601_mdio_read(ueth, ueth->phy_id, MII_BMCR); + + if (bmcr & BMCR_ANENABLE) { + bmcr |= BMCR_ANRESTART; + dm9601_mdio_write(ueth, ueth->phy_id, MII_BMCR, bmcr); + r = 0; + } + + return r; +} + +static void dm9601_set_multicast(struct usb_device *udev) +{ + u8 hashes[DM_MCAST_SIZE]; + u8 rx_ctl = 0x31; + + memset(hashes, 0x00, DM_MCAST_SIZE); + hashes[DM_MCAST_SIZE - 1] |= 0x80; /* broadcast address */ + + dm_write(udev, DM_MCAST_ADDR, DM_MCAST_SIZE, hashes); + dm_write_reg(udev, DM_RX_CTRL, rx_ctl); +} + +static int dm9601_bind(struct udevice *dev) +{ + struct eth_pdata *pdata = dev_get_platdata(dev); + struct dm9601_private *priv = dev_get_priv(dev); + struct ueth_data *ueth = &priv->ueth; + int ret = 0; + u8 mac[ETH_ALEN], id; + + /* reset */ + dm_write_reg(ueth->pusb_dev, DM_NET_CTRL, 1); + udelay(20); + + /* read MAC */ + if (dm_read(ueth->pusb_dev, DM_PHY_ADDR, ETH_ALEN, mac) < 0) { + printf("Error reading MAC address\n"); + ret = -ENODEV; + goto out; + } + + /* + * Overwrite the auto-generated address only with good ones. + */ + if (is_valid_ethaddr(mac)) + memcpy(pdata->enetaddr, mac, ETH_ALEN); + else + printf("dm9601: No valid MAC address in EEPROM, using %pM\n", pdata->enetaddr); + + if (dm_read_reg(ueth->pusb_dev, DM_CHIP_ID, &id) < 0) { + printf("Error reading chip ID\n"); + ret = -ENODEV; + goto out; + } + + /* put dm9620 devices in dm9601 mode */ + if (id == ID_DM9620) { + u8 mode; + + if (dm_read_reg(ueth->pusb_dev, DM_MODE_CTRL, &mode) < 0) { + printf("Error reading MODE_CTRL\n"); + ret = -ENODEV; + goto out; + } + dm_write_reg(ueth->pusb_dev, DM_MODE_CTRL, mode & 0x7f); + } + + /* power up phy */ + dm_write_reg(ueth->pusb_dev, DM_GPR_CTRL, 1); + dm_write_reg(ueth->pusb_dev, DM_GPR_DATA, 0); + + /* receive broadcast packets */ + dm9601_set_multicast(ueth->pusb_dev); + + dm9601_mdio_write(ueth, ueth->phy_id, MII_BMCR, BMCR_RESET); + dm9601_mdio_write(ueth, ueth->phy_id, MII_ADVERTISE, + ADVERTISE_ALL | ADVERTISE_CSMA | ADVERTISE_PAUSE_CAP); + mii_nway_restart(ueth); + +out: + return ret; +} + +static int dm9601_eth_start(struct udevice *dev) +{ + struct dm9601_private *priv = dev_get_priv(dev); + struct ueth_data *ueth = &priv->ueth; + int timeout = 0; + int link_detected; + + debug("\n----> %s()\n", __func__); + +#define TIMEOUT_RESOLUTION 50 /* ms */ + do { + link_detected = dm9601_mdio_read(ueth, ueth->phy_id, MII_BMSR) & BMSR_LSTATUS; + if (!link_detected) { + if (timeout == 0) + printf("Waiting for Ethernet connection... "); + + udelay(TIMEOUT_RESOLUTION * 1000); + timeout += TIMEOUT_RESOLUTION; + } + } while (!link_detected && timeout < PHY_CONNECT_TIMEOUT); + + if (link_detected) { + if (timeout != 0) + printf("done.\n"); + + } else { + printf("unable to connect.\n"); + return -EIO; + } + return 0; +} + +static int dm9601_eth_send(struct udevice *dev, void *packet, int length) +{ + int err; + u16 packet_len; + int actual_len; + struct dm9601_private *priv = dev_get_priv(dev); + struct ueth_data *ueth = &priv->ueth; + + ALLOC_CACHE_ALIGN_BUFFER(unsigned char, msg, PKTSIZE + DM_TX_OVERHEAD); + + /* format: + * b1: packet length low + * b2: packet length high + * b3..n: packet data + */ + + packet_len = length; + cpu_to_le16s(&packet_len); + + memcpy(msg, &packet_len, DM_TX_OVERHEAD); + memcpy(msg + DM_TX_OVERHEAD, (void *)packet, length); + + err = usb_bulk_msg(ueth->pusb_dev, + usb_sndbulkpipe(ueth->pusb_dev, ueth->ep_out), + (void *)msg, + length + sizeof(packet_len), + &actual_len, + USB_BULK_SEND_TIMEOUT); + + return err; +} + +static int dm9601_eth_recv(struct udevice *dev, int flags, uchar **packetp) +{ + struct dm9601_private *priv = dev_get_priv(dev); + struct ueth_data *ueth = &priv->ueth; + u8 *ptr; + int ret, len; + u8 status; + u32 packet_len; + + len = usb_ether_get_rx_bytes(ueth, &ptr); + debug("%s: first try, len=%d\n", __func__, len); + if (!len) { + if (!(flags & ETH_RECV_CHECK_DEVICE)) + return -EAGAIN; + ret = usb_ether_receive(ueth, RX_URB_SIZE); + if (ret == -EAGAIN) + return ret; + + len = usb_ether_get_rx_bytes(ueth, &ptr); + debug("%s: second try, len=%d\n", __func__, len); + } + + /* format: + * 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(len < DM_RX_OVERHEAD)) { + debug("unexpected tiny rx frame\n"); + goto err; + } + + status = ptr[0]; + packet_len = (ptr[1] | (ptr[2] << 8)) - 4; + + if (unlikely(status & 0xbf)) { + debug("Rx: packet status failure: %d\n", status); + goto err; + } + + if (packet_len > len - DM_RX_OVERHEAD) { + debug("Rx: too large packet: %d\n", packet_len); + goto err; + } + + *packetp = ptr + 3; + + return packet_len; + +err: + usb_ether_advance_rxbuf(ueth, -1); + return -EINVAL; +} + +static int dm9601_free_pkt(struct udevice *dev, uchar *packet, int packet_len) +{ + struct dm9601_private *priv = dev_get_priv(dev); + + usb_ether_advance_rxbuf(&priv->ueth, DM_RX_OVERHEAD + packet_len); + + return 0; +} + +static void dm9601_eth_stop(struct udevice *dev) +{ + debug("** %s()\n", __func__); +} + +static int dm9601_write_hwaddr(struct udevice *dev) +{ + struct eth_pdata *pdata = dev_get_platdata(dev); + struct dm9601_private *priv = dev_get_priv(dev); + struct ueth_data *ueth = &priv->ueth; + + return dm9601_set_mac_address(ueth->pusb_dev, pdata->enetaddr); +} + +static int dm9601_eth_probe(struct udevice *dev) +{ + struct dm9601_private *priv = dev_get_priv(dev); + struct ueth_data *ueth = &priv->ueth; + int ret; + + ret = usb_ether_register(dev, ueth, RX_URB_SIZE); + if (ret) { + printf("usb ether register failed! ret = %d\n", ret); + return ret; + } + + if (dm9601_bind(dev)) { + printf("basic init failed!\n"); + goto err; + } + + return 0; +err: + return usb_ether_deregister(ueth); +} + +static const struct eth_ops dm9601_eth_ops = { + .start = dm9601_eth_start, + .send = dm9601_eth_send, + .recv = dm9601_eth_recv, + .free_pkt = dm9601_free_pkt, + .stop = dm9601_eth_stop, + .write_hwaddr = dm9601_write_hwaddr, +}; + +U_BOOT_DRIVER(dm9601_eth) = { + .name = "dm9601_eth", + .id = UCLASS_ETH, + .probe = dm9601_eth_probe, + .ops = &dm9601_eth_ops, + .priv_auto_alloc_size = sizeof(struct dm9601_private), + .platdata_auto_alloc_size = sizeof(struct eth_pdata), +}; + +static const struct usb_device_id dm9601_eth_id_table[] = { + { USB_DEVICE(0x07aa, 0x9601) }, /* Corega FEther USB-TXC */ + { USB_DEVICE(0x0a46, 0x9601) }, /* Davicom USB-100 */ + { USB_DEVICE(0x0a46, 0x6688) }, /* ZT6688 USB NIC */ + { USB_DEVICE(0x0a46, 0x0268) }, /* ShanTou ST268 USB NIC */ + { USB_DEVICE(0x0a46, 0x8515) }, /* ADMtek ADM8515 USB NIC */ + { USB_DEVICE(0x0a47, 0x9601) }, /* Hirose USB-100 */ + { USB_DEVICE(0x0fe6, 0x8101) }, /* DM9601 USB to Fast Ethernet Adapter */ + { USB_DEVICE(0x0fe6, 0x9700) }, /* DM9601 USB to Fast Ethernet Adapter */ + { USB_DEVICE(0x0a46, 0x9000) }, /* DM9000E */ + { USB_DEVICE(0x0a46, 0x9620) }, /* DM9620 USB to Fast Ethernet Adapter */ + { USB_DEVICE(0x0a46, 0x9621) }, /* DM9621A USB to Fast Ethernet Adapter */ + { USB_DEVICE(0x0a46, 0x9622) }, /* DM9622 USB to Fast Ethernet Adapter */ + { USB_DEVICE(0x0a46, 0x0269) }, /* DM962OA USB to Fast Ethernet Adapter */ + { USB_DEVICE(0x0a46, 0x1269) }, /* DM9621A USB to Fast Ethernet Adapter */ + {}, +}; + +U_BOOT_USB_DEVICE(dm9601_eth, dm9601_eth_id_table);