net: dsa: realtek: common rtl83xx module
authorLuiz Angelo Daros de Luca <luizluca@gmail.com>
Fri, 9 Feb 2024 05:03:41 +0000 (02:03 -0300)
committerDavid S. Miller <davem@davemloft.net>
Mon, 12 Feb 2024 10:42:17 +0000 (10:42 +0000)
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
drivers/net/dsa/realtek/realtek-mdio.c
drivers/net/dsa/realtek/realtek-smi.c
drivers/net/dsa/realtek/realtek.h
drivers/net/dsa/realtek/rtl8365mb.c
drivers/net/dsa/realtek/rtl8366rb.c
drivers/net/dsa/realtek/rtl83xx.c [new file with mode: 0644]
drivers/net/dsa/realtek/rtl83xx.h [new file with mode: 0644]

index 0aab57252a7cdf7b0451e8a4d7aa7bfdd5083184..d579127f05f7712965e0d77c9b94cf70372a8f02 100644 (file)
@@ -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
index 7be00aa2a9e7bb5637f3e24bba203b9ae7862884..90a017cbe168111506b2af1e9412b589fa3436f1 100644 (file)
@@ -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,
-       .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,
+static const struct realtek_interface_info realtek_mdio_info = {
        .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, REALTEK_DSA);
  * @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_device *mdiodev)
        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_DSA);
  * realtek_mdio_shutdown() - Shutdown the driver of a MDIO-connected switch
  * @mdiodev: mdio_device shutting down.
  *
- * This function should be used as the .shutdown in 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_device *mdiodev)
        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);
 
index ad3b5731101523ea6618824002c8ecbe0dbbf71e..d676cc44d517927d7e0454d0ca8cb4cf442e352f 100644 (file)
@@ -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, 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,
-       .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, REALTEK_DSA);
  * @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_device *pdev)
        if (!priv)
                return;
 
-       dsa_unregister_switch(priv->ds);
+       rtl83xx_unregister_switch(priv);
+
        if (priv->user_mii_bus)
                of_node_put(priv->user_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_DSA);
  * realtek_smi_shutdown() - Shutdown the driver of a SMI-connected switch
  * @pdev: platform_device shutting down.
  *
- * This function should be used as the .shutdown in a platform_driver. It 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 platform_device *pdev)
        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);
 
index c7d5ef99e9db8a337749721aa88abf89783e538a..e9e28b18950991aa5fc3a693ab3918581ca02e1d 100644 (file)
@@ -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;
 
index e67c4562f5dbc231f8c27f3c724e922fb3c8c93c..60f826a5a00a19402d95b79fe200249a2344e27a 100644 (file)
 #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 realtek_priv *priv, int phy,
        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 realtek_priv *priv, int phy,
        *data = val & 0xFFFF;
 
 out:
-       mutex_unlock(&priv->map_lock);
+       rtl83xx_unlock(priv);
 
        return ret;
 }
@@ -735,7 +736,7 @@ static int rtl8365mb_phy_ocp_write(struct realtek_priv *priv, int phy,
        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(struct realtek_priv *priv, int phy,
                goto out;
 
 out:
-       mutex_unlock(&priv->map_lock);
+       rtl83xx_unlock(priv);
 
        return 0;
 }
index 747407ae82254668dcfa685f5dfbbe1cd0185a90..a0c365325b4a7b7bd4a730927106ad9b3e67cc6c 100644 (file)
@@ -25,6 +25,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
@@ -1720,7 +1721,7 @@ static int rtl8366rb_phy_read(struct realtek_priv *priv, int phy, int regnum)
        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);
@@ -1748,7 +1749,7 @@ static int rtl8366rb_phy_read(struct realtek_priv *priv, int phy, int regnum)
                phy, regnum, reg, val);
 
 out:
-       mutex_unlock(&priv->map_lock);
+       rtl83xx_unlock(priv);
 
        return ret;
 }
@@ -1762,7 +1763,7 @@ static int rtl8366rb_phy_write(struct realtek_priv *priv, int phy, int regnum,
        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);
@@ -1779,7 +1780,7 @@ static int rtl8366rb_phy_write(struct realtek_priv *priv, int phy, int regnum,
                goto out;
 
 out:
-       mutex_unlock(&priv->map_lock);
+       rtl83xx_unlock(priv);
 
        return ret;
 }
diff --git a/drivers/net/dsa/realtek/rtl83xx.c b/drivers/net/dsa/realtek/rtl83xx.c
new file mode 100644 (file)
index 0000000..0f62806
--- /dev/null
@@ -0,0 +1,235 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#include "realtek.h"
+#include "rtl83xx.h"
+
+/**
+ * rtl83xx_lock() - Locks the mutex used by regmaps
+ * @ctx: realtek_priv pointer
+ *
+ * This function is passed to regmap to be used as the lock function.
+ * It is also used externally to block regmap before executing multiple
+ * operations that must happen in sequence (which will use
+ * realtek_priv.map_nolock instead).
+ *
+ * Context: Can sleep. Holds priv->map_lock lock.
+ * Return: nothing
+ */
+void rtl83xx_lock(void *ctx)
+{
+       struct realtek_priv *priv = ctx;
+
+       mutex_lock(&priv->map_lock);
+}
+EXPORT_SYMBOL_NS_GPL(rtl83xx_lock, REALTEK_DSA);
+
+/**
+ * rtl83xx_unlock() - Unlocks the mutex used by regmaps
+ * @ctx: realtek_priv pointer
+ *
+ * This function unlocks the lock acquired by rtl83xx_lock.
+ *
+ * Context: Releases priv->map_lock lock.
+ * Return: nothing
+ */
+void rtl83xx_unlock(void *ctx)
+{
+       struct realtek_priv *priv = ctx;
+
+       mutex_unlock(&priv->map_lock);
+}
+EXPORT_SYMBOL_NS_GPL(rtl83xx_unlock, REALTEK_DSA);
+
+/**
+ * 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");
diff --git a/drivers/net/dsa/realtek/rtl83xx.h b/drivers/net/dsa/realtek/rtl83xx.h
new file mode 100644 (file)
index 0000000..a8eed92
--- /dev/null
@@ -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 */