// 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 . ******************************************************************************/ #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, };