$(eval $(call KernelPackage,dsa-qca8k))
+
+define KernelPackage/dsa-realtek
+ SUBMENU:=$(NETWORK_DEVICES_MENU)
+ TITLE:=Realtek common module RTL83xx DSA switch family
+ DEPENDS:=+kmod-dsa +kmod-phy-realtek +kmod-regmap-core @!TARGET_x86 @!TARGET_bcm47xx @!TARGET_uml
+ KCONFIG:= \
+ CONFIG_NET_DSA_REALTEK \
+ CONFIG_NET_DSA_REALTEK_MDIO=y \
+ CONFIG_NET_DSA_REALTEK_SMI=y
+ FILES:= $(LINUX_DIR)/drivers/net/dsa/realtek/realtek_dsa.ko
+endef
+
+define KernelPackage/dsa-realtek/description
+ Common kernel module for Realtek RTL83xx DSA switch family
+endef
+
+$(eval $(call KernelPackage,dsa-realtek))
+
+
+define KernelPackage/dsa-rtl8366rb
+ SUBMENU:=$(NETWORK_DEVICES_MENU)
+ TITLE:=Realtek RTL8365MB switch DSA support
+ DEPENDS:=+kmod-dsa-realtek @!TARGET_x86 @!TARGET_bcm47xx @!TARGET_uml
+ KCONFIG:= \
+ CONFIG_NET_DSA_REALTEK_RTL8366RB \
+ CONFIG_NET_DSA_TAG_RTL4_A
+ FILES:= \
+ $(LINUX_DIR)/drivers/net/dsa/realtek/rtl8366.ko \
+ $(LINUX_DIR)/net/dsa/tag_rtl4_a.ko
+ AUTOLOAD:=$(call AutoLoad,42,rtl8366,1)
+endef
+
+define KernelPackage/dsa-rtl8366rb/description
+ DSA based kernel modules for the Realtek RTL8366RB switch family
+endef
+
+$(eval $(call KernelPackage,dsa-rtl8366rb))
+
+
+define KernelPackage/dsa-rtl8365mb
+ SUBMENU:=$(NETWORK_DEVICES_MENU)
+ TITLE:=Realtek RTL8365MB switch DSA support
+ DEPENDS:=+kmod-dsa-realtek @!TARGET_x86 @!TARGET_bcm47xx @!TARGET_uml
+ KCONFIG:= \
+ CONFIG_NET_DSA_REALTEK_RTL8365MB \
+ CONFIG_NET_DSA_TAG_RTL8_4
+ FILES:= \
+ $(LINUX_DIR)/drivers/net/dsa/realtek/rtl8365mb.ko \
+ $(LINUX_DIR)/net/dsa/tag_rtl8_4.ko
+ AUTOLOAD:=$(call AutoLoad,42,rtl8365mb,1)
+endef
+
+define KernelPackage/dsa-rtl8365mb/description
+ DSA based kernel modules for the Realtek RTL8365MB switch family
+endef
+
+$(eval $(call KernelPackage,dsa-rtl8365mb))
+
+
define KernelPackage/swconfig
SUBMENU:=$(NETWORK_DEVICES_MENU)
TITLE:=switch configuration API
--- /dev/null
+From d48a5472b8f2b29800bb25913f9403765005f1bc Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= <u.kleine-koenig@pengutronix.de>
+Date: Mon, 18 Sep 2023 21:19:14 +0200
+Subject: [PATCH] net: dsa: realtek: Convert to platform remove callback
+ returning void
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The .remove() callback for a platform driver returns an int which makes
+many driver authors wrongly assume it's possible to do error handling by
+returning an error code. However the value returned is ignored (apart
+from emitting a warning) and this typically results in resource leaks.
+To improve here there is a quest to make the remove callback return
+void. In the first step of this quest all drivers are converted to
+.remove_new() which already returns void. Eventually after all drivers
+are converted, .remove_new() is renamed to .remove().
+
+Trivially convert this driver from always returning zero in the remove
+callback to the void returning variant.
+
+Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Reviewed-by: Alvin Šipraga <alsi@bang-olufsen.dk>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+---
+ drivers/net/dsa/realtek/realtek-smi.c | 8 +++-----
+ 1 file changed, 3 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/dsa/realtek/realtek-smi.c
++++ b/drivers/net/dsa/realtek/realtek-smi.c
+@@ -506,12 +506,12 @@ static int realtek_smi_probe(struct plat
+ return 0;
+ }
+
+-static int realtek_smi_remove(struct platform_device *pdev)
++static void realtek_smi_remove(struct platform_device *pdev)
+ {
+ struct realtek_priv *priv = platform_get_drvdata(pdev);
+
+ if (!priv)
+- return 0;
++ return;
+
+ dsa_unregister_switch(priv->ds);
+ if (priv->slave_mii_bus)
+@@ -520,8 +520,6 @@ static int realtek_smi_remove(struct pla
+ /* leave the device reset asserted */
+ if (priv->reset)
+ gpiod_set_value(priv->reset, 1);
+-
+- return 0;
+ }
+
+ static void realtek_smi_shutdown(struct platform_device *pdev)
+@@ -559,7 +557,7 @@ static struct platform_driver realtek_sm
+ .of_match_table = realtek_smi_of_match,
+ },
+ .probe = realtek_smi_probe,
+- .remove = realtek_smi_remove,
++ .remove_new = realtek_smi_remove,
+ .shutdown = realtek_smi_shutdown,
+ };
+ module_platform_driver(realtek_smi_driver);
--- /dev/null
+From 33f4336cbd32c21717b60d013693a0bd51a27db6 Mon Sep 17 00:00:00 2001
+From: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Date: Fri, 9 Feb 2024 02:03:37 -0300
+Subject: net: dsa: realtek: drop cleanup from realtek_ops
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+It was never used and never referenced.
+
+Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Reviewed-by: Alvin Šipraga <alsi@bang-olufsen.dk>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/realtek/realtek.h | 1 -
+ 1 file changed, 1 deletion(-)
+
+--- a/drivers/net/dsa/realtek/realtek.h
++++ b/drivers/net/dsa/realtek/realtek.h
+@@ -91,7 +91,6 @@ struct realtek_ops {
+ int (*detect)(struct realtek_priv *priv);
+ int (*reset_chip)(struct realtek_priv *priv);
+ int (*setup)(struct realtek_priv *priv);
+- void (*cleanup)(struct realtek_priv *priv);
+ int (*get_mib_counter)(struct realtek_priv *priv,
+ int port,
+ struct rtl8366_mib_counter *mib,
--- /dev/null
+From ded3813b44fe11a3bbd2c9d7df8870e8c19a7ccd Mon Sep 17 00:00:00 2001
+From: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Date: Fri, 9 Feb 2024 02:03:38 -0300
+Subject: net: dsa: realtek: introduce REALTEK_DSA namespace
+
+Create a namespace to group the exported symbols.
+
+Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/realtek/realtek-mdio.c | 1 +
+ drivers/net/dsa/realtek/realtek-smi.c | 1 +
+ drivers/net/dsa/realtek/rtl8365mb.c | 1 +
+ drivers/net/dsa/realtek/rtl8366-core.c | 22 +++++++++++-----------
+ drivers/net/dsa/realtek/rtl8366rb.c | 1 +
+ 5 files changed, 15 insertions(+), 11 deletions(-)
+
+--- a/drivers/net/dsa/realtek/realtek-mdio.c
++++ b/drivers/net/dsa/realtek/realtek-mdio.c
+@@ -288,3 +288,4 @@ mdio_module_driver(realtek_mdio_driver);
+ MODULE_AUTHOR("Luiz Angelo Daros de Luca <luizluca@gmail.com>");
+ MODULE_DESCRIPTION("Driver for Realtek ethernet switch connected via MDIO interface");
+ MODULE_LICENSE("GPL");
++MODULE_IMPORT_NS(REALTEK_DSA);
+--- a/drivers/net/dsa/realtek/realtek-smi.c
++++ b/drivers/net/dsa/realtek/realtek-smi.c
+@@ -565,3 +565,4 @@ module_platform_driver(realtek_smi_drive
+ MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
+ MODULE_DESCRIPTION("Driver for Realtek ethernet switch connected via SMI interface");
+ MODULE_LICENSE("GPL");
++MODULE_IMPORT_NS(REALTEK_DSA);
+--- a/drivers/net/dsa/realtek/rtl8365mb.c
++++ b/drivers/net/dsa/realtek/rtl8365mb.c
+@@ -2178,3 +2178,4 @@ EXPORT_SYMBOL_GPL(rtl8365mb_variant);
+ MODULE_AUTHOR("Alvin Šipraga <alsi@bang-olufsen.dk>");
+ MODULE_DESCRIPTION("Driver for RTL8365MB-VC ethernet switch");
+ MODULE_LICENSE("GPL");
++MODULE_IMPORT_NS(REALTEK_DSA);
+--- a/drivers/net/dsa/realtek/rtl8366-core.c
++++ b/drivers/net/dsa/realtek/rtl8366-core.c
+@@ -34,7 +34,7 @@ int rtl8366_mc_is_used(struct realtek_pr
+
+ return 0;
+ }
+-EXPORT_SYMBOL_GPL(rtl8366_mc_is_used);
++EXPORT_SYMBOL_NS_GPL(rtl8366_mc_is_used, REALTEK_DSA);
+
+ /**
+ * rtl8366_obtain_mc() - retrieve or allocate a VLAN member configuration
+@@ -187,7 +187,7 @@ int rtl8366_set_vlan(struct realtek_priv
+
+ return ret;
+ }
+-EXPORT_SYMBOL_GPL(rtl8366_set_vlan);
++EXPORT_SYMBOL_NS_GPL(rtl8366_set_vlan, REALTEK_DSA);
+
+ int rtl8366_set_pvid(struct realtek_priv *priv, unsigned int port,
+ unsigned int vid)
+@@ -217,7 +217,7 @@ int rtl8366_set_pvid(struct realtek_priv
+
+ return 0;
+ }
+-EXPORT_SYMBOL_GPL(rtl8366_set_pvid);
++EXPORT_SYMBOL_NS_GPL(rtl8366_set_pvid, REALTEK_DSA);
+
+ int rtl8366_enable_vlan4k(struct realtek_priv *priv, bool enable)
+ {
+@@ -243,7 +243,7 @@ int rtl8366_enable_vlan4k(struct realtek
+ priv->vlan4k_enabled = enable;
+ return 0;
+ }
+-EXPORT_SYMBOL_GPL(rtl8366_enable_vlan4k);
++EXPORT_SYMBOL_NS_GPL(rtl8366_enable_vlan4k, REALTEK_DSA);
+
+ int rtl8366_enable_vlan(struct realtek_priv *priv, bool enable)
+ {
+@@ -265,7 +265,7 @@ int rtl8366_enable_vlan(struct realtek_p
+
+ return ret;
+ }
+-EXPORT_SYMBOL_GPL(rtl8366_enable_vlan);
++EXPORT_SYMBOL_NS_GPL(rtl8366_enable_vlan, REALTEK_DSA);
+
+ int rtl8366_reset_vlan(struct realtek_priv *priv)
+ {
+@@ -290,7 +290,7 @@ int rtl8366_reset_vlan(struct realtek_pr
+
+ return 0;
+ }
+-EXPORT_SYMBOL_GPL(rtl8366_reset_vlan);
++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,
+@@ -345,7 +345,7 @@ int rtl8366_vlan_add(struct dsa_switch *
+
+ return 0;
+ }
+-EXPORT_SYMBOL_GPL(rtl8366_vlan_add);
++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)
+@@ -389,7 +389,7 @@ int rtl8366_vlan_del(struct dsa_switch *
+
+ return 0;
+ }
+-EXPORT_SYMBOL_GPL(rtl8366_vlan_del);
++EXPORT_SYMBOL_NS_GPL(rtl8366_vlan_del, REALTEK_DSA);
+
+ void rtl8366_get_strings(struct dsa_switch *ds, int port, u32 stringset,
+ uint8_t *data)
+@@ -407,7 +407,7 @@ void rtl8366_get_strings(struct dsa_swit
+ mib->name, ETH_GSTRING_LEN);
+ }
+ }
+-EXPORT_SYMBOL_GPL(rtl8366_get_strings);
++EXPORT_SYMBOL_NS_GPL(rtl8366_get_strings, REALTEK_DSA);
+
+ int rtl8366_get_sset_count(struct dsa_switch *ds, int port, int sset)
+ {
+@@ -421,7 +421,7 @@ int rtl8366_get_sset_count(struct dsa_sw
+
+ return priv->num_mib_counters;
+ }
+-EXPORT_SYMBOL_GPL(rtl8366_get_sset_count);
++EXPORT_SYMBOL_NS_GPL(rtl8366_get_sset_count, REALTEK_DSA);
+
+ void rtl8366_get_ethtool_stats(struct dsa_switch *ds, int port, uint64_t *data)
+ {
+@@ -445,4 +445,4 @@ void rtl8366_get_ethtool_stats(struct ds
+ data[i] = mibvalue;
+ }
+ }
+-EXPORT_SYMBOL_GPL(rtl8366_get_ethtool_stats);
++EXPORT_SYMBOL_NS_GPL(rtl8366_get_ethtool_stats, REALTEK_DSA);
+--- a/drivers/net/dsa/realtek/rtl8366rb.c
++++ b/drivers/net/dsa/realtek/rtl8366rb.c
+@@ -1852,3 +1852,4 @@ EXPORT_SYMBOL_GPL(rtl8366rb_variant);
+ MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
+ MODULE_DESCRIPTION("Driver for RTL8366RB ethernet switch");
+ MODULE_LICENSE("GPL");
++MODULE_IMPORT_NS(REALTEK_DSA);
--- /dev/null
+From bce254b839abe67577bebdef0838796af409c229 Mon Sep 17 00:00:00 2001
+From: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Date: Fri, 9 Feb 2024 02:03:39 -0300
+Subject: net: dsa: realtek: convert variants into real drivers
+
+Previously, the interface modules realtek-smi and realtek-mdio served as
+a platform and an MDIO driver, respectively. Each interface module
+redundantly specified the same compatible strings for both variants and
+referenced symbols from the variants.
+
+Now, each variant module has been transformed into a unified driver
+serving both as a platform and an MDIO driver. This modification
+reverses the relationship between the interface and variant modules,
+with the variant module now utilizing symbols from the interface
+modules.
+
+Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
+Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/realtek/Kconfig | 20 +++----
+ drivers/net/dsa/realtek/realtek-mdio.c | 68 +++++++++++++++---------
+ drivers/net/dsa/realtek/realtek-mdio.h | 48 +++++++++++++++++
+ drivers/net/dsa/realtek/realtek-smi.c | 73 +++++++++++++++-----------
+ drivers/net/dsa/realtek/realtek-smi.h | 48 +++++++++++++++++
+ drivers/net/dsa/realtek/rtl8365mb.c | 54 ++++++++++++++++++-
+ drivers/net/dsa/realtek/rtl8366rb.c | 54 ++++++++++++++++++-
+ 7 files changed, 292 insertions(+), 73 deletions(-)
+ create mode 100644 drivers/net/dsa/realtek/realtek-mdio.h
+ create mode 100644 drivers/net/dsa/realtek/realtek-smi.h
+
+--- a/drivers/net/dsa/realtek/Kconfig
++++ b/drivers/net/dsa/realtek/Kconfig
+@@ -16,37 +16,29 @@ menuconfig NET_DSA_REALTEK
+ if NET_DSA_REALTEK
+
+ config NET_DSA_REALTEK_MDIO
+- tristate "Realtek MDIO interface driver"
++ tristate "Realtek MDIO interface support"
+ depends on OF
+- depends on NET_DSA_REALTEK_RTL8365MB || NET_DSA_REALTEK_RTL8366RB
+- depends on NET_DSA_REALTEK_RTL8365MB || !NET_DSA_REALTEK_RTL8365MB
+- depends on NET_DSA_REALTEK_RTL8366RB || !NET_DSA_REALTEK_RTL8366RB
+ help
+ Select to enable support for registering switches configured
+ through MDIO.
+
+ config NET_DSA_REALTEK_SMI
+- tristate "Realtek SMI interface driver"
++ tristate "Realtek SMI interface support"
+ depends on OF
+- depends on NET_DSA_REALTEK_RTL8365MB || NET_DSA_REALTEK_RTL8366RB
+- depends on NET_DSA_REALTEK_RTL8365MB || !NET_DSA_REALTEK_RTL8365MB
+- depends on NET_DSA_REALTEK_RTL8366RB || !NET_DSA_REALTEK_RTL8366RB
+ help
+ Select to enable support for registering switches connected
+ through SMI.
+
+ config NET_DSA_REALTEK_RTL8365MB
+- tristate "Realtek RTL8365MB switch subdriver"
+- imply NET_DSA_REALTEK_SMI
+- imply NET_DSA_REALTEK_MDIO
++ 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 subdriver"
+- imply NET_DSA_REALTEK_SMI
+- imply NET_DSA_REALTEK_MDIO
++ 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.
+--- a/drivers/net/dsa/realtek/realtek-mdio.c
++++ b/drivers/net/dsa/realtek/realtek-mdio.c
+@@ -25,6 +25,7 @@
+ #include <linux/regmap.h>
+
+ #include "realtek.h"
++#include "realtek-mdio.h"
+
+ /* Read/write via mdiobus */
+ #define REALTEK_MDIO_CTRL0_REG 31
+@@ -140,7 +141,19 @@ static const struct regmap_config realte
+ .disable_locking = true,
+ };
+
+-static int realtek_mdio_probe(struct mdio_device *mdiodev)
++/**
++ * 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. It
++ * initializes realtek_priv and read data from the device-tree node. The switch
++ * is hard reset if a method is provided. It checks the switch chip ID and,
++ * finally, a DSA switch is registered.
++ *
++ * 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 realtek_priv *priv;
+ struct device *dev = &mdiodev->dev;
+@@ -235,8 +248,20 @@ static int realtek_mdio_probe(struct mdi
+
+ return 0;
+ }
++EXPORT_SYMBOL_NS_GPL(realtek_mdio_probe, REALTEK_DSA);
+
+-static void realtek_mdio_remove(struct mdio_device *mdiodev)
++/**
++ * 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_new in an mdio_driver. First
++ * it unregisters the DSA switch and cleans internal data. If a method is
++ * provided, the hard reset is asserted to avoid traffic leakage.
++ *
++ * Context: Can sleep.
++ * Return: Nothing.
++ */
++void realtek_mdio_remove(struct mdio_device *mdiodev)
+ {
+ struct realtek_priv *priv = dev_get_drvdata(&mdiodev->dev);
+
+@@ -249,8 +274,21 @@ static void realtek_mdio_remove(struct m
+ if (priv->reset)
+ gpiod_set_value(priv->reset, 1);
+ }
++EXPORT_SYMBOL_NS_GPL(realtek_mdio_remove, REALTEK_DSA);
+
+-static void realtek_mdio_shutdown(struct mdio_device *mdiodev)
++/**
++ * 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 an mdio_driver. It shuts
++ * down the DSA switch and cleans the platform driver data, to prevent
++ * realtek_mdio_remove() from running afterwards, which is possible if the
++ * parent bus implements its own .shutdown() as .remove().
++ *
++ * Context: Can sleep.
++ * Return: Nothing.
++ */
++void realtek_mdio_shutdown(struct mdio_device *mdiodev)
+ {
+ struct realtek_priv *priv = dev_get_drvdata(&mdiodev->dev);
+
+@@ -261,29 +299,7 @@ static void realtek_mdio_shutdown(struct
+
+ dev_set_drvdata(&mdiodev->dev, NULL);
+ }
+-
+-static const struct of_device_id realtek_mdio_of_match[] = {
+-#if IS_ENABLED(CONFIG_NET_DSA_REALTEK_RTL8366RB)
+- { .compatible = "realtek,rtl8366rb", .data = &rtl8366rb_variant, },
+-#endif
+-#if IS_ENABLED(CONFIG_NET_DSA_REALTEK_RTL8365MB)
+- { .compatible = "realtek,rtl8365mb", .data = &rtl8365mb_variant, },
+-#endif
+- { /* sentinel */ },
+-};
+-MODULE_DEVICE_TABLE(of, realtek_mdio_of_match);
+-
+-static struct mdio_driver realtek_mdio_driver = {
+- .mdiodrv.driver = {
+- .name = "realtek-mdio",
+- .of_match_table = realtek_mdio_of_match,
+- },
+- .probe = realtek_mdio_probe,
+- .remove = realtek_mdio_remove,
+- .shutdown = realtek_mdio_shutdown,
+-};
+-
+-mdio_module_driver(realtek_mdio_driver);
++EXPORT_SYMBOL_NS_GPL(realtek_mdio_shutdown, REALTEK_DSA);
+
+ MODULE_AUTHOR("Luiz Angelo Daros de Luca <luizluca@gmail.com>");
+ MODULE_DESCRIPTION("Driver for Realtek ethernet switch connected via MDIO interface");
+--- /dev/null
++++ b/drivers/net/dsa/realtek/realtek-mdio.h
+@@ -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 */
+--- a/drivers/net/dsa/realtek/realtek-smi.c
++++ b/drivers/net/dsa/realtek/realtek-smi.c
+@@ -40,6 +40,7 @@
+ #include <linux/if_bridge.h>
+
+ #include "realtek.h"
++#include "realtek-smi.h"
+
+ #define REALTEK_SMI_ACK_RETRY_COUNT 5
+
+@@ -408,7 +409,19 @@ err_put_node:
+ return ret;
+ }
+
+-static int realtek_smi_probe(struct platform_device *pdev)
++/**
++ * 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. It
++ * initializes realtek_priv and read data from the device-tree node. The switch
++ * is hard reset if a method is provided. It checks the switch chip ID and,
++ * finally, a DSA switch is registered.
++ *
++ * 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)
+ {
+ const struct realtek_variant *var;
+ struct device *dev = &pdev->dev;
+@@ -505,8 +518,20 @@ static int realtek_smi_probe(struct plat
+ }
+ return 0;
+ }
++EXPORT_SYMBOL_NS_GPL(realtek_smi_probe, REALTEK_DSA);
+
+-static void realtek_smi_remove(struct platform_device *pdev)
++/**
++ * 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_new in a platform_driver. First
++ * it unregisters the DSA switch and cleans internal data. If a method is
++ * provided, the hard reset is asserted to avoid traffic leakage.
++ *
++ * Context: Can sleep.
++ * Return: Nothing.
++ */
++void realtek_smi_remove(struct platform_device *pdev)
+ {
+ struct realtek_priv *priv = platform_get_drvdata(pdev);
+
+@@ -521,8 +546,21 @@ static void realtek_smi_remove(struct pl
+ if (priv->reset)
+ gpiod_set_value(priv->reset, 1);
+ }
++EXPORT_SYMBOL_NS_GPL(realtek_smi_remove, REALTEK_DSA);
+
+-static void realtek_smi_shutdown(struct platform_device *pdev)
++/**
++ * 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 shuts
++ * down the DSA switch and cleans the platform driver data, to prevent
++ * realtek_smi_remove() from running afterwards, which is possible if the
++ * parent bus implements its own .shutdown() as .remove().
++ *
++ * Context: Can sleep.
++ * Return: Nothing.
++ */
++void realtek_smi_shutdown(struct platform_device *pdev)
+ {
+ struct realtek_priv *priv = platform_get_drvdata(pdev);
+
+@@ -533,34 +571,7 @@ static void realtek_smi_shutdown(struct
+
+ platform_set_drvdata(pdev, NULL);
+ }
+-
+-static const struct of_device_id realtek_smi_of_match[] = {
+-#if IS_ENABLED(CONFIG_NET_DSA_REALTEK_RTL8366RB)
+- {
+- .compatible = "realtek,rtl8366rb",
+- .data = &rtl8366rb_variant,
+- },
+-#endif
+-#if IS_ENABLED(CONFIG_NET_DSA_REALTEK_RTL8365MB)
+- {
+- .compatible = "realtek,rtl8365mb",
+- .data = &rtl8365mb_variant,
+- },
+-#endif
+- { /* sentinel */ },
+-};
+-MODULE_DEVICE_TABLE(of, realtek_smi_of_match);
+-
+-static struct platform_driver realtek_smi_driver = {
+- .driver = {
+- .name = "realtek-smi",
+- .of_match_table = realtek_smi_of_match,
+- },
+- .probe = realtek_smi_probe,
+- .remove_new = realtek_smi_remove,
+- .shutdown = realtek_smi_shutdown,
+-};
+-module_platform_driver(realtek_smi_driver);
++EXPORT_SYMBOL_NS_GPL(realtek_smi_shutdown, REALTEK_DSA);
+
+ MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
+ MODULE_DESCRIPTION("Driver for Realtek ethernet switch connected via SMI interface");
+--- /dev/null
++++ b/drivers/net/dsa/realtek/realtek-smi.h
+@@ -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 */
+--- a/drivers/net/dsa/realtek/rtl8365mb.c
++++ b/drivers/net/dsa/realtek/rtl8365mb.c
+@@ -101,6 +101,8 @@
+ #include <linux/if_vlan.h>
+
+ #include "realtek.h"
++#include "realtek-smi.h"
++#include "realtek-mdio.h"
+
+ /* Family-specific data and limits */
+ #define RTL8365MB_PHYADDRMAX 7
+@@ -2173,7 +2175,57 @@ const struct realtek_variant rtl8365mb_v
+ .cmd_write = 0xb8,
+ .chip_data_sz = sizeof(struct rtl8365mb),
+ };
+-EXPORT_SYMBOL_GPL(rtl8365mb_variant);
++
++static const struct of_device_id rtl8365mb_of_match[] = {
++ { .compatible = "realtek,rtl8365mb", .data = &rtl8365mb_variant, },
++ { /* sentinel */ },
++};
++MODULE_DEVICE_TABLE(of, rtl8365mb_of_match);
++
++static struct platform_driver rtl8365mb_smi_driver = {
++ .driver = {
++ .name = "rtl8365mb-smi",
++ .of_match_table = rtl8365mb_of_match,
++ },
++ .probe = realtek_smi_probe,
++ .remove_new = realtek_smi_remove,
++ .shutdown = realtek_smi_shutdown,
++};
++
++static struct mdio_driver rtl8365mb_mdio_driver = {
++ .mdiodrv.driver = {
++ .name = "rtl8365mb-mdio",
++ .of_match_table = rtl8365mb_of_match,
++ },
++ .probe = realtek_mdio_probe,
++ .remove = realtek_mdio_remove,
++ .shutdown = realtek_mdio_shutdown,
++};
++
++static int rtl8365mb_init(void)
++{
++ int ret;
++
++ ret = realtek_mdio_driver_register(&rtl8365mb_mdio_driver);
++ if (ret)
++ return ret;
++
++ ret = realtek_smi_driver_register(&rtl8365mb_smi_driver);
++ if (ret) {
++ realtek_mdio_driver_unregister(&rtl8365mb_mdio_driver);
++ return ret;
++ }
++
++ return 0;
++}
++module_init(rtl8365mb_init);
++
++static void __exit rtl8365mb_exit(void)
++{
++ realtek_smi_driver_unregister(&rtl8365mb_smi_driver);
++ realtek_mdio_driver_unregister(&rtl8365mb_mdio_driver);
++}
++module_exit(rtl8365mb_exit);
+
+ MODULE_AUTHOR("Alvin Šipraga <alsi@bang-olufsen.dk>");
+ MODULE_DESCRIPTION("Driver for RTL8365MB-VC ethernet switch");
+--- a/drivers/net/dsa/realtek/rtl8366rb.c
++++ b/drivers/net/dsa/realtek/rtl8366rb.c
+@@ -22,6 +22,8 @@
+ #include <linux/regmap.h>
+
+ #include "realtek.h"
++#include "realtek-smi.h"
++#include "realtek-mdio.h"
+
+ #define RTL8366RB_PORT_NUM_CPU 5
+ #define RTL8366RB_NUM_PORTS 6
+@@ -1847,7 +1849,57 @@ const struct realtek_variant rtl8366rb_v
+ .cmd_write = 0xa8,
+ .chip_data_sz = sizeof(struct rtl8366rb),
+ };
+-EXPORT_SYMBOL_GPL(rtl8366rb_variant);
++
++static const struct of_device_id rtl8366rb_of_match[] = {
++ { .compatible = "realtek,rtl8366rb", .data = &rtl8366rb_variant, },
++ { /* sentinel */ },
++};
++MODULE_DEVICE_TABLE(of, rtl8366rb_of_match);
++
++static struct platform_driver rtl8366rb_smi_driver = {
++ .driver = {
++ .name = "rtl8366rb-smi",
++ .of_match_table = rtl8366rb_of_match,
++ },
++ .probe = realtek_smi_probe,
++ .remove_new = realtek_smi_remove,
++ .shutdown = realtek_smi_shutdown,
++};
++
++static struct mdio_driver rtl8366rb_mdio_driver = {
++ .mdiodrv.driver = {
++ .name = "rtl8366rb-mdio",
++ .of_match_table = rtl8366rb_of_match,
++ },
++ .probe = realtek_mdio_probe,
++ .remove = realtek_mdio_remove,
++ .shutdown = realtek_mdio_shutdown,
++};
++
++static int rtl8366rb_init(void)
++{
++ int ret;
++
++ ret = realtek_mdio_driver_register(&rtl8366rb_mdio_driver);
++ if (ret)
++ return ret;
++
++ ret = realtek_smi_driver_register(&rtl8366rb_smi_driver);
++ if (ret) {
++ realtek_mdio_driver_unregister(&rtl8366rb_mdio_driver);
++ return ret;
++ }
++
++ return 0;
++}
++module_init(rtl8366rb_init);
++
++static void __exit rtl8366rb_exit(void)
++{
++ realtek_smi_driver_unregister(&rtl8366rb_smi_driver);
++ realtek_mdio_driver_unregister(&rtl8366rb_mdio_driver);
++}
++module_exit(rtl8366rb_exit);
+
+ MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
+ MODULE_DESCRIPTION("Driver for RTL8366RB ethernet switch");
--- /dev/null
+From 4667a1db2f550d23e01ba655fce331196ead6e92 Mon Sep 17 00:00:00 2001
+From: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Date: Fri, 9 Feb 2024 02:03:40 -0300
+Subject: net: dsa: realtek: keep variant reference in
+ realtek_priv
+
+Instead of copying values from the variant, we can keep a reference in
+realtek_priv.
+
+This is a preliminary change for sharing code betwen interfaces. It will
+allow to move most of the probe into a common module while still allow
+code specific to each interface to read variant fields.
+
+Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/realtek/realtek-mdio.c | 4 +---
+ drivers/net/dsa/realtek/realtek-smi.c | 10 ++++------
+ drivers/net/dsa/realtek/realtek.h | 5 ++---
+ 3 files changed, 7 insertions(+), 12 deletions(-)
+
+--- a/drivers/net/dsa/realtek/realtek-mdio.c
++++ b/drivers/net/dsa/realtek/realtek-mdio.c
+@@ -196,9 +196,7 @@ int realtek_mdio_probe(struct mdio_devic
+ priv->dev = &mdiodev->dev;
+ priv->chip_data = (void *)priv + sizeof(*priv);
+
+- priv->clk_delay = var->clk_delay;
+- priv->cmd_read = var->cmd_read;
+- priv->cmd_write = var->cmd_write;
++ priv->variant = var;
+ priv->ops = var->ops;
+
+ priv->write_reg_noack = realtek_mdio_write;
+--- a/drivers/net/dsa/realtek/realtek-smi.c
++++ b/drivers/net/dsa/realtek/realtek-smi.c
+@@ -46,7 +46,7 @@
+
+ static inline void realtek_smi_clk_delay(struct realtek_priv *priv)
+ {
+- ndelay(priv->clk_delay);
++ ndelay(priv->variant->clk_delay);
+ }
+
+ static void realtek_smi_start(struct realtek_priv *priv)
+@@ -209,7 +209,7 @@ static int realtek_smi_read_reg(struct r
+ realtek_smi_start(priv);
+
+ /* Send READ command */
+- ret = realtek_smi_write_byte(priv, priv->cmd_read);
++ ret = realtek_smi_write_byte(priv, priv->variant->cmd_read);
+ if (ret)
+ goto out;
+
+@@ -250,7 +250,7 @@ static int realtek_smi_write_reg(struct
+ realtek_smi_start(priv);
+
+ /* Send WRITE command */
+- ret = realtek_smi_write_byte(priv, priv->cmd_write);
++ ret = realtek_smi_write_byte(priv, priv->variant->cmd_write);
+ if (ret)
+ goto out;
+
+@@ -459,9 +459,7 @@ int realtek_smi_probe(struct platform_de
+
+ /* Link forward and backward */
+ priv->dev = dev;
+- priv->clk_delay = var->clk_delay;
+- priv->cmd_read = var->cmd_read;
+- priv->cmd_write = var->cmd_write;
++ priv->variant = var;
+ priv->ops = var->ops;
+
+ priv->setup_interface = realtek_smi_setup_mdio;
+--- a/drivers/net/dsa/realtek/realtek.h
++++ b/drivers/net/dsa/realtek/realtek.h
+@@ -58,9 +58,8 @@ struct realtek_priv {
+ struct mii_bus *bus;
+ int mdio_addr;
+
+- unsigned int clk_delay;
+- u8 cmd_read;
+- u8 cmd_write;
++ const struct realtek_variant *variant;
++
+ spinlock_t lock; /* Locks around command writes */
+ struct dsa_switch *ds;
+ struct irq_domain *irqdomain;
--- /dev/null
+From 8be040ecd94c1a9a137927d18534edfae0a9b68a Mon Sep 17 00:00:00 2001
+From: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Date: Fri, 9 Feb 2024 02:03:41 -0300
+Subject: net: dsa: realtek: common rtl83xx module
+
+Some code can be shared between both interface modules (MDIO and SMI)
+and among variants. These interface functions migrated to a common
+module:
+
+- rtl83xx_lock
+- rtl83xx_unlock
+- rtl83xx_probe
+- rtl83xx_register_switch
+- rtl83xx_unregister_switch
+- rtl83xx_shutdown
+- rtl83xx_remove
+
+The reset during probe was moved to the end of the common probe. This way,
+we avoid a reset if anything else fails.
+
+Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/realtek/Makefile | 2 +
+ drivers/net/dsa/realtek/realtek-mdio.c | 152 +++-------------
+ drivers/net/dsa/realtek/realtek-smi.c | 166 ++++-------------
+ drivers/net/dsa/realtek/realtek.h | 1 +
+ drivers/net/dsa/realtek/rtl8365mb.c | 9 +-
+ drivers/net/dsa/realtek/rtl8366rb.c | 9 +-
+ drivers/net/dsa/realtek/rtl83xx.c | 235 +++++++++++++++++++++++++
+ drivers/net/dsa/realtek/rtl83xx.h | 21 +++
+ 8 files changed, 322 insertions(+), 273 deletions(-)
+ create mode 100644 drivers/net/dsa/realtek/rtl83xx.c
+ create mode 100644 drivers/net/dsa/realtek/rtl83xx.h
+
+--- a/drivers/net/dsa/realtek/Makefile
++++ b/drivers/net/dsa/realtek/Makefile
+@@ -1,4 +1,6 @@
+ # SPDX-License-Identifier: GPL-2.0
++obj-$(CONFIG_NET_DSA_REALTEK) += realtek_dsa.o
++realtek_dsa-objs := rtl83xx.o
+ obj-$(CONFIG_NET_DSA_REALTEK_MDIO) += realtek-mdio.o
+ obj-$(CONFIG_NET_DSA_REALTEK_SMI) += realtek-smi.o
+ obj-$(CONFIG_NET_DSA_REALTEK_RTL8366RB) += rtl8366.o
+--- a/drivers/net/dsa/realtek/realtek-mdio.c
++++ b/drivers/net/dsa/realtek/realtek-mdio.c
+@@ -26,6 +26,7 @@
+
+ #include "realtek.h"
+ #include "realtek-mdio.h"
++#include "rtl83xx.h"
+
+ /* Read/write via mdiobus */
+ #define REALTEK_MDIO_CTRL0_REG 31
+@@ -100,147 +101,41 @@ out_unlock:
+ return ret;
+ }
+
+-static void realtek_mdio_lock(void *ctx)
+-{
+- struct realtek_priv *priv = ctx;
+-
+- mutex_lock(&priv->map_lock);
+-}
+-
+-static void realtek_mdio_unlock(void *ctx)
+-{
+- struct realtek_priv *priv = ctx;
+-
+- mutex_unlock(&priv->map_lock);
+-}
+-
+-static const struct regmap_config realtek_mdio_regmap_config = {
+- .reg_bits = 10, /* A4..A0 R4..R0 */
+- .val_bits = 16,
+- .reg_stride = 1,
+- /* PHY regs are at 0x8000 */
+- .max_register = 0xffff,
+- .reg_format_endian = REGMAP_ENDIAN_BIG,
++static const struct realtek_interface_info realtek_mdio_info = {
+ .reg_read = realtek_mdio_read,
+ .reg_write = realtek_mdio_write,
+- .cache_type = REGCACHE_NONE,
+- .lock = realtek_mdio_lock,
+- .unlock = realtek_mdio_unlock,
+-};
+-
+-static const struct regmap_config realtek_mdio_nolock_regmap_config = {
+- .reg_bits = 10, /* A4..A0 R4..R0 */
+- .val_bits = 16,
+- .reg_stride = 1,
+- /* PHY regs are at 0x8000 */
+- .max_register = 0xffff,
+- .reg_format_endian = REGMAP_ENDIAN_BIG,
+- .reg_read = realtek_mdio_read,
+- .reg_write = realtek_mdio_write,
+- .cache_type = REGCACHE_NONE,
+- .disable_locking = true,
+ };
+
+ /**
+ * 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. It
+- * initializes realtek_priv and read data from the device-tree node. The switch
+- * is hard reset if a method is provided. It checks the switch chip ID and,
+- * finally, a DSA switch is registered.
++ * 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 realtek_priv *priv;
+ struct device *dev = &mdiodev->dev;
+- const struct realtek_variant *var;
+- struct regmap_config rc;
+- struct device_node *np;
++ struct realtek_priv *priv;
+ int ret;
+
+- var = of_device_get_match_data(dev);
+- if (!var)
+- return -EINVAL;
+-
+- priv = devm_kzalloc(&mdiodev->dev,
+- size_add(sizeof(*priv), var->chip_data_sz),
+- GFP_KERNEL);
+- if (!priv)
+- return -ENOMEM;
+-
+- mutex_init(&priv->map_lock);
+-
+- rc = realtek_mdio_regmap_config;
+- 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 ret;
+- }
++ priv = rtl83xx_probe(dev, &realtek_mdio_info);
++ if (IS_ERR(priv))
++ return PTR_ERR(priv);
+
+- rc = realtek_mdio_nolock_regmap_config;
+- 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 ret;
+- }
+-
+- priv->mdio_addr = mdiodev->addr;
+ priv->bus = mdiodev->bus;
+- priv->dev = &mdiodev->dev;
+- priv->chip_data = (void *)priv + sizeof(*priv);
+-
+- priv->variant = var;
+- priv->ops = var->ops;
+-
++ priv->mdio_addr = mdiodev->addr;
+ priv->write_reg_noack = realtek_mdio_write;
++ priv->ds_ops = priv->variant->ds_ops_mdio;
+
+- np = dev->of_node;
+-
+- dev_set_drvdata(dev, priv);
+-
+- /* TODO: if power is software controlled, set up any regulators here */
+- priv->leds_disabled = of_property_read_bool(np, "realtek,disable-leds");
+-
+- 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 PTR_ERR(priv->reset);
+- }
+-
+- if (priv->reset) {
+- gpiod_set_value(priv->reset, 1);
+- dev_dbg(dev, "asserted RESET\n");
+- msleep(REALTEK_HW_STOP_DELAY);
+- gpiod_set_value(priv->reset, 0);
+- msleep(REALTEK_HW_START_DELAY);
+- dev_dbg(dev, "deasserted RESET\n");
+- }
+-
+- ret = priv->ops->detect(priv);
+- if (ret) {
+- dev_err(dev, "unable to detect switch\n");
+- return ret;
+- }
+-
+- priv->ds = devm_kzalloc(dev, sizeof(*priv->ds), GFP_KERNEL);
+- if (!priv->ds)
+- return -ENOMEM;
+-
+- priv->ds->dev = dev;
+- priv->ds->num_ports = priv->num_ports;
+- priv->ds->priv = priv;
+- priv->ds->ops = var->ds_ops_mdio;
+-
+- ret = dsa_register_switch(priv->ds);
++ ret = rtl83xx_register_switch(priv);
+ if (ret) {
+- dev_err(priv->dev, "unable to register switch ret = %d\n", ret);
++ rtl83xx_remove(priv);
+ return ret;
+ }
+
+@@ -253,8 +148,7 @@ EXPORT_SYMBOL_NS_GPL(realtek_mdio_probe,
+ * @mdiodev: mdio_device to be removed.
+ *
+ * This function should be used as the .remove_new in an mdio_driver. First
+- * it unregisters the DSA switch and cleans internal data. If a method is
+- * provided, the hard reset is asserted to avoid traffic leakage.
++ * it unregisters the DSA switch and then it calls the common remove function.
+ *
+ * Context: Can sleep.
+ * Return: Nothing.
+@@ -266,11 +160,9 @@ void realtek_mdio_remove(struct mdio_dev
+ if (!priv)
+ return;
+
+- dsa_unregister_switch(priv->ds);
++ rtl83xx_unregister_switch(priv);
+
+- /* leave the device reset asserted */
+- if (priv->reset)
+- gpiod_set_value(priv->reset, 1);
++ rtl83xx_remove(priv);
+ }
+ EXPORT_SYMBOL_NS_GPL(realtek_mdio_remove, REALTEK_DSA);
+
+@@ -278,10 +170,8 @@ EXPORT_SYMBOL_NS_GPL(realtek_mdio_remove
+ * 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 an mdio_driver. It shuts
+- * down the DSA switch and cleans the platform driver data, to prevent
+- * realtek_mdio_remove() from running afterwards, which is possible if the
+- * parent bus implements its own .shutdown() as .remove().
++ * This function should be used as the .shutdown in a platform_driver. It calls
++ * the common shutdown function.
+ *
+ * Context: Can sleep.
+ * Return: Nothing.
+@@ -293,9 +183,7 @@ void realtek_mdio_shutdown(struct mdio_d
+ if (!priv)
+ return;
+
+- dsa_switch_shutdown(priv->ds);
+-
+- dev_set_drvdata(&mdiodev->dev, NULL);
++ rtl83xx_shutdown(priv);
+ }
+ EXPORT_SYMBOL_NS_GPL(realtek_mdio_shutdown, REALTEK_DSA);
+
+--- a/drivers/net/dsa/realtek/realtek-smi.c
++++ b/drivers/net/dsa/realtek/realtek-smi.c
+@@ -41,6 +41,7 @@
+
+ #include "realtek.h"
+ #include "realtek-smi.h"
++#include "rtl83xx.h"
+
+ #define REALTEK_SMI_ACK_RETRY_COUNT 5
+
+@@ -311,47 +312,6 @@ static int realtek_smi_read(void *ctx, u
+ return realtek_smi_read_reg(priv, reg, val);
+ }
+
+-static void realtek_smi_lock(void *ctx)
+-{
+- struct realtek_priv *priv = ctx;
+-
+- mutex_lock(&priv->map_lock);
+-}
+-
+-static void realtek_smi_unlock(void *ctx)
+-{
+- struct realtek_priv *priv = ctx;
+-
+- mutex_unlock(&priv->map_lock);
+-}
+-
+-static const struct regmap_config realtek_smi_regmap_config = {
+- .reg_bits = 10, /* A4..A0 R4..R0 */
+- .val_bits = 16,
+- .reg_stride = 1,
+- /* PHY regs are at 0x8000 */
+- .max_register = 0xffff,
+- .reg_format_endian = REGMAP_ENDIAN_BIG,
+- .reg_read = realtek_smi_read,
+- .reg_write = realtek_smi_write,
+- .cache_type = REGCACHE_NONE,
+- .lock = realtek_smi_lock,
+- .unlock = realtek_smi_unlock,
+-};
+-
+-static const struct regmap_config realtek_smi_nolock_regmap_config = {
+- .reg_bits = 10, /* A4..A0 R4..R0 */
+- .val_bits = 16,
+- .reg_stride = 1,
+- /* PHY regs are at 0x8000 */
+- .max_register = 0xffff,
+- .reg_format_endian = REGMAP_ENDIAN_BIG,
+- .reg_read = realtek_smi_read,
+- .reg_write = realtek_smi_write,
+- .cache_type = REGCACHE_NONE,
+- .disable_locking = true,
+-};
+-
+ static int realtek_smi_mdio_read(struct mii_bus *bus, int addr, int regnum)
+ {
+ struct realtek_priv *priv = bus->priv;
+@@ -409,111 +369,56 @@ err_put_node:
+ return ret;
+ }
+
++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. It
+- * initializes realtek_priv and read data from the device-tree node. The switch
+- * is hard reset if a method is provided. It checks the switch chip ID and,
+- * finally, a DSA switch is registered.
++ * 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)
+ {
+- const struct realtek_variant *var;
+ struct device *dev = &pdev->dev;
+ struct realtek_priv *priv;
+- struct regmap_config rc;
+- struct device_node *np;
+ int ret;
+
+- var = of_device_get_match_data(dev);
+- np = dev->of_node;
+-
+- priv = devm_kzalloc(dev, sizeof(*priv) + var->chip_data_sz, GFP_KERNEL);
+- if (!priv)
+- return -ENOMEM;
+- priv->chip_data = (void *)priv + sizeof(*priv);
+-
+- mutex_init(&priv->map_lock);
+-
+- rc = realtek_smi_regmap_config;
+- 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 ret;
+- }
+-
+- rc = realtek_smi_nolock_regmap_config;
+- 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 ret;
+- }
+-
+- /* Link forward and backward */
+- priv->dev = dev;
+- priv->variant = var;
+- priv->ops = var->ops;
+-
+- priv->setup_interface = realtek_smi_setup_mdio;
+- priv->write_reg_noack = realtek_smi_write_reg_noack;
+-
+- dev_set_drvdata(dev, priv);
+- spin_lock_init(&priv->lock);
+-
+- /* TODO: if power is software controlled, set up any regulators here */
+-
+- 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 PTR_ERR(priv->reset);
+- }
+- if (priv->reset) {
+- gpiod_set_value(priv->reset, 1);
+- dev_dbg(dev, "asserted RESET\n");
+- msleep(REALTEK_HW_STOP_DELAY);
+- gpiod_set_value(priv->reset, 0);
+- msleep(REALTEK_HW_START_DELAY);
+- dev_dbg(dev, "deasserted RESET\n");
+- }
++ 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))
++ 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))
++ if (IS_ERR(priv->mdio)) {
++ rtl83xx_remove(priv);
+ return PTR_ERR(priv->mdio);
+-
+- priv->leds_disabled = of_property_read_bool(np, "realtek,disable-leds");
+-
+- ret = priv->ops->detect(priv);
+- if (ret) {
+- dev_err(dev, "unable to detect switch\n");
+- return ret;
+ }
+
+- priv->ds = devm_kzalloc(dev, sizeof(*priv->ds), GFP_KERNEL);
+- if (!priv->ds)
+- return -ENOMEM;
+-
+- priv->ds->dev = dev;
+- priv->ds->num_ports = priv->num_ports;
+- priv->ds->priv = priv;
++ priv->write_reg_noack = realtek_smi_write_reg_noack;
++ priv->setup_interface = realtek_smi_setup_mdio;
++ priv->ds_ops = priv->variant->ds_ops_smi;
+
+- priv->ds->ops = var->ds_ops_smi;
+- ret = dsa_register_switch(priv->ds);
++ ret = rtl83xx_register_switch(priv);
+ if (ret) {
+- dev_err_probe(dev, ret, "unable to register switch\n");
++ rtl83xx_remove(priv);
+ return ret;
+ }
++
+ return 0;
+ }
+ EXPORT_SYMBOL_NS_GPL(realtek_smi_probe, REALTEK_DSA);
+@@ -523,8 +428,8 @@ EXPORT_SYMBOL_NS_GPL(realtek_smi_probe,
+ * @pdev: platform_device to be removed.
+ *
+ * This function should be used as the .remove_new in a platform_driver. First
+- * it unregisters the DSA switch and cleans internal data. If a method is
+- * provided, the hard reset is asserted to avoid traffic leakage.
++ * it unregisters the DSA switch and cleans internal data. Finally, it calls
++ * the common remove function.
+ *
+ * Context: Can sleep.
+ * Return: Nothing.
+@@ -536,13 +441,12 @@ void realtek_smi_remove(struct platform_
+ if (!priv)
+ return;
+
+- dsa_unregister_switch(priv->ds);
++ rtl83xx_unregister_switch(priv);
++
+ if (priv->slave_mii_bus)
+ of_node_put(priv->slave_mii_bus->dev.of_node);
+
+- /* leave the device reset asserted */
+- if (priv->reset)
+- gpiod_set_value(priv->reset, 1);
++ rtl83xx_remove(priv);
+ }
+ EXPORT_SYMBOL_NS_GPL(realtek_smi_remove, REALTEK_DSA);
+
+@@ -550,10 +454,8 @@ EXPORT_SYMBOL_NS_GPL(realtek_smi_remove,
+ * 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 shuts
+- * down the DSA switch and cleans the platform driver data, to prevent
+- * realtek_smi_remove() from running afterwards, which is possible if the
+- * parent bus implements its own .shutdown() as .remove().
++ * This function should be used as the .shutdown in a platform_driver. It calls
++ * the common shutdown function.
+ *
+ * Context: Can sleep.
+ * Return: Nothing.
+@@ -565,9 +467,7 @@ void realtek_smi_shutdown(struct platfor
+ if (!priv)
+ return;
+
+- dsa_switch_shutdown(priv->ds);
+-
+- platform_set_drvdata(pdev, NULL);
++ rtl83xx_shutdown(priv);
+ }
+ EXPORT_SYMBOL_NS_GPL(realtek_smi_shutdown, REALTEK_DSA);
+
+--- a/drivers/net/dsa/realtek/realtek.h
++++ b/drivers/net/dsa/realtek/realtek.h
+@@ -62,6 +62,7 @@ struct realtek_priv {
+
+ spinlock_t lock; /* Locks around command writes */
+ struct dsa_switch *ds;
++ const struct dsa_switch_ops *ds_ops;
+ struct irq_domain *irqdomain;
+ bool leds_disabled;
+
+--- a/drivers/net/dsa/realtek/rtl8365mb.c
++++ b/drivers/net/dsa/realtek/rtl8365mb.c
+@@ -103,6 +103,7 @@
+ #include "realtek.h"
+ #include "realtek-smi.h"
+ #include "realtek-mdio.h"
++#include "rtl83xx.h"
+
+ /* Family-specific data and limits */
+ #define RTL8365MB_PHYADDRMAX 7
+@@ -691,7 +692,7 @@ static int rtl8365mb_phy_ocp_read(struct
+ u32 val;
+ int ret;
+
+- mutex_lock(&priv->map_lock);
++ rtl83xx_lock(priv);
+
+ ret = rtl8365mb_phy_poll_busy(priv);
+ if (ret)
+@@ -724,7 +725,7 @@ static int rtl8365mb_phy_ocp_read(struct
+ *data = val & 0xFFFF;
+
+ out:
+- mutex_unlock(&priv->map_lock);
++ rtl83xx_unlock(priv);
+
+ return ret;
+ }
+@@ -735,7 +736,7 @@ static int rtl8365mb_phy_ocp_write(struc
+ u32 val;
+ int ret;
+
+- mutex_lock(&priv->map_lock);
++ rtl83xx_lock(priv);
+
+ ret = rtl8365mb_phy_poll_busy(priv);
+ if (ret)
+@@ -766,7 +767,7 @@ static int rtl8365mb_phy_ocp_write(struc
+ goto out;
+
+ out:
+- mutex_unlock(&priv->map_lock);
++ rtl83xx_unlock(priv);
+
+ return 0;
+ }
+--- a/drivers/net/dsa/realtek/rtl8366rb.c
++++ b/drivers/net/dsa/realtek/rtl8366rb.c
+@@ -24,6 +24,7 @@
+ #include "realtek.h"
+ #include "realtek-smi.h"
+ #include "realtek-mdio.h"
++#include "rtl83xx.h"
+
+ #define RTL8366RB_PORT_NUM_CPU 5
+ #define RTL8366RB_NUM_PORTS 6
+@@ -1634,7 +1635,7 @@ static int rtl8366rb_phy_read(struct rea
+ if (phy > RTL8366RB_PHY_NO_MAX)
+ return -EINVAL;
+
+- mutex_lock(&priv->map_lock);
++ rtl83xx_lock(priv);
+
+ ret = regmap_write(priv->map_nolock, RTL8366RB_PHY_ACCESS_CTRL_REG,
+ RTL8366RB_PHY_CTRL_READ);
+@@ -1662,7 +1663,7 @@ static int rtl8366rb_phy_read(struct rea
+ phy, regnum, reg, val);
+
+ out:
+- mutex_unlock(&priv->map_lock);
++ rtl83xx_unlock(priv);
+
+ return ret;
+ }
+@@ -1676,7 +1677,7 @@ static int rtl8366rb_phy_write(struct re
+ if (phy > RTL8366RB_PHY_NO_MAX)
+ return -EINVAL;
+
+- mutex_lock(&priv->map_lock);
++ rtl83xx_lock(priv);
+
+ ret = regmap_write(priv->map_nolock, RTL8366RB_PHY_ACCESS_CTRL_REG,
+ RTL8366RB_PHY_CTRL_WRITE);
+@@ -1693,7 +1694,7 @@ static int rtl8366rb_phy_write(struct re
+ goto out;
+
+ out:
+- mutex_unlock(&priv->map_lock);
++ rtl83xx_unlock(priv);
+
+ return ret;
+ }
+--- /dev/null
++++ b/drivers/net/dsa/realtek/rtl83xx.c
+@@ -0,0 +1,236 @@
++// SPDX-License-Identifier: GPL-2.0+
++
++#include <linux/module.h>
++#include <linux/regmap.h>
++#include <linux/of_device.h> /* krnl 6.1 only */
++
++#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);
++
++/**
++ * 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);
++
++ mutex_init(&priv->map_lock);
++
++ 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 = 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) {
++ gpiod_set_value(priv->reset, 1);
++ dev_dbg(dev, "asserted RESET\n");
++ msleep(REALTEK_HW_STOP_DELAY);
++ gpiod_set_value(priv->reset, 0);
++ 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;
++ int ret;
++
++ ret = priv->ops->detect(priv);
++ if (ret) {
++ dev_err_probe(priv->dev, ret, "unable to detect switch\n");
++ return ret;
++ }
++
++ ds = devm_kzalloc(priv->dev, sizeof(*ds), GFP_KERNEL);
++ if (!ds)
++ return -ENOMEM;
++
++ ds->priv = priv;
++ ds->dev = priv->dev;
++ ds->ops = priv->ds_ops;
++ ds->num_ports = priv->num_ports;
++ priv->ds = ds;
++
++ 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)
++{
++ dsa_unregister_switch(priv->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)
++{
++ dsa_switch_shutdown(priv->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
++ *
++ * If a method is provided, this function asserts the hard reset of the switch
++ * in order to avoid leaking traffic when the driver is gone.
++ *
++ * Context: Might sleep if priv->gdev->chip->can_sleep.
++ * Return: nothing
++ */
++void rtl83xx_remove(struct realtek_priv *priv)
++{
++ /* leave the device reset asserted */
++ if (priv->reset)
++ gpiod_set_value(priv->reset, 1);
++}
++EXPORT_SYMBOL_NS_GPL(rtl83xx_remove, REALTEK_DSA);
++
++MODULE_AUTHOR("Luiz Angelo Daros de Luca <luizluca@gmail.com>");
++MODULE_DESCRIPTION("Realtek DSA switches common module");
++MODULE_LICENSE("GPL");
+--- /dev/null
++++ b/drivers/net/dsa/realtek/rtl83xx.h
+@@ -0,0 +1,21 @@
++/* 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);
++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);
++
++#endif /* _RTL83XX_H */
--- /dev/null
+From 98b75c1c149c653ad11a440636213eb070325158 Mon Sep 17 00:00:00 2001
+From: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Date: Fri, 9 Feb 2024 02:03:42 -0300
+Subject: net: dsa: realtek: merge rtl83xx and interface
+ modules into realtek_dsa
+
+Since rtl83xx and realtek-{smi,mdio} are always loaded together,
+we can optimize resource usage by consolidating them into a single
+module.
+
+Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/realtek/Kconfig | 4 ++--
+ drivers/net/dsa/realtek/Makefile | 11 +++++++++--
+ drivers/net/dsa/realtek/realtek-mdio.c | 5 -----
+ drivers/net/dsa/realtek/realtek-smi.c | 5 -----
+ drivers/net/dsa/realtek/rtl83xx.c | 1 +
+ 5 files changed, 12 insertions(+), 14 deletions(-)
+
+--- a/drivers/net/dsa/realtek/Kconfig
++++ b/drivers/net/dsa/realtek/Kconfig
+@@ -16,14 +16,14 @@ menuconfig NET_DSA_REALTEK
+ if NET_DSA_REALTEK
+
+ config NET_DSA_REALTEK_MDIO
+- tristate "Realtek MDIO interface support"
++ bool "Realtek MDIO interface support"
+ depends on OF
+ help
+ Select to enable support for registering switches configured
+ through MDIO.
+
+ config NET_DSA_REALTEK_SMI
+- tristate "Realtek SMI interface support"
++ bool "Realtek SMI interface support"
+ depends on OF
+ help
+ Select to enable support for registering switches connected
+--- a/drivers/net/dsa/realtek/Makefile
++++ b/drivers/net/dsa/realtek/Makefile
+@@ -1,8 +1,15 @@
+ # SPDX-License-Identifier: GPL-2.0
+ obj-$(CONFIG_NET_DSA_REALTEK) += realtek_dsa.o
+ realtek_dsa-objs := rtl83xx.o
+-obj-$(CONFIG_NET_DSA_REALTEK_MDIO) += realtek-mdio.o
+-obj-$(CONFIG_NET_DSA_REALTEK_SMI) += realtek-smi.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
+ obj-$(CONFIG_NET_DSA_REALTEK_RTL8365MB) += rtl8365mb.o
+--- a/drivers/net/dsa/realtek/realtek-mdio.c
++++ b/drivers/net/dsa/realtek/realtek-mdio.c
+@@ -186,8 +186,3 @@ void realtek_mdio_shutdown(struct mdio_d
+ rtl83xx_shutdown(priv);
+ }
+ EXPORT_SYMBOL_NS_GPL(realtek_mdio_shutdown, REALTEK_DSA);
+-
+-MODULE_AUTHOR("Luiz Angelo Daros de Luca <luizluca@gmail.com>");
+-MODULE_DESCRIPTION("Driver for Realtek ethernet switch connected via MDIO interface");
+-MODULE_LICENSE("GPL");
+-MODULE_IMPORT_NS(REALTEK_DSA);
+--- a/drivers/net/dsa/realtek/realtek-smi.c
++++ b/drivers/net/dsa/realtek/realtek-smi.c
+@@ -470,8 +470,3 @@ void realtek_smi_shutdown(struct platfor
+ rtl83xx_shutdown(priv);
+ }
+ EXPORT_SYMBOL_NS_GPL(realtek_smi_shutdown, REALTEK_DSA);
+-
+-MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
+-MODULE_DESCRIPTION("Driver for Realtek ethernet switch connected via SMI interface");
+-MODULE_LICENSE("GPL");
+-MODULE_IMPORT_NS(REALTEK_DSA);
+--- a/drivers/net/dsa/realtek/rtl83xx.c
++++ b/drivers/net/dsa/realtek/rtl83xx.c
+@@ -232,5 +232,6 @@ void rtl83xx_remove(struct realtek_priv
+ EXPORT_SYMBOL_NS_GPL(rtl83xx_remove, 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");
--- /dev/null
+From 8685c98d45c54346caf005de69988e13c731c533 Mon Sep 17 00:00:00 2001
+From: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Date: Fri, 9 Feb 2024 02:03:43 -0300
+Subject: net: dsa: realtek: get internal MDIO node by name
+
+The binding docs requires for SMI-connected devices that the switch
+must have a child node named "mdio" and with a compatible string of
+"realtek,smi-mdio". Meanwile, for MDIO-connected switches, the binding
+docs only requires a child node named "mdio".
+
+This patch changes the driver to use the common denominator for both
+interfaces, looking for the MDIO node by name, ignoring the compatible
+string.
+
+Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
+Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/realtek/realtek-smi.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/net/dsa/realtek/realtek-smi.c
++++ b/drivers/net/dsa/realtek/realtek-smi.c
+@@ -333,7 +333,7 @@ static int realtek_smi_setup_mdio(struct
+ struct device_node *mdio_np;
+ int ret;
+
+- mdio_np = of_get_compatible_child(priv->dev->of_node, "realtek,smi-mdio");
++ 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;
--- /dev/null
+From 68c66d8d8a19088967a0ab6bb98cb5ecc80ca0be Mon Sep 17 00:00:00 2001
+From: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Date: Fri, 9 Feb 2024 02:03:44 -0300
+Subject: net: dsa: realtek: clean slave_mii_bus setup
+
+Remove the line assigning dev.of_node in mdio_bus as subsequent
+of_mdiobus_register will always overwrite it.
+
+As discussed in [1], allow the DSA core to be simplified, by not
+assigning ds->slave_mii_bus when the MDIO bus is described in OF, as it
+is unnecessary.
+
+Since commit 3b73a7b8ec38 ("net: mdio_bus: add refcounting for fwnodes
+to mdiobus"), we can put the "mdio" node just after the MDIO bus
+registration.
+
+[1] https://lkml.kernel.org/netdev/20231213120656.x46fyad6ls7sqyzv@skbuf/T/#u
+
+Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
+Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/realtek/realtek-smi.c | 13 +++----------
+ 1 file changed, 3 insertions(+), 10 deletions(-)
+
+--- a/drivers/net/dsa/realtek/realtek-smi.c
++++ b/drivers/net/dsa/realtek/realtek-smi.c
+@@ -331,7 +331,7 @@ static int realtek_smi_setup_mdio(struct
+ {
+ struct realtek_priv *priv = ds->priv;
+ struct device_node *mdio_np;
+- int ret;
++ int ret = 0;
+
+ mdio_np = of_get_child_by_name(priv->dev->of_node, "mdio");
+ if (!mdio_np) {
+@@ -344,15 +344,14 @@ static int realtek_smi_setup_mdio(struct
+ ret = -ENOMEM;
+ goto err_put_node;
+ }
++
+ priv->slave_mii_bus->priv = priv;
+ priv->slave_mii_bus->name = "SMI slave MII";
+ priv->slave_mii_bus->read = realtek_smi_mdio_read;
+ priv->slave_mii_bus->write = realtek_smi_mdio_write;
+ snprintf(priv->slave_mii_bus->id, MII_BUS_ID_SIZE, "SMI-%d",
+ ds->index);
+- priv->slave_mii_bus->dev.of_node = mdio_np;
+ priv->slave_mii_bus->parent = priv->dev;
+- ds->slave_mii_bus = priv->slave_mii_bus;
+
+ ret = devm_of_mdiobus_register(priv->dev, priv->slave_mii_bus, mdio_np);
+ if (ret) {
+@@ -361,8 +360,6 @@ static int realtek_smi_setup_mdio(struct
+ goto err_put_node;
+ }
+
+- return 0;
+-
+ err_put_node:
+ of_node_put(mdio_np);
+
+@@ -428,8 +425,7 @@ EXPORT_SYMBOL_NS_GPL(realtek_smi_probe,
+ * @pdev: platform_device to be removed.
+ *
+ * This function should be used as the .remove_new in a platform_driver. First
+- * it unregisters the DSA switch and cleans internal data. Finally, it calls
+- * the common remove function.
++ * it unregisters the DSA switch and then it calls the common remove function.
+ *
+ * Context: Can sleep.
+ * Return: Nothing.
+@@ -443,9 +439,6 @@ void realtek_smi_remove(struct platform_
+
+ rtl83xx_unregister_switch(priv);
+
+- if (priv->slave_mii_bus)
+- of_node_put(priv->slave_mii_bus->dev.of_node);
+-
+ rtl83xx_remove(priv);
+ }
+ EXPORT_SYMBOL_NS_GPL(realtek_smi_remove, REALTEK_DSA);
--- /dev/null
+From b4bd77971f3c290c4694ed710cc6967593b10bc2 Mon Sep 17 00:00:00 2001
+From: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Date: Fri, 9 Feb 2024 02:03:45 -0300
+Subject: net: dsa: realtek: migrate slave_mii_bus setup to
+ realtek_dsa
+
+In the user MDIO driver, despite numerous references to SMI, including
+its compatible string, there's nothing inherently specific about the SMI
+interface in the user MDIO bus. Consequently, the code has been migrated
+to the rtl83xx module. All references to SMI have been eliminated.
+
+The MDIO bus id was changed from Realtek-<switch id> to the switch
+devname suffixed with :slave_mii, giving more information about the bus
+it is referencing.
+
+Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/realtek/realtek-smi.c | 57 +---------------------
+ drivers/net/dsa/realtek/rtl83xx.c | 68 +++++++++++++++++++++++++++
+ drivers/net/dsa/realtek/rtl83xx.h | 1 +
+ 3 files changed, 70 insertions(+), 56 deletions(-)
+
+--- a/drivers/net/dsa/realtek/realtek-smi.c
++++ b/drivers/net/dsa/realtek/realtek-smi.c
+@@ -31,7 +31,6 @@
+ #include <linux/spinlock.h>
+ #include <linux/skbuff.h>
+ #include <linux/of.h>
+-#include <linux/of_mdio.h>
+ #include <linux/delay.h>
+ #include <linux/gpio/consumer.h>
+ #include <linux/platform_device.h>
+@@ -312,60 +311,6 @@ static int realtek_smi_read(void *ctx, u
+ return realtek_smi_read_reg(priv, reg, val);
+ }
+
+-static int realtek_smi_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 realtek_smi_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);
+-}
+-
+-static int realtek_smi_setup_mdio(struct dsa_switch *ds)
+-{
+- struct realtek_priv *priv = ds->priv;
+- struct device_node *mdio_np;
+- 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;
+- }
+-
+- priv->slave_mii_bus = devm_mdiobus_alloc(priv->dev);
+- if (!priv->slave_mii_bus) {
+- ret = -ENOMEM;
+- goto err_put_node;
+- }
+-
+- priv->slave_mii_bus->priv = priv;
+- priv->slave_mii_bus->name = "SMI slave MII";
+- priv->slave_mii_bus->read = realtek_smi_mdio_read;
+- priv->slave_mii_bus->write = realtek_smi_mdio_write;
+- snprintf(priv->slave_mii_bus->id, MII_BUS_ID_SIZE, "SMI-%d",
+- ds->index);
+- priv->slave_mii_bus->parent = priv->dev;
+-
+- ret = devm_of_mdiobus_register(priv->dev, priv->slave_mii_bus, mdio_np);
+- if (ret) {
+- dev_err(priv->dev, "unable to register MDIO bus %s\n",
+- priv->slave_mii_bus->id);
+- goto err_put_node;
+- }
+-
+-err_put_node:
+- of_node_put(mdio_np);
+-
+- return ret;
+-}
+-
+ static const struct realtek_interface_info realtek_smi_info = {
+ .reg_read = realtek_smi_read,
+ .reg_write = realtek_smi_write,
+@@ -407,7 +352,7 @@ int realtek_smi_probe(struct platform_de
+ }
+
+ priv->write_reg_noack = realtek_smi_write_reg_noack;
+- priv->setup_interface = realtek_smi_setup_mdio;
++ priv->setup_interface = rtl83xx_setup_user_mdio;
+ priv->ds_ops = priv->variant->ds_ops_smi;
+
+ ret = rtl83xx_register_switch(priv);
+--- a/drivers/net/dsa/realtek/rtl83xx.c
++++ b/drivers/net/dsa/realtek/rtl83xx.c
+@@ -3,6 +3,7 @@
+ #include <linux/module.h>
+ #include <linux/regmap.h>
+ #include <linux/of_device.h> /* krnl 6.1 only */
++#include <linux/of_mdio.h>
+
+ #include "realtek.h"
+ #include "rtl83xx.h"
+@@ -44,6 +45,73 @@ void rtl83xx_unlock(void *ctx)
+ }
+ 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 slave_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:slave_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->slave_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
+--- a/drivers/net/dsa/realtek/rtl83xx.h
++++ b/drivers/net/dsa/realtek/rtl83xx.h
+@@ -10,6 +10,7 @@ struct realtek_interface_info {
+
+ 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);
--- /dev/null
+From bba140a566ed075304c49c52ab32c0016cab624a Mon Sep 17 00:00:00 2001
+From: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Date: Fri, 9 Feb 2024 02:03:46 -0300
+Subject: net: dsa: realtek: use the same mii bus driver for
+ both interfaces
+
+The realtek-mdio will now use this driver instead of the generic DSA
+driver ("dsa user smi"), which should not be used with OF[1].
+
+With a single ds_ops for both interfaces, the ds_ops in realtek_priv is
+no longer necessary. Now, the realtek_variant.ds_ops can be used
+directly.
+
+The realtek_priv.setup_interface() has been removed as we can directly
+call the new common function.
+
+[1] https://lkml.kernel.org/netdev/20220630200423.tieprdu5fpabflj7@bang-olufsen.dk/T/
+
+Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
+Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/realtek/realtek-mdio.c | 1 -
+ drivers/net/dsa/realtek/realtek-smi.c | 2 -
+ drivers/net/dsa/realtek/realtek.h | 5 +--
+ drivers/net/dsa/realtek/rtl8365mb.c | 49 +++---------------------
+ drivers/net/dsa/realtek/rtl8366rb.c | 52 +++-----------------------
+ drivers/net/dsa/realtek/rtl83xx.c | 2 +-
+ 6 files changed, 14 insertions(+), 97 deletions(-)
+
+--- a/drivers/net/dsa/realtek/realtek-mdio.c
++++ b/drivers/net/dsa/realtek/realtek-mdio.c
+@@ -131,7 +131,6 @@ int realtek_mdio_probe(struct mdio_devic
+ priv->bus = mdiodev->bus;
+ priv->mdio_addr = mdiodev->addr;
+ priv->write_reg_noack = realtek_mdio_write;
+- priv->ds_ops = priv->variant->ds_ops_mdio;
+
+ ret = rtl83xx_register_switch(priv);
+ if (ret) {
+--- a/drivers/net/dsa/realtek/realtek-smi.c
++++ b/drivers/net/dsa/realtek/realtek-smi.c
+@@ -352,8 +352,6 @@ int realtek_smi_probe(struct platform_de
+ }
+
+ priv->write_reg_noack = realtek_smi_write_reg_noack;
+- priv->setup_interface = rtl83xx_setup_user_mdio;
+- priv->ds_ops = priv->variant->ds_ops_smi;
+
+ ret = rtl83xx_register_switch(priv);
+ if (ret) {
+--- a/drivers/net/dsa/realtek/realtek.h
++++ b/drivers/net/dsa/realtek/realtek.h
+@@ -62,7 +62,6 @@ struct realtek_priv {
+
+ spinlock_t lock; /* Locks around command writes */
+ struct dsa_switch *ds;
+- const struct dsa_switch_ops *ds_ops;
+ struct irq_domain *irqdomain;
+ bool leds_disabled;
+
+@@ -73,7 +72,6 @@ struct realtek_priv {
+ struct rtl8366_mib_counter *mib_counters;
+
+ const struct realtek_ops *ops;
+- int (*setup_interface)(struct dsa_switch *ds);
+ int (*write_reg_noack)(void *ctx, u32 addr, u32 data);
+
+ int vlan_enabled;
+@@ -115,8 +113,7 @@ struct realtek_ops {
+ };
+
+ struct realtek_variant {
+- const struct dsa_switch_ops *ds_ops_smi;
+- const struct dsa_switch_ops *ds_ops_mdio;
++ const struct dsa_switch_ops *ds_ops;
+ const struct realtek_ops *ops;
+ unsigned int clk_delay;
+ u8 cmd_read;
+--- a/drivers/net/dsa/realtek/rtl8365mb.c
++++ b/drivers/net/dsa/realtek/rtl8365mb.c
+@@ -828,17 +828,6 @@ static int rtl8365mb_phy_write(struct re
+ return 0;
+ }
+
+-static int rtl8365mb_dsa_phy_read(struct dsa_switch *ds, int phy, int regnum)
+-{
+- return rtl8365mb_phy_read(ds->priv, phy, regnum);
+-}
+-
+-static int rtl8365mb_dsa_phy_write(struct dsa_switch *ds, int phy, int regnum,
+- u16 val)
+-{
+- return rtl8365mb_phy_write(ds->priv, phy, regnum, val);
+-}
+-
+ static const struct rtl8365mb_extint *
+ rtl8365mb_get_port_extint(struct realtek_priv *priv, int port)
+ {
+@@ -2018,12 +2007,10 @@ static int rtl8365mb_setup(struct dsa_sw
+ if (ret)
+ goto out_teardown_irq;
+
+- if (priv->setup_interface) {
+- ret = priv->setup_interface(ds);
+- if (ret) {
+- dev_err(priv->dev, "could not set up MDIO bus\n");
+- goto out_teardown_irq;
+- }
++ ret = rtl83xx_setup_user_mdio(ds);
++ if (ret) {
++ dev_err(priv->dev, "could not set up MDIO bus\n");
++ goto out_teardown_irq;
+ }
+
+ /* Start statistics counter polling */
+@@ -2117,28 +2104,7 @@ static int rtl8365mb_detect(struct realt
+ return 0;
+ }
+
+-static const struct dsa_switch_ops rtl8365mb_switch_ops_smi = {
+- .get_tag_protocol = rtl8365mb_get_tag_protocol,
+- .change_tag_protocol = rtl8365mb_change_tag_protocol,
+- .setup = rtl8365mb_setup,
+- .teardown = rtl8365mb_teardown,
+- .phylink_get_caps = rtl8365mb_phylink_get_caps,
+- .phylink_mac_config = rtl8365mb_phylink_mac_config,
+- .phylink_mac_link_down = rtl8365mb_phylink_mac_link_down,
+- .phylink_mac_link_up = rtl8365mb_phylink_mac_link_up,
+- .port_stp_state_set = rtl8365mb_port_stp_state_set,
+- .get_strings = rtl8365mb_get_strings,
+- .get_ethtool_stats = rtl8365mb_get_ethtool_stats,
+- .get_sset_count = rtl8365mb_get_sset_count,
+- .get_eth_phy_stats = rtl8365mb_get_phy_stats,
+- .get_eth_mac_stats = rtl8365mb_get_mac_stats,
+- .get_eth_ctrl_stats = rtl8365mb_get_ctrl_stats,
+- .get_stats64 = rtl8365mb_get_stats64,
+- .port_change_mtu = rtl8365mb_port_change_mtu,
+- .port_max_mtu = rtl8365mb_port_max_mtu,
+-};
+-
+-static const struct dsa_switch_ops rtl8365mb_switch_ops_mdio = {
++static const struct dsa_switch_ops rtl8365mb_switch_ops = {
+ .get_tag_protocol = rtl8365mb_get_tag_protocol,
+ .change_tag_protocol = rtl8365mb_change_tag_protocol,
+ .setup = rtl8365mb_setup,
+@@ -2147,8 +2113,6 @@ static const struct dsa_switch_ops rtl83
+ .phylink_mac_config = rtl8365mb_phylink_mac_config,
+ .phylink_mac_link_down = rtl8365mb_phylink_mac_link_down,
+ .phylink_mac_link_up = rtl8365mb_phylink_mac_link_up,
+- .phy_read = rtl8365mb_dsa_phy_read,
+- .phy_write = rtl8365mb_dsa_phy_write,
+ .port_stp_state_set = rtl8365mb_port_stp_state_set,
+ .get_strings = rtl8365mb_get_strings,
+ .get_ethtool_stats = rtl8365mb_get_ethtool_stats,
+@@ -2168,8 +2132,7 @@ static const struct realtek_ops rtl8365m
+ };
+
+ const struct realtek_variant rtl8365mb_variant = {
+- .ds_ops_smi = &rtl8365mb_switch_ops_smi,
+- .ds_ops_mdio = &rtl8365mb_switch_ops_mdio,
++ .ds_ops = &rtl8365mb_switch_ops,
+ .ops = &rtl8365mb_ops,
+ .clk_delay = 10,
+ .cmd_read = 0xb9,
+--- a/drivers/net/dsa/realtek/rtl8366rb.c
++++ b/drivers/net/dsa/realtek/rtl8366rb.c
+@@ -1035,12 +1035,10 @@ static int rtl8366rb_setup(struct dsa_sw
+ if (ret)
+ dev_info(priv->dev, "no interrupt support\n");
+
+- if (priv->setup_interface) {
+- ret = priv->setup_interface(ds);
+- if (ret) {
+- dev_err(priv->dev, "could not set up MDIO bus\n");
+- return -ENODEV;
+- }
++ ret = rtl83xx_setup_user_mdio(ds);
++ if (ret) {
++ dev_err(priv->dev, "could not set up MDIO bus\n");
++ return -ENODEV;
+ }
+
+ return 0;
+@@ -1699,17 +1697,6 @@ out:
+ return ret;
+ }
+
+-static int rtl8366rb_dsa_phy_read(struct dsa_switch *ds, int phy, int regnum)
+-{
+- return rtl8366rb_phy_read(ds->priv, phy, regnum);
+-}
+-
+-static int rtl8366rb_dsa_phy_write(struct dsa_switch *ds, int phy, int regnum,
+- u16 val)
+-{
+- return rtl8366rb_phy_write(ds->priv, phy, regnum, val);
+-}
+-
+ static int rtl8366rb_reset_chip(struct realtek_priv *priv)
+ {
+ int timeout = 10;
+@@ -1775,35 +1762,9 @@ static int rtl8366rb_detect(struct realt
+ return 0;
+ }
+
+-static const struct dsa_switch_ops rtl8366rb_switch_ops_smi = {
+- .get_tag_protocol = rtl8366_get_tag_protocol,
+- .setup = rtl8366rb_setup,
+- .phylink_get_caps = rtl8366rb_phylink_get_caps,
+- .phylink_mac_link_up = rtl8366rb_mac_link_up,
+- .phylink_mac_link_down = rtl8366rb_mac_link_down,
+- .get_strings = rtl8366_get_strings,
+- .get_ethtool_stats = rtl8366_get_ethtool_stats,
+- .get_sset_count = rtl8366_get_sset_count,
+- .port_bridge_join = rtl8366rb_port_bridge_join,
+- .port_bridge_leave = rtl8366rb_port_bridge_leave,
+- .port_vlan_filtering = rtl8366rb_vlan_filtering,
+- .port_vlan_add = rtl8366_vlan_add,
+- .port_vlan_del = rtl8366_vlan_del,
+- .port_enable = rtl8366rb_port_enable,
+- .port_disable = rtl8366rb_port_disable,
+- .port_pre_bridge_flags = rtl8366rb_port_pre_bridge_flags,
+- .port_bridge_flags = rtl8366rb_port_bridge_flags,
+- .port_stp_state_set = rtl8366rb_port_stp_state_set,
+- .port_fast_age = rtl8366rb_port_fast_age,
+- .port_change_mtu = rtl8366rb_change_mtu,
+- .port_max_mtu = rtl8366rb_max_mtu,
+-};
+-
+-static const struct dsa_switch_ops rtl8366rb_switch_ops_mdio = {
++static const struct dsa_switch_ops rtl8366rb_switch_ops = {
+ .get_tag_protocol = rtl8366_get_tag_protocol,
+ .setup = rtl8366rb_setup,
+- .phy_read = rtl8366rb_dsa_phy_read,
+- .phy_write = rtl8366rb_dsa_phy_write,
+ .phylink_get_caps = rtl8366rb_phylink_get_caps,
+ .phylink_mac_link_up = rtl8366rb_mac_link_up,
+ .phylink_mac_link_down = rtl8366rb_mac_link_down,
+@@ -1842,8 +1803,7 @@ static const struct realtek_ops rtl8366r
+ };
+
+ const struct realtek_variant rtl8366rb_variant = {
+- .ds_ops_smi = &rtl8366rb_switch_ops_smi,
+- .ds_ops_mdio = &rtl8366rb_switch_ops_mdio,
++ .ds_ops = &rtl8366rb_switch_ops,
+ .ops = &rtl8366rb_ops,
+ .clk_delay = 10,
+ .cmd_read = 0xa9,
+--- a/drivers/net/dsa/realtek/rtl83xx.c
++++ b/drivers/net/dsa/realtek/rtl83xx.c
+@@ -233,7 +233,7 @@ int rtl83xx_register_switch(struct realt
+
+ ds->priv = priv;
+ ds->dev = priv->dev;
+- ds->ops = priv->ds_ops;
++ ds->ops = priv->variant->ds_ops;
+ ds->num_ports = priv->num_ports;
+ priv->ds = ds;
+
--- /dev/null
+From 9fc469b2943d9b1ff2a7800f823e7cd7a5cac0ca Mon Sep 17 00:00:00 2001
+From: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Date: Fri, 9 Feb 2024 02:03:47 -0300
+Subject: net: dsa: realtek: embed dsa_switch into realtek_priv
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Embed dsa_switch within realtek_priv to eliminate the need for a second
+memory allocation.
+
+Suggested-by: Alvin Šipraga <alsi@bang-olufsen.dk>
+Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
+Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/realtek/realtek.h | 2 +-
+ drivers/net/dsa/realtek/rtl8365mb.c | 15 +++++++++------
+ drivers/net/dsa/realtek/rtl8366rb.c | 3 ++-
+ drivers/net/dsa/realtek/rtl83xx.c | 15 +++++++--------
+ 4 files changed, 19 insertions(+), 16 deletions(-)
+
+--- a/drivers/net/dsa/realtek/realtek.h
++++ b/drivers/net/dsa/realtek/realtek.h
+@@ -61,7 +61,7 @@ struct realtek_priv {
+ const struct realtek_variant *variant;
+
+ spinlock_t lock; /* Locks around command writes */
+- struct dsa_switch *ds;
++ struct dsa_switch ds;
+ struct irq_domain *irqdomain;
+ bool leds_disabled;
+
+--- a/drivers/net/dsa/realtek/rtl8365mb.c
++++ b/drivers/net/dsa/realtek/rtl8365mb.c
+@@ -870,6 +870,7 @@ static int rtl8365mb_ext_config_rgmii(st
+ {
+ const struct rtl8365mb_extint *extint =
+ rtl8365mb_get_port_extint(priv, port);
++ struct dsa_switch *ds = &priv->ds;
+ struct device_node *dn;
+ struct dsa_port *dp;
+ int tx_delay = 0;
+@@ -880,7 +881,7 @@ static int rtl8365mb_ext_config_rgmii(st
+ if (!extint)
+ return -ENODEV;
+
+- dp = dsa_to_port(priv->ds, port);
++ dp = dsa_to_port(ds, port);
+ dn = dp->dn;
+
+ /* Set the RGMII TX/RX delay
+@@ -1534,6 +1535,7 @@ static void rtl8365mb_get_stats64(struct
+ static void rtl8365mb_stats_setup(struct realtek_priv *priv)
+ {
+ struct rtl8365mb *mb = priv->chip_data;
++ struct dsa_switch *ds = &priv->ds;
+ int i;
+
+ /* Per-chip global mutex to protect MIB counter access, since doing
+@@ -1544,7 +1546,7 @@ static void rtl8365mb_stats_setup(struct
+ for (i = 0; i < priv->num_ports; i++) {
+ struct rtl8365mb_port *p = &mb->ports[i];
+
+- if (dsa_is_unused_port(priv->ds, i))
++ if (dsa_is_unused_port(ds, i))
+ continue;
+
+ /* Per-port spinlock to protect the stats64 data */
+@@ -1560,12 +1562,13 @@ static void rtl8365mb_stats_setup(struct
+ static void rtl8365mb_stats_teardown(struct realtek_priv *priv)
+ {
+ struct rtl8365mb *mb = priv->chip_data;
++ struct dsa_switch *ds = &priv->ds;
+ int i;
+
+ for (i = 0; i < priv->num_ports; i++) {
+ struct rtl8365mb_port *p = &mb->ports[i];
+
+- if (dsa_is_unused_port(priv->ds, i))
++ if (dsa_is_unused_port(ds, i))
+ continue;
+
+ cancel_delayed_work_sync(&p->mib_work);
+@@ -1964,7 +1967,7 @@ static int rtl8365mb_setup(struct dsa_sw
+ dev_info(priv->dev, "no interrupt support\n");
+
+ /* Configure CPU tagging */
+- dsa_switch_for_each_cpu_port(cpu_dp, priv->ds) {
++ dsa_switch_for_each_cpu_port(cpu_dp, ds) {
+ cpu->mask |= BIT(cpu_dp->index);
+
+ if (cpu->trap_port == RTL8365MB_MAX_NUM_PORTS)
+@@ -1979,7 +1982,7 @@ static int rtl8365mb_setup(struct dsa_sw
+ for (i = 0; i < priv->num_ports; i++) {
+ struct rtl8365mb_port *p = &mb->ports[i];
+
+- if (dsa_is_unused_port(priv->ds, i))
++ if (dsa_is_unused_port(ds, i))
+ continue;
+
+ /* Forward only to the CPU */
+@@ -1996,7 +1999,7 @@ static int rtl8365mb_setup(struct dsa_sw
+ * ports will still forward frames to the CPU despite being
+ * administratively down by default.
+ */
+- rtl8365mb_port_stp_state_set(priv->ds, i, BR_STATE_DISABLED);
++ rtl8365mb_port_stp_state_set(ds, i, BR_STATE_DISABLED);
+
+ /* Set up per-port private data */
+ p->priv = priv;
+--- a/drivers/net/dsa/realtek/rtl8366rb.c
++++ b/drivers/net/dsa/realtek/rtl8366rb.c
+@@ -1565,6 +1565,7 @@ static int rtl8366rb_get_mc_index(struct
+
+ static int rtl8366rb_set_mc_index(struct realtek_priv *priv, int port, int index)
+ {
++ struct dsa_switch *ds = &priv->ds;
+ struct rtl8366rb *rb;
+ bool pvid_enabled;
+ int ret;
+@@ -1589,7 +1590,7 @@ static int rtl8366rb_set_mc_index(struct
+ * not drop any untagged or C-tagged frames. Make sure to update the
+ * filtering setting.
+ */
+- if (dsa_port_is_vlan_filtering(dsa_to_port(priv->ds, port)))
++ if (dsa_port_is_vlan_filtering(dsa_to_port(ds, port)))
+ ret = rtl8366rb_drop_untagged(priv, port, !pvid_enabled);
+
+ return ret;
+--- a/drivers/net/dsa/realtek/rtl83xx.c
++++ b/drivers/net/dsa/realtek/rtl83xx.c
+@@ -218,7 +218,7 @@ EXPORT_SYMBOL_NS_GPL(rtl83xx_probe, REAL
+ */
+ int rtl83xx_register_switch(struct realtek_priv *priv)
+ {
+- struct dsa_switch *ds;
++ struct dsa_switch *ds = &priv->ds;
+ int ret;
+
+ ret = priv->ops->detect(priv);
+@@ -227,15 +227,10 @@ int rtl83xx_register_switch(struct realt
+ return ret;
+ }
+
+- ds = devm_kzalloc(priv->dev, sizeof(*ds), GFP_KERNEL);
+- if (!ds)
+- return -ENOMEM;
+-
+ ds->priv = priv;
+ ds->dev = priv->dev;
+ ds->ops = priv->variant->ds_ops;
+ ds->num_ports = priv->num_ports;
+- priv->ds = ds;
+
+ ret = dsa_register_switch(ds);
+ if (ret) {
+@@ -258,7 +253,9 @@ EXPORT_SYMBOL_NS_GPL(rtl83xx_register_sw
+ */
+ void rtl83xx_unregister_switch(struct realtek_priv *priv)
+ {
+- dsa_unregister_switch(priv->ds);
++ struct dsa_switch *ds = &priv->ds;
++
++ dsa_unregister_switch(ds);
+ }
+ EXPORT_SYMBOL_NS_GPL(rtl83xx_unregister_switch, REALTEK_DSA);
+
+@@ -275,7 +272,9 @@ EXPORT_SYMBOL_NS_GPL(rtl83xx_unregister_
+ */
+ void rtl83xx_shutdown(struct realtek_priv *priv)
+ {
+- dsa_switch_shutdown(priv->ds);
++ struct dsa_switch *ds = &priv->ds;
++
++ dsa_switch_shutdown(ds);
+
+ dev_set_drvdata(priv->dev, NULL);
+ }
--- /dev/null
+From 32e4a5447ed9fa904a2dfcf4609c64bce053b4e8 Mon Sep 17 00:00:00 2001
+From: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Date: Mon, 12 Feb 2024 18:34:33 -0300
+Subject: [PATCH] net: dsa: realtek: fix digital interface select macro for
+ EXT0
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+While no supported devices currently utilize EXT0, the register reserves
+the bits for an EXT0. EXT0 is utilized by devices from the generation
+prior to rtl8365mb, such as those supported by the driver library
+rtl8367b.
+
+Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Reviewed-by: Alvin Šipraga <alsi@bang-olufsen.dk>
+Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
+Link: https://lore.kernel.org/r/20240212-realtek-fix_ext0-v1-1-f3d2536d191a@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/realtek/rtl8365mb.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/dsa/realtek/rtl8365mb.c
++++ b/drivers/net/dsa/realtek/rtl8365mb.c
+@@ -209,10 +209,10 @@
+ #define RTL8365MB_EXT_PORT_MODE_100FX 13
+
+ /* External interface mode configuration registers 0~1 */
+-#define RTL8365MB_DIGITAL_INTERFACE_SELECT_REG0 0x1305 /* EXT1 */
++#define RTL8365MB_DIGITAL_INTERFACE_SELECT_REG0 0x1305 /* EXT0,EXT1 */
+ #define RTL8365MB_DIGITAL_INTERFACE_SELECT_REG1 0x13C3 /* EXT2 */
+ #define RTL8365MB_DIGITAL_INTERFACE_SELECT_REG(_extint) \
+- ((_extint) == 1 ? RTL8365MB_DIGITAL_INTERFACE_SELECT_REG0 : \
++ ((_extint) <= 1 ? RTL8365MB_DIGITAL_INTERFACE_SELECT_REG0 : \
+ (_extint) == 2 ? RTL8365MB_DIGITAL_INTERFACE_SELECT_REG1 : \
+ 0x0)
+ #define RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_MASK(_extint) \
--- /dev/null
+From 28001bb1955fcfa63e535848c4289fcd7bb88daf Mon Sep 17 00:00:00 2001
+From: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Date: Sun, 25 Feb 2024 13:29:53 -0300
+Subject: [PATCH 1/3] dt-bindings: net: dsa: realtek: reset-gpios is not
+ required
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The 'reset-gpios' should not be mandatory. although they might be
+required for some devices if the switch reset was left asserted by a
+previous driver, such as the bootloader.
+
+Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Cc: devicetree@vger.kernel.org
+Acked-by: Arınç ÜNAL <arinc.unal@arinc9.com>
+Acked-by: Rob Herring <robh@kernel.org>
+Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
+Reviewed-by: Alvin Šipraga <alsi@bang-olufsen.dk>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ Documentation/devicetree/bindings/net/dsa/realtek.yaml | 1 -
+ 1 file changed, 1 deletion(-)
+
+--- a/Documentation/devicetree/bindings/net/dsa/realtek.yaml
++++ b/Documentation/devicetree/bindings/net/dsa/realtek.yaml
+@@ -125,7 +125,6 @@ else:
+ - mdc-gpios
+ - mdio-gpios
+ - mdio
+- - reset-gpios
+
+ required:
+ - compatible
--- /dev/null
+From 5fc2d68fc81801162188995e4d3dc0b26747dd76 Mon Sep 17 00:00:00 2001
+From: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Date: Sun, 25 Feb 2024 13:29:54 -0300
+Subject: [PATCH 2/3] dt-bindings: net: dsa: realtek: add reset controller
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Realtek switches can use a reset controller instead of reset-gpios.
+
+Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Cc: devicetree@vger.kernel.org
+Acked-by: Arınç ÜNAL <arinc.unal@arinc9.com>
+Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
+Reviewed-by: Alvin Šipraga <alsi@bang-olufsen.dk>
+Acked-by: Rob Herring <robh@kernel.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ Documentation/devicetree/bindings/net/dsa/realtek.yaml | 3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/Documentation/devicetree/bindings/net/dsa/realtek.yaml
++++ b/Documentation/devicetree/bindings/net/dsa/realtek.yaml
+@@ -59,6 +59,9 @@ properties:
+ description: GPIO to be used to reset the whole device
+ maxItems: 1
+
++ resets:
++ maxItems: 1
++
+ realtek,disable-leds:
+ type: boolean
+ description: |
--- /dev/null
+From 56998aa6b7f0f31ce8df23c00701af2d8e8a1f1a Mon Sep 17 00:00:00 2001
+From: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Date: Sun, 25 Feb 2024 13:29:55 -0300
+Subject: [PATCH 3/3] net: dsa: realtek: support reset controller
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Add support for resetting the device using a reset controller,
+complementing the existing GPIO reset functionality (reset-gpios).
+
+Although the reset is optional and the driver performs a soft reset
+during setup, if the initial reset pin state was asserted, the driver
+will not detect the device until the reset is deasserted.
+
+Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
+Reviewed-by: Alvin Šipraga <alsi@bang-olufsen.dk>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/realtek/realtek.h | 2 ++
+ drivers/net/dsa/realtek/rtl83xx.c | 42 +++++++++++++++++++++++++++----
+ drivers/net/dsa/realtek/rtl83xx.h | 2 ++
+ 3 files changed, 41 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/dsa/realtek/realtek.h
++++ b/drivers/net/dsa/realtek/realtek.h
+@@ -12,6 +12,7 @@
+ #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 */
+@@ -48,6 +49,7 @@ struct rtl8366_vlan_4k {
+
+ struct realtek_priv {
+ struct device *dev;
++ struct reset_control *reset_ctl;
+ struct gpio_desc *reset;
+ struct gpio_desc *mdc;
+ struct gpio_desc *mdio;
+--- a/drivers/net/dsa/realtek/rtl83xx.c
++++ b/drivers/net/dsa/realtek/rtl83xx.c
+@@ -185,6 +185,13 @@ rtl83xx_probe(struct device *dev,
+ "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)) {
++ ret = PTR_ERR(priv->reset_ctl);
++ dev_err_probe(dev, ret, "failed to get reset control\n");
++ return ERR_CAST(priv->reset_ctl);
++ }
++
+ 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");
+@@ -193,11 +200,11 @@ rtl83xx_probe(struct device *dev,
+
+ dev_set_drvdata(dev, priv);
+
+- if (priv->reset) {
+- gpiod_set_value(priv->reset, 1);
++ if (priv->reset_ctl || priv->reset) {
++ rtl83xx_reset_assert(priv);
+ dev_dbg(dev, "asserted RESET\n");
+ msleep(REALTEK_HW_STOP_DELAY);
+- gpiod_set_value(priv->reset, 0);
++ rtl83xx_reset_deassert(priv);
+ msleep(REALTEK_HW_START_DELAY);
+ dev_dbg(dev, "deasserted RESET\n");
+ }
+@@ -293,11 +300,36 @@ EXPORT_SYMBOL_NS_GPL(rtl83xx_shutdown, R
+ void rtl83xx_remove(struct realtek_priv *priv)
+ {
+ /* leave the device reset asserted */
+- if (priv->reset)
+- gpiod_set_value(priv->reset, 1);
++ rtl83xx_reset_assert(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);
++}
++
+ 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");
+--- a/drivers/net/dsa/realtek/rtl83xx.h
++++ b/drivers/net/dsa/realtek/rtl83xx.h
+@@ -18,5 +18,7 @@ int rtl83xx_register_switch(struct realt
+ 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);
+
+ #endif /* _RTL83XX_H */
--- /dev/null
+From 4f580e9aced1816398c1c64f178302a22b8ea6e2 Mon Sep 17 00:00:00 2001
+From: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Date: Sat, 27 Apr 2024 02:11:29 -0300
+Subject: [PATCH 2/3] net: dsa: realtek: do not assert reset on remove
+
+The necessity of asserting the reset on removal was previously
+questioned, as DSA's own cleanup methods should suffice to prevent
+traffic leakage[1].
+
+When a driver has subdrivers controlled by devres, they will be
+unregistered after the main driver's .remove is executed. If it asserts
+a reset, the subdrivers will be unable to communicate with the hardware
+during their cleanup. For LEDs, this means that they will fail to turn
+off, resulting in a timeout error.
+
+[1] https://lore.kernel.org/r/20240123215606.26716-9-luizluca@gmail.com/
+
+Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/realtek/rtl83xx.c | 7 ++-----
+ 1 file changed, 2 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/dsa/realtek/rtl83xx.c
++++ b/drivers/net/dsa/realtek/rtl83xx.c
+@@ -291,16 +291,13 @@ EXPORT_SYMBOL_NS_GPL(rtl83xx_shutdown, R
+ * rtl83xx_remove() - Cleanup a realtek switch driver
+ * @priv: realtek_priv pointer
+ *
+- * If a method is provided, this function asserts the hard reset of the switch
+- * in order to avoid leaking traffic when the driver is gone.
++ * Placehold for common cleanup procedures.
+ *
+- * Context: Might sleep if priv->gdev->chip->can_sleep.
++ * Context: Any
+ * Return: nothing
+ */
+ void rtl83xx_remove(struct realtek_priv *priv)
+ {
+- /* leave the device reset asserted */
+- rtl83xx_reset_assert(priv);
+ }
+ EXPORT_SYMBOL_NS_GPL(rtl83xx_remove, REALTEK_DSA);
+
--- /dev/null
+From 32d617005475a71ebcc4ec8b2791e8d1481e9a10 Mon Sep 17 00:00:00 2001
+From: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Date: Sat, 27 Apr 2024 02:11:30 -0300
+Subject: [PATCH 3/3] net: dsa: realtek: add LED drivers for rtl8366rb
+
+This commit introduces LED drivers for rtl8366rb, enabling LEDs to be
+described in the device tree using the same format as qca8k. Each port
+can configure up to 4 LEDs.
+
+If all LEDs in a group use the default state "keep", they will use the
+default behavior after a reset. Changing the brightness of one LED,
+either manually or by a trigger, will disable the default hardware
+trigger and switch the entire LED group to manually controlled LEDs.
+Once in this mode, there is no way to revert to hardware-controlled LEDs
+(except by resetting the switch).
+
+Software triggers function as expected with manually controlled LEDs.
+
+Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
+Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/realtek/rtl8366rb.c | 304 ++++++++++++++++++++++++----
+ 1 file changed, 265 insertions(+), 39 deletions(-)
+
+--- a/drivers/net/dsa/realtek/rtl8366rb.c
++++ b/drivers/net/dsa/realtek/rtl8366rb.c
+@@ -180,6 +180,7 @@
+ #define RTL8366RB_VLAN_INGRESS_CTRL2_REG 0x037f
+
+ /* 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
+@@ -195,31 +196,21 @@
+ (4 * (led_group))
+ #define RTL8366RB_LED_CTRL_MASK(led_group) \
+ (0xf << RTL8366RB_LED_CTRL_OFFSET(led_group))
+-#define RTL8366RB_LED_OFF 0x0
+-#define RTL8366RB_LED_DUP_COL 0x1
+-#define RTL8366RB_LED_LINK_ACT 0x2
+-#define RTL8366RB_LED_SPD1000 0x3
+-#define RTL8366RB_LED_SPD100 0x4
+-#define RTL8366RB_LED_SPD10 0x5
+-#define RTL8366RB_LED_SPD1000_ACT 0x6
+-#define RTL8366RB_LED_SPD100_ACT 0x7
+-#define RTL8366RB_LED_SPD10_ACT 0x8
+-#define RTL8366RB_LED_SPD100_10_ACT 0x9
+-#define RTL8366RB_LED_FIBER 0xa
+-#define RTL8366RB_LED_AN_FAULT 0xb
+-#define RTL8366RB_LED_LINK_RX 0xc
+-#define RTL8366RB_LED_LINK_TX 0xd
+-#define RTL8366RB_LED_MASTER 0xe
+-#define RTL8366RB_LED_FORCE 0xf
+
+ /* 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_LED_FORCE. Otherwise, it is ignored.
++ * RTL8366RB_LEDGROUP_FORCE. Otherwise, it is ignored.
+ */
+ #define RTL8366RB_LED_0_1_CTRL_REG 0x0432
+-#define RTL8366RB_LED_1_OFFSET 6
+ #define RTL8366RB_LED_2_3_CTRL_REG 0x0433
+-#define RTL8366RB_LED_3_OFFSET 6
++#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)
+
+ #define RTL8366RB_MIB_COUNT 33
+ #define RTL8366RB_GLOBAL_MIB_COUNT 1
+@@ -363,14 +354,44 @@
+ #define RTL8366RB_GREEN_FEATURE_TX BIT(0)
+ #define RTL8366RB_GREEN_FEATURE_RX BIT(2)
+
++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
++};
++
++struct rtl8366rb_led {
++ u8 port_num;
++ u8 led_group;
++ struct realtek_priv *priv;
++ struct led_classdev cdev;
++};
++
+ /**
+ * 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];
++ struct rtl8366rb_led leds[RTL8366RB_NUM_PORTS][RTL8366RB_NUM_LEDGROUPS];
+ };
+
+ static struct rtl8366_mib_counter rtl8366rb_mib_counters[] = {
+@@ -813,6 +834,217 @@ static int rtl8366rb_jam_table(const str
+ return 0;
+ }
+
++static int rb8366rb_set_ledgroup_mode(struct realtek_priv *priv,
++ u8 led_group,
++ enum rtl8366_ledgroup_mode mode)
++{
++ int ret;
++ u32 val;
++
++ val = mode << RTL8366RB_LED_CTRL_OFFSET(led_group);
++
++ ret = regmap_update_bits(priv->map,
++ RTL8366RB_LED_CTRL_REG,
++ RTL8366RB_LED_CTRL_MASK(led_group),
++ val);
++ if (ret)
++ return ret;
++
++ return 0;
++}
++
++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_0_X_CTRL_MASK, BIT(port));
++ case 2:
++ return FIELD_PREP(RTL8366RB_LED_0_X_CTRL_MASK, BIT(port));
++ case 3:
++ return FIELD_PREP(RTL8366RB_LED_0_X_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;
++ 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;
++
++ init_data.devicename = kasprintf(GFP_KERNEL, "Realtek-%d:0%d:%d",
++ dp->ds->index, dp->index, led_group);
++ if (!init_data.devicename)
++ return -ENOMEM;
++
++ 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;
++}
++
++static int rtl8366rb_setup_all_leds_off(struct realtek_priv *priv)
++{
++ int ret = 0;
++ int i;
++
++ regmap_update_bits(priv->map,
++ RTL8366RB_INTERRUPT_CONTROL_REG,
++ RTL8366RB_P4_RGMII_LED,
++ 0);
++
++ for (i = 0; i < RTL8366RB_NUM_LEDGROUPS; i++) {
++ ret = rb8366rb_set_ledgroup_mode(priv, i,
++ RTL8366RB_LEDGROUP_OFF);
++ if (ret)
++ return ret;
++ }
++
++ return ret;
++}
++
++static int rtl8366rb_setup_leds(struct realtek_priv *priv)
++{
++ struct device_node *leds_np, *led_np;
++ struct dsa_switch *ds = &priv->ds;
++ 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(leds_np, led_np) {
++ ret = rtl8366rb_setup_led(priv, dp,
++ of_fwnode_handle(led_np));
++ if (ret) {
++ of_node_put(led_np);
++ break;
++ }
++ }
++
++ of_node_put(leds_np);
++ if (ret)
++ return ret;
++ }
++ return 0;
++}
++
+ static int rtl8366rb_setup(struct dsa_switch *ds)
+ {
+ struct realtek_priv *priv = ds->priv;
+@@ -821,7 +1053,6 @@ static int rtl8366rb_setup(struct dsa_sw
+ u32 chip_ver = 0;
+ u32 chip_id = 0;
+ int jam_size;
+- u32 val;
+ int ret;
+ int i;
+
+@@ -997,7 +1228,9 @@ static int rtl8366rb_setup(struct dsa_sw
+ if (ret)
+ return ret;
+
+- /* Set blinking, TODO: make this configurable */
++ /* Set blinking, used by all LED groups using HW triggers.
++ * TODO: make this configurable
++ */
+ ret = regmap_update_bits(priv->map, RTL8366RB_LED_BLINKRATE_REG,
+ RTL8366RB_LED_BLINKRATE_MASK,
+ RTL8366RB_LED_BLINKRATE_56MS);
+@@ -1005,26 +1238,19 @@ static int rtl8366rb_setup(struct dsa_sw
+ return ret;
+
+ /* Set up LED activity:
+- * Each port has 4 LEDs, we configure all ports to the same
+- * behaviour (no individual config) but we can set up each
+- * LED separately.
++ * Each port has 4 LEDs on fixed groups. Each group shares the same
++ * hardware trigger across all ports. LEDs can only be indiviually
++ * controlled setting the LED group to fixed mode and using the driver
++ * to toggle them LEDs on/off.
+ */
+ if (priv->leds_disabled) {
+- /* Turn everything off */
+- regmap_update_bits(priv->map,
+- RTL8366RB_INTERRUPT_CONTROL_REG,
+- RTL8366RB_P4_RGMII_LED,
+- 0);
+-
+- for (i = 0; i < RTL8366RB_NUM_LEDGROUPS; i++) {
+- val = RTL8366RB_LED_OFF << RTL8366RB_LED_CTRL_OFFSET(i);
+- ret = regmap_update_bits(priv->map,
+- RTL8366RB_LED_CTRL_REG,
+- RTL8366RB_LED_CTRL_MASK(i),
+- val);
+- if (ret)
+- return ret;
+- }
++ ret = rtl8366rb_setup_all_leds_off(priv);
++ if (ret)
++ return ret;
++ } else {
++ ret = rtl8366rb_setup_leds(priv);
++ if (ret)
++ return ret;
+ }
+
+ ret = rtl8366_reset_vlan(priv);