1054 lines
26 KiB
C
1054 lines
26 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*******************************************************************************
|
|
* Copyright (c) 2022 ASIX Electronic Corporation All rights reserved.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify it under
|
|
* the terms of the GNU General Public License as published by the Free Software
|
|
* Foundation, either version 2 of the License, or (at your option) any later
|
|
* version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
|
******************************************************************************/
|
|
#include "ax_main.h"
|
|
#include "ax88179_178a.h"
|
|
|
|
struct _ax_buikin_setting AX88179_BULKIN_SIZE[] = {
|
|
{7, 0x70, 0, 0x0C, 0x0f},
|
|
{7, 0x70, 0, 0x0C, 0x0f},
|
|
{7, 0x20, 3, 0x16, 0xff},
|
|
{7, 0xae, 7, 0x18, 0xff},
|
|
};
|
|
|
|
const struct ethtool_ops ax88179_ethtool_ops = {
|
|
.get_drvinfo = ax_get_drvinfo,
|
|
.get_link_ksettings = ax_get_link_ksettings,
|
|
.set_link_ksettings = ax_set_link_ksettings,
|
|
.get_link = ethtool_op_get_link,
|
|
.get_msglevel = ax_get_msglevel,
|
|
.set_msglevel = ax_set_msglevel,
|
|
.get_wol = ax_get_wol,
|
|
.set_wol = ax_set_wol,
|
|
.get_ts_info = ethtool_op_get_ts_info,
|
|
.get_strings = ax_get_strings,
|
|
.get_sset_count = ax_get_sset_count,
|
|
.get_ethtool_stats = ax_get_ethtool_stats,
|
|
.get_regs_len = ax_get_regs_len,
|
|
.get_regs = ax_get_regs,
|
|
};
|
|
|
|
int ax88179_signature(struct ax_device *axdev, struct _ax_ioctl_command *info)
|
|
{
|
|
strscpy(info->sig, AX88179_SIGNATURE, sizeof(info->sig));
|
|
return 0;
|
|
}
|
|
|
|
int ax88179_read_eeprom(struct ax_device *axdev, struct _ax_ioctl_command *info)
|
|
{
|
|
u8 i;
|
|
u16 tmp;
|
|
u8 value;
|
|
unsigned short *buf;
|
|
|
|
if (info->buf) {
|
|
buf = kmalloc_array(info->size, sizeof(unsigned short),
|
|
GFP_KERNEL);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
} else {
|
|
netdev_info(axdev->netdev, "The EEPROM buffer cannot be NULL. \r\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (info->type == 0) {
|
|
for (i = 0; i < info->size; i++) {
|
|
if (ax_write_cmd(axdev, AX_ACCESS_MAC, AX_SROM_ADDR, 1, 1, &i) < 0) {
|
|
kfree(buf);
|
|
return -EINVAL;
|
|
}
|
|
|
|
value = EEP_RD;
|
|
if (ax_write_cmd(axdev, AX_ACCESS_MAC, AX_SROM_CMD, 1, 1, &value) < 0) {
|
|
kfree(buf);
|
|
return -EINVAL;
|
|
}
|
|
|
|
do {
|
|
ax_read_cmd(axdev, AX_ACCESS_MAC, AX_SROM_CMD, 1, 1, &value, 0);
|
|
} while (value & EEP_BUSY);
|
|
|
|
if (ax_read_cmd(axdev, AX_ACCESS_MAC, AX_SROM_DATA_LOW,
|
|
2, 2, &tmp, 1) < 0) {
|
|
kfree(buf);
|
|
return -EINVAL;
|
|
}
|
|
|
|
*(buf + i) = be16_to_cpu(tmp);
|
|
|
|
if (i == (info->size - 1))
|
|
break;
|
|
}
|
|
} else {
|
|
for (i = 0; i < info->size; i++) {
|
|
if (ax_read_cmd(axdev, AX_ACCESS_EFUSE, i, 1, 2, &tmp, 1) < 0) {
|
|
kfree(buf);
|
|
return -EINVAL;
|
|
}
|
|
*(buf + i) = be16_to_cpu(tmp);
|
|
if (i == (info->size - 1))
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (copy_to_user(info->buf, buf, sizeof(unsigned short) * info->size)) {
|
|
kfree(buf);
|
|
return -EFAULT;
|
|
}
|
|
|
|
kfree(buf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ax88179_write_eeprom(struct ax_device *axdev,
|
|
struct _ax_ioctl_command *info)
|
|
{
|
|
int i;
|
|
u16 data, csum = 0;
|
|
unsigned short *buf;
|
|
|
|
if (info->buf) {
|
|
buf = kmalloc_array(info->size, sizeof(unsigned short),
|
|
GFP_KERNEL);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
|
|
if (copy_from_user(buf, info->buf,
|
|
sizeof(unsigned short) * info->size)) {
|
|
kfree(buf);
|
|
return -EFAULT;
|
|
}
|
|
} else {
|
|
netdev_err(axdev->netdev, "The EEPROM buffer cannot be NULL. \r\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (info->type == 0) {
|
|
if ((*(buf) >> 8) & 0x01) {
|
|
netdev_info(axdev->netdev, "Cannot be set to muliticast MAC address, ");
|
|
netdev_info(axdev->netdev, "bit0 of Node ID-0 cannot be set to 1. \r\n");
|
|
kfree(buf);
|
|
return -EINVAL;
|
|
}
|
|
|
|
csum = (*(buf + 3) & 0xff) + ((*(buf + 3) >> 8) & 0xff) +
|
|
(*(buf + 4) & 0xff) + ((*(buf + 4) >> 8) & 0xff);
|
|
csum = 0xff - ((csum >> 8) + (csum & 0xff));
|
|
data = ((*(buf + 5)) & 0xff) | (csum << 8);
|
|
*(buf + 5) = data;
|
|
|
|
for (i = 0; i < info->size; i++) {
|
|
data = cpu_to_be16(*(buf + i));
|
|
if (ax_write_cmd(axdev, AX_ACCESS_EEPROM,
|
|
i, 1, 2, &data) < 0) {
|
|
kfree(buf);
|
|
return -EINVAL;
|
|
}
|
|
|
|
msleep(info->delay);
|
|
}
|
|
} else if (info->type == 1) {
|
|
if ((*(buf) >> 8) & 0x01) {
|
|
netdev_info(axdev->netdev, "Cannot be set to muliticast MAC address, ");
|
|
netdev_info(axdev->netdev, "bit0 of Node ID-0 cannot be set to 1. \r\n");
|
|
kfree(buf);
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < info->size; i++)
|
|
csum += (*(buf + i) & 0xff) + ((*(buf + i) >> 8) & 0xff);
|
|
|
|
csum -= ((*(buf + 0x19) >> 8) & 0xff);
|
|
while (csum > 255)
|
|
csum = (csum & 0x00FF) + ((csum >> 8) & 0x00FF);
|
|
csum = 0xFF - csum;
|
|
|
|
data = ((*(buf + 0x19)) & 0xff) | (csum << 8);
|
|
*(buf + 0x19) = data;
|
|
|
|
if (ax_write_cmd(axdev, AX_WRITE_EFUSE_EN, 0, 0, 0, NULL) < 0) {
|
|
kfree(buf);
|
|
return -EINVAL;
|
|
}
|
|
|
|
msleep(info->delay);
|
|
|
|
for (i = 0; i < info->size; i++) {
|
|
data = cpu_to_be16(*(buf + i));
|
|
if (ax_write_cmd(axdev, AX_ACCESS_EFUSE, i, 1, 2, &data) < 0) {
|
|
kfree(buf);
|
|
return -EINVAL;
|
|
}
|
|
|
|
msleep(info->delay);
|
|
}
|
|
|
|
if (ax_write_cmd(axdev, AX_WRITE_EFUSE_DIS, 0, 0, 0, NULL) < 0) {
|
|
kfree(buf);
|
|
return -EINVAL;
|
|
}
|
|
|
|
msleep(info->delay);
|
|
} else if (info->type == 2) {
|
|
if (ax_read_cmd(axdev, AX_ACCESS_EFUSE, 0, 1, 2, &data, 1) < 0) {
|
|
kfree(buf);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (data == 0xFFFF)
|
|
info->type = 0;
|
|
else
|
|
info->type = 1;
|
|
} else {
|
|
kfree(buf);
|
|
return -EINVAL;
|
|
}
|
|
|
|
kfree(buf);
|
|
return 0;
|
|
}
|
|
|
|
IOCTRL_TABLE ax88179_tbl[] = {
|
|
ax88179_signature,
|
|
NULL,//ax_usb_command,
|
|
ax88179_read_eeprom,
|
|
ax88179_write_eeprom,
|
|
};
|
|
|
|
int ax88179_siocdevprivate(struct net_device *netdev, struct ifreq *rq,
|
|
void __user *udata, int cmd)
|
|
{
|
|
struct ax_device *axdev = netdev_priv(netdev);
|
|
struct _ax_ioctl_command info;
|
|
struct _ax_ioctl_command *uptr = (struct _ax_ioctl_command *)rq->ifr_data;
|
|
int ret = 0;
|
|
|
|
switch (cmd) {
|
|
case AX_PRIVATE:
|
|
if (copy_from_user(&info, uptr,
|
|
sizeof(struct _ax_ioctl_command)))
|
|
return -EFAULT;
|
|
|
|
if ((*ax88179_tbl[info.ioctl_cmd])(axdev, &info) < 0) {
|
|
netdev_info(netdev, "ax88179_tbl, return -EFAULT");
|
|
return -EFAULT;
|
|
}
|
|
|
|
if (copy_to_user(uptr, &info, sizeof(struct _ax_ioctl_command)))
|
|
return -EFAULT;
|
|
|
|
break;
|
|
default:
|
|
ret = -EOPNOTSUPP;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int ax88179_ioctl(struct net_device *netdev, struct ifreq *rq, int cmd)
|
|
{
|
|
struct ax_device *axdev = netdev_priv(netdev);
|
|
|
|
return generic_mii_ioctl(&axdev->mii, if_mii(rq), cmd, NULL);
|
|
}
|
|
|
|
void ax88179_set_multicast(struct net_device *net)
|
|
{
|
|
struct ax_device *axdev = netdev_priv(net);
|
|
u8 *m_filter = axdev->m_filter;
|
|
int mc_count = 0;
|
|
|
|
if (!test_bit(AX_ENABLE, &axdev->flags))
|
|
return;
|
|
|
|
mc_count = netdev_mc_count(net);
|
|
|
|
axdev->rxctl = (AX_RX_CTL_START | AX_RX_CTL_AB);
|
|
|
|
if (net->flags & IFF_PROMISC) {
|
|
axdev->rxctl |= AX_RX_CTL_PRO;
|
|
} else if (net->flags & IFF_ALLMULTI || mc_count > AX_MAX_MCAST) {
|
|
axdev->rxctl |= AX_RX_CTL_AMALL;
|
|
} else if (mc_count == 0) {
|
|
} else {
|
|
u32 crc_bits;
|
|
struct netdev_hw_addr *ha = NULL;
|
|
|
|
memset(m_filter, 0, AX_MCAST_FILTER_SIZE);
|
|
netdev_for_each_mc_addr(ha, net) {
|
|
crc_bits = ether_crc(ETH_ALEN, ha->addr) >> 26;
|
|
*(m_filter + (crc_bits >> 3)) |=
|
|
1 << (crc_bits & 7);
|
|
}
|
|
ax_write_cmd_async(axdev, AX_ACCESS_MAC, AX_MULTI_FILTER_ARRY,
|
|
AX_MCAST_FILTER_SIZE, AX_MCAST_FILTER_SIZE, m_filter);
|
|
axdev->rxctl |= AX_RX_CTL_AM;
|
|
}
|
|
ax_write_cmd_async(axdev, AX_ACCESS_MAC, AX_RX_CTL, 2, 2, &axdev->rxctl);
|
|
}
|
|
|
|
int ax88179_set_mac_addr(struct net_device *netdev, void *p)
|
|
{
|
|
struct ax_device *axdev = netdev_priv(netdev);
|
|
struct sockaddr *addr = p;
|
|
int ret;
|
|
|
|
if (!is_valid_ether_addr(addr->sa_data))
|
|
return -EADDRNOTAVAIL;
|
|
|
|
if (netif_running(netdev))
|
|
return -EBUSY;
|
|
|
|
memcpy(netdev->dev_addr, addr->sa_data, ETH_ALEN);
|
|
|
|
ret = ax_write_cmd(axdev, AX_ACCESS_MAC, AX_NODE_ID, ETH_ALEN,
|
|
ETH_ALEN, netdev->dev_addr);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ax88179_check_eeprom(struct ax_device *axdev)
|
|
{
|
|
u8 i = 0;
|
|
u8 buf[2];
|
|
u8 eeprom[20];
|
|
u16 csum = 0, delay = HZ / 10;
|
|
|
|
for (i = 0 ; i < 6; i++) {
|
|
buf[0] = i;
|
|
if (ax_write_cmd(axdev, AX_ACCESS_MAC, AX_SROM_ADDR, 1, 1, buf) < 0)
|
|
return -EINVAL;
|
|
|
|
buf[0] = EEP_RD;
|
|
if (ax_write_cmd(axdev, AX_ACCESS_MAC, AX_SROM_CMD, 1, 1, buf) < 0)
|
|
return -EINVAL;
|
|
|
|
do {
|
|
ax_read_cmd(axdev, AX_ACCESS_MAC, AX_SROM_CMD, 1, 1, buf, 0);
|
|
|
|
if (time_after(jiffies, (jiffies + delay)))
|
|
return -EINVAL;
|
|
} while (buf[0] & EEP_BUSY);
|
|
|
|
ax_read_cmd(axdev, AX_ACCESS_MAC, AX_SROM_DATA_LOW, 2, 2, &eeprom[i * 2], 0);
|
|
|
|
if (i == 0 && eeprom[0] == 0xFF)
|
|
return -EINVAL;
|
|
}
|
|
|
|
csum = eeprom[6] + eeprom[7] + eeprom[8] + eeprom[9];
|
|
csum = (csum >> 8) + (csum & 0xff);
|
|
|
|
if ((csum + eeprom[10]) == 0xff)
|
|
return 0;
|
|
else
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ax88179_check_efuse(struct ax_device *axdev, void *ledmode)
|
|
{
|
|
u8 i = 0;
|
|
u16 csum = 0;
|
|
u8 efuse[64];
|
|
|
|
if (ax_read_cmd(axdev, AX_ACCESS_EFUSE, 0, 64, 64, efuse, 0) < 0)
|
|
return -EINVAL;
|
|
|
|
if (efuse[0] == 0xFF)
|
|
return -EINVAL;
|
|
|
|
for (i = 0; i < 64; i++)
|
|
csum = csum + efuse[i];
|
|
|
|
while (csum > 255)
|
|
csum = (csum & 0x00FF) + ((csum >> 8) & 0x00FF);
|
|
|
|
if (csum == 0xFF) {
|
|
memcpy((u8 *)ledmode, &efuse[51], 2);
|
|
return 0;
|
|
} else {
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ax88179_convert_old_led(struct ax_device *axdev, u8 efuse, void *ledvalue)
|
|
{
|
|
u8 ledmode = 0;
|
|
u16 reg16;
|
|
u16 led = 0;
|
|
|
|
/* loaded the old eFuse LED Mode */
|
|
if (efuse) {
|
|
if (ax_read_cmd(axdev, AX_ACCESS_EFUSE, 0x18, 1, 2, ®16, 1) < 0)
|
|
return -EINVAL;
|
|
ledmode = (u8)(reg16 & 0xFF);
|
|
} else { /* loaded the old EEprom LED Mode */
|
|
if (ax_read_cmd(axdev, AX_ACCESS_EEPROM, 0x3C, 1, 2, ®16, 1) < 0)
|
|
return -EINVAL;
|
|
ledmode = (u8)(reg16 >> 8);
|
|
}
|
|
netdev_dbg(axdev->netdev, "Old LED Mode = %02X\n", ledmode);
|
|
|
|
switch (ledmode) {
|
|
case 0xFF:
|
|
led = LED0_ACTIVE | LED1_LINK_10 | LED1_LINK_100 |
|
|
LED1_LINK_1000 | LED2_ACTIVE | LED2_LINK_10 |
|
|
LED2_LINK_100 | LED2_LINK_1000 | LED_VALID;
|
|
break;
|
|
case 0xFE:
|
|
led = LED0_ACTIVE | LED1_LINK_1000 | LED2_LINK_100 | LED_VALID;
|
|
break;
|
|
case 0xFD:
|
|
led = LED0_ACTIVE | LED1_LINK_1000 | LED2_LINK_100 |
|
|
LED2_LINK_10 | LED_VALID;
|
|
break;
|
|
case 0xFC:
|
|
led = LED0_ACTIVE | LED1_ACTIVE | LED1_LINK_1000 | LED2_ACTIVE |
|
|
LED2_LINK_100 | LED2_LINK_10 | LED_VALID;
|
|
break;
|
|
default:
|
|
led = LED0_ACTIVE | LED1_LINK_10 | LED1_LINK_100 |
|
|
LED1_LINK_1000 | LED2_ACTIVE | LED2_LINK_10 |
|
|
LED2_LINK_100 | LED2_LINK_1000 | LED_VALID;
|
|
break;
|
|
}
|
|
|
|
memcpy((u8 *)ledvalue, &led, 2);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ax88179_gether_setting(struct ax_device *axdev)
|
|
{
|
|
u16 reg16;
|
|
|
|
reg16 = 0x03;
|
|
ax_write_cmd(axdev, AX_ACCESS_PHY, AX88179_PHY_ID,
|
|
31, 2, ®16);
|
|
reg16 = 0x3246;
|
|
ax_write_cmd(axdev, AX_ACCESS_PHY, AX88179_PHY_ID,
|
|
25, 2, ®16);
|
|
reg16 = 0;
|
|
ax_write_cmd(axdev, AX_ACCESS_PHY, AX88179_PHY_ID,
|
|
31, 2, ®16);
|
|
}
|
|
|
|
static int ax88179_LED_setting(struct ax_device *axdev)
|
|
{
|
|
u16 ledvalue = 0, delay = HZ / 10;
|
|
u16 ledact, ledlink;
|
|
u16 reg16;
|
|
u8 value;
|
|
|
|
ax_read_cmd(axdev, AX_ACCESS_MAC, GENERAL_STATUS, 1, 1, &value, 0);
|
|
|
|
if (!(value & AX_SECLD)) {
|
|
value = AX_GPIO_CTRL_GPIO3EN | AX_GPIO_CTRL_GPIO2EN |
|
|
AX_GPIO_CTRL_GPIO1EN;
|
|
if (ax_write_cmd(axdev, AX_ACCESS_MAC, AX_GPIO_CTRL,
|
|
1, 1, &value) < 0)
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!ax88179_check_eeprom(axdev)) {
|
|
value = 0x42;
|
|
if (ax_write_cmd(axdev, AX_ACCESS_MAC, AX_SROM_ADDR, 1, 1, &value) < 0)
|
|
return -EINVAL;
|
|
|
|
value = EEP_RD;
|
|
if (ax_write_cmd(axdev, AX_ACCESS_MAC, AX_SROM_CMD, 1, 1, &value) < 0)
|
|
return -EINVAL;
|
|
|
|
do {
|
|
ax_read_cmd(axdev, AX_ACCESS_MAC, AX_SROM_CMD, 1, 1, &value, 0);
|
|
|
|
ax_read_cmd(axdev, AX_ACCESS_MAC, AX_SROM_CMD, 1, 1, &value, 0);
|
|
|
|
if (time_after(jiffies, (jiffies + delay)))
|
|
return -EINVAL;
|
|
} while (value & EEP_BUSY);
|
|
|
|
ax_read_cmd(axdev, AX_ACCESS_MAC, AX_SROM_DATA_HIGH, 1, 1, &value, 0);
|
|
ledvalue = (value << 8);
|
|
ax_read_cmd(axdev, AX_ACCESS_MAC, AX_SROM_DATA_LOW, 1, 1, &value, 0);
|
|
ledvalue |= value;
|
|
|
|
if (ledvalue == 0xFFFF || ((ledvalue & LED_VALID) == 0))
|
|
ax88179_convert_old_led(axdev, 0, &ledvalue);
|
|
|
|
} else if (!ax88179_check_efuse(axdev, &ledvalue)) {
|
|
if (ledvalue == 0xFFFF || ((ledvalue & LED_VALID) == 0))
|
|
ax88179_convert_old_led(axdev, 0, &ledvalue);
|
|
} else {
|
|
ax88179_convert_old_led(axdev, 0, &ledvalue);
|
|
}
|
|
|
|
reg16 = GMII_PHY_PAGE_SELECT_EXT;
|
|
ax_write_cmd(axdev, AX_ACCESS_PHY, AX88179_PHY_ID, GMII_PHY_PAGE_SELECT, 2, ®16);
|
|
|
|
reg16 = 0x2c;
|
|
ax_write_cmd(axdev, AX_ACCESS_PHY, AX88179_PHY_ID, GMII_PHYPAGE, 2, ®16);
|
|
|
|
ax_read_cmd(axdev, AX_ACCESS_PHY, AX88179_PHY_ID, GMII_LED_ACTIVE, 2, &ledact, 1);
|
|
|
|
ax_read_cmd(axdev, AX_ACCESS_PHY, AX88179_PHY_ID, GMII_LED_LINK, 2, &ledlink, 1);
|
|
|
|
ledact &= GMII_LED_ACTIVE_MASK;
|
|
ledlink &= GMII_LED_LINK_MASK;
|
|
|
|
if (ledvalue & LED0_ACTIVE)
|
|
ledact |= GMII_LED0_ACTIVE;
|
|
if (ledvalue & LED1_ACTIVE)
|
|
ledact |= GMII_LED1_ACTIVE;
|
|
if (ledvalue & LED2_ACTIVE)
|
|
ledact |= GMII_LED2_ACTIVE;
|
|
|
|
if (ledvalue & LED0_LINK_10)
|
|
ledlink |= GMII_LED0_LINK_10;
|
|
if (ledvalue & LED1_LINK_10)
|
|
ledlink |= GMII_LED1_LINK_10;
|
|
if (ledvalue & LED2_LINK_10)
|
|
ledlink |= GMII_LED2_LINK_10;
|
|
|
|
if (ledvalue & LED0_LINK_100)
|
|
ledlink |= GMII_LED0_LINK_100;
|
|
if (ledvalue & LED1_LINK_100)
|
|
ledlink |= GMII_LED1_LINK_100;
|
|
if (ledvalue & LED2_LINK_100)
|
|
ledlink |= GMII_LED2_LINK_100;
|
|
|
|
if (ledvalue & LED0_LINK_1000)
|
|
ledlink |= GMII_LED0_LINK_1000;
|
|
if (ledvalue & LED1_LINK_1000)
|
|
ledlink |= GMII_LED1_LINK_1000;
|
|
if (ledvalue & LED2_LINK_1000)
|
|
ledlink |= GMII_LED2_LINK_1000;
|
|
|
|
ax_write_cmd(axdev, AX_ACCESS_PHY, AX88179_PHY_ID, GMII_LED_ACTIVE, 2, &ledact);
|
|
|
|
ax_write_cmd(axdev, AX_ACCESS_PHY, AX88179_PHY_ID, GMII_LED_LINK, 2, &ledlink);
|
|
|
|
reg16 = GMII_PHY_PAGE_SELECT_PAGE0;
|
|
ax_write_cmd(axdev, AX_ACCESS_PHY, AX88179_PHY_ID, GMII_PHY_PAGE_SELECT, 2, ®16);
|
|
|
|
/* LED full duplex setting */
|
|
reg16 = 0;
|
|
if (ledvalue & LED0_FD)
|
|
reg16 |= 0x01;
|
|
else if ((ledvalue & LED0_USB3_MASK) == 0)
|
|
reg16 |= 0x02;
|
|
|
|
if (ledvalue & LED1_FD)
|
|
reg16 |= 0x04;
|
|
else if ((ledvalue & LED1_USB3_MASK) == 0)
|
|
reg16 |= 0x08;
|
|
|
|
if (ledvalue & LED2_FD) /* LED2_FD */
|
|
reg16 |= 0x10;
|
|
else if ((ledvalue & LED2_USB3_MASK) == 0) /* LED2_USB3 */
|
|
reg16 |= 0x20;
|
|
|
|
ax_write_cmd(axdev, AX_ACCESS_MAC, 0x73, 1, 1, ®16);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ax88179_EEE_setting(struct ax_device *axdev)
|
|
{
|
|
u16 reg16;
|
|
/* Disable */
|
|
reg16 = 0x07;
|
|
ax_write_cmd(axdev, AX_ACCESS_PHY, AX88179_PHY_ID, GMII_PHY_MACR, 2, ®16);
|
|
reg16 = 0x3c;
|
|
ax_write_cmd(axdev, AX_ACCESS_PHY, AX88179_PHY_ID, GMII_PHY_MAADR, 2, ®16);
|
|
reg16 = 0x4007;
|
|
ax_write_cmd(axdev, AX_ACCESS_PHY, AX88179_PHY_ID, GMII_PHY_MACR, 2, ®16);
|
|
reg16 = 0x00;
|
|
ax_write_cmd(axdev, AX_ACCESS_PHY, AX88179_PHY_ID, GMII_PHY_MAADR, 2, ®16);
|
|
}
|
|
|
|
static int ax88179_auto_detach(struct ax_device *axdev, int in_pm)
|
|
{
|
|
u16 reg16;
|
|
usb_read_function fnr;
|
|
usb_write_function fnw;
|
|
|
|
if (!in_pm) {
|
|
fnr = ax_read_cmd;
|
|
fnw = ax_write_cmd;
|
|
} else {
|
|
fnr = ax_read_cmd_nopm;
|
|
fnw = ax_write_cmd_nopm;
|
|
}
|
|
|
|
if (fnr(axdev, AX_ACCESS_EEPROM, 0x43, 1, 2, ®16, 1) < 0)
|
|
return 0;
|
|
|
|
if (reg16 == 0xFFFF || !(reg16 & 0x0100))
|
|
return 0;
|
|
|
|
reg16 = 0;
|
|
fnr(axdev, AX_ACCESS_MAC, AX_CLK_SELECT, 1, 1, ®16, 0);
|
|
reg16 |= AX_CLK_SELECT_ULR;
|
|
fnw(axdev, AX_ACCESS_MAC, AX_CLK_SELECT, 1, 1, ®16);
|
|
|
|
fnr(axdev, AX_ACCESS_MAC, AX_PHYPWR_RSTCTL, 2, 2, ®16, 1);
|
|
reg16 |= AX_PHYPWR_RSTCTL_AUTODETACH;
|
|
fnw(axdev, AX_ACCESS_MAC, AX_PHYPWR_RSTCTL, 2, 2, ®16);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ax88179_hw_init(struct ax_device *axdev)
|
|
{
|
|
u32 reg32;
|
|
u16 reg16;
|
|
u8 reg8;
|
|
u8 buf[6] = {0};
|
|
|
|
reg32 = 0;
|
|
ax_write_cmd(axdev, 0x81, 0x310, 0, 4, ®32);
|
|
|
|
reg16 = 0;
|
|
ax_write_cmd(axdev, AX_ACCESS_MAC, AX_PHYPWR_RSTCTL, 2, 2, ®16);
|
|
reg16 = AX_PHYPWR_RSTCTL_IPRL;
|
|
ax_write_cmd(axdev, AX_ACCESS_MAC, AX_PHYPWR_RSTCTL, 2, 2, ®16);
|
|
msleep(200);
|
|
|
|
reg8 = AX_CLK_SELECT_ACS | AX_CLK_SELECT_BCS;
|
|
ax_write_cmd(axdev, AX_ACCESS_MAC, AX_CLK_SELECT, 1, 1, ®8);
|
|
msleep(100);
|
|
|
|
ax88179_auto_detach(axdev, 0);
|
|
|
|
memcpy(buf, &AX88179_BULKIN_SIZE[0], 5);
|
|
ax_write_cmd(axdev, AX_ACCESS_MAC, AX_RX_BULKIN_QCTRL, 5, 5, buf);
|
|
|
|
reg8 = 0x34;
|
|
ax_write_cmd(axdev, AX_ACCESS_MAC, AX_PAUSE_WATERLVL_LOW, 1, 1, ®8);
|
|
|
|
reg8 = 0x52;
|
|
ax_write_cmd(axdev, AX_ACCESS_MAC, AX_PAUSE_WATERLVL_HIGH, 1, 1, ®8);
|
|
|
|
ax_write_cmd(axdev, 0x91, 0, 0, 0, NULL);
|
|
|
|
reg8 = AX_RXCOE_IP | AX_RXCOE_TCP | AX_RXCOE_UDP |
|
|
AX_RXCOE_TCPV6 | AX_RXCOE_UDPV6;
|
|
ax_write_cmd(axdev, AX_ACCESS_MAC, AX_RXCOE_CTL, 1, 1, ®8);
|
|
|
|
reg8 = AX_TXCOE_IP | AX_TXCOE_TCP | AX_TXCOE_UDP |
|
|
AX_TXCOE_TCPV6 | AX_TXCOE_UDPV6;
|
|
ax_write_cmd(axdev, AX_ACCESS_MAC, AX_TXCOE_CTL, 1, 1, ®8);
|
|
|
|
reg8 = AX_MONITOR_MODE_PMETYPE | AX_MONITOR_MODE_PMEPOL |
|
|
AX_MONITOR_MODE_RWLC | AX_MONITOR_MODE_RWMP;
|
|
ax_write_cmd(axdev, AX_ACCESS_MAC, AX_MONITOR_MODE, 1, 1, ®8);
|
|
|
|
ax88179_LED_setting(axdev);
|
|
|
|
ax88179_EEE_setting(axdev);
|
|
|
|
ax88179_gether_setting(axdev);
|
|
|
|
ax_set_tx_qlen(axdev);
|
|
|
|
mii_nway_restart(&axdev->mii);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ax88179_bind(struct ax_device *axdev)
|
|
{
|
|
struct net_device *netdev = axdev->netdev;
|
|
|
|
ax_print_version(axdev, AX_DRIVER_STRING_179_178A);
|
|
|
|
netdev->features |= NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM |
|
|
NETIF_F_SG | NETIF_F_TSO | NETIF_F_FRAGLIST;
|
|
netdev->hw_features |= NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM |
|
|
NETIF_F_SG | NETIF_F_TSO | NETIF_F_FRAGLIST;
|
|
|
|
axdev->tx_casecade_size = TX_CASECADES_SIZE;
|
|
axdev->gso_max_size = AX_GSO_DEFAULT_SIZE;
|
|
axdev->mii.supports_gmii = 1;
|
|
axdev->mii.dev = netdev;
|
|
axdev->mii.mdio_read = ax_mdio_read;
|
|
axdev->mii.mdio_write = ax_mdio_write;
|
|
axdev->mii.phy_id_mask = 0xff;
|
|
axdev->mii.reg_num_mask = 0xff;
|
|
axdev->mii.phy_id = AX88179_PHY_ID;
|
|
axdev->mii.force_media = 0;
|
|
axdev->mii.advertising = ADVERTISE_10HALF | ADVERTISE_10FULL |
|
|
ADVERTISE_100HALF | ADVERTISE_100FULL;
|
|
netif_set_gso_max_size(netdev, axdev->gso_max_size);
|
|
|
|
axdev->bin_setting.custom = 1;
|
|
axdev->tx_align_len = 4;
|
|
|
|
netdev->ethtool_ops = &ax88179_ethtool_ops;
|
|
axdev->netdev->netdev_ops = &ax88179_netdev_ops;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ax88179_unbind(struct ax_device *axdev)
|
|
{
|
|
}
|
|
|
|
static int ax88179_stop(struct ax_device *axdev)
|
|
{
|
|
u16 reg16;
|
|
|
|
reg16 = AX_RX_CTL_STOP;
|
|
ax_write_cmd(axdev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, 2, 2, ®16);
|
|
|
|
reg16 = 0;
|
|
ax_write_cmd(axdev, AX_ACCESS_MAC, AX_CLK_SELECT, 1, 1, ®16);
|
|
|
|
reg16 = AX_PHYPWR_RSTCTL_BZ;
|
|
ax_write_cmd(axdev, AX_ACCESS_MAC, AX_PHYPWR_RSTCTL, 2, 2, ®16);
|
|
msleep(200);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ax88179_link_reset(struct ax_device *axdev)
|
|
{
|
|
u8 reg8[5], link_sts;
|
|
u16 mode, reg16, delay;
|
|
u32 reg32;
|
|
|
|
mode = AX_MEDIUM_TXFLOW_CTRLEN | AX_MEDIUM_RXFLOW_CTRLEN;
|
|
|
|
ax_read_cmd_nopm(axdev, AX_ACCESS_MAC, PHYSICAL_LINK_STATUS,
|
|
1, 1, &link_sts, 0);
|
|
ax_read_cmd_nopm(axdev, AX_ACCESS_PHY, AX88179_PHY_ID,
|
|
GMII_PHY_PHYSR, 2, ®16, 1);
|
|
|
|
if (!(reg16 & GMII_PHY_PHYSR_LINK)) {
|
|
return -1;
|
|
} else if (GMII_PHY_PHYSR_GIGA == (reg16 & GMII_PHY_PHYSR_SMASK)) {
|
|
mode |= AX_MEDIUM_GIGAMODE;
|
|
if (axdev->netdev->mtu > 1500)
|
|
mode |= AX_MEDIUM_JUMBO_EN;
|
|
|
|
if (link_sts & AX_USB_SS)
|
|
memcpy(reg8, &AX88179_BULKIN_SIZE[0], 5);
|
|
else if (link_sts & AX_USB_HS)
|
|
memcpy(reg8, &AX88179_BULKIN_SIZE[1], 5);
|
|
else
|
|
memcpy(reg8, &AX88179_BULKIN_SIZE[3], 5);
|
|
} else if (GMII_PHY_PHYSR_100 == (reg16 & GMII_PHY_PHYSR_SMASK)) {
|
|
mode |= AX_MEDIUM_PS;
|
|
if (link_sts & (AX_USB_SS | AX_USB_HS))
|
|
memcpy(reg8, &AX88179_BULKIN_SIZE[2], 5);
|
|
else
|
|
memcpy(reg8, &AX88179_BULKIN_SIZE[3], 5);
|
|
} else {
|
|
memcpy(reg8, &AX88179_BULKIN_SIZE[3], 5);
|
|
}
|
|
|
|
ax_write_cmd_nopm(axdev, AX_ACCESS_MAC, AX_RX_BULKIN_QCTRL, 5, 5, reg8);
|
|
|
|
if (reg16 & GMII_PHY_PHYSR_FULL)
|
|
mode |= AX_MEDIUM_FULL_DUPLEX;
|
|
|
|
ax_read_cmd_nopm(axdev, 0x81, 0x8c, 0, 4, ®32, 1);
|
|
delay = HZ / 2;
|
|
if (reg32 & 0x40000000) {
|
|
unsigned long jtimeout;
|
|
u16 temp16 = 0;
|
|
|
|
ax_write_cmd_nopm(axdev, AX_ACCESS_MAC, AX_RX_CTL,
|
|
2, 2, &temp16);
|
|
ax_write_cmd_nopm(axdev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE,
|
|
2, 2, &mode);
|
|
|
|
jtimeout = jiffies + delay;
|
|
while (time_before(jiffies, jtimeout)) {
|
|
ax_read_cmd_nopm(axdev, 0x81, 0x8c, 0, 4, ®32, 1);
|
|
|
|
if (!(reg32 & 0x40000000))
|
|
break;
|
|
|
|
reg32 = 0x80000000;
|
|
ax_write_cmd(axdev, 0x81, 0x8c, 0, 4, ®32);
|
|
}
|
|
|
|
temp16 = AX_RX_CTL_DROPCRCERR | AX_RX_CTL_START |
|
|
AX_RX_CTL_AP | AX_RX_CTL_AMALL | AX_RX_CTL_AB;
|
|
ax_write_cmd_nopm(axdev, AX_ACCESS_MAC, AX_RX_CTL,
|
|
2, 2, &temp16);
|
|
}
|
|
|
|
axdev->rxctl |= AX_RX_CTL_DROPCRCERR | AX_RX_CTL_START | AX_RX_CTL_AB;
|
|
ax_write_cmd_nopm(axdev, AX_ACCESS_MAC, AX_RX_CTL,
|
|
2, 2, &axdev->rxctl);
|
|
|
|
mode |= AX_MEDIUM_RECEIVE_EN;
|
|
ax_write_cmd_nopm(axdev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE,
|
|
2, 2, &mode);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ax88179_tx_fixup(struct ax_device *axdev, struct tx_desc *desc)
|
|
{
|
|
struct sk_buff_head skb_head, *tx_queue = &axdev->tx_queue[0];
|
|
struct net_device_stats *stats = &axdev->netdev->stats;
|
|
int remain, ret;
|
|
u8 *tx_data;
|
|
|
|
__skb_queue_head_init(&skb_head);
|
|
spin_lock(&tx_queue->lock);
|
|
skb_queue_splice_init(tx_queue, &skb_head);
|
|
spin_unlock(&tx_queue->lock);
|
|
|
|
tx_data = desc->head;
|
|
desc->skb_num = 0;
|
|
desc->skb_len = 0;
|
|
remain = axdev->tx_casecade_size;
|
|
|
|
while (remain >= ETH_ZLEN + 8) {
|
|
struct sk_buff *skb;
|
|
u32 *tx_hdr1, *tx_hdr2;
|
|
|
|
skb = __skb_dequeue(&skb_head);
|
|
if (!skb)
|
|
break;
|
|
|
|
if ((skb->len + AX_TX_HEADER_LEN) > remain &&
|
|
(skb_shinfo(skb)->gso_size == 0)) {
|
|
__skb_queue_head(&skb_head, skb);
|
|
break;
|
|
}
|
|
|
|
memset(tx_data, 0, AX_TX_HEADER_LEN);
|
|
tx_hdr1 = (u32 *)tx_data;
|
|
tx_hdr2 = tx_hdr1 + 1;
|
|
*tx_hdr1 = skb->len;
|
|
*tx_hdr2 = skb_shinfo(skb)->gso_size;
|
|
cpu_to_le32s(tx_hdr1);
|
|
cpu_to_le32s(tx_hdr2);
|
|
tx_data += 8;
|
|
|
|
if (skb_copy_bits(skb, 0, tx_data, skb->len) < 0) {
|
|
stats->tx_dropped++;
|
|
dev_kfree_skb_any(skb);
|
|
continue;
|
|
}
|
|
|
|
tx_data += skb->len;
|
|
desc->skb_len += skb->len;
|
|
desc->skb_num += skb_shinfo(skb)->gso_segs ?: 1;
|
|
dev_kfree_skb_any(skb);
|
|
|
|
tx_data = __tx_buf_align(tx_data, axdev->tx_align_len);
|
|
if (*tx_hdr2 > 0)
|
|
break;
|
|
remain = axdev->tx_casecade_size -
|
|
(int)((void *)tx_data - desc->head);
|
|
}
|
|
|
|
if (!skb_queue_empty(&skb_head)) {
|
|
spin_lock(&tx_queue->lock);
|
|
skb_queue_splice(&skb_head, tx_queue);
|
|
spin_unlock(&tx_queue->lock);
|
|
}
|
|
|
|
netif_tx_lock(axdev->netdev);
|
|
|
|
if (netif_queue_stopped(axdev->netdev) &&
|
|
skb_queue_len(tx_queue) < axdev->tx_qlen) {
|
|
netif_wake_queue(axdev->netdev);
|
|
}
|
|
|
|
netif_tx_unlock(axdev->netdev);
|
|
|
|
ret = usb_autopm_get_interface_async(axdev->intf);
|
|
if (ret < 0)
|
|
goto out_tx_fill;
|
|
|
|
usb_fill_bulk_urb(desc->urb, axdev->udev,
|
|
usb_sndbulkpipe(axdev->udev, 3),
|
|
desc->head, (int)(tx_data - (u8 *)desc->head),
|
|
(usb_complete_t)ax_write_bulk_callback, desc);
|
|
|
|
ret = usb_submit_urb(desc->urb, GFP_ATOMIC);
|
|
if (ret < 0)
|
|
usb_autopm_put_interface_async(axdev->intf);
|
|
|
|
out_tx_fill:
|
|
return ret;
|
|
}
|
|
|
|
static void ax88179_rx_checksum(struct sk_buff *skb, u32 *pkt_hdr)
|
|
{
|
|
skb->ip_summed = CHECKSUM_NONE;
|
|
|
|
if ((*pkt_hdr & AX_RXHDR_L3CSUM_ERR) ||
|
|
(*pkt_hdr & AX_RXHDR_L4CSUM_ERR))
|
|
return;
|
|
|
|
if (((*pkt_hdr & AX_RXHDR_L4_TYPE_MASK) == AX_RXHDR_L4_TYPE_TCP) ||
|
|
((*pkt_hdr & AX_RXHDR_L4_TYPE_MASK) == AX_RXHDR_L4_TYPE_UDP))
|
|
skb->ip_summed = CHECKSUM_UNNECESSARY;
|
|
}
|
|
|
|
static void ax88179_rx_fixup
|
|
(struct ax_device *axdev, struct rx_desc *desc, int *work_done, int budget)
|
|
{
|
|
u8 *rx_data;
|
|
u32 const actual_length = desc->urb->actual_length;
|
|
u32 rx_hdr = 0, pkt_hdr = 0, pkt_hdr_curr = 0, hdr_off = 0;
|
|
u32 aa = 0;
|
|
int pkt_cnt = 0;
|
|
struct net_device *netdev = axdev->netdev;
|
|
struct net_device_stats *stats = ax_get_stats(netdev);
|
|
|
|
memcpy(&rx_hdr, (((u8 *)desc->head) + actual_length - 4),
|
|
sizeof(rx_hdr));
|
|
le32_to_cpus(&rx_hdr);
|
|
|
|
pkt_cnt = rx_hdr & 0xFF;
|
|
hdr_off = rx_hdr >> 16;
|
|
pkt_hdr_curr = hdr_off;
|
|
|
|
aa = (actual_length - (((pkt_cnt + 2) & 0xFE) * 4));
|
|
if (aa != hdr_off ||
|
|
hdr_off >= desc->urb->actual_length ||
|
|
pkt_cnt == 0) {
|
|
desc->urb->actual_length = 0;
|
|
stats->rx_length_errors++;
|
|
return;
|
|
}
|
|
|
|
rx_data = desc->head;
|
|
while (pkt_cnt--) {
|
|
u32 pkt_len;
|
|
struct sk_buff *skb;
|
|
|
|
memcpy(&pkt_hdr, (((u8 *)desc->head) + pkt_hdr_curr),
|
|
sizeof(pkt_hdr));
|
|
pkt_hdr_curr += 4;
|
|
|
|
le32_to_cpus(&pkt_hdr);
|
|
pkt_len = (pkt_hdr >> 16) & 0x1FFF;
|
|
|
|
if (pkt_hdr & AX_RXHDR_CRC_ERR) {
|
|
stats->rx_crc_errors++;
|
|
goto find_next_rx;
|
|
}
|
|
if (pkt_hdr & AX_RXHDR_DROP_ERR) {
|
|
stats->rx_dropped++;
|
|
goto find_next_rx;
|
|
}
|
|
|
|
skb = napi_alloc_skb(napi, pkt_len);
|
|
if (!skb) {
|
|
stats->rx_dropped++;
|
|
goto find_next_rx;
|
|
}
|
|
|
|
memcpy(skb->data, rx_data, pkt_len);
|
|
skb_put(skb, pkt_len);
|
|
|
|
ax88179_rx_checksum(skb, &pkt_hdr);
|
|
|
|
skb->protocol = eth_type_trans(skb, netdev);
|
|
|
|
if (*work_done < budget) {
|
|
napi_gro_receive(&axdev->napi, skb);
|
|
*work_done += 1;
|
|
stats->rx_packets++;
|
|
stats->rx_bytes += pkt_len;
|
|
} else {
|
|
__skb_queue_tail(&axdev->rx_queue, skb);
|
|
}
|
|
find_next_rx:
|
|
rx_data += (pkt_len + 7) & 0xFFF8;
|
|
}
|
|
}
|
|
|
|
static int ax88179_system_suspend(struct ax_device *axdev)
|
|
{
|
|
u16 reg16;
|
|
|
|
ax_read_cmd_nopm(axdev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE,
|
|
2, 2, ®16, 1);
|
|
reg16 &= ~AX_MEDIUM_RECEIVE_EN;
|
|
ax_write_cmd_nopm(axdev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE,
|
|
2, 2, ®16);
|
|
|
|
ax_read_cmd_nopm(axdev, AX_ACCESS_MAC, AX_PHYPWR_RSTCTL,
|
|
2, 2, ®16, 1);
|
|
reg16 |= AX_PHYPWR_RSTCTL_IPRL;
|
|
ax_write_cmd_nopm(axdev, AX_ACCESS_MAC, AX_PHYPWR_RSTCTL,
|
|
2, 2, ®16);
|
|
|
|
reg16 = AX_RX_CTL_STOP;
|
|
ax_write_cmd_nopm(axdev, AX_ACCESS_MAC, AX_RX_CTL, 2, 2, ®16);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ax88179_system_resume(struct ax_device *axdev)
|
|
{
|
|
u16 reg16;
|
|
u8 reg8;
|
|
|
|
reg16 = 0;
|
|
ax_write_cmd_nopm(axdev, AX_ACCESS_MAC, AX_PHYPWR_RSTCTL, 2, 2, ®16);
|
|
usleep_range(1000, 2000);
|
|
|
|
reg16 = AX_PHYPWR_RSTCTL_IPRL;
|
|
ax_write_cmd_nopm(axdev, AX_ACCESS_MAC, AX_PHYPWR_RSTCTL, 2, 2, ®16);
|
|
msleep(200);
|
|
|
|
ax88179_auto_detach(axdev, 1);
|
|
|
|
ax_read_cmd_nopm(axdev, AX_ACCESS_MAC, AX_CLK_SELECT, 1, 1, ®8, 0);
|
|
reg8 |= AX_CLK_SELECT_ACS | AX_CLK_SELECT_BCS;
|
|
ax_write_cmd_nopm(axdev, AX_ACCESS_MAC, AX_CLK_SELECT, 1, 1, ®8);
|
|
msleep(200);
|
|
|
|
reg16 = AX_RX_CTL_START | AX_RX_CTL_AP |
|
|
AX_RX_CTL_AMALL | AX_RX_CTL_AB;
|
|
ax_write_cmd_nopm(axdev, AX_ACCESS_MAC, AX_RX_CTL, 2, 2, ®16);
|
|
|
|
return 0;
|
|
}
|
|
|
|
const struct driver_info ax88179_info = {
|
|
.bind = ax88179_bind,
|
|
.unbind = ax88179_unbind,
|
|
.hw_init = ax88179_hw_init,
|
|
.stop = ax88179_stop,
|
|
.link_reset = ax88179_link_reset,
|
|
.rx_fixup = ax88179_rx_fixup,
|
|
.tx_fixup = ax88179_tx_fixup,
|
|
.system_suspend = ax88179_system_suspend,
|
|
.system_resume = ax88179_system_resume,
|
|
.napi_weight = AX88179_NAPI_WEIGHT,
|
|
.buf_rx_size = AX88179_BUF_RX_SIZE,
|
|
};
|