[PATCH net-next v15 07/13] ax88179_178a: Add VLAN offload support for AX88179A
From: Birger Koblitz
Date: Fri Oct 02 2026 - 00:49:55 EST
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 <mail@xxxxxxxxxxxxxxxxx>
Reviewed-by: Andrew Lunn <andrew@xxxxxxx>
---
drivers/net/usb/ax88179a_devices.c | 141 ++++++++++++++++++++++++++++++++++++-
1 file changed, 140 insertions(+), 1 deletion(-)
diff --git a/drivers/net/usb/ax88179a_devices.c b/drivers/net/usb/ax88179a_devices.c
index e2ea9724433929d4cbfba0597787ddcb84dacf98..febb4b0ef12895414f9bdea916315d6d48246ab8 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);
+ 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 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)
+ 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;
+
+ 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,58 @@ 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) {
+ 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);
+ ax88179_write_u8(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL,
+ 1, AX_VLAN_CONTROL_WE);
+ }
+ 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;
+ 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 +661,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)
@@ -611,9 +748,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