18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/* Realtek SMI library helpers for the RTL8366x variants
38c2ecf20Sopenharmony_ci * RTL8366RB and RTL8366S
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org>
68c2ecf20Sopenharmony_ci * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
78c2ecf20Sopenharmony_ci * Copyright (C) 2010 Antti Seppälä <a.seppala@gmail.com>
88c2ecf20Sopenharmony_ci * Copyright (C) 2010 Roman Yeryomin <roman@advem.lv>
98c2ecf20Sopenharmony_ci * Copyright (C) 2011 Colin Leitner <colin.leitner@googlemail.com>
108c2ecf20Sopenharmony_ci */
118c2ecf20Sopenharmony_ci#include <linux/if_bridge.h>
128c2ecf20Sopenharmony_ci#include <net/dsa.h>
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci#include "realtek-smi-core.h"
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ciint rtl8366_mc_is_used(struct realtek_smi *smi, int mc_index, int *used)
178c2ecf20Sopenharmony_ci{
188c2ecf20Sopenharmony_ci	int ret;
198c2ecf20Sopenharmony_ci	int i;
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci	*used = 0;
228c2ecf20Sopenharmony_ci	for (i = 0; i < smi->num_ports; i++) {
238c2ecf20Sopenharmony_ci		int index = 0;
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci		ret = smi->ops->get_mc_index(smi, i, &index);
268c2ecf20Sopenharmony_ci		if (ret)
278c2ecf20Sopenharmony_ci			return ret;
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci		if (mc_index == index) {
308c2ecf20Sopenharmony_ci			*used = 1;
318c2ecf20Sopenharmony_ci			break;
328c2ecf20Sopenharmony_ci		}
338c2ecf20Sopenharmony_ci	}
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci	return 0;
368c2ecf20Sopenharmony_ci}
378c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(rtl8366_mc_is_used);
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci/**
408c2ecf20Sopenharmony_ci * rtl8366_obtain_mc() - retrieve or allocate a VLAN member configuration
418c2ecf20Sopenharmony_ci * @smi: the Realtek SMI device instance
428c2ecf20Sopenharmony_ci * @vid: the VLAN ID to look up or allocate
438c2ecf20Sopenharmony_ci * @vlanmc: the pointer will be assigned to a pointer to a valid member config
448c2ecf20Sopenharmony_ci * if successful
458c2ecf20Sopenharmony_ci * @return: index of a new member config or negative error number
468c2ecf20Sopenharmony_ci */
478c2ecf20Sopenharmony_cistatic int rtl8366_obtain_mc(struct realtek_smi *smi, int vid,
488c2ecf20Sopenharmony_ci			     struct rtl8366_vlan_mc *vlanmc)
498c2ecf20Sopenharmony_ci{
508c2ecf20Sopenharmony_ci	struct rtl8366_vlan_4k vlan4k;
518c2ecf20Sopenharmony_ci	int ret;
528c2ecf20Sopenharmony_ci	int i;
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci	/* Try to find an existing member config entry for this VID */
558c2ecf20Sopenharmony_ci	for (i = 0; i < smi->num_vlan_mc; i++) {
568c2ecf20Sopenharmony_ci		ret = smi->ops->get_vlan_mc(smi, i, vlanmc);
578c2ecf20Sopenharmony_ci		if (ret) {
588c2ecf20Sopenharmony_ci			dev_err(smi->dev, "error searching for VLAN MC %d for VID %d\n",
598c2ecf20Sopenharmony_ci				i, vid);
608c2ecf20Sopenharmony_ci			return ret;
618c2ecf20Sopenharmony_ci		}
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci		if (vid == vlanmc->vid)
648c2ecf20Sopenharmony_ci			return i;
658c2ecf20Sopenharmony_ci	}
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci	/* We have no MC entry for this VID, try to find an empty one */
688c2ecf20Sopenharmony_ci	for (i = 0; i < smi->num_vlan_mc; i++) {
698c2ecf20Sopenharmony_ci		ret = smi->ops->get_vlan_mc(smi, i, vlanmc);
708c2ecf20Sopenharmony_ci		if (ret) {
718c2ecf20Sopenharmony_ci			dev_err(smi->dev, "error searching for VLAN MC %d for VID %d\n",
728c2ecf20Sopenharmony_ci				i, vid);
738c2ecf20Sopenharmony_ci			return ret;
748c2ecf20Sopenharmony_ci		}
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci		if (vlanmc->vid == 0 && vlanmc->member == 0) {
778c2ecf20Sopenharmony_ci			/* Update the entry from the 4K table */
788c2ecf20Sopenharmony_ci			ret = smi->ops->get_vlan_4k(smi, vid, &vlan4k);
798c2ecf20Sopenharmony_ci			if (ret) {
808c2ecf20Sopenharmony_ci				dev_err(smi->dev, "error looking for 4K VLAN MC %d for VID %d\n",
818c2ecf20Sopenharmony_ci					i, vid);
828c2ecf20Sopenharmony_ci				return ret;
838c2ecf20Sopenharmony_ci			}
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci			vlanmc->vid = vid;
868c2ecf20Sopenharmony_ci			vlanmc->member = vlan4k.member;
878c2ecf20Sopenharmony_ci			vlanmc->untag = vlan4k.untag;
888c2ecf20Sopenharmony_ci			vlanmc->fid = vlan4k.fid;
898c2ecf20Sopenharmony_ci			ret = smi->ops->set_vlan_mc(smi, i, vlanmc);
908c2ecf20Sopenharmony_ci			if (ret) {
918c2ecf20Sopenharmony_ci				dev_err(smi->dev, "unable to set/update VLAN MC %d for VID %d\n",
928c2ecf20Sopenharmony_ci					i, vid);
938c2ecf20Sopenharmony_ci				return ret;
948c2ecf20Sopenharmony_ci			}
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci			dev_dbg(smi->dev, "created new MC at index %d for VID %d\n",
978c2ecf20Sopenharmony_ci				i, vid);
988c2ecf20Sopenharmony_ci			return i;
998c2ecf20Sopenharmony_ci		}
1008c2ecf20Sopenharmony_ci	}
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	/* MC table is full, try to find an unused entry and replace it */
1038c2ecf20Sopenharmony_ci	for (i = 0; i < smi->num_vlan_mc; i++) {
1048c2ecf20Sopenharmony_ci		int used;
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci		ret = rtl8366_mc_is_used(smi, i, &used);
1078c2ecf20Sopenharmony_ci		if (ret)
1088c2ecf20Sopenharmony_ci			return ret;
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci		if (!used) {
1118c2ecf20Sopenharmony_ci			/* Update the entry from the 4K table */
1128c2ecf20Sopenharmony_ci			ret = smi->ops->get_vlan_4k(smi, vid, &vlan4k);
1138c2ecf20Sopenharmony_ci			if (ret)
1148c2ecf20Sopenharmony_ci				return ret;
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci			vlanmc->vid = vid;
1178c2ecf20Sopenharmony_ci			vlanmc->member = vlan4k.member;
1188c2ecf20Sopenharmony_ci			vlanmc->untag = vlan4k.untag;
1198c2ecf20Sopenharmony_ci			vlanmc->fid = vlan4k.fid;
1208c2ecf20Sopenharmony_ci			ret = smi->ops->set_vlan_mc(smi, i, vlanmc);
1218c2ecf20Sopenharmony_ci			if (ret) {
1228c2ecf20Sopenharmony_ci				dev_err(smi->dev, "unable to set/update VLAN MC %d for VID %d\n",
1238c2ecf20Sopenharmony_ci					i, vid);
1248c2ecf20Sopenharmony_ci				return ret;
1258c2ecf20Sopenharmony_ci			}
1268c2ecf20Sopenharmony_ci			dev_dbg(smi->dev, "recycled MC at index %i for VID %d\n",
1278c2ecf20Sopenharmony_ci				i, vid);
1288c2ecf20Sopenharmony_ci			return i;
1298c2ecf20Sopenharmony_ci		}
1308c2ecf20Sopenharmony_ci	}
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci	dev_err(smi->dev, "all VLAN member configurations are in use\n");
1338c2ecf20Sopenharmony_ci	return -ENOSPC;
1348c2ecf20Sopenharmony_ci}
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ciint rtl8366_set_vlan(struct realtek_smi *smi, int vid, u32 member,
1378c2ecf20Sopenharmony_ci		     u32 untag, u32 fid)
1388c2ecf20Sopenharmony_ci{
1398c2ecf20Sopenharmony_ci	struct rtl8366_vlan_mc vlanmc;
1408c2ecf20Sopenharmony_ci	struct rtl8366_vlan_4k vlan4k;
1418c2ecf20Sopenharmony_ci	int mc;
1428c2ecf20Sopenharmony_ci	int ret;
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci	if (!smi->ops->is_vlan_valid(smi, vid))
1458c2ecf20Sopenharmony_ci		return -EINVAL;
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci	dev_dbg(smi->dev,
1488c2ecf20Sopenharmony_ci		"setting VLAN%d 4k members: 0x%02x, untagged: 0x%02x\n",
1498c2ecf20Sopenharmony_ci		vid, member, untag);
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci	/* Update the 4K table */
1528c2ecf20Sopenharmony_ci	ret = smi->ops->get_vlan_4k(smi, vid, &vlan4k);
1538c2ecf20Sopenharmony_ci	if (ret)
1548c2ecf20Sopenharmony_ci		return ret;
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci	vlan4k.member |= member;
1578c2ecf20Sopenharmony_ci	vlan4k.untag |= untag;
1588c2ecf20Sopenharmony_ci	vlan4k.fid = fid;
1598c2ecf20Sopenharmony_ci	ret = smi->ops->set_vlan_4k(smi, &vlan4k);
1608c2ecf20Sopenharmony_ci	if (ret)
1618c2ecf20Sopenharmony_ci		return ret;
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	dev_dbg(smi->dev,
1648c2ecf20Sopenharmony_ci		"resulting VLAN%d 4k members: 0x%02x, untagged: 0x%02x\n",
1658c2ecf20Sopenharmony_ci		vid, vlan4k.member, vlan4k.untag);
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	/* Find or allocate a member config for this VID */
1688c2ecf20Sopenharmony_ci	ret = rtl8366_obtain_mc(smi, vid, &vlanmc);
1698c2ecf20Sopenharmony_ci	if (ret < 0)
1708c2ecf20Sopenharmony_ci		return ret;
1718c2ecf20Sopenharmony_ci	mc = ret;
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ci	/* Update the MC entry */
1748c2ecf20Sopenharmony_ci	vlanmc.member |= member;
1758c2ecf20Sopenharmony_ci	vlanmc.untag |= untag;
1768c2ecf20Sopenharmony_ci	vlanmc.fid = fid;
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci	/* Commit updates to the MC entry */
1798c2ecf20Sopenharmony_ci	ret = smi->ops->set_vlan_mc(smi, mc, &vlanmc);
1808c2ecf20Sopenharmony_ci	if (ret)
1818c2ecf20Sopenharmony_ci		dev_err(smi->dev, "failed to commit changes to VLAN MC index %d for VID %d\n",
1828c2ecf20Sopenharmony_ci			mc, vid);
1838c2ecf20Sopenharmony_ci	else
1848c2ecf20Sopenharmony_ci		dev_dbg(smi->dev,
1858c2ecf20Sopenharmony_ci			"resulting VLAN%d MC members: 0x%02x, untagged: 0x%02x\n",
1868c2ecf20Sopenharmony_ci			vid, vlanmc.member, vlanmc.untag);
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci	return ret;
1898c2ecf20Sopenharmony_ci}
1908c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(rtl8366_set_vlan);
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ciint rtl8366_set_pvid(struct realtek_smi *smi, unsigned int port,
1938c2ecf20Sopenharmony_ci		     unsigned int vid)
1948c2ecf20Sopenharmony_ci{
1958c2ecf20Sopenharmony_ci	struct rtl8366_vlan_mc vlanmc;
1968c2ecf20Sopenharmony_ci	int mc;
1978c2ecf20Sopenharmony_ci	int ret;
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ci	if (!smi->ops->is_vlan_valid(smi, vid))
2008c2ecf20Sopenharmony_ci		return -EINVAL;
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_ci	/* Find or allocate a member config for this VID */
2038c2ecf20Sopenharmony_ci	ret = rtl8366_obtain_mc(smi, vid, &vlanmc);
2048c2ecf20Sopenharmony_ci	if (ret < 0)
2058c2ecf20Sopenharmony_ci		return ret;
2068c2ecf20Sopenharmony_ci	mc = ret;
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_ci	ret = smi->ops->set_mc_index(smi, port, mc);
2098c2ecf20Sopenharmony_ci	if (ret) {
2108c2ecf20Sopenharmony_ci		dev_err(smi->dev, "set PVID: failed to set MC index %d for port %d\n",
2118c2ecf20Sopenharmony_ci			mc, port);
2128c2ecf20Sopenharmony_ci		return ret;
2138c2ecf20Sopenharmony_ci	}
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_ci	dev_dbg(smi->dev, "set PVID: the PVID for port %d set to %d using existing MC index %d\n",
2168c2ecf20Sopenharmony_ci		port, vid, mc);
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_ci	return 0;
2198c2ecf20Sopenharmony_ci}
2208c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(rtl8366_set_pvid);
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_ciint rtl8366_enable_vlan4k(struct realtek_smi *smi, bool enable)
2238c2ecf20Sopenharmony_ci{
2248c2ecf20Sopenharmony_ci	int ret;
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci	/* To enable 4k VLAN, ordinary VLAN must be enabled first,
2278c2ecf20Sopenharmony_ci	 * but if we disable 4k VLAN it is fine to leave ordinary
2288c2ecf20Sopenharmony_ci	 * VLAN enabled.
2298c2ecf20Sopenharmony_ci	 */
2308c2ecf20Sopenharmony_ci	if (enable) {
2318c2ecf20Sopenharmony_ci		/* Make sure VLAN is ON */
2328c2ecf20Sopenharmony_ci		ret = smi->ops->enable_vlan(smi, true);
2338c2ecf20Sopenharmony_ci		if (ret)
2348c2ecf20Sopenharmony_ci			return ret;
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_ci		smi->vlan_enabled = true;
2378c2ecf20Sopenharmony_ci	}
2388c2ecf20Sopenharmony_ci
2398c2ecf20Sopenharmony_ci	ret = smi->ops->enable_vlan4k(smi, enable);
2408c2ecf20Sopenharmony_ci	if (ret)
2418c2ecf20Sopenharmony_ci		return ret;
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_ci	smi->vlan4k_enabled = enable;
2448c2ecf20Sopenharmony_ci	return 0;
2458c2ecf20Sopenharmony_ci}
2468c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(rtl8366_enable_vlan4k);
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ciint rtl8366_enable_vlan(struct realtek_smi *smi, bool enable)
2498c2ecf20Sopenharmony_ci{
2508c2ecf20Sopenharmony_ci	int ret;
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_ci	ret = smi->ops->enable_vlan(smi, enable);
2538c2ecf20Sopenharmony_ci	if (ret)
2548c2ecf20Sopenharmony_ci		return ret;
2558c2ecf20Sopenharmony_ci
2568c2ecf20Sopenharmony_ci	smi->vlan_enabled = enable;
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_ci	/* If we turn VLAN off, make sure that we turn off
2598c2ecf20Sopenharmony_ci	 * 4k VLAN as well, if that happened to be on.
2608c2ecf20Sopenharmony_ci	 */
2618c2ecf20Sopenharmony_ci	if (!enable) {
2628c2ecf20Sopenharmony_ci		smi->vlan4k_enabled = false;
2638c2ecf20Sopenharmony_ci		ret = smi->ops->enable_vlan4k(smi, false);
2648c2ecf20Sopenharmony_ci	}
2658c2ecf20Sopenharmony_ci
2668c2ecf20Sopenharmony_ci	return ret;
2678c2ecf20Sopenharmony_ci}
2688c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(rtl8366_enable_vlan);
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_ciint rtl8366_reset_vlan(struct realtek_smi *smi)
2718c2ecf20Sopenharmony_ci{
2728c2ecf20Sopenharmony_ci	struct rtl8366_vlan_mc vlanmc;
2738c2ecf20Sopenharmony_ci	int ret;
2748c2ecf20Sopenharmony_ci	int i;
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_ci	rtl8366_enable_vlan(smi, false);
2778c2ecf20Sopenharmony_ci	rtl8366_enable_vlan4k(smi, false);
2788c2ecf20Sopenharmony_ci
2798c2ecf20Sopenharmony_ci	/* Clear the 16 VLAN member configurations */
2808c2ecf20Sopenharmony_ci	vlanmc.vid = 0;
2818c2ecf20Sopenharmony_ci	vlanmc.priority = 0;
2828c2ecf20Sopenharmony_ci	vlanmc.member = 0;
2838c2ecf20Sopenharmony_ci	vlanmc.untag = 0;
2848c2ecf20Sopenharmony_ci	vlanmc.fid = 0;
2858c2ecf20Sopenharmony_ci	for (i = 0; i < smi->num_vlan_mc; i++) {
2868c2ecf20Sopenharmony_ci		ret = smi->ops->set_vlan_mc(smi, i, &vlanmc);
2878c2ecf20Sopenharmony_ci		if (ret)
2888c2ecf20Sopenharmony_ci			return ret;
2898c2ecf20Sopenharmony_ci	}
2908c2ecf20Sopenharmony_ci
2918c2ecf20Sopenharmony_ci	return 0;
2928c2ecf20Sopenharmony_ci}
2938c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(rtl8366_reset_vlan);
2948c2ecf20Sopenharmony_ci
2958c2ecf20Sopenharmony_ciint rtl8366_init_vlan(struct realtek_smi *smi)
2968c2ecf20Sopenharmony_ci{
2978c2ecf20Sopenharmony_ci	int port;
2988c2ecf20Sopenharmony_ci	int ret;
2998c2ecf20Sopenharmony_ci
3008c2ecf20Sopenharmony_ci	ret = rtl8366_reset_vlan(smi);
3018c2ecf20Sopenharmony_ci	if (ret)
3028c2ecf20Sopenharmony_ci		return ret;
3038c2ecf20Sopenharmony_ci
3048c2ecf20Sopenharmony_ci	/* Loop over the available ports, for each port, associate
3058c2ecf20Sopenharmony_ci	 * it with the VLAN (port+1)
3068c2ecf20Sopenharmony_ci	 */
3078c2ecf20Sopenharmony_ci	for (port = 0; port < smi->num_ports; port++) {
3088c2ecf20Sopenharmony_ci		u32 mask;
3098c2ecf20Sopenharmony_ci
3108c2ecf20Sopenharmony_ci		if (port == smi->cpu_port)
3118c2ecf20Sopenharmony_ci			/* For the CPU port, make all ports members of this
3128c2ecf20Sopenharmony_ci			 * VLAN.
3138c2ecf20Sopenharmony_ci			 */
3148c2ecf20Sopenharmony_ci			mask = GENMASK((int)smi->num_ports - 1, 0);
3158c2ecf20Sopenharmony_ci		else
3168c2ecf20Sopenharmony_ci			/* For all other ports, enable itself plus the
3178c2ecf20Sopenharmony_ci			 * CPU port.
3188c2ecf20Sopenharmony_ci			 */
3198c2ecf20Sopenharmony_ci			mask = BIT(port) | BIT(smi->cpu_port);
3208c2ecf20Sopenharmony_ci
3218c2ecf20Sopenharmony_ci		/* For each port, set the port as member of VLAN (port+1)
3228c2ecf20Sopenharmony_ci		 * and untagged, except for the CPU port: the CPU port (5) is
3238c2ecf20Sopenharmony_ci		 * member of VLAN 6 and so are ALL the other ports as well.
3248c2ecf20Sopenharmony_ci		 * Use filter 0 (no filter).
3258c2ecf20Sopenharmony_ci		 */
3268c2ecf20Sopenharmony_ci		dev_info(smi->dev, "VLAN%d port mask for port %d, %08x\n",
3278c2ecf20Sopenharmony_ci			 (port + 1), port, mask);
3288c2ecf20Sopenharmony_ci		ret = rtl8366_set_vlan(smi, (port + 1), mask, mask, 0);
3298c2ecf20Sopenharmony_ci		if (ret)
3308c2ecf20Sopenharmony_ci			return ret;
3318c2ecf20Sopenharmony_ci
3328c2ecf20Sopenharmony_ci		dev_info(smi->dev, "VLAN%d port %d, PVID set to %d\n",
3338c2ecf20Sopenharmony_ci			 (port + 1), port, (port + 1));
3348c2ecf20Sopenharmony_ci		ret = rtl8366_set_pvid(smi, port, (port + 1));
3358c2ecf20Sopenharmony_ci		if (ret)
3368c2ecf20Sopenharmony_ci			return ret;
3378c2ecf20Sopenharmony_ci	}
3388c2ecf20Sopenharmony_ci
3398c2ecf20Sopenharmony_ci	return rtl8366_enable_vlan(smi, true);
3408c2ecf20Sopenharmony_ci}
3418c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(rtl8366_init_vlan);
3428c2ecf20Sopenharmony_ci
3438c2ecf20Sopenharmony_ciint rtl8366_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering,
3448c2ecf20Sopenharmony_ci			   struct switchdev_trans *trans)
3458c2ecf20Sopenharmony_ci{
3468c2ecf20Sopenharmony_ci	struct realtek_smi *smi = ds->priv;
3478c2ecf20Sopenharmony_ci	struct rtl8366_vlan_4k vlan4k;
3488c2ecf20Sopenharmony_ci	int ret;
3498c2ecf20Sopenharmony_ci
3508c2ecf20Sopenharmony_ci	/* Use VLAN nr port + 1 since VLAN0 is not valid */
3518c2ecf20Sopenharmony_ci	if (switchdev_trans_ph_prepare(trans)) {
3528c2ecf20Sopenharmony_ci		if (!smi->ops->is_vlan_valid(smi, port + 1))
3538c2ecf20Sopenharmony_ci			return -EINVAL;
3548c2ecf20Sopenharmony_ci
3558c2ecf20Sopenharmony_ci		return 0;
3568c2ecf20Sopenharmony_ci	}
3578c2ecf20Sopenharmony_ci
3588c2ecf20Sopenharmony_ci	dev_info(smi->dev, "%s filtering on port %d\n",
3598c2ecf20Sopenharmony_ci		 vlan_filtering ? "enable" : "disable",
3608c2ecf20Sopenharmony_ci		 port);
3618c2ecf20Sopenharmony_ci
3628c2ecf20Sopenharmony_ci	/* TODO:
3638c2ecf20Sopenharmony_ci	 * The hardware support filter ID (FID) 0..7, I have no clue how to
3648c2ecf20Sopenharmony_ci	 * support this in the driver when the callback only says on/off.
3658c2ecf20Sopenharmony_ci	 */
3668c2ecf20Sopenharmony_ci	ret = smi->ops->get_vlan_4k(smi, port + 1, &vlan4k);
3678c2ecf20Sopenharmony_ci	if (ret)
3688c2ecf20Sopenharmony_ci		return ret;
3698c2ecf20Sopenharmony_ci
3708c2ecf20Sopenharmony_ci	/* Just set the filter to FID 1 for now then */
3718c2ecf20Sopenharmony_ci	ret = rtl8366_set_vlan(smi, port + 1,
3728c2ecf20Sopenharmony_ci			       vlan4k.member,
3738c2ecf20Sopenharmony_ci			       vlan4k.untag,
3748c2ecf20Sopenharmony_ci			       1);
3758c2ecf20Sopenharmony_ci	if (ret)
3768c2ecf20Sopenharmony_ci		return ret;
3778c2ecf20Sopenharmony_ci
3788c2ecf20Sopenharmony_ci	return 0;
3798c2ecf20Sopenharmony_ci}
3808c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(rtl8366_vlan_filtering);
3818c2ecf20Sopenharmony_ci
3828c2ecf20Sopenharmony_ciint rtl8366_vlan_prepare(struct dsa_switch *ds, int port,
3838c2ecf20Sopenharmony_ci			 const struct switchdev_obj_port_vlan *vlan)
3848c2ecf20Sopenharmony_ci{
3858c2ecf20Sopenharmony_ci	struct realtek_smi *smi = ds->priv;
3868c2ecf20Sopenharmony_ci	u16 vid;
3878c2ecf20Sopenharmony_ci	int ret;
3888c2ecf20Sopenharmony_ci
3898c2ecf20Sopenharmony_ci	for (vid = vlan->vid_begin; vid < vlan->vid_end; vid++)
3908c2ecf20Sopenharmony_ci		if (!smi->ops->is_vlan_valid(smi, vid))
3918c2ecf20Sopenharmony_ci			return -EINVAL;
3928c2ecf20Sopenharmony_ci
3938c2ecf20Sopenharmony_ci	dev_info(smi->dev, "prepare VLANs %04x..%04x\n",
3948c2ecf20Sopenharmony_ci		 vlan->vid_begin, vlan->vid_end);
3958c2ecf20Sopenharmony_ci
3968c2ecf20Sopenharmony_ci	/* Enable VLAN in the hardware
3978c2ecf20Sopenharmony_ci	 * FIXME: what's with this 4k business?
3988c2ecf20Sopenharmony_ci	 * Just rtl8366_enable_vlan() seems inconclusive.
3998c2ecf20Sopenharmony_ci	 */
4008c2ecf20Sopenharmony_ci	ret = rtl8366_enable_vlan4k(smi, true);
4018c2ecf20Sopenharmony_ci	if (ret)
4028c2ecf20Sopenharmony_ci		return ret;
4038c2ecf20Sopenharmony_ci
4048c2ecf20Sopenharmony_ci	return 0;
4058c2ecf20Sopenharmony_ci}
4068c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(rtl8366_vlan_prepare);
4078c2ecf20Sopenharmony_ci
4088c2ecf20Sopenharmony_civoid rtl8366_vlan_add(struct dsa_switch *ds, int port,
4098c2ecf20Sopenharmony_ci		      const struct switchdev_obj_port_vlan *vlan)
4108c2ecf20Sopenharmony_ci{
4118c2ecf20Sopenharmony_ci	bool untagged = !!(vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED);
4128c2ecf20Sopenharmony_ci	bool pvid = !!(vlan->flags & BRIDGE_VLAN_INFO_PVID);
4138c2ecf20Sopenharmony_ci	struct realtek_smi *smi = ds->priv;
4148c2ecf20Sopenharmony_ci	u32 member = 0;
4158c2ecf20Sopenharmony_ci	u32 untag = 0;
4168c2ecf20Sopenharmony_ci	u16 vid;
4178c2ecf20Sopenharmony_ci	int ret;
4188c2ecf20Sopenharmony_ci
4198c2ecf20Sopenharmony_ci	for (vid = vlan->vid_begin; vid < vlan->vid_end; vid++)
4208c2ecf20Sopenharmony_ci		if (!smi->ops->is_vlan_valid(smi, vid))
4218c2ecf20Sopenharmony_ci			return;
4228c2ecf20Sopenharmony_ci
4238c2ecf20Sopenharmony_ci	dev_info(smi->dev, "add VLAN %d on port %d, %s, %s\n",
4248c2ecf20Sopenharmony_ci		 vlan->vid_begin,
4258c2ecf20Sopenharmony_ci		 port,
4268c2ecf20Sopenharmony_ci		 untagged ? "untagged" : "tagged",
4278c2ecf20Sopenharmony_ci		 pvid ? " PVID" : "no PVID");
4288c2ecf20Sopenharmony_ci
4298c2ecf20Sopenharmony_ci	if (dsa_is_dsa_port(ds, port) || dsa_is_cpu_port(ds, port))
4308c2ecf20Sopenharmony_ci		dev_err(smi->dev, "port is DSA or CPU port\n");
4318c2ecf20Sopenharmony_ci
4328c2ecf20Sopenharmony_ci	for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) {
4338c2ecf20Sopenharmony_ci		member |= BIT(port);
4348c2ecf20Sopenharmony_ci
4358c2ecf20Sopenharmony_ci		if (untagged)
4368c2ecf20Sopenharmony_ci			untag |= BIT(port);
4378c2ecf20Sopenharmony_ci
4388c2ecf20Sopenharmony_ci		ret = rtl8366_set_vlan(smi, vid, member, untag, 0);
4398c2ecf20Sopenharmony_ci		if (ret)
4408c2ecf20Sopenharmony_ci			dev_err(smi->dev,
4418c2ecf20Sopenharmony_ci				"failed to set up VLAN %04x",
4428c2ecf20Sopenharmony_ci				vid);
4438c2ecf20Sopenharmony_ci
4448c2ecf20Sopenharmony_ci		if (!pvid)
4458c2ecf20Sopenharmony_ci			continue;
4468c2ecf20Sopenharmony_ci
4478c2ecf20Sopenharmony_ci		ret = rtl8366_set_pvid(smi, port, vid);
4488c2ecf20Sopenharmony_ci		if (ret)
4498c2ecf20Sopenharmony_ci			dev_err(smi->dev,
4508c2ecf20Sopenharmony_ci				"failed to set PVID on port %d to VLAN %04x",
4518c2ecf20Sopenharmony_ci				port, vid);
4528c2ecf20Sopenharmony_ci
4538c2ecf20Sopenharmony_ci		if (!ret)
4548c2ecf20Sopenharmony_ci			dev_dbg(smi->dev, "VLAN add: added VLAN %d with PVID on port %d\n",
4558c2ecf20Sopenharmony_ci				vid, port);
4568c2ecf20Sopenharmony_ci	}
4578c2ecf20Sopenharmony_ci}
4588c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(rtl8366_vlan_add);
4598c2ecf20Sopenharmony_ci
4608c2ecf20Sopenharmony_ciint rtl8366_vlan_del(struct dsa_switch *ds, int port,
4618c2ecf20Sopenharmony_ci		     const struct switchdev_obj_port_vlan *vlan)
4628c2ecf20Sopenharmony_ci{
4638c2ecf20Sopenharmony_ci	struct realtek_smi *smi = ds->priv;
4648c2ecf20Sopenharmony_ci	u16 vid;
4658c2ecf20Sopenharmony_ci	int ret;
4668c2ecf20Sopenharmony_ci
4678c2ecf20Sopenharmony_ci	dev_info(smi->dev, "del VLAN on port %d\n", port);
4688c2ecf20Sopenharmony_ci
4698c2ecf20Sopenharmony_ci	for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) {
4708c2ecf20Sopenharmony_ci		int i;
4718c2ecf20Sopenharmony_ci
4728c2ecf20Sopenharmony_ci		dev_info(smi->dev, "del VLAN %04x\n", vid);
4738c2ecf20Sopenharmony_ci
4748c2ecf20Sopenharmony_ci		for (i = 0; i < smi->num_vlan_mc; i++) {
4758c2ecf20Sopenharmony_ci			struct rtl8366_vlan_mc vlanmc;
4768c2ecf20Sopenharmony_ci
4778c2ecf20Sopenharmony_ci			ret = smi->ops->get_vlan_mc(smi, i, &vlanmc);
4788c2ecf20Sopenharmony_ci			if (ret)
4798c2ecf20Sopenharmony_ci				return ret;
4808c2ecf20Sopenharmony_ci
4818c2ecf20Sopenharmony_ci			if (vid == vlanmc.vid) {
4828c2ecf20Sopenharmony_ci				/* Remove this port from the VLAN */
4838c2ecf20Sopenharmony_ci				vlanmc.member &= ~BIT(port);
4848c2ecf20Sopenharmony_ci				vlanmc.untag &= ~BIT(port);
4858c2ecf20Sopenharmony_ci				/*
4868c2ecf20Sopenharmony_ci				 * If no ports are members of this VLAN
4878c2ecf20Sopenharmony_ci				 * anymore then clear the whole member
4888c2ecf20Sopenharmony_ci				 * config so it can be reused.
4898c2ecf20Sopenharmony_ci				 */
4908c2ecf20Sopenharmony_ci				if (!vlanmc.member && vlanmc.untag) {
4918c2ecf20Sopenharmony_ci					vlanmc.vid = 0;
4928c2ecf20Sopenharmony_ci					vlanmc.priority = 0;
4938c2ecf20Sopenharmony_ci					vlanmc.fid = 0;
4948c2ecf20Sopenharmony_ci				}
4958c2ecf20Sopenharmony_ci				ret = smi->ops->set_vlan_mc(smi, i, &vlanmc);
4968c2ecf20Sopenharmony_ci				if (ret) {
4978c2ecf20Sopenharmony_ci					dev_err(smi->dev,
4988c2ecf20Sopenharmony_ci						"failed to remove VLAN %04x\n",
4998c2ecf20Sopenharmony_ci						vid);
5008c2ecf20Sopenharmony_ci					return ret;
5018c2ecf20Sopenharmony_ci				}
5028c2ecf20Sopenharmony_ci				break;
5038c2ecf20Sopenharmony_ci			}
5048c2ecf20Sopenharmony_ci		}
5058c2ecf20Sopenharmony_ci	}
5068c2ecf20Sopenharmony_ci
5078c2ecf20Sopenharmony_ci	return 0;
5088c2ecf20Sopenharmony_ci}
5098c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(rtl8366_vlan_del);
5108c2ecf20Sopenharmony_ci
5118c2ecf20Sopenharmony_civoid rtl8366_get_strings(struct dsa_switch *ds, int port, u32 stringset,
5128c2ecf20Sopenharmony_ci			 uint8_t *data)
5138c2ecf20Sopenharmony_ci{
5148c2ecf20Sopenharmony_ci	struct realtek_smi *smi = ds->priv;
5158c2ecf20Sopenharmony_ci	struct rtl8366_mib_counter *mib;
5168c2ecf20Sopenharmony_ci	int i;
5178c2ecf20Sopenharmony_ci
5188c2ecf20Sopenharmony_ci	if (port >= smi->num_ports)
5198c2ecf20Sopenharmony_ci		return;
5208c2ecf20Sopenharmony_ci
5218c2ecf20Sopenharmony_ci	for (i = 0; i < smi->num_mib_counters; i++) {
5228c2ecf20Sopenharmony_ci		mib = &smi->mib_counters[i];
5238c2ecf20Sopenharmony_ci		strncpy(data + i * ETH_GSTRING_LEN,
5248c2ecf20Sopenharmony_ci			mib->name, ETH_GSTRING_LEN);
5258c2ecf20Sopenharmony_ci	}
5268c2ecf20Sopenharmony_ci}
5278c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(rtl8366_get_strings);
5288c2ecf20Sopenharmony_ci
5298c2ecf20Sopenharmony_ciint rtl8366_get_sset_count(struct dsa_switch *ds, int port, int sset)
5308c2ecf20Sopenharmony_ci{
5318c2ecf20Sopenharmony_ci	struct realtek_smi *smi = ds->priv;
5328c2ecf20Sopenharmony_ci
5338c2ecf20Sopenharmony_ci	/* We only support SS_STATS */
5348c2ecf20Sopenharmony_ci	if (sset != ETH_SS_STATS)
5358c2ecf20Sopenharmony_ci		return 0;
5368c2ecf20Sopenharmony_ci	if (port >= smi->num_ports)
5378c2ecf20Sopenharmony_ci		return -EINVAL;
5388c2ecf20Sopenharmony_ci
5398c2ecf20Sopenharmony_ci	return smi->num_mib_counters;
5408c2ecf20Sopenharmony_ci}
5418c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(rtl8366_get_sset_count);
5428c2ecf20Sopenharmony_ci
5438c2ecf20Sopenharmony_civoid rtl8366_get_ethtool_stats(struct dsa_switch *ds, int port, uint64_t *data)
5448c2ecf20Sopenharmony_ci{
5458c2ecf20Sopenharmony_ci	struct realtek_smi *smi = ds->priv;
5468c2ecf20Sopenharmony_ci	int i;
5478c2ecf20Sopenharmony_ci	int ret;
5488c2ecf20Sopenharmony_ci
5498c2ecf20Sopenharmony_ci	if (port >= smi->num_ports)
5508c2ecf20Sopenharmony_ci		return;
5518c2ecf20Sopenharmony_ci
5528c2ecf20Sopenharmony_ci	for (i = 0; i < smi->num_mib_counters; i++) {
5538c2ecf20Sopenharmony_ci		struct rtl8366_mib_counter *mib;
5548c2ecf20Sopenharmony_ci		u64 mibvalue = 0;
5558c2ecf20Sopenharmony_ci
5568c2ecf20Sopenharmony_ci		mib = &smi->mib_counters[i];
5578c2ecf20Sopenharmony_ci		ret = smi->ops->get_mib_counter(smi, port, mib, &mibvalue);
5588c2ecf20Sopenharmony_ci		if (ret) {
5598c2ecf20Sopenharmony_ci			dev_err(smi->dev, "error reading MIB counter %s\n",
5608c2ecf20Sopenharmony_ci				mib->name);
5618c2ecf20Sopenharmony_ci		}
5628c2ecf20Sopenharmony_ci		data[i] = mibvalue;
5638c2ecf20Sopenharmony_ci	}
5648c2ecf20Sopenharmony_ci}
5658c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(rtl8366_get_ethtool_stats);
566