18c2ecf20Sopenharmony_ci/* 28c2ecf20Sopenharmony_ci * Davicom DM96xx USB 10/100Mbps ethernet devices 38c2ecf20Sopenharmony_ci * 48c2ecf20Sopenharmony_ci * Peter Korsgaard <jacmet@sunsite.dk> 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * This file is licensed under the terms of the GNU General Public License 78c2ecf20Sopenharmony_ci * version 2. This program is licensed "as is" without any warranty of any 88c2ecf20Sopenharmony_ci * kind, whether express or implied. 98c2ecf20Sopenharmony_ci */ 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci//#define DEBUG 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#include <linux/module.h> 148c2ecf20Sopenharmony_ci#include <linux/sched.h> 158c2ecf20Sopenharmony_ci#include <linux/stddef.h> 168c2ecf20Sopenharmony_ci#include <linux/netdevice.h> 178c2ecf20Sopenharmony_ci#include <linux/etherdevice.h> 188c2ecf20Sopenharmony_ci#include <linux/ethtool.h> 198c2ecf20Sopenharmony_ci#include <linux/mii.h> 208c2ecf20Sopenharmony_ci#include <linux/usb.h> 218c2ecf20Sopenharmony_ci#include <linux/crc32.h> 228c2ecf20Sopenharmony_ci#include <linux/usb/usbnet.h> 238c2ecf20Sopenharmony_ci#include <linux/slab.h> 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci/* datasheet: 268c2ecf20Sopenharmony_ci http://ptm2.cc.utu.fi/ftp/network/cards/DM9601/From_NET/DM9601-DS-P01-930914.pdf 278c2ecf20Sopenharmony_ci*/ 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci/* control requests */ 308c2ecf20Sopenharmony_ci#define DM_READ_REGS 0x00 318c2ecf20Sopenharmony_ci#define DM_WRITE_REGS 0x01 328c2ecf20Sopenharmony_ci#define DM_READ_MEMS 0x02 338c2ecf20Sopenharmony_ci#define DM_WRITE_REG 0x03 348c2ecf20Sopenharmony_ci#define DM_WRITE_MEMS 0x05 358c2ecf20Sopenharmony_ci#define DM_WRITE_MEM 0x07 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci/* registers */ 388c2ecf20Sopenharmony_ci#define DM_NET_CTRL 0x00 398c2ecf20Sopenharmony_ci#define DM_RX_CTRL 0x05 408c2ecf20Sopenharmony_ci#define DM_SHARED_CTRL 0x0b 418c2ecf20Sopenharmony_ci#define DM_SHARED_ADDR 0x0c 428c2ecf20Sopenharmony_ci#define DM_SHARED_DATA 0x0d /* low + high */ 438c2ecf20Sopenharmony_ci#define DM_PHY_ADDR 0x10 /* 6 bytes */ 448c2ecf20Sopenharmony_ci#define DM_MCAST_ADDR 0x16 /* 8 bytes */ 458c2ecf20Sopenharmony_ci#define DM_GPR_CTRL 0x1e 468c2ecf20Sopenharmony_ci#define DM_GPR_DATA 0x1f 478c2ecf20Sopenharmony_ci#define DM_CHIP_ID 0x2c 488c2ecf20Sopenharmony_ci#define DM_MODE_CTRL 0x91 /* only on dm9620 */ 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci/* chip id values */ 518c2ecf20Sopenharmony_ci#define ID_DM9601 0 528c2ecf20Sopenharmony_ci#define ID_DM9620 1 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci#define DM_MAX_MCAST 64 558c2ecf20Sopenharmony_ci#define DM_MCAST_SIZE 8 568c2ecf20Sopenharmony_ci#define DM_EEPROM_LEN 256 578c2ecf20Sopenharmony_ci#define DM_TX_OVERHEAD 2 /* 2 byte header */ 588c2ecf20Sopenharmony_ci#define DM_RX_OVERHEAD 7 /* 3 byte header + 4 byte crc tail */ 598c2ecf20Sopenharmony_ci#define DM_TIMEOUT 1000 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_cistatic int dm_read(struct usbnet *dev, u8 reg, u16 length, void *data) 628c2ecf20Sopenharmony_ci{ 638c2ecf20Sopenharmony_ci int err; 648c2ecf20Sopenharmony_ci err = usbnet_read_cmd(dev, DM_READ_REGS, 658c2ecf20Sopenharmony_ci USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, 668c2ecf20Sopenharmony_ci 0, reg, data, length); 678c2ecf20Sopenharmony_ci if(err != length && err >= 0) 688c2ecf20Sopenharmony_ci err = -EINVAL; 698c2ecf20Sopenharmony_ci return err; 708c2ecf20Sopenharmony_ci} 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_cistatic int dm_read_reg(struct usbnet *dev, u8 reg, u8 *value) 738c2ecf20Sopenharmony_ci{ 748c2ecf20Sopenharmony_ci return dm_read(dev, reg, 1, value); 758c2ecf20Sopenharmony_ci} 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_cistatic int dm_write(struct usbnet *dev, u8 reg, u16 length, void *data) 788c2ecf20Sopenharmony_ci{ 798c2ecf20Sopenharmony_ci int err; 808c2ecf20Sopenharmony_ci err = usbnet_write_cmd(dev, DM_WRITE_REGS, 818c2ecf20Sopenharmony_ci USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, 828c2ecf20Sopenharmony_ci 0, reg, data, length); 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci if (err >= 0 && err < length) 858c2ecf20Sopenharmony_ci err = -EINVAL; 868c2ecf20Sopenharmony_ci return err; 878c2ecf20Sopenharmony_ci} 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_cistatic int dm_write_reg(struct usbnet *dev, u8 reg, u8 value) 908c2ecf20Sopenharmony_ci{ 918c2ecf20Sopenharmony_ci return usbnet_write_cmd(dev, DM_WRITE_REG, 928c2ecf20Sopenharmony_ci USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, 938c2ecf20Sopenharmony_ci value, reg, NULL, 0); 948c2ecf20Sopenharmony_ci} 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_cistatic void dm_write_async(struct usbnet *dev, u8 reg, u16 length, void *data) 978c2ecf20Sopenharmony_ci{ 988c2ecf20Sopenharmony_ci usbnet_write_cmd_async(dev, DM_WRITE_REGS, 998c2ecf20Sopenharmony_ci USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, 1008c2ecf20Sopenharmony_ci 0, reg, data, length); 1018c2ecf20Sopenharmony_ci} 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_cistatic void dm_write_reg_async(struct usbnet *dev, u8 reg, u8 value) 1048c2ecf20Sopenharmony_ci{ 1058c2ecf20Sopenharmony_ci usbnet_write_cmd_async(dev, DM_WRITE_REG, 1068c2ecf20Sopenharmony_ci USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, 1078c2ecf20Sopenharmony_ci value, reg, NULL, 0); 1088c2ecf20Sopenharmony_ci} 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_cistatic int dm_read_shared_word(struct usbnet *dev, int phy, u8 reg, __le16 *value) 1118c2ecf20Sopenharmony_ci{ 1128c2ecf20Sopenharmony_ci int ret, i; 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci mutex_lock(&dev->phy_mutex); 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci dm_write_reg(dev, DM_SHARED_ADDR, phy ? (reg | 0x40) : reg); 1178c2ecf20Sopenharmony_ci dm_write_reg(dev, DM_SHARED_CTRL, phy ? 0xc : 0x4); 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci for (i = 0; i < DM_TIMEOUT; i++) { 1208c2ecf20Sopenharmony_ci u8 tmp = 0; 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci udelay(1); 1238c2ecf20Sopenharmony_ci ret = dm_read_reg(dev, DM_SHARED_CTRL, &tmp); 1248c2ecf20Sopenharmony_ci if (ret < 0) 1258c2ecf20Sopenharmony_ci goto out; 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci /* ready */ 1288c2ecf20Sopenharmony_ci if ((tmp & 1) == 0) 1298c2ecf20Sopenharmony_ci break; 1308c2ecf20Sopenharmony_ci } 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci if (i == DM_TIMEOUT) { 1338c2ecf20Sopenharmony_ci netdev_err(dev->net, "%s read timed out!\n", phy ? "phy" : "eeprom"); 1348c2ecf20Sopenharmony_ci ret = -EIO; 1358c2ecf20Sopenharmony_ci goto out; 1368c2ecf20Sopenharmony_ci } 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci dm_write_reg(dev, DM_SHARED_CTRL, 0x0); 1398c2ecf20Sopenharmony_ci ret = dm_read(dev, DM_SHARED_DATA, 2, value); 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci netdev_dbg(dev->net, "read shared %d 0x%02x returned 0x%04x, %d\n", 1428c2ecf20Sopenharmony_ci phy, reg, *value, ret); 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci out: 1458c2ecf20Sopenharmony_ci mutex_unlock(&dev->phy_mutex); 1468c2ecf20Sopenharmony_ci return ret; 1478c2ecf20Sopenharmony_ci} 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_cistatic int dm_write_shared_word(struct usbnet *dev, int phy, u8 reg, __le16 value) 1508c2ecf20Sopenharmony_ci{ 1518c2ecf20Sopenharmony_ci int ret, i; 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci mutex_lock(&dev->phy_mutex); 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci ret = dm_write(dev, DM_SHARED_DATA, 2, &value); 1568c2ecf20Sopenharmony_ci if (ret < 0) 1578c2ecf20Sopenharmony_ci goto out; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci dm_write_reg(dev, DM_SHARED_ADDR, phy ? (reg | 0x40) : reg); 1608c2ecf20Sopenharmony_ci dm_write_reg(dev, DM_SHARED_CTRL, phy ? 0x1a : 0x12); 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci for (i = 0; i < DM_TIMEOUT; i++) { 1638c2ecf20Sopenharmony_ci u8 tmp = 0; 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci udelay(1); 1668c2ecf20Sopenharmony_ci ret = dm_read_reg(dev, DM_SHARED_CTRL, &tmp); 1678c2ecf20Sopenharmony_ci if (ret < 0) 1688c2ecf20Sopenharmony_ci goto out; 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci /* ready */ 1718c2ecf20Sopenharmony_ci if ((tmp & 1) == 0) 1728c2ecf20Sopenharmony_ci break; 1738c2ecf20Sopenharmony_ci } 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci if (i == DM_TIMEOUT) { 1768c2ecf20Sopenharmony_ci netdev_err(dev->net, "%s write timed out!\n", phy ? "phy" : "eeprom"); 1778c2ecf20Sopenharmony_ci ret = -EIO; 1788c2ecf20Sopenharmony_ci goto out; 1798c2ecf20Sopenharmony_ci } 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci dm_write_reg(dev, DM_SHARED_CTRL, 0x0); 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ciout: 1848c2ecf20Sopenharmony_ci mutex_unlock(&dev->phy_mutex); 1858c2ecf20Sopenharmony_ci return ret; 1868c2ecf20Sopenharmony_ci} 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_cistatic int dm_read_eeprom_word(struct usbnet *dev, u8 offset, void *value) 1898c2ecf20Sopenharmony_ci{ 1908c2ecf20Sopenharmony_ci return dm_read_shared_word(dev, 0, offset, value); 1918c2ecf20Sopenharmony_ci} 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_cistatic int dm9601_get_eeprom_len(struct net_device *dev) 1968c2ecf20Sopenharmony_ci{ 1978c2ecf20Sopenharmony_ci return DM_EEPROM_LEN; 1988c2ecf20Sopenharmony_ci} 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_cistatic int dm9601_get_eeprom(struct net_device *net, 2018c2ecf20Sopenharmony_ci struct ethtool_eeprom *eeprom, u8 * data) 2028c2ecf20Sopenharmony_ci{ 2038c2ecf20Sopenharmony_ci struct usbnet *dev = netdev_priv(net); 2048c2ecf20Sopenharmony_ci __le16 *ebuf = (__le16 *) data; 2058c2ecf20Sopenharmony_ci int i; 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci /* access is 16bit */ 2088c2ecf20Sopenharmony_ci if ((eeprom->offset % 2) || (eeprom->len % 2)) 2098c2ecf20Sopenharmony_ci return -EINVAL; 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci for (i = 0; i < eeprom->len / 2; i++) { 2128c2ecf20Sopenharmony_ci if (dm_read_eeprom_word(dev, eeprom->offset / 2 + i, 2138c2ecf20Sopenharmony_ci &ebuf[i]) < 0) 2148c2ecf20Sopenharmony_ci return -EINVAL; 2158c2ecf20Sopenharmony_ci } 2168c2ecf20Sopenharmony_ci return 0; 2178c2ecf20Sopenharmony_ci} 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_cistatic int dm9601_mdio_read(struct net_device *netdev, int phy_id, int loc) 2208c2ecf20Sopenharmony_ci{ 2218c2ecf20Sopenharmony_ci struct usbnet *dev = netdev_priv(netdev); 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci __le16 res; 2248c2ecf20Sopenharmony_ci int err; 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci if (phy_id) { 2278c2ecf20Sopenharmony_ci netdev_dbg(dev->net, "Only internal phy supported\n"); 2288c2ecf20Sopenharmony_ci return 0; 2298c2ecf20Sopenharmony_ci } 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci err = dm_read_shared_word(dev, 1, loc, &res); 2328c2ecf20Sopenharmony_ci if (err < 0) { 2338c2ecf20Sopenharmony_ci netdev_err(dev->net, "MDIO read error: %d\n", err); 2348c2ecf20Sopenharmony_ci return err; 2358c2ecf20Sopenharmony_ci } 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci netdev_dbg(dev->net, 2388c2ecf20Sopenharmony_ci "dm9601_mdio_read() phy_id=0x%02x, loc=0x%02x, returns=0x%04x\n", 2398c2ecf20Sopenharmony_ci phy_id, loc, le16_to_cpu(res)); 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci return le16_to_cpu(res); 2428c2ecf20Sopenharmony_ci} 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_cistatic void dm9601_mdio_write(struct net_device *netdev, int phy_id, int loc, 2458c2ecf20Sopenharmony_ci int val) 2468c2ecf20Sopenharmony_ci{ 2478c2ecf20Sopenharmony_ci struct usbnet *dev = netdev_priv(netdev); 2488c2ecf20Sopenharmony_ci __le16 res = cpu_to_le16(val); 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_ci if (phy_id) { 2518c2ecf20Sopenharmony_ci netdev_dbg(dev->net, "Only internal phy supported\n"); 2528c2ecf20Sopenharmony_ci return; 2538c2ecf20Sopenharmony_ci } 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_ci netdev_dbg(dev->net, "dm9601_mdio_write() phy_id=0x%02x, loc=0x%02x, val=0x%04x\n", 2568c2ecf20Sopenharmony_ci phy_id, loc, val); 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci dm_write_shared_word(dev, 1, loc, res); 2598c2ecf20Sopenharmony_ci} 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_cistatic void dm9601_get_drvinfo(struct net_device *net, 2628c2ecf20Sopenharmony_ci struct ethtool_drvinfo *info) 2638c2ecf20Sopenharmony_ci{ 2648c2ecf20Sopenharmony_ci /* Inherit standard device info */ 2658c2ecf20Sopenharmony_ci usbnet_get_drvinfo(net, info); 2668c2ecf20Sopenharmony_ci} 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_cistatic u32 dm9601_get_link(struct net_device *net) 2698c2ecf20Sopenharmony_ci{ 2708c2ecf20Sopenharmony_ci struct usbnet *dev = netdev_priv(net); 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_ci return mii_link_ok(&dev->mii); 2738c2ecf20Sopenharmony_ci} 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_cistatic int dm9601_ioctl(struct net_device *net, struct ifreq *rq, int cmd) 2768c2ecf20Sopenharmony_ci{ 2778c2ecf20Sopenharmony_ci struct usbnet *dev = netdev_priv(net); 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ci return generic_mii_ioctl(&dev->mii, if_mii(rq), cmd, NULL); 2808c2ecf20Sopenharmony_ci} 2818c2ecf20Sopenharmony_ci 2828c2ecf20Sopenharmony_cistatic const struct ethtool_ops dm9601_ethtool_ops = { 2838c2ecf20Sopenharmony_ci .get_drvinfo = dm9601_get_drvinfo, 2848c2ecf20Sopenharmony_ci .get_link = dm9601_get_link, 2858c2ecf20Sopenharmony_ci .get_msglevel = usbnet_get_msglevel, 2868c2ecf20Sopenharmony_ci .set_msglevel = usbnet_set_msglevel, 2878c2ecf20Sopenharmony_ci .get_eeprom_len = dm9601_get_eeprom_len, 2888c2ecf20Sopenharmony_ci .get_eeprom = dm9601_get_eeprom, 2898c2ecf20Sopenharmony_ci .nway_reset = usbnet_nway_reset, 2908c2ecf20Sopenharmony_ci .get_link_ksettings = usbnet_get_link_ksettings, 2918c2ecf20Sopenharmony_ci .set_link_ksettings = usbnet_set_link_ksettings, 2928c2ecf20Sopenharmony_ci}; 2938c2ecf20Sopenharmony_ci 2948c2ecf20Sopenharmony_cistatic void dm9601_set_multicast(struct net_device *net) 2958c2ecf20Sopenharmony_ci{ 2968c2ecf20Sopenharmony_ci struct usbnet *dev = netdev_priv(net); 2978c2ecf20Sopenharmony_ci /* We use the 20 byte dev->data for our 8 byte filter buffer 2988c2ecf20Sopenharmony_ci * to avoid allocating memory that is tricky to free later */ 2998c2ecf20Sopenharmony_ci u8 *hashes = (u8 *) & dev->data; 3008c2ecf20Sopenharmony_ci u8 rx_ctl = 0x31; 3018c2ecf20Sopenharmony_ci 3028c2ecf20Sopenharmony_ci memset(hashes, 0x00, DM_MCAST_SIZE); 3038c2ecf20Sopenharmony_ci hashes[DM_MCAST_SIZE - 1] |= 0x80; /* broadcast address */ 3048c2ecf20Sopenharmony_ci 3058c2ecf20Sopenharmony_ci if (net->flags & IFF_PROMISC) { 3068c2ecf20Sopenharmony_ci rx_ctl |= 0x02; 3078c2ecf20Sopenharmony_ci } else if (net->flags & IFF_ALLMULTI || 3088c2ecf20Sopenharmony_ci netdev_mc_count(net) > DM_MAX_MCAST) { 3098c2ecf20Sopenharmony_ci rx_ctl |= 0x08; 3108c2ecf20Sopenharmony_ci } else if (!netdev_mc_empty(net)) { 3118c2ecf20Sopenharmony_ci struct netdev_hw_addr *ha; 3128c2ecf20Sopenharmony_ci 3138c2ecf20Sopenharmony_ci netdev_for_each_mc_addr(ha, net) { 3148c2ecf20Sopenharmony_ci u32 crc = ether_crc(ETH_ALEN, ha->addr) >> 26; 3158c2ecf20Sopenharmony_ci hashes[crc >> 3] |= 1 << (crc & 0x7); 3168c2ecf20Sopenharmony_ci } 3178c2ecf20Sopenharmony_ci } 3188c2ecf20Sopenharmony_ci 3198c2ecf20Sopenharmony_ci dm_write_async(dev, DM_MCAST_ADDR, DM_MCAST_SIZE, hashes); 3208c2ecf20Sopenharmony_ci dm_write_reg_async(dev, DM_RX_CTRL, rx_ctl); 3218c2ecf20Sopenharmony_ci} 3228c2ecf20Sopenharmony_ci 3238c2ecf20Sopenharmony_cistatic void __dm9601_set_mac_address(struct usbnet *dev) 3248c2ecf20Sopenharmony_ci{ 3258c2ecf20Sopenharmony_ci dm_write_async(dev, DM_PHY_ADDR, ETH_ALEN, dev->net->dev_addr); 3268c2ecf20Sopenharmony_ci} 3278c2ecf20Sopenharmony_ci 3288c2ecf20Sopenharmony_cistatic int dm9601_set_mac_address(struct net_device *net, void *p) 3298c2ecf20Sopenharmony_ci{ 3308c2ecf20Sopenharmony_ci struct sockaddr *addr = p; 3318c2ecf20Sopenharmony_ci struct usbnet *dev = netdev_priv(net); 3328c2ecf20Sopenharmony_ci 3338c2ecf20Sopenharmony_ci if (!is_valid_ether_addr(addr->sa_data)) { 3348c2ecf20Sopenharmony_ci dev_err(&net->dev, "not setting invalid mac address %pM\n", 3358c2ecf20Sopenharmony_ci addr->sa_data); 3368c2ecf20Sopenharmony_ci return -EINVAL; 3378c2ecf20Sopenharmony_ci } 3388c2ecf20Sopenharmony_ci 3398c2ecf20Sopenharmony_ci memcpy(net->dev_addr, addr->sa_data, net->addr_len); 3408c2ecf20Sopenharmony_ci __dm9601_set_mac_address(dev); 3418c2ecf20Sopenharmony_ci 3428c2ecf20Sopenharmony_ci return 0; 3438c2ecf20Sopenharmony_ci} 3448c2ecf20Sopenharmony_ci 3458c2ecf20Sopenharmony_cistatic const struct net_device_ops dm9601_netdev_ops = { 3468c2ecf20Sopenharmony_ci .ndo_open = usbnet_open, 3478c2ecf20Sopenharmony_ci .ndo_stop = usbnet_stop, 3488c2ecf20Sopenharmony_ci .ndo_start_xmit = usbnet_start_xmit, 3498c2ecf20Sopenharmony_ci .ndo_tx_timeout = usbnet_tx_timeout, 3508c2ecf20Sopenharmony_ci .ndo_change_mtu = usbnet_change_mtu, 3518c2ecf20Sopenharmony_ci .ndo_get_stats64 = usbnet_get_stats64, 3528c2ecf20Sopenharmony_ci .ndo_validate_addr = eth_validate_addr, 3538c2ecf20Sopenharmony_ci .ndo_do_ioctl = dm9601_ioctl, 3548c2ecf20Sopenharmony_ci .ndo_set_rx_mode = dm9601_set_multicast, 3558c2ecf20Sopenharmony_ci .ndo_set_mac_address = dm9601_set_mac_address, 3568c2ecf20Sopenharmony_ci}; 3578c2ecf20Sopenharmony_ci 3588c2ecf20Sopenharmony_cistatic int dm9601_bind(struct usbnet *dev, struct usb_interface *intf) 3598c2ecf20Sopenharmony_ci{ 3608c2ecf20Sopenharmony_ci int ret; 3618c2ecf20Sopenharmony_ci u8 mac[ETH_ALEN], id; 3628c2ecf20Sopenharmony_ci 3638c2ecf20Sopenharmony_ci ret = usbnet_get_endpoints(dev, intf); 3648c2ecf20Sopenharmony_ci if (ret) 3658c2ecf20Sopenharmony_ci goto out; 3668c2ecf20Sopenharmony_ci 3678c2ecf20Sopenharmony_ci dev->net->netdev_ops = &dm9601_netdev_ops; 3688c2ecf20Sopenharmony_ci dev->net->ethtool_ops = &dm9601_ethtool_ops; 3698c2ecf20Sopenharmony_ci dev->net->hard_header_len += DM_TX_OVERHEAD; 3708c2ecf20Sopenharmony_ci dev->hard_mtu = dev->net->mtu + dev->net->hard_header_len; 3718c2ecf20Sopenharmony_ci 3728c2ecf20Sopenharmony_ci /* dm9620/21a require room for 4 byte padding, even in dm9601 3738c2ecf20Sopenharmony_ci * mode, so we need +1 to be able to receive full size 3748c2ecf20Sopenharmony_ci * ethernet frames. 3758c2ecf20Sopenharmony_ci */ 3768c2ecf20Sopenharmony_ci dev->rx_urb_size = dev->net->mtu + ETH_HLEN + DM_RX_OVERHEAD + 1; 3778c2ecf20Sopenharmony_ci 3788c2ecf20Sopenharmony_ci dev->mii.dev = dev->net; 3798c2ecf20Sopenharmony_ci dev->mii.mdio_read = dm9601_mdio_read; 3808c2ecf20Sopenharmony_ci dev->mii.mdio_write = dm9601_mdio_write; 3818c2ecf20Sopenharmony_ci dev->mii.phy_id_mask = 0x1f; 3828c2ecf20Sopenharmony_ci dev->mii.reg_num_mask = 0x1f; 3838c2ecf20Sopenharmony_ci 3848c2ecf20Sopenharmony_ci /* reset */ 3858c2ecf20Sopenharmony_ci dm_write_reg(dev, DM_NET_CTRL, 1); 3868c2ecf20Sopenharmony_ci udelay(20); 3878c2ecf20Sopenharmony_ci 3888c2ecf20Sopenharmony_ci /* read MAC */ 3898c2ecf20Sopenharmony_ci if (dm_read(dev, DM_PHY_ADDR, ETH_ALEN, mac) < 0) { 3908c2ecf20Sopenharmony_ci printk(KERN_ERR "Error reading MAC address\n"); 3918c2ecf20Sopenharmony_ci ret = -ENODEV; 3928c2ecf20Sopenharmony_ci goto out; 3938c2ecf20Sopenharmony_ci } 3948c2ecf20Sopenharmony_ci 3958c2ecf20Sopenharmony_ci /* 3968c2ecf20Sopenharmony_ci * Overwrite the auto-generated address only with good ones. 3978c2ecf20Sopenharmony_ci */ 3988c2ecf20Sopenharmony_ci if (is_valid_ether_addr(mac)) 3998c2ecf20Sopenharmony_ci memcpy(dev->net->dev_addr, mac, ETH_ALEN); 4008c2ecf20Sopenharmony_ci else { 4018c2ecf20Sopenharmony_ci printk(KERN_WARNING 4028c2ecf20Sopenharmony_ci "dm9601: No valid MAC address in EEPROM, using %pM\n", 4038c2ecf20Sopenharmony_ci dev->net->dev_addr); 4048c2ecf20Sopenharmony_ci __dm9601_set_mac_address(dev); 4058c2ecf20Sopenharmony_ci } 4068c2ecf20Sopenharmony_ci 4078c2ecf20Sopenharmony_ci if (dm_read_reg(dev, DM_CHIP_ID, &id) < 0) { 4088c2ecf20Sopenharmony_ci netdev_err(dev->net, "Error reading chip ID\n"); 4098c2ecf20Sopenharmony_ci ret = -ENODEV; 4108c2ecf20Sopenharmony_ci goto out; 4118c2ecf20Sopenharmony_ci } 4128c2ecf20Sopenharmony_ci 4138c2ecf20Sopenharmony_ci /* put dm9620 devices in dm9601 mode */ 4148c2ecf20Sopenharmony_ci if (id == ID_DM9620) { 4158c2ecf20Sopenharmony_ci u8 mode; 4168c2ecf20Sopenharmony_ci 4178c2ecf20Sopenharmony_ci if (dm_read_reg(dev, DM_MODE_CTRL, &mode) < 0) { 4188c2ecf20Sopenharmony_ci netdev_err(dev->net, "Error reading MODE_CTRL\n"); 4198c2ecf20Sopenharmony_ci ret = -ENODEV; 4208c2ecf20Sopenharmony_ci goto out; 4218c2ecf20Sopenharmony_ci } 4228c2ecf20Sopenharmony_ci dm_write_reg(dev, DM_MODE_CTRL, mode & 0x7f); 4238c2ecf20Sopenharmony_ci } 4248c2ecf20Sopenharmony_ci 4258c2ecf20Sopenharmony_ci /* power up phy */ 4268c2ecf20Sopenharmony_ci dm_write_reg(dev, DM_GPR_CTRL, 1); 4278c2ecf20Sopenharmony_ci dm_write_reg(dev, DM_GPR_DATA, 0); 4288c2ecf20Sopenharmony_ci 4298c2ecf20Sopenharmony_ci /* receive broadcast packets */ 4308c2ecf20Sopenharmony_ci dm9601_set_multicast(dev->net); 4318c2ecf20Sopenharmony_ci 4328c2ecf20Sopenharmony_ci dm9601_mdio_write(dev->net, dev->mii.phy_id, MII_BMCR, BMCR_RESET); 4338c2ecf20Sopenharmony_ci dm9601_mdio_write(dev->net, dev->mii.phy_id, MII_ADVERTISE, 4348c2ecf20Sopenharmony_ci ADVERTISE_ALL | ADVERTISE_CSMA | ADVERTISE_PAUSE_CAP); 4358c2ecf20Sopenharmony_ci mii_nway_restart(&dev->mii); 4368c2ecf20Sopenharmony_ci 4378c2ecf20Sopenharmony_ciout: 4388c2ecf20Sopenharmony_ci return ret; 4398c2ecf20Sopenharmony_ci} 4408c2ecf20Sopenharmony_ci 4418c2ecf20Sopenharmony_cistatic int dm9601_rx_fixup(struct usbnet *dev, struct sk_buff *skb) 4428c2ecf20Sopenharmony_ci{ 4438c2ecf20Sopenharmony_ci u8 status; 4448c2ecf20Sopenharmony_ci int len; 4458c2ecf20Sopenharmony_ci 4468c2ecf20Sopenharmony_ci /* format: 4478c2ecf20Sopenharmony_ci b1: rx status 4488c2ecf20Sopenharmony_ci b2: packet length (incl crc) low 4498c2ecf20Sopenharmony_ci b3: packet length (incl crc) high 4508c2ecf20Sopenharmony_ci b4..n-4: packet data 4518c2ecf20Sopenharmony_ci bn-3..bn: ethernet crc 4528c2ecf20Sopenharmony_ci */ 4538c2ecf20Sopenharmony_ci 4548c2ecf20Sopenharmony_ci if (unlikely(skb->len < DM_RX_OVERHEAD)) { 4558c2ecf20Sopenharmony_ci dev_err(&dev->udev->dev, "unexpected tiny rx frame\n"); 4568c2ecf20Sopenharmony_ci return 0; 4578c2ecf20Sopenharmony_ci } 4588c2ecf20Sopenharmony_ci 4598c2ecf20Sopenharmony_ci status = skb->data[0]; 4608c2ecf20Sopenharmony_ci len = (skb->data[1] | (skb->data[2] << 8)) - 4; 4618c2ecf20Sopenharmony_ci 4628c2ecf20Sopenharmony_ci if (unlikely(status & 0xbf)) { 4638c2ecf20Sopenharmony_ci if (status & 0x01) dev->net->stats.rx_fifo_errors++; 4648c2ecf20Sopenharmony_ci if (status & 0x02) dev->net->stats.rx_crc_errors++; 4658c2ecf20Sopenharmony_ci if (status & 0x04) dev->net->stats.rx_frame_errors++; 4668c2ecf20Sopenharmony_ci if (status & 0x20) dev->net->stats.rx_missed_errors++; 4678c2ecf20Sopenharmony_ci if (status & 0x90) dev->net->stats.rx_length_errors++; 4688c2ecf20Sopenharmony_ci return 0; 4698c2ecf20Sopenharmony_ci } 4708c2ecf20Sopenharmony_ci 4718c2ecf20Sopenharmony_ci skb_pull(skb, 3); 4728c2ecf20Sopenharmony_ci skb_trim(skb, len); 4738c2ecf20Sopenharmony_ci 4748c2ecf20Sopenharmony_ci return 1; 4758c2ecf20Sopenharmony_ci} 4768c2ecf20Sopenharmony_ci 4778c2ecf20Sopenharmony_cistatic struct sk_buff *dm9601_tx_fixup(struct usbnet *dev, struct sk_buff *skb, 4788c2ecf20Sopenharmony_ci gfp_t flags) 4798c2ecf20Sopenharmony_ci{ 4808c2ecf20Sopenharmony_ci int len, pad; 4818c2ecf20Sopenharmony_ci 4828c2ecf20Sopenharmony_ci /* format: 4838c2ecf20Sopenharmony_ci b1: packet length low 4848c2ecf20Sopenharmony_ci b2: packet length high 4858c2ecf20Sopenharmony_ci b3..n: packet data 4868c2ecf20Sopenharmony_ci */ 4878c2ecf20Sopenharmony_ci 4888c2ecf20Sopenharmony_ci len = skb->len + DM_TX_OVERHEAD; 4898c2ecf20Sopenharmony_ci 4908c2ecf20Sopenharmony_ci /* workaround for dm962x errata with tx fifo getting out of 4918c2ecf20Sopenharmony_ci * sync if a USB bulk transfer retry happens right after a 4928c2ecf20Sopenharmony_ci * packet with odd / maxpacket length by adding up to 3 bytes 4938c2ecf20Sopenharmony_ci * padding. 4948c2ecf20Sopenharmony_ci */ 4958c2ecf20Sopenharmony_ci while ((len & 1) || !(len % dev->maxpacket)) 4968c2ecf20Sopenharmony_ci len++; 4978c2ecf20Sopenharmony_ci 4988c2ecf20Sopenharmony_ci len -= DM_TX_OVERHEAD; /* hw header doesn't count as part of length */ 4998c2ecf20Sopenharmony_ci pad = len - skb->len; 5008c2ecf20Sopenharmony_ci 5018c2ecf20Sopenharmony_ci if (skb_headroom(skb) < DM_TX_OVERHEAD || skb_tailroom(skb) < pad) { 5028c2ecf20Sopenharmony_ci struct sk_buff *skb2; 5038c2ecf20Sopenharmony_ci 5048c2ecf20Sopenharmony_ci skb2 = skb_copy_expand(skb, DM_TX_OVERHEAD, pad, flags); 5058c2ecf20Sopenharmony_ci dev_kfree_skb_any(skb); 5068c2ecf20Sopenharmony_ci skb = skb2; 5078c2ecf20Sopenharmony_ci if (!skb) 5088c2ecf20Sopenharmony_ci return NULL; 5098c2ecf20Sopenharmony_ci } 5108c2ecf20Sopenharmony_ci 5118c2ecf20Sopenharmony_ci __skb_push(skb, DM_TX_OVERHEAD); 5128c2ecf20Sopenharmony_ci 5138c2ecf20Sopenharmony_ci if (pad) { 5148c2ecf20Sopenharmony_ci memset(skb->data + skb->len, 0, pad); 5158c2ecf20Sopenharmony_ci __skb_put(skb, pad); 5168c2ecf20Sopenharmony_ci } 5178c2ecf20Sopenharmony_ci 5188c2ecf20Sopenharmony_ci skb->data[0] = len; 5198c2ecf20Sopenharmony_ci skb->data[1] = len >> 8; 5208c2ecf20Sopenharmony_ci 5218c2ecf20Sopenharmony_ci return skb; 5228c2ecf20Sopenharmony_ci} 5238c2ecf20Sopenharmony_ci 5248c2ecf20Sopenharmony_cistatic void dm9601_status(struct usbnet *dev, struct urb *urb) 5258c2ecf20Sopenharmony_ci{ 5268c2ecf20Sopenharmony_ci int link; 5278c2ecf20Sopenharmony_ci u8 *buf; 5288c2ecf20Sopenharmony_ci 5298c2ecf20Sopenharmony_ci /* format: 5308c2ecf20Sopenharmony_ci b0: net status 5318c2ecf20Sopenharmony_ci b1: tx status 1 5328c2ecf20Sopenharmony_ci b2: tx status 2 5338c2ecf20Sopenharmony_ci b3: rx status 5348c2ecf20Sopenharmony_ci b4: rx overflow 5358c2ecf20Sopenharmony_ci b5: rx count 5368c2ecf20Sopenharmony_ci b6: tx count 5378c2ecf20Sopenharmony_ci b7: gpr 5388c2ecf20Sopenharmony_ci */ 5398c2ecf20Sopenharmony_ci 5408c2ecf20Sopenharmony_ci if (urb->actual_length < 8) 5418c2ecf20Sopenharmony_ci return; 5428c2ecf20Sopenharmony_ci 5438c2ecf20Sopenharmony_ci buf = urb->transfer_buffer; 5448c2ecf20Sopenharmony_ci 5458c2ecf20Sopenharmony_ci link = !!(buf[0] & 0x40); 5468c2ecf20Sopenharmony_ci if (netif_carrier_ok(dev->net) != link) { 5478c2ecf20Sopenharmony_ci usbnet_link_change(dev, link, 1); 5488c2ecf20Sopenharmony_ci netdev_dbg(dev->net, "Link Status is: %d\n", link); 5498c2ecf20Sopenharmony_ci } 5508c2ecf20Sopenharmony_ci} 5518c2ecf20Sopenharmony_ci 5528c2ecf20Sopenharmony_cistatic int dm9601_link_reset(struct usbnet *dev) 5538c2ecf20Sopenharmony_ci{ 5548c2ecf20Sopenharmony_ci struct ethtool_cmd ecmd = { .cmd = ETHTOOL_GSET }; 5558c2ecf20Sopenharmony_ci 5568c2ecf20Sopenharmony_ci mii_check_media(&dev->mii, 1, 1); 5578c2ecf20Sopenharmony_ci mii_ethtool_gset(&dev->mii, &ecmd); 5588c2ecf20Sopenharmony_ci 5598c2ecf20Sopenharmony_ci netdev_dbg(dev->net, "link_reset() speed: %u duplex: %d\n", 5608c2ecf20Sopenharmony_ci ethtool_cmd_speed(&ecmd), ecmd.duplex); 5618c2ecf20Sopenharmony_ci 5628c2ecf20Sopenharmony_ci return 0; 5638c2ecf20Sopenharmony_ci} 5648c2ecf20Sopenharmony_ci 5658c2ecf20Sopenharmony_cistatic const struct driver_info dm9601_info = { 5668c2ecf20Sopenharmony_ci .description = "Davicom DM96xx USB 10/100 Ethernet", 5678c2ecf20Sopenharmony_ci .flags = FLAG_ETHER | FLAG_LINK_INTR, 5688c2ecf20Sopenharmony_ci .bind = dm9601_bind, 5698c2ecf20Sopenharmony_ci .rx_fixup = dm9601_rx_fixup, 5708c2ecf20Sopenharmony_ci .tx_fixup = dm9601_tx_fixup, 5718c2ecf20Sopenharmony_ci .status = dm9601_status, 5728c2ecf20Sopenharmony_ci .link_reset = dm9601_link_reset, 5738c2ecf20Sopenharmony_ci .reset = dm9601_link_reset, 5748c2ecf20Sopenharmony_ci}; 5758c2ecf20Sopenharmony_ci 5768c2ecf20Sopenharmony_cistatic const struct usb_device_id products[] = { 5778c2ecf20Sopenharmony_ci { 5788c2ecf20Sopenharmony_ci USB_DEVICE(0x07aa, 0x9601), /* Corega FEther USB-TXC */ 5798c2ecf20Sopenharmony_ci .driver_info = (unsigned long)&dm9601_info, 5808c2ecf20Sopenharmony_ci }, 5818c2ecf20Sopenharmony_ci { 5828c2ecf20Sopenharmony_ci USB_DEVICE(0x0a46, 0x9601), /* Davicom USB-100 */ 5838c2ecf20Sopenharmony_ci .driver_info = (unsigned long)&dm9601_info, 5848c2ecf20Sopenharmony_ci }, 5858c2ecf20Sopenharmony_ci { 5868c2ecf20Sopenharmony_ci USB_DEVICE(0x0a46, 0x6688), /* ZT6688 USB NIC */ 5878c2ecf20Sopenharmony_ci .driver_info = (unsigned long)&dm9601_info, 5888c2ecf20Sopenharmony_ci }, 5898c2ecf20Sopenharmony_ci { 5908c2ecf20Sopenharmony_ci USB_DEVICE(0x0a46, 0x0268), /* ShanTou ST268 USB NIC */ 5918c2ecf20Sopenharmony_ci .driver_info = (unsigned long)&dm9601_info, 5928c2ecf20Sopenharmony_ci }, 5938c2ecf20Sopenharmony_ci { 5948c2ecf20Sopenharmony_ci USB_DEVICE(0x0a46, 0x8515), /* ADMtek ADM8515 USB NIC */ 5958c2ecf20Sopenharmony_ci .driver_info = (unsigned long)&dm9601_info, 5968c2ecf20Sopenharmony_ci }, 5978c2ecf20Sopenharmony_ci { 5988c2ecf20Sopenharmony_ci USB_DEVICE(0x0a47, 0x9601), /* Hirose USB-100 */ 5998c2ecf20Sopenharmony_ci .driver_info = (unsigned long)&dm9601_info, 6008c2ecf20Sopenharmony_ci }, 6018c2ecf20Sopenharmony_ci { 6028c2ecf20Sopenharmony_ci USB_DEVICE(0x0fe6, 0x8101), /* DM9601 USB to Fast Ethernet Adapter */ 6038c2ecf20Sopenharmony_ci .driver_info = (unsigned long)&dm9601_info, 6048c2ecf20Sopenharmony_ci }, 6058c2ecf20Sopenharmony_ci { 6068c2ecf20Sopenharmony_ci USB_DEVICE(0x0fe6, 0x9700), /* DM9601 USB to Fast Ethernet Adapter */ 6078c2ecf20Sopenharmony_ci .driver_info = (unsigned long)&dm9601_info, 6088c2ecf20Sopenharmony_ci }, 6098c2ecf20Sopenharmony_ci { 6108c2ecf20Sopenharmony_ci USB_DEVICE(0x0a46, 0x9000), /* DM9000E */ 6118c2ecf20Sopenharmony_ci .driver_info = (unsigned long)&dm9601_info, 6128c2ecf20Sopenharmony_ci }, 6138c2ecf20Sopenharmony_ci { 6148c2ecf20Sopenharmony_ci USB_DEVICE(0x0a46, 0x9620), /* DM9620 USB to Fast Ethernet Adapter */ 6158c2ecf20Sopenharmony_ci .driver_info = (unsigned long)&dm9601_info, 6168c2ecf20Sopenharmony_ci }, 6178c2ecf20Sopenharmony_ci { 6188c2ecf20Sopenharmony_ci USB_DEVICE(0x0a46, 0x9621), /* DM9621A USB to Fast Ethernet Adapter */ 6198c2ecf20Sopenharmony_ci .driver_info = (unsigned long)&dm9601_info, 6208c2ecf20Sopenharmony_ci }, 6218c2ecf20Sopenharmony_ci { 6228c2ecf20Sopenharmony_ci USB_DEVICE(0x0a46, 0x9622), /* DM9622 USB to Fast Ethernet Adapter */ 6238c2ecf20Sopenharmony_ci .driver_info = (unsigned long)&dm9601_info, 6248c2ecf20Sopenharmony_ci }, 6258c2ecf20Sopenharmony_ci { 6268c2ecf20Sopenharmony_ci USB_DEVICE(0x0a46, 0x0269), /* DM962OA USB to Fast Ethernet Adapter */ 6278c2ecf20Sopenharmony_ci .driver_info = (unsigned long)&dm9601_info, 6288c2ecf20Sopenharmony_ci }, 6298c2ecf20Sopenharmony_ci { 6308c2ecf20Sopenharmony_ci USB_DEVICE(0x0a46, 0x1269), /* DM9621A USB to Fast Ethernet Adapter */ 6318c2ecf20Sopenharmony_ci .driver_info = (unsigned long)&dm9601_info, 6328c2ecf20Sopenharmony_ci }, 6338c2ecf20Sopenharmony_ci { 6348c2ecf20Sopenharmony_ci USB_DEVICE(0x0586, 0x3427), /* ZyXEL Keenetic Plus DSL xDSL modem */ 6358c2ecf20Sopenharmony_ci .driver_info = (unsigned long)&dm9601_info, 6368c2ecf20Sopenharmony_ci }, 6378c2ecf20Sopenharmony_ci {}, // END 6388c2ecf20Sopenharmony_ci}; 6398c2ecf20Sopenharmony_ci 6408c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(usb, products); 6418c2ecf20Sopenharmony_ci 6428c2ecf20Sopenharmony_cistatic struct usb_driver dm9601_driver = { 6438c2ecf20Sopenharmony_ci .name = "dm9601", 6448c2ecf20Sopenharmony_ci .id_table = products, 6458c2ecf20Sopenharmony_ci .probe = usbnet_probe, 6468c2ecf20Sopenharmony_ci .disconnect = usbnet_disconnect, 6478c2ecf20Sopenharmony_ci .suspend = usbnet_suspend, 6488c2ecf20Sopenharmony_ci .resume = usbnet_resume, 6498c2ecf20Sopenharmony_ci .disable_hub_initiated_lpm = 1, 6508c2ecf20Sopenharmony_ci}; 6518c2ecf20Sopenharmony_ci 6528c2ecf20Sopenharmony_cimodule_usb_driver(dm9601_driver); 6538c2ecf20Sopenharmony_ci 6548c2ecf20Sopenharmony_ciMODULE_AUTHOR("Peter Korsgaard <jacmet@sunsite.dk>"); 6558c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Davicom DM96xx USB 10/100 ethernet devices"); 6568c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 657