Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,52 @@
# SPDX-License-Identifier: GPL-2.0-only
menuconfig NET_DSA_REALTEK
tristate "Realtek Ethernet switch family support"
depends on NET_DSA
select FIXED_PHY
select IRQ_DOMAIN
select REALTEK_PHY
select REGMAP
help
Select to enable support for Realtek Ethernet switch chips.
Note that at least one interface driver must be enabled for the
subdrivers to be loaded. Moreover, an interface driver cannot achieve
anything without at least one subdriver enabled.
if NET_DSA_REALTEK
config NET_DSA_REALTEK_MDIO
bool "Realtek MDIO interface support"
depends on OF
help
Select to enable support for registering switches configured
through MDIO.
config NET_DSA_REALTEK_SMI
bool "Realtek SMI interface support"
depends on OF
help
Select to enable support for registering switches connected
through SMI.
config NET_DSA_REALTEK_RTL8365MB
tristate "Realtek RTL8365MB switch driver"
depends on NET_DSA_REALTEK_SMI || NET_DSA_REALTEK_MDIO
select NET_DSA_TAG_RTL8_4
help
Select to enable support for Realtek RTL8365MB-VC and RTL8367S.
config NET_DSA_REALTEK_RTL8366RB
tristate "Realtek RTL8366RB switch driver"
depends on NET_DSA_REALTEK_SMI || NET_DSA_REALTEK_MDIO
select NET_DSA_TAG_RTL4_A
help
Select to enable support for Realtek RTL8366RB.
config NET_DSA_REALTEK_RTL8366RB_LEDS
bool
depends on (LEDS_CLASS=y || LEDS_CLASS=NET_DSA_REALTEK_RTL8366RB)
depends on NET_DSA_REALTEK_RTL8366RB
default NET_DSA_REALTEK_RTL8366RB
endif
@@ -0,0 +1,23 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_NET_DSA_REALTEK) += realtek_dsa.o
realtek_dsa-objs := rtl83xx.o
ifdef CONFIG_NET_DSA_REALTEK_MDIO
realtek_dsa-objs += realtek-mdio.o
endif
ifdef CONFIG_NET_DSA_REALTEK_SMI
realtek_dsa-objs += realtek-smi.o
endif
obj-$(CONFIG_NET_DSA_REALTEK_RTL8366RB) += rtl8366.o
rtl8366-objs := rtl8366-core.o rtl8366rb.o
ifdef CONFIG_NET_DSA_REALTEK_RTL8366RB_LEDS
rtl8366-objs += rtl8366rb-leds.o
endif
obj-$(CONFIG_NET_DSA_REALTEK_RTL8365MB) += rtl8365mb.o
rtl8365mb-objs := rtl8365mb_main.o \
rtl8365mb_table.o \
rtl8365mb_vlan.o \
rtl8365mb_l2.o \
# end of rtl8365mb-objs
@@ -0,0 +1,187 @@
// SPDX-License-Identifier: GPL-2.0+
/* Realtek MDIO interface driver
*
* ASICs we intend to support with this driver:
*
* RTL8366 - The original version, apparently
* RTL8369 - Similar enough to have the same datsheet as RTL8366
* RTL8366RB - Probably reads out "RTL8366 revision B", has a quite
* different register layout from the other two
* RTL8366S - Is this "RTL8366 super"?
* RTL8367 - Has an OpenWRT driver as well
* RTL8368S - Seems to be an alternative name for RTL8366RB
* RTL8370 - Also uses SMI
*
* Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org>
* Copyright (C) 2010 Antti Seppälä <a.seppala@gmail.com>
* Copyright (C) 2010 Roman Yeryomin <roman@advem.lv>
* Copyright (C) 2011 Colin Leitner <colin.leitner@googlemail.com>
* Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
*/
#include <linux/module.h>
#include <linux/of.h>
#include <linux/overflow.h>
#include <linux/regmap.h>
#include "realtek.h"
#include "realtek-mdio.h"
#include "rtl83xx.h"
/* Read/write via mdiobus */
#define REALTEK_MDIO_CTRL0_REG 31
#define REALTEK_MDIO_START_REG 29
#define REALTEK_MDIO_CTRL1_REG 21
#define REALTEK_MDIO_ADDRESS_REG 23
#define REALTEK_MDIO_DATA_WRITE_REG 24
#define REALTEK_MDIO_DATA_READ_REG 25
#define REALTEK_MDIO_START_OP 0xFFFF
#define REALTEK_MDIO_ADDR_OP 0x000E
#define REALTEK_MDIO_READ_OP 0x0001
#define REALTEK_MDIO_WRITE_OP 0x0003
static int realtek_mdio_write(void *ctx, u32 reg, u32 val)
{
struct realtek_priv *priv = ctx;
struct mii_bus *bus = priv->bus;
int ret;
mutex_lock(&bus->mdio_lock);
ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_CTRL0_REG, REALTEK_MDIO_ADDR_OP);
if (ret)
goto out_unlock;
ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_ADDRESS_REG, reg);
if (ret)
goto out_unlock;
ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_DATA_WRITE_REG, val);
if (ret)
goto out_unlock;
ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_CTRL1_REG, REALTEK_MDIO_WRITE_OP);
out_unlock:
mutex_unlock(&bus->mdio_lock);
return ret;
}
static int realtek_mdio_read(void *ctx, u32 reg, u32 *val)
{
struct realtek_priv *priv = ctx;
struct mii_bus *bus = priv->bus;
int ret;
mutex_lock(&bus->mdio_lock);
ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_CTRL0_REG, REALTEK_MDIO_ADDR_OP);
if (ret)
goto out_unlock;
ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_ADDRESS_REG, reg);
if (ret)
goto out_unlock;
ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_CTRL1_REG, REALTEK_MDIO_READ_OP);
if (ret)
goto out_unlock;
ret = bus->read(bus, priv->mdio_addr, REALTEK_MDIO_DATA_READ_REG);
if (ret >= 0) {
*val = ret;
ret = 0;
}
out_unlock:
mutex_unlock(&bus->mdio_lock);
return ret;
}
static const struct realtek_interface_info realtek_mdio_info = {
.reg_read = realtek_mdio_read,
.reg_write = realtek_mdio_write,
};
/**
* realtek_mdio_probe() - Probe a platform device for an MDIO-connected switch
* @mdiodev: mdio_device to probe on.
*
* This function should be used as the .probe in an mdio_driver. After
* calling the common probe function for both interfaces, it initializes the
* values specific for MDIO-connected devices. Finally, it calls a common
* function to register the DSA switch.
*
* Context: Can sleep. Takes and releases priv->map_lock.
* Return: Returns 0 on success, a negative error on failure.
*/
int realtek_mdio_probe(struct mdio_device *mdiodev)
{
struct device *dev = &mdiodev->dev;
struct realtek_priv *priv;
int ret;
priv = rtl83xx_probe(dev, &realtek_mdio_info);
if (IS_ERR(priv))
return PTR_ERR(priv);
priv->bus = mdiodev->bus;
priv->mdio_addr = mdiodev->addr;
priv->write_reg_noack = realtek_mdio_write;
ret = rtl83xx_register_switch(priv);
if (ret) {
rtl83xx_remove(priv);
return ret;
}
return 0;
}
EXPORT_SYMBOL_NS_GPL(realtek_mdio_probe, "REALTEK_DSA");
/**
* realtek_mdio_remove() - Remove the driver of an MDIO-connected switch
* @mdiodev: mdio_device to be removed.
*
* This function should be used as the .remove in an mdio_driver. First
* it unregisters the DSA switch and then it calls the common remove function.
*
* Context: Can sleep.
* Return: Nothing.
*/
void realtek_mdio_remove(struct mdio_device *mdiodev)
{
struct realtek_priv *priv = dev_get_drvdata(&mdiodev->dev);
if (!priv)
return;
rtl83xx_unregister_switch(priv);
rtl83xx_remove(priv);
}
EXPORT_SYMBOL_NS_GPL(realtek_mdio_remove, "REALTEK_DSA");
/**
* realtek_mdio_shutdown() - Shutdown the driver of a MDIO-connected switch
* @mdiodev: mdio_device shutting down.
*
* This function should be used as the .shutdown in a platform_driver. It calls
* the common shutdown function.
*
* Context: Can sleep.
* Return: Nothing.
*/
void realtek_mdio_shutdown(struct mdio_device *mdiodev)
{
struct realtek_priv *priv = dev_get_drvdata(&mdiodev->dev);
if (!priv)
return;
rtl83xx_shutdown(priv);
}
EXPORT_SYMBOL_NS_GPL(realtek_mdio_shutdown, "REALTEK_DSA");
@@ -0,0 +1,48 @@
/* SPDX-License-Identifier: GPL-2.0+ */
#ifndef _REALTEK_MDIO_H
#define _REALTEK_MDIO_H
#if IS_ENABLED(CONFIG_NET_DSA_REALTEK_MDIO)
static inline int realtek_mdio_driver_register(struct mdio_driver *drv)
{
return mdio_driver_register(drv);
}
static inline void realtek_mdio_driver_unregister(struct mdio_driver *drv)
{
mdio_driver_unregister(drv);
}
int realtek_mdio_probe(struct mdio_device *mdiodev);
void realtek_mdio_remove(struct mdio_device *mdiodev);
void realtek_mdio_shutdown(struct mdio_device *mdiodev);
#else /* IS_ENABLED(CONFIG_NET_DSA_REALTEK_MDIO) */
static inline int realtek_mdio_driver_register(struct mdio_driver *drv)
{
return 0;
}
static inline void realtek_mdio_driver_unregister(struct mdio_driver *drv)
{
}
static inline int realtek_mdio_probe(struct mdio_device *mdiodev)
{
return -ENOENT;
}
static inline void realtek_mdio_remove(struct mdio_device *mdiodev)
{
}
static inline void realtek_mdio_shutdown(struct mdio_device *mdiodev)
{
}
#endif /* IS_ENABLED(CONFIG_NET_DSA_REALTEK_MDIO) */
#endif /* _REALTEK_MDIO_H */
@@ -0,0 +1,408 @@
// SPDX-License-Identifier: GPL-2.0+
/* Realtek Simple Management Interface (SMI) driver
* It can be discussed how "simple" this interface is.
*
* The SMI protocol piggy-backs the MDIO MDC and MDIO signals levels
* but the protocol is not MDIO at all. Instead it is a Realtek
* pecularity that need to bit-bang the lines in a special way to
* communicate with the switch.
*
* ASICs we intend to support with this driver:
*
* RTL8366 - The original version, apparently
* RTL8369 - Similar enough to have the same datsheet as RTL8366
* RTL8366RB - Probably reads out "RTL8366 revision B", has a quite
* different register layout from the other two
* RTL8366S - Is this "RTL8366 super"?
* RTL8367 - Has an OpenWRT driver as well
* RTL8368S - Seems to be an alternative name for RTL8366RB
* RTL8370 - Also uses SMI
*
* Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org>
* Copyright (C) 2010 Antti Seppälä <a.seppala@gmail.com>
* Copyright (C) 2010 Roman Yeryomin <roman@advem.lv>
* Copyright (C) 2011 Colin Leitner <colin.leitner@googlemail.com>
* Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/spinlock.h>
#include <linux/skbuff.h>
#include <linux/of.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/bitops.h>
#include <linux/if_bridge.h>
#include "realtek.h"
#include "realtek-smi.h"
#include "rtl83xx.h"
#define REALTEK_SMI_ACK_RETRY_COUNT 5
static inline void realtek_smi_clk_delay(struct realtek_priv *priv)
{
ndelay(priv->variant->clk_delay);
}
static void realtek_smi_start(struct realtek_priv *priv)
{
/* Set GPIO pins to output mode, with initial state:
* SCK = 0, SDA = 1
*/
gpiod_direction_output(priv->mdc, 0);
gpiod_direction_output(priv->mdio, 1);
realtek_smi_clk_delay(priv);
/* CLK 1: 0 -> 1, 1 -> 0 */
gpiod_set_value(priv->mdc, 1);
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdc, 0);
realtek_smi_clk_delay(priv);
/* CLK 2: */
gpiod_set_value(priv->mdc, 1);
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdio, 0);
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdc, 0);
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdio, 1);
}
static void realtek_smi_stop(struct realtek_priv *priv)
{
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdio, 0);
gpiod_set_value(priv->mdc, 1);
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdio, 1);
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdc, 1);
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdc, 0);
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdc, 1);
/* Add a click */
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdc, 0);
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdc, 1);
/* Set GPIO pins to input mode */
gpiod_direction_input(priv->mdio);
gpiod_direction_input(priv->mdc);
}
static void realtek_smi_write_bits(struct realtek_priv *priv, u32 data, u32 len)
{
for (; len > 0; len--) {
realtek_smi_clk_delay(priv);
/* Prepare data */
gpiod_set_value(priv->mdio, !!(data & (1 << (len - 1))));
realtek_smi_clk_delay(priv);
/* Clocking */
gpiod_set_value(priv->mdc, 1);
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdc, 0);
}
}
static void realtek_smi_read_bits(struct realtek_priv *priv, u32 len, u32 *data)
{
gpiod_direction_input(priv->mdio);
for (*data = 0; len > 0; len--) {
u32 u;
realtek_smi_clk_delay(priv);
/* Clocking */
gpiod_set_value(priv->mdc, 1);
realtek_smi_clk_delay(priv);
u = !!gpiod_get_value(priv->mdio);
gpiod_set_value(priv->mdc, 0);
*data |= (u << (len - 1));
}
gpiod_direction_output(priv->mdio, 0);
}
static int realtek_smi_wait_for_ack(struct realtek_priv *priv)
{
int retry_cnt;
retry_cnt = 0;
do {
u32 ack;
realtek_smi_read_bits(priv, 1, &ack);
if (ack == 0)
break;
if (++retry_cnt > REALTEK_SMI_ACK_RETRY_COUNT) {
dev_err(priv->dev, "ACK timeout\n");
return -ETIMEDOUT;
}
} while (1);
return 0;
}
static int realtek_smi_write_byte(struct realtek_priv *priv, u8 data)
{
realtek_smi_write_bits(priv, data, 8);
return realtek_smi_wait_for_ack(priv);
}
static int realtek_smi_write_byte_noack(struct realtek_priv *priv, u8 data)
{
realtek_smi_write_bits(priv, data, 8);
return 0;
}
static int realtek_smi_read_byte0(struct realtek_priv *priv, u8 *data)
{
u32 t;
/* Read data */
realtek_smi_read_bits(priv, 8, &t);
*data = (t & 0xff);
/* Send an ACK */
realtek_smi_write_bits(priv, 0x00, 1);
return 0;
}
static int realtek_smi_read_byte1(struct realtek_priv *priv, u8 *data)
{
u32 t;
/* Read data */
realtek_smi_read_bits(priv, 8, &t);
*data = (t & 0xff);
/* Send an ACK */
realtek_smi_write_bits(priv, 0x01, 1);
return 0;
}
static int realtek_smi_read_reg(struct realtek_priv *priv, u32 addr, u32 *data)
{
unsigned long flags;
u8 lo = 0;
u8 hi = 0;
int ret;
spin_lock_irqsave(&priv->lock, flags);
realtek_smi_start(priv);
/* Send READ command */
ret = realtek_smi_write_byte(priv, priv->variant->cmd_read);
if (ret)
goto out;
/* Set ADDR[7:0] */
ret = realtek_smi_write_byte(priv, addr & 0xff);
if (ret)
goto out;
/* Set ADDR[15:8] */
ret = realtek_smi_write_byte(priv, addr >> 8);
if (ret)
goto out;
/* Read DATA[7:0] */
realtek_smi_read_byte0(priv, &lo);
/* Read DATA[15:8] */
realtek_smi_read_byte1(priv, &hi);
*data = ((u32)lo) | (((u32)hi) << 8);
ret = 0;
out:
realtek_smi_stop(priv);
spin_unlock_irqrestore(&priv->lock, flags);
return ret;
}
static int realtek_smi_write_reg(struct realtek_priv *priv,
u32 addr, u32 data, bool ack)
{
unsigned long flags;
int ret;
spin_lock_irqsave(&priv->lock, flags);
realtek_smi_start(priv);
/* Send WRITE command */
ret = realtek_smi_write_byte(priv, priv->variant->cmd_write);
if (ret)
goto out;
/* Set ADDR[7:0] */
ret = realtek_smi_write_byte(priv, addr & 0xff);
if (ret)
goto out;
/* Set ADDR[15:8] */
ret = realtek_smi_write_byte(priv, addr >> 8);
if (ret)
goto out;
/* Write DATA[7:0] */
ret = realtek_smi_write_byte(priv, data & 0xff);
if (ret)
goto out;
/* Write DATA[15:8] */
if (ack)
ret = realtek_smi_write_byte(priv, data >> 8);
else
ret = realtek_smi_write_byte_noack(priv, data >> 8);
if (ret)
goto out;
ret = 0;
out:
realtek_smi_stop(priv);
spin_unlock_irqrestore(&priv->lock, flags);
return ret;
}
/* There is one single case when we need to use this accessor and that
* is when issueing soft reset. Since the device reset as soon as we write
* that bit, no ACK will come back for natural reasons.
*/
static int realtek_smi_write_reg_noack(void *ctx, u32 reg, u32 val)
{
return realtek_smi_write_reg(ctx, reg, val, false);
}
/* Regmap accessors */
static int realtek_smi_write(void *ctx, u32 reg, u32 val)
{
struct realtek_priv *priv = ctx;
return realtek_smi_write_reg(priv, reg, val, true);
}
static int realtek_smi_read(void *ctx, u32 reg, u32 *val)
{
struct realtek_priv *priv = ctx;
return realtek_smi_read_reg(priv, reg, val);
}
static const struct realtek_interface_info realtek_smi_info = {
.reg_read = realtek_smi_read,
.reg_write = realtek_smi_write,
};
/**
* realtek_smi_probe() - Probe a platform device for an SMI-connected switch
* @pdev: platform_device to probe on.
*
* This function should be used as the .probe in a platform_driver. After
* calling the common probe function for both interfaces, it initializes the
* values specific for SMI-connected devices. Finally, it calls a common
* function to register the DSA switch.
*
* Context: Can sleep. Takes and releases priv->map_lock.
* Return: Returns 0 on success, a negative error on failure.
*/
int realtek_smi_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct realtek_priv *priv;
int ret;
priv = rtl83xx_probe(dev, &realtek_smi_info);
if (IS_ERR(priv))
return PTR_ERR(priv);
/* Fetch MDIO pins */
priv->mdc = devm_gpiod_get_optional(dev, "mdc", GPIOD_OUT_LOW);
if (IS_ERR(priv->mdc)) {
rtl83xx_remove(priv);
return PTR_ERR(priv->mdc);
}
priv->mdio = devm_gpiod_get_optional(dev, "mdio", GPIOD_OUT_LOW);
if (IS_ERR(priv->mdio)) {
rtl83xx_remove(priv);
return PTR_ERR(priv->mdio);
}
priv->write_reg_noack = realtek_smi_write_reg_noack;
ret = rtl83xx_register_switch(priv);
if (ret) {
rtl83xx_remove(priv);
return ret;
}
return 0;
}
EXPORT_SYMBOL_NS_GPL(realtek_smi_probe, "REALTEK_DSA");
/**
* realtek_smi_remove() - Remove the driver of a SMI-connected switch
* @pdev: platform_device to be removed.
*
* This function should be used as the .remove in a platform_driver. First
* it unregisters the DSA switch and then it calls the common remove function.
*
* Context: Can sleep.
* Return: Nothing.
*/
void realtek_smi_remove(struct platform_device *pdev)
{
struct realtek_priv *priv = platform_get_drvdata(pdev);
if (!priv)
return;
rtl83xx_unregister_switch(priv);
rtl83xx_remove(priv);
}
EXPORT_SYMBOL_NS_GPL(realtek_smi_remove, "REALTEK_DSA");
/**
* realtek_smi_shutdown() - Shutdown the driver of a SMI-connected switch
* @pdev: platform_device shutting down.
*
* This function should be used as the .shutdown in a platform_driver. It calls
* the common shutdown function.
*
* Context: Can sleep.
* Return: Nothing.
*/
void realtek_smi_shutdown(struct platform_device *pdev)
{
struct realtek_priv *priv = platform_get_drvdata(pdev);
if (!priv)
return;
rtl83xx_shutdown(priv);
}
EXPORT_SYMBOL_NS_GPL(realtek_smi_shutdown, "REALTEK_DSA");
@@ -0,0 +1,48 @@
/* SPDX-License-Identifier: GPL-2.0+ */
#ifndef _REALTEK_SMI_H
#define _REALTEK_SMI_H
#if IS_ENABLED(CONFIG_NET_DSA_REALTEK_SMI)
static inline int realtek_smi_driver_register(struct platform_driver *drv)
{
return platform_driver_register(drv);
}
static inline void realtek_smi_driver_unregister(struct platform_driver *drv)
{
platform_driver_unregister(drv);
}
int realtek_smi_probe(struct platform_device *pdev);
void realtek_smi_remove(struct platform_device *pdev);
void realtek_smi_shutdown(struct platform_device *pdev);
#else /* IS_ENABLED(CONFIG_NET_DSA_REALTEK_SMI) */
static inline int realtek_smi_driver_register(struct platform_driver *drv)
{
return 0;
}
static inline void realtek_smi_driver_unregister(struct platform_driver *drv)
{
}
static inline int realtek_smi_probe(struct platform_device *pdev)
{
return -ENOENT;
}
static inline void realtek_smi_remove(struct platform_device *pdev)
{
}
static inline void realtek_smi_shutdown(struct platform_device *pdev)
{
}
#endif /* IS_ENABLED(CONFIG_NET_DSA_REALTEK_SMI) */
#endif /* _REALTEK_SMI_H */
@@ -0,0 +1,193 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/* Realtek SMI interface driver defines
*
* Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org>
* Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
*/
#ifndef _REALTEK_H
#define _REALTEK_H
#include <linux/phy.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <net/dsa.h>
#include <linux/reset.h>
#define REALTEK_HW_STOP_DELAY 25 /* msecs */
#define REALTEK_HW_START_DELAY 100 /* msecs */
struct phylink_mac_ops;
struct realtek_ops;
struct rtl8366_mib_counter {
unsigned int base;
unsigned int offset;
unsigned int length;
const char *name;
};
/*
* struct rtl8366_vlan_mc - Virtual LAN member configuration
*/
struct rtl8366_vlan_mc {
u16 vid;
u16 untag;
u16 member;
u8 fid;
u8 priority;
};
struct rtl8366_vlan_4k {
u16 vid;
u16 untag;
u16 member;
u8 fid;
};
struct realtek_fdb_entry {
u8 mac_addr[ETH_ALEN];
u16 vid;
bool is_static;
};
struct realtek_priv {
struct device *dev;
struct reset_control *reset_ctl;
struct gpio_desc *reset;
struct gpio_desc *mdc;
struct gpio_desc *mdio;
struct regmap *map;
struct regmap *map_nolock;
struct mutex map_lock;
/* vlan_lock protects against concurrent Read-Modify-Write operations
* on the global VLAN 4K and VLANMC tables, such as when adding or
* deleting port VLAN memberships and PVID configurations.
*/
struct mutex vlan_lock;
/* l2_lock is used to prevent concurrent modifications of L2 table
* entries while another function is reading it. l2_(add,del)_mc
* is an example that first read current table entry and then
* create/update it. l2_(add|del)_uc uses a single table op and,
* internally, it might not need this lock. However, altering FDB
* may still collide, as well as l2_flush, with fdb_dump iterating
* over FDB.
*/
struct mutex l2_lock;
struct mii_bus *user_mii_bus;
struct mii_bus *bus;
int mdio_addr;
const struct realtek_variant *variant;
spinlock_t lock; /* Locks around command writes */
struct dsa_switch ds;
struct irq_domain *irqdomain;
bool leds_disabled;
unsigned int cpu_port;
unsigned int num_ports;
unsigned int num_vlan_mc;
unsigned int num_mib_counters;
struct rtl8366_mib_counter *mib_counters;
const struct realtek_ops *ops;
int (*write_reg_noack)(void *ctx, u32 addr, u32 data);
int vlan_enabled;
int vlan4k_enabled;
char buf[4096];
void *chip_data; /* Per-chip extra variant data */
};
/*
* struct realtek_ops - vtable for the per-SMI-chiptype operations
* @detect: detects the chiptype
*/
struct realtek_ops {
int (*detect)(struct realtek_priv *priv);
int (*reset_chip)(struct realtek_priv *priv);
int (*setup)(struct realtek_priv *priv);
int (*get_mib_counter)(struct realtek_priv *priv,
int port,
struct rtl8366_mib_counter *mib,
u64 *mibvalue);
int (*get_vlan_mc)(struct realtek_priv *priv, u32 index,
struct rtl8366_vlan_mc *vlanmc);
int (*set_vlan_mc)(struct realtek_priv *priv, u32 index,
const struct rtl8366_vlan_mc *vlanmc);
int (*get_vlan_4k)(struct realtek_priv *priv, u32 vid,
struct rtl8366_vlan_4k *vlan4k);
int (*set_vlan_4k)(struct realtek_priv *priv,
const struct rtl8366_vlan_4k *vlan4k);
int (*get_mc_index)(struct realtek_priv *priv, int port, int *val);
int (*set_mc_index)(struct realtek_priv *priv, int port, int index);
bool (*is_vlan_valid)(struct realtek_priv *priv, unsigned int vlan);
int (*enable_vlan)(struct realtek_priv *priv, bool enable);
int (*enable_vlan4k)(struct realtek_priv *priv, bool enable);
int (*enable_port)(struct realtek_priv *priv, int port, bool enable);
int (*port_add_isolation)(struct realtek_priv *priv, int port,
u32 mask);
int (*port_remove_isolation)(struct realtek_priv *priv, int port,
u32 mask);
int (*port_set_efid)(struct realtek_priv *priv, int port, u32 efid);
int (*port_set_learning)(struct realtek_priv *priv, int port,
bool enable);
int (*port_set_ucast_flood)(struct realtek_priv *priv, int port,
bool enable);
int (*port_set_mcast_flood)(struct realtek_priv *priv, int port,
bool enable);
int (*port_set_bcast_flood)(struct realtek_priv *priv, int port,
bool enable);
int (*l2_add_uc)(struct realtek_priv *priv, int port,
const unsigned char addr[ETH_ALEN],
u16 efid, u16 vid);
int (*l2_del_uc)(struct realtek_priv *priv, int port,
const unsigned char addr[ETH_ALEN],
u16 efid, u16 vid);
int (*l2_get_next_uc)(struct realtek_priv *priv, u16 *addr,
int port, struct realtek_fdb_entry *entry);
int (*l2_add_mc)(struct realtek_priv *priv, int port,
const unsigned char addr[ETH_ALEN], u16 vid);
int (*l2_del_mc)(struct realtek_priv *priv, int port,
const unsigned char addr[ETH_ALEN], u16 vid);
int (*l2_flush)(struct realtek_priv *priv, int port, u16 vid);
int (*phy_read)(struct realtek_priv *priv, int phy, int regnum);
int (*phy_write)(struct realtek_priv *priv, int phy, int regnum,
u16 val);
};
struct realtek_variant {
const struct dsa_switch_ops *ds_ops;
const struct realtek_ops *ops;
const struct phylink_mac_ops *phylink_mac_ops;
unsigned int clk_delay;
u8 cmd_read;
u8 cmd_write;
size_t chip_data_sz;
};
/* RTL8366 library helpers */
int rtl8366_mc_is_used(struct realtek_priv *priv, int mc_index, int *used);
int rtl8366_set_vlan(struct realtek_priv *priv, int vid, u32 member,
u32 untag, u32 fid);
int rtl8366_set_pvid(struct realtek_priv *priv, unsigned int port,
unsigned int vid);
int rtl8366_enable_vlan4k(struct realtek_priv *priv, bool enable);
int rtl8366_enable_vlan(struct realtek_priv *priv, bool enable);
int rtl8366_reset_vlan(struct realtek_priv *priv);
int rtl8366_vlan_add(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_vlan *vlan,
struct netlink_ext_ack *extack);
int rtl8366_vlan_del(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_vlan *vlan);
void rtl8366_get_strings(struct dsa_switch *ds, int port, u32 stringset,
uint8_t *data);
int rtl8366_get_sset_count(struct dsa_switch *ds, int port, int sset);
void rtl8366_get_ethtool_stats(struct dsa_switch *ds, int port, uint64_t *data);
extern const struct realtek_variant rtl8366rb_variant;
extern const struct realtek_variant rtl8365mb_variant;
#endif /* _REALTEK_H */
@@ -0,0 +1,576 @@
// SPDX-License-Identifier: GPL-2.0
/* Forwarding and multicast database interface for the rtl8365mb switch family
*
* Copyright (C) 2022 Alvin Šipraga <alsi@bang-olufsen.dk>
*/
#include <linux/etherdevice.h>
#include "rtl8365mb_l2.h"
#include "rtl8365mb_table.h"
#include <linux/regmap.h>
#define RTL8365MB_L2_ENTRY_SIZE 6
#define RTL8365MB_L2_UC_D0_MAC5_MSK GENMASK(7, 0)
#define RTL8365MB_L2_UC_D0_MAC4_MSK GENMASK(15, 8)
#define RTL8365MB_L2_UC_D1_MAC3_MSK GENMASK(7, 0)
#define RTL8365MB_L2_UC_D1_MAC2_MSK GENMASK(15, 8)
#define RTL8365MB_L2_UC_D2_MAC1_MSK GENMASK(7, 0)
#define RTL8365MB_L2_UC_D2_MAC0_MSK GENMASK(15, 8)
#define RTL8365MB_L2_UC_D3_VID_MSK GENMASK(11, 0)
#define RTL8365MB_L2_UC_D3_IVL_MSK GENMASK(13, 13)
#define RTL8365MB_L2_UC_D3_PORT_EXT_MSK GENMASK(15, 15)
#define RTL8365MB_L2_UC_PORT_HI_MSK GENMASK(3, 3)
#define RTL8365MB_L2_UC_D4_EFID_MSK GENMASK(2, 0)
#define RTL8365MB_L2_UC_D4_FID_MSK GENMASK(6, 3)
#define RTL8365MB_L2_UC_D4_SA_PRI_MSK GENMASK(7, 7)
#define RTL8365MB_L2_UC_D4_PORT_MSK GENMASK(10, 8)
#define RTL8365MB_L2_UC_PORT_LO_MSK GENMASK(2, 0)
#define RTL8365MB_L2_UC_D4_AGE_MSK GENMASK(13, 11)
#define RTL8365MB_L2_UC_D4_AUTH_MSK GENMASK(14, 14)
#define RTL8365MB_L2_UC_D4_SA_BLOCK_MSK GENMASK(15, 15)
#define RTL8365MB_L2_UC_D5_DA_BLOCK_MSK GENMASK(0, 0)
#define RTL8365MB_L2_UC_D5_PRIORITY_MSK GENMASK(3, 1)
#define RTL8365MB_L2_UC_D5_FWD_PRI_MSK GENMASK(4, 4)
#define RTL8365MB_L2_UC_D5_STATIC_MSK GENMASK(5, 5)
#define RTL8365MB_L2_MC_D0_MAC5_MSK GENMASK(7, 0)
#define RTL8365MB_L2_MC_D0_MAC4_MSK GENMASK(15, 8)
#define RTL8365MB_L2_MC_D1_MAC3_MSK GENMASK(7, 0)
#define RTL8365MB_L2_MC_D1_MAC2_MSK GENMASK(15, 8)
#define RTL8365MB_L2_MC_D2_MAC1_MSK GENMASK(7, 0)
#define RTL8365MB_L2_MC_D2_MAC0_MSK GENMASK(15, 8)
#define RTL8365MB_L2_MC_D3_VID_MSK GENMASK(11, 0)
#define RTL8365MB_L2_MC_D3_IVL_MSK GENMASK(13, 13)
#define RTL8365MB_L2_MC_D3_MBR_HI1_MSK GENMASK(15, 14)
#define RTL8365MB_L2_MC_MBR_HI1_MSK GENMASK(9, 8)
#define RTL8365MB_L2_MC_D4_MBR_MSK GENMASK(7, 0)
#define RTL8365MB_L2_MC_MBR_LO_MSK GENMASK(7, 0)
#define RTL8365MB_L2_MC_D4_IGMPIDX_MSK GENMASK(15, 8)
#define RTL8365MB_L2_MC_D5_IGMP_ASIC_MSK GENMASK(0, 0)
#define RTL8365MB_L2_MC_D5_PRIORITY_MSK GENMASK(3, 1)
#define RTL8365MB_L2_MC_D5_FWD_PRI_MSK GENMASK(4, 4)
#define RTL8365MB_L2_MC_D5_STATIC_MSK GENMASK(5, 5)
#define RTL8365MB_L2_MC_D5_MBR_HI2_MSK GENMASK(7, 7)
#define RTL8365MB_L2_MC_MBR_HI2_MSK GENMASK(10, 10)
/* Port flush command registers - writing a 1 to the port's MASK bit will
* initiate the flush procedure. Completion is signalled when the corresponding
* BUSY bit is 0.
*/
#define RTL8365MB_L2_FLUSH_PORT_REG 0x0A36
#define RTL8365MB_L2_FLUSH_PORT_MSK_MSK GENMASK(7, 0)
#define RTL8365MB_L2_FLUSH_PORT_BUSY_MSK GENMASK(15, 8)
#define RTL8365MB_L2_FLUSH_PORT_EXT_REG 0x0A35
#define RTL8365MB_L2_FLUSH_PORT_EXT_MSK_MSK GENMASK(2, 0)
#define RTL8365MB_L2_FLUSH_PORT_EXT_BUSY_MSK GENMASK(5, 3)
#define RTL8365MB_L2_FLUSH_CTRL1_REG 0x0A37
#define RTL8365MB_L2_FLUSH_CTRL1_VID_MSK GENMASK(11, 0)
#define RTL8365MB_L2_FLUSH_CTRL1_FID_MSK GENMASK(15, 12)
#define RTL8365MB_L2_FLUSH_CTRL2_REG 0x0A38
#define RTL8365MB_L2_FLUSH_CTRL2_MODE_MSK GENMASK(1, 0)
#define RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT 0
#define RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT_VID 1
#define RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT_FID 2
#define RTL8365MB_L2_FLUSH_CTRL2_TYPE_MSK GENMASK(2, 2)
#define RTL8365MB_L2_FLUSH_CTRL2_TYPE_DYNAMIC 0
#define RTL8365MB_L2_FLUSH_CTRL2_TYPE_BOTH 1
/* This flushes the entire LUT, reading it back it will turn 0 when the
* operation is complete
*/
#define RTL8365MB_L2_FLUSH_CTRL3_REG 0x0A39
#define RTL8365MB_L2_FLUSH_CTRL3_MSK GENMASK(0, 0)
struct rtl8365mb_l2_uc_key {
u8 mac_addr[ETH_ALEN];
u16 vid;
u16 fid;
bool ivl;
u16 efid;
};
struct rtl8365mb_l2_uc {
struct rtl8365mb_l2_uc_key key;
u8 port;
u8 age;
u8 priority;
bool sa_block;
bool da_block;
bool auth;
bool is_static;
bool sa_pri;
bool fwd_pri;
};
struct rtl8365mb_l2_mc_key {
u8 mac_addr[ETH_ALEN];
union {
u16 vid; /* IVL */
u16 fid; /* SVL */
};
bool ivl;
};
struct rtl8365mb_l2_mc {
struct rtl8365mb_l2_mc_key key;
u16 member;
u8 priority;
u8 igmpidx;
bool is_static;
bool fwd_pri;
bool igmp_asic;
};
static void rtl8365mb_l2_data_to_uc(const u16 *data, struct rtl8365mb_l2_uc *uc)
{
u32 val;
uc->key.mac_addr[5] = FIELD_GET(RTL8365MB_L2_UC_D0_MAC5_MSK, data[0]);
uc->key.mac_addr[4] = FIELD_GET(RTL8365MB_L2_UC_D0_MAC4_MSK, data[0]);
uc->key.mac_addr[3] = FIELD_GET(RTL8365MB_L2_UC_D1_MAC3_MSK, data[1]);
uc->key.mac_addr[2] = FIELD_GET(RTL8365MB_L2_UC_D1_MAC2_MSK, data[1]);
uc->key.mac_addr[1] = FIELD_GET(RTL8365MB_L2_UC_D2_MAC1_MSK, data[2]);
uc->key.mac_addr[0] = FIELD_GET(RTL8365MB_L2_UC_D2_MAC0_MSK, data[2]);
uc->key.efid = FIELD_GET(RTL8365MB_L2_UC_D4_EFID_MSK, data[4]);
uc->key.vid = FIELD_GET(RTL8365MB_L2_UC_D3_VID_MSK, data[3]);
uc->key.ivl = FIELD_GET(RTL8365MB_L2_UC_D3_IVL_MSK, data[3]);
uc->key.fid = FIELD_GET(RTL8365MB_L2_UC_D4_FID_MSK, data[4]);
uc->age = FIELD_GET(RTL8365MB_L2_UC_D4_AGE_MSK, data[4]);
uc->auth = FIELD_GET(RTL8365MB_L2_UC_D4_AUTH_MSK, data[4]);
val = FIELD_GET(RTL8365MB_L2_UC_D4_PORT_MSK, data[4]);
uc->port = FIELD_PREP(RTL8365MB_L2_UC_PORT_LO_MSK, val);
val = FIELD_GET(RTL8365MB_L2_UC_D3_PORT_EXT_MSK, data[3]);
uc->port |= FIELD_PREP(RTL8365MB_L2_UC_PORT_HI_MSK, val);
uc->sa_pri = FIELD_GET(RTL8365MB_L2_UC_D4_SA_PRI_MSK, data[4]);
uc->fwd_pri = FIELD_GET(RTL8365MB_L2_UC_D5_FWD_PRI_MSK, data[5]);
uc->sa_block = FIELD_GET(RTL8365MB_L2_UC_D4_SA_BLOCK_MSK, data[4]);
uc->da_block = FIELD_GET(RTL8365MB_L2_UC_D5_DA_BLOCK_MSK, data[5]);
uc->priority = FIELD_GET(RTL8365MB_L2_UC_D5_PRIORITY_MSK, data[5]);
uc->is_static = FIELD_GET(RTL8365MB_L2_UC_D5_STATIC_MSK, data[5]);
}
static void rtl8365mb_l2_uc_to_data(const struct rtl8365mb_l2_uc *uc, u16 *data)
{
u32 val;
memset(data, 0, RTL8365MB_L2_ENTRY_SIZE * 2);
data[0] |=
FIELD_PREP(RTL8365MB_L2_UC_D0_MAC5_MSK, uc->key.mac_addr[5]);
data[0] |=
FIELD_PREP(RTL8365MB_L2_UC_D0_MAC4_MSK, uc->key.mac_addr[4]);
data[1] |=
FIELD_PREP(RTL8365MB_L2_UC_D1_MAC3_MSK, uc->key.mac_addr[3]);
data[1] |=
FIELD_PREP(RTL8365MB_L2_UC_D1_MAC2_MSK, uc->key.mac_addr[2]);
data[2] |=
FIELD_PREP(RTL8365MB_L2_UC_D2_MAC1_MSK, uc->key.mac_addr[1]);
data[2] |=
FIELD_PREP(RTL8365MB_L2_UC_D2_MAC0_MSK, uc->key.mac_addr[0]);
data[3] |= FIELD_PREP(RTL8365MB_L2_UC_D3_VID_MSK, uc->key.vid);
data[3] |= FIELD_PREP(RTL8365MB_L2_UC_D3_IVL_MSK, uc->key.ivl);
val = FIELD_GET(RTL8365MB_L2_UC_PORT_HI_MSK, uc->port);
data[3] |= FIELD_PREP(RTL8365MB_L2_UC_D3_PORT_EXT_MSK, val);
data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_FID_MSK, uc->key.fid);
data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_EFID_MSK, uc->key.efid);
data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_AGE_MSK, uc->age);
data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_AUTH_MSK, uc->auth);
val = FIELD_GET(RTL8365MB_L2_UC_PORT_LO_MSK, uc->port);
data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_PORT_MSK, val);
data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_SA_PRI_MSK, uc->sa_pri);
data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_SA_BLOCK_MSK, uc->sa_block);
data[5] |= FIELD_PREP(RTL8365MB_L2_UC_D5_FWD_PRI_MSK, uc->fwd_pri);
data[5] |= FIELD_PREP(RTL8365MB_L2_UC_D5_DA_BLOCK_MSK, uc->da_block);
data[5] |= FIELD_PREP(RTL8365MB_L2_UC_D5_PRIORITY_MSK, uc->priority);
data[5] |= FIELD_PREP(RTL8365MB_L2_UC_D5_STATIC_MSK, uc->is_static);
}
static void rtl8365mb_l2_data_to_mc(const u16 *data, struct rtl8365mb_l2_mc *mc)
{
u32 val;
mc->key.mac_addr[5] = FIELD_GET(RTL8365MB_L2_MC_D0_MAC5_MSK, data[0]);
mc->key.mac_addr[4] = FIELD_GET(RTL8365MB_L2_MC_D0_MAC4_MSK, data[0]);
mc->key.mac_addr[3] = FIELD_GET(RTL8365MB_L2_MC_D1_MAC3_MSK, data[1]);
mc->key.mac_addr[2] = FIELD_GET(RTL8365MB_L2_MC_D1_MAC2_MSK, data[1]);
mc->key.mac_addr[1] = FIELD_GET(RTL8365MB_L2_MC_D2_MAC1_MSK, data[2]);
mc->key.mac_addr[0] = FIELD_GET(RTL8365MB_L2_MC_D2_MAC0_MSK, data[2]);
/* key.vid,key.fid shares the same memory space */
mc->key.vid = FIELD_GET(RTL8365MB_L2_MC_D3_VID_MSK, data[3]);
mc->key.ivl = FIELD_GET(RTL8365MB_L2_MC_D3_IVL_MSK, data[3]);
mc->priority = FIELD_GET(RTL8365MB_L2_MC_D5_PRIORITY_MSK, data[5]);
mc->fwd_pri = FIELD_GET(RTL8365MB_L2_MC_D5_FWD_PRI_MSK, data[5]);
mc->is_static = FIELD_GET(RTL8365MB_L2_MC_D5_STATIC_MSK, data[5]);
val = FIELD_GET(RTL8365MB_L2_MC_D4_MBR_MSK, data[4]);
mc->member = FIELD_PREP(RTL8365MB_L2_MC_MBR_LO_MSK, val);
val = FIELD_GET(RTL8365MB_L2_MC_D3_MBR_HI1_MSK, data[3]);
mc->member |= FIELD_PREP(RTL8365MB_L2_MC_MBR_HI1_MSK, val);
val = FIELD_GET(RTL8365MB_L2_MC_D5_MBR_HI2_MSK, data[5]);
mc->member |= FIELD_PREP(RTL8365MB_L2_MC_MBR_HI2_MSK, val);
mc->igmpidx = FIELD_GET(RTL8365MB_L2_MC_D4_IGMPIDX_MSK, data[4]);
mc->igmp_asic = FIELD_GET(RTL8365MB_L2_MC_D5_IGMP_ASIC_MSK, data[5]);
}
static void rtl8365mb_l2_mc_to_data(const struct rtl8365mb_l2_mc *mc, u16 *data)
{
u32 val;
memset(data, 0, RTL8365MB_L2_ENTRY_SIZE * 2);
data[0] |= FIELD_PREP(RTL8365MB_L2_MC_D0_MAC5_MSK, mc->key.mac_addr[5]);
data[0] |= FIELD_PREP(RTL8365MB_L2_MC_D0_MAC4_MSK, mc->key.mac_addr[4]);
data[1] |= FIELD_PREP(RTL8365MB_L2_MC_D1_MAC3_MSK, mc->key.mac_addr[3]);
data[1] |= FIELD_PREP(RTL8365MB_L2_MC_D1_MAC2_MSK, mc->key.mac_addr[2]);
data[2] |= FIELD_PREP(RTL8365MB_L2_MC_D2_MAC1_MSK, mc->key.mac_addr[1]);
data[2] |= FIELD_PREP(RTL8365MB_L2_MC_D2_MAC0_MSK, mc->key.mac_addr[0]);
data[3] |= FIELD_PREP(RTL8365MB_L2_MC_D3_VID_MSK, mc->key.vid);
data[3] |= FIELD_PREP(RTL8365MB_L2_MC_D3_IVL_MSK, mc->key.ivl);
val = FIELD_GET(RTL8365MB_L2_MC_MBR_HI1_MSK, mc->member);
data[3] |= FIELD_PREP(RTL8365MB_L2_MC_D3_MBR_HI1_MSK, val);
val = FIELD_GET(RTL8365MB_L2_MC_MBR_LO_MSK, mc->member);
data[4] |= FIELD_PREP(RTL8365MB_L2_MC_D4_MBR_MSK, val);
data[4] |= FIELD_PREP(RTL8365MB_L2_MC_D4_IGMPIDX_MSK, mc->igmpidx);
data[5] |= FIELD_PREP(RTL8365MB_L2_MC_D5_IGMP_ASIC_MSK, mc->igmp_asic);
data[5] |= FIELD_PREP(RTL8365MB_L2_MC_D5_PRIORITY_MSK, mc->priority);
data[5] |= FIELD_PREP(RTL8365MB_L2_MC_D5_FWD_PRI_MSK, mc->fwd_pri);
data[5] |= FIELD_PREP(RTL8365MB_L2_MC_D5_STATIC_MSK, mc->is_static);
val = FIELD_GET(RTL8365MB_L2_MC_MBR_HI2_MSK, mc->member);
data[5] |= FIELD_PREP(RTL8365MB_L2_MC_D5_MBR_HI2_MSK, val);
}
/*
* rtl8365mb_l2_get_next_uc() - get the next Unicast L2 entry
* @priv: realtek_priv pointer
* @addr: as input, the table index to start the walk
* as output, the found table index
* @port: restrict the walk on entries related to port
* @entry: returned L2 Unicast table entry
*
* This function gets the next unicast L2 table entry starting from @addr
* and checking exclusively entries related to @port.
*
* On success, it returns 0, updates @addr to the index of the found entry,
* and populates @entry. If the search reaches the end of the table and
* wraps around and @addr will be strictly lower than the input @addr.
* Callers must detect this wrap-around condition to prevent infinite loops.
*
* If the table contains no matching entries at all, it returns -ENOENT
* and leaves @addr and @entry unmodified.
*
* Return: Returns 0 on success, a negative error on failure.
**/
int rtl8365mb_l2_get_next_uc(struct realtek_priv *priv, u16 *addr, int port,
struct realtek_fdb_entry *entry)
{
u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 };
struct rtl8365mb_l2_uc uc;
int ret;
ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,
RTL8365MB_TABLE_OP_READ, addr,
RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT,
port, data, RTL8365MB_L2_ENTRY_SIZE);
if (ret)
return ret;
rtl8365mb_l2_data_to_uc(data, &uc);
ether_addr_copy(entry->mac_addr, uc.key.mac_addr);
entry->vid = uc.key.vid;
entry->is_static = uc.is_static;
return 0;
}
int rtl8365mb_l2_add_uc(struct realtek_priv *priv, int port,
const unsigned char mac_addr[static ETH_ALEN],
u16 efid, u16 vid)
{
u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 };
struct rtl8365mb_l2_uc uc = { 0 };
u16 addr;
int ret;
memcpy(uc.key.mac_addr, mac_addr, ETH_ALEN);
uc.key.efid = efid;
uc.key.fid = 0;
uc.key.ivl = true;
uc.key.vid = vid;
uc.port = port;
/* Entries programmed by DSA (including those dynamically learned by
* the software bridge and injected into the CPU port via assisted
* learning) must be static. We do not let HW decrease age behind the
* OS's back. As a trade-off, these will show up as permanent to users.
*/
uc.is_static = true;
/* age greater than 0 adds/updates entries */
uc.age = 1;
rtl8365mb_l2_uc_to_data(&uc, data);
/* add the new entry or update an existing one */
ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,
RTL8365MB_TABLE_OP_WRITE, &addr,
0, 0,
data, RTL8365MB_L2_ENTRY_SIZE);
/* Assume the missing new entry as the table is full */
if (ret == -ENOENT)
return -ENOSPC;
/* addr will hold the table index, but it is not used here */
return ret;
}
int rtl8365mb_l2_del_uc(struct realtek_priv *priv, int port,
const unsigned char mac_addr[static ETH_ALEN],
u16 efid, u16 vid)
{
u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 };
struct rtl8365mb_l2_uc uc = { 0 };
u16 addr;
int ret;
memcpy(uc.key.mac_addr, mac_addr, ETH_ALEN);
uc.key.efid = efid;
uc.key.fid = 0;
uc.key.ivl = true;
uc.key.vid = vid;
/* age 0 deletes the entry */
uc.age = 0;
rtl8365mb_l2_uc_to_data(&uc, data);
/* it looks like the switch will always add/update the entry,
* even when age is 0 or uc.key did not match an existing entry,
* just to immediately drop it because age is zero. You can still
* get the added/updated address from @addr
*/
ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,
RTL8365MB_TABLE_OP_WRITE, &addr,
0, 0,
data, RTL8365MB_L2_ENTRY_SIZE);
if (ret == -ENOENT) {
dev_dbg(priv->dev, "%s: %pM vid=%d efid=%d missing\n",
__func__, mac_addr, vid, efid);
/* Silently return success */
return 0;
}
/* addr will hold the table index, but it is not used here */
return ret;
}
int rtl8365mb_l2_flush(struct realtek_priv *priv, int port, u16 vid)
{
int mode = vid ? RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT_VID :
RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT;
u32 val, mask;
int ret;
mutex_lock(&priv->map_lock);
/* Configure flushing mode; only flush dynamic entries */
ret = regmap_write(priv->map_nolock, RTL8365MB_L2_FLUSH_CTRL2_REG,
FIELD_PREP(RTL8365MB_L2_FLUSH_CTRL2_MODE_MSK,
mode) |
FIELD_PREP(RTL8365MB_L2_FLUSH_CTRL2_TYPE_MSK,
RTL8365MB_L2_FLUSH_CTRL2_TYPE_DYNAMIC));
if (ret)
goto out;
ret = regmap_write(priv->map_nolock, RTL8365MB_L2_FLUSH_CTRL1_REG,
FIELD_PREP(RTL8365MB_L2_FLUSH_CTRL1_VID_MSK, vid));
if (ret)
goto out;
/* Now issue the flush command and wait for its completion. There are
* two registers for this purpose, and which one to use depends on the
* port number. The _EXT register is for ports 8 or higher.
*/
if (port < 8) {
val = FIELD_PREP(RTL8365MB_L2_FLUSH_PORT_MSK_MSK,
BIT(port) & 0xFF);
ret = regmap_write(priv->map_nolock,
RTL8365MB_L2_FLUSH_PORT_REG, val);
if (ret)
goto out;
mask = FIELD_PREP(RTL8365MB_L2_FLUSH_PORT_BUSY_MSK,
BIT(port) & 0xFF);
ret = regmap_read_poll_timeout(priv->map_nolock,
RTL8365MB_L2_FLUSH_PORT_REG,
val, !(val & mask), 10, 10000);
if (ret)
goto out;
} else {
val = FIELD_PREP(RTL8365MB_L2_FLUSH_PORT_EXT_MSK_MSK,
BIT(port) >> 8);
ret = regmap_write(priv->map_nolock,
RTL8365MB_L2_FLUSH_PORT_EXT_REG, val);
if (ret)
goto out;
mask = FIELD_PREP(RTL8365MB_L2_FLUSH_PORT_EXT_BUSY_MSK,
BIT(port) >> 8);
ret = regmap_read_poll_timeout(priv->map_nolock,
RTL8365MB_L2_FLUSH_PORT_EXT_REG,
val, !(val & mask), 10, 10000);
if (ret)
goto out;
}
out:
mutex_unlock(&priv->map_lock);
return ret;
}
int rtl8365mb_l2_add_mc(struct realtek_priv *priv, int port,
const unsigned char mac_addr[static ETH_ALEN],
u16 vid)
{
u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 };
struct rtl8365mb_l2_mc mc = { 0 };
u16 addr;
int ret;
memcpy(mc.key.mac_addr, mac_addr, ETH_ALEN);
mc.key.vid = vid;
mc.key.ivl = true;
/* Already set the port and is_static, although not used in OP_READ,
* data will be ready for OP_WRITE if it is a new entry.
*/
mc.member |= BIT(port);
mc.is_static = 1;
rtl8365mb_l2_mc_to_data(&mc, data);
/* First look for an existing entry (to get existing port members) */
ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,
RTL8365MB_TABLE_OP_READ, &addr,
RTL8365MB_TABLE_L2_METHOD_MAC, 0,
data, RTL8365MB_L2_ENTRY_SIZE);
if (!ret) {
/* There is already an entry... */
rtl8365mb_l2_data_to_mc(data, &mc);
dev_dbg(priv->dev,
"%s: found %pM addr=%d member=0x%x igmpidx=0x%x %s\n",
__func__, mac_addr, addr, mc.member, mc.igmpidx,
mc.is_static ? "static" : "dynamic");
/* the port must be added as a member */
mc.member |= BIT(port);
if (!mc.is_static) {
dev_dbg(priv->dev,
"%s: promoting addr=%d group to static\n",
__func__, addr);
mc.is_static = 1;
}
rtl8365mb_l2_mc_to_data(&mc, data);
} else if (ret == -ENOENT) {
/* New entry, no need to update data again as it already
* includes the member.
*
* Multicast hardware entries do not support EFID (bridge
* isolation). However, traffic isolation is still maintained
* because the hardware applies the port isolation masks
* (pmasks) configured in bridge_join after the L2 lookup.
* Entries from different bridges will collide on the same
* MAC+VID slot with an OR'ed member mask, but packets will
* only exit through ports allowed by the source port's pmask.
*/
} else {
return ret;
}
/* add the new entry or update an existing one */
ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,
RTL8365MB_TABLE_OP_WRITE, &addr,
0, 0,
data, RTL8365MB_L2_ENTRY_SIZE);
/* Assume the missing new entry as the table is full */
if (ret == -ENOENT)
return -ENOSPC;
return ret;
}
int rtl8365mb_l2_del_mc(struct realtek_priv *priv, int port,
const unsigned char mac_addr[static ETH_ALEN],
u16 vid)
{
u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 };
struct rtl8365mb_l2_mc mc = { 0 };
u16 addr;
int ret;
memcpy(mc.key.mac_addr, mac_addr, ETH_ALEN);
mc.key.vid = vid;
mc.key.ivl = true;
rtl8365mb_l2_mc_to_data(&mc, data);
/* First look for an existing entry (to get existing port members) */
ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,
RTL8365MB_TABLE_OP_READ, &addr,
RTL8365MB_TABLE_L2_METHOD_MAC, 0,
data, RTL8365MB_L2_ENTRY_SIZE);
if (ret == -ENOENT) {
dev_dbg(priv->dev, "%s: %pM vid=%d missing\n",
__func__, mac_addr, vid);
/* Silently return success */
return 0;
}
if (ret)
/* Return on any other error */
return ret;
rtl8365mb_l2_data_to_mc(data, &mc);
dev_dbg(priv->dev,
"%s: found %pM addr=%d member=0x%x igmpidx=0x%x %s\n",
__func__, mac_addr, addr, mc.member, mc.igmpidx,
mc.is_static ? "static" : "dynamic");
/* the port must be removed as a member */
mc.member &= ~BIT(port);
if (!mc.member) {
/* Multicast entries do not have an age field. Clearing both
* the member portmask and is_static flags is the hardware
* signal to invalidate and reclaim the L2 table slot.
*/
mc.is_static = 0;
mc.igmpidx = 0;
mc.priority = 0;
mc.fwd_pri = 0;
mc.igmp_asic = 0;
}
rtl8365mb_l2_mc_to_data(&mc, data);
/* update the existing entry. */
ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2,
RTL8365MB_TABLE_OP_WRITE, &addr,
0, 0,
data, RTL8365MB_L2_ENTRY_SIZE);
return ret;
}
@@ -0,0 +1,32 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Forwarding and multicast database interface for the rtl8365mb switch family
*
* Copyright (C) 2022 Alvin Šipraga <alsi@bang-olufsen.dk>
*/
#ifndef _REALTEK_RTL8365MB_L2_H
#define _REALTEK_RTL8365MB_L2_H
#include <linux/if_ether.h>
#include <linux/types.h>
#include "realtek.h"
int rtl8365mb_l2_get_next_uc(struct realtek_priv *priv, u16 *addr, int port,
struct realtek_fdb_entry *entry);
int rtl8365mb_l2_add_uc(struct realtek_priv *priv, int port,
const unsigned char addr[static ETH_ALEN],
u16 efid, u16 vid);
int rtl8365mb_l2_del_uc(struct realtek_priv *priv, int port,
const unsigned char addr[static ETH_ALEN],
u16 efid, u16 vid);
int rtl8365mb_l2_flush(struct realtek_priv *priv, int port, u16 vid);
int rtl8365mb_l2_add_mc(struct realtek_priv *priv, int port,
const unsigned char mac_addr[static ETH_ALEN],
u16 vid);
int rtl8365mb_l2_del_mc(struct realtek_priv *priv, int port,
const unsigned char mac_addr[static ETH_ALEN],
u16 vid);
#endif /* _REALTEK_RTL8365MB_L2_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,214 @@
// SPDX-License-Identifier: GPL-2.0
/* Look-up table query interface for the rtl8365mb switch family
*
* Copyright (C) 2022 Alvin Šipraga <alsi@bang-olufsen.dk>
*/
#include "rtl8365mb_table.h"
#include <linux/regmap.h>
/* Table access control register */
#define RTL8365MB_TABLE_CTRL_REG 0x0500
/* Should be one of rtl8365mb_table enum members */
#define RTL8365MB_TABLE_CTRL_TABLE_MASK GENMASK(2, 0)
/* Should be one of rtl8365mb_table_op enum members */
#define RTL8365MB_TABLE_CTRL_OP_MASK GENMASK(3, 3)
/* Should be one of rtl8365mb_table_l2_method enum members */
#define RTL8365MB_TABLE_CTRL_METHOD_MASK GENMASK(6, 4)
#define RTL8365MB_TABLE_CTRL_PORT_MASK GENMASK(11, 8)
/* Table access address register */
#define RTL8365MB_TABLE_ACCESS_ADDR_REG 0x0501
#define RTL8365MB_TABLE_ADDR_MASK GENMASK(12, 0)
/* Table status register */
#define RTL8365MB_TABLE_STATUS_REG 0x0502
#define RTL8365MB_TABLE_STATUS_ADDRESS_MASK GENMASK(10, 0)
/* set for L3, unset for L2 */
#define RTL8365MB_TABLE_STATUS_ADDR_TYPE_MASK GENMASK(11, 11)
#define RTL8365MB_TABLE_STATUS_HIT_STATUS_MASK GENMASK(12, 12)
#define RTL8365MB_TABLE_STATUS_BUSY_FLAG_MASK GENMASK(13, 13)
#define RTL8365MB_TABLE_STATUS_ADDRESS_EXT_MASK GENMASK(14, 14)
/* Table read/write registers */
#define RTL8365MB_TABLE_WRITE_BASE 0x0510
#define RTL8365MB_TABLE_WRITE_REG(_x) \
(RTL8365MB_TABLE_WRITE_BASE + (_x))
#define RTL8365MB_TABLE_READ_BASE 0x0520
#define RTL8365MB_TABLE_READ_REG(_x) \
(RTL8365MB_TABLE_READ_BASE + (_x))
#define RTL8365MB_TABLE_10TH_DATA_MASK GENMASK(3, 0)
#define RTL8365MB_TABLE_WRITE_10TH_REG \
RTL8365MB_TABLE_WRITE_REG(RTL8365MB_TABLE_ENTRY_MAX_SIZE - 1)
static int rtl8365mb_table_poll_busy(struct realtek_priv *priv)
{
u32 val;
return regmap_read_poll_timeout(priv->map_nolock,
RTL8365MB_TABLE_STATUS_REG, val,
!FIELD_GET(RTL8365MB_TABLE_STATUS_BUSY_FLAG_MASK, val),
10, 10000);
}
int rtl8365mb_table_query(struct realtek_priv *priv,
enum rtl8365mb_table table,
enum rtl8365mb_table_op op, u16 *addr,
enum rtl8365mb_table_l2_method method,
u16 port, u16 *data, size_t size)
{
bool addr_as_input = true;
bool write_data = false;
int ret = 0;
u32 cmd;
u32 val;
u32 hit;
/* Prepare target table and operation (read or write) */
cmd = 0;
cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_TABLE_MASK, table);
cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_OP_MASK, op);
if (op == RTL8365MB_TABLE_OP_READ && table == RTL8365MB_TABLE_L2) {
cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_METHOD_MASK, method);
switch (method) {
case RTL8365MB_TABLE_L2_METHOD_MAC:
/*
* Method MAC requires as input the same L2 table format
* you'll get as result. However, it might only use mac
* address and FID/VID fields.
*/
write_data = true;
/* METHOD_MAC does not use addr as input, but may return
* the matched index.
*/
addr_as_input = false;
break;
case RTL8365MB_TABLE_L2_METHOD_ADDR:
case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT:
case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC:
case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC:
break;
case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT:
cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_PORT_MASK, port);
break;
default:
return -EINVAL;
}
} else if (op == RTL8365MB_TABLE_OP_WRITE) {
write_data = true;
/* Writing to L2 does not use addr as input, as the table index
* is derived from key fields.
*/
if (table == RTL8365MB_TABLE_L2)
addr_as_input = false;
}
/* To prevent concurrent access to the look-up tables, take the regmap
* lock manually and access via the map_nolock regmap.
*/
mutex_lock(&priv->map_lock);
/* Protect from a busy table access (i.e. previous access timeouts) */
ret = rtl8365mb_table_poll_busy(priv);
if (ret)
goto out;
/* Write entry data if writing to the table (or L2_METHOD_MAC) */
if (write_data) {
/* bulk write data up to 9th word */
ret = regmap_bulk_write(priv->map_nolock,
RTL8365MB_TABLE_WRITE_BASE,
data,
min_t(size_t, size,
RTL8365MB_TABLE_ENTRY_MAX_SIZE -
1));
if (ret)
goto out;
/* 10th register uses only 4 least significant bits */
if (size == RTL8365MB_TABLE_ENTRY_MAX_SIZE) {
val = FIELD_PREP(RTL8365MB_TABLE_10TH_DATA_MASK,
data[size - 1]);
ret = regmap_update_bits(priv->map_nolock,
RTL8365MB_TABLE_WRITE_10TH_REG,
RTL8365MB_TABLE_10TH_DATA_MASK,
val);
}
if (ret)
goto out;
}
/* Write address (if needed) */
if (addr_as_input) {
ret = regmap_write(priv->map_nolock,
RTL8365MB_TABLE_ACCESS_ADDR_REG,
FIELD_PREP(RTL8365MB_TABLE_ADDR_MASK,
*addr));
if (ret)
goto out;
}
/* Execute */
ret = regmap_write(priv->map_nolock, RTL8365MB_TABLE_CTRL_REG, cmd);
if (ret)
goto out;
/* Poll for completion */
ret = rtl8365mb_table_poll_busy(priv);
if (ret)
goto out;
/* For both reads and writes to the L2 table, check status */
if (table == RTL8365MB_TABLE_L2) {
ret = regmap_read(priv->map_nolock, RTL8365MB_TABLE_STATUS_REG,
&val);
if (ret)
goto out;
/* Did the query find an entry? */
hit = FIELD_GET(RTL8365MB_TABLE_STATUS_HIT_STATUS_MASK, val);
if (!hit) {
ret = -ENOENT;
goto out;
}
/* If so, extract the address */
*addr = 0;
*addr |= FIELD_GET(RTL8365MB_TABLE_STATUS_ADDRESS_MASK, val);
*addr |= FIELD_GET(RTL8365MB_TABLE_STATUS_ADDRESS_EXT_MASK, val)
<< 11;
/* only set if it is a L3 address */
*addr |= FIELD_GET(RTL8365MB_TABLE_STATUS_ADDR_TYPE_MASK, val)
<< 12;
}
/* Finally, get the table entry if we were reading */
if (op == RTL8365MB_TABLE_OP_READ) {
ret = regmap_bulk_read(priv->map_nolock,
RTL8365MB_TABLE_READ_BASE,
data, size);
if (ret)
goto out;
/* For the biggest table entries, the uppermost table
* entry register has space for only one nibble. Mask
* out the remainder bits. Empirically I saw nothing
* wrong with omitting this mask, but it may prevent
* unwanted behaviour. FYI.
*/
if (size == RTL8365MB_TABLE_ENTRY_MAX_SIZE) {
val = FIELD_GET(RTL8365MB_TABLE_10TH_DATA_MASK,
data[size - 1]);
data[size - 1] = val;
}
}
out:
mutex_unlock(&priv->map_lock);
return ret;
}
@@ -0,0 +1,138 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Look-up table query interface for the rtl8365mb switch family
*
* Copyright (C) 2022 Alvin Šipraga <alsi@bang-olufsen.dk>
*/
#ifndef _REALTEK_RTL8365MB_TABLE_H
#define _REALTEK_RTL8365MB_TABLE_H
#include <linux/if_ether.h>
#include <linux/types.h>
#include "realtek.h"
#define RTL8365MB_TABLE_ENTRY_MAX_SIZE 10
/*
* enum rtl8365mb_table - available switch tables
* @RTL8365MB_TABLE_ACL_RULE: ACL rules
* @RTL8365MB_TABLE_ACL_ACTION: ACL actions
* @RTL8365MB_TABLE_CVLAN: VLAN4k configurations
* @RTL8365MB_TABLE_L2: filtering database (2K hash table)
* @RTL8365MB_TABLE_IGMP_GROUP: IGMP group database (readonly)
*
* NOTE: Don't change the enum values. They must concur with the field
* described by @RTL8365MB_TABLE_CTRL_TABLE_MASK.
*/
enum rtl8365mb_table {
RTL8365MB_TABLE_ACL_RULE = 1,
RTL8365MB_TABLE_ACL_ACTION = 2,
RTL8365MB_TABLE_CVLAN = 3,
RTL8365MB_TABLE_L2 = 4,
RTL8365MB_TABLE_IGMP_GROUP = 5,
};
/*
* enum rtl8365mb_table_op - table query operation
* @RTL8365MB_TABLE_OP_READ: read an entry from the target table
* @RTL8365MB_TABLE_OP_WRITE: write an entry to the target table
*
* NOTE: Don't change the enum values. They must concur with the field
* described by @RTL8365MB_TABLE_CTRL_OP_MASK.
*/
enum rtl8365mb_table_op {
RTL8365MB_TABLE_OP_READ = 0,
RTL8365MB_TABLE_OP_WRITE = 1,
};
/*
* enum rtl8365mb_table_l2_method - look-up method for read queries of L2 table
* @RTL8365MB_TABLE_L2_METHOD_MAC: look-up by source MAC address and FID (or
* VID)
* @RTL8365MB_TABLE_L2_METHOD_ADDR: look-up by entry address
* @RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT: look-up next entry starting from the
* supplied address
* @RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC: same as ADDR_NEXT but search only
* unicast addresses
* @RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC: same as ADDR_NEXT but search only
* multicast addresses
* @RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT: same as ADDR_NEXT_UC but
* search only entries with matching source port
*
* NOTE: Don't change the enum values. They must concur with the field
* described by @RTL8365MB_TABLE_CTRL_METHOD_MASK
*/
enum rtl8365mb_table_l2_method {
RTL8365MB_TABLE_L2_METHOD_MAC = 0,
RTL8365MB_TABLE_L2_METHOD_ADDR = 1,
RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT = 2,
RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC = 3,
RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC = 4,
/*
* RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC_L3 = 5,
* RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC_L2L3 = 6,
*/
RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT = 7,
};
/*
* rtl8365mb_table_query() - read from or write to a switch table
* @priv: driver context
* @table: target table, see &enum rtl8365mb_table
* @op: read or write operation, see &enum rtl8365mb_table_op
* @addr: table address. For indexed tables, this selects the entry to access.
* For L2 read queries, it is ignored as input for MAC-based lookup
* methods and used as input for address-based lookup methods. On
* successful L2 queries, it is updated with the matched entry address.
* @method: L2 table lookup method, see &enum rtl8365mb_table_l2_method.
* Ignored for non-L2 tables.
* @port: for L2 read queries using method
* %RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT, restrict the search
* to entries associated with this source port. Ignored otherwise.
* @data: data buffer used to read from or write to the table. For L2 MAC
* lookups, this buffer provides the lookup key and receives the
* matched entry contents on success.
* @size: size of @data in 16-bit words. The caller must ensure that @size
* matches the target table's entry size and does not exceed
* RTL8365MB_TABLE_ENTRY_MAX_SIZE.
*
* This function provides unified access to the internal tables of the switch.
* All tables except the L2 table are simple indexed tables, where @addr
* selects the entry and @op determines whether the access is a read or a
* write operation.
*
* The content of @data is used as input when writing to tables or when
* specifying the lookup key for L2 MAC searches, and as output for all
* successful read operations. It remains unchanged during write operations or
* failed read operations that return %-ENOENT. For other errors during read
* operations, it is undefined.
*
* The L2 table is a hash table and supports multiple lookup methods. For
* %RTL8365MB_TABLE_L2_METHOD_MAC, an entry is searched based on the MAC
* address and FID/VID fields provided in @data, using the same format as
* an L2 table entry. Address-based methods either read a specific entry
* (%RTL8365MB_TABLE_L2_METHOD_ADDR) or iterate over valid entries starting
* from @addr (%RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT and variants). When using
* %RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT, only entries associated with
* the specified @port are considered.
*
* On successful L2 operations, @addr is updated with the matched table address
* or allocated entry address. If no matching entry is found, or if an L2 write
* operation fails (e.g., due to a full table during addition or a missing entry
* during deletion), %-ENOENT is returned and @addr remains unchanged. It is the
* caller's responsibility to map the returned error to the appropriate
* semantic error.
*
* @size must match the size of the target table entry, expressed in 16-bit
* words.
*
* Return: 0 on success, or a negative error code on failure.
*/
int rtl8365mb_table_query(struct realtek_priv *priv,
enum rtl8365mb_table table,
enum rtl8365mb_table_op op, u16 *addr,
enum rtl8365mb_table_l2_method method,
u16 port, u16 *data, size_t size);
#endif /* _REALTEK_RTL8365MB_TABLE_H */
@@ -0,0 +1,944 @@
// SPDX-License-Identifier: GPL-2.0
/* VLAN configuration interface for the rtl8365mb switch family
*
* Copyright (C) 2022 Alvin Šipraga <alsi@bang-olufsen.dk>
*
* VLAN configuration takes place in two separate domains of the switch: the
* VLAN4k table and the VLAN membership configuration (MC) database. While the
* VLAN4k table is exhaustive and can be fully populated with 4096 VLAN
* configurations, the same does not hold for the VLAN membership configuration
* database, which is limited to 32 entries.
*
* The switch will normally only use the VLAN4k table when making forwarding
* decisions. The VLAN membership configuration database is a vestigial ASIC
* design and is only used for a few specific features in the rtl8365mb
* family. This means that the limit of 32 entries should not hinder us in
* programming a huge number of VLANs into the switch.
*
* One necessary use of the VLAN membership configuration database is for the
* programming of a port-based VLAN ID (PVID). The PVID is programmed on a
* per-port basis via register field, which refers to a specific VLAN membership
* configuration via an index 0~31. In order to maintain coherent behaviour on a
* port with a PVID, it is necessary to keep the VLAN configuration synchronized
* between the VLAN4k table and the VLAN membership configuration database.
*
* Since VLAN membership configs are a scarce resource, it will only be used
* when strictly needed (i.e. a VLAN with members using PVID). Otherwise, the
* VLAN4k will be enough.
*
* With some exceptions, the entries in both the VLAN4k table and the VLAN
* membership configuration database offer the same configuration options. The
* differences are as follows:
*
* 1. VLAN4k entries can specify whether to use Independent or Shared VLAN
* Learning (IVL or SVL respectively). VLAN membership config entries
* cannot. This underscores the fact that VLAN membership configs are not
* involved in the learning process of the ASIC.
*
* 2. VLAN membership config entries use an "enhanced VLAN ID" (efid), which has
* a range 0~8191 compared with the standard 0~4095 range of the VLAN4k
* table. This underscores the fact that VLAN membership configs can be used
* to group ports on a layer beyond the standard VLAN configuration, which
* may be useful for ACL rules which specify alternative forwarding
* decisions.
*
* VLANMC index 0 is reserved as a neutral PVID, used for standalone ports.
*
*/
#include "rtl8365mb_vlan.h"
#include "rtl8365mb_table.h"
#include <linux/if_bridge.h>
#include <linux/lockdep.h>
#include <linux/regmap.h>
/* CVLAN (i.e. VLAN4k) table entry layout, u16[3] */
#define RTL8365MB_CVLAN_ENTRY_SIZE 3 /* 48-bits */
#define RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK GENMASK(7, 0)
#define RTL8365MB_CVLAN_MBR_LO_MASK GENMASK(7, 0)
#define RTL8365MB_CVLAN_ENTRY_D0_UNTAG_MASK GENMASK(15, 8)
#define RTL8365MB_CVLAN_UNTAG_LO_MASK GENMASK(7, 0)
#define RTL8365MB_CVLAN_ENTRY_D1_FID_MASK GENMASK(3, 0)
#define RTL8365MB_CVLAN_ENTRY_D1_VBPEN_MASK GENMASK(4, 4)
#define RTL8365MB_CVLAN_ENTRY_D1_VBPRI_MASK GENMASK(7, 5)
#define RTL8365MB_CVLAN_ENTRY_D1_ENVLANPOL_MASK GENMASK(8, 8)
#define RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK GENMASK(13, 9)
#define RTL8365MB_CVLAN_METERIDX_LO_MASK GENMASK(4, 0)
#define RTL8365MB_CVLAN_ENTRY_D1_IVL_SVL_MASK GENMASK(14, 14)
/* extends RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK */
#define RTL8365MB_CVLAN_ENTRY_D2_MBR_EXT_MASK GENMASK(2, 0)
#define RTL8365MB_CVLAN_MBR_HI_MASK GENMASK(10, 8)
/* extends RTL8365MB_CVLAN_ENTRY_D0_UNTAG_MASK */
#define RTL8365MB_CVLAN_ENTRY_D2_UNTAG_EXT_MASK GENMASK(5, 3)
#define RTL8365MB_CVLAN_UNTAG_HI_MASK GENMASK(10, 8)
/* extends RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK */
#define RTL8365MB_CVLAN_ENTRY_D2_METERIDX_EXT_MASK GENMASK(6, 6)
#define RTL8365MB_CVLAN_METERIDX_HI_MASK GENMASK(5, 5)
/* VLAN member configuration registers 0~31, u16[3] */
#define RTL8365MB_VLAN_MC_BASE 0x0728
#define RTL8365MB_VLAN_MC_ENTRY_SIZE 4 /* 64-bit */
#define RTL8365MB_VLAN_MC_REG(index) \
(RTL8365MB_VLAN_MC_BASE + \
(RTL8365MB_VLAN_MC_ENTRY_SIZE * (index)))
#define RTL8365MB_VLAN_MC_D0_MBR_MASK GENMASK(10, 0)
#define RTL8365MB_VLAN_MC_D1_FID_MASK GENMASK(3, 0)
#define RTL8365MB_VLAN_MC_D2_VBPEN_MASK GENMASK(0, 0)
#define RTL8365MB_VLAN_MC_D2_VBPRI_MASK GENMASK(3, 1)
#define RTL8365MB_VLAN_MC_D2_ENVLANPOL_MASK GENMASK(4, 4)
#define RTL8365MB_VLAN_MC_D2_METERIDX_MASK GENMASK(10, 5)
#define RTL8365MB_VLAN_MC_D3_EVID_MASK GENMASK(12, 0)
/* Some limits for VLAN4k/VLAN membership config entries */
#define RTL8365MB_PRIORITYMAX 7
#define RTL8365MB_FIDMAX 15
#define RTL8365MB_METERMAX 63
#define RTL8365MB_VLAN_MCMAX 31
/* RTL8367S supports 4k vlans (vid<=4095) and 32 enhanced vlans
* for VIDs up to 8191
*/
#define RTL8365MB_MAX_4K_VID 0x0FFF /* 4095 */
#define RTL8365MB_MAX_MC_VID 0x1FFF /* 8191 */
/* Port-based VID registers 0~5 - each one holds an MC index for two ports */
#define RTL8365MB_VLAN_PVID_CTRL_BASE 0x0700
#define RTL8365MB_VLAN_PVID_CTRL_REG(_p) \
(RTL8365MB_VLAN_PVID_CTRL_BASE + ((_p) >> 1))
#define RTL8365MB_VLAN_PVID_CTRL_PORT0_MCIDX_MASK 0x001F
#define RTL8365MB_VLAN_PVID_CTRL_PORT1_MCIDX_MASK 0x1F00
#define RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_OFFSET(_p) \
(((_p) & 1) << 3)
#define RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_MASK(_p) \
(0x1F << RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_OFFSET(_p))
/* Frame type filtering registers */
#define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_BASE 0x07aa
#define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_REG(port) \
(RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_BASE + ((port) >> 3))
/* required as FIELD_PREP cannot use non-constant masks */
#define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_MASK(port) \
(0x3 << RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_OFFSET(port))
#define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_OFFSET(port) \
(((port) & 0x7) << 1)
/*
* struct rtl8365mb_vlan4k - VLAN4k table entry
* @vid: VLAN ID (0~4095)
* @member: port mask of ports in this VLAN
* @untag: port mask of ports which untag on egress
* @fid: filter ID - only used with SVL (unused)
* @priority: priority classification (unused)
* @priority_en: enable priority (unused)
* @policing_en: enable policing (unused)
* @ivl_en: enable IVL instead of default SVL
* @meteridx: metering index (unused)
*
* This structure is used to get/set entries in the VLAN4k table. The
* VLAN4k table dictates the VLAN configuration for the switch for the
* vast majority of features.
*/
struct rtl8365mb_vlan4k {
u16 vid;
u16 member;
u16 untag;
u8 fid : 4;
u8 priority : 3;
u8 priority_en : 1;
u8 policing_en : 1;
u8 ivl_en : 1;
u8 meteridx : 6;
};
/*
* struct rtl8365mb_vlanmc - VLAN membership config
* @evid: Enhanced VLAN ID (0~8191)
* @member: port mask of ports in this VLAN
* @fid: filter ID - only used with SVL (unused)
* @priority: priority classification (unused)
* @priority_en: enable priority (unused)
* @policing_en: enable policing (unused)
* @meteridx: metering index (unused)
*
* This structure is used to get/set entries in the VLAN membership
* configuration database. This feature is largely vestigial, but
* still needed for at least the following features:
* - PVID configuration
* - ACL configuration
* - selection of VLAN by the CPU tag when VSEL=1, although the switch
* can also select VLAN based on the VLAN tag if VSEL=0
*
* This is a low-level structure and it is recommended to interface with
* the VLAN membership config database via &struct rtl8365mb_vlanmc_entry.
*/
struct rtl8365mb_vlanmc {
u16 evid;
u16 member;
u8 fid : 4;
u8 priority : 3;
u8 priority_en : 1;
u8 policing_en : 1;
u8 meteridx : 6;
};
static int rtl8365mb_vlan_4k_read(struct realtek_priv *priv, u16 vid,
struct rtl8365mb_vlan4k *vlan4k)
{
u16 data[RTL8365MB_CVLAN_ENTRY_SIZE];
int val;
int ret;
ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_CVLAN,
RTL8365MB_TABLE_OP_READ, &vid, 0, 0,
data, ARRAY_SIZE(data));
if (ret)
return ret;
/* Unpack table entry */
memset(vlan4k, 0, sizeof(*vlan4k));
vlan4k->vid = vid;
val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK, data[0]);
vlan4k->member = FIELD_PREP(RTL8365MB_CVLAN_MBR_LO_MASK, val);
val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_MBR_EXT_MASK, data[2]);
vlan4k->member |= FIELD_PREP(RTL8365MB_CVLAN_MBR_HI_MASK, val);
val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D0_UNTAG_MASK, data[0]);
vlan4k->untag = FIELD_PREP(RTL8365MB_CVLAN_UNTAG_LO_MASK, val);
val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_UNTAG_EXT_MASK, data[2]);
vlan4k->untag |= FIELD_PREP(RTL8365MB_CVLAN_UNTAG_HI_MASK, val);
vlan4k->fid = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_FID_MASK, data[1]);
vlan4k->priority_en =
FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_VBPEN_MASK, data[1]);
vlan4k->priority =
FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_VBPRI_MASK, data[1]);
vlan4k->policing_en =
FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_ENVLANPOL_MASK, data[1]);
val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK, data[1]);
val = FIELD_PREP(RTL8365MB_CVLAN_METERIDX_LO_MASK, val);
vlan4k->meteridx = val;
val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_METERIDX_EXT_MASK, data[2]);
val = FIELD_PREP(RTL8365MB_CVLAN_METERIDX_HI_MASK, val);
vlan4k->meteridx |= val;
vlan4k->ivl_en =
FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_IVL_SVL_MASK, data[1]);
return 0;
}
static int rtl8365mb_vlan_4k_write(struct realtek_priv *priv,
const struct rtl8365mb_vlan4k *vlan4k)
{
u16 data[RTL8365MB_CVLAN_ENTRY_SIZE] = { 0 };
u16 vid;
int val;
/* Pack table entry value */
val = FIELD_GET(RTL8365MB_CVLAN_MBR_LO_MASK, vlan4k->member);
data[0] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK, val);
val = FIELD_GET(RTL8365MB_CVLAN_UNTAG_LO_MASK, vlan4k->untag);
data[0] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D0_UNTAG_MASK, val);
data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_FID_MASK, vlan4k->fid);
data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_VBPEN_MASK,
vlan4k->priority_en);
data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_VBPRI_MASK,
vlan4k->priority);
data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_ENVLANPOL_MASK,
vlan4k->policing_en);
/* FIELD_* does not play nice with struct bitfield. */
val = vlan4k->meteridx;
val = FIELD_GET(RTL8365MB_CVLAN_METERIDX_LO_MASK, val);
data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK, val);
data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_IVL_SVL_MASK,
vlan4k->ivl_en);
val = FIELD_GET(RTL8365MB_CVLAN_MBR_HI_MASK, vlan4k->member);
data[2] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_MBR_EXT_MASK, val);
val = FIELD_GET(RTL8365MB_CVLAN_UNTAG_HI_MASK, vlan4k->untag);
data[2] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_UNTAG_EXT_MASK, val);
val = vlan4k->meteridx;
val = FIELD_GET(RTL8365MB_CVLAN_METERIDX_HI_MASK, val);
data[2] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_METERIDX_EXT_MASK, val);
vid = vlan4k->vid;
return rtl8365mb_table_query(priv, RTL8365MB_TABLE_CVLAN,
RTL8365MB_TABLE_OP_WRITE, &vid, 0, 0,
data, ARRAY_SIZE(data));
}
static int
rtl8365mb_vlan_4k_port_set(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_vlan *vlan,
struct netlink_ext_ack *extack,
bool include)
{
struct realtek_priv *priv = ds->priv;
struct rtl8365mb_vlan4k vlan4k = {0};
int ret;
dev_dbg(priv->dev, "%s VLAN %d 4K on port %d\n",
include ? "add" : "del",
vlan->vid, port);
if (vlan->vid > RTL8365MB_MAX_4K_VID) {
NL_SET_ERR_MSG_MOD(extack, "VLAN ID greater than "
__stringify(RTL8365MB_MAX_4K_VID));
return -EINVAL;
}
ret = rtl8365mb_vlan_4k_read(priv, vlan->vid, &vlan4k);
if (ret) {
dev_err(priv->dev, "Failed to read VLAN 4k table\n");
return ret;
}
if (include)
vlan4k.member |= BIT(port);
else
vlan4k.member &= ~BIT(port);
if (include && (vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED))
vlan4k.untag |= BIT(port);
else
vlan4k.untag &= ~BIT(port);
vlan4k.ivl_en = true; /* always use Independent VLAN Learning */
ret = rtl8365mb_vlan_4k_write(priv, &vlan4k);
if (ret) {
dev_err(priv->dev, "Failed to write VLAN 4k table\n");
return ret;
}
return 0;
}
/*
* rtl8365mb_vlan_4k_port_add() - Add a port to a VLAN 4K table entry
* @ds: dsa switch instance
* @port: port index
* @vlan: switchdev VLAN object containing the target VID and flags
* @extack: netlink extended ACK for error reporting
*
* Adds the specified port to the hardware VLAN 4K membership table.
*
* Context: Can sleep. Must be called with &priv->vlan_lock held.
* Takes and releases &priv->map_lock.
* Return: 0 on success, or a negative error code on failure.
*/
int rtl8365mb_vlan_4k_port_add(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_vlan *vlan,
struct netlink_ext_ack *extack)
{
struct realtek_priv *priv = ds->priv;
lockdep_assert_held(&priv->vlan_lock);
return rtl8365mb_vlan_4k_port_set(ds, port, vlan, extack, true);
}
/*
* rtl8365mb_vlan_4k_port_del() - Remove a port from a VLAN 4K table entry
* @ds: dsa switch instance
* @port: port index
* @vlan: switchdev VLAN object containing the target VID
*
* Removes the specified port from the hardware VLAN 4K membership table.
*
* Context: Can sleep. Must be called with &priv->vlan_lock held.
* Takes and releases &priv->map_lock.
* Return: 0 on success, or a negative error code on failure.
*/
int rtl8365mb_vlan_4k_port_del(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_vlan *vlan)
{
struct realtek_priv *priv = ds->priv;
lockdep_assert_held(&priv->vlan_lock);
return rtl8365mb_vlan_4k_port_set(ds, port, vlan, NULL, false);
}
static int rtl8365mb_vlan_mc_read(struct realtek_priv *priv, u32 index,
struct rtl8365mb_vlanmc *vlanmc)
{
u16 data[RTL8365MB_VLAN_MC_ENTRY_SIZE];
int ret;
ret = regmap_bulk_read(priv->map, RTL8365MB_VLAN_MC_REG(index), &data,
RTL8365MB_VLAN_MC_ENTRY_SIZE);
if (ret)
return ret;
vlanmc->member = FIELD_GET(RTL8365MB_VLAN_MC_D0_MBR_MASK, data[0]);
vlanmc->fid = FIELD_GET(RTL8365MB_VLAN_MC_D1_FID_MASK, data[1]);
vlanmc->meteridx = FIELD_GET(RTL8365MB_VLAN_MC_D2_METERIDX_MASK,
data[2]);
vlanmc->policing_en = FIELD_GET(RTL8365MB_VLAN_MC_D2_ENVLANPOL_MASK,
data[2]);
vlanmc->priority = FIELD_GET(RTL8365MB_VLAN_MC_D2_VBPRI_MASK, data[2]);
vlanmc->priority_en = FIELD_GET(RTL8365MB_VLAN_MC_D2_VBPEN_MASK,
data[2]);
vlanmc->evid = FIELD_GET(RTL8365MB_VLAN_MC_D3_EVID_MASK, data[3]);
return 0;
}
static int rtl8365mb_vlan_mc_write(struct realtek_priv *priv, u32 index,
const struct rtl8365mb_vlanmc *vlanmc)
{
u16 data[RTL8365MB_VLAN_MC_ENTRY_SIZE] = { 0 };
int ret;
data[0] |= FIELD_PREP(RTL8365MB_VLAN_MC_D0_MBR_MASK, vlanmc->member);
data[1] |= FIELD_PREP(RTL8365MB_VLAN_MC_D1_FID_MASK, vlanmc->fid);
data[2] |= FIELD_PREP(RTL8365MB_VLAN_MC_D2_METERIDX_MASK,
vlanmc->meteridx);
data[2] |= FIELD_PREP(RTL8365MB_VLAN_MC_D2_ENVLANPOL_MASK,
vlanmc->policing_en);
data[2] |=
FIELD_PREP(RTL8365MB_VLAN_MC_D2_VBPRI_MASK, vlanmc->priority);
data[2] |= FIELD_PREP(RTL8365MB_VLAN_MC_D2_VBPEN_MASK,
vlanmc->priority_en);
data[3] |= FIELD_PREP(RTL8365MB_VLAN_MC_D3_EVID_MASK, vlanmc->evid);
ret = regmap_bulk_write(priv->map, RTL8365MB_VLAN_MC_REG(index), &data,
RTL8365MB_VLAN_MC_ENTRY_SIZE);
return ret;
}
static int rtl8365mb_vlan_mc_erase(struct realtek_priv *priv, u32 index)
{
u16 data[RTL8365MB_VLAN_MC_ENTRY_SIZE] = { 0 };
int ret;
ret = regmap_bulk_write(priv->map, RTL8365MB_VLAN_MC_REG(index), &data,
RTL8365MB_VLAN_MC_ENTRY_SIZE);
return ret;
}
/*
* rtl8365mb_vlan_mc_find() - find VLANMC index by VID or the first free index
*
* @priv: realtek_priv pointer
* @vid: VLAN ID
* @index: found index
* @first_free: found free index
*
* If a VLAN MC entry using @vid was found, @index will return the matched index
* and @first_free is undefined. If not found, @index will return 0 and
* @first_free will return the first found free index in VLAN MC or 0 if the
* table is full.
*
* Although 0 is a valid VLAN MC index, it is reserved for ports without PVID,
* including standalone, non-member ports. It uses VID == 0.
*
* Both @index and @first_free will be in the * 1..@RTL8365MB_VLAN_MCMAX range.
*
* Return: Returns 0 on success, a negative error on failure.
*/
static int rtl8365mb_vlan_mc_find(struct realtek_priv *priv, u16 vid,
u8 *index, u8 *first_free)
{
u32 vlan_entry_d3;
u8 vlanmc_idx;
u16 evid;
int ret;
*index = 0;
*first_free = 0;
/* look for existing entry or an empty one */
/* By design, VlanMC[0] is reserved as a neutral PVID value for
* standalone ports. It always has EVID == 0. That way, we assume that
* all entries after index 0 with VID == 0 are empty.
**/
for (vlanmc_idx = 1; vlanmc_idx <= RTL8365MB_VLAN_MCMAX; vlanmc_idx++) {
/* just read the 4th word, where the evid is */
ret = regmap_read(priv->map,
RTL8365MB_VLAN_MC_REG(vlanmc_idx) + 3,
&vlan_entry_d3);
if (ret)
return ret;
evid = FIELD_GET(RTL8365MB_VLAN_MC_D3_EVID_MASK, vlan_entry_d3);
if (evid == vid) {
*index = vlanmc_idx;
return 0;
}
if (evid == 0x0 && *first_free < 1)
*first_free = vlanmc_idx;
}
return 0;
}
static int rtl8365mb_vlan_port_get_pvid_idx(struct realtek_priv *priv,
int port, u8 *vlanmc_idx)
{
u32 data;
int ret;
ret = regmap_read(priv->map, RTL8365MB_VLAN_PVID_CTRL_REG(port), &data);
if (ret)
return ret;
*vlanmc_idx = (data & RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_MASK(port))
>> RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_OFFSET(port);
return 0;
}
/*
* rtl8365mb_vlan_mc_port_set() - include or exclude a port from VlanMC
* @ds: dsa switch
* @port: the port number
* @vid: the vlan VID to include/exclude @port
* @pvid: inform if vid is used as pvid in @port
* @extack: optional extack to return errors
* @include: whether to include or exclude @port
*
* This function is used to include/exclude ports to the VlanMC table.
*
* VlanMC stands for VLAN membership config and it is used exclusively for
* PVID. If @vlan members are not using PVID, this function will either
* remove or not create a new VlanMC entry.
*
* VlanMC members are used as a reference port map, cleaning the entry once
* no port is using it.
*
* Port PVID and accepted frame type are updated as well.
*
* Context: Can sleep. Must be called with &priv->vlan_lock held.
* Takes and releases &priv->map_lock.
* Return: Returns 0 on success, a negative error on failure.
*/
static
int rtl8365mb_vlan_mc_port_set(struct dsa_switch *ds, int port,
u16 vid, bool pvid,
struct netlink_ext_ack *extack,
bool include)
{
struct realtek_priv *priv = ds->priv;
struct rtl8365mb_vlanmc vlanmc = {0};
u8 first_unused = 0;
u8 vlanmc_idx = 0;
int ret;
dev_dbg(priv->dev, "%s VLAN %d MC on port %d\n",
include ? "add" : "del",
vid, port);
if (vid > RTL8365MB_MAX_MC_VID) {
NL_SET_ERR_MSG_MOD(extack, "VLAN ID greater than "
__stringify(RTL8365MB_MAX_MC_VID));
return -EINVAL;
}
/* look for existing entry or an empty slot */
ret = rtl8365mb_vlan_mc_find(priv, vid, &vlanmc_idx,
&first_unused);
if (ret) {
dev_err(priv->dev, "Failed to find a VLAN MC table index\n");
return ret;
}
if (vlanmc_idx) {
ret = rtl8365mb_vlan_mc_read(priv, vlanmc_idx, &vlanmc);
if (ret) {
dev_err(priv->dev, "Failed to read VLAN MC table\n");
return ret;
}
} else if (include) {
/* for now, vlan_mc is only required for PVID. Defer allocation
* until at least one port uses PVID.
*/
if (!pvid) {
dev_dbg(priv->dev,
"Not creating VlanMC for vlan %d until a port uses PVID (%d does not)\n",
vid, port);
return 0;
}
if (!first_unused) {
NL_SET_ERR_MSG_MOD(extack, "All VLAN MC entries (0.."
__stringify(RTL8365MB_VLAN_MCMAX)
") are in use.");
return -ENOSPC;
}
vlanmc_idx = first_unused;
vlanmc.evid = vid;
} else /* excluding and VLANMC not found */ {
return 0;
}
dev_dbg(priv->dev,
"VLAN %d (idx: %d) PVID curr members: %08x\n",
vid, vlanmc_idx, vlanmc.member);
/* here we either have an existing VLANMC (with PVID members) or the
* added port is using this VLAN as PVID
*/
if (include)
vlanmc.member |= BIT(port);
else
vlanmc.member &= ~BIT(port);
/* just like we don't need to create a VLAN_MC when there is no port
* using it as PVID, we can erase it when there is no more port using
* it as PVID.
*/
if (!vlanmc.member) {
dev_dbg(priv->dev,
"Clearing VlanMC index %d previously used by VID %d\n",
vlanmc_idx, vid);
ret = rtl8365mb_vlan_mc_erase(priv, vlanmc_idx);
} else {
dev_dbg(priv->dev,
"Saving VlanMC index %d with VID %d\n",
vlanmc_idx, vid);
ret = rtl8365mb_vlan_mc_write(priv, vlanmc_idx, &vlanmc);
}
if (ret) {
dev_err(priv->dev, "Failed to write vlan MC entry\n");
return ret;
}
return 0;
}
static int rtl8365mb_vlan_port_set_pvid(struct realtek_priv *priv,
int port, u16 vlanmc_idx)
{
int ret;
u32 val;
dev_dbg(priv->dev, "set PVID IDX %d on port %d\n", vlanmc_idx, port);
val = vlanmc_idx << RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_OFFSET(port);
ret = regmap_update_bits(priv->map,
RTL8365MB_VLAN_PVID_CTRL_REG(port),
RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_MASK(port),
val);
if (ret)
return ret;
return 0;
}
static int rtl8365mb_vlan_get_pvid_mc(struct realtek_priv *priv,
int port, u8 *vlanmc_idx,
struct rtl8365mb_vlanmc *vlanmc)
{
int ret;
ret = rtl8365mb_vlan_port_get_pvid_idx(priv, port, vlanmc_idx);
if (ret)
return ret;
memset(vlanmc, 0, sizeof(*vlanmc));
if (!*vlanmc_idx)
return 0;
ret = rtl8365mb_vlan_mc_read(priv, *vlanmc_idx, vlanmc);
if (ret)
return ret;
return 0;
}
/*
* rtl8365mb_vlan_port_get_pvid - Retrieve the port PVID
* @priv: realtek switch private structure
* @port: port index
* @pvid: pointer to store the retrieved VLAN ID
*
* Returns the port PVID if defined or 0 if not.
*
* Context: Can sleep. Takes and releases &priv->map_lock.
* Return: 0 on success or a negative error code on failure.
*/
int rtl8365mb_vlan_port_get_pvid(struct realtek_priv *priv, int port, u16 *pvid)
{
struct rtl8365mb_vlanmc vlanmc;
u8 vlanmc_idx;
int ret;
ret = rtl8365mb_vlan_get_pvid_mc(priv, port, &vlanmc_idx, &vlanmc);
if (ret)
return ret;
*pvid = vlanmc.evid;
return 0;
}
/*
* rtl8365mb_vlan_port_get_framefilter() - Get the ingress frame filtering mode
* for a port
* @priv: realtek switch private structure
* @port: port index
* @frame_type: pointer to store the retrieved ingress frame filter type
*
* Context: Can sleep. Takes and releases &priv->map_lock.
* Return: 0 on success, or a negative error code on failure.
*/
int
rtl8365mb_vlan_port_get_framefilter(struct realtek_priv *priv,
int port,
enum rtl8365mb_frame_ingress *frame_type)
{
u32 val;
int ret;
/* Even if ACCEPT_FRAME_TYPE_ANY, the switch will still check if the
* port is a member of vlan PVID
*/
ret = regmap_read(priv->map, RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_REG(port),
&val);
if (ret)
return ret;
*frame_type = field_get(RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_MASK(port),
val);
return 0;
}
/*
* rtl8365mb_vlan_port_set_framefilter() - Set the ingress frame filtering mode
* for a port
* @priv: realtek switch private structure
* @port: port index
* @frame_type: the ingress frame filter type to configure
*
* Context: Can sleep. Takes and releases &priv->map_lock.
* Return: 0 on success, or a negative error code on failure.
*/
int
rtl8365mb_vlan_port_set_framefilter(struct realtek_priv *priv,
int port,
enum rtl8365mb_frame_ingress frame_type)
{
u32 val;
/* Even if ACCEPT_FRAME_TYPE_ANY, the switch will still check if the
* port is a member of vlan PVID
*/
val = frame_type << RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_OFFSET(port);
return regmap_update_bits(priv->map,
RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_REG(port),
RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_MASK(port),
val);
}
/*
* rtl8365mb_vlan_pvid_port_set() - Configure a port's PVID and associated
* VLANMC entry
* @ds: dsa switch instance
* @port: port index
* @vid: target VID
* @extack: netlink extended ACK for error reporting
*
* Allocates or reuses a hardware VLANMC entry to map the given port to its new
* PVID. Gracefully unwinds and restores previous configuration if a hardware
* write operation fails during execution.
*
* Context: Can sleep. Must be called with &priv->vlan_lock held.
* Takes and releases &priv->map_lock.
* Return: 0 on success, or a negative error code on failure.
*/
int rtl8365mb_vlan_pvid_port_set(struct dsa_switch *ds, int port, u16 vid,
struct netlink_ext_ack *extack)
{
enum rtl8365mb_frame_ingress accepted_frame, prev_accepted_frame;
struct realtek_priv *priv = ds->priv;
struct rtl8365mb_vlanmc prev_vlanmc = {0};
u8 _unused_first_free_idx;
u8 prev_vlanmc_idx;
u8 vlanmc_idx;
int ret;
lockdep_assert_held(&priv->vlan_lock);
/* Read the old PVID exclusively to undo in case of error */
ret = rtl8365mb_vlan_get_pvid_mc(priv, port, &prev_vlanmc_idx,
&prev_vlanmc);
if (ret) {
dev_err(priv->dev, "Failed to read current VLAN MC\n");
return ret;
}
ret = rtl8365mb_vlan_port_get_framefilter(priv, port,
&prev_accepted_frame);
if (ret) {
dev_err(priv->dev, "Failed to get current framefilter\n");
return ret;
}
/* Find or allocate a new vlan MC and add port to members,
* although members are not checked by the HW in vlan MC.
*/
ret = rtl8365mb_vlan_mc_port_set(ds, port, vid, true, extack, true);
if (ret)
return ret;
/* look for existing entry */
ret = rtl8365mb_vlan_mc_find(priv, vid, &vlanmc_idx,
&_unused_first_free_idx);
if (ret) {
dev_err(priv->dev, "Failed to find a VLAN MC table index\n");
goto undo_vlan_mc_port_set;
}
if (!vlanmc_idx) {
dev_err(priv->dev, "VLAN should already exist in VLAN MC\n");
ret = -ENOENT;
goto undo_vlan_mc_port_set;
}
ret = rtl8365mb_vlan_port_set_pvid(priv, port, vlanmc_idx);
if (ret) {
dev_err(priv->dev, "Failed to set port PVID\n");
goto undo_vlan_mc_port_set;
}
/* Changing accept frame is what enables PVID (if not enabled before) */
accepted_frame = RTL8365MB_FRAME_TYPE_ANY_FRAME;
ret = rtl8365mb_vlan_port_set_framefilter(priv, port, accepted_frame);
if (ret) {
dev_err(priv->dev, "Failed to set port frame filter\n");
goto undo_vlan_port_set_pvid;
}
/* A VLAN can be added with PVID without removing from the old
* PVID VLAN. Clear PVID from the old VLAN MC (if needed).
*/
if (prev_vlanmc_idx && (prev_vlanmc.evid != vid)) {
ret = rtl8365mb_vlan_mc_port_set(ds, port, prev_vlanmc.evid,
false, NULL, false);
if (ret) {
dev_err(priv->dev, "Failed to clear old VLAN MC\n");
goto undo_set_framefilter;
}
}
return 0;
undo_set_framefilter:
(void)rtl8365mb_vlan_port_set_framefilter(priv, port,
prev_accepted_frame);
undo_vlan_port_set_pvid:
(void)rtl8365mb_vlan_port_set_pvid(priv, port, prev_vlanmc_idx);
undo_vlan_mc_port_set:
if (prev_vlanmc.evid != vid)
(void)rtl8365mb_vlan_mc_port_set(ds, port, vid, false, NULL,
false);
return ret;
}
/*
* rtl8365mb_vlan_pvid_port_clear() - Remove a port's PVID configuration
* @ds: dsa switch instance
* @port: port index
* @vid: VLAN VID for PVID
*
* Resets the target port's hardware PVID allocation to 0. Cleans up and frees
* the associated VLANMC entry if no other ports are referencing it.
*
* Context: Can sleep. Must be called with &priv->vlan_lock held.
* Takes and releases &priv->map_lock.
* Return: 0 on success, or a negative error code on failure.
*/
int rtl8365mb_vlan_pvid_port_clear(struct dsa_switch *ds, int port, u16 vid)
{
enum rtl8365mb_frame_ingress accepted_frame, prev_accepted_frame;
struct realtek_priv *priv = ds->priv;
struct rtl8365mb_vlanmc vlanmc = {0};
u8 vlanmc_idx;
int ret;
lockdep_assert_held(&priv->vlan_lock);
ret = rtl8365mb_vlan_get_pvid_mc(priv, port, &vlanmc_idx,
&vlanmc);
if (ret) {
dev_err(priv->dev, "Failed to read current VLAN MC\n");
return ret;
}
/* Port is not using PVID. Nothing to remove. */
if (!vlanmc_idx)
return 0;
/* We are leaving a non PVID vlan, Nothing to remove. */
if (vlanmc.evid != vid)
return 0;
ret = rtl8365mb_vlan_port_get_framefilter(priv, port,
&prev_accepted_frame);
if (ret) {
dev_err(priv->dev, "Failed to get current framefilter\n");
return ret;
}
/* Changing accept frame is what really removes PVID. But only do
* that if we are filtering vlan
*/
if (dsa_port_is_vlan_filtering(dsa_to_port(ds, port))) {
accepted_frame = RTL8365MB_FRAME_TYPE_TAGGED_ONLY;
ret = rtl8365mb_vlan_port_set_framefilter(priv, port,
accepted_frame);
if (ret) {
dev_err(priv->dev, "Failed to set port frame filter\n");
return ret;
}
} else {
/* skip undo_set_framefilter */
accepted_frame = prev_accepted_frame;
}
ret = rtl8365mb_vlan_port_set_pvid(priv, port, 0);
if (ret) {
dev_err(priv->dev, "Failed to set port PVID to 0\n");
goto undo_set_framefilter;
}
/* Clears the VLAN MC membership and maybe VLAN MC entry if empty */
ret = rtl8365mb_vlan_mc_port_set(ds, port, vlanmc.evid,
false, NULL, false);
if (ret)
goto undo_port_set_pvid;
return 0;
undo_port_set_pvid:
(void)rtl8365mb_vlan_port_set_pvid(priv, port, vlanmc_idx);
undo_set_framefilter:
if (prev_accepted_frame != accepted_frame)
(void)rtl8365mb_vlan_port_set_framefilter(priv, port,
prev_accepted_frame);
return ret;
}
@@ -0,0 +1,39 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* VLAN configuration interface for the rtl8365mb switch family
*
* Copyright (C) 2022 Alvin Šipraga <alsi@bang-olufsen.dk>
*
*/
#ifndef _REALTEK_RTL8365MB_VLAN_H
#define _REALTEK_RTL8365MB_VLAN_H
#include <linux/types.h>
#include "realtek.h"
enum rtl8365mb_frame_ingress {
RTL8365MB_FRAME_TYPE_ANY_FRAME = 0,
RTL8365MB_FRAME_TYPE_TAGGED_ONLY,
RTL8365MB_FRAME_TYPE_UNTAGGED_ONLY,
};
int rtl8365mb_vlan_port_get_pvid(struct realtek_priv *priv, int port,
u16 *pvid);
int
rtl8365mb_vlan_port_get_framefilter(struct realtek_priv *priv,
int port,
enum rtl8365mb_frame_ingress *frame_type);
int
rtl8365mb_vlan_port_set_framefilter(struct realtek_priv *priv,
int port,
enum rtl8365mb_frame_ingress frame_type);
int rtl8365mb_vlan_4k_port_add(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_vlan *vlan,
struct netlink_ext_ack *extack);
int rtl8365mb_vlan_4k_port_del(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_vlan *vlan);
int rtl8365mb_vlan_pvid_port_set(struct dsa_switch *ds, int port, u16 vid,
struct netlink_ext_ack *extack);
int rtl8365mb_vlan_pvid_port_clear(struct dsa_switch *ds, int port, u16 vid);
#endif /* _REALTEK_RTL8365MB_VLAN_H */
@@ -0,0 +1,444 @@
// SPDX-License-Identifier: GPL-2.0
/* Realtek SMI library helpers for the RTL8366x variants
* RTL8366RB and RTL8366S
*
* Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org>
* Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
* Copyright (C) 2010 Antti Seppälä <a.seppala@gmail.com>
* Copyright (C) 2010 Roman Yeryomin <roman@advem.lv>
* Copyright (C) 2011 Colin Leitner <colin.leitner@googlemail.com>
*/
#include <linux/if_bridge.h>
#include <net/dsa.h>
#include "realtek.h"
int rtl8366_mc_is_used(struct realtek_priv *priv, int mc_index, int *used)
{
int ret;
int i;
*used = 0;
for (i = 0; i < priv->num_ports; i++) {
int index = 0;
ret = priv->ops->get_mc_index(priv, i, &index);
if (ret)
return ret;
if (mc_index == index) {
*used = 1;
break;
}
}
return 0;
}
EXPORT_SYMBOL_NS_GPL(rtl8366_mc_is_used, "REALTEK_DSA");
/**
* rtl8366_obtain_mc() - retrieve or allocate a VLAN member configuration
* @priv: the Realtek SMI device instance
* @vid: the VLAN ID to look up or allocate
* @vlanmc: the pointer will be assigned to a pointer to a valid member config
* if successful
* @return: index of a new member config or negative error number
*/
static int rtl8366_obtain_mc(struct realtek_priv *priv, int vid,
struct rtl8366_vlan_mc *vlanmc)
{
struct rtl8366_vlan_4k vlan4k;
int ret;
int i;
/* Try to find an existing member config entry for this VID */
for (i = 0; i < priv->num_vlan_mc; i++) {
ret = priv->ops->get_vlan_mc(priv, i, vlanmc);
if (ret) {
dev_err(priv->dev, "error searching for VLAN MC %d for VID %d\n",
i, vid);
return ret;
}
if (vid == vlanmc->vid)
return i;
}
/* We have no MC entry for this VID, try to find an empty one */
for (i = 0; i < priv->num_vlan_mc; i++) {
ret = priv->ops->get_vlan_mc(priv, i, vlanmc);
if (ret) {
dev_err(priv->dev, "error searching for VLAN MC %d for VID %d\n",
i, vid);
return ret;
}
if (vlanmc->vid == 0 && vlanmc->member == 0) {
/* Update the entry from the 4K table */
ret = priv->ops->get_vlan_4k(priv, vid, &vlan4k);
if (ret) {
dev_err(priv->dev, "error looking for 4K VLAN MC %d for VID %d\n",
i, vid);
return ret;
}
vlanmc->vid = vid;
vlanmc->member = vlan4k.member;
vlanmc->untag = vlan4k.untag;
vlanmc->fid = vlan4k.fid;
ret = priv->ops->set_vlan_mc(priv, i, vlanmc);
if (ret) {
dev_err(priv->dev, "unable to set/update VLAN MC %d for VID %d\n",
i, vid);
return ret;
}
dev_dbg(priv->dev, "created new MC at index %d for VID %d\n",
i, vid);
return i;
}
}
/* MC table is full, try to find an unused entry and replace it */
for (i = 0; i < priv->num_vlan_mc; i++) {
int used;
ret = rtl8366_mc_is_used(priv, i, &used);
if (ret)
return ret;
if (!used) {
/* Update the entry from the 4K table */
ret = priv->ops->get_vlan_4k(priv, vid, &vlan4k);
if (ret)
return ret;
vlanmc->vid = vid;
vlanmc->member = vlan4k.member;
vlanmc->untag = vlan4k.untag;
vlanmc->fid = vlan4k.fid;
ret = priv->ops->set_vlan_mc(priv, i, vlanmc);
if (ret) {
dev_err(priv->dev, "unable to set/update VLAN MC %d for VID %d\n",
i, vid);
return ret;
}
dev_dbg(priv->dev, "recycled MC at index %i for VID %d\n",
i, vid);
return i;
}
}
dev_err(priv->dev, "all VLAN member configurations are in use\n");
return -ENOSPC;
}
int rtl8366_set_vlan(struct realtek_priv *priv, int vid, u32 member,
u32 untag, u32 fid)
{
struct rtl8366_vlan_mc vlanmc;
struct rtl8366_vlan_4k vlan4k;
int mc;
int ret;
if (!priv->ops->is_vlan_valid(priv, vid))
return -EINVAL;
dev_dbg(priv->dev,
"setting VLAN%d 4k members: 0x%02x, untagged: 0x%02x\n",
vid, member, untag);
/* Update the 4K table */
ret = priv->ops->get_vlan_4k(priv, vid, &vlan4k);
if (ret)
return ret;
vlan4k.member |= member;
vlan4k.untag |= untag;
vlan4k.fid = fid;
ret = priv->ops->set_vlan_4k(priv, &vlan4k);
if (ret)
return ret;
dev_dbg(priv->dev,
"resulting VLAN%d 4k members: 0x%02x, untagged: 0x%02x\n",
vid, vlan4k.member, vlan4k.untag);
/* Find or allocate a member config for this VID */
ret = rtl8366_obtain_mc(priv, vid, &vlanmc);
if (ret < 0)
return ret;
mc = ret;
/* Update the MC entry */
vlanmc.member |= member;
vlanmc.untag |= untag;
vlanmc.fid = fid;
/* Commit updates to the MC entry */
ret = priv->ops->set_vlan_mc(priv, mc, &vlanmc);
if (ret)
dev_err(priv->dev, "failed to commit changes to VLAN MC index %d for VID %d\n",
mc, vid);
else
dev_dbg(priv->dev,
"resulting VLAN%d MC members: 0x%02x, untagged: 0x%02x\n",
vid, vlanmc.member, vlanmc.untag);
return ret;
}
EXPORT_SYMBOL_NS_GPL(rtl8366_set_vlan, "REALTEK_DSA");
int rtl8366_set_pvid(struct realtek_priv *priv, unsigned int port,
unsigned int vid)
{
struct rtl8366_vlan_mc vlanmc;
int mc;
int ret;
if (!priv->ops->is_vlan_valid(priv, vid))
return -EINVAL;
/* Find or allocate a member config for this VID */
ret = rtl8366_obtain_mc(priv, vid, &vlanmc);
if (ret < 0)
return ret;
mc = ret;
ret = priv->ops->set_mc_index(priv, port, mc);
if (ret) {
dev_err(priv->dev, "set PVID: failed to set MC index %d for port %d\n",
mc, port);
return ret;
}
dev_dbg(priv->dev, "set PVID: the PVID for port %d set to %d using existing MC index %d\n",
port, vid, mc);
return 0;
}
EXPORT_SYMBOL_NS_GPL(rtl8366_set_pvid, "REALTEK_DSA");
int rtl8366_enable_vlan4k(struct realtek_priv *priv, bool enable)
{
int ret;
/* To enable 4k VLAN, ordinary VLAN must be enabled first,
* but if we disable 4k VLAN it is fine to leave ordinary
* VLAN enabled.
*/
if (enable) {
/* Make sure VLAN is ON */
ret = priv->ops->enable_vlan(priv, true);
if (ret)
return ret;
priv->vlan_enabled = true;
}
ret = priv->ops->enable_vlan4k(priv, enable);
if (ret)
return ret;
priv->vlan4k_enabled = enable;
return 0;
}
EXPORT_SYMBOL_NS_GPL(rtl8366_enable_vlan4k, "REALTEK_DSA");
int rtl8366_enable_vlan(struct realtek_priv *priv, bool enable)
{
int ret;
ret = priv->ops->enable_vlan(priv, enable);
if (ret)
return ret;
priv->vlan_enabled = enable;
/* If we turn VLAN off, make sure that we turn off
* 4k VLAN as well, if that happened to be on.
*/
if (!enable) {
priv->vlan4k_enabled = false;
ret = priv->ops->enable_vlan4k(priv, false);
}
return ret;
}
EXPORT_SYMBOL_NS_GPL(rtl8366_enable_vlan, "REALTEK_DSA");
int rtl8366_reset_vlan(struct realtek_priv *priv)
{
struct rtl8366_vlan_mc vlanmc;
int ret;
int i;
rtl8366_enable_vlan(priv, false);
rtl8366_enable_vlan4k(priv, false);
/* Clear the 16 VLAN member configurations */
vlanmc.vid = 0;
vlanmc.priority = 0;
vlanmc.member = 0;
vlanmc.untag = 0;
vlanmc.fid = 0;
for (i = 0; i < priv->num_vlan_mc; i++) {
ret = priv->ops->set_vlan_mc(priv, i, &vlanmc);
if (ret)
return ret;
}
return 0;
}
EXPORT_SYMBOL_NS_GPL(rtl8366_reset_vlan, "REALTEK_DSA");
int rtl8366_vlan_add(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_vlan *vlan,
struct netlink_ext_ack *extack)
{
bool untagged = !!(vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED);
bool pvid = !!(vlan->flags & BRIDGE_VLAN_INFO_PVID);
struct realtek_priv *priv = ds->priv;
u32 member = 0;
u32 untag = 0;
int ret;
if (!priv->ops->is_vlan_valid(priv, vlan->vid)) {
NL_SET_ERR_MSG_MOD(extack, "VLAN ID not valid");
return -EINVAL;
}
/* Enable VLAN in the hardware
* FIXME: what's with this 4k business?
* Just rtl8366_enable_vlan() seems inconclusive.
*/
ret = rtl8366_enable_vlan4k(priv, true);
if (ret) {
NL_SET_ERR_MSG_MOD(extack, "Failed to enable VLAN 4K");
return ret;
}
dev_dbg(priv->dev, "add VLAN %d on port %d, %s, %s\n",
vlan->vid, port, untagged ? "untagged" : "tagged",
pvid ? "PVID" : "no PVID");
member |= BIT(port);
if (untagged)
untag |= BIT(port);
ret = rtl8366_set_vlan(priv, vlan->vid, member, untag, 0);
if (ret) {
dev_err(priv->dev, "failed to set up VLAN %04x", vlan->vid);
return ret;
}
if (!pvid)
return 0;
ret = rtl8366_set_pvid(priv, port, vlan->vid);
if (ret) {
dev_err(priv->dev, "failed to set PVID on port %d to VLAN %04x",
port, vlan->vid);
return ret;
}
return 0;
}
EXPORT_SYMBOL_NS_GPL(rtl8366_vlan_add, "REALTEK_DSA");
int rtl8366_vlan_del(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_vlan *vlan)
{
struct realtek_priv *priv = ds->priv;
int ret, i;
dev_dbg(priv->dev, "del VLAN %d on port %d\n", vlan->vid, port);
for (i = 0; i < priv->num_vlan_mc; i++) {
struct rtl8366_vlan_mc vlanmc;
ret = priv->ops->get_vlan_mc(priv, i, &vlanmc);
if (ret)
return ret;
if (vlan->vid == vlanmc.vid) {
/* Remove this port from the VLAN */
vlanmc.member &= ~BIT(port);
vlanmc.untag &= ~BIT(port);
/*
* If no ports are members of this VLAN
* anymore then clear the whole member
* config so it can be reused.
*/
if (!vlanmc.member) {
vlanmc.vid = 0;
vlanmc.priority = 0;
vlanmc.fid = 0;
}
ret = priv->ops->set_vlan_mc(priv, i, &vlanmc);
if (ret) {
dev_err(priv->dev,
"failed to remove VLAN %04x\n",
vlan->vid);
return ret;
}
break;
}
}
return 0;
}
EXPORT_SYMBOL_NS_GPL(rtl8366_vlan_del, "REALTEK_DSA");
void rtl8366_get_strings(struct dsa_switch *ds, int port, u32 stringset,
uint8_t *data)
{
struct realtek_priv *priv = ds->priv;
int i;
if (port >= priv->num_ports)
return;
for (i = 0; i < priv->num_mib_counters; i++)
ethtool_puts(&data, priv->mib_counters[i].name);
}
EXPORT_SYMBOL_NS_GPL(rtl8366_get_strings, "REALTEK_DSA");
int rtl8366_get_sset_count(struct dsa_switch *ds, int port, int sset)
{
struct realtek_priv *priv = ds->priv;
/* We only support SS_STATS */
if (sset != ETH_SS_STATS)
return 0;
if (port >= priv->num_ports)
return -EINVAL;
return priv->num_mib_counters;
}
EXPORT_SYMBOL_NS_GPL(rtl8366_get_sset_count, "REALTEK_DSA");
void rtl8366_get_ethtool_stats(struct dsa_switch *ds, int port, uint64_t *data)
{
struct realtek_priv *priv = ds->priv;
int i;
int ret;
if (port >= priv->num_ports)
return;
for (i = 0; i < priv->num_mib_counters; i++) {
struct rtl8366_mib_counter *mib;
u64 mibvalue = 0;
mib = &priv->mib_counters[i];
ret = priv->ops->get_mib_counter(priv, port, mib, &mibvalue);
if (ret) {
dev_err(priv->dev, "error reading MIB counter %s\n",
mib->name);
}
data[i] = mibvalue;
}
}
EXPORT_SYMBOL_NS_GPL(rtl8366_get_ethtool_stats, "REALTEK_DSA");
@@ -0,0 +1,177 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/bitops.h>
#include <linux/regmap.h>
#include <net/dsa.h>
#include "rtl83xx.h"
#include "rtl8366rb.h"
static inline u32 rtl8366rb_led_group_port_mask(u8 led_group, u8 port)
{
switch (led_group) {
case 0:
return FIELD_PREP(RTL8366RB_LED_0_X_CTRL_MASK, BIT(port));
case 1:
return FIELD_PREP(RTL8366RB_LED_X_1_CTRL_MASK, BIT(port));
case 2:
return FIELD_PREP(RTL8366RB_LED_2_X_CTRL_MASK, BIT(port));
case 3:
return FIELD_PREP(RTL8366RB_LED_X_3_CTRL_MASK, BIT(port));
default:
return 0;
}
}
static int rb8366rb_get_port_led(struct rtl8366rb_led *led)
{
struct realtek_priv *priv = led->priv;
u8 led_group = led->led_group;
u8 port_num = led->port_num;
int ret;
u32 val;
ret = regmap_read(priv->map, RTL8366RB_LED_X_X_CTRL_REG(led_group),
&val);
if (ret) {
dev_err(priv->dev, "error reading LED on port %d group %d\n",
led_group, port_num);
return ret;
}
return !!(val & rtl8366rb_led_group_port_mask(led_group, port_num));
}
static int rb8366rb_set_port_led(struct rtl8366rb_led *led, bool enable)
{
struct realtek_priv *priv = led->priv;
u8 led_group = led->led_group;
u8 port_num = led->port_num;
int ret;
ret = regmap_update_bits(priv->map,
RTL8366RB_LED_X_X_CTRL_REG(led_group),
rtl8366rb_led_group_port_mask(led_group,
port_num),
enable ? 0xffff : 0);
if (ret) {
dev_err(priv->dev, "error updating LED on port %d group %d\n",
led_group, port_num);
return ret;
}
/* Change the LED group to manual controlled LEDs if required */
ret = rb8366rb_set_ledgroup_mode(priv, led_group,
RTL8366RB_LEDGROUP_FORCE);
if (ret) {
dev_err(priv->dev, "error updating LED GROUP group %d\n",
led_group);
return ret;
}
return 0;
}
static int
rtl8366rb_cled_brightness_set_blocking(struct led_classdev *ldev,
enum led_brightness brightness)
{
struct rtl8366rb_led *led = container_of(ldev, struct rtl8366rb_led,
cdev);
return rb8366rb_set_port_led(led, brightness == LED_ON);
}
static int rtl8366rb_setup_led(struct realtek_priv *priv, struct dsa_port *dp,
struct fwnode_handle *led_fwnode)
{
struct rtl8366rb *rb = priv->chip_data;
struct led_init_data init_data = { };
enum led_default_state state;
struct rtl8366rb_led *led;
char name[64];
u32 led_group;
int ret;
ret = fwnode_property_read_u32(led_fwnode, "reg", &led_group);
if (ret)
return ret;
if (led_group >= RTL8366RB_NUM_LEDGROUPS) {
dev_warn(priv->dev, "Invalid LED reg %d defined for port %d",
led_group, dp->index);
return -EINVAL;
}
led = &rb->leds[dp->index][led_group];
led->port_num = dp->index;
led->led_group = led_group;
led->priv = priv;
state = led_init_default_state_get(led_fwnode);
switch (state) {
case LEDS_DEFSTATE_ON:
led->cdev.brightness = 1;
rb8366rb_set_port_led(led, 1);
break;
case LEDS_DEFSTATE_KEEP:
led->cdev.brightness =
rb8366rb_get_port_led(led);
break;
case LEDS_DEFSTATE_OFF:
default:
led->cdev.brightness = 0;
rb8366rb_set_port_led(led, 0);
}
led->cdev.max_brightness = 1;
led->cdev.brightness_set_blocking =
rtl8366rb_cled_brightness_set_blocking;
init_data.fwnode = led_fwnode;
init_data.devname_mandatory = true;
snprintf(name, sizeof(name), "Realtek-%d:0%d:%d",
dp->ds->index, dp->index, led_group);
init_data.devicename = name;
ret = devm_led_classdev_register_ext(priv->dev, &led->cdev, &init_data);
if (ret) {
dev_warn(priv->dev, "Failed to init LED %d for port %d",
led_group, dp->index);
return ret;
}
return 0;
}
int rtl8366rb_setup_leds(struct realtek_priv *priv)
{
struct dsa_switch *ds = &priv->ds;
struct device_node *leds_np;
struct dsa_port *dp;
int ret = 0;
dsa_switch_for_each_port(dp, ds) {
if (!dp->dn)
continue;
leds_np = of_get_child_by_name(dp->dn, "leds");
if (!leds_np) {
dev_dbg(priv->dev, "No leds defined for port %d",
dp->index);
continue;
}
for_each_child_of_node_scoped(leds_np, led_np) {
ret = rtl8366rb_setup_led(priv, dp,
of_fwnode_handle(led_np));
if (ret)
break;
}
of_node_put(leds_np);
if (ret)
return ret;
}
return 0;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,107 @@
/* SPDX-License-Identifier: GPL-2.0+ */
#ifndef _RTL8366RB_H
#define _RTL8366RB_H
#include "realtek.h"
#define RTL8366RB_PORT_NUM_CPU 5
#define RTL8366RB_NUM_PORTS 6
#define RTL8366RB_PHY_NO_MAX 4
#define RTL8366RB_NUM_LEDGROUPS 4
#define RTL8366RB_PHY_ADDR_MAX 31
/* LED control registers */
/* The LED blink rate is global; it is used by all triggers in all groups. */
#define RTL8366RB_LED_BLINKRATE_REG 0x0430
#define RTL8366RB_LED_BLINKRATE_MASK 0x0007
#define RTL8366RB_LED_BLINKRATE_28MS 0x0000
#define RTL8366RB_LED_BLINKRATE_56MS 0x0001
#define RTL8366RB_LED_BLINKRATE_84MS 0x0002
#define RTL8366RB_LED_BLINKRATE_111MS 0x0003
#define RTL8366RB_LED_BLINKRATE_222MS 0x0004
#define RTL8366RB_LED_BLINKRATE_446MS 0x0005
/* LED trigger event for each group */
#define RTL8366RB_LED_CTRL_REG 0x0431
#define RTL8366RB_LED_CTRL_OFFSET(led_group) \
(4 * (led_group))
#define RTL8366RB_LED_CTRL_MASK(led_group) \
(0xf << RTL8366RB_LED_CTRL_OFFSET(led_group))
/* The RTL8366RB_LED_X_X registers are used to manually set the LED state only
* when the corresponding LED group in RTL8366RB_LED_CTRL_REG is
* RTL8366RB_LEDGROUP_FORCE. Otherwise, it is ignored.
*/
#define RTL8366RB_LED_0_1_CTRL_REG 0x0432
#define RTL8366RB_LED_2_3_CTRL_REG 0x0433
#define RTL8366RB_LED_X_X_CTRL_REG(led_group) \
((led_group) <= 1 ? \
RTL8366RB_LED_0_1_CTRL_REG : \
RTL8366RB_LED_2_3_CTRL_REG)
#define RTL8366RB_LED_0_X_CTRL_MASK GENMASK(5, 0)
#define RTL8366RB_LED_X_1_CTRL_MASK GENMASK(11, 6)
#define RTL8366RB_LED_2_X_CTRL_MASK GENMASK(5, 0)
#define RTL8366RB_LED_X_3_CTRL_MASK GENMASK(11, 6)
enum rtl8366_ledgroup_mode {
RTL8366RB_LEDGROUP_OFF = 0x0,
RTL8366RB_LEDGROUP_DUP_COL = 0x1,
RTL8366RB_LEDGROUP_LINK_ACT = 0x2,
RTL8366RB_LEDGROUP_SPD1000 = 0x3,
RTL8366RB_LEDGROUP_SPD100 = 0x4,
RTL8366RB_LEDGROUP_SPD10 = 0x5,
RTL8366RB_LEDGROUP_SPD1000_ACT = 0x6,
RTL8366RB_LEDGROUP_SPD100_ACT = 0x7,
RTL8366RB_LEDGROUP_SPD10_ACT = 0x8,
RTL8366RB_LEDGROUP_SPD100_10_ACT = 0x9,
RTL8366RB_LEDGROUP_FIBER = 0xa,
RTL8366RB_LEDGROUP_AN_FAULT = 0xb,
RTL8366RB_LEDGROUP_LINK_RX = 0xc,
RTL8366RB_LEDGROUP_LINK_TX = 0xd,
RTL8366RB_LEDGROUP_MASTER = 0xe,
RTL8366RB_LEDGROUP_FORCE = 0xf,
__RTL8366RB_LEDGROUP_MODE_MAX
};
#if IS_ENABLED(CONFIG_NET_DSA_REALTEK_RTL8366RB_LEDS)
struct rtl8366rb_led {
u8 port_num;
u8 led_group;
struct realtek_priv *priv;
struct led_classdev cdev;
};
int rtl8366rb_setup_leds(struct realtek_priv *priv);
#else
static inline int rtl8366rb_setup_leds(struct realtek_priv *priv)
{
return 0;
}
#endif /* IS_ENABLED(CONFIG_LEDS_CLASS) */
/**
* struct rtl8366rb - RTL8366RB-specific data
* @max_mtu: per-port max MTU setting
* @pvid_enabled: if PVID is set for respective port
* @leds: per-port and per-ledgroup led info
*/
struct rtl8366rb {
unsigned int max_mtu[RTL8366RB_NUM_PORTS];
bool pvid_enabled[RTL8366RB_NUM_PORTS];
#if IS_ENABLED(CONFIG_NET_DSA_REALTEK_RTL8366RB_LEDS)
struct rtl8366rb_led leds[RTL8366RB_NUM_PORTS][RTL8366RB_NUM_LEDGROUPS];
#endif
};
/* This code is used also with LEDs disabled */
int rb8366rb_set_ledgroup_mode(struct realtek_priv *priv,
u8 led_group,
enum rtl8366_ledgroup_mode mode);
#endif /* _RTL8366RB_H */
@@ -0,0 +1,902 @@
// SPDX-License-Identifier: GPL-2.0+
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/of_mdio.h>
#include <linux/if_bridge.h>
#include <linux/etherdevice.h>
#include "realtek.h"
#include "rtl83xx.h"
/**
* rtl83xx_lock() - Locks the mutex used by regmaps
* @ctx: realtek_priv pointer
*
* This function is passed to regmap to be used as the lock function.
* It is also used externally to block regmap before executing multiple
* operations that must happen in sequence (which will use
* realtek_priv.map_nolock instead).
*
* Context: Can sleep. Holds priv->map_lock lock.
* Return: nothing
*/
void rtl83xx_lock(void *ctx)
{
struct realtek_priv *priv = ctx;
mutex_lock(&priv->map_lock);
}
EXPORT_SYMBOL_NS_GPL(rtl83xx_lock, "REALTEK_DSA");
/**
* rtl83xx_unlock() - Unlocks the mutex used by regmaps
* @ctx: realtek_priv pointer
*
* This function unlocks the lock acquired by rtl83xx_lock.
*
* Context: Releases priv->map_lock lock.
* Return: nothing
*/
void rtl83xx_unlock(void *ctx)
{
struct realtek_priv *priv = ctx;
mutex_unlock(&priv->map_lock);
}
EXPORT_SYMBOL_NS_GPL(rtl83xx_unlock, "REALTEK_DSA");
static int rtl83xx_user_mdio_read(struct mii_bus *bus, int addr, int regnum)
{
struct realtek_priv *priv = bus->priv;
return priv->ops->phy_read(priv, addr, regnum);
}
static int rtl83xx_user_mdio_write(struct mii_bus *bus, int addr, int regnum,
u16 val)
{
struct realtek_priv *priv = bus->priv;
return priv->ops->phy_write(priv, addr, regnum, val);
}
/**
* rtl83xx_setup_user_mdio() - register the user mii bus driver
* @ds: DSA switch associated with this user_mii_bus
*
* Registers the MDIO bus for built-in Ethernet PHYs, and associates it with
* the mandatory 'mdio' child OF node of the switch.
*
* Context: Can sleep.
* Return: 0 on success, negative value for failure.
*/
int rtl83xx_setup_user_mdio(struct dsa_switch *ds)
{
struct realtek_priv *priv = ds->priv;
struct device_node *mdio_np;
struct mii_bus *bus;
int ret = 0;
mdio_np = of_get_child_by_name(priv->dev->of_node, "mdio");
if (!mdio_np) {
dev_err(priv->dev, "no MDIO bus node\n");
return -ENODEV;
}
bus = devm_mdiobus_alloc(priv->dev);
if (!bus) {
ret = -ENOMEM;
goto err_put_node;
}
bus->priv = priv;
bus->name = "Realtek user MII";
bus->read = rtl83xx_user_mdio_read;
bus->write = rtl83xx_user_mdio_write;
snprintf(bus->id, MII_BUS_ID_SIZE, "%s:user_mii", dev_name(priv->dev));
bus->parent = priv->dev;
ret = devm_of_mdiobus_register(priv->dev, bus, mdio_np);
if (ret) {
dev_err(priv->dev, "unable to register MDIO bus %s\n",
bus->id);
goto err_put_node;
}
priv->user_mii_bus = bus;
err_put_node:
of_node_put(mdio_np);
return ret;
}
EXPORT_SYMBOL_NS_GPL(rtl83xx_setup_user_mdio, "REALTEK_DSA");
/**
* rtl83xx_probe() - probe a Realtek switch
* @dev: the device being probed
* @interface_info: specific management interface info.
*
* This function initializes realtek_priv and reads data from the device tree
* node. The switch is hard resetted if a method is provided.
*
* Context: Can sleep.
* Return: Pointer to the realtek_priv or ERR_PTR() in case of failure.
*
* The realtek_priv pointer does not need to be freed as it is controlled by
* devres.
*/
struct realtek_priv *
rtl83xx_probe(struct device *dev,
const struct realtek_interface_info *interface_info)
{
const struct realtek_variant *var;
struct realtek_priv *priv;
struct regmap_config rc = {
.reg_bits = 10, /* A4..A0 R4..R0 */
.val_bits = 16,
.reg_stride = 1,
.max_register = 0xffff,
.reg_format_endian = REGMAP_ENDIAN_BIG,
.reg_read = interface_info->reg_read,
.reg_write = interface_info->reg_write,
.cache_type = REGCACHE_NONE,
.lock = rtl83xx_lock,
.unlock = rtl83xx_unlock,
};
int ret;
var = of_device_get_match_data(dev);
if (!var)
return ERR_PTR(-EINVAL);
priv = devm_kzalloc(dev, size_add(sizeof(*priv), var->chip_data_sz),
GFP_KERNEL);
if (!priv)
return ERR_PTR(-ENOMEM);
ret = devm_mutex_init(dev, &priv->map_lock);
if (ret)
return ERR_PTR(ret);
ret = devm_mutex_init(dev, &priv->vlan_lock);
if (ret)
return ERR_PTR(ret);
ret = devm_mutex_init(dev, &priv->l2_lock);
if (ret)
return ERR_PTR(ret);
rc.lock_arg = priv;
priv->map = devm_regmap_init(dev, NULL, priv, &rc);
if (IS_ERR(priv->map)) {
ret = PTR_ERR(priv->map);
dev_err(dev, "regmap init failed: %d\n", ret);
return ERR_PTR(ret);
}
rc.disable_locking = true;
priv->map_nolock = devm_regmap_init(dev, NULL, priv, &rc);
if (IS_ERR(priv->map_nolock)) {
ret = PTR_ERR(priv->map_nolock);
dev_err(dev, "regmap init failed: %d\n", ret);
return ERR_PTR(ret);
}
/* Link forward and backward */
priv->dev = dev;
priv->variant = var;
priv->ops = var->ops;
priv->chip_data = (void *)priv + sizeof(*priv);
spin_lock_init(&priv->lock);
priv->leds_disabled = of_property_read_bool(dev->of_node,
"realtek,disable-leds");
/* TODO: if power is software controlled, set up any regulators here */
priv->reset_ctl = devm_reset_control_get_optional(dev, NULL);
if (IS_ERR(priv->reset_ctl))
return dev_err_cast_probe(dev, priv->reset_ctl,
"failed to get reset control\n");
priv->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
if (IS_ERR(priv->reset)) {
dev_err(dev, "failed to get RESET GPIO\n");
return ERR_CAST(priv->reset);
}
dev_set_drvdata(dev, priv);
if (priv->reset_ctl || priv->reset) {
rtl83xx_reset_assert(priv);
dev_dbg(dev, "asserted RESET\n");
msleep(REALTEK_HW_STOP_DELAY);
rtl83xx_reset_deassert(priv);
msleep(REALTEK_HW_START_DELAY);
dev_dbg(dev, "deasserted RESET\n");
}
return priv;
}
EXPORT_SYMBOL_NS_GPL(rtl83xx_probe, "REALTEK_DSA");
/**
* rtl83xx_register_switch() - detects and register a switch
* @priv: realtek_priv pointer
*
* This function first checks the switch chip ID and register a DSA
* switch.
*
* Context: Can sleep. Takes and releases priv->map_lock.
* Return: 0 on success, negative value for failure.
*/
int rtl83xx_register_switch(struct realtek_priv *priv)
{
struct dsa_switch *ds = &priv->ds;
int ret;
ret = priv->ops->detect(priv);
if (ret) {
dev_err_probe(priv->dev, ret, "unable to detect switch\n");
return ret;
}
ds->priv = priv;
ds->dev = priv->dev;
ds->ops = priv->variant->ds_ops;
ds->phylink_mac_ops = priv->variant->phylink_mac_ops;
ds->num_ports = priv->num_ports;
ret = dsa_register_switch(ds);
if (ret) {
dev_err_probe(priv->dev, ret, "unable to register switch\n");
return ret;
}
return 0;
}
EXPORT_SYMBOL_NS_GPL(rtl83xx_register_switch, "REALTEK_DSA");
/**
* rtl83xx_unregister_switch() - unregister a switch
* @priv: realtek_priv pointer
*
* This function unregister a DSA switch.
*
* Context: Can sleep.
* Return: Nothing.
*/
void rtl83xx_unregister_switch(struct realtek_priv *priv)
{
struct dsa_switch *ds = &priv->ds;
dsa_unregister_switch(ds);
}
EXPORT_SYMBOL_NS_GPL(rtl83xx_unregister_switch, "REALTEK_DSA");
/**
* rtl83xx_shutdown() - shutdown a switch
* @priv: realtek_priv pointer
*
* This function shuts down the DSA switch and cleans the platform driver data,
* to prevent realtek_{smi,mdio}_remove() from running afterwards, which is
* possible if the parent bus implements its own .shutdown() as .remove().
*
* Context: Can sleep.
* Return: Nothing.
*/
void rtl83xx_shutdown(struct realtek_priv *priv)
{
struct dsa_switch *ds = &priv->ds;
dsa_switch_shutdown(ds);
dev_set_drvdata(priv->dev, NULL);
}
EXPORT_SYMBOL_NS_GPL(rtl83xx_shutdown, "REALTEK_DSA");
/**
* rtl83xx_remove() - Cleanup a realtek switch driver
* @priv: realtek_priv pointer
*
* Placehold for common cleanup procedures.
*
* Context: Any
* Return: nothing
*/
void rtl83xx_remove(struct realtek_priv *priv)
{
}
EXPORT_SYMBOL_NS_GPL(rtl83xx_remove, "REALTEK_DSA");
void rtl83xx_reset_assert(struct realtek_priv *priv)
{
int ret;
ret = reset_control_assert(priv->reset_ctl);
if (ret)
dev_warn(priv->dev,
"Failed to assert the switch reset control: %pe\n",
ERR_PTR(ret));
gpiod_set_value(priv->reset, true);
}
void rtl83xx_reset_deassert(struct realtek_priv *priv)
{
int ret;
ret = reset_control_deassert(priv->reset_ctl);
if (ret)
dev_warn(priv->dev,
"Failed to deassert the switch reset control: %pe\n",
ERR_PTR(ret));
gpiod_set_value(priv->reset, false);
}
/**
* rtl83xx_port_bridge_join() - join a port to a bridge
* @ds: DSA switch instance
* @port: port index
* @bridge: bridge being joined
* @tx_forward_offload: if the switch can offload TX forwarding
* @extack: netlink extended ack for reporting errors
*
* This function handles joining a port to a bridge. It updates the port
* isolation masks and EFID.
*
* Context: Can sleep.
* Return: 0 on success, negative value for failure.
*/
int rtl83xx_port_bridge_join(struct dsa_switch *ds, int port,
struct dsa_bridge bridge,
bool *tx_forward_offload,
struct netlink_ext_ack *extack)
{
struct realtek_priv *priv = ds->priv;
struct dsa_port *dp;
u32 mask = 0;
int ret;
if (!priv->ops->port_add_isolation)
return -EOPNOTSUPP;
if (!priv->ops->port_set_learning)
return -EOPNOTSUPP;
dev_dbg(priv->dev, "bridge %d join port %d\n", bridge.num, port);
/* Add this port to the isolation group of every other port
* offloading this bridge.
*/
dsa_switch_for_each_user_port(dp, ds) {
/* Handle this port after */
if (dp->index == port)
continue;
/* Skip ports that are not in this bridge */
if (!dsa_port_offloads_bridge(dp, &bridge))
continue;
ret = priv->ops->port_add_isolation(priv, dp->index, BIT(port));
if (ret)
goto undo_isolation;
mask |= BIT(dp->index);
}
/* If we support cascade switches, it should also include the
* downstream DSA ports to the isolation group.
*/
/* Add those ports to the isolation group of this port */
ret = priv->ops->port_add_isolation(priv, port, mask);
if (ret)
goto undo_isolation;
/* Use the bridge number as the EFID for this port */
if (priv->ops->port_set_efid) {
ret = priv->ops->port_set_efid(priv, port, bridge.num);
if (ret)
goto undo_self_isolation;
}
ret = priv->ops->port_set_learning(priv, port, true);
if (ret)
goto undo_efid;
return 0;
undo_efid:
if (priv->ops->port_set_efid)
priv->ops->port_set_efid(priv, port, 0);
undo_self_isolation:
priv->ops->port_remove_isolation(priv, port, mask);
undo_isolation:
dsa_switch_for_each_port(dp, ds) {
if (mask & BIT(dp->index))
priv->ops->port_remove_isolation(priv, dp->index,
BIT(port));
}
return ret;
}
EXPORT_SYMBOL_NS_GPL(rtl83xx_port_bridge_join, "REALTEK_DSA");
/**
* rtl83xx_port_bridge_leave() - leave a bridge
* @ds: DSA switch instance
* @port: port index
* @bridge: bridge being left
*
* This function handles removing a port from a bridge. It updates the port
* isolation masks and EFID.
*
* Context: Can sleep.
* Return: nothing
*/
void rtl83xx_port_bridge_leave(struct dsa_switch *ds, int port,
struct dsa_bridge bridge)
{
struct realtek_priv *priv = ds->priv;
struct dsa_port *dp;
u32 mask = 0;
int ret;
if (!priv->ops->port_remove_isolation)
return;
if (!priv->ops->port_set_learning)
return;
dev_dbg(priv->dev, "bridge %d leave port %d\n", bridge.num, port);
/* Remove this port from the isolation group of every other
* port offloading this bridge.
*/
dsa_switch_for_each_user_port(dp, ds) {
/* Handle this port after */
if (dp->index == port)
continue;
/* Skip ports that are not in this bridge */
if (!dsa_port_offloads_bridge(dp, &bridge))
continue;
ret = priv->ops->port_remove_isolation(priv, dp->index,
BIT(port));
if (ret)
dev_err(priv->dev,
"failed to isolate port %d from port %d: %pe\n",
port, dp->index, ERR_PTR(ret));
mask |= BIT(dp->index);
}
/* If we support cascade switches, it should also exclude the
* downstream DSA ports from the isolation group.
*/
ret = priv->ops->port_set_learning(priv, port, false);
if (ret)
dev_err(priv->dev,
"failed to disable learning on port %d: %pe\n",
port, ERR_PTR(ret));
/* Remove those ports from the isolation group of this port */
ret = priv->ops->port_remove_isolation(priv, port, mask);
if (ret)
dev_err(priv->dev,
"failed to remove isolation mask from port %d: %pe\n",
port, ERR_PTR(ret));
/* Revert to the default EFID 0 for standalone mode */
if (priv->ops->port_set_efid) {
ret = priv->ops->port_set_efid(priv, port, 0);
if (ret)
dev_err(priv->dev,
"failed to clear EFID on port %d: %pe\n",
port, ERR_PTR(ret));
}
}
EXPORT_SYMBOL_NS_GPL(rtl83xx_port_bridge_leave, "REALTEK_DSA");
/**
* rtl83xx_port_fast_age() - flush dynamic FDB entries learned on a port
* @ds: DSA switch instance
* @port: port index
*
* This function requests the switch to age out dynamic FDB entries learned on
* @port.
*
* Context: Can sleep.
* Return: Nothing.
*/
void rtl83xx_port_fast_age(struct dsa_switch *ds, int port)
{
struct realtek_priv *priv = ds->priv;
int ret;
if (!priv->ops->l2_flush) {
dev_warn_once(priv->dev, "l2_flush op not defined\n");
return;
}
dev_dbg(priv->dev, "fast_age port %d\n", port);
mutex_lock(&priv->l2_lock);
ret = priv->ops->l2_flush(priv, port, 0);
mutex_unlock(&priv->l2_lock);
if (ret)
dev_err(priv->dev, "failed to fast age on port %d: %d\n", port,
ret);
}
EXPORT_SYMBOL_NS_GPL(rtl83xx_port_fast_age, "REALTEK_DSA");
/**
* rtl83xx_port_fdb_add() - add a static FDB entry to a port database
* @ds: DSA switch instance
* @port: port index
* @addr: MAC address to add
* @vid: VLAN ID associated with @addr
* @db: database where the entry should be added
*
* This function adds a static unicast FDB entry to the standalone port
* database or to a bridge database.
*
* Context: Can sleep.
* Return: 0 on success, negative value for failure.
*/
int rtl83xx_port_fdb_add(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid,
struct dsa_db db)
{
struct realtek_priv *priv = ds->priv;
int efid;
int ret;
if (is_multicast_ether_addr(addr))
return -EOPNOTSUPP;
if (!priv->ops->l2_add_uc)
return -EOPNOTSUPP;
if (db.type != DSA_DB_PORT && db.type != DSA_DB_BRIDGE)
return -EOPNOTSUPP;
/* Bridge ports use bridge.num as EFID, while standalone ports use
* EFID 0. FDB entries for the CPU port follow the bridge EFID due
* to assisted learning.
*/
efid = db.type == DSA_DB_BRIDGE ? db.bridge.num : 0;
dev_dbg(priv->dev, "%s: port:%d addr:%pM efid:%d vid:%d dbtype:%d\n",
__func__, port, addr, efid, vid, db.type);
mutex_lock(&priv->l2_lock);
ret = priv->ops->l2_add_uc(priv, port, addr, efid, vid);
mutex_unlock(&priv->l2_lock);
if (ret)
dev_err(priv->dev, "fdb_add ERROR %pe\n", ERR_PTR(ret));
return ret;
}
EXPORT_SYMBOL_NS_GPL(rtl83xx_port_fdb_add, "REALTEK_DSA");
/**
* rtl83xx_port_fdb_del() - delete a static FDB entry from a port database
* @ds: DSA switch instance
* @port: port index
* @addr: MAC address to delete
* @vid: VLAN ID associated with @addr
* @db: database where the entry should be removed
*
* This function deletes a static unicast FDB entry from the standalone port
* database or from a bridge database.
*
* Context: Can sleep.
* Return: 0 on success, negative value for failure.
*/
int rtl83xx_port_fdb_del(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid,
struct dsa_db db)
{
struct realtek_priv *priv = ds->priv;
int efid;
int ret;
if (is_multicast_ether_addr(addr))
return -EOPNOTSUPP;
if (!priv->ops->l2_del_uc)
return -EOPNOTSUPP;
if (db.type != DSA_DB_PORT && db.type != DSA_DB_BRIDGE)
return -EOPNOTSUPP;
/*
* DSA_DB_BRIDGE ports use bridge number [1..N] as EFID, while
* DSA_DB_PORT use the default EFID (0), not used by any bridge.
*/
efid = db.type == DSA_DB_BRIDGE ? db.bridge.num : 0;
dev_dbg(priv->dev, "%s: port:%d addr:%pM efid:%d vid:%d dbtype:%d\n",
__func__, port, addr, efid, vid, db.type);
mutex_lock(&priv->l2_lock);
ret = priv->ops->l2_del_uc(priv, port, addr, efid, vid);
mutex_unlock(&priv->l2_lock);
if (ret)
dev_err(priv->dev, "fdb_del ERROR %pe\n", ERR_PTR(ret));
return ret;
}
EXPORT_SYMBOL_NS_GPL(rtl83xx_port_fdb_del, "REALTEK_DSA");
/**
* rtl83xx_port_fdb_dump() - iterate over FDB entries associated with a port
* @ds: DSA switch instance
* @port: port index
* @cb: callback invoked for each entry
* @data: opaque pointer passed to @cb
*
* This function walks the unicast FDB entries associated with @port and calls
* @cb for each matching entry.
*
* Context: Can sleep.
* Return: 0 on success, or negative value for failure.
*/
int rtl83xx_port_fdb_dump(struct dsa_switch *ds, int port,
dsa_fdb_dump_cb_t *cb, void *data)
{
struct realtek_fdb_entry entry = { 0 };
struct realtek_priv *priv = ds->priv;
u16 start_addr, addr = 0;
int ret = 0;
if (!priv->ops->l2_get_next_uc)
return -EOPNOTSUPP;
mutex_lock(&priv->l2_lock);
while (true) {
start_addr = addr;
dev_dbg(priv->dev, "l2_get_next_uc, addr:%d, port:%d\n",
addr, port);
ret = priv->ops->l2_get_next_uc(priv, &addr, port, &entry);
dev_dbg(priv->dev,
"%s addr:%d mac:%pM vid:%d static:%d ret:%pe\n",
__func__, addr, entry.mac_addr, entry.vid,
entry.is_static, ERR_PTR(ret));
if (ret == -ENOENT) {
/* If the table is empty, returns without errors. Note
* that the l2_get_next_uc overflow to the first match
* when it reaches the end of the table.
*/
ret = 0;
break;
}
if (ret)
break;
/* When the addr returned is before the requested one, it
* indicates that we reached the end.
*/
if (addr < start_addr)
break;
ret = cb(entry.mac_addr, entry.vid, entry.is_static, data);
if (ret)
break;
addr++;
}
mutex_unlock(&priv->l2_lock);
return ret;
}
EXPORT_SYMBOL_NS_GPL(rtl83xx_port_fdb_dump, "REALTEK_DSA");
/**
* rtl83xx_port_mdb_add() - add a multicast database entry to a port database
* @ds: DSA switch instance
* @port: port index
* @mdb: multicast database entry to add
* @db: database where the entry should be added
*
* This function adds a multicast database entry to the standalone port
* database or to a bridge database.
*
* Context: Can sleep.
* Return: 0 on success, negative value for failure.
*/
int rtl83xx_port_mdb_add(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_mdb *mdb,
struct dsa_db db)
{
struct realtek_priv *priv = ds->priv;
const unsigned char *addr = mdb->addr;
u16 vid = mdb->vid;
int efid;
int ret;
if (!priv->ops->l2_add_mc)
return -EOPNOTSUPP;
if (db.type != DSA_DB_PORT && db.type != DSA_DB_BRIDGE)
return -EOPNOTSUPP;
/* EFID is not used by hardware MDB entries; debugging only */
efid = db.type == DSA_DB_BRIDGE ? db.bridge.num : 0;
dev_dbg(priv->dev, "%s: port:%d addr:%pM efid:%d vid:%d dbtype:%d\n",
__func__, port, addr, efid, vid, db.type);
mutex_lock(&priv->l2_lock);
ret = priv->ops->l2_add_mc(priv, port, addr, vid);
mutex_unlock(&priv->l2_lock);
if (ret)
dev_err(priv->dev, "mdb_add ERROR %pe\n", ERR_PTR(ret));
return ret;
}
EXPORT_SYMBOL_NS_GPL(rtl83xx_port_mdb_add, "REALTEK_DSA");
/**
* rtl83xx_port_mdb_del() - delete a multicast database entry from a port
* database
* @ds: DSA switch instance
* @port: port index
* @mdb: multicast database entry to delete
* @db: database where the entry should be removed
*
* This function deletes a multicast database entry from the standalone port
* database or from a bridge database.
*
* Context: Can sleep.
* Return: 0 on success, negative value for failure.
*/
int rtl83xx_port_mdb_del(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_mdb *mdb,
struct dsa_db db)
{
struct realtek_priv *priv = ds->priv;
const unsigned char *addr = mdb->addr;
u16 vid = mdb->vid;
int efid;
int ret;
if (!priv->ops->l2_del_mc)
return -EOPNOTSUPP;
if (db.type != DSA_DB_PORT && db.type != DSA_DB_BRIDGE)
return -EOPNOTSUPP;
/* EFID is not used by hardware MDB entries; debugging only */
efid = db.type == DSA_DB_BRIDGE ? db.bridge.num : 0;
dev_dbg(priv->dev, "%s: port:%d addr:%pM efid:%d vid:%d dbtype:%d\n",
__func__, port, addr, efid, vid, db.type);
mutex_lock(&priv->l2_lock);
ret = priv->ops->l2_del_mc(priv, port, addr, vid);
mutex_unlock(&priv->l2_lock);
if (ret)
dev_err(priv->dev, "mdb_del ERROR %pe\n", ERR_PTR(ret));
return ret;
}
EXPORT_SYMBOL_NS_GPL(rtl83xx_port_mdb_del, "REALTEK_DSA");
/**
* rtl83xx_port_bridge_flags() - set port bridge flags
* @ds: DSA switch instance
* @port: port index
* @flags: bridge port flags
* @extack: netlink extended ack for reporting errors
*
* This function handles setting bridge port flags like learning and flooding.
*
* Context: Can sleep.
* Return: 0 on success, negative value for failure.
*/
int rtl83xx_port_bridge_flags(struct dsa_switch *ds, int port,
struct switchdev_brport_flags flags,
struct netlink_ext_ack *extack)
{
struct realtek_priv *priv = ds->priv;
bool enable;
int ret;
if (flags.mask & BR_LEARNING) {
if (!priv->ops->port_set_learning)
return -EOPNOTSUPP;
enable = !!(flags.val & BR_LEARNING);
ret = priv->ops->port_set_learning(priv, port, enable);
if (ret)
return ret;
}
if (flags.mask & BR_FLOOD) {
if (!priv->ops->port_set_ucast_flood)
return -EOPNOTSUPP;
enable = !!(flags.val & BR_FLOOD);
ret = priv->ops->port_set_ucast_flood(priv, port, enable);
if (ret)
return ret;
}
if (flags.mask & BR_MCAST_FLOOD) {
if (!priv->ops->port_set_mcast_flood)
return -EOPNOTSUPP;
enable = !!(flags.val & BR_MCAST_FLOOD);
ret = priv->ops->port_set_mcast_flood(priv, port, enable);
if (ret)
return ret;
}
if (flags.mask & BR_BCAST_FLOOD) {
if (!priv->ops->port_set_bcast_flood)
return -EOPNOTSUPP;
enable = !!(flags.val & BR_BCAST_FLOOD);
ret = priv->ops->port_set_bcast_flood(priv, port, enable);
if (ret)
return ret;
}
return 0;
}
EXPORT_SYMBOL_NS_GPL(rtl83xx_port_bridge_flags, "REALTEK_DSA");
/**
* rtl83xx_setup_port_flood_control() - setup default flood control for a port
* @priv: realtek_priv pointer
* @port: port index
*
* This function enables flooding for a given port.
*
* Context: Can sleep.
* Return: 0 on success, negative value for failure.
*/
int rtl83xx_setup_port_flood_control(struct realtek_priv *priv, int port)
{
int ret;
if (priv->ops->port_set_ucast_flood) {
ret = priv->ops->port_set_ucast_flood(priv, port, true);
if (ret)
return ret;
}
if (priv->ops->port_set_mcast_flood) {
ret = priv->ops->port_set_mcast_flood(priv, port, true);
if (ret)
return ret;
}
if (priv->ops->port_set_bcast_flood) {
ret = priv->ops->port_set_bcast_flood(priv, port, true);
if (ret)
return ret;
}
return 0;
}
EXPORT_SYMBOL_NS_GPL(rtl83xx_setup_port_flood_control, "REALTEK_DSA");
MODULE_AUTHOR("Luiz Angelo Daros de Luca <luizluca@gmail.com>");
MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
MODULE_DESCRIPTION("Realtek DSA switches common module");
MODULE_LICENSE("GPL");
@@ -0,0 +1,51 @@
/* SPDX-License-Identifier: GPL-2.0+ */
#ifndef _RTL83XX_H
#define _RTL83XX_H
struct realtek_interface_info {
int (*reg_read)(void *ctx, u32 reg, u32 *val);
int (*reg_write)(void *ctx, u32 reg, u32 val);
};
void rtl83xx_lock(void *ctx);
void rtl83xx_unlock(void *ctx);
int rtl83xx_setup_user_mdio(struct dsa_switch *ds);
struct realtek_priv *
rtl83xx_probe(struct device *dev,
const struct realtek_interface_info *interface_info);
int rtl83xx_register_switch(struct realtek_priv *priv);
void rtl83xx_unregister_switch(struct realtek_priv *priv);
void rtl83xx_shutdown(struct realtek_priv *priv);
void rtl83xx_remove(struct realtek_priv *priv);
void rtl83xx_reset_assert(struct realtek_priv *priv);
void rtl83xx_reset_deassert(struct realtek_priv *priv);
int rtl83xx_port_bridge_join(struct dsa_switch *ds, int port,
struct dsa_bridge bridge,
bool *tx_forward_offload,
struct netlink_ext_ack *extack);
void rtl83xx_port_bridge_leave(struct dsa_switch *ds, int port,
struct dsa_bridge bridge);
int rtl83xx_port_bridge_flags(struct dsa_switch *ds, int port,
struct switchdev_brport_flags flags,
struct netlink_ext_ack *extack);
int rtl83xx_setup_port_flood_control(struct realtek_priv *priv, int port);
void rtl83xx_port_fast_age(struct dsa_switch *ds, int port);
int rtl83xx_port_fdb_add(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid,
struct dsa_db db);
int rtl83xx_port_fdb_del(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid,
struct dsa_db db);
int rtl83xx_port_fdb_dump(struct dsa_switch *ds, int port,
dsa_fdb_dump_cb_t *cb, void *data);
int rtl83xx_port_mdb_add(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_mdb *mdb,
struct dsa_db db);
int rtl83xx_port_mdb_del(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_mdb *mdb,
struct dsa_db db);
#endif /* _RTL83XX_H */