Commit | Line | Data |
---|---|---|
1ccea77e | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2e55cc72 DB |
2 | /* |
3 | * ASIX AX8817X based USB 2.0 Ethernet Devices | |
933a27d3 | 4 | * Copyright (C) 2003-2006 David Hollis <dhollis@davehollis.com> |
2e55cc72 | 5 | * Copyright (C) 2005 Phil Chang <pchang23@sbcglobal.net> |
933a27d3 | 6 | * Copyright (C) 2006 James Painter <jamie.painter@iname.com> |
2e55cc72 | 7 | * Copyright (c) 2002-2003 TiVo Inc. |
2e55cc72 DB |
8 | */ |
9 | ||
607740bc CR |
10 | #include "asix.h" |
11 | ||
d1652b70 PS |
12 | #define AX_HOST_EN_RETRIES 30 |
13 | ||
920a9fa2 PS |
14 | int __must_check asix_read_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index, |
15 | u16 size, void *data, int in_pm) | |
2e55cc72 | 16 | { |
0bc69efb | 17 | int ret; |
d9fe64e5 RF |
18 | int (*fn)(struct usbnet *, u8, u8, u16, u16, void *, u16); |
19 | ||
20 | BUG_ON(!dev); | |
21 | ||
22 | if (!in_pm) | |
23 | fn = usbnet_read_cmd; | |
24 | else | |
25 | fn = usbnet_read_cmd_nopm; | |
26 | ||
27 | ret = fn(dev, cmd, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, | |
28 | value, index, data, size); | |
29 | ||
920a9fa2 PS |
30 | if (unlikely(ret < size)) { |
31 | ret = ret < 0 ? ret : -ENODATA; | |
32 | ||
d9fe64e5 RF |
33 | netdev_warn(dev->net, "Failed to read reg index 0x%04x: %d\n", |
34 | index, ret); | |
920a9fa2 | 35 | } |
0bc69efb | 36 | |
0bc69efb | 37 | return ret; |
2e55cc72 DB |
38 | } |
39 | ||
607740bc | 40 | int asix_write_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index, |
d9fe64e5 | 41 | u16 size, void *data, int in_pm) |
2e55cc72 | 42 | { |
d9fe64e5 RF |
43 | int ret; |
44 | int (*fn)(struct usbnet *, u8, u8, u16, u16, const void *, u16); | |
45 | ||
46 | BUG_ON(!dev); | |
47 | ||
48 | if (!in_pm) | |
49 | fn = usbnet_write_cmd; | |
50 | else | |
51 | fn = usbnet_write_cmd_nopm; | |
52 | ||
53 | ret = fn(dev, cmd, USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, | |
54 | value, index, data, size); | |
55 | ||
56 | if (unlikely(ret < 0)) | |
57 | netdev_warn(dev->net, "Failed to write reg index 0x%04x: %d\n", | |
58 | index, ret); | |
59 | ||
60 | return ret; | |
2e55cc72 DB |
61 | } |
62 | ||
607740bc CR |
63 | void asix_write_cmd_async(struct usbnet *dev, u8 cmd, u16 value, u16 index, |
64 | u16 size, void *data) | |
933a27d3 | 65 | { |
0bc69efb ML |
66 | usbnet_write_cmd_async(dev, cmd, |
67 | USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, | |
68 | value, index, data, size); | |
933a27d3 DH |
69 | } |
70 | ||
7e40e16e ZS |
71 | static int asix_set_sw_mii(struct usbnet *dev, int in_pm) |
72 | { | |
73 | int ret; | |
74 | ||
75 | ret = asix_write_cmd(dev, AX_CMD_SET_SW_MII, 0x0000, 0, 0, NULL, in_pm); | |
76 | ||
77 | if (ret < 0) | |
78 | netdev_err(dev->net, "Failed to enable software MII access\n"); | |
79 | return ret; | |
80 | } | |
81 | ||
82 | static int asix_set_hw_mii(struct usbnet *dev, int in_pm) | |
83 | { | |
84 | int ret; | |
85 | ||
86 | ret = asix_write_cmd(dev, AX_CMD_SET_HW_MII, 0x0000, 0, 0, NULL, in_pm); | |
87 | if (ret < 0) | |
88 | netdev_err(dev->net, "Failed to enable hardware MII access\n"); | |
89 | return ret; | |
90 | } | |
91 | ||
a786e319 PS |
92 | static int asix_check_host_enable(struct usbnet *dev, int in_pm) |
93 | { | |
94 | int i, ret; | |
95 | u8 smsr; | |
96 | ||
d1652b70 | 97 | for (i = 0; i < AX_HOST_EN_RETRIES; ++i) { |
a786e319 PS |
98 | ret = asix_set_sw_mii(dev, in_pm); |
99 | if (ret == -ENODEV || ret == -ETIMEDOUT) | |
100 | break; | |
101 | usleep_range(1000, 1100); | |
102 | ret = asix_read_cmd(dev, AX_CMD_STATMNGSTS_REG, | |
103 | 0, 0, 1, &smsr, in_pm); | |
104 | if (ret == -ENODEV) | |
105 | break; | |
920a9fa2 | 106 | else if (ret < 0) |
a786e319 PS |
107 | continue; |
108 | else if (smsr & AX_HOST_EN) | |
109 | break; | |
110 | } | |
111 | ||
d1652b70 | 112 | return i >= AX_HOST_EN_RETRIES ? -ETIMEDOUT : ret; |
a786e319 PS |
113 | } |
114 | ||
960eb4ee DJ |
115 | static void reset_asix_rx_fixup_info(struct asix_rx_fixup_info *rx) |
116 | { | |
117 | /* Reset the variables that have a lifetime outside of | |
118 | * asix_rx_fixup_internal() so that future processing starts from a | |
119 | * known set of initial conditions. | |
120 | */ | |
121 | ||
122 | if (rx->ax_skb) { | |
123 | /* Discard any incomplete Ethernet frame in the netdev buffer */ | |
124 | kfree_skb(rx->ax_skb); | |
125 | rx->ax_skb = NULL; | |
126 | } | |
127 | ||
128 | /* Assume the Data header 32-bit word is at the start of the current | |
129 | * or next URB socket buffer so reset all the state variables. | |
130 | */ | |
131 | rx->remaining = 0; | |
132 | rx->split_head = false; | |
133 | rx->header = 0; | |
134 | } | |
135 | ||
8b5b6f54 LS |
136 | int asix_rx_fixup_internal(struct usbnet *dev, struct sk_buff *skb, |
137 | struct asix_rx_fixup_info *rx) | |
933a27d3 | 138 | { |
a9e0aca4 | 139 | int offset = 0; |
7b0378f5 | 140 | u16 size; |
933a27d3 | 141 | |
3f30b158 DJ |
142 | /* When an Ethernet frame spans multiple URB socket buffers, |
143 | * do a sanity test for the Data header synchronisation. | |
144 | * Attempt to detect the situation of the previous socket buffer having | |
145 | * been truncated or a socket buffer was missing. These situations | |
146 | * cause a discontinuity in the data stream and therefore need to avoid | |
147 | * appending bad data to the end of the current netdev socket buffer. | |
148 | * Also avoid unnecessarily discarding a good current netdev socket | |
149 | * buffer. | |
150 | */ | |
151 | if (rx->remaining && (rx->remaining + sizeof(u32) <= skb->len)) { | |
cd9e2e5d | 152 | offset = ((rx->remaining + 1) & 0xfffe); |
3f30b158 DJ |
153 | rx->header = get_unaligned_le32(skb->data + offset); |
154 | offset = 0; | |
155 | ||
156 | size = (u16)(rx->header & 0x7ff); | |
157 | if (size != ((~rx->header >> 16) & 0x7ff)) { | |
158 | netdev_err(dev->net, "asix_rx_fixup() Data Header synchronisation was lost, remaining %d\n", | |
159 | rx->remaining); | |
960eb4ee | 160 | reset_asix_rx_fixup_info(rx); |
3f30b158 DJ |
161 | } |
162 | } | |
163 | ||
8b5b6f54 | 164 | while (offset + sizeof(u16) <= skb->len) { |
7b0378f5 | 165 | u16 copy_length; |
933a27d3 | 166 | |
7b0378f5 | 167 | if (!rx->remaining) { |
3bfc69ab DJ |
168 | if (skb->len - offset == sizeof(u16)) { |
169 | rx->header = get_unaligned_le16( | |
170 | skb->data + offset); | |
171 | rx->split_head = true; | |
172 | offset += sizeof(u16); | |
173 | break; | |
174 | } | |
175 | ||
176 | if (rx->split_head == true) { | |
177 | rx->header |= (get_unaligned_le16( | |
178 | skb->data + offset) << 16); | |
179 | rx->split_head = false; | |
180 | offset += sizeof(u16); | |
8b5b6f54 LS |
181 | } else { |
182 | rx->header = get_unaligned_le32(skb->data + | |
183 | offset); | |
184 | offset += sizeof(u32); | |
185 | } | |
bc466e67 | 186 | |
7b0378f5 DJ |
187 | /* take frame length from Data header 32-bit word */ |
188 | size = (u16)(rx->header & 0x7ff); | |
189 | if (size != ((~rx->header >> 16) & 0x7ff)) { | |
8b5b6f54 LS |
190 | netdev_err(dev->net, "asix_rx_fixup() Bad Header Length 0x%x, offset %d\n", |
191 | rx->header, offset); | |
960eb4ee | 192 | reset_asix_rx_fixup_info(rx); |
8b5b6f54 LS |
193 | return 0; |
194 | } | |
9a5ccd8e | 195 | if (size > dev->net->mtu + ETH_HLEN + VLAN_HLEN) { |
b70183db | 196 | netdev_dbg(dev->net, "asix_rx_fixup() Bad RX Length %d\n", |
9a5ccd8e | 197 | size); |
960eb4ee | 198 | reset_asix_rx_fixup_info(rx); |
9a5ccd8e DJ |
199 | return 0; |
200 | } | |
201 | ||
6a570814 DJ |
202 | /* Sometimes may fail to get a netdev socket buffer but |
203 | * continue to process the URB socket buffer so that | |
204 | * synchronisation of the Ethernet frame Data header | |
205 | * word is maintained. | |
206 | */ | |
7b0378f5 | 207 | rx->ax_skb = netdev_alloc_skb_ip_align(dev->net, size); |
3f78d1f2 | 208 | |
9a5ccd8e | 209 | rx->remaining = size; |
933a27d3 | 210 | } |
933a27d3 | 211 | |
7b0378f5 DJ |
212 | if (rx->remaining > skb->len - offset) { |
213 | copy_length = skb->len - offset; | |
214 | rx->remaining -= copy_length; | |
215 | } else { | |
216 | copy_length = rx->remaining; | |
217 | rx->remaining = 0; | |
8b5b6f54 | 218 | } |
933a27d3 | 219 | |
6a570814 | 220 | if (rx->ax_skb) { |
b952f4df | 221 | skb_put_data(rx->ax_skb, skb->data + offset, |
222 | copy_length); | |
22889dbb | 223 | if (!rx->remaining) { |
6a570814 | 224 | usbnet_skb_return(dev, rx->ax_skb); |
22889dbb DJ |
225 | rx->ax_skb = NULL; |
226 | } | |
6a570814 | 227 | } |
8b5b6f54 | 228 | |
7b0378f5 | 229 | offset += (copy_length + 1) & 0xfffe; |
933a27d3 DH |
230 | } |
231 | ||
a9e0aca4 | 232 | if (skb->len != offset) { |
8b5b6f54 LS |
233 | netdev_err(dev->net, "asix_rx_fixup() Bad SKB Length %d, %d\n", |
234 | skb->len, offset); | |
960eb4ee | 235 | reset_asix_rx_fixup_info(rx); |
933a27d3 DH |
236 | return 0; |
237 | } | |
8b5b6f54 | 238 | |
933a27d3 DH |
239 | return 1; |
240 | } | |
241 | ||
8b5b6f54 LS |
242 | int asix_rx_fixup_common(struct usbnet *dev, struct sk_buff *skb) |
243 | { | |
244 | struct asix_common_private *dp = dev->driver_priv; | |
245 | struct asix_rx_fixup_info *rx = &dp->rx_fixup_info; | |
246 | ||
247 | return asix_rx_fixup_internal(dev, skb, rx); | |
248 | } | |
249 | ||
d0c8f338 DJ |
250 | void asix_rx_fixup_common_free(struct asix_common_private *dp) |
251 | { | |
252 | struct asix_rx_fixup_info *rx; | |
253 | ||
254 | if (!dp) | |
255 | return; | |
256 | ||
257 | rx = &dp->rx_fixup_info; | |
258 | ||
259 | if (rx->ax_skb) { | |
260 | kfree_skb(rx->ax_skb); | |
261 | rx->ax_skb = NULL; | |
262 | } | |
263 | } | |
264 | ||
607740bc CR |
265 | struct sk_buff *asix_tx_fixup(struct usbnet *dev, struct sk_buff *skb, |
266 | gfp_t flags) | |
933a27d3 DH |
267 | { |
268 | int padlen; | |
269 | int headroom = skb_headroom(skb); | |
270 | int tailroom = skb_tailroom(skb); | |
271 | u32 packet_len; | |
272 | u32 padbytes = 0xffff0000; | |
7e24b4ed | 273 | void *ptr; |
933a27d3 | 274 | |
2a580949 | 275 | padlen = ((skb->len + 4) & (dev->maxpacket - 1)) ? 0 : 4; |
933a27d3 | 276 | |
95162d65 ED |
277 | /* We need to push 4 bytes in front of frame (packet_len) |
278 | * and maybe add 4 bytes after the end (if padlen is 4) | |
279 | * | |
280 | * Avoid skb_copy_expand() expensive call, using following rules : | |
281 | * - We are allowed to push 4 bytes in headroom if skb_header_cloned() | |
282 | * is false (and if we have 4 bytes of headroom) | |
283 | * - We are allowed to put 4 bytes at tail if skb_cloned() | |
284 | * is false (and if we have 4 bytes of tailroom) | |
285 | * | |
242c1a28 | 286 | * TCP packets for example are cloned, but __skb_header_release() |
95162d65 ED |
287 | * was called in tcp stack, allowing us to use headroom for our needs. |
288 | */ | |
289 | if (!skb_header_cloned(skb) && | |
290 | !(padlen && skb_cloned(skb)) && | |
291 | headroom + tailroom >= 4 + padlen) { | |
292 | /* following should not happen, but better be safe */ | |
293 | if (headroom < 4 || | |
294 | tailroom < padlen) { | |
933a27d3 | 295 | skb->data = memmove(skb->head + 4, skb->data, skb->len); |
27a884dc | 296 | skb_set_tail_pointer(skb, skb->len); |
933a27d3 DH |
297 | } |
298 | } else { | |
299 | struct sk_buff *skb2; | |
95162d65 | 300 | |
933a27d3 DH |
301 | skb2 = skb_copy_expand(skb, 4, padlen, flags); |
302 | dev_kfree_skb_any(skb); | |
303 | skb = skb2; | |
304 | if (!skb) | |
305 | return NULL; | |
306 | } | |
307 | ||
95162d65 | 308 | packet_len = ((skb->len ^ 0x0000ffff) << 16) + skb->len; |
7e24b4ed CY |
309 | ptr = skb_push(skb, 4); |
310 | put_unaligned_le32(packet_len, ptr); | |
933a27d3 | 311 | |
2a580949 | 312 | if (padlen) { |
7e24b4ed | 313 | put_unaligned_le32(padbytes, skb_tail_pointer(skb)); |
933a27d3 DH |
314 | skb_put(skb, sizeof(padbytes)); |
315 | } | |
1e9e39f4 | 316 | |
7a1e890e | 317 | usbnet_set_skb_tx_stats(skb, 1, 0); |
933a27d3 DH |
318 | return skb; |
319 | } | |
320 | ||
7e88b11a | 321 | int asix_read_phy_addr(struct usbnet *dev, bool internal) |
48b1be6a | 322 | { |
7e88b11a | 323 | int ret, offset; |
51bf2976 | 324 | u8 buf[2]; |
48b1be6a | 325 | |
7e88b11a OR |
326 | ret = asix_read_cmd(dev, AX_CMD_READ_PHY_ID, 0, 0, 2, buf, 0); |
327 | if (ret < 0) | |
328 | goto error; | |
933a27d3 | 329 | |
a092b723 | 330 | if (ret < 2) { |
7e88b11a OR |
331 | ret = -EIO; |
332 | goto error; | |
48b1be6a | 333 | } |
7e88b11a OR |
334 | |
335 | offset = (internal ? 1 : 0); | |
16626b0c | 336 | ret = buf[offset]; |
51bf2976 | 337 | |
7e88b11a OR |
338 | netdev_dbg(dev->net, "%s PHY address 0x%x\n", |
339 | internal ? "internal" : "external", ret); | |
340 | ||
48b1be6a | 341 | return ret; |
48b1be6a | 342 | |
7e88b11a OR |
343 | error: |
344 | netdev_err(dev->net, "Error reading PHY_ID register: %02x\n", ret); | |
16626b0c | 345 | |
7e88b11a OR |
346 | return ret; |
347 | } | |
16626b0c | 348 | |
d9fe64e5 | 349 | int asix_sw_reset(struct usbnet *dev, u8 flags, int in_pm) |
48b1be6a DH |
350 | { |
351 | int ret; | |
352 | ||
d9fe64e5 | 353 | ret = asix_write_cmd(dev, AX_CMD_SW_RESET, flags, 0, 0, NULL, in_pm); |
48b1be6a | 354 | if (ret < 0) |
60b86755 | 355 | netdev_err(dev->net, "Failed to send software reset: %02x\n", ret); |
933a27d3 DH |
356 | |
357 | return ret; | |
358 | } | |
48b1be6a | 359 | |
d9fe64e5 | 360 | u16 asix_read_rx_ctl(struct usbnet *dev, int in_pm) |
933a27d3 | 361 | { |
51bf2976 | 362 | __le16 v; |
d9fe64e5 | 363 | int ret = asix_read_cmd(dev, AX_CMD_READ_RX_CTL, 0, 0, 2, &v, in_pm); |
933a27d3 | 364 | |
51bf2976 | 365 | if (ret < 0) { |
60b86755 | 366 | netdev_err(dev->net, "Error reading RX_CTL register: %02x\n", ret); |
51bf2976 | 367 | goto out; |
933a27d3 | 368 | } |
51bf2976 AV |
369 | ret = le16_to_cpu(v); |
370 | out: | |
48b1be6a DH |
371 | return ret; |
372 | } | |
373 | ||
d9fe64e5 | 374 | int asix_write_rx_ctl(struct usbnet *dev, u16 mode, int in_pm) |
48b1be6a DH |
375 | { |
376 | int ret; | |
377 | ||
60b86755 | 378 | netdev_dbg(dev->net, "asix_write_rx_ctl() - mode = 0x%04x\n", mode); |
d9fe64e5 | 379 | ret = asix_write_cmd(dev, AX_CMD_WRITE_RX_CTL, mode, 0, 0, NULL, in_pm); |
48b1be6a | 380 | if (ret < 0) |
60b86755 JP |
381 | netdev_err(dev->net, "Failed to write RX_CTL mode to 0x%04x: %02x\n", |
382 | mode, ret); | |
48b1be6a DH |
383 | |
384 | return ret; | |
385 | } | |
386 | ||
d9fe64e5 | 387 | u16 asix_read_medium_status(struct usbnet *dev, int in_pm) |
2e55cc72 | 388 | { |
51bf2976 | 389 | __le16 v; |
d9fe64e5 RF |
390 | int ret = asix_read_cmd(dev, AX_CMD_READ_MEDIUM_STATUS, |
391 | 0, 0, 2, &v, in_pm); | |
2e55cc72 | 392 | |
51bf2976 | 393 | if (ret < 0) { |
60b86755 JP |
394 | netdev_err(dev->net, "Error reading Medium Status register: %02x\n", |
395 | ret); | |
83e1b918 | 396 | return ret; /* TODO: callers not checking for error ret */ |
2e55cc72 | 397 | } |
83e1b918 GG |
398 | |
399 | return le16_to_cpu(v); | |
400 | ||
2e55cc72 DB |
401 | } |
402 | ||
d9fe64e5 | 403 | int asix_write_medium_mode(struct usbnet *dev, u16 mode, int in_pm) |
2e55cc72 | 404 | { |
933a27d3 | 405 | int ret; |
2e55cc72 | 406 | |
60b86755 | 407 | netdev_dbg(dev->net, "asix_write_medium_mode() - mode = 0x%04x\n", mode); |
d9fe64e5 RF |
408 | ret = asix_write_cmd(dev, AX_CMD_WRITE_MEDIUM_MODE, |
409 | mode, 0, 0, NULL, in_pm); | |
933a27d3 | 410 | if (ret < 0) |
60b86755 JP |
411 | netdev_err(dev->net, "Failed to write Medium Mode mode to 0x%04x: %02x\n", |
412 | mode, ret); | |
2e55cc72 | 413 | |
933a27d3 DH |
414 | return ret; |
415 | } | |
2e55cc72 | 416 | |
e532a096 OR |
417 | /* set MAC link settings according to information from phylib */ |
418 | void asix_adjust_link(struct net_device *netdev) | |
419 | { | |
420 | struct phy_device *phydev = netdev->phydev; | |
421 | struct usbnet *dev = netdev_priv(netdev); | |
422 | u16 mode = 0; | |
423 | ||
424 | if (phydev->link) { | |
425 | mode = AX88772_MEDIUM_DEFAULT; | |
426 | ||
427 | if (phydev->duplex == DUPLEX_HALF) | |
428 | mode &= ~AX_MEDIUM_FD; | |
429 | ||
430 | if (phydev->speed != SPEED_100) | |
431 | mode &= ~AX_MEDIUM_PS; | |
432 | } | |
433 | ||
434 | asix_write_medium_mode(dev, mode, 0); | |
435 | phy_print_status(phydev); | |
805206e6 | 436 | usbnet_link_change(dev, phydev->link, 0); |
e532a096 OR |
437 | } |
438 | ||
d9fe64e5 | 439 | int asix_write_gpio(struct usbnet *dev, u16 value, int sleep, int in_pm) |
933a27d3 DH |
440 | { |
441 | int ret; | |
2e55cc72 | 442 | |
60b86755 | 443 | netdev_dbg(dev->net, "asix_write_gpio() - value = 0x%04x\n", value); |
d9fe64e5 | 444 | ret = asix_write_cmd(dev, AX_CMD_WRITE_GPIOS, value, 0, 0, NULL, in_pm); |
933a27d3 | 445 | if (ret < 0) |
60b86755 JP |
446 | netdev_err(dev->net, "Failed to write GPIO value 0x%04x: %02x\n", |
447 | value, ret); | |
2e55cc72 | 448 | |
933a27d3 DH |
449 | if (sleep) |
450 | msleep(sleep); | |
451 | ||
452 | return ret; | |
2e55cc72 DB |
453 | } |
454 | ||
933a27d3 DH |
455 | /* |
456 | * AX88772 & AX88178 have a 16-bit RX_CTL value | |
457 | */ | |
607740bc | 458 | void asix_set_multicast(struct net_device *net) |
2e55cc72 DB |
459 | { |
460 | struct usbnet *dev = netdev_priv(net); | |
48b1be6a | 461 | struct asix_data *data = (struct asix_data *)&dev->data; |
933a27d3 | 462 | u16 rx_ctl = AX_DEFAULT_RX_CTL; |
2e55cc72 DB |
463 | |
464 | if (net->flags & IFF_PROMISC) { | |
933a27d3 | 465 | rx_ctl |= AX_RX_CTL_PRO; |
8e95a202 | 466 | } else if (net->flags & IFF_ALLMULTI || |
4cd24eaf | 467 | netdev_mc_count(net) > AX_MAX_MCAST) { |
933a27d3 | 468 | rx_ctl |= AX_RX_CTL_AMALL; |
4cd24eaf | 469 | } else if (netdev_mc_empty(net)) { |
2e55cc72 DB |
470 | /* just broadcast and directed */ |
471 | } else { | |
472 | /* We use the 20 byte dev->data | |
473 | * for our 8 byte filter buffer | |
474 | * to avoid allocating memory that | |
475 | * is tricky to free later */ | |
22bedad3 | 476 | struct netdev_hw_addr *ha; |
2e55cc72 | 477 | u32 crc_bits; |
2e55cc72 DB |
478 | |
479 | memset(data->multi_filter, 0, AX_MCAST_FILTER_SIZE); | |
480 | ||
481 | /* Build the multicast hash filter. */ | |
22bedad3 JP |
482 | netdev_for_each_mc_addr(ha, net) { |
483 | crc_bits = ether_crc(ETH_ALEN, ha->addr) >> 26; | |
2e55cc72 DB |
484 | data->multi_filter[crc_bits >> 3] |= |
485 | 1 << (crc_bits & 7); | |
2e55cc72 DB |
486 | } |
487 | ||
48b1be6a | 488 | asix_write_cmd_async(dev, AX_CMD_WRITE_MULTI_FILTER, 0, 0, |
2e55cc72 DB |
489 | AX_MCAST_FILTER_SIZE, data->multi_filter); |
490 | ||
933a27d3 | 491 | rx_ctl |= AX_RX_CTL_AM; |
2e55cc72 DB |
492 | } |
493 | ||
48b1be6a | 494 | asix_write_cmd_async(dev, AX_CMD_WRITE_RX_CTL, rx_ctl, 0, 0, NULL); |
2e55cc72 DB |
495 | } |
496 | ||
89183b6e OR |
497 | static int __asix_mdio_read(struct net_device *netdev, int phy_id, int loc, |
498 | bool in_pm) | |
2e55cc72 DB |
499 | { |
500 | struct usbnet *dev = netdev_priv(netdev); | |
51bf2976 | 501 | __le16 res; |
8a46f665 | 502 | int ret; |
2e55cc72 | 503 | |
a9fc6338 | 504 | mutex_lock(&dev->phy_mutex); |
a786e319 | 505 | |
89183b6e | 506 | ret = asix_check_host_enable(dev, in_pm); |
610df1d2 | 507 | if (ret == -ENODEV || ret == -ETIMEDOUT) { |
8a46f665 RF |
508 | mutex_unlock(&dev->phy_mutex); |
509 | return ret; | |
510 | } | |
d9fe64e5 | 511 | |
d275afb6 | 512 | ret = asix_read_cmd(dev, AX_CMD_READ_MII_REG, phy_id, (__u16)loc, 2, |
89183b6e | 513 | &res, in_pm); |
d275afb6 OR |
514 | if (ret < 0) |
515 | goto out; | |
516 | ||
89183b6e | 517 | ret = asix_set_hw_mii(dev, in_pm); |
d275afb6 | 518 | out: |
a9fc6338 | 519 | mutex_unlock(&dev->phy_mutex); |
2e55cc72 | 520 | |
60b86755 | 521 | netdev_dbg(dev->net, "asix_mdio_read() phy_id=0x%02x, loc=0x%02x, returns=0x%04x\n", |
d9fe64e5 | 522 | phy_id, loc, le16_to_cpu(res)); |
2e55cc72 | 523 | |
d275afb6 | 524 | return ret < 0 ? ret : le16_to_cpu(res); |
2e55cc72 DB |
525 | } |
526 | ||
89183b6e OR |
527 | int asix_mdio_read(struct net_device *netdev, int phy_id, int loc) |
528 | { | |
529 | return __asix_mdio_read(netdev, phy_id, loc, false); | |
530 | } | |
531 | ||
d275afb6 | 532 | static int __asix_mdio_write(struct net_device *netdev, int phy_id, int loc, |
89183b6e | 533 | int val, bool in_pm) |
2e55cc72 DB |
534 | { |
535 | struct usbnet *dev = netdev_priv(netdev); | |
51bf2976 | 536 | __le16 res = cpu_to_le16(val); |
8a46f665 | 537 | int ret; |
2e55cc72 | 538 | |
60b86755 | 539 | netdev_dbg(dev->net, "asix_mdio_write() phy_id=0x%02x, loc=0x%02x, val=0x%04x\n", |
d9fe64e5 RF |
540 | phy_id, loc, val); |
541 | ||
542 | mutex_lock(&dev->phy_mutex); | |
d9fe64e5 | 543 | |
89183b6e | 544 | ret = asix_check_host_enable(dev, in_pm); |
d275afb6 OR |
545 | if (ret == -ENODEV) |
546 | goto out; | |
547 | ||
548 | ret = asix_write_cmd(dev, AX_CMD_WRITE_MII_REG, phy_id, (__u16)loc, 2, | |
89183b6e | 549 | &res, in_pm); |
d275afb6 OR |
550 | if (ret < 0) |
551 | goto out; | |
552 | ||
89183b6e | 553 | ret = asix_set_hw_mii(dev, in_pm); |
d275afb6 | 554 | out: |
d9fe64e5 | 555 | mutex_unlock(&dev->phy_mutex); |
d275afb6 OR |
556 | |
557 | return ret < 0 ? ret : 0; | |
558 | } | |
559 | ||
560 | void asix_mdio_write(struct net_device *netdev, int phy_id, int loc, int val) | |
561 | { | |
89183b6e | 562 | __asix_mdio_write(netdev, phy_id, loc, val, false); |
d9fe64e5 RF |
563 | } |
564 | ||
e532a096 OR |
565 | /* MDIO read and write wrappers for phylib */ |
566 | int asix_mdio_bus_read(struct mii_bus *bus, int phy_id, int regnum) | |
567 | { | |
568 | struct usbnet *priv = bus->priv; | |
569 | ||
89183b6e | 570 | return __asix_mdio_read(priv->net, phy_id, regnum, false); |
e532a096 OR |
571 | } |
572 | ||
573 | int asix_mdio_bus_write(struct mii_bus *bus, int phy_id, int regnum, u16 val) | |
574 | { | |
575 | struct usbnet *priv = bus->priv; | |
576 | ||
89183b6e | 577 | return __asix_mdio_write(priv->net, phy_id, regnum, val, false); |
e532a096 OR |
578 | } |
579 | ||
d9fe64e5 RF |
580 | int asix_mdio_read_nopm(struct net_device *netdev, int phy_id, int loc) |
581 | { | |
89183b6e | 582 | return __asix_mdio_read(netdev, phy_id, loc, true); |
d9fe64e5 RF |
583 | } |
584 | ||
585 | void | |
586 | asix_mdio_write_nopm(struct net_device *netdev, int phy_id, int loc, int val) | |
587 | { | |
89183b6e | 588 | __asix_mdio_write(netdev, phy_id, loc, val, true); |
2e55cc72 DB |
589 | } |
590 | ||
607740bc | 591 | void asix_get_wol(struct net_device *net, struct ethtool_wolinfo *wolinfo) |
2e55cc72 DB |
592 | { |
593 | struct usbnet *dev = netdev_priv(net); | |
594 | u8 opt; | |
595 | ||
d9fe64e5 RF |
596 | if (asix_read_cmd(dev, AX_CMD_READ_MONITOR_MODE, |
597 | 0, 0, 1, &opt, 0) < 0) { | |
2e55cc72 DB |
598 | wolinfo->supported = 0; |
599 | wolinfo->wolopts = 0; | |
600 | return; | |
601 | } | |
602 | wolinfo->supported = WAKE_PHY | WAKE_MAGIC; | |
603 | wolinfo->wolopts = 0; | |
f87ce5b2 | 604 | if (opt & AX_MONITOR_LINK) |
605 | wolinfo->wolopts |= WAKE_PHY; | |
606 | if (opt & AX_MONITOR_MAGIC) | |
607 | wolinfo->wolopts |= WAKE_MAGIC; | |
2e55cc72 DB |
608 | } |
609 | ||
607740bc | 610 | int asix_set_wol(struct net_device *net, struct ethtool_wolinfo *wolinfo) |
2e55cc72 DB |
611 | { |
612 | struct usbnet *dev = netdev_priv(net); | |
613 | u8 opt = 0; | |
2e55cc72 | 614 | |
c4ce446e FF |
615 | if (wolinfo->wolopts & ~(WAKE_PHY | WAKE_MAGIC)) |
616 | return -EINVAL; | |
617 | ||
2e55cc72 DB |
618 | if (wolinfo->wolopts & WAKE_PHY) |
619 | opt |= AX_MONITOR_LINK; | |
620 | if (wolinfo->wolopts & WAKE_MAGIC) | |
621 | opt |= AX_MONITOR_MAGIC; | |
2e55cc72 | 622 | |
48b1be6a | 623 | if (asix_write_cmd(dev, AX_CMD_WRITE_MONITOR_MODE, |
d9fe64e5 | 624 | opt, 0, 0, NULL, 0) < 0) |
2e55cc72 DB |
625 | return -EINVAL; |
626 | ||
627 | return 0; | |
628 | } | |
629 | ||
607740bc | 630 | int asix_get_eeprom_len(struct net_device *net) |
2e55cc72 | 631 | { |
ceb02c91 | 632 | return AX_EEPROM_LEN; |
2e55cc72 DB |
633 | } |
634 | ||
607740bc CR |
635 | int asix_get_eeprom(struct net_device *net, struct ethtool_eeprom *eeprom, |
636 | u8 *data) | |
2e55cc72 DB |
637 | { |
638 | struct usbnet *dev = netdev_priv(net); | |
ceb02c91 CR |
639 | u16 *eeprom_buff; |
640 | int first_word, last_word; | |
2e55cc72 DB |
641 | int i; |
642 | ||
ceb02c91 | 643 | if (eeprom->len == 0) |
2e55cc72 DB |
644 | return -EINVAL; |
645 | ||
646 | eeprom->magic = AX_EEPROM_MAGIC; | |
647 | ||
ceb02c91 CR |
648 | first_word = eeprom->offset >> 1; |
649 | last_word = (eeprom->offset + eeprom->len - 1) >> 1; | |
650 | ||
6da2ec56 KC |
651 | eeprom_buff = kmalloc_array(last_word - first_word + 1, sizeof(u16), |
652 | GFP_KERNEL); | |
ceb02c91 CR |
653 | if (!eeprom_buff) |
654 | return -ENOMEM; | |
655 | ||
2e55cc72 | 656 | /* ax8817x returns 2 bytes from eeprom on read */ |
ceb02c91 CR |
657 | for (i = first_word; i <= last_word; i++) { |
658 | if (asix_read_cmd(dev, AX_CMD_READ_EEPROM, i, 0, 2, | |
d9fe64e5 | 659 | &eeprom_buff[i - first_word], 0) < 0) { |
ceb02c91 CR |
660 | kfree(eeprom_buff); |
661 | return -EIO; | |
662 | } | |
2e55cc72 | 663 | } |
ceb02c91 CR |
664 | |
665 | memcpy(data, (u8 *)eeprom_buff + (eeprom->offset & 1), eeprom->len); | |
666 | kfree(eeprom_buff); | |
2e55cc72 DB |
667 | return 0; |
668 | } | |
669 | ||
cb7b24cd CR |
670 | int asix_set_eeprom(struct net_device *net, struct ethtool_eeprom *eeprom, |
671 | u8 *data) | |
672 | { | |
673 | struct usbnet *dev = netdev_priv(net); | |
674 | u16 *eeprom_buff; | |
675 | int first_word, last_word; | |
676 | int i; | |
677 | int ret; | |
678 | ||
679 | netdev_dbg(net, "write EEPROM len %d, offset %d, magic 0x%x\n", | |
680 | eeprom->len, eeprom->offset, eeprom->magic); | |
681 | ||
682 | if (eeprom->len == 0) | |
683 | return -EINVAL; | |
684 | ||
685 | if (eeprom->magic != AX_EEPROM_MAGIC) | |
686 | return -EINVAL; | |
687 | ||
688 | first_word = eeprom->offset >> 1; | |
689 | last_word = (eeprom->offset + eeprom->len - 1) >> 1; | |
690 | ||
6da2ec56 KC |
691 | eeprom_buff = kmalloc_array(last_word - first_word + 1, sizeof(u16), |
692 | GFP_KERNEL); | |
cb7b24cd CR |
693 | if (!eeprom_buff) |
694 | return -ENOMEM; | |
695 | ||
696 | /* align data to 16 bit boundaries, read the missing data from | |
697 | the EEPROM */ | |
698 | if (eeprom->offset & 1) { | |
699 | ret = asix_read_cmd(dev, AX_CMD_READ_EEPROM, first_word, 0, 2, | |
d9fe64e5 | 700 | &eeprom_buff[0], 0); |
cb7b24cd CR |
701 | if (ret < 0) { |
702 | netdev_err(net, "Failed to read EEPROM at offset 0x%02x.\n", first_word); | |
703 | goto free; | |
704 | } | |
705 | } | |
706 | ||
707 | if ((eeprom->offset + eeprom->len) & 1) { | |
708 | ret = asix_read_cmd(dev, AX_CMD_READ_EEPROM, last_word, 0, 2, | |
d9fe64e5 | 709 | &eeprom_buff[last_word - first_word], 0); |
cb7b24cd CR |
710 | if (ret < 0) { |
711 | netdev_err(net, "Failed to read EEPROM at offset 0x%02x.\n", last_word); | |
712 | goto free; | |
713 | } | |
714 | } | |
715 | ||
716 | memcpy((u8 *)eeprom_buff + (eeprom->offset & 1), data, eeprom->len); | |
717 | ||
718 | /* write data to EEPROM */ | |
d9fe64e5 | 719 | ret = asix_write_cmd(dev, AX_CMD_WRITE_ENABLE, 0x0000, 0, 0, NULL, 0); |
cb7b24cd CR |
720 | if (ret < 0) { |
721 | netdev_err(net, "Failed to enable EEPROM write\n"); | |
722 | goto free; | |
723 | } | |
724 | msleep(20); | |
725 | ||
726 | for (i = first_word; i <= last_word; i++) { | |
727 | netdev_dbg(net, "write to EEPROM at offset 0x%02x, data 0x%04x\n", | |
728 | i, eeprom_buff[i - first_word]); | |
729 | ret = asix_write_cmd(dev, AX_CMD_WRITE_EEPROM, i, | |
d9fe64e5 | 730 | eeprom_buff[i - first_word], 0, NULL, 0); |
cb7b24cd CR |
731 | if (ret < 0) { |
732 | netdev_err(net, "Failed to write EEPROM at offset 0x%02x.\n", | |
733 | i); | |
734 | goto free; | |
735 | } | |
736 | msleep(20); | |
737 | } | |
738 | ||
d9fe64e5 | 739 | ret = asix_write_cmd(dev, AX_CMD_WRITE_DISABLE, 0x0000, 0, 0, NULL, 0); |
cb7b24cd CR |
740 | if (ret < 0) { |
741 | netdev_err(net, "Failed to disable EEPROM write\n"); | |
742 | goto free; | |
743 | } | |
744 | ||
745 | ret = 0; | |
746 | free: | |
747 | kfree(eeprom_buff); | |
748 | return ret; | |
749 | } | |
750 | ||
607740bc | 751 | void asix_get_drvinfo(struct net_device *net, struct ethtool_drvinfo *info) |
2e55cc72 DB |
752 | { |
753 | /* Inherit standard device info */ | |
754 | usbnet_get_drvinfo(net, info); | |
fb3ceec1 WS |
755 | strscpy(info->driver, DRIVER_NAME, sizeof(info->driver)); |
756 | strscpy(info->version, DRIVER_VERSION, sizeof(info->version)); | |
2e55cc72 DB |
757 | } |
758 | ||
607740bc | 759 | int asix_set_mac_address(struct net_device *net, void *p) |
7f29a3ba JK |
760 | { |
761 | struct usbnet *dev = netdev_priv(net); | |
762 | struct asix_data *data = (struct asix_data *)&dev->data; | |
763 | struct sockaddr *addr = p; | |
764 | ||
765 | if (netif_running(net)) | |
766 | return -EBUSY; | |
767 | if (!is_valid_ether_addr(addr->sa_data)) | |
768 | return -EADDRNOTAVAIL; | |
769 | ||
16813717 | 770 | eth_hw_addr_set(net, addr->sa_data); |
7f29a3ba JK |
771 | |
772 | /* We use the 20 byte dev->data | |
773 | * for our 6 byte mac buffer | |
774 | * to avoid allocating memory that | |
775 | * is tricky to free later */ | |
776 | memcpy(data->mac_addr, addr->sa_data, ETH_ALEN); | |
777 | asix_write_cmd_async(dev, AX_CMD_WRITE_NODE_ID, 0, 0, ETH_ALEN, | |
778 | data->mac_addr); | |
779 | ||
780 | return 0; | |
781 | } |