11ccea77eSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2cc28a20eSOndrej Zary /*
3cc28a20eSOndrej Zary * Driver for USB ethernet port of Conexant CX82310-based ADSL routers
4cc28a20eSOndrej Zary * Copyright (C) 2010 by Ondrej Zary
5cc28a20eSOndrej Zary * some parts inspired by the cxacru driver
6cc28a20eSOndrej Zary */
7cc28a20eSOndrej Zary
8cc28a20eSOndrej Zary #include <linux/module.h>
9cc28a20eSOndrej Zary #include <linux/netdevice.h>
10cc28a20eSOndrej Zary #include <linux/etherdevice.h>
11cc28a20eSOndrej Zary #include <linux/ethtool.h>
12cc28a20eSOndrej Zary #include <linux/workqueue.h>
13cc28a20eSOndrej Zary #include <linux/mii.h>
14cc28a20eSOndrej Zary #include <linux/usb.h>
15cc28a20eSOndrej Zary #include <linux/usb/usbnet.h>
16cc28a20eSOndrej Zary
17cc28a20eSOndrej Zary enum cx82310_cmd {
18cc28a20eSOndrej Zary CMD_START = 0x84, /* no effect? */
19cc28a20eSOndrej Zary CMD_STOP = 0x85, /* no effect? */
20cc28a20eSOndrej Zary CMD_GET_STATUS = 0x90, /* returns nothing? */
21cc28a20eSOndrej Zary CMD_GET_MAC_ADDR = 0x91, /* read MAC address */
22cc28a20eSOndrej Zary CMD_GET_LINK_STATUS = 0x92, /* not useful, link is always up */
23cc28a20eSOndrej Zary CMD_ETHERNET_MODE = 0x99, /* unknown, needed during init */
24cc28a20eSOndrej Zary };
25cc28a20eSOndrej Zary
26cc28a20eSOndrej Zary enum cx82310_status {
27cc28a20eSOndrej Zary STATUS_UNDEFINED,
28cc28a20eSOndrej Zary STATUS_SUCCESS,
29cc28a20eSOndrej Zary STATUS_ERROR,
30cc28a20eSOndrej Zary STATUS_UNSUPPORTED,
31cc28a20eSOndrej Zary STATUS_UNIMPLEMENTED,
32cc28a20eSOndrej Zary STATUS_PARAMETER_ERROR,
33cc28a20eSOndrej Zary STATUS_DBG_LOOPBACK,
34cc28a20eSOndrej Zary };
35cc28a20eSOndrej Zary
36cc28a20eSOndrej Zary #define CMD_PACKET_SIZE 64
37f40bff42SOndrej Zary #define CMD_TIMEOUT 100
38cc28a20eSOndrej Zary #define CMD_REPLY_RETRY 5
39cc28a20eSOndrej Zary
40cc28a20eSOndrej Zary #define CX82310_MTU 1514
41cc28a20eSOndrej Zary #define CMD_EP 0x01
42cc28a20eSOndrej Zary
43ca139d76SOndrej Zary struct cx82310_priv {
44ca139d76SOndrej Zary struct work_struct reenable_work;
45ca139d76SOndrej Zary struct usbnet *dev;
46ca139d76SOndrej Zary };
47ca139d76SOndrej Zary
48cc28a20eSOndrej Zary /*
49cc28a20eSOndrej Zary * execute control command
50cc28a20eSOndrej Zary * - optionally send some data (command parameters)
51cc28a20eSOndrej Zary * - optionally wait for the reply
52cc28a20eSOndrej Zary * - optionally read some data from the reply
53cc28a20eSOndrej Zary */
cx82310_cmd(struct usbnet * dev,enum cx82310_cmd cmd,bool reply,u8 * wdata,int wlen,u8 * rdata,int rlen)54cc28a20eSOndrej Zary static int cx82310_cmd(struct usbnet *dev, enum cx82310_cmd cmd, bool reply,
55cc28a20eSOndrej Zary u8 *wdata, int wlen, u8 *rdata, int rlen)
56cc28a20eSOndrej Zary {
57cc28a20eSOndrej Zary int actual_len, retries, ret;
58cc28a20eSOndrej Zary struct usb_device *udev = dev->udev;
59cc28a20eSOndrej Zary u8 *buf = kzalloc(CMD_PACKET_SIZE, GFP_KERNEL);
60cc28a20eSOndrej Zary
61cc28a20eSOndrej Zary if (!buf)
62cc28a20eSOndrej Zary return -ENOMEM;
63cc28a20eSOndrej Zary
64cc28a20eSOndrej Zary /* create command packet */
65cc28a20eSOndrej Zary buf[0] = cmd;
66cc28a20eSOndrej Zary if (wdata)
67cc28a20eSOndrej Zary memcpy(buf + 4, wdata, min_t(int, wlen, CMD_PACKET_SIZE - 4));
68cc28a20eSOndrej Zary
69cc28a20eSOndrej Zary /* send command packet */
70cc28a20eSOndrej Zary ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, CMD_EP), buf,
71cc28a20eSOndrej Zary CMD_PACKET_SIZE, &actual_len, CMD_TIMEOUT);
72cc28a20eSOndrej Zary if (ret < 0) {
73f40bff42SOndrej Zary if (cmd != CMD_GET_LINK_STATUS)
7415f5e48fSOndrej Zary netdev_err(dev->net, "send command %#x: error %d\n",
75cc28a20eSOndrej Zary cmd, ret);
76cc28a20eSOndrej Zary goto end;
77cc28a20eSOndrej Zary }
78cc28a20eSOndrej Zary
79cc28a20eSOndrej Zary if (reply) {
80cc28a20eSOndrej Zary /* wait for reply, retry if it's empty */
81cc28a20eSOndrej Zary for (retries = 0; retries < CMD_REPLY_RETRY; retries++) {
82cc28a20eSOndrej Zary ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, CMD_EP),
83cc28a20eSOndrej Zary buf, CMD_PACKET_SIZE, &actual_len,
84cc28a20eSOndrej Zary CMD_TIMEOUT);
85cc28a20eSOndrej Zary if (ret < 0) {
86f40bff42SOndrej Zary if (cmd != CMD_GET_LINK_STATUS)
8715f5e48fSOndrej Zary netdev_err(dev->net, "reply receive error %d\n",
88f40bff42SOndrej Zary ret);
89cc28a20eSOndrej Zary goto end;
90cc28a20eSOndrej Zary }
91cc28a20eSOndrej Zary if (actual_len > 0)
92cc28a20eSOndrej Zary break;
93cc28a20eSOndrej Zary }
94cc28a20eSOndrej Zary if (actual_len == 0) {
9515f5e48fSOndrej Zary netdev_err(dev->net, "no reply to command %#x\n", cmd);
96cc28a20eSOndrej Zary ret = -EIO;
97cc28a20eSOndrej Zary goto end;
98cc28a20eSOndrej Zary }
99cc28a20eSOndrej Zary if (buf[0] != cmd) {
10015f5e48fSOndrej Zary netdev_err(dev->net, "got reply to command %#x, expected: %#x\n",
101cc28a20eSOndrej Zary buf[0], cmd);
102cc28a20eSOndrej Zary ret = -EIO;
103cc28a20eSOndrej Zary goto end;
104cc28a20eSOndrej Zary }
105cc28a20eSOndrej Zary if (buf[1] != STATUS_SUCCESS) {
10615f5e48fSOndrej Zary netdev_err(dev->net, "command %#x failed: %#x\n", cmd,
10715f5e48fSOndrej Zary buf[1]);
108cc28a20eSOndrej Zary ret = -EIO;
109cc28a20eSOndrej Zary goto end;
110cc28a20eSOndrej Zary }
111cc28a20eSOndrej Zary if (rdata)
112cc28a20eSOndrej Zary memcpy(rdata, buf + 4,
113cc28a20eSOndrej Zary min_t(int, rlen, CMD_PACKET_SIZE - 4));
114cc28a20eSOndrej Zary }
115cc28a20eSOndrej Zary end:
116cc28a20eSOndrej Zary kfree(buf);
117cc28a20eSOndrej Zary return ret;
118cc28a20eSOndrej Zary }
119cc28a20eSOndrej Zary
cx82310_enable_ethernet(struct usbnet * dev)120ca139d76SOndrej Zary static int cx82310_enable_ethernet(struct usbnet *dev)
121ca139d76SOndrej Zary {
122ca139d76SOndrej Zary int ret = cx82310_cmd(dev, CMD_ETHERNET_MODE, true, "\x01", 1, NULL, 0);
123ca139d76SOndrej Zary
124ca139d76SOndrej Zary if (ret)
125ca139d76SOndrej Zary netdev_err(dev->net, "unable to enable ethernet mode: %d\n",
126ca139d76SOndrej Zary ret);
127ca139d76SOndrej Zary return ret;
128ca139d76SOndrej Zary }
129ca139d76SOndrej Zary
cx82310_reenable_work(struct work_struct * work)130ca139d76SOndrej Zary static void cx82310_reenable_work(struct work_struct *work)
131ca139d76SOndrej Zary {
132ca139d76SOndrej Zary struct cx82310_priv *priv = container_of(work, struct cx82310_priv,
133ca139d76SOndrej Zary reenable_work);
134ca139d76SOndrej Zary cx82310_enable_ethernet(priv->dev);
135ca139d76SOndrej Zary }
136ca139d76SOndrej Zary
137cc28a20eSOndrej Zary #define partial_len data[0] /* length of partial packet data */
138cc28a20eSOndrej Zary #define partial_rem data[1] /* remaining (missing) data length */
139cc28a20eSOndrej Zary #define partial_data data[2] /* partial packet data */
140cc28a20eSOndrej Zary
cx82310_bind(struct usbnet * dev,struct usb_interface * intf)141cc28a20eSOndrej Zary static int cx82310_bind(struct usbnet *dev, struct usb_interface *intf)
142cc28a20eSOndrej Zary {
143cc28a20eSOndrej Zary int ret;
144cc28a20eSOndrej Zary char buf[15];
145cc28a20eSOndrej Zary struct usb_device *udev = dev->udev;
146f40bff42SOndrej Zary u8 link[3];
147f40bff42SOndrej Zary int timeout = 50;
148ca139d76SOndrej Zary struct cx82310_priv *priv;
149*2674e7eaSJakub Kicinski u8 addr[ETH_ALEN];
150cc28a20eSOndrej Zary
151cc28a20eSOndrej Zary /* avoid ADSL modems - continue only if iProduct is "USB NET CARD" */
1527dbfdc23SOndrej Zary if (usb_string(udev, udev->descriptor.iProduct, buf, sizeof(buf)) > 0
1537dbfdc23SOndrej Zary && strcmp(buf, "USB NET CARD")) {
1547dbfdc23SOndrej Zary dev_info(&udev->dev, "ignoring: probably an ADSL modem\n");
155cc28a20eSOndrej Zary return -ENODEV;
156cc28a20eSOndrej Zary }
157cc28a20eSOndrej Zary
158cc28a20eSOndrej Zary ret = usbnet_get_endpoints(dev, intf);
159cc28a20eSOndrej Zary if (ret)
160cc28a20eSOndrej Zary return ret;
161cc28a20eSOndrej Zary
162cc28a20eSOndrej Zary /*
163cc28a20eSOndrej Zary * this must not include ethernet header as the device can send partial
164441993daSOndrej Zary * packets with no header (and sometimes even empty URBs)
165cc28a20eSOndrej Zary */
166441993daSOndrej Zary dev->net->hard_header_len = 0;
167cc28a20eSOndrej Zary /* we can send at most 1514 bytes of data (+ 2-byte header) per URB */
168441993daSOndrej Zary dev->hard_mtu = CX82310_MTU + 2;
169cc28a20eSOndrej Zary /* we can receive URBs up to 4KB from the device */
170cc28a20eSOndrej Zary dev->rx_urb_size = 4096;
171cc28a20eSOndrej Zary
172cc28a20eSOndrej Zary dev->partial_data = (unsigned long) kmalloc(dev->hard_mtu, GFP_KERNEL);
173cc28a20eSOndrej Zary if (!dev->partial_data)
174cc28a20eSOndrej Zary return -ENOMEM;
175cc28a20eSOndrej Zary
176ca139d76SOndrej Zary priv = kzalloc(sizeof(*priv), GFP_KERNEL);
177ca139d76SOndrej Zary if (!priv) {
178ca139d76SOndrej Zary ret = -ENOMEM;
179ca139d76SOndrej Zary goto err_partial;
180ca139d76SOndrej Zary }
181ca139d76SOndrej Zary dev->driver_priv = priv;
182ca139d76SOndrej Zary INIT_WORK(&priv->reenable_work, cx82310_reenable_work);
183ca139d76SOndrej Zary priv->dev = dev;
184ca139d76SOndrej Zary
185f40bff42SOndrej Zary /* wait for firmware to become ready (indicated by the link being up) */
186f40bff42SOndrej Zary while (--timeout) {
187f40bff42SOndrej Zary ret = cx82310_cmd(dev, CMD_GET_LINK_STATUS, true, NULL, 0,
188f40bff42SOndrej Zary link, sizeof(link));
189f40bff42SOndrej Zary /* the command can time out during boot - it's not an error */
190f40bff42SOndrej Zary if (!ret && link[0] == 1 && link[2] == 1)
191f40bff42SOndrej Zary break;
192f40bff42SOndrej Zary msleep(500);
1938263d57eSWu Fengguang }
194f40bff42SOndrej Zary if (!timeout) {
19515f5e48fSOndrej Zary netdev_err(dev->net, "firmware not ready in time\n");
1961eca92eeSWenwen Wang ret = -ETIMEDOUT;
1971eca92eeSWenwen Wang goto err;
198f40bff42SOndrej Zary }
199f40bff42SOndrej Zary
200cc28a20eSOndrej Zary /* enable ethernet mode (?) */
201cfbaa8b3SZhang Changzhong ret = cx82310_enable_ethernet(dev);
202cfbaa8b3SZhang Changzhong if (ret)
203cc28a20eSOndrej Zary goto err;
204cc28a20eSOndrej Zary
205cc28a20eSOndrej Zary /* get the MAC address */
206*2674e7eaSJakub Kicinski ret = cx82310_cmd(dev, CMD_GET_MAC_ADDR, true, NULL, 0, addr, ETH_ALEN);
207cc28a20eSOndrej Zary if (ret) {
20815f5e48fSOndrej Zary netdev_err(dev->net, "unable to read MAC address: %d\n", ret);
209cc28a20eSOndrej Zary goto err;
210cc28a20eSOndrej Zary }
211*2674e7eaSJakub Kicinski eth_hw_addr_set(dev->net, addr);
212cc28a20eSOndrej Zary
213cc28a20eSOndrej Zary /* start (does not seem to have any effect?) */
214cc28a20eSOndrej Zary ret = cx82310_cmd(dev, CMD_START, false, NULL, 0, NULL, 0);
215cc28a20eSOndrej Zary if (ret)
216cc28a20eSOndrej Zary goto err;
217cc28a20eSOndrej Zary
218cc28a20eSOndrej Zary return 0;
219cc28a20eSOndrej Zary err:
220ca139d76SOndrej Zary kfree(dev->driver_priv);
221ca139d76SOndrej Zary err_partial:
222cc28a20eSOndrej Zary kfree((void *)dev->partial_data);
223cc28a20eSOndrej Zary return ret;
224cc28a20eSOndrej Zary }
225cc28a20eSOndrej Zary
cx82310_unbind(struct usbnet * dev,struct usb_interface * intf)226cc28a20eSOndrej Zary static void cx82310_unbind(struct usbnet *dev, struct usb_interface *intf)
227cc28a20eSOndrej Zary {
228ca139d76SOndrej Zary struct cx82310_priv *priv = dev->driver_priv;
229ca139d76SOndrej Zary
230cc28a20eSOndrej Zary kfree((void *)dev->partial_data);
231ca139d76SOndrej Zary cancel_work_sync(&priv->reenable_work);
232ca139d76SOndrej Zary kfree(dev->driver_priv);
233cc28a20eSOndrej Zary }
234cc28a20eSOndrej Zary
235cc28a20eSOndrej Zary /*
236cc28a20eSOndrej Zary * RX is NOT easy - we can receive multiple packets per skb, each having 2-byte
237cc28a20eSOndrej Zary * packet length at the beginning.
238cc28a20eSOndrej Zary * The last packet might be incomplete (when it crosses the 4KB URB size),
239cc28a20eSOndrej Zary * continuing in the next skb (without any headers).
240cc28a20eSOndrej Zary * If a packet has odd length, there is one extra byte at the end (before next
241cc28a20eSOndrej Zary * packet or at the end of the URB).
242cc28a20eSOndrej Zary */
cx82310_rx_fixup(struct usbnet * dev,struct sk_buff * skb)243cc28a20eSOndrej Zary static int cx82310_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
244cc28a20eSOndrej Zary {
245cc28a20eSOndrej Zary int len;
246cc28a20eSOndrej Zary struct sk_buff *skb2;
247ca139d76SOndrej Zary struct cx82310_priv *priv = dev->driver_priv;
248cc28a20eSOndrej Zary
249cc28a20eSOndrej Zary /*
250cc28a20eSOndrej Zary * If the last skb ended with an incomplete packet, this skb contains
251cc28a20eSOndrej Zary * end of that packet at the beginning.
252cc28a20eSOndrej Zary */
253cc28a20eSOndrej Zary if (dev->partial_rem) {
254cc28a20eSOndrej Zary len = dev->partial_len + dev->partial_rem;
255cc28a20eSOndrej Zary skb2 = alloc_skb(len, GFP_ATOMIC);
256cc28a20eSOndrej Zary if (!skb2)
257cc28a20eSOndrej Zary return 0;
258cc28a20eSOndrej Zary skb_put(skb2, len);
259cc28a20eSOndrej Zary memcpy(skb2->data, (void *)dev->partial_data,
260cc28a20eSOndrej Zary dev->partial_len);
261cc28a20eSOndrej Zary memcpy(skb2->data + dev->partial_len, skb->data,
262cc28a20eSOndrej Zary dev->partial_rem);
263cc28a20eSOndrej Zary usbnet_skb_return(dev, skb2);
264cc28a20eSOndrej Zary skb_pull(skb, (dev->partial_rem + 1) & ~1);
265cc28a20eSOndrej Zary dev->partial_rem = 0;
266cc28a20eSOndrej Zary if (skb->len < 2)
267cc28a20eSOndrej Zary return 1;
268cc28a20eSOndrej Zary }
269cc28a20eSOndrej Zary
270cc28a20eSOndrej Zary /* a skb can contain multiple packets */
271cc28a20eSOndrej Zary while (skb->len > 1) {
272cc28a20eSOndrej Zary /* first two bytes are packet length */
273cc28a20eSOndrej Zary len = skb->data[0] | (skb->data[1] << 8);
274cc28a20eSOndrej Zary skb_pull(skb, 2);
275cc28a20eSOndrej Zary
276cc28a20eSOndrej Zary /* if last packet in the skb, let usbnet to process it */
277cc28a20eSOndrej Zary if (len == skb->len || len + 1 == skb->len) {
278cc28a20eSOndrej Zary skb_trim(skb, len);
279cc28a20eSOndrej Zary break;
280cc28a20eSOndrej Zary }
281cc28a20eSOndrej Zary
282ca139d76SOndrej Zary if (len == 0xffff) {
283ca139d76SOndrej Zary netdev_info(dev->net, "router was rebooted, re-enabling ethernet mode");
284ca139d76SOndrej Zary schedule_work(&priv->reenable_work);
285ca139d76SOndrej Zary } else if (len > CX82310_MTU) {
28615f5e48fSOndrej Zary netdev_err(dev->net, "RX packet too long: %d B\n", len);
287cc28a20eSOndrej Zary return 0;
288cc28a20eSOndrej Zary }
289cc28a20eSOndrej Zary
290cc28a20eSOndrej Zary /* incomplete packet, save it for the next skb */
291cc28a20eSOndrej Zary if (len > skb->len) {
292cc28a20eSOndrej Zary dev->partial_len = skb->len;
293cc28a20eSOndrej Zary dev->partial_rem = len - skb->len;
294cc28a20eSOndrej Zary memcpy((void *)dev->partial_data, skb->data,
295cc28a20eSOndrej Zary dev->partial_len);
296cc28a20eSOndrej Zary skb_pull(skb, skb->len);
297cc28a20eSOndrej Zary break;
298cc28a20eSOndrej Zary }
299cc28a20eSOndrej Zary
300cc28a20eSOndrej Zary skb2 = alloc_skb(len, GFP_ATOMIC);
301cc28a20eSOndrej Zary if (!skb2)
302cc28a20eSOndrej Zary return 0;
303cc28a20eSOndrej Zary skb_put(skb2, len);
304cc28a20eSOndrej Zary memcpy(skb2->data, skb->data, len);
305cc28a20eSOndrej Zary /* process the packet */
306cc28a20eSOndrej Zary usbnet_skb_return(dev, skb2);
307cc28a20eSOndrej Zary
308cc28a20eSOndrej Zary skb_pull(skb, (len + 1) & ~1);
309cc28a20eSOndrej Zary }
310cc28a20eSOndrej Zary
311cc28a20eSOndrej Zary /* let usbnet process the last packet */
312cc28a20eSOndrej Zary return 1;
313cc28a20eSOndrej Zary }
314cc28a20eSOndrej Zary
315cc28a20eSOndrej Zary /* TX is easy, just add 2 bytes of length at the beginning */
cx82310_tx_fixup(struct usbnet * dev,struct sk_buff * skb,gfp_t flags)316cc28a20eSOndrej Zary static struct sk_buff *cx82310_tx_fixup(struct usbnet *dev, struct sk_buff *skb,
317cc28a20eSOndrej Zary gfp_t flags)
318cc28a20eSOndrej Zary {
319cc28a20eSOndrej Zary int len = skb->len;
320cc28a20eSOndrej Zary
321a9e840a2SEric Dumazet if (skb_cow_head(skb, 2)) {
322cc28a20eSOndrej Zary dev_kfree_skb_any(skb);
323cc28a20eSOndrej Zary return NULL;
324cc28a20eSOndrej Zary }
325cc28a20eSOndrej Zary skb_push(skb, 2);
326cc28a20eSOndrej Zary
327cc28a20eSOndrej Zary skb->data[0] = len;
328cc28a20eSOndrej Zary skb->data[1] = len >> 8;
329cc28a20eSOndrej Zary
330cc28a20eSOndrej Zary return skb;
331cc28a20eSOndrej Zary }
332cc28a20eSOndrej Zary
333cc28a20eSOndrej Zary
334cc28a20eSOndrej Zary static const struct driver_info cx82310_info = {
335cc28a20eSOndrej Zary .description = "Conexant CX82310 USB ethernet",
336cc28a20eSOndrej Zary .flags = FLAG_ETHER,
337cc28a20eSOndrej Zary .bind = cx82310_bind,
338cc28a20eSOndrej Zary .unbind = cx82310_unbind,
339cc28a20eSOndrej Zary .rx_fixup = cx82310_rx_fixup,
340cc28a20eSOndrej Zary .tx_fixup = cx82310_tx_fixup,
341cc28a20eSOndrej Zary };
342cc28a20eSOndrej Zary
3438d006e01SOndrej Zary #define USB_DEVICE_CLASS(vend, prod, cl, sc, pr) \
3448d006e01SOndrej Zary .match_flags = USB_DEVICE_ID_MATCH_DEVICE | \
3458d006e01SOndrej Zary USB_DEVICE_ID_MATCH_DEV_INFO, \
3468d006e01SOndrej Zary .idVendor = (vend), \
3478d006e01SOndrej Zary .idProduct = (prod), \
3488d006e01SOndrej Zary .bDeviceClass = (cl), \
3498d006e01SOndrej Zary .bDeviceSubClass = (sc), \
3508d006e01SOndrej Zary .bDeviceProtocol = (pr)
3518d006e01SOndrej Zary
352cc28a20eSOndrej Zary static const struct usb_device_id products[] = {
353cc28a20eSOndrej Zary {
3548d006e01SOndrej Zary USB_DEVICE_CLASS(0x0572, 0xcb01, 0xff, 0, 0),
355cc28a20eSOndrej Zary .driver_info = (unsigned long) &cx82310_info
356cc28a20eSOndrej Zary },
357cc28a20eSOndrej Zary { },
358cc28a20eSOndrej Zary };
359cc28a20eSOndrej Zary MODULE_DEVICE_TABLE(usb, products);
360cc28a20eSOndrej Zary
361cc28a20eSOndrej Zary static struct usb_driver cx82310_driver = {
362cc28a20eSOndrej Zary .name = "cx82310_eth",
363cc28a20eSOndrej Zary .id_table = products,
364cc28a20eSOndrej Zary .probe = usbnet_probe,
365cc28a20eSOndrej Zary .disconnect = usbnet_disconnect,
366cc28a20eSOndrej Zary .suspend = usbnet_suspend,
367cc28a20eSOndrej Zary .resume = usbnet_resume,
368e1f12eb6SSarah Sharp .disable_hub_initiated_lpm = 1,
369cc28a20eSOndrej Zary };
370cc28a20eSOndrej Zary
371d632eb1bSGreg Kroah-Hartman module_usb_driver(cx82310_driver);
372cc28a20eSOndrej Zary
373cc28a20eSOndrej Zary MODULE_AUTHOR("Ondrej Zary");
374cc28a20eSOndrej Zary MODULE_DESCRIPTION("Conexant CX82310-based ADSL router USB ethernet driver");
375cc28a20eSOndrej Zary MODULE_LICENSE("GPL");
376