Commit 08f62716 authored by David S. Miller's avatar David S. Miller
Browse files

Merge branch 'dsa-realtek-common'



Luiz Angelo Daros de Luca says:

====================
net: dsa: realtek: variants to drivers, interfaces to a common module

The current driver consists of two interface modules (SMI and MDIO) and
two family/variant modules (RTL8365MB and RTL8366RB). The SMI and MDIO
modules serve as the platform and MDIO drivers, respectively, calling
functions from the variant modules. In this setup, one interface module
can be loaded independently of the other, but both variants must be
loaded (if not disabled at build time) for any type of interface. This
approach doesn't scale well, especially with the addition of more switch
variants (e.g., RTL8366B), leading to loaded but unused modules.
Additionally, this also seems upside down, as the specific driver code
normally depends on the more generic functions and not the other way
around.

Each variant module was converted into real drivers, serving as both a
platform driver (for switches connected using the SMI interface) and an
MDIO driver (for MDIO-connected switches). The relationship between the
variant and interface modules is reversed, with the variant module now
calling both interface functions (if not disabled at build time). While
in most devices only one interface is likely used, the interface code is
significantly smaller than a variant module, consuming fewer resources
than the previous code. With variant modules now functioning as real
drivers, compatible strings are published only in a single variant
module, preventing conflicts.

The patch series introduces a new common module for functions shared by
both variants. This module also absorbs the two previous interface
modules, as they would always be loaded anyway.

The series relocates the user MII driver from realtek-smi to rtl83xx. It
is now used by MDIO-connected switches instead of the generic DSA
driver. There's a change in how this driver locates the MDIO node. It
now only searches for a child node named "mdio".

The dsa_switch in realtek_priv->ds is now embedded in the struct. It is
always in use and avoids dynamic memory allocation.

Testing has been performed with an RTL8367S (rtl8365mb) using MDIO
interface and an RTL8366RB (rtl8366) with SMI interface.
====================

Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 0918c1dc 9fc469b2
Loading
Loading
Loading
Loading
+6 −14
Original line number Diff line number Diff line
@@ -16,37 +16,29 @@ menuconfig NET_DSA_REALTEK
if NET_DSA_REALTEK

config NET_DSA_REALTEK_MDIO
	tristate "Realtek MDIO interface driver"
	bool "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"
	bool "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.
+11 −2
Original line number Diff line number Diff line
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_NET_DSA_REALTEK_MDIO) 	+= realtek-mdio.o
obj-$(CONFIG_NET_DSA_REALTEK_SMI) 	+= realtek-smi.o
obj-$(CONFIG_NET_DSA_REALTEK)		+= realtek_dsa.o
realtek_dsa-objs			:= rtl83xx.o

ifdef CONFIG_NET_DSA_REALTEK_MDIO
realtek_dsa-objs += realtek-mdio.o
endif

ifdef CONFIG_NET_DSA_REALTEK_SMI
realtek_dsa-objs += realtek-smi.o
endif

obj-$(CONFIG_NET_DSA_REALTEK_RTL8366RB) += rtl8366.o
rtl8366-objs 				:= rtl8366-core.o rtl8366rb.o
obj-$(CONFIG_NET_DSA_REALTEK_RTL8365MB) += rtl8365mb.o
+51 −154
Original line number Diff line number Diff line
@@ -25,6 +25,8 @@
#include <linux/regmap.h>

#include "realtek.h"
#include "realtek-mdio.h"
#include "rtl83xx.h"

/* Read/write via mdiobus */
#define REALTEK_MDIO_CTRL0_REG		31
@@ -99,192 +101,87 @@ static int realtek_mdio_read(void *ctx, u32 reg, u32 *val)
	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,
};

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. 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);
	priv = rtl83xx_probe(dev, &realtek_mdio_info);
	if (IS_ERR(priv))
		return PTR_ERR(priv);

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

	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->clk_delay = var->clk_delay;
	priv->cmd_read = var->cmd_read;
	priv->cmd_write = var->cmd_write;
	priv->ops = var->ops;

	priv->mdio_addr = mdiodev->addr;
	priv->write_reg_noack = realtek_mdio_write;

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

	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 then it calls the common remove function.
 *
 * Context: Can sleep.
 * Return: Nothing.
 */
void realtek_mdio_remove(struct mdio_device *mdiodev)
{
	struct realtek_priv *priv = dev_get_drvdata(&mdiodev->dev);

	if (!priv)
		return;

	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);

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 a platform_driver. It calls
 * the common shutdown function.
 *
 * Context: Can sleep.
 * Return: Nothing.
 */
void realtek_mdio_shutdown(struct mdio_device *mdiodev)
{
	struct realtek_priv *priv = dev_get_drvdata(&mdiodev->dev);

	if (!priv)
		return;

	dsa_switch_shutdown(priv->ds);

	dev_set_drvdata(&mdiodev->dev, NULL);
	rtl83xx_shutdown(priv);
}

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);

MODULE_AUTHOR("Luiz Angelo Daros de Luca <luizluca@gmail.com>");
MODULE_DESCRIPTION("Driver for Realtek ethernet switch connected via MDIO interface");
MODULE_LICENSE("GPL");
EXPORT_SYMBOL_NS_GPL(realtek_mdio_shutdown, REALTEK_DSA);
+48 −0
Original line number Diff line number Diff line
/* 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 */
+60 −219
Original line number Diff line number Diff line
@@ -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>
@@ -40,12 +39,14 @@
#include <linux/if_bridge.h>

#include "realtek.h"
#include "realtek-smi.h"
#include "rtl83xx.h"

#define REALTEK_SMI_ACK_RETRY_COUNT		5

static inline void realtek_smi_clk_delay(struct realtek_priv *priv)
{
	ndelay(priv->clk_delay);
	ndelay(priv->variant->clk_delay);
}

static void realtek_smi_start(struct realtek_priv *priv)
@@ -208,7 +209,7 @@ static int realtek_smi_read_reg(struct realtek_priv *priv, u32 addr, u32 *data)
	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;

@@ -249,7 +250,7 @@ static int realtek_smi_write_reg(struct realtek_priv *priv,
	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;

@@ -310,258 +311,98 @@ static int realtek_smi_read(void *ctx, u32 reg, u32 *val)
	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,
static const struct realtek_interface_info realtek_smi_info = {
	.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;

	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;

	mdio_np = of_get_compatible_child(priv->dev->of_node, "realtek,smi-mdio");
	if (!mdio_np) {
		dev_err(priv->dev, "no MDIO bus node\n");
		return -ENODEV;
	}

	priv->user_mii_bus = devm_mdiobus_alloc(priv->dev);
	if (!priv->user_mii_bus) {
		ret = -ENOMEM;
		goto err_put_node;
	}
	priv->user_mii_bus->priv = priv;
	priv->user_mii_bus->name = "SMI user MII";
	priv->user_mii_bus->read = realtek_smi_mdio_read;
	priv->user_mii_bus->write = realtek_smi_mdio_write;
	snprintf(priv->user_mii_bus->id, MII_BUS_ID_SIZE, "SMI-%d",
		 ds->index);
	priv->user_mii_bus->dev.of_node = mdio_np;
	priv->user_mii_bus->parent = priv->dev;
	ds->user_mii_bus = priv->user_mii_bus;

	ret = devm_of_mdiobus_register(priv->dev, priv->user_mii_bus, mdio_np);
	if (ret) {
		dev_err(priv->dev, "unable to register MDIO bus %s\n",
			priv->user_mii_bus->id);
		goto err_put_node;
	}

	return 0;

err_put_node:
	of_node_put(mdio_np);

	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. 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->clk_delay = var->clk_delay;
	priv->cmd_read = var->cmd_read;
	priv->cmd_write = var->cmd_write;
	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->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);

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 then it calls the common remove function.
 *
 * Context: Can sleep.
 * Return: Nothing.
 */
void realtek_smi_remove(struct platform_device *pdev)
{
	struct realtek_priv *priv = platform_get_drvdata(pdev);

	if (!priv)
		return;

	dsa_unregister_switch(priv->ds);
	if (priv->user_mii_bus)
		of_node_put(priv->user_mii_bus->dev.of_node);
	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_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 calls
 * the common shutdown function.
 *
 * Context: Can sleep.
 * Return: Nothing.
 */
void realtek_smi_shutdown(struct platform_device *pdev)
{
	struct realtek_priv *priv = platform_get_drvdata(pdev);

	if (!priv)
		return;

	dsa_switch_shutdown(priv->ds);

	platform_set_drvdata(pdev, NULL);
	rtl83xx_shutdown(priv);
}

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);

MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
MODULE_DESCRIPTION("Driver for Realtek ethernet switch connected via SMI interface");
MODULE_LICENSE("GPL");
EXPORT_SYMBOL_NS_GPL(realtek_smi_shutdown, REALTEK_DSA);
Loading