1232 lines
30 KiB
C
1232 lines
30 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 "ax88179a_772d.h"
|
||
|
|
|
||
|
|
struct _ax_buikin_setting AX88179A_BULKIN_SIZE[] = {
|
||
|
|
{5, 0x7B, 0x00, 0x18, 0x0F}, //1G, SS
|
||
|
|
{5, 0xC0, 0x02, 0x06, 0x0F}, //1G, HS
|
||
|
|
{7, 0xF0, 0x00, 0x0C, 0x0F}, //100M, Full, SS
|
||
|
|
{6, 0x00, 0x00, 0x06, 0x0F}, //100M, Half, SS
|
||
|
|
{5, 0xC0, 0x04, 0x06, 0x0F}, //100M, Full, HS
|
||
|
|
{7, 0xC0, 0x04, 0x06, 0x0F}, //100M, Half, HS
|
||
|
|
{7, 0x00, 0, 0x03, 0x3F}, //FS
|
||
|
|
};
|
||
|
|
|
||
|
|
static int ax88179a_chk_eee(struct ax_device *axdev)
|
||
|
|
{
|
||
|
|
struct ethtool_cmd ecmd = { .cmd = ETHTOOL_GSET };
|
||
|
|
|
||
|
|
mii_ethtool_gset(&axdev->mii, &ecmd);
|
||
|
|
|
||
|
|
if (ecmd.speed == SPEED_1000) {
|
||
|
|
int eee_lp, eee_cap, eee_adv;
|
||
|
|
u32 lp, cap, adv;
|
||
|
|
|
||
|
|
eee_cap = ax_mmd_read(axdev->netdev,
|
||
|
|
MDIO_MMD_PCS, MDIO_PCS_EEE_ABLE);
|
||
|
|
if (eee_cap < 0)
|
||
|
|
goto exit;
|
||
|
|
eee_cap &= ~MDIO_EEE_100TX;
|
||
|
|
|
||
|
|
cap = mmd_eee_cap_to_ethtool_sup_t(eee_cap);
|
||
|
|
if (!cap)
|
||
|
|
goto exit;
|
||
|
|
|
||
|
|
eee_lp = ax_mmd_read(axdev->netdev,
|
||
|
|
MDIO_MMD_AN, MDIO_AN_EEE_LPABLE);
|
||
|
|
if (eee_lp < 0)
|
||
|
|
goto exit;
|
||
|
|
|
||
|
|
eee_adv = ax_mmd_read(axdev->netdev,
|
||
|
|
MDIO_MMD_AN, MDIO_AN_EEE_ADV);
|
||
|
|
if (eee_adv < 0)
|
||
|
|
goto exit;
|
||
|
|
|
||
|
|
adv = mmd_eee_adv_to_ethtool_adv_t(eee_adv);
|
||
|
|
lp = mmd_eee_adv_to_ethtool_adv_t(eee_lp);
|
||
|
|
if (!(lp & adv & SUPPORTED_1000baseT_Full))
|
||
|
|
goto exit;
|
||
|
|
|
||
|
|
axdev->eee_active = 1;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
exit:
|
||
|
|
axdev->eee_active = 0;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int ax88179a_ethtool_get_eee(struct ax_device *axdev,
|
||
|
|
struct ethtool_eee *data)
|
||
|
|
{
|
||
|
|
int val;
|
||
|
|
|
||
|
|
val = ax_mmd_read(axdev->netdev, MDIO_MMD_PCS, MDIO_PCS_EEE_ABLE);
|
||
|
|
if (val < 0)
|
||
|
|
return val;
|
||
|
|
val &= ~MDIO_EEE_100TX;
|
||
|
|
data->supported = mmd_eee_cap_to_ethtool_sup_t(val);
|
||
|
|
|
||
|
|
val = ax_mmd_read(axdev->netdev, MDIO_MMD_AN, MDIO_AN_EEE_ADV);
|
||
|
|
if (val < 0)
|
||
|
|
return val;
|
||
|
|
data->advertised = mmd_eee_adv_to_ethtool_adv_t(val);
|
||
|
|
|
||
|
|
val = ax_mmd_read(axdev->netdev, MDIO_MMD_AN, MDIO_AN_EEE_LPABLE);
|
||
|
|
if (val < 0)
|
||
|
|
return val;
|
||
|
|
data->lp_advertised = mmd_eee_adv_to_ethtool_adv_t(val);
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int ax88179a_get_eee(struct net_device *net, struct ethtool_eee *edata)
|
||
|
|
{
|
||
|
|
struct ax_device *axdev = netdev_priv(net);
|
||
|
|
|
||
|
|
edata->eee_enabled = axdev->eee_enabled;
|
||
|
|
edata->eee_active = axdev->eee_active;
|
||
|
|
|
||
|
|
return ax88179a_ethtool_get_eee(axdev, edata);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void ax88179a_eee_setting(struct ax_device *axdev, bool enable)
|
||
|
|
{
|
||
|
|
ax_write_cmd(axdev, AX88179_GPHY_CTRL, AX_GPHY_EEE_CTRL,
|
||
|
|
enable, 0, NULL);
|
||
|
|
}
|
||
|
|
|
||
|
|
static int ax88179a_set_eee(struct net_device *net, struct ethtool_eee *edata)
|
||
|
|
{
|
||
|
|
struct ax_device *axdev = netdev_priv(net);
|
||
|
|
|
||
|
|
if (edata->advertised & MDIO_EEE_100TX)
|
||
|
|
return -EOPNOTSUPP;
|
||
|
|
|
||
|
|
axdev->eee_enabled = edata->eee_enabled;
|
||
|
|
ax88179a_eee_setting(axdev, axdev->eee_enabled);
|
||
|
|
if (axdev->eee_enabled) {
|
||
|
|
axdev->eee_enabled = ax88179a_chk_eee(axdev);
|
||
|
|
if (!axdev->eee_enabled) {
|
||
|
|
ax88179a_eee_setting(axdev, false);
|
||
|
|
return -EOPNOTSUPP;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
mii_nway_restart(&axdev->mii);
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int ax88179a_get_coalesce(struct net_device *netdev,
|
||
|
|
struct ethtool_coalesce *coalesce,
|
||
|
|
struct kernel_ethtool_coalesce *kernel_coal,
|
||
|
|
struct netlink_ext_ack *extack)
|
||
|
|
{
|
||
|
|
struct ax_device *axdev = netdev_priv(netdev);
|
||
|
|
|
||
|
|
coalesce->rx_coalesce_usecs = axdev->coalesce;
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static u16 ax88179a_usec_to_bin_timer(struct ax_device *axdev)
|
||
|
|
{
|
||
|
|
u16 speed_multiple;
|
||
|
|
|
||
|
|
switch (axdev->link_info.eth_speed) {
|
||
|
|
case ETHER_LINK_10:
|
||
|
|
speed_multiple = 100;
|
||
|
|
break;
|
||
|
|
case ETHER_LINK_100:
|
||
|
|
speed_multiple = 10;
|
||
|
|
break;
|
||
|
|
case ETHER_LINK_1000:
|
||
|
|
default:
|
||
|
|
speed_multiple = 1;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
return (axdev->coalesce * US_TO_NS * speed_multiple) /
|
||
|
|
AX88179A_BIN_TIMER_UINT;
|
||
|
|
}
|
||
|
|
|
||
|
|
static u32 ax88179a_bin_timer_to_usec(struct ax_device *axdev, u16 timer)
|
||
|
|
{
|
||
|
|
u16 speed_multiple;
|
||
|
|
|
||
|
|
switch (axdev->link_info.eth_speed) {
|
||
|
|
case ETHER_LINK_10:
|
||
|
|
speed_multiple = 100;
|
||
|
|
break;
|
||
|
|
case ETHER_LINK_100:
|
||
|
|
speed_multiple = 10;
|
||
|
|
break;
|
||
|
|
case ETHER_LINK_1000:
|
||
|
|
default:
|
||
|
|
speed_multiple = 1;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
return (AX88179A_BIN_TIMER_UINT * timer) / (US_TO_NS * speed_multiple);
|
||
|
|
}
|
||
|
|
|
||
|
|
static int ax88179a_set_coalesce(struct net_device *netdev,
|
||
|
|
struct ethtool_coalesce *coalesce,
|
||
|
|
struct kernel_ethtool_coalesce *kernel_coal,
|
||
|
|
struct netlink_ext_ack *extack)
|
||
|
|
{
|
||
|
|
struct ax_device *axdev = netdev_priv(netdev);
|
||
|
|
u16 timer;
|
||
|
|
int ret = 0;
|
||
|
|
|
||
|
|
ret = usb_autopm_get_interface(axdev->intf);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
axdev->coalesce = coalesce->rx_coalesce_usecs;
|
||
|
|
|
||
|
|
timer = ax88179a_usec_to_bin_timer(axdev);
|
||
|
|
if (timer > 0) {
|
||
|
|
timer &= 0x7FFF;
|
||
|
|
ax_write_cmd(axdev, AX_ACCESS_MAC, AX_RX_BULKIN_QTIMR_LOW, 2, 2, &timer);
|
||
|
|
}
|
||
|
|
|
||
|
|
usb_autopm_put_interface(axdev->intf);
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
const struct ethtool_ops ax88179a_ethtool_ops = {
|
||
|
|
.supported_coalesce_params = ETHTOOL_COALESCE_RX_USECS,
|
||
|
|
.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_eee = ax88179a_get_eee,
|
||
|
|
.set_eee = ax88179a_set_eee,
|
||
|
|
.get_coalesce = ax88179a_get_coalesce,
|
||
|
|
.set_coalesce = ax88179a_set_coalesce,
|
||
|
|
.get_strings = ax_get_strings,
|
||
|
|
.get_sset_count = ax_get_sset_count,
|
||
|
|
.get_ethtool_stats = ax_get_ethtool_stats,
|
||
|
|
.get_pauseparam = ax_get_pauseparam,
|
||
|
|
.set_pauseparam = ax_set_pauseparam,
|
||
|
|
.get_regs_len = ax_get_regs_len,
|
||
|
|
.get_regs = ax_get_regs,
|
||
|
|
.get_ts_info = ethtool_op_get_ts_info,
|
||
|
|
};
|
||
|
|
|
||
|
|
void ax88179a_get_fw_version(struct ax_device *axdev)
|
||
|
|
{
|
||
|
|
int i;
|
||
|
|
|
||
|
|
for (i = 0; i < 3; i++) {
|
||
|
|
if (ax_read_cmd(axdev, AX88179A_ACCESS_BL, (0xFD + i),
|
||
|
|
1, 1, &axdev->fw_version[i], 1) < 0)
|
||
|
|
axdev->fw_version[i] = ~0;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (ax_read_cmd(axdev, AX88179A_ACCESS_BL, AX88179A_SW_REVERSION,
|
||
|
|
1, 1, &axdev->fw_version[3], 0) < 0)
|
||
|
|
axdev->fw_version[3] = ~0;
|
||
|
|
else
|
||
|
|
axdev->fw_version[3] &= 0xF;
|
||
|
|
}
|
||
|
|
|
||
|
|
int ax88179a_signature(struct ax_device *axdev, struct _ax_ioctl_command *info)
|
||
|
|
{
|
||
|
|
strscpy(info->sig, AX88179A_SIGNATURE, sizeof(info->sig));
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
int ax88179a_read_version(struct ax_device *axdev,
|
||
|
|
struct _ax_ioctl_command *info)
|
||
|
|
{
|
||
|
|
unsigned char temp[16] = {0};
|
||
|
|
|
||
|
|
snprintf(temp, sizeof(temp), "v%d.%d.%d.%d", axdev->fw_version[0],
|
||
|
|
axdev->fw_version[1], axdev->fw_version[2], axdev->fw_version[3]);
|
||
|
|
memcpy(&info->version.version, temp, 16);
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
int ax88179a_write_flash(struct ax_device *axdev,
|
||
|
|
struct _ax_ioctl_command *info)
|
||
|
|
{
|
||
|
|
int i, ret;
|
||
|
|
u8 *buf = NULL;
|
||
|
|
u8 *block;
|
||
|
|
|
||
|
|
buf = kzalloc(256, GFP_KERNEL);
|
||
|
|
if (!buf)
|
||
|
|
return -ENOMEM;
|
||
|
|
block = buf;
|
||
|
|
|
||
|
|
ret = ax_write_cmd(axdev, AX88179A_FLASH_WEN, 0, 0, 0, NULL);
|
||
|
|
if (ret < 0) {
|
||
|
|
netdev_err(axdev->netdev, "Flash write enable failed");
|
||
|
|
info->flash.status = -ERR_FALSH_WRITE_EN;
|
||
|
|
goto out;
|
||
|
|
}
|
||
|
|
|
||
|
|
for (i = info->flash.offset;
|
||
|
|
i < (info->flash.length + info->flash.offset);
|
||
|
|
i += 256) {
|
||
|
|
if (copy_from_user(block,
|
||
|
|
(void __user *)&info->flash.buf[i], 256)) {
|
||
|
|
ret = -EFAULT;
|
||
|
|
goto out;
|
||
|
|
}
|
||
|
|
|
||
|
|
ret = ax_write_cmd(axdev, AX88179A_FLASH_WRITE,
|
||
|
|
(u16)((i >> 16) & 0xFFFF),
|
||
|
|
(u16)(i & 0xFFFF), 256, block);
|
||
|
|
if (ret < 0) {
|
||
|
|
info->flash.status = -ERR_FALSH_WRITE;
|
||
|
|
goto out;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
ret = ax_write_cmd(axdev, AX88179A_FLASH_WDIS, 0, 0, 0, NULL);
|
||
|
|
if (ret < 0) {
|
||
|
|
netdev_err(axdev->netdev, "Flash write disable failed");
|
||
|
|
info->flash.status = -ERR_FALSH_WRITE_DIS;
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
out:
|
||
|
|
kfree(buf);
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
int ax88179a_read_flash(struct ax_device *axdev, struct _ax_ioctl_command *info)
|
||
|
|
{
|
||
|
|
int i, ret = 0;
|
||
|
|
void *buf = NULL;
|
||
|
|
u8 *block;
|
||
|
|
|
||
|
|
buf = kzalloc(256, GFP_KERNEL);
|
||
|
|
if (!buf)
|
||
|
|
return -ENOMEM;
|
||
|
|
block = buf;
|
||
|
|
|
||
|
|
for (i = info->flash.offset;
|
||
|
|
i < (info->flash.length + info->flash.offset);
|
||
|
|
i += 256) {
|
||
|
|
ret = ax_read_cmd(axdev, AX88179A_FLASH_READ,
|
||
|
|
(u16)((i >> 16) & 0xFFFF),
|
||
|
|
(u16)(i & 0xFFFF), 256, block, 0);
|
||
|
|
if (ret < 0) {
|
||
|
|
info->flash.status = -ERR_FALSH_READ;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (copy_to_user((void __user *)&info->flash.buf[i],
|
||
|
|
block, 256)) {
|
||
|
|
ret = -EFAULT;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
kfree(buf);
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
int ax88179a_program_efuse(struct ax_device *axdev,
|
||
|
|
struct _ax_ioctl_command *info)
|
||
|
|
{
|
||
|
|
int ret = 0;
|
||
|
|
u16 offset = (u16)(info->flash.offset * 16);
|
||
|
|
u8 buf[20] = {0};
|
||
|
|
|
||
|
|
ret = copy_from_user(buf, (void __user *)info->flash.buf, 20);
|
||
|
|
if (ret)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
ret = ax_write_cmd(axdev, AX_ACCESS_EFUSE, offset, 0, 20, buf);
|
||
|
|
if (ret < 0) {
|
||
|
|
info->flash.status = -ERR_EFUSE_WRITE;
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
int ax88179a_dump_efuse(struct ax_device *axdev, struct _ax_ioctl_command *info)
|
||
|
|
{
|
||
|
|
int ret = 0;
|
||
|
|
u16 offset = (u16)(info->flash.offset * 16);
|
||
|
|
u8 buf[20] = {0};
|
||
|
|
|
||
|
|
ret = ax_read_cmd(axdev, AX_ACCESS_EFUSE, offset, 0, 20, buf, 0);
|
||
|
|
if (ret < 0) {
|
||
|
|
info->flash.status = -ERR_EFUSE_READ;
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
ret = copy_to_user((void __user *)info->flash.buf, buf, 20);
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
int ax88179a_boot_to_rom(struct ax_device *axdev,
|
||
|
|
struct _ax_ioctl_command *info)
|
||
|
|
{
|
||
|
|
usb_control_msg(axdev->udev, usb_sndctrlpipe(axdev->udev, 0),
|
||
|
|
AX88179A_BOOT_TO_ROM,
|
||
|
|
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
|
||
|
|
0x5A5A, 0xA5A5, NULL, 0, 1);
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
int ax88179a_erase_flash(struct ax_device *axdev,
|
||
|
|
struct _ax_ioctl_command *info)
|
||
|
|
{
|
||
|
|
int ret = 0;
|
||
|
|
|
||
|
|
ret = ax_write_cmd(axdev, AX88179A_FLASH_WEN, 0, 0, 0, NULL);
|
||
|
|
if (ret < 0) {
|
||
|
|
netdev_err(axdev->netdev, "Flash write enable failed");
|
||
|
|
info->flash.status = -ERR_FALSH_WRITE_EN;
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
ret = usb_control_msg(axdev->udev, usb_sndctrlpipe(axdev->udev, 0),
|
||
|
|
AX88179A_FLASH_EARSE_ALL,
|
||
|
|
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
|
||
|
|
0, 0, NULL, 0, 300000);
|
||
|
|
if (ret < 0) {
|
||
|
|
netdev_err(axdev->netdev, "Flash erase all failed");
|
||
|
|
info->flash.status = -ERR_FALSH_ERASE_ALL;
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
ret = ax_write_cmd(axdev, AX88179A_FLASH_WDIS, 0, 0, 0, NULL);
|
||
|
|
if (ret < 0) {
|
||
|
|
netdev_err(axdev->netdev, "Flash write disable failed");
|
||
|
|
info->flash.status = -ERR_FALSH_WRITE_DIS;
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
int ax88179a_sw_reset(struct ax_device *axdev, struct _ax_ioctl_command *info)
|
||
|
|
{
|
||
|
|
void *buf = NULL;
|
||
|
|
|
||
|
|
buf = kzalloc(sizeof(u32), GFP_KERNEL);
|
||
|
|
if (!buf)
|
||
|
|
return -ENOMEM;
|
||
|
|
|
||
|
|
*((u32 *)buf) = 1;
|
||
|
|
|
||
|
|
usb_control_msg(axdev->udev, usb_sndctrlpipe(axdev->udev, 0), 0x10,
|
||
|
|
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
|
||
|
|
0x18E8, 0x000F, buf, 4, 10);
|
||
|
|
|
||
|
|
kfree(buf);
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
IOCTRL_TABLE ax88179a_tbl[] = {
|
||
|
|
ax88179a_signature,
|
||
|
|
ax_usb_command,
|
||
|
|
ax88179a_read_version,
|
||
|
|
ax88179a_write_flash,
|
||
|
|
ax88179a_boot_to_rom,
|
||
|
|
ax88179a_erase_flash,
|
||
|
|
ax88179a_sw_reset,
|
||
|
|
ax88179a_read_flash,
|
||
|
|
ax88179a_program_efuse,
|
||
|
|
ax88179a_dump_efuse,
|
||
|
|
};
|
||
|
|
|
||
|
|
int ax88179a_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;
|
||
|
|
void __user *uptr = (void __user *)rq->ifr_data;
|
||
|
|
int ret = 0;
|
||
|
|
|
||
|
|
switch (cmd) {
|
||
|
|
case AX_PRIVATE:
|
||
|
|
if (copy_from_user(&info, uptr,
|
||
|
|
sizeof(struct _ax_ioctl_command))) {
|
||
|
|
netdev_err(netdev, "copy_from_user, return -EFAULT");
|
||
|
|
return -EFAULT;
|
||
|
|
}
|
||
|
|
|
||
|
|
ret = (*ax88179a_tbl[info.ioctl_cmd])(axdev, &info);
|
||
|
|
if (ret < 0) {
|
||
|
|
netdev_err(netdev, "ax88179a_tbl, return %d", ret);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (copy_to_user(uptr, &info,
|
||
|
|
sizeof(struct _ax_ioctl_command))) {
|
||
|
|
netdev_err(netdev, "copy_to_user, return -EFAULT");
|
||
|
|
return -EFAULT;
|
||
|
|
}
|
||
|
|
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
ret = -EOPNOTSUPP;
|
||
|
|
}
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
int ax88179a_ioctl(struct net_device *netdev, struct ifreq *rq, int cmd)
|
||
|
|
{
|
||
|
|
struct ax_device *axdev = netdev_priv(netdev);
|
||
|
|
|
||
|
|
switch (cmd) {
|
||
|
|
}
|
||
|
|
return generic_mii_ioctl(&axdev->mii, if_mii(rq), cmd, NULL);
|
||
|
|
}
|
||
|
|
|
||
|
|
static int ax88179a_autodetach(struct ax_device *axdev)
|
||
|
|
{
|
||
|
|
u16 value = ((axdev->autodetach) ? 1 : 0) | AX88179A_AUTODETACH_DELAY;
|
||
|
|
|
||
|
|
return ax_write_cmd(axdev, AX88179A_AUTODETACH, value, 0, 0, NULL);
|
||
|
|
}
|
||
|
|
|
||
|
|
static bool ax88179a_check_phy_power(struct ax_device *axdev)
|
||
|
|
{
|
||
|
|
u8 reg8 = 0;
|
||
|
|
int ret = 0;
|
||
|
|
|
||
|
|
ret = ax_read_cmd_nopm(axdev, AX88179A_PHY_POWER, 0, 0, 1, ®8, 1);
|
||
|
|
if (ret < 0)
|
||
|
|
return false;
|
||
|
|
|
||
|
|
return (reg8 & AX_PHY_POWER);
|
||
|
|
}
|
||
|
|
|
||
|
|
static int ax88179a_set_phy_power(struct ax_device *axdev, bool on)
|
||
|
|
{
|
||
|
|
u8 reg8;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
reg8 = (on) ? AX_PHY_POWER : 0;
|
||
|
|
ret = ax_write_cmd_nopm(axdev, AX88179A_PHY_POWER, 0, 0, 1, ®8);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
msleep(250);
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int ax88179a_bind(struct ax_device *axdev)
|
||
|
|
{
|
||
|
|
struct net_device *netdev = axdev->netdev;
|
||
|
|
u16 wvalue = 0;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
ax88179a_get_fw_version(axdev);
|
||
|
|
ax_print_version(axdev, AX_DRIVER_STRING_179A_772D);
|
||
|
|
|
||
|
|
ret = ax_write_cmd(axdev, AX_FW_MODE, AX_FW_MODE_179A, wvalue, 0, NULL);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
ret = ax_write_cmd(axdev, AX_RELOAD_FLASH_EFUSE, 0, 0, 0, NULL);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
netdev->features |= NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM |
|
||
|
|
NETIF_F_SG | NETIF_F_TSO | NETIF_F_FRAGLIST |
|
||
|
|
NETIF_F_HW_VLAN_CTAG_RX |
|
||
|
|
NETIF_F_HW_VLAN_CTAG_TX;
|
||
|
|
netdev->hw_features |= NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM |
|
||
|
|
NETIF_F_SG | NETIF_F_TSO | NETIF_F_FRAGLIST |
|
||
|
|
NETIF_F_HW_VLAN_CTAG_RX |
|
||
|
|
NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_RXALL;
|
||
|
|
netdev->vlan_features = NETIF_F_SG | NETIF_F_IP_CSUM | NETIF_F_TSO |
|
||
|
|
NETIF_F_HIGHDMA | NETIF_F_FRAGLIST |
|
||
|
|
NETIF_F_IPV6_CSUM;
|
||
|
|
|
||
|
|
axdev->tx_casecade_size = TX_CASECADES_SIZE;
|
||
|
|
axdev->gso_max_size = AX_GSO_DEFAULT_SIZE;
|
||
|
|
axdev->mii.supports_gmii = true;
|
||
|
|
axdev->mii.dev = netdev;
|
||
|
|
axdev->mii.mdio_read = ax_mdio_read;
|
||
|
|
axdev->mii.mdio_write = ax_mdio_write;
|
||
|
|
axdev->mii.phy_id_mask = 0x3F;
|
||
|
|
axdev->mii.reg_num_mask = 0x1F;
|
||
|
|
axdev->mii.phy_id = AX88179A_PHY_ID;
|
||
|
|
|
||
|
|
netif_set_gso_max_size(netdev, axdev->gso_max_size);
|
||
|
|
|
||
|
|
axdev->bin_setting.custom = false;
|
||
|
|
axdev->tx_align_len = 8;
|
||
|
|
axdev->coalesce = 0;
|
||
|
|
|
||
|
|
netdev->ethtool_ops = &ax88179a_ethtool_ops;
|
||
|
|
axdev->netdev->netdev_ops = &ax88179a_netdev_ops;
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void ax88179a_unbind(struct ax_device *axdev)
|
||
|
|
{
|
||
|
|
}
|
||
|
|
|
||
|
|
static int ax88179a_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);
|
||
|
|
ax88179a_set_phy_power(axdev, false);
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
void ax88179a_set_multicast(struct net_device *netdev)
|
||
|
|
{
|
||
|
|
struct ax_device *axdev = netdev_priv(netdev);
|
||
|
|
u8 *m_filter = axdev->m_filter;
|
||
|
|
int mc_count = 0;
|
||
|
|
|
||
|
|
if (!test_bit(AX_ENABLE, &axdev->flags))
|
||
|
|
return;
|
||
|
|
|
||
|
|
mc_count = netdev_mc_count(netdev);
|
||
|
|
|
||
|
|
axdev->rxctl = (AX_RX_CTL_START | AX_RX_CTL_AB);
|
||
|
|
|
||
|
|
if (netdev->flags & IFF_PROMISC) {
|
||
|
|
axdev->rxctl |= AX_RX_CTL_PRO;
|
||
|
|
} else if (netdev->flags & IFF_ALLMULTI || mc_count > AX_MAX_MCAST) {
|
||
|
|
axdev->rxctl |= AX_RX_CTL_AMALL;
|
||
|
|
} else if (netdev_mc_empty(netdev)) {
|
||
|
|
} else {
|
||
|
|
u32 crc_bits;
|
||
|
|
struct netdev_hw_addr *ha = NULL;
|
||
|
|
|
||
|
|
memset(m_filter, 0, AX_MCAST_FILTER_SIZE);
|
||
|
|
netdev_for_each_mc_addr(ha, netdev) {
|
||
|
|
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);
|
||
|
|
}
|
||
|
|
|
||
|
|
static int ax88179a_hw_init(struct ax_device *axdev)
|
||
|
|
{
|
||
|
|
u16 reg16;
|
||
|
|
u8 reg8;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
ret = ax88179a_set_phy_power(axdev, true);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
msleep(250);
|
||
|
|
|
||
|
|
ret = ax88179a_autodetach(axdev);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
reg8 = AX_TXCOE_IP | AX_TXCOE_TCP | AX_TXCOE_UDP |
|
||
|
|
AX_TXCOE_TCPV6 | AX_TXCOE_UDPV6;
|
||
|
|
ret = ax_write_cmd(axdev, AX_ACCESS_MAC, AX_TXCOE_CTL, 1, 1, ®8);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
reg8 = AX_RXCOE_IP | AX_RXCOE_TCP | AX_RXCOE_UDP |
|
||
|
|
AX_RXCOE_TCPV6 | AX_RXCOE_UDPV6;
|
||
|
|
ret = ax_write_cmd(axdev, AX_ACCESS_MAC, AX_RXCOE_CTL, 1, 1, ®8);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
reg8 = AX_MAC_EFF_EN;
|
||
|
|
ret = ax_write_cmd(axdev, AX_ACCESS_MAC, AX88179A_MAC_BULK_OUT_CTRL,
|
||
|
|
1, 1, ®8);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
reg16 = 0;
|
||
|
|
ret = ax_write_cmd_async(axdev, AX_ACCESS_MAC, AX_RX_CTL, 2, 2, ®16);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
reg8 = 0x04;
|
||
|
|
ret = ax_write_cmd(axdev, AX_ACCESS_MAC, AX_PAUSE_WATERLVL_LOW, 1, 1, ®8);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
reg8 = 0x10;
|
||
|
|
ret = ax_write_cmd(axdev, AX_ACCESS_MAC, AX_PAUSE_WATERLVL_HIGH, 1, 1, ®8);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
reg8 = 0;
|
||
|
|
if (axdev->netdev->features & NETIF_F_HW_VLAN_CTAG_FILTER)
|
||
|
|
reg8 |= AX_VLAN_CONTROL_VFE;
|
||
|
|
if (axdev->netdev->features & NETIF_F_HW_VLAN_CTAG_RX)
|
||
|
|
reg8 |= AX_VLAN_CONTROL_VSO;
|
||
|
|
ret = ax_write_cmd(axdev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, ®8);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
reg8 = 0xff;
|
||
|
|
ret = ax_write_cmd(axdev, AX_ACCESS_MAC, AX88179A_MAC_BM_INT_MASK, 1, 1, ®8);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
reg8 = 0;
|
||
|
|
ret = ax_write_cmd(axdev, AX_ACCESS_MAC, AX88179A_MAC_BM_RX_DMA_CTL, 1, 1, ®8);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
ret = ax_write_cmd(axdev, AX_ACCESS_MAC, AX88179A_MAC_BM_TX_DMA_CTL, 1, 1, ®8);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
ret = ax_write_cmd(axdev, AX_ACCESS_MAC, AX88179A_MAC_ARC_CTRL, 1, 1, ®8);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
ret = ax_write_cmd(axdev, AX_ACCESS_MAC, AX88179A_MAC_SWP_CTRL, 1, 1, ®8);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
reg8 = AX_TXHDR_CKSUM_EN;
|
||
|
|
ret = ax_write_cmd_nopm(axdev, AX_ACCESS_MAC, AX88179A_MAC_TX_HDR_CKSUM, 1, 1, ®8);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
reg16 = AX_RX_CTL_START | AX_RX_CTL_AP | AX_RX_CTL_AMALL |
|
||
|
|
AX_RX_CTL_AB | AX_RX_CTL_DROPCRCERR;
|
||
|
|
ret = ax_write_cmd(axdev, AX_ACCESS_MAC, AX_RX_CTL, 1, 1, ®16);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
reg8 = 0;
|
||
|
|
ret = ax_write_cmd(axdev, AX_ACCESS_MAC, AX88179A_MAC_PATH, 1, 1, ®8);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
ret = ax_read_cmd(axdev, AX_ACCESS_MAC, AX_MONITOR_MODE, 1, 1, ®8, 1);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
reg8 &= 0xE0;
|
||
|
|
ret = ax_write_cmd(axdev, AX_ACCESS_MAC, AX_MONITOR_MODE, 1, 1, ®8);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
ret = ax_read_cmd(axdev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, 2, 2, ®16, 2);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
reg16 &= ~AX_MEDIUM_GIGAMODE;
|
||
|
|
ret = ax_write_cmd(axdev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, 2, 2, ®16);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
reg16 |= AX_MEDIUM_GIGAMODE;
|
||
|
|
ret = ax_write_cmd(axdev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, 2, 2, ®16);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
reg8 = 0;
|
||
|
|
ret = ax_write_cmd(axdev, AX_ACCESS_MAC, AX88179A_BFM_DATA, 1, 1, ®8);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
ret = ax_write_cmd(axdev, AX_ACCESS_MAC, AX88179A_CDC_ECM_CTRL, 1, 1, ®8);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
ax_set_tx_qlen(axdev);
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int ax88179a_get_ether_link(struct ax_device *axdev)
|
||
|
|
{
|
||
|
|
struct ax_link_info *link_info = &axdev->link_info;
|
||
|
|
struct ethtool_link_ksettings cmd;
|
||
|
|
|
||
|
|
mii_ethtool_get_link_ksettings(&axdev->mii, &cmd);
|
||
|
|
switch (cmd.base.speed) {
|
||
|
|
case SPEED_1000:
|
||
|
|
link_info->eth_speed = ETHER_LINK_1000;
|
||
|
|
break;
|
||
|
|
case SPEED_100:
|
||
|
|
link_info->eth_speed = ETHER_LINK_100;
|
||
|
|
break;
|
||
|
|
case SPEED_10:
|
||
|
|
default:
|
||
|
|
link_info->eth_speed = ETHER_LINK_10;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
link_info->full_duplex = cmd.base.duplex;
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int ax88179a_set_bulkin_setting(struct ax_device *axdev)
|
||
|
|
{
|
||
|
|
struct ax_link_info *link_info = &axdev->link_info;
|
||
|
|
u8 link_sts;
|
||
|
|
int index = 0, ret;
|
||
|
|
|
||
|
|
ret = ax_read_cmd_nopm(axdev, AX_ACCESS_MAC, PHYSICAL_LINK_STATUS, 1, 1, &link_sts, 0);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
switch (link_info->eth_speed) {
|
||
|
|
case ETHER_LINK_1000:
|
||
|
|
if (link_sts & AX_USB_SS)
|
||
|
|
index = 0;
|
||
|
|
else if (link_sts & AX_USB_HS)
|
||
|
|
index = 1;
|
||
|
|
break;
|
||
|
|
case ETHER_LINK_100:
|
||
|
|
if (link_sts & AX_USB_SS)
|
||
|
|
index = 2;
|
||
|
|
else if (link_sts & AX_USB_HS)
|
||
|
|
index = 4;
|
||
|
|
|
||
|
|
if (!link_info->full_duplex)
|
||
|
|
index++;
|
||
|
|
break;
|
||
|
|
case ETHER_LINK_10:
|
||
|
|
index = 6;
|
||
|
|
break;
|
||
|
|
case ETHER_LINK_NONE:
|
||
|
|
default:
|
||
|
|
index = 0;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (axdev->coalesce == 0) {
|
||
|
|
u16 timer = *((u16 *)&AX88179A_BULKIN_SIZE[index].timer_l);
|
||
|
|
|
||
|
|
axdev->coalesce = ax88179a_bin_timer_to_usec(axdev, timer);
|
||
|
|
} else {
|
||
|
|
u16 timer = ax88179a_usec_to_bin_timer(axdev);
|
||
|
|
|
||
|
|
memcpy(&AX88179A_BULKIN_SIZE[index].timer_l, &timer, 2);
|
||
|
|
}
|
||
|
|
|
||
|
|
ret = ax_write_cmd_nopm(axdev, AX_ACCESS_MAC, AX_RX_BULKIN_QCTRL,
|
||
|
|
5, 5, &AX88179A_BULKIN_SIZE[index]);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int ax88179a_link_setting(struct ax_device *axdev)
|
||
|
|
{
|
||
|
|
struct ax_link_info *link_info = &axdev->link_info;
|
||
|
|
struct ethtool_pauseparam pause;
|
||
|
|
u16 medium_mode, reg16;
|
||
|
|
u8 reg8[3];
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
reg16 = AX_RX_CTL_STOP;
|
||
|
|
ret = ax_write_cmd_nopm(axdev, AX_ACCESS_MAC, AX_RX_CTL, 2, 2, ®16);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
reg8[0] = 0;
|
||
|
|
ret = ax_write_cmd_nopm(axdev, AX_ACCESS_MAC, AX88179A_MAC_PATH, 1, 1, reg8);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
reg8[0] = 0xA5;
|
||
|
|
ret = ax_write_cmd_nopm(axdev, AX_ACCESS_MAC, AX88179A_MAC_CDC_DELAY_TX, 1, 1, reg8);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
reg8[0] = 0x10;
|
||
|
|
reg8[1] = 0x04;
|
||
|
|
ret = ax_write_cmd_nopm(axdev, AX_ACCESS_MAC, AX_PAUSE_WATERLVL_HIGH, 2, 2, reg8);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
memset(&pause, 0, sizeof(pause));
|
||
|
|
ax_get_pauseparam(axdev->netdev, &pause);
|
||
|
|
medium_mode = AX_MEDIUM_RECEIVE_EN;
|
||
|
|
if (pause.rx_pause)
|
||
|
|
medium_mode |= AX_MEDIUM_RXFLOW_CTRLEN;
|
||
|
|
|
||
|
|
if (pause.tx_pause) {
|
||
|
|
medium_mode |= AX_MEDIUM_TXFLOW_CTRLEN;
|
||
|
|
reg8[0] = 0x28 | AX_NEW_PAUSE_EN;
|
||
|
|
} else {
|
||
|
|
reg8[0] = 0;
|
||
|
|
}
|
||
|
|
ret = ax_write_cmd(axdev, AX_ACCESS_MAC, AX88179A_NEW_PAUSE_CTRL,
|
||
|
|
1, 1, reg8);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
switch (link_info->eth_speed) {
|
||
|
|
case ETHER_LINK_1000:
|
||
|
|
case ETHER_LINK_100:
|
||
|
|
reg8[0] = 0x78;
|
||
|
|
reg8[1] = (AX_LSOFC_WCNT_7_ACCESS << 5) | AX_GMII_CRC_APPEND;
|
||
|
|
reg8[2] = 0;
|
||
|
|
ret = ax_write_cmd_nopm(axdev, AX_ACCESS_MAC,
|
||
|
|
AX88178A_MAC_RX_STATUS_CDC, 3, 3, reg8);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
reg8[0] = 0x40;
|
||
|
|
ret = ax_write_cmd_nopm(axdev, AX_ACCESS_MAC,
|
||
|
|
AX88179A_MAC_RX_DATA_CDC_CNT,
|
||
|
|
1, 1, reg8);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
if (link_info->eth_speed == ETHER_LINK_1000)
|
||
|
|
medium_mode |= AX_MEDIUM_GIGAMODE;
|
||
|
|
break;
|
||
|
|
case ETHER_LINK_10:
|
||
|
|
reg8[0] = 0xFA;
|
||
|
|
reg8[1] = (AX_LSOFC_WCNT_7_ACCESS << 5) | AX_GMII_CRC_APPEND;
|
||
|
|
reg8[2] = 0xFF;
|
||
|
|
ret = ax_write_cmd_nopm(axdev, AX_ACCESS_MAC,
|
||
|
|
AX88178A_MAC_RX_STATUS_CDC, 3, 3, reg8);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
reg8[0] = 0xFA;
|
||
|
|
ret = ax_write_cmd_nopm(axdev, AX_ACCESS_MAC,
|
||
|
|
AX88179A_MAC_RX_DATA_CDC_CNT,
|
||
|
|
1, 1, reg8);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
reg8[0] = 0;
|
||
|
|
ret = ax_write_cmd_nopm(axdev, AX_ACCESS_MAC, AX88179A_BFM_DATA,
|
||
|
|
1, 1, reg8);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
ret = ax88179a_set_bulkin_setting(axdev);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
if (link_info->full_duplex)
|
||
|
|
medium_mode |= AX_MEDIUM_FULL_DUPLEX;
|
||
|
|
ret = ax_write_cmd_nopm(axdev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE,
|
||
|
|
2, 2, &medium_mode);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
axdev->rxctl |= AX_RX_CTL_START | AX_RX_CTL_AB;
|
||
|
|
ret = ax_write_cmd_nopm(axdev, AX_ACCESS_MAC, AX_RX_CTL,
|
||
|
|
2, 2, &axdev->rxctl);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
reg8[0] = AX_MAC_RX_PATH_READY | AX_MAC_TX_PATH_READY;
|
||
|
|
ret = ax_write_cmd_nopm(axdev, AX_ACCESS_MAC, AX88179A_MAC_PATH,
|
||
|
|
1, 1, reg8);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int ax88179a_link_reset(struct ax_device *axdev)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
ret = ax88179a_get_ether_link(axdev);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
ret = ax88179a_link_setting(axdev);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
axdev->eee_enabled = ax88179a_chk_eee(axdev);
|
||
|
|
ax88179a_eee_setting(axdev, axdev->eee_enabled);
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
inline void ax88179a_rx_checksum(struct sk_buff *skb, void *pkt_hdr)
|
||
|
|
{
|
||
|
|
struct _179a_rx_pkt_header *hdr = (struct _179a_rx_pkt_header *)pkt_hdr;
|
||
|
|
|
||
|
|
skb->ip_summed = CHECKSUM_NONE;
|
||
|
|
|
||
|
|
if (hdr->L4_err || hdr->L3_err)
|
||
|
|
return;
|
||
|
|
|
||
|
|
if (hdr->L4_pkt_type == AX_RXHDR_L4_TYPE_TCP || hdr->L4_pkt_type == AX_RXHDR_L4_TYPE_UDP)
|
||
|
|
skb->ip_summed = CHECKSUM_UNNECESSARY;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void ax88179a_rx_fixup(struct ax_device *axdev, struct rx_desc *desc,
|
||
|
|
int *work_done, int budget)
|
||
|
|
{
|
||
|
|
struct napi_struct *napi = &axdev->napi;
|
||
|
|
struct net_device *netdev = axdev->netdev;
|
||
|
|
struct net_device_stats *stats = ax_get_stats(netdev);
|
||
|
|
struct _179a_rx_pkt_header *pkt_hdr;
|
||
|
|
struct _179a_rx_header *rx_header;
|
||
|
|
const u32 actual_length = desc->urb->actual_length;
|
||
|
|
u8 *rx_data;
|
||
|
|
u32 aa = 0, rx_hdroffset = 0;
|
||
|
|
u16 pkt_count = 0;
|
||
|
|
|
||
|
|
rx_header = (struct _179a_rx_header *)
|
||
|
|
(((u8 *)desc->head) + actual_length - AX88179A_RX_HEADER_SIZE);
|
||
|
|
le64_to_cpus(rx_header);
|
||
|
|
|
||
|
|
rx_hdroffset = rx_header->hdr_off;
|
||
|
|
pkt_count = rx_header->pkt_cnt;
|
||
|
|
aa = (actual_length - ((pkt_count + 1) * 8));
|
||
|
|
if ((aa != rx_hdroffset && ((aa - rx_hdroffset) % 16) != 0) ||
|
||
|
|
rx_hdroffset >= desc->urb->actual_length ||
|
||
|
|
pkt_count == 0) {
|
||
|
|
desc->urb->actual_length = 0;
|
||
|
|
stats->rx_length_errors++;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
pkt_hdr = (struct _179a_rx_pkt_header *)
|
||
|
|
(((u8 *)desc->head) + rx_header->hdr_off);
|
||
|
|
|
||
|
|
rx_data = desc->head;
|
||
|
|
while (pkt_count--) {
|
||
|
|
struct sk_buff *skb;
|
||
|
|
u32 pkt_len = 0;
|
||
|
|
|
||
|
|
le64_to_cpus(pkt_hdr);
|
||
|
|
|
||
|
|
if (!pkt_hdr->rx_ok) {
|
||
|
|
stats->rx_crc_errors++;
|
||
|
|
if (!(netdev->features & NETIF_F_RXALL))
|
||
|
|
goto find_next_rx;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (pkt_hdr->drop) {
|
||
|
|
stats->rx_dropped++;
|
||
|
|
if (!(netdev->features & NETIF_F_RXALL))
|
||
|
|
goto find_next_rx;
|
||
|
|
}
|
||
|
|
|
||
|
|
pkt_len = (u32)(pkt_hdr->length & 0x7FFF);
|
||
|
|
|
||
|
|
skb = napi_alloc_skb(napi, pkt_len);
|
||
|
|
if (!skb) {
|
||
|
|
stats->rx_dropped++;
|
||
|
|
goto find_next_rx;
|
||
|
|
}
|
||
|
|
|
||
|
|
skb_put(skb, pkt_len);
|
||
|
|
memcpy(skb->data, rx_data, pkt_len);
|
||
|
|
ax88179a_rx_checksum(skb, pkt_hdr);
|
||
|
|
|
||
|
|
skb->truesize = skb->len + sizeof(struct sk_buff);
|
||
|
|
|
||
|
|
if (pkt_hdr->vlan_ind)
|
||
|
|
__vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q),
|
||
|
|
pkt_hdr->vlan_tag & VLAN_VID_MASK);
|
||
|
|
|
||
|
|
skb->protocol = eth_type_trans(skb, netdev);
|
||
|
|
if (*work_done < budget) {
|
||
|
|
napi_gro_receive(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) & 0x7FFF8;
|
||
|
|
pkt_hdr++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static int ax88179a_tx_fixup(struct ax_device *axdev, struct tx_desc *desc)
|
||
|
|
{
|
||
|
|
struct sk_buff_head skb_head, *tx_queue;
|
||
|
|
struct net_device_stats *stats = &axdev->netdev->stats;
|
||
|
|
int remain, ret;
|
||
|
|
int endpoint = 3;
|
||
|
|
u8 *tx_data;
|
||
|
|
|
||
|
|
tx_queue = axdev->tx_queue;
|
||
|
|
|
||
|
|
__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 + AX88179A_TX_HEADER_SIZE)) {
|
||
|
|
struct sk_buff *skb;
|
||
|
|
struct _179a_tx_pkt_header *tx_hdr;
|
||
|
|
u16 tci = 0;
|
||
|
|
|
||
|
|
skb = __skb_dequeue(&skb_head);
|
||
|
|
if (!skb)
|
||
|
|
break;
|
||
|
|
|
||
|
|
if ((skb->len + AX88179A_TX_HEADER_SIZE) > remain &&
|
||
|
|
(skb_shinfo(skb)->gso_size == 0)) {
|
||
|
|
__skb_queue_head(&skb_head, skb);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
tx_hdr = (struct _179a_tx_pkt_header *)tx_data;
|
||
|
|
memset(tx_hdr, 0, AX88179A_TX_HEADER_SIZE);
|
||
|
|
tx_hdr->length = (skb->len & 0x1FFFFF);
|
||
|
|
tx_hdr->checksum = AX88179A_TX_HERDER_CHKSUM(tx_hdr->length);
|
||
|
|
tx_hdr->max_seg_size = skb_shinfo(skb)->gso_size;
|
||
|
|
if ((axdev->netdev->features & NETIF_F_HW_VLAN_CTAG_TX) &&
|
||
|
|
(vlan_get_tag(skb, &tci) >= 0)) {
|
||
|
|
tx_hdr->vlan_tag = 1;
|
||
|
|
tx_hdr->vlan_info = tci;
|
||
|
|
}
|
||
|
|
|
||
|
|
cpu_to_le64s(tx_hdr);
|
||
|
|
tx_data += AX88179A_TX_HEADER_SIZE;
|
||
|
|
|
||
|
|
if (skb_copy_bits(skb, 0, tx_data, skb->len) < 0) {
|
||
|
|
stats->tx_dropped += skb_shinfo(skb)->gso_segs ?: 1;
|
||
|
|
dev_kfree_skb_any(skb);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
tx_data = __tx_buf_align((void *)(tx_data + skb->len),
|
||
|
|
axdev->tx_align_len);
|
||
|
|
desc->skb_len += skb->len;
|
||
|
|
desc->skb_num += skb_shinfo(skb)->gso_segs ?: 1;
|
||
|
|
dev_kfree_skb_any(skb);
|
||
|
|
|
||
|
|
if (tx_hdr->max_seg_size)
|
||
|
|
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)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
usb_fill_bulk_urb(desc->urb, axdev->udev,
|
||
|
|
usb_sndbulkpipe(axdev->udev, endpoint),
|
||
|
|
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);
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int ax88179a_system_suspend(struct ax_device *axdev)
|
||
|
|
{
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int ax88179a_system_resume(struct ax_device *axdev)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
if (!ax88179a_check_phy_power(axdev))
|
||
|
|
ax88179a_set_phy_power(axdev, true);
|
||
|
|
|
||
|
|
ret = ax_write_cmd_nopm(axdev, AX_FW_MODE, AX_FW_MODE_179A, 0, 0, NULL);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
axdev->driver_info->hw_init(axdev);
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
const struct driver_info ax88179a_info = {
|
||
|
|
.bind = ax88179a_bind,
|
||
|
|
.unbind = ax88179a_unbind,
|
||
|
|
.hw_init = ax88179a_hw_init,
|
||
|
|
.stop = ax88179a_stop,
|
||
|
|
.link_reset = ax88179a_link_reset,
|
||
|
|
.rx_fixup = ax88179a_rx_fixup,
|
||
|
|
.tx_fixup = ax88179a_tx_fixup,
|
||
|
|
.system_suspend = ax88179a_system_suspend,
|
||
|
|
.system_resume = ax88179a_system_resume,
|
||
|
|
.napi_weight = AX88179A_NAPI_WEIGHT,
|
||
|
|
.buf_rx_size = AX88179A_BUF_RX_SIZE,
|
||
|
|
};
|
||
|
|
|
||
|
|
const struct driver_info ax88772d_info = {
|
||
|
|
.bind = ax88179a_bind,
|
||
|
|
.unbind = ax88179a_unbind,
|
||
|
|
.hw_init = ax88179a_hw_init,
|
||
|
|
.stop = ax88179a_stop,
|
||
|
|
.link_reset = ax88179a_link_reset,
|
||
|
|
.rx_fixup = ax88179a_rx_fixup,
|
||
|
|
.tx_fixup = ax88179a_tx_fixup,
|
||
|
|
.system_suspend = ax88179a_system_suspend,
|
||
|
|
.system_resume = ax88179a_system_resume,
|
||
|
|
.napi_weight = AX88179A_NAPI_WEIGHT,
|
||
|
|
.buf_rx_size = AX88179A_BUF_RX_SIZE,
|
||
|
|
};
|