The AX88179A-based chips support VLAN offload. Add configuration support in netdev_ops. Features supported are: NETIF_F_HW_VLAN_CTAG_TX, NETIF_F_HW_VLAN_CTAG_RX and NETIF_F_HW_VLAN_CTAG_FILTER. Signed-off-by: Birger Koblitz Reviewed-by: Andrew Lunn Error-checking fixes Suggested-by: Jianhui Xu --- drivers/net/usb/ax88179a_devices.c | 158 ++++++++++++++++++++++++++++++++++++- 1 file changed, 157 insertions(+), 1 deletion(-) diff --git a/drivers/net/usb/ax88179a_devices.c b/drivers/net/usb/ax88179a_devices.c index 272e92365683247b42a5fd25013c728a9c1af711..4207d2cc2b865e5780b3c7f5ba2645a6242df477 100644 --- a/drivers/net/usb/ax88179a_devices.c +++ b/drivers/net/usb/ax88179a_devices.c @@ -258,6 +258,88 @@ static const struct ethtool_ops ax88179a_ethtool_ops = { .get_ts_info = ethtool_op_get_ts_info, }; +static int ax88179a_vlan_rx_kill_vid(struct net_device *net, __be16 proto, u16 vid) +{ + struct usbnet *dev = netdev_priv(net); + u16 reg16 = 0; + u8 vlan_ctrl; + int ret = 0; + u8 reg8 = 0; + + ret = ax88179_read_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, ®8); + if (ret < 0) + return ret; + vlan_ctrl = reg8; + + /* Address */ + reg8 = (vid / 16); + ret = ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_ADDRESS, 1, 1, ®8); + if (ret < 0) + return ret; + + /* Data */ + reg8 = vlan_ctrl | AX_VLAN_CONTROL_RD; + ret = ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, ®8); + if (ret < 0) + return ret; + + ret = ax88179_read_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_DATA0, 2, 2, ®16); + if (ret < 0) + return ret; + reg16 &= ~(1 << (vid % 16)); + ret = ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_DATA0, 2, 2, ®16); + if (ret < 0) + return ret; + + reg8 = vlan_ctrl | AX_VLAN_CONTROL_WE; + ret = ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, ®8); + if (ret < 0) + return ret; + + return 0; +} + +static int ax88179a_vlan_rx_add_vid(struct net_device *net, __be16 proto, u16 vid) +{ + struct usbnet *dev = netdev_priv(net); + u8 vlan_ctrl; + int ret = 0; + u16 reg16; + u8 reg8; + + ret = ax88179_read_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, ®8); + if (ret < 0) + return ret; + vlan_ctrl = reg8; + + /* Address */ + reg8 = (vid / 16); + ret = ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_ADDRESS, 1, 1, ®8); + if (ret < 0) + return ret; + + /* Data */ + reg8 = vlan_ctrl | AX_VLAN_CONTROL_RD; + ret = ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, ®8); + if (ret < 0) + return ret; + + ret = ax88179_read_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_DATA0, 2, 2, ®16); + if (ret < 0) + return ret; + reg16 |= (1 << (vid % 16)); + ret = ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_DATA0, 2, 2, ®16); + if (ret < 0) + return ret; + + reg8 = vlan_ctrl | AX_VLAN_CONTROL_WE; + ret = ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, ®8); + if (ret < 0) + return ret; + + return 0; +} + static void ax88179a_mdio_unregister(struct ax88179_data *data) { mdiobus_unregister(data->mdio); @@ -517,6 +599,75 @@ static int ax88179a_mii_ioctl(struct net_device *net, struct ifreq *rq, int cmd) return phylink_mii_ioctl(data->phylink, rq, cmd); } +static int ax88179a_set_features(struct net_device *net, netdev_features_t features) +{ + struct usbnet *dev = netdev_priv(net); + netdev_features_t changed; + int ret; + u8 tmp; + + changed = net->features ^ features; + + ret = ax88179_set_features(net, features); + if (ret) + return ret; + + if (changed & NETIF_F_HW_VLAN_CTAG_FILTER) { + if (features & NETIF_F_HW_VLAN_CTAG_FILTER) { + u8 vlan_state = 0; + + ret = ax88179_read_cmd(dev, AX_ACCESS_MAC, + AX88179A_VLAN_ID_CONTROL, 1, 1, &vlan_state); + if (ret < 0) + return ret; + + for (int i = 0; i < 256; i++) { + /* Address */ + tmp = i; + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_ADDRESS, + 1, 1, &tmp); + /* Data */ + ax88179_write_u16(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_DATA0, + 2, 0); + /* Write-Execute */ + ax88179_write_u8(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, + 1, vlan_state | AX_VLAN_CONTROL_WE); + } + vlan_state |= AX_VLAN_CONTROL_VFE; + ret = ax88179_write_cmd(dev, AX_ACCESS_MAC, + AX88179A_VLAN_ID_CONTROL, 1, 1, &vlan_state); + if (ret < 0) + return ret; + } else { + ret = ax88179_read_cmd(dev, AX_ACCESS_MAC, + AX88179A_VLAN_ID_CONTROL, 1, 1, &tmp); + if (ret < 0) + return ret; + tmp &= ~AX_VLAN_CONTROL_VFE; + ret = ax88179_write_cmd(dev, AX_ACCESS_MAC, + AX88179A_VLAN_ID_CONTROL, 1, 1, &tmp); + if (ret < 0) + return ret; + } + } + + if (changed & NETIF_F_HW_VLAN_CTAG_RX) { + ret = ax88179_read_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, + 1, 1, &tmp); + if (ret < 0) + return ret; + if (features & NETIF_F_HW_VLAN_CTAG_RX) + tmp |= AX_VLAN_CONTROL_VSO; + else + tmp &= ~AX_VLAN_CONTROL_VSO; + ret = ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, + 1, 1, &tmp); + if (ret < 0) + return ret; + } + return 0; +} + static const struct net_device_ops ax88179a_netdev_ops = { .ndo_open = usbnet_open, .ndo_stop = usbnet_stop, @@ -527,6 +678,9 @@ static const struct net_device_ops ax88179a_netdev_ops = { .ndo_set_mac_address = ax88179_set_mac_addr, .ndo_validate_addr = eth_validate_addr, .ndo_eth_ioctl = ax88179a_mii_ioctl, + .ndo_set_features = ax88179a_set_features, + .ndo_vlan_rx_add_vid = ax88179a_vlan_rx_add_vid, + .ndo_vlan_rx_kill_vid = ax88179a_vlan_rx_kill_vid, }; static int ax88179a_bind(struct usbnet *dev, struct usb_interface *intf) @@ -612,9 +766,11 @@ static int ax88179a_bind(struct usbnet *dev, struct usb_interface *intf) dev->mii.supports_gmii = 1; dev->net->features |= NETIF_F_SG | NETIF_F_IP_CSUM | - NETIF_F_IPV6_CSUM | NETIF_F_RXCSUM | NETIF_F_TSO; + NETIF_F_IPV6_CSUM | NETIF_F_RXCSUM | NETIF_F_TSO | + NETIF_F_HW_VLAN_CTAG_TX; dev->net->hw_features |= dev->net->features; + dev->net->hw_features |= NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_CTAG_FILTER; dev->net->vlan_features = NETIF_F_SG | NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM | NETIF_F_RXCSUM | NETIF_F_TSO; -- 2.47.3