| // 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; |
| } |