18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Driver for USB ethernet port of Conexant CX82310-based ADSL routers
48c2ecf20Sopenharmony_ci * Copyright (C) 2010 by Ondrej Zary
58c2ecf20Sopenharmony_ci * some parts inspired by the cxacru driver
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/module.h>
98c2ecf20Sopenharmony_ci#include <linux/netdevice.h>
108c2ecf20Sopenharmony_ci#include <linux/etherdevice.h>
118c2ecf20Sopenharmony_ci#include <linux/ethtool.h>
128c2ecf20Sopenharmony_ci#include <linux/workqueue.h>
138c2ecf20Sopenharmony_ci#include <linux/mii.h>
148c2ecf20Sopenharmony_ci#include <linux/usb.h>
158c2ecf20Sopenharmony_ci#include <linux/usb/usbnet.h>
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_cienum cx82310_cmd {
188c2ecf20Sopenharmony_ci	CMD_START		= 0x84,	/* no effect? */
198c2ecf20Sopenharmony_ci	CMD_STOP		= 0x85,	/* no effect? */
208c2ecf20Sopenharmony_ci	CMD_GET_STATUS		= 0x90,	/* returns nothing? */
218c2ecf20Sopenharmony_ci	CMD_GET_MAC_ADDR	= 0x91,	/* read MAC address */
228c2ecf20Sopenharmony_ci	CMD_GET_LINK_STATUS	= 0x92,	/* not useful, link is always up */
238c2ecf20Sopenharmony_ci	CMD_ETHERNET_MODE	= 0x99,	/* unknown, needed during init */
248c2ecf20Sopenharmony_ci};
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_cienum cx82310_status {
278c2ecf20Sopenharmony_ci	STATUS_UNDEFINED,
288c2ecf20Sopenharmony_ci	STATUS_SUCCESS,
298c2ecf20Sopenharmony_ci	STATUS_ERROR,
308c2ecf20Sopenharmony_ci	STATUS_UNSUPPORTED,
318c2ecf20Sopenharmony_ci	STATUS_UNIMPLEMENTED,
328c2ecf20Sopenharmony_ci	STATUS_PARAMETER_ERROR,
338c2ecf20Sopenharmony_ci	STATUS_DBG_LOOPBACK,
348c2ecf20Sopenharmony_ci};
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci#define CMD_PACKET_SIZE	64
378c2ecf20Sopenharmony_ci#define CMD_TIMEOUT	100
388c2ecf20Sopenharmony_ci#define CMD_REPLY_RETRY 5
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci#define CX82310_MTU	1514
418c2ecf20Sopenharmony_ci#define CMD_EP		0x01
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_cistruct cx82310_priv {
448c2ecf20Sopenharmony_ci	struct work_struct reenable_work;
458c2ecf20Sopenharmony_ci	struct usbnet *dev;
468c2ecf20Sopenharmony_ci};
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci/*
498c2ecf20Sopenharmony_ci * execute control command
508c2ecf20Sopenharmony_ci *  - optionally send some data (command parameters)
518c2ecf20Sopenharmony_ci *  - optionally wait for the reply
528c2ecf20Sopenharmony_ci *  - optionally read some data from the reply
538c2ecf20Sopenharmony_ci */
548c2ecf20Sopenharmony_cistatic int cx82310_cmd(struct usbnet *dev, enum cx82310_cmd cmd, bool reply,
558c2ecf20Sopenharmony_ci		       u8 *wdata, int wlen, u8 *rdata, int rlen)
568c2ecf20Sopenharmony_ci{
578c2ecf20Sopenharmony_ci	int actual_len, retries, ret;
588c2ecf20Sopenharmony_ci	struct usb_device *udev = dev->udev;
598c2ecf20Sopenharmony_ci	u8 *buf = kzalloc(CMD_PACKET_SIZE, GFP_KERNEL);
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci	if (!buf)
628c2ecf20Sopenharmony_ci		return -ENOMEM;
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci	/* create command packet */
658c2ecf20Sopenharmony_ci	buf[0] = cmd;
668c2ecf20Sopenharmony_ci	if (wdata)
678c2ecf20Sopenharmony_ci		memcpy(buf + 4, wdata, min_t(int, wlen, CMD_PACKET_SIZE - 4));
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci	/* send command packet */
708c2ecf20Sopenharmony_ci	ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, CMD_EP), buf,
718c2ecf20Sopenharmony_ci			   CMD_PACKET_SIZE, &actual_len, CMD_TIMEOUT);
728c2ecf20Sopenharmony_ci	if (ret < 0) {
738c2ecf20Sopenharmony_ci		if (cmd != CMD_GET_LINK_STATUS)
748c2ecf20Sopenharmony_ci			netdev_err(dev->net, "send command %#x: error %d\n",
758c2ecf20Sopenharmony_ci				   cmd, ret);
768c2ecf20Sopenharmony_ci		goto end;
778c2ecf20Sopenharmony_ci	}
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	if (reply) {
808c2ecf20Sopenharmony_ci		/* wait for reply, retry if it's empty */
818c2ecf20Sopenharmony_ci		for (retries = 0; retries < CMD_REPLY_RETRY; retries++) {
828c2ecf20Sopenharmony_ci			ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, CMD_EP),
838c2ecf20Sopenharmony_ci					   buf, CMD_PACKET_SIZE, &actual_len,
848c2ecf20Sopenharmony_ci					   CMD_TIMEOUT);
858c2ecf20Sopenharmony_ci			if (ret < 0) {
868c2ecf20Sopenharmony_ci				if (cmd != CMD_GET_LINK_STATUS)
878c2ecf20Sopenharmony_ci					netdev_err(dev->net, "reply receive error %d\n",
888c2ecf20Sopenharmony_ci						   ret);
898c2ecf20Sopenharmony_ci				goto end;
908c2ecf20Sopenharmony_ci			}
918c2ecf20Sopenharmony_ci			if (actual_len > 0)
928c2ecf20Sopenharmony_ci				break;
938c2ecf20Sopenharmony_ci		}
948c2ecf20Sopenharmony_ci		if (actual_len == 0) {
958c2ecf20Sopenharmony_ci			netdev_err(dev->net, "no reply to command %#x\n", cmd);
968c2ecf20Sopenharmony_ci			ret = -EIO;
978c2ecf20Sopenharmony_ci			goto end;
988c2ecf20Sopenharmony_ci		}
998c2ecf20Sopenharmony_ci		if (buf[0] != cmd) {
1008c2ecf20Sopenharmony_ci			netdev_err(dev->net, "got reply to command %#x, expected: %#x\n",
1018c2ecf20Sopenharmony_ci				   buf[0], cmd);
1028c2ecf20Sopenharmony_ci			ret = -EIO;
1038c2ecf20Sopenharmony_ci			goto end;
1048c2ecf20Sopenharmony_ci		}
1058c2ecf20Sopenharmony_ci		if (buf[1] != STATUS_SUCCESS) {
1068c2ecf20Sopenharmony_ci			netdev_err(dev->net, "command %#x failed: %#x\n", cmd,
1078c2ecf20Sopenharmony_ci				   buf[1]);
1088c2ecf20Sopenharmony_ci			ret = -EIO;
1098c2ecf20Sopenharmony_ci			goto end;
1108c2ecf20Sopenharmony_ci		}
1118c2ecf20Sopenharmony_ci		if (rdata)
1128c2ecf20Sopenharmony_ci			memcpy(rdata, buf + 4,
1138c2ecf20Sopenharmony_ci			       min_t(int, rlen, CMD_PACKET_SIZE - 4));
1148c2ecf20Sopenharmony_ci	}
1158c2ecf20Sopenharmony_ciend:
1168c2ecf20Sopenharmony_ci	kfree(buf);
1178c2ecf20Sopenharmony_ci	return ret;
1188c2ecf20Sopenharmony_ci}
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_cistatic int cx82310_enable_ethernet(struct usbnet *dev)
1218c2ecf20Sopenharmony_ci{
1228c2ecf20Sopenharmony_ci	int ret = cx82310_cmd(dev, CMD_ETHERNET_MODE, true, "\x01", 1, NULL, 0);
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	if (ret)
1258c2ecf20Sopenharmony_ci		netdev_err(dev->net, "unable to enable ethernet mode: %d\n",
1268c2ecf20Sopenharmony_ci			   ret);
1278c2ecf20Sopenharmony_ci	return ret;
1288c2ecf20Sopenharmony_ci}
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_cistatic void cx82310_reenable_work(struct work_struct *work)
1318c2ecf20Sopenharmony_ci{
1328c2ecf20Sopenharmony_ci	struct cx82310_priv *priv = container_of(work, struct cx82310_priv,
1338c2ecf20Sopenharmony_ci						 reenable_work);
1348c2ecf20Sopenharmony_ci	cx82310_enable_ethernet(priv->dev);
1358c2ecf20Sopenharmony_ci}
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci#define partial_len	data[0]		/* length of partial packet data */
1388c2ecf20Sopenharmony_ci#define partial_rem	data[1]		/* remaining (missing) data length */
1398c2ecf20Sopenharmony_ci#define partial_data	data[2]		/* partial packet data */
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_cistatic int cx82310_bind(struct usbnet *dev, struct usb_interface *intf)
1428c2ecf20Sopenharmony_ci{
1438c2ecf20Sopenharmony_ci	int ret;
1448c2ecf20Sopenharmony_ci	char buf[15];
1458c2ecf20Sopenharmony_ci	struct usb_device *udev = dev->udev;
1468c2ecf20Sopenharmony_ci	u8 link[3];
1478c2ecf20Sopenharmony_ci	int timeout = 50;
1488c2ecf20Sopenharmony_ci	struct cx82310_priv *priv;
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci	/* avoid ADSL modems - continue only if iProduct is "USB NET CARD" */
1518c2ecf20Sopenharmony_ci	if (usb_string(udev, udev->descriptor.iProduct, buf, sizeof(buf)) > 0
1528c2ecf20Sopenharmony_ci	    && strcmp(buf, "USB NET CARD")) {
1538c2ecf20Sopenharmony_ci		dev_info(&udev->dev, "ignoring: probably an ADSL modem\n");
1548c2ecf20Sopenharmony_ci		return -ENODEV;
1558c2ecf20Sopenharmony_ci	}
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	ret = usbnet_get_endpoints(dev, intf);
1588c2ecf20Sopenharmony_ci	if (ret)
1598c2ecf20Sopenharmony_ci		return ret;
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci	/*
1628c2ecf20Sopenharmony_ci	 * this must not include ethernet header as the device can send partial
1638c2ecf20Sopenharmony_ci	 * packets with no header (and sometimes even empty URBs)
1648c2ecf20Sopenharmony_ci	 */
1658c2ecf20Sopenharmony_ci	dev->net->hard_header_len = 0;
1668c2ecf20Sopenharmony_ci	/* we can send at most 1514 bytes of data (+ 2-byte header) per URB */
1678c2ecf20Sopenharmony_ci	dev->hard_mtu = CX82310_MTU + 2;
1688c2ecf20Sopenharmony_ci	/* we can receive URBs up to 4KB from the device */
1698c2ecf20Sopenharmony_ci	dev->rx_urb_size = 4096;
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci	dev->partial_data = (unsigned long) kmalloc(dev->hard_mtu, GFP_KERNEL);
1728c2ecf20Sopenharmony_ci	if (!dev->partial_data)
1738c2ecf20Sopenharmony_ci		return -ENOMEM;
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
1768c2ecf20Sopenharmony_ci	if (!priv) {
1778c2ecf20Sopenharmony_ci		ret = -ENOMEM;
1788c2ecf20Sopenharmony_ci		goto err_partial;
1798c2ecf20Sopenharmony_ci	}
1808c2ecf20Sopenharmony_ci	dev->driver_priv = priv;
1818c2ecf20Sopenharmony_ci	INIT_WORK(&priv->reenable_work, cx82310_reenable_work);
1828c2ecf20Sopenharmony_ci	priv->dev = dev;
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	/* wait for firmware to become ready (indicated by the link being up) */
1858c2ecf20Sopenharmony_ci	while (--timeout) {
1868c2ecf20Sopenharmony_ci		ret = cx82310_cmd(dev, CMD_GET_LINK_STATUS, true, NULL, 0,
1878c2ecf20Sopenharmony_ci				  link, sizeof(link));
1888c2ecf20Sopenharmony_ci		/* the command can time out during boot - it's not an error */
1898c2ecf20Sopenharmony_ci		if (!ret && link[0] == 1 && link[2] == 1)
1908c2ecf20Sopenharmony_ci			break;
1918c2ecf20Sopenharmony_ci		msleep(500);
1928c2ecf20Sopenharmony_ci	}
1938c2ecf20Sopenharmony_ci	if (!timeout) {
1948c2ecf20Sopenharmony_ci		netdev_err(dev->net, "firmware not ready in time\n");
1958c2ecf20Sopenharmony_ci		ret = -ETIMEDOUT;
1968c2ecf20Sopenharmony_ci		goto err;
1978c2ecf20Sopenharmony_ci	}
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ci	/* enable ethernet mode (?) */
2008c2ecf20Sopenharmony_ci	ret = cx82310_enable_ethernet(dev);
2018c2ecf20Sopenharmony_ci	if (ret)
2028c2ecf20Sopenharmony_ci		goto err;
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci	/* get the MAC address */
2058c2ecf20Sopenharmony_ci	ret = cx82310_cmd(dev, CMD_GET_MAC_ADDR, true, NULL, 0,
2068c2ecf20Sopenharmony_ci			  dev->net->dev_addr, ETH_ALEN);
2078c2ecf20Sopenharmony_ci	if (ret) {
2088c2ecf20Sopenharmony_ci		netdev_err(dev->net, "unable to read MAC address: %d\n", ret);
2098c2ecf20Sopenharmony_ci		goto err;
2108c2ecf20Sopenharmony_ci	}
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci	/* start (does not seem to have any effect?) */
2138c2ecf20Sopenharmony_ci	ret = cx82310_cmd(dev, CMD_START, false, NULL, 0, NULL, 0);
2148c2ecf20Sopenharmony_ci	if (ret)
2158c2ecf20Sopenharmony_ci		goto err;
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci	return 0;
2188c2ecf20Sopenharmony_cierr:
2198c2ecf20Sopenharmony_ci	kfree(dev->driver_priv);
2208c2ecf20Sopenharmony_cierr_partial:
2218c2ecf20Sopenharmony_ci	kfree((void *)dev->partial_data);
2228c2ecf20Sopenharmony_ci	return ret;
2238c2ecf20Sopenharmony_ci}
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_cistatic void cx82310_unbind(struct usbnet *dev, struct usb_interface *intf)
2268c2ecf20Sopenharmony_ci{
2278c2ecf20Sopenharmony_ci	struct cx82310_priv *priv = dev->driver_priv;
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_ci	kfree((void *)dev->partial_data);
2308c2ecf20Sopenharmony_ci	cancel_work_sync(&priv->reenable_work);
2318c2ecf20Sopenharmony_ci	kfree(dev->driver_priv);
2328c2ecf20Sopenharmony_ci}
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_ci/*
2358c2ecf20Sopenharmony_ci * RX is NOT easy - we can receive multiple packets per skb, each having 2-byte
2368c2ecf20Sopenharmony_ci * packet length at the beginning.
2378c2ecf20Sopenharmony_ci * The last packet might be incomplete (when it crosses the 4KB URB size),
2388c2ecf20Sopenharmony_ci * continuing in the next skb (without any headers).
2398c2ecf20Sopenharmony_ci * If a packet has odd length, there is one extra byte at the end (before next
2408c2ecf20Sopenharmony_ci * packet or at the end of the URB).
2418c2ecf20Sopenharmony_ci */
2428c2ecf20Sopenharmony_cistatic int cx82310_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
2438c2ecf20Sopenharmony_ci{
2448c2ecf20Sopenharmony_ci	int len;
2458c2ecf20Sopenharmony_ci	struct sk_buff *skb2;
2468c2ecf20Sopenharmony_ci	struct cx82310_priv *priv = dev->driver_priv;
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ci	/*
2498c2ecf20Sopenharmony_ci	 * If the last skb ended with an incomplete packet, this skb contains
2508c2ecf20Sopenharmony_ci	 * end of that packet at the beginning.
2518c2ecf20Sopenharmony_ci	 */
2528c2ecf20Sopenharmony_ci	if (dev->partial_rem) {
2538c2ecf20Sopenharmony_ci		len = dev->partial_len + dev->partial_rem;
2548c2ecf20Sopenharmony_ci		skb2 = alloc_skb(len, GFP_ATOMIC);
2558c2ecf20Sopenharmony_ci		if (!skb2)
2568c2ecf20Sopenharmony_ci			return 0;
2578c2ecf20Sopenharmony_ci		skb_put(skb2, len);
2588c2ecf20Sopenharmony_ci		memcpy(skb2->data, (void *)dev->partial_data,
2598c2ecf20Sopenharmony_ci		       dev->partial_len);
2608c2ecf20Sopenharmony_ci		memcpy(skb2->data + dev->partial_len, skb->data,
2618c2ecf20Sopenharmony_ci		       dev->partial_rem);
2628c2ecf20Sopenharmony_ci		usbnet_skb_return(dev, skb2);
2638c2ecf20Sopenharmony_ci		skb_pull(skb, (dev->partial_rem + 1) & ~1);
2648c2ecf20Sopenharmony_ci		dev->partial_rem = 0;
2658c2ecf20Sopenharmony_ci		if (skb->len < 2)
2668c2ecf20Sopenharmony_ci			return 1;
2678c2ecf20Sopenharmony_ci	}
2688c2ecf20Sopenharmony_ci
2698c2ecf20Sopenharmony_ci	/* a skb can contain multiple packets */
2708c2ecf20Sopenharmony_ci	while (skb->len > 1) {
2718c2ecf20Sopenharmony_ci		/* first two bytes are packet length */
2728c2ecf20Sopenharmony_ci		len = skb->data[0] | (skb->data[1] << 8);
2738c2ecf20Sopenharmony_ci		skb_pull(skb, 2);
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_ci		/* if last packet in the skb, let usbnet to process it */
2768c2ecf20Sopenharmony_ci		if (len == skb->len || len + 1 == skb->len) {
2778c2ecf20Sopenharmony_ci			skb_trim(skb, len);
2788c2ecf20Sopenharmony_ci			break;
2798c2ecf20Sopenharmony_ci		}
2808c2ecf20Sopenharmony_ci
2818c2ecf20Sopenharmony_ci		if (len == 0xffff) {
2828c2ecf20Sopenharmony_ci			netdev_info(dev->net, "router was rebooted, re-enabling ethernet mode");
2838c2ecf20Sopenharmony_ci			schedule_work(&priv->reenable_work);
2848c2ecf20Sopenharmony_ci		} else if (len > CX82310_MTU) {
2858c2ecf20Sopenharmony_ci			netdev_err(dev->net, "RX packet too long: %d B\n", len);
2868c2ecf20Sopenharmony_ci			return 0;
2878c2ecf20Sopenharmony_ci		}
2888c2ecf20Sopenharmony_ci
2898c2ecf20Sopenharmony_ci		/* incomplete packet, save it for the next skb */
2908c2ecf20Sopenharmony_ci		if (len > skb->len) {
2918c2ecf20Sopenharmony_ci			dev->partial_len = skb->len;
2928c2ecf20Sopenharmony_ci			dev->partial_rem = len - skb->len;
2938c2ecf20Sopenharmony_ci			memcpy((void *)dev->partial_data, skb->data,
2948c2ecf20Sopenharmony_ci			       dev->partial_len);
2958c2ecf20Sopenharmony_ci			skb_pull(skb, skb->len);
2968c2ecf20Sopenharmony_ci			break;
2978c2ecf20Sopenharmony_ci		}
2988c2ecf20Sopenharmony_ci
2998c2ecf20Sopenharmony_ci		skb2 = alloc_skb(len, GFP_ATOMIC);
3008c2ecf20Sopenharmony_ci		if (!skb2)
3018c2ecf20Sopenharmony_ci			return 0;
3028c2ecf20Sopenharmony_ci		skb_put(skb2, len);
3038c2ecf20Sopenharmony_ci		memcpy(skb2->data, skb->data, len);
3048c2ecf20Sopenharmony_ci		/* process the packet */
3058c2ecf20Sopenharmony_ci		usbnet_skb_return(dev, skb2);
3068c2ecf20Sopenharmony_ci
3078c2ecf20Sopenharmony_ci		skb_pull(skb, (len + 1) & ~1);
3088c2ecf20Sopenharmony_ci	}
3098c2ecf20Sopenharmony_ci
3108c2ecf20Sopenharmony_ci	/* let usbnet process the last packet */
3118c2ecf20Sopenharmony_ci	return 1;
3128c2ecf20Sopenharmony_ci}
3138c2ecf20Sopenharmony_ci
3148c2ecf20Sopenharmony_ci/* TX is easy, just add 2 bytes of length at the beginning */
3158c2ecf20Sopenharmony_cistatic struct sk_buff *cx82310_tx_fixup(struct usbnet *dev, struct sk_buff *skb,
3168c2ecf20Sopenharmony_ci				       gfp_t flags)
3178c2ecf20Sopenharmony_ci{
3188c2ecf20Sopenharmony_ci	int len = skb->len;
3198c2ecf20Sopenharmony_ci
3208c2ecf20Sopenharmony_ci	if (skb_cow_head(skb, 2)) {
3218c2ecf20Sopenharmony_ci		dev_kfree_skb_any(skb);
3228c2ecf20Sopenharmony_ci		return NULL;
3238c2ecf20Sopenharmony_ci	}
3248c2ecf20Sopenharmony_ci	skb_push(skb, 2);
3258c2ecf20Sopenharmony_ci
3268c2ecf20Sopenharmony_ci	skb->data[0] = len;
3278c2ecf20Sopenharmony_ci	skb->data[1] = len >> 8;
3288c2ecf20Sopenharmony_ci
3298c2ecf20Sopenharmony_ci	return skb;
3308c2ecf20Sopenharmony_ci}
3318c2ecf20Sopenharmony_ci
3328c2ecf20Sopenharmony_ci
3338c2ecf20Sopenharmony_cistatic const struct driver_info	cx82310_info = {
3348c2ecf20Sopenharmony_ci	.description	= "Conexant CX82310 USB ethernet",
3358c2ecf20Sopenharmony_ci	.flags		= FLAG_ETHER,
3368c2ecf20Sopenharmony_ci	.bind		= cx82310_bind,
3378c2ecf20Sopenharmony_ci	.unbind		= cx82310_unbind,
3388c2ecf20Sopenharmony_ci	.rx_fixup	= cx82310_rx_fixup,
3398c2ecf20Sopenharmony_ci	.tx_fixup	= cx82310_tx_fixup,
3408c2ecf20Sopenharmony_ci};
3418c2ecf20Sopenharmony_ci
3428c2ecf20Sopenharmony_ci#define USB_DEVICE_CLASS(vend, prod, cl, sc, pr) \
3438c2ecf20Sopenharmony_ci	.match_flags = USB_DEVICE_ID_MATCH_DEVICE | \
3448c2ecf20Sopenharmony_ci		       USB_DEVICE_ID_MATCH_DEV_INFO, \
3458c2ecf20Sopenharmony_ci	.idVendor = (vend), \
3468c2ecf20Sopenharmony_ci	.idProduct = (prod), \
3478c2ecf20Sopenharmony_ci	.bDeviceClass = (cl), \
3488c2ecf20Sopenharmony_ci	.bDeviceSubClass = (sc), \
3498c2ecf20Sopenharmony_ci	.bDeviceProtocol = (pr)
3508c2ecf20Sopenharmony_ci
3518c2ecf20Sopenharmony_cistatic const struct usb_device_id products[] = {
3528c2ecf20Sopenharmony_ci	{
3538c2ecf20Sopenharmony_ci		USB_DEVICE_CLASS(0x0572, 0xcb01, 0xff, 0, 0),
3548c2ecf20Sopenharmony_ci		.driver_info = (unsigned long) &cx82310_info
3558c2ecf20Sopenharmony_ci	},
3568c2ecf20Sopenharmony_ci	{ },
3578c2ecf20Sopenharmony_ci};
3588c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(usb, products);
3598c2ecf20Sopenharmony_ci
3608c2ecf20Sopenharmony_cistatic struct usb_driver cx82310_driver = {
3618c2ecf20Sopenharmony_ci	.name		= "cx82310_eth",
3628c2ecf20Sopenharmony_ci	.id_table	= products,
3638c2ecf20Sopenharmony_ci	.probe		= usbnet_probe,
3648c2ecf20Sopenharmony_ci	.disconnect	= usbnet_disconnect,
3658c2ecf20Sopenharmony_ci	.suspend	= usbnet_suspend,
3668c2ecf20Sopenharmony_ci	.resume		= usbnet_resume,
3678c2ecf20Sopenharmony_ci	.disable_hub_initiated_lpm = 1,
3688c2ecf20Sopenharmony_ci};
3698c2ecf20Sopenharmony_ci
3708c2ecf20Sopenharmony_cimodule_usb_driver(cx82310_driver);
3718c2ecf20Sopenharmony_ci
3728c2ecf20Sopenharmony_ciMODULE_AUTHOR("Ondrej Zary");
3738c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Conexant CX82310-based ADSL router USB ethernet driver");
3748c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
375