// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2025, Altera Corporation * stmmac VLAN (802.1Q) handling */ #include "stmmac.h" #include "stmmac_vlan.h" static void vlan_write_single(struct net_device *dev, u16 vid) { void __iomem *ioaddr = (void __iomem *)dev->base_addr; u32 val; val = readl(ioaddr + VLAN_TAG); val &= ~VLAN_TAG_VID; val |= VLAN_TAG_ETV | vid; writel(val, ioaddr + VLAN_TAG); } static int vlan_write_filter(struct net_device *dev, struct mac_device_info *hw, u8 index, u32 data) { void __iomem *ioaddr = (void __iomem *)dev->base_addr; int ret; u32 val; if (index >= hw->num_vlan) return -EINVAL; writel(data, ioaddr + VLAN_TAG_DATA); val = readl(ioaddr + VLAN_TAG); val &= ~(VLAN_TAG_CTRL_OFS_MASK | VLAN_TAG_CTRL_CT | VLAN_TAG_CTRL_OB); val |= (index << VLAN_TAG_CTRL_OFS_SHIFT) | VLAN_TAG_CTRL_OB; writel(val, ioaddr + VLAN_TAG); ret = readl_poll_timeout(ioaddr + VLAN_TAG, val, !(val & VLAN_TAG_CTRL_OB), 1000, 500000); if (ret) { netdev_err(dev, "Timeout accessing MAC_VLAN_Tag_Filter\n"); return -EBUSY; } return 0; } static int vlan_add_hw_rx_fltr(struct net_device *dev, struct mac_device_info *hw, __be16 proto, u16 vid) { int index = -1; u32 val = 0; int i, ret; if (vid > 4095) return -EINVAL; /* Single Rx VLAN Filter */ if (hw->num_vlan == 1) { /* For single VLAN filter, VID 0 means VLAN promiscuous */ if (vid == 0) { netdev_warn(dev, "Adding VLAN ID 0 is not supported\n"); return -EPERM; } if (hw->vlan_filter[0] & VLAN_TAG_VID) { netdev_err(dev, "Only single VLAN ID supported\n"); return -EPERM; } hw->vlan_filter[0] = vid; vlan_write_single(dev, vid); return 0; } /* Extended Rx VLAN Filter Enable */ val |= VLAN_TAG_DATA_ETV | VLAN_TAG_DATA_VEN | vid; for (i = 0; i < hw->num_vlan; i++) { if (hw->vlan_filter[i] == val) return 0; else if (!(hw->vlan_filter[i] & VLAN_TAG_DATA_VEN)) index = i; } if (index == -1) { netdev_err(dev, "MAC_VLAN_Tag_Filter full (size: %0u)\n", hw->num_vlan); return -EPERM; } ret = vlan_write_filter(dev, hw, index, val); if (!ret) hw->vlan_filter[index] = val; return ret; } static int vlan_del_hw_rx_fltr(struct net_device *dev, struct mac_device_info *hw, __be16 proto, u16 vid) { int i, ret = 0; /* Single Rx VLAN Filter */ if (hw->num_vlan == 1) { if ((hw->vlan_filter[0] & VLAN_TAG_VID) == vid) { hw->vlan_filter[0] = 0; vlan_write_single(dev, 0); } return 0; } /* Extended Rx VLAN Filter Enable */ for (i = 0; i < hw->num_vlan; i++) { if ((hw->vlan_filter[i] & VLAN_TAG_DATA_VID) == vid) { ret = vlan_write_filter(dev, hw, i, 0); if (!ret) hw->vlan_filter[i] = 0; else return ret; } } return ret; } static void vlan_restore_hw_rx_fltr(struct net_device *dev, struct mac_device_info *hw) { void __iomem *ioaddr = hw->pcsr; u32 value; u32 hash; u32 val; int i; /* Single Rx VLAN Filter */ if (hw->num_vlan == 1) { vlan_write_single(dev, hw->vlan_filter[0]); return; } /* Extended Rx VLAN Filter Enable */ for (i = 0; i < hw->num_vlan; i++) { if (hw->vlan_filter[i] & VLAN_TAG_DATA_VEN) { val = hw->vlan_filter[i]; vlan_write_filter(dev, hw, i, val); } } hash = readl(ioaddr + VLAN_HASH_TABLE); if (hash & VLAN_VLHT) { value = readl(ioaddr + VLAN_TAG); value |= VLAN_VTHM; writel(value, ioaddr + VLAN_TAG); } } static void vlan_update_hash(struct mac_device_info *hw, u32 hash, u16 perfect_match, bool is_double) { void __iomem *ioaddr = hw->pcsr; u32 value; writel(hash, ioaddr + VLAN_HASH_TABLE); value = readl(ioaddr + VLAN_TAG); if (hash) { value |= VLAN_VTHM | VLAN_ETV; if (is_double) { value |= VLAN_EDVLP; value |= VLAN_ESVL; value |= VLAN_DOVLTC; } writel(value, ioaddr + VLAN_TAG); } else if (perfect_match) { u32 value = VLAN_ETV; if (is_double) { value |= VLAN_EDVLP; value |= VLAN_ESVL; value |= VLAN_DOVLTC; } writel(value | perfect_match, ioaddr + VLAN_TAG); } else { value &= ~(VLAN_VTHM | VLAN_ETV); value &= ~(VLAN_EDVLP | VLAN_ESVL); value &= ~VLAN_DOVLTC; value &= ~VLAN_VID; writel(value, ioaddr + VLAN_TAG); } } static void vlan_enable(struct mac_device_info *hw, u32 type) { void __iomem *ioaddr = hw->pcsr; u32 value; value = readl(ioaddr + VLAN_INCL); value |= VLAN_VLTI; value |= VLAN_CSVL; /* Only use SVLAN */ value &= ~VLAN_VLC; value |= (type << VLAN_VLC_SHIFT) & VLAN_VLC; writel(value, ioaddr + VLAN_INCL); } static void vlan_rx_hw(struct mac_device_info *hw, struct dma_desc *rx_desc, struct sk_buff *skb) { if (hw->desc->get_rx_vlan_valid(rx_desc)) { u16 vid = hw->desc->get_rx_vlan_tci(rx_desc); __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), vid); } } static void vlan_set_hw_mode(struct mac_device_info *hw) { void __iomem *ioaddr = hw->pcsr; u32 value = readl(ioaddr + VLAN_TAG); value &= ~VLAN_TAG_CTRL_EVLS_MASK; if (hw->hw_vlan_en) /* Always strip VLAN on Receive */ value |= VLAN_TAG_STRIP_ALL; else /* Do not strip VLAN on Receive */ value |= VLAN_TAG_STRIP_NONE; /* Enable outer VLAN Tag in Rx DMA descriptor */ value |= VLAN_TAG_CTRL_EVLRXS; writel(value, ioaddr + VLAN_TAG); } static void dwxgmac2_update_vlan_hash(struct mac_device_info *hw, u32 hash, u16 perfect_match, bool is_double) { void __iomem *ioaddr = hw->pcsr; writel(hash, ioaddr + VLAN_HASH_TABLE); if (hash) { u32 value = readl(ioaddr + XGMAC_PACKET_FILTER); value |= XGMAC_FILTER_VTFE; writel(value, ioaddr + XGMAC_PACKET_FILTER); value = readl(ioaddr + VLAN_TAG); value |= VLAN_VTHM | VLAN_ETV; if (is_double) { value |= VLAN_EDVLP; value |= VLAN_ESVL; value |= VLAN_DOVLTC; } else { value &= ~VLAN_EDVLP; value &= ~VLAN_ESVL; value &= ~VLAN_DOVLTC; } value &= ~VLAN_VID; writel(value, ioaddr + VLAN_TAG); } else if (perfect_match) { u32 value = readl(ioaddr + XGMAC_PACKET_FILTER); value |= XGMAC_FILTER_VTFE; writel(value, ioaddr + XGMAC_PACKET_FILTER); value = readl(ioaddr + VLAN_TAG); value &= ~VLAN_VTHM; value |= VLAN_ETV; if (is_double) { value |= VLAN_EDVLP; value |= VLAN_ESVL; value |= VLAN_DOVLTC; } else { value &= ~VLAN_EDVLP; value &= ~VLAN_ESVL; value &= ~VLAN_DOVLTC; } value &= ~VLAN_VID; writel(value | perfect_match, ioaddr + VLAN_TAG); } else { u32 value = readl(ioaddr + XGMAC_PACKET_FILTER); value &= ~XGMAC_FILTER_VTFE; writel(value, ioaddr + XGMAC_PACKET_FILTER); value = readl(ioaddr + VLAN_TAG); value &= ~(VLAN_VTHM | VLAN_ETV); value &= ~(VLAN_EDVLP | VLAN_ESVL); value &= ~VLAN_DOVLTC; value &= ~VLAN_VID; writel(value, ioaddr + VLAN_TAG); } } const struct stmmac_vlan_ops dwmac_vlan_ops = { .update_vlan_hash = vlan_update_hash, .enable_vlan = vlan_enable, .add_hw_vlan_rx_fltr = vlan_add_hw_rx_fltr, .del_hw_vlan_rx_fltr = vlan_del_hw_rx_fltr, .restore_hw_vlan_rx_fltr = vlan_restore_hw_rx_fltr, .rx_hw_vlan = vlan_rx_hw, .set_hw_vlan_mode = vlan_set_hw_mode, }; const struct stmmac_vlan_ops dwxlgmac2_vlan_ops = { .update_vlan_hash = dwxgmac2_update_vlan_hash, .enable_vlan = vlan_enable, }; const struct stmmac_vlan_ops dwxgmac210_vlan_ops = { .update_vlan_hash = dwxgmac2_update_vlan_hash, .enable_vlan = vlan_enable, .add_hw_vlan_rx_fltr = vlan_add_hw_rx_fltr, .del_hw_vlan_rx_fltr = vlan_del_hw_rx_fltr, .restore_hw_vlan_rx_fltr = vlan_restore_hw_rx_fltr, .rx_hw_vlan = vlan_rx_hw, .set_hw_vlan_mode = vlan_set_hw_mode, }; u32 stmmac_get_num_vlan(void __iomem *ioaddr) { u32 val, num_vlan; val = readl(ioaddr + HW_FEATURE3); switch (val & VLAN_HW_FEAT_NRVF) { case 0: num_vlan = 1; break; case 1: num_vlan = 4; break; case 2: num_vlan = 8; break; case 3: num_vlan = 16; break; case 4: num_vlan = 24; break; case 5: num_vlan = 32; break; default: num_vlan = 1; } return num_vlan; }