* [PATCH net-next v11 1/5] net: dsa: yt921x: Check lock status with lockdep_assert_held_once()
2026-08-31 4:38 [PATCH net-next v11 0/5] net: dsa: motorcomm: Add LED support David Yang
@ 2026-08-31 4:38 ` David Yang
2026-08-31 23:29 ` Andrew Lunn
2026-09-02 0:56 ` Jakub Kicinski
2026-08-31 4:38 ` [PATCH net-next v11 2/5] net: dsa: motorcomm: Move to subdirectory David Yang
` (3 subsequent siblings)
4 siblings, 2 replies; 12+ messages in thread
From: David Yang @ 2026-08-31 4:38 UTC (permalink / raw)
To: netdev
Cc: David Yang, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, devicetree, linux-kernel
mutex_is_locked() was used to check the lock status. While it should not
be triggered unless a bug is introduced, it does not warn on lock being
held by another thread. Fix it with lockdep helpers.
Fixes: 186623f4aa72 ("net: dsa: yt921x: Add support for Motorcomm YT921x")
Signed-off-by: David Yang <mmyangfl@gmail.com>
---
drivers/net/dsa/yt921x.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/net/dsa/yt921x.c b/drivers/net/dsa/yt921x.c
index 159b16606f6c..44fcdd47ffb5 100644
--- a/drivers/net/dsa/yt921x.c
+++ b/drivers/net/dsa/yt921x.c
@@ -198,14 +198,14 @@ static u32 ethaddr_lo2_to_u32(const unsigned char *addr)
static int yt921x_reg_read(struct yt921x_priv *priv, u32 reg, u32 *valp)
{
- WARN_ON(!mutex_is_locked(&priv->reg_lock));
+ lockdep_assert_held_once(&priv->reg_lock);
return priv->reg_ops->read(priv->reg_ctx, reg, valp);
}
static int yt921x_reg_write(struct yt921x_priv *priv, u32 reg, u32 val)
{
- WARN_ON(!mutex_is_locked(&priv->reg_lock));
+ lockdep_assert_held_once(&priv->reg_lock);
return priv->reg_ops->write(priv->reg_ctx, reg, val);
}
--
2.53.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH net-next v11 1/5] net: dsa: yt921x: Check lock status with lockdep_assert_held_once()
2026-08-31 4:38 ` [PATCH net-next v11 1/5] net: dsa: yt921x: Check lock status with lockdep_assert_held_once() David Yang
@ 2026-08-31 23:29 ` Andrew Lunn
2026-09-02 0:56 ` Jakub Kicinski
1 sibling, 0 replies; 12+ messages in thread
From: Andrew Lunn @ 2026-08-31 23:29 UTC (permalink / raw)
To: David Yang
Cc: netdev, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, devicetree, linux-kernel
On Mon, Aug 31, 2026 at 12:38:54PM +0800, David Yang wrote:
> mutex_is_locked() was used to check the lock status. While it should not
> be triggered unless a bug is introduced, it does not warn on lock being
> held by another thread. Fix it with lockdep helpers.
>
> Fixes: 186623f4aa72 ("net: dsa: yt921x: Add support for Motorcomm YT921x")
> Signed-off-by: David Yang <mmyangfl@gmail.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Andrew
^ permalink raw reply [flat|nested] 12+ messages in thread* Re: [PATCH net-next v11 1/5] net: dsa: yt921x: Check lock status with lockdep_assert_held_once()
2026-08-31 4:38 ` [PATCH net-next v11 1/5] net: dsa: yt921x: Check lock status with lockdep_assert_held_once() David Yang
2026-08-31 23:29 ` Andrew Lunn
@ 2026-09-02 0:56 ` Jakub Kicinski
1 sibling, 0 replies; 12+ messages in thread
From: Jakub Kicinski @ 2026-09-02 0:56 UTC (permalink / raw)
To: David Yang
Cc: netdev, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, devicetree, linux-kernel
On Mon, 31 Aug 2026 12:38:54 +0800 David Yang wrote:
> mutex_is_locked() was used to check the lock status. While it should not
> be triggered unless a bug is introduced, it does not warn on lock being
> held by another thread. Fix it with lockdep helpers.
>
> Fixes: 186623f4aa72 ("net: dsa: yt921x: Add support for Motorcomm YT921x")
If you're reposting please drop this Fixes tag.
Either it's a bug that needs to go to net or it's a net-next
improvement which doesn't need a Fixes tag.
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH net-next v11 2/5] net: dsa: motorcomm: Move to subdirectory
2026-08-31 4:38 [PATCH net-next v11 0/5] net: dsa: motorcomm: Add LED support David Yang
2026-08-31 4:38 ` [PATCH net-next v11 1/5] net: dsa: yt921x: Check lock status with lockdep_assert_held_once() David Yang
@ 2026-08-31 4:38 ` David Yang
2026-08-31 4:38 ` [PATCH net-next v11 3/5] net: dsa: motorcomm: Split SMI module David Yang
` (2 subsequent siblings)
4 siblings, 0 replies; 12+ messages in thread
From: David Yang @ 2026-08-31 4:38 UTC (permalink / raw)
To: netdev
Cc: David Yang, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, devicetree, linux-kernel
yt921x is already the longest single-file DSA driver, so it's time to
split it into parts.
Signed-off-by: David Yang <mmyangfl@gmail.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
---
MAINTAINERS | 2 +-
drivers/net/dsa/Kconfig | 10 ++--------
drivers/net/dsa/Makefile | 2 +-
drivers/net/dsa/motorcomm/Kconfig | 8 ++++++++
drivers/net/dsa/motorcomm/Makefile | 3 +++
drivers/net/dsa/{yt921x.c => motorcomm/chip.c} | 2 +-
drivers/net/dsa/{yt921x.h => motorcomm/chip.h} | 0
7 files changed, 16 insertions(+), 11 deletions(-)
create mode 100644 drivers/net/dsa/motorcomm/Kconfig
create mode 100644 drivers/net/dsa/motorcomm/Makefile
rename drivers/net/dsa/{yt921x.c => motorcomm/chip.c} (99%)
rename drivers/net/dsa/{yt921x.h => motorcomm/chip.h} (100%)
diff --git a/MAINTAINERS b/MAINTAINERS
index 0b42e898f4d8..4b0254f62277 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18422,7 +18422,7 @@ M: David Yang <mmyangfl@gmail.com>
L: netdev@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/net/dsa/motorcomm,yt921x.yaml
-F: drivers/net/dsa/yt921x.*
+F: drivers/net/dsa/motorcomm/
F: net/dsa/tag_yt921x.c
MOXA SMARTIO/INDUSTIO/INTELLIO SERIAL CARD
diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig
index 676fb7dffe14..8d8edcf89f10 100644
--- a/drivers/net/dsa/Kconfig
+++ b/drivers/net/dsa/Kconfig
@@ -82,6 +82,8 @@ config NET_DSA_MV88E6060
source "drivers/net/dsa/microchip/Kconfig"
+source "drivers/net/dsa/motorcomm/Kconfig"
+
source "drivers/net/dsa/mv88e6xxx/Kconfig"
source "drivers/net/dsa/mxl862xx/Kconfig"
@@ -168,12 +170,4 @@ config NET_DSA_VITESSE_VSC73XX_PLATFORM
This enables support for the Vitesse VSC7385, VSC7388, VSC7395
and VSC7398 SparX integrated ethernet switches, connected over
a CPU-attached address bus and work in memory-mapped I/O mode.
-
-config NET_DSA_YT921X
- tristate "Motorcomm YT9215 ethernet switch chip support"
- select NET_DSA_TAG_YT921X
- select NET_IEEE8021Q_HELPERS if DCB
- help
- This enables support for the Motorcomm YT9215 ethernet switch
- chip.
endmenu
diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile
index 6ceb78a755d7..df849cd0e640 100644
--- a/drivers/net/dsa/Makefile
+++ b/drivers/net/dsa/Makefile
@@ -15,11 +15,11 @@ obj-$(CONFIG_NET_DSA_SMSC_LAN9303_MDIO) += lan9303_mdio.o
obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX) += vitesse-vsc73xx-core.o
obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX_PLATFORM) += vitesse-vsc73xx-platform.o
obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX_SPI) += vitesse-vsc73xx-spi.o
-obj-$(CONFIG_NET_DSA_YT921X) += yt921x.o
obj-y += b53/
obj-y += hirschmann/
obj-y += lantiq/
obj-y += microchip/
+obj-y += motorcomm/
obj-y += mv88e6xxx/
obj-y += mxl862xx/
obj-y += netc/
diff --git a/drivers/net/dsa/motorcomm/Kconfig b/drivers/net/dsa/motorcomm/Kconfig
new file mode 100644
index 000000000000..64ff7d07a91b
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/Kconfig
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config NET_DSA_YT921X
+ tristate "Motorcomm YT9215 ethernet switch chip support"
+ select NET_DSA_TAG_YT921X
+ select NET_IEEE8021Q_HELPERS if DCB
+ help
+ This enables support for the Motorcomm YT9215 ethernet switch
+ chip.
diff --git a/drivers/net/dsa/motorcomm/Makefile b/drivers/net/dsa/motorcomm/Makefile
new file mode 100644
index 000000000000..bf99feb4c454
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_NET_DSA_YT921X) += yt921x.o
+yt921x-objs := chip.o
diff --git a/drivers/net/dsa/yt921x.c b/drivers/net/dsa/motorcomm/chip.c
similarity index 99%
rename from drivers/net/dsa/yt921x.c
rename to drivers/net/dsa/motorcomm/chip.c
index 44fcdd47ffb5..019b3a2d5d97 100644
--- a/drivers/net/dsa/yt921x.c
+++ b/drivers/net/dsa/motorcomm/chip.c
@@ -26,7 +26,7 @@
#include <net/ieee8021q.h>
#include <net/pkt_cls.h>
-#include "yt921x.h"
+#include "chip.h"
struct yt921x_mib_desc {
unsigned int size;
diff --git a/drivers/net/dsa/yt921x.h b/drivers/net/dsa/motorcomm/chip.h
similarity index 100%
rename from drivers/net/dsa/yt921x.h
rename to drivers/net/dsa/motorcomm/chip.h
--
2.53.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH net-next v11 3/5] net: dsa: motorcomm: Split SMI module
2026-08-31 4:38 [PATCH net-next v11 0/5] net: dsa: motorcomm: Add LED support David Yang
2026-08-31 4:38 ` [PATCH net-next v11 1/5] net: dsa: yt921x: Check lock status with lockdep_assert_held_once() David Yang
2026-08-31 4:38 ` [PATCH net-next v11 2/5] net: dsa: motorcomm: Move to subdirectory David Yang
@ 2026-08-31 4:38 ` David Yang
2026-08-31 4:38 ` [PATCH net-next v11 4/5] net: dsa: motorcomm: Add LED support David Yang
2026-08-31 4:38 ` [PATCH net-next v11 5/5] dt-bindings: net: dsa: yt921x: Add LEDs definition example David Yang
4 siblings, 0 replies; 12+ messages in thread
From: David Yang @ 2026-08-31 4:38 UTC (permalink / raw)
To: netdev
Cc: David Yang, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, devicetree, linux-kernel
SMI operations are going to be used across different modules. Minor
changes are applied to fix build errors.
Signed-off-by: David Yang <mmyangfl@gmail.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
---
drivers/net/dsa/motorcomm/Makefile | 1 +
drivers/net/dsa/motorcomm/chip.c | 207 +----------------------------
drivers/net/dsa/motorcomm/smi.c | 180 +++++++++++++++++++++++++
drivers/net/dsa/motorcomm/smi.h | 61 +++++++++
4 files changed, 243 insertions(+), 206 deletions(-)
create mode 100644 drivers/net/dsa/motorcomm/smi.c
create mode 100644 drivers/net/dsa/motorcomm/smi.h
diff --git a/drivers/net/dsa/motorcomm/Makefile b/drivers/net/dsa/motorcomm/Makefile
index bf99feb4c454..9fa24929007c 100644
--- a/drivers/net/dsa/motorcomm/Makefile
+++ b/drivers/net/dsa/motorcomm/Makefile
@@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_NET_DSA_YT921X) += yt921x.o
yt921x-objs := chip.o
+yt921x-objs += smi.o
diff --git a/drivers/net/dsa/motorcomm/chip.c b/drivers/net/dsa/motorcomm/chip.c
index 019b3a2d5d97..6dee25b6754a 100644
--- a/drivers/net/dsa/motorcomm/chip.c
+++ b/drivers/net/dsa/motorcomm/chip.c
@@ -13,7 +13,6 @@
#include <linux/if_bridge.h>
#include <linux/if_hsr.h>
#include <linux/if_vlan.h>
-#include <linux/iopoll.h>
#include <linux/mdio.h>
#include <linux/module.h>
#include <linux/of.h>
@@ -27,6 +26,7 @@
#include <net/pkt_cls.h>
#include "chip.h"
+#include "smi.h"
struct yt921x_mib_desc {
unsigned int size;
@@ -155,9 +155,6 @@ static const struct yt921x_info yt921x_infos[] = {
#define YT921X_VID_UNWARE 4095
-#define YT921X_POLL_SLEEP_US 10000
-#define YT921X_POLL_TIMEOUT_US 100000
-
/* The interval should be small enough to avoid overflow of 32bit MIBs.
*
* Until we can read MIBs from stats64 call directly (i.e. sleep
@@ -196,208 +193,6 @@ static u32 ethaddr_lo2_to_u32(const unsigned char *addr)
return (addr[4] << 8) | addr[5];
}
-static int yt921x_reg_read(struct yt921x_priv *priv, u32 reg, u32 *valp)
-{
- lockdep_assert_held_once(&priv->reg_lock);
-
- return priv->reg_ops->read(priv->reg_ctx, reg, valp);
-}
-
-static int yt921x_reg_write(struct yt921x_priv *priv, u32 reg, u32 val)
-{
- lockdep_assert_held_once(&priv->reg_lock);
-
- return priv->reg_ops->write(priv->reg_ctx, reg, val);
-}
-
-static int
-yt921x_reg_wait(struct yt921x_priv *priv, u32 reg, u32 mask, u32 *valp)
-{
- u32 val;
- int res;
- int ret;
-
- ret = read_poll_timeout(yt921x_reg_read, res,
- res || (val & mask) == *valp,
- YT921X_POLL_SLEEP_US, YT921X_POLL_TIMEOUT_US,
- false, priv, reg, &val);
- if (ret)
- return ret;
- if (res)
- return res;
-
- *valp = val;
- return 0;
-}
-
-static int
-yt921x_reg_update_bits(struct yt921x_priv *priv, u32 reg, u32 mask, u32 val)
-{
- int res;
- u32 v;
- u32 u;
-
- res = yt921x_reg_read(priv, reg, &v);
- if (res)
- return res;
-
- u = v;
- u &= ~mask;
- u |= val;
- if (u == v)
- return 0;
-
- return yt921x_reg_write(priv, reg, u);
-}
-
-static int yt921x_reg_set_bits(struct yt921x_priv *priv, u32 reg, u32 mask)
-{
- return yt921x_reg_update_bits(priv, reg, 0, mask);
-}
-
-static int yt921x_reg_clear_bits(struct yt921x_priv *priv, u32 reg, u32 mask)
-{
- return yt921x_reg_update_bits(priv, reg, mask, 0);
-}
-
-static int
-yt921x_reg_toggle_bits(struct yt921x_priv *priv, u32 reg, u32 mask, bool set)
-{
- return yt921x_reg_update_bits(priv, reg, mask, !set ? 0 : mask);
-}
-
-/* Some multi-word registers, like VLANn_CTRL, should be treated as a single
- * long register. More specifically, writes to parts of its words won't become
- * visible, until the last word is written.
- *
- * Here we require full read and write operations over these registers to
- * eliminate potential issues, although partial reads/writes are also possible.
- */
-
-static void update_ctrls_unaligned(u32 *lo, u32 *hi, u64 mask, u64 val)
-{
- *lo &= ~lower_32_bits(mask);
- *hi &= ~upper_32_bits(mask);
- *lo |= lower_32_bits(val);
- *hi |= upper_32_bits(val);
-}
-
-static int
-yt921x_regs_read(struct yt921x_priv *priv, u32 reg, u32 *vals,
- unsigned int num_regs)
-{
- int res;
-
- for (unsigned int i = 0; i < num_regs; i++) {
- res = yt921x_reg_read(priv, reg + 4 * i, &vals[i]);
- if (res)
- return res;
- }
-
- return 0;
-}
-
-static int
-yt921x_regs_write(struct yt921x_priv *priv, u32 reg, const u32 *vals,
- unsigned int num_regs)
-{
- int res;
-
- for (unsigned int i = 0; i < num_regs; i++) {
- res = yt921x_reg_write(priv, reg + 4 * i, vals[i]);
- if (res)
- return res;
- }
-
- return 0;
-}
-
-static int
-yt921x_regs_update_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
- const u32 *vals, unsigned int num_regs)
-{
- bool changed = false;
- u32 vs[4];
- int res;
-
- BUILD_BUG_ON(num_regs > ARRAY_SIZE(vs));
-
- res = yt921x_regs_read(priv, reg, vs, num_regs);
- if (res)
- return res;
-
- for (unsigned int i = 0; i < num_regs; i++) {
- u32 u = vs[i];
-
- u &= ~masks[i];
- u |= vals[i];
- if (u != vs[i])
- changed = true;
-
- vs[i] = u;
- }
-
- if (!changed)
- return 0;
-
- return yt921x_regs_write(priv, reg, vs, num_regs);
-}
-
-static int
-yt921x_regs_clear_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
- unsigned int num_regs)
-{
- bool changed = false;
- u32 vs[4];
- int res;
-
- BUILD_BUG_ON(num_regs > ARRAY_SIZE(vs));
-
- res = yt921x_regs_read(priv, reg, vs, num_regs);
- if (res)
- return res;
-
- for (unsigned int i = 0; i < num_regs; i++) {
- u32 u = vs[i];
-
- u &= ~masks[i];
- if (u != vs[i])
- changed = true;
-
- vs[i] = u;
- }
-
- if (!changed)
- return 0;
-
- return yt921x_regs_write(priv, reg, vs, num_regs);
-}
-
-static int
-yt921x_reg64_write(struct yt921x_priv *priv, u32 reg, const u32 *vals)
-{
- return yt921x_regs_write(priv, reg, vals, 2);
-}
-
-static int
-yt921x_reg64_update_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
- const u32 *vals)
-{
- return yt921x_regs_update_bits(priv, reg, masks, vals, 2);
-}
-
-static int
-yt921x_reg64_clear_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks)
-{
- return yt921x_regs_clear_bits(priv, reg, masks, 2);
-}
-
-static int
-yt921x_reg96_write(struct yt921x_priv *priv, u32 reg, const u32 *vals)
-{
- return yt921x_regs_write(priv, reg, vals, 3);
-}
-
static int yt921x_reg_mdio_read(void *context, u32 reg, u32 *valp)
{
struct yt921x_reg_mdio *mdio = context;
diff --git a/drivers/net/dsa/motorcomm/smi.c b/drivers/net/dsa/motorcomm/smi.c
new file mode 100644
index 000000000000..bf3adfd64165
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/smi.c
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 David Yang
+ */
+
+#include <linux/iopoll.h>
+
+#include "chip.h"
+#include "smi.h"
+
+#define YT921X_POLL_SLEEP_US 10000
+#define YT921X_POLL_TIMEOUT_US 100000
+
+int yt921x_reg_read(struct yt921x_priv *priv, u32 reg, u32 *valp)
+{
+ lockdep_assert_held_once(&priv->reg_lock);
+
+ return priv->reg_ops->read(priv->reg_ctx, reg, valp);
+}
+
+int yt921x_reg_write(struct yt921x_priv *priv, u32 reg, u32 val)
+{
+ lockdep_assert_held_once(&priv->reg_lock);
+
+ return priv->reg_ops->write(priv->reg_ctx, reg, val);
+}
+
+int yt921x_reg_wait(struct yt921x_priv *priv, u32 reg, u32 mask, u32 *valp)
+{
+ u32 val;
+ int res;
+ int ret;
+
+ ret = read_poll_timeout(yt921x_reg_read, res,
+ res || (val & mask) == *valp,
+ YT921X_POLL_SLEEP_US, YT921X_POLL_TIMEOUT_US,
+ false, priv, reg, &val);
+ if (ret)
+ return ret;
+ if (res)
+ return res;
+
+ *valp = val;
+ return 0;
+}
+
+int yt921x_reg_update_bits(struct yt921x_priv *priv, u32 reg, u32 mask, u32 val)
+{
+ int res;
+ u32 v;
+ u32 u;
+
+ res = yt921x_reg_read(priv, reg, &v);
+ if (res)
+ return res;
+
+ u = v;
+ u &= ~mask;
+ u |= val;
+ if (u == v)
+ return 0;
+
+ return yt921x_reg_write(priv, reg, u);
+}
+
+static int
+yt921x_regs_read(struct yt921x_priv *priv, u32 reg, u32 *vals,
+ unsigned int num_regs)
+{
+ int res;
+
+ for (unsigned int i = 0; i < num_regs; i++) {
+ res = yt921x_reg_read(priv, reg + 4 * i, &vals[i]);
+ if (res)
+ return res;
+ }
+
+ return 0;
+}
+
+static int
+yt921x_regs_write(struct yt921x_priv *priv, u32 reg, const u32 *vals,
+ unsigned int num_regs)
+{
+ int res;
+
+ for (unsigned int i = 0; i < num_regs; i++) {
+ res = yt921x_reg_write(priv, reg + 4 * i, vals[i]);
+ if (res)
+ return res;
+ }
+
+ return 0;
+}
+
+static int
+yt921x_regs_update_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
+ const u32 *vals, unsigned int num_regs)
+{
+ bool changed = false;
+ u32 vs[4];
+ int res;
+
+ BUILD_BUG_ON(num_regs > ARRAY_SIZE(vs));
+
+ res = yt921x_regs_read(priv, reg, vs, num_regs);
+ if (res)
+ return res;
+
+ for (unsigned int i = 0; i < num_regs; i++) {
+ u32 u = vs[i];
+
+ u &= ~masks[i];
+ u |= vals[i];
+ if (u != vs[i])
+ changed = true;
+
+ vs[i] = u;
+ }
+
+ if (!changed)
+ return 0;
+
+ return yt921x_regs_write(priv, reg, vs, num_regs);
+}
+
+static int
+yt921x_regs_clear_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
+ unsigned int num_regs)
+{
+ bool changed = false;
+ u32 vs[4];
+ int res;
+
+ BUILD_BUG_ON(num_regs > ARRAY_SIZE(vs));
+
+ res = yt921x_regs_read(priv, reg, vs, num_regs);
+ if (res)
+ return res;
+
+ for (unsigned int i = 0; i < num_regs; i++) {
+ u32 u = vs[i];
+
+ u &= ~masks[i];
+ if (u != vs[i])
+ changed = true;
+
+ vs[i] = u;
+ }
+
+ if (!changed)
+ return 0;
+
+ return yt921x_regs_write(priv, reg, vs, num_regs);
+}
+
+int
+yt921x_reg64_write(struct yt921x_priv *priv, u32 reg, const u32 *vals)
+{
+ return yt921x_regs_write(priv, reg, vals, 2);
+}
+
+int
+yt921x_reg64_update_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks,
+ const u32 *vals)
+{
+ return yt921x_regs_update_bits(priv, reg, masks, vals, 2);
+}
+
+int
+yt921x_reg64_clear_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks)
+{
+ return yt921x_regs_clear_bits(priv, reg, masks, 2);
+}
+
+int
+yt921x_reg96_write(struct yt921x_priv *priv, u32 reg, const u32 *vals)
+{
+ return yt921x_regs_write(priv, reg, vals, 3);
+}
diff --git a/drivers/net/dsa/motorcomm/smi.h b/drivers/net/dsa/motorcomm/smi.h
new file mode 100644
index 000000000000..212e20f71d80
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/smi.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2026 David Yang
+ */
+
+#ifndef _YT_SMI_H
+#define _YT_SMI_H
+
+#include <linux/types.h>
+#include <linux/wordpart.h>
+
+struct yt921x_priv;
+
+int yt921x_reg_read(struct yt921x_priv *priv, u32 reg, u32 *valp);
+int yt921x_reg_write(struct yt921x_priv *priv, u32 reg, u32 val);
+int yt921x_reg_wait(struct yt921x_priv *priv, u32 reg, u32 mask, u32 *valp);
+int yt921x_reg_update_bits(struct yt921x_priv *priv, u32 reg, u32 mask,
+ u32 val);
+
+static inline int
+yt921x_reg_set_bits(struct yt921x_priv *priv, u32 reg, u32 mask)
+{
+ return yt921x_reg_update_bits(priv, reg, 0, mask);
+}
+
+static inline int
+yt921x_reg_clear_bits(struct yt921x_priv *priv, u32 reg, u32 mask)
+{
+ return yt921x_reg_update_bits(priv, reg, mask, 0);
+}
+
+static inline int
+yt921x_reg_toggle_bits(struct yt921x_priv *priv, u32 reg, u32 mask, bool set)
+{
+ return yt921x_reg_update_bits(priv, reg, mask, !set ? 0 : mask);
+}
+
+/* Some multi-word registers, like VLANn_CTRL, should be treated as a single
+ * long register. More specifically, writes to parts of its words won't become
+ * visible, until the last word is written.
+ *
+ * Here we require full read and write operations over these registers to
+ * eliminate potential issues, although partial reads/writes are also possible.
+ */
+
+static inline void update_ctrls_unaligned(u32 *lo, u32 *hi, u64 mask, u64 val)
+{
+ *lo &= ~lower_32_bits(mask);
+ *hi &= ~upper_32_bits(mask);
+ *lo |= lower_32_bits(val);
+ *hi |= upper_32_bits(val);
+}
+
+int yt921x_reg64_write(struct yt921x_priv *priv, u32 reg, const u32 *vals);
+int yt921x_reg64_update_bits(struct yt921x_priv *priv, u32 reg,
+ const u32 *masks, const u32 *vals);
+int yt921x_reg64_clear_bits(struct yt921x_priv *priv, u32 reg,
+ const u32 *masks);
+int yt921x_reg96_write(struct yt921x_priv *priv, u32 reg, const u32 *vals);
+
+#endif
--
2.53.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH net-next v11 4/5] net: dsa: motorcomm: Add LED support
2026-08-31 4:38 [PATCH net-next v11 0/5] net: dsa: motorcomm: Add LED support David Yang
` (2 preceding siblings ...)
2026-08-31 4:38 ` [PATCH net-next v11 3/5] net: dsa: motorcomm: Split SMI module David Yang
@ 2026-08-31 4:38 ` David Yang
2026-09-01 4:49 ` sashiko-bot
2026-09-02 0:56 ` Jakub Kicinski
2026-08-31 4:38 ` [PATCH net-next v11 5/5] dt-bindings: net: dsa: yt921x: Add LEDs definition example David Yang
4 siblings, 2 replies; 12+ messages in thread
From: David Yang @ 2026-08-31 4:38 UTC (permalink / raw)
To: netdev
Cc: David Yang, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, devicetree, linux-kernel
Each port has at most 3 LEDs connected and can be declared using the
standard LEDs structure.
Currently, only parallel mode and strict 1:1 mapping (i.e. the HW
default) are supported.
Signed-off-by: David Yang <mmyangfl@gmail.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
---
drivers/net/dsa/motorcomm/Kconfig | 9 +
drivers/net/dsa/motorcomm/Makefile | 1 +
drivers/net/dsa/motorcomm/chip.c | 19 +-
drivers/net/dsa/motorcomm/chip.h | 15 +
drivers/net/dsa/motorcomm/leds.c | 641 +++++++++++++++++++++++++++++
drivers/net/dsa/motorcomm/leds.h | 118 ++++++
6 files changed, 801 insertions(+), 2 deletions(-)
create mode 100644 drivers/net/dsa/motorcomm/leds.c
create mode 100644 drivers/net/dsa/motorcomm/leds.h
diff --git a/drivers/net/dsa/motorcomm/Kconfig b/drivers/net/dsa/motorcomm/Kconfig
index 64ff7d07a91b..79cdd79a1fd2 100644
--- a/drivers/net/dsa/motorcomm/Kconfig
+++ b/drivers/net/dsa/motorcomm/Kconfig
@@ -6,3 +6,12 @@ config NET_DSA_YT921X
help
This enables support for the Motorcomm YT9215 ethernet switch
chip.
+
+config NET_DSA_YT921X_LEDS
+ bool "LED support for Motorcomm YT9215"
+ default y
+ depends on NET_DSA_YT921X
+ depends on LEDS_CLASS=y || LEDS_CLASS=NET_DSA_YT921X
+ help
+ This enables support for controlling the LEDs attached to the
+ Motorcomm YT9215 switch chips.
diff --git a/drivers/net/dsa/motorcomm/Makefile b/drivers/net/dsa/motorcomm/Makefile
index 9fa24929007c..aeb12cb91f93 100644
--- a/drivers/net/dsa/motorcomm/Makefile
+++ b/drivers/net/dsa/motorcomm/Makefile
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_NET_DSA_YT921X) += yt921x.o
yt921x-objs := chip.o
+yt921x-$(CONFIG_NET_DSA_YT921X_LEDS) += leds.o
yt921x-objs += smi.o
diff --git a/drivers/net/dsa/motorcomm/chip.c b/drivers/net/dsa/motorcomm/chip.c
index 6dee25b6754a..d663af010f43 100644
--- a/drivers/net/dsa/motorcomm/chip.c
+++ b/drivers/net/dsa/motorcomm/chip.c
@@ -26,6 +26,7 @@
#include <net/pkt_cls.h>
#include "chip.h"
+#include "leds.h"
#include "smi.h"
struct yt921x_mib_desc {
@@ -151,8 +152,6 @@ static const struct yt921x_info yt921x_infos[] = {
{}
};
-#define YT921X_NAME "yt921x"
-
#define YT921X_VID_UNWARE 4095
/* The interval should be small enough to avoid overflow of 32bit MIBs.
@@ -4225,6 +4224,15 @@ static int yt921x_edata_read(struct yt921x_priv *priv, u8 addr, u8 *valp)
return yt921x_edata_read_cont(priv, addr, valp);
}
+static void yt921x_dsa_teardown(struct dsa_switch *ds)
+{
+ struct yt921x_priv *priv = to_yt921x_priv(ds);
+
+#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)
+ yt921x_leds_remove(priv);
+#endif
+}
+
static int yt921x_chip_detect(struct yt921x_priv *priv)
{
struct device *dev = to_device(priv);
@@ -4581,6 +4589,12 @@ static int yt921x_dsa_setup(struct dsa_switch *ds)
if (res)
return res;
+#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)
+ res = yt921x_leds_setup(priv);
+ if (res)
+ dev_warn(dev, "Failed to setup LEDs: %d\n", res);
+#endif
+
return 0;
}
@@ -4661,6 +4675,7 @@ static const struct dsa_switch_ops yt921x_dsa_switch_ops = {
.port_add_dscp_prio = yt921x_dsa_port_add_dscp_prio,
#endif
/* chip */
+ .teardown = yt921x_dsa_teardown,
.setup = yt921x_dsa_setup,
};
diff --git a/drivers/net/dsa/motorcomm/chip.h b/drivers/net/dsa/motorcomm/chip.h
index 5f3b99e189c4..83cd454955dd 100644
--- a/drivers/net/dsa/motorcomm/chip.h
+++ b/drivers/net/dsa/motorcomm/chip.h
@@ -850,9 +850,14 @@ enum yt921x_fdb_entry_status {
#define YT921X_ACL_NUM (YT921X_ACL_BLK_NUM * YT921X_ACL_ENT_PER_BLK)
#define YT921X_UDF_NUM 8
+#define YT921X_LED_GROUP_NUM 3
+#define YT921X_LED_PORT_NUM 10
+
/* 8 internal + 2 external + 1 mcu */
#define YT921X_PORT_NUM 11
+#define YT921X_NAME "yt921x"
+
#define yt921x_port_is_internal(port) ((port) < 8)
#define yt921x_port_is_external(port) ((port) == 8 || (port) == 9)
@@ -938,6 +943,16 @@ struct yt921x_port {
struct yt921x_mib mib;
u64 rx_frames;
u64 tx_frames;
+
+#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)
+ unsigned char led_duty;
+ unsigned short led_cycle;
+
+ unsigned char led_duty_mask;
+ unsigned char led_cycle_mask;
+
+ struct yt921x_led *leds[YT921X_LED_GROUP_NUM];
+#endif
};
struct yt921x_reg_ops {
diff --git a/drivers/net/dsa/motorcomm/leds.c b/drivers/net/dsa/motorcomm/leds.c
new file mode 100644
index 000000000000..7d3c0f68d8ff
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/leds.c
@@ -0,0 +1,641 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 David Yang
+ */
+
+#include <linux/uleds.h>
+
+#include "chip.h"
+#include "leds.h"
+#include "smi.h"
+
+#define to_yt921x_led(led_cdev) \
+ container_of_const((led_cdev), struct yt921x_led, cdev)
+#define to_yt921x_port(led) ((led)->port)
+#define to_yt921x_priv(pp) \
+ container_of_const((pp), struct yt921x_priv, ports[(pp)->index])
+#define to_device(priv) ((priv)->ds.dev)
+
+static u32 yt921x_led_regaddr(struct yt921x_priv *priv, int port, int group)
+{
+ switch (group) {
+ case 0:
+ default:
+ return YT921X_LED0_PORTn(port);
+ case 1:
+ return YT921X_LED1_PORTn(port);
+ case 2:
+ return YT921X_LED2_PORTn(port);
+ }
+}
+
+static int
+yt921x_led_force_set(struct yt921x_priv *priv, int port, int group, bool on)
+{
+ struct yt921x_port *pp = &priv->ports[port];
+ u32 ctrl;
+ u32 mask;
+
+ /* No reversion - LED is sacrificed in case of IO error */
+ pp->led_duty_mask &= ~BIT(group);
+ pp->led_cycle_mask &= ~BIT(group);
+
+ mask = YT921X_LED2_PORT_FORCEn_M(group);
+ ctrl = on ? YT921X_LED2_PORT_FORCEn_ON(group) :
+ YT921X_LED2_PORT_FORCEn_OFF(group);
+ return yt921x_reg_update_bits(priv, YT921X_LED2_PORTn(port), mask,
+ ctrl);
+}
+
+static int
+yt921x_led_blink_select(const struct yt921x_priv *priv, unsigned long on,
+ unsigned long off, unsigned short *cyclep,
+ unsigned char *dutyp)
+{
+ static const unsigned char dutys[] = {
+ YT921X_LED_DUTY(1, 6),
+ YT921X_LED_DUTY(1, 4),
+ YT921X_LED_DUTY(1, 3),
+ YT921X_LED_DUTY(1, 2),
+ };
+ unsigned int cycle_upper;
+ unsigned int cycle_req;
+ unsigned int duty_req;
+ unsigned int cycle;
+ unsigned int duty;
+
+ cycle = YT921X_LED_BLINK_MAX;
+ cycle_upper = cycle * 11585 / 8192 + 1; /* M_SQRT2 * cycle */
+ if (check_add_overflow(on, off, &cycle_req) || cycle_req >= cycle_upper)
+ return -EOPNOTSUPP;
+
+ for (; cycle > YT921X_LED_BLINK_MIN; cycle_upper >>= 1, cycle >>= 1)
+ if (cycle_req >= cycle_upper >> 1)
+ break;
+ *cyclep = cycle;
+
+ duty_req = DIV_ROUND_CLOSEST(YT921X_LED_DUTY_DENOM *
+ (on > off ? off : on), cycle_req);
+ for (unsigned int i = ARRAY_SIZE(dutys) - 1;; i--)
+ if (i == 0 || duty_req >= (dutys[i - 1] + dutys[i]) / 2) {
+ duty = dutys[i];
+ break;
+ }
+ if (on > off)
+ duty = YT921X_LED_DUTY_DENOM - duty;
+ *dutyp = duty;
+
+ return 0;
+}
+
+static int
+yt921x_led_blink_set(struct yt921x_priv *priv, int port, int group,
+ unsigned long *onp, unsigned long *offp)
+{
+ struct yt921x_port *pp = &priv->ports[port];
+ unsigned short cycle;
+ unsigned char duty;
+ bool use_cycle;
+ u32 ctrl;
+ u32 mask;
+ u32 val;
+ int res;
+
+ if (!*onp && !*offp) {
+ cycle = YT921X_LED_BLINK_DEF;
+ duty = (pp->led_duty_mask & ~BIT(group)) ? pp->led_duty :
+ YT921X_LED_DUTY(1, 2);
+
+ use_cycle = false;
+ } else {
+ res = yt921x_led_blink_select(priv, *onp, *offp, &cycle, &duty);
+ if (res)
+ return res;
+
+ use_cycle = cycle < YT921X_LED_BLINK_DEF;
+
+ if (use_cycle && cycle != pp->led_cycle &&
+ (pp->led_cycle_mask & ~BIT(group)))
+ return -EOPNOTSUPP;
+ if (duty != pp->led_duty && (pp->led_duty_mask & ~BIT(group)))
+ return -EOPNOTSUPP;
+ }
+
+ pp->led_cycle_mask &= ~BIT(group);
+ pp->led_duty_mask &= ~BIT(group);
+
+ /* The chip seems to jam a while if changing duty directly */
+ res = yt921x_reg_read(priv, YT921X_LED2_PORTn(port), &val);
+ if (res)
+ return res;
+
+ ctrl = val & ~YT921X_LED2_PORT_FORCEn_M(group);
+ ctrl |= YT921X_LED2_PORT_FORCEn_OFF(group);
+ if (val != ctrl) {
+ res = yt921x_reg_write(priv, YT921X_LED2_PORTn(port), ctrl);
+ if (res)
+ return res;
+ }
+
+ mask = YT921X_LED1_PORT_BLINK_DUTY_M | YT921X_LED1_PORT_BLINK_DUTY_COMP;
+ switch (duty >= YT921X_LED_DUTY(1, 2) ? duty :
+ YT921X_LED_DUTY_DENOM - duty) {
+ default:
+ duty = YT921X_LED_DUTY(1, 2);
+ fallthrough;
+ case YT921X_LED_DUTY(1, 2):
+ ctrl = YT921X_LED1_PORT_BLINK_DUTY_1_2;
+ break;
+ case YT921X_LED_DUTY(2, 3):
+ ctrl = YT921X_LED1_PORT_BLINK_DUTY_2_3;
+ break;
+ case YT921X_LED_DUTY(3, 4):
+ ctrl = YT921X_LED1_PORT_BLINK_DUTY_3_4;
+ break;
+ case YT921X_LED_DUTY(5, 6):
+ ctrl = YT921X_LED1_PORT_BLINK_DUTY_5_6;
+ break;
+ }
+ if (duty < YT921X_LED_DUTY(1, 2))
+ ctrl |= YT921X_LED1_PORT_BLINK_DUTY_COMP;
+ if (use_cycle) {
+ mask |= YT921X_LED1_PORT_OTHER_BLINK_M;
+ ctrl |= YT921X_LED1_PORT_OTHER_BLINK(9 - __fls(cycle));
+ }
+ res = yt921x_reg_update_bits(priv, YT921X_LED1_PORTn(port), mask, ctrl);
+ if (res)
+ return res;
+
+ ctrl = val & ~(YT921X_LED2_PORT_FORCEn_M(group) |
+ YT921X_LED2_PORT_FORCE_BLINKn_M(group));
+ ctrl |= YT921X_LED2_PORT_FORCEn_BLINK(group);
+ if (use_cycle)
+ ctrl |= YT921X_LED2_PORT_FORCE_BLINKn_OTHER(group);
+ else
+ ctrl |= YT921X_LED2_PORT_FORCE_BLINKn(group, __fls(cycle) - 9);
+ res = yt921x_reg_write(priv, YT921X_LED2_PORTn(port), ctrl);
+ if (res)
+ return res;
+
+ if (use_cycle) {
+ pp->led_cycle_mask |= BIT(group);
+ pp->led_cycle = cycle;
+ }
+ pp->led_duty_mask |= BIT(group);
+ pp->led_duty = duty;
+
+ *onp = DIV_ROUND_CLOSEST(duty * cycle, YT921X_LED_DUTY_DENOM);
+ *offp = cycle - *onp;
+ return 0;
+}
+
+struct yt921x_led_trigger_map {
+ unsigned long flags;
+ u32 mask;
+};
+
+static const struct yt921x_led_trigger_map yt921x_led_trigger_maps[] = {
+ {BIT(TRIGGER_NETDEV_LINK),
+ YT921X_LEDx_PORT_ACT_DUPLEX_HALF | YT921X_LEDx_PORT_ACT_DUPLEX_FULL},
+ {BIT(TRIGGER_NETDEV_LINK_10), YT921X_LEDx_PORT_ACT_10M},
+ {BIT(TRIGGER_NETDEV_LINK_100), YT921X_LEDx_PORT_ACT_100M},
+ {BIT(TRIGGER_NETDEV_LINK_1000), YT921X_LEDx_PORT_ACT_1000M},
+ {BIT(TRIGGER_NETDEV_HALF_DUPLEX), YT921X_LEDx_PORT_ACT_DUPLEX_HALF},
+ {BIT(TRIGGER_NETDEV_FULL_DUPLEX), YT921X_LEDx_PORT_ACT_DUPLEX_FULL},
+ {BIT(TRIGGER_NETDEV_TX), YT921X_LEDx_PORT_ACT_TX_BLINK},
+ {BIT(TRIGGER_NETDEV_RX), YT921X_LEDx_PORT_ACT_RX_BLINK},
+ {BIT(TRIGGER_NETDEV_TX_ERR) | BIT(TRIGGER_NETDEV_RX_ERR),
+ YT921X_LEDx_PORT_ACT_COLLISION_BLINK},
+};
+
+static bool
+yt921x_led_trigger_is_supported(const struct yt921x_priv *priv, int port,
+ int group, unsigned long flags)
+{
+ if (!flags)
+ return true;
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(yt921x_led_trigger_maps); i++) {
+ const struct yt921x_led_trigger_map *map = &yt921x_led_trigger_maps[i];
+
+ if ((flags & map->flags) == map->flags) {
+ flags &= ~map->flags;
+ if (!flags)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static int
+yt921x_led_trigger_get(struct yt921x_priv *priv, int port, int group,
+ unsigned long *flagsp)
+{
+ u32 addr;
+ u32 val;
+ int res;
+
+ res = yt921x_reg_read(priv, YT921X_LED2_PORTn(port), &val);
+ if (res)
+ return res;
+
+ if ((val & YT921X_LED2_PORT_FORCEn_M(group)) !=
+ YT921X_LED2_PORT_FORCEn_DONTCARE(group)) {
+ *flagsp = 0;
+ return 0;
+ }
+
+ if (group != 2) {
+ addr = yt921x_led_regaddr(priv, port, group);
+ res = yt921x_reg_read(priv, addr, &val);
+ if (res)
+ return res;
+ }
+
+ *flagsp = 0;
+ for (unsigned int i = 0; i < ARRAY_SIZE(yt921x_led_trigger_maps); i++) {
+ const struct yt921x_led_trigger_map *map = &yt921x_led_trigger_maps[i];
+
+ if ((val & map->mask) == map->mask)
+ *flagsp |= map->flags;
+ }
+
+ return 0;
+}
+
+static int
+yt921x_led_trigger_set(struct yt921x_priv *priv, int port, int group,
+ unsigned long flags)
+{
+ struct yt921x_port *pp = &priv->ports[port];
+ u32 addr;
+ u32 ctrl;
+ u32 mask;
+ int res;
+
+ ctrl = 0;
+ for (unsigned int i = 0; i < ARRAY_SIZE(yt921x_led_trigger_maps); i++) {
+ const struct yt921x_led_trigger_map *map = &yt921x_led_trigger_maps[i];
+
+ if ((flags & map->flags) == map->flags) {
+ flags &= ~map->flags;
+ ctrl |= map->mask;
+ if (!flags)
+ break;
+ }
+ }
+ if (flags)
+ return -EOPNOTSUPP;
+
+ pp->led_duty_mask &= ~BIT(group);
+ pp->led_cycle_mask &= ~BIT(group);
+
+ mask = !group ? YT921X_LED0_PORT_ACT_M : YT921X_LEDx_PORT_ACT_M;
+ if (group == 2) {
+ mask |= YT921X_LED2_PORT_FORCEn_M(group);
+ ctrl |= YT921X_LED2_PORT_FORCEn_DONTCARE(group);
+ }
+ addr = yt921x_led_regaddr(priv, port, group);
+ res = yt921x_reg_update_bits(priv, addr, mask, ctrl);
+ if (res)
+ return res;
+
+ if (group != 2) {
+ mask = YT921X_LED2_PORT_FORCEn_M(group);
+ ctrl = YT921X_LED2_PORT_FORCEn_DONTCARE(group);
+ res = yt921x_reg_update_bits(priv, YT921X_LED2_PORTn(port),
+ mask, ctrl);
+ if (res)
+ return res;
+ }
+
+ return 0;
+}
+
+static int
+yt921x_cled_brightness_set_blocking(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct yt921x_led *led = to_yt921x_led(led_cdev);
+ struct yt921x_port *pp = to_yt921x_port(led);
+ struct yt921x_priv *priv = to_yt921x_priv(pp);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_led_force_set(priv, pp->index, led->group, brightness);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int
+yt921x_cled_blink_set(struct led_classdev *led_cdev, unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct yt921x_led *led = to_yt921x_led(led_cdev);
+ struct yt921x_port *pp = to_yt921x_port(led);
+ struct yt921x_priv *priv = to_yt921x_priv(pp);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_led_blink_set(priv, pp->index, led->group, delay_on,
+ delay_off);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static struct device * __maybe_unused
+yt921x_cled_hw_control_get_device(struct led_classdev *led_cdev)
+{
+ struct yt921x_led *led = to_yt921x_led(led_cdev);
+ struct yt921x_port *pp = to_yt921x_port(led);
+ struct yt921x_priv *priv = to_yt921x_priv(pp);
+ struct dsa_port *dp;
+
+ dp = dsa_to_port(&priv->ds, pp->index);
+ if (!dp)
+ return NULL;
+
+ if (dsa_port_is_user(dp))
+ return !dp->user ? NULL : &dp->user->dev;
+ if (dsa_port_is_cpu(dp))
+ return !dp->conduit ? NULL : &dp->conduit->dev;
+
+ return NULL;
+}
+
+static int __maybe_unused
+yt921x_cled_hw_control_is_supported(struct led_classdev *led_cdev,
+ unsigned long flags)
+{
+ struct yt921x_led *led = to_yt921x_led(led_cdev);
+ struct yt921x_port *pp = to_yt921x_port(led);
+ struct yt921x_priv *priv = to_yt921x_priv(pp);
+
+ if (yt921x_led_trigger_is_supported(priv, pp->index, led->group, flags))
+ return 0;
+ return -EOPNOTSUPP;
+}
+
+static int __maybe_unused
+yt921x_cled_hw_control_get(struct led_classdev *led_cdev, unsigned long *flagsp)
+{
+ struct yt921x_led *led = to_yt921x_led(led_cdev);
+ struct yt921x_port *pp = to_yt921x_port(led);
+ struct yt921x_priv *priv = to_yt921x_priv(pp);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_led_trigger_get(priv, pp->index, led->group, flagsp);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int __maybe_unused
+yt921x_cled_hw_control_set(struct led_classdev *led_cdev, unsigned long flags)
+{
+ struct yt921x_led *led = to_yt921x_led(led_cdev);
+ struct yt921x_port *pp = to_yt921x_port(led);
+ struct yt921x_priv *priv = to_yt921x_priv(pp);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_led_trigger_set(priv, pp->index, led->group, flags);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int
+yt921x_led_setup(struct yt921x_priv *priv, int port,
+ struct fwnode_handle *fwnode)
+{
+ struct yt921x_port *pp = &priv->ports[port];
+ struct device *dev = to_device(priv);
+ struct led_init_data init_data;
+ struct led_classdev *led_cdev;
+ char name[LED_MAX_NAME_SIZE];
+ enum led_default_state state;
+ struct yt921x_led *led;
+ bool force_high;
+ bool force_low;
+ u32 led2_val;
+ u32 inv_val;
+ u32 group;
+ u32 mask;
+ u32 ctrl;
+ bool on;
+ int res;
+ int ret;
+
+ res = fwnode_property_read_u32(fwnode, "reg", &group);
+ if (res) {
+ dev_err(dev, "Missing LED reg for %pfw\n", fwnode);
+ return res;
+ }
+ if (group >= YT921X_LED_GROUP_NUM) {
+ dev_err(dev, "Invalid LED reg %u for port %d\n", group, port);
+ return -EINVAL;
+ }
+ if (pp->leds[group]) {
+ res = -EEXIST;
+ goto err;
+ }
+
+ force_high = fwnode_property_read_bool(fwnode, "active-high");
+ force_low = fwnode_property_read_bool(fwnode, "active-low");
+ if (force_high && force_low) {
+ dev_err(dev, "Duplicate polarities for LED %02d:%02u\n",
+ port, group);
+ return -EINVAL;
+ }
+
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+ if (!led) {
+ res = -ENOMEM;
+ goto err;
+ }
+ pp->leds[group] = led;
+
+ led->port = pp;
+ led->group = group;
+
+ state = led_init_default_state_get(fwnode);
+
+ mutex_lock(&priv->reg_lock);
+
+ /* Inversion is internal - force on will give low logic.
+ * In the rest of the file, treat LEDs as if active-low.
+ */
+ inv_val = U32_MAX;
+ if (force_high || force_low) {
+ res = yt921x_reg_read(priv, YT921X_LED_PAR_INV, &inv_val);
+ if (res)
+ goto revoke_inv;
+
+ mask = YT921X_LED_PAR_INV_INVnm(group, port);
+ ctrl = force_high ? inv_val | mask : inv_val & ~mask;
+ res = yt921x_reg_write(priv, YT921X_LED_PAR_INV, ctrl);
+ if (res)
+ goto revoke_inv;
+ }
+
+ led2_val = U32_MAX;
+ res = yt921x_reg_read(priv, YT921X_LED2_PORTn(port), &led2_val);
+ if (res)
+ goto revoke_led2;
+ mask = YT921X_LED2_PORT_FORCEn_M(group);
+
+ switch (state) {
+ case LEDS_DEFSTATE_OFF:
+ case LEDS_DEFSTATE_ON:
+ default:
+ on = state == LEDS_DEFSTATE_ON;
+ ctrl = on ? YT921X_LED2_PORT_FORCEn_ON(group) :
+ YT921X_LED2_PORT_FORCEn_OFF(group);
+ res = yt921x_reg_write(priv, YT921X_LED2_PORTn(port),
+ (led2_val & ~mask) | ctrl);
+ if (res)
+ goto revoke_led2;
+ break;
+ case LEDS_DEFSTATE_KEEP:
+ on = (led2_val & mask) == YT921X_LED2_PORT_FORCEn_ON(group);
+ led2_val = U32_MAX;
+ break;
+ }
+
+ mutex_unlock(&priv->reg_lock);
+
+ led_cdev = &led->cdev;
+ led_cdev->brightness = on;
+ led_cdev->max_brightness = 1;
+ led_cdev->flags = LED_RETAIN_AT_SHUTDOWN;
+ led_cdev->brightness_set_blocking = yt921x_cled_brightness_set_blocking;
+ led_cdev->blink_set = yt921x_cled_blink_set;
+#ifdef CONFIG_LEDS_TRIGGERS
+ led_cdev->hw_control_trigger = "netdev";
+ led_cdev->hw_control_get_device = yt921x_cled_hw_control_get_device;
+ led_cdev->hw_control_is_supported = yt921x_cled_hw_control_is_supported;
+ led_cdev->hw_control_get = yt921x_cled_hw_control_get;
+ led_cdev->hw_control_set = yt921x_cled_hw_control_set;
+#endif
+
+ snprintf(name, sizeof(name), YT921X_NAME "-%u:%02d:%02u",
+ priv->ds.index, port, group);
+ init_data = (typeof(init_data)){
+ .fwnode = fwnode,
+ .default_label = ":port",
+ .devicename = name,
+ .devname_mandatory = true,
+ };
+ res = devm_led_classdev_register_ext(dev, led_cdev, &init_data);
+ if (res)
+ goto revoke;
+
+ return 0;
+
+revoke:
+ mutex_lock(&priv->reg_lock);
+revoke_led2:
+ if (led2_val != U32_MAX) {
+ ret = yt921x_reg_write(priv, YT921X_LED2_PORTn(port), led2_val);
+ if (ret)
+ dev_err(dev,
+ "Failed to restore %s for LED %02d:%02u: %d\n",
+ "LED2_PORT", port, group, ret);
+ }
+revoke_inv:
+ if (inv_val != U32_MAX) {
+ ret = yt921x_reg_write(priv, YT921X_LED_PAR_INV, inv_val);
+ if (ret)
+ dev_err(dev,
+ "Failed to restore %s for LED %02d:%02u: %d\n",
+ "LED_PAR_INV", port, group, ret);
+ }
+ mutex_unlock(&priv->reg_lock);
+
+ pp->leds[group] = NULL;
+ devm_kfree(dev, led);
+
+err:
+ dev_err(dev, "Failed to initialize LED %02d:%02u: %d\n",
+ port, group, res);
+ return res;
+}
+
+static void yt921x_leds_remove_port(struct yt921x_priv *priv, int port)
+{
+ struct yt921x_port *pp = &priv->ports[port];
+ struct device *dev = to_device(priv);
+
+ for (int group = 0; group < YT921X_LED_GROUP_NUM; group++) {
+ struct yt921x_led *led = pp->leds[group];
+
+ if (led) {
+ devm_led_classdev_unregister(dev, &led->cdev);
+ pp->leds[group] = NULL;
+ devm_kfree(dev, led);
+ }
+ }
+
+ pp->led_duty_mask = 0;
+ pp->led_cycle_mask = 0;
+}
+
+static int yt921x_leds_setup_port(struct yt921x_priv *priv, int port)
+{
+ struct device *dev = to_device(priv);
+ struct dsa_switch *ds = &priv->ds;
+ struct device_node *leds_np;
+ struct dsa_port *dp;
+
+ dp = dsa_to_port(ds, port);
+ leds_np = of_get_child_by_name(dp->dn, "leds");
+ if (!leds_np)
+ return 0;
+
+ if (port >= YT921X_LED_PORT_NUM) {
+ dev_err(dev, "Cannot configure LEDs for port %d\n", port);
+ of_node_put(leds_np);
+ return -EINVAL;
+ }
+
+ for_each_child_of_node_scoped(leds_np, led_np) {
+ yt921x_led_setup(priv, port, of_fwnode_handle(led_np));
+ /* Allow partial configuration: LEDs are optional */
+ }
+
+ of_node_put(leds_np);
+ return 0;
+}
+
+void yt921x_leds_remove(struct yt921x_priv *priv)
+{
+ for (int port = 0; port < YT921X_LED_PORT_NUM; port++)
+ yt921x_leds_remove_port(priv, port);
+}
+
+int yt921x_leds_setup(struct yt921x_priv *priv)
+{
+ struct dsa_switch *ds = &priv->ds;
+ struct dsa_port *dp;
+
+ /* LEDs are always enabled. There is no way to disable them altogether
+ * (as far as I know).
+ */
+
+ dsa_switch_for_each_port(dp, ds) {
+ int port = dp->index;
+
+ if (!dp->dn)
+ continue;
+
+ yt921x_leds_setup_port(priv, port);
+ /* Allow partial configuration: LEDs are optional */
+ }
+
+ return 0;
+}
diff --git a/drivers/net/dsa/motorcomm/leds.h b/drivers/net/dsa/motorcomm/leds.h
new file mode 100644
index 000000000000..11df479ee581
--- /dev/null
+++ b/drivers/net/dsa/motorcomm/leds.h
@@ -0,0 +1,118 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2026 David Yang
+ */
+
+#ifndef _YT_LEDS_H
+#define _YT_LEDS_H
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/kconfig.h>
+#include <linux/leds.h>
+
+#define YT921X_LED_CTRL 0xd0000
+#define YT921X_LED_CTRL_UNK BIT(21)
+#define YT921X_LED_CTRL_LOOPDETECT_BLINK_M GENMASK(20, 19) /* cycle = 512 * x ms */
+#define YT921X_LED_CTRL_LOOPDETECT_BLINK(x) FIELD_PREP(YT921X_LED_CTRL_LOOPDETECT_BLINK_M, (x))
+#define YT921X_LED_CTRL_PORT_NUM_M GENMASK(16, 13)
+#define YT921X_LED_CTRL_PORT_NUM(x) FIELD_PREP(YT921X_LED_CTRL_PORT_NUM_M, (x))
+#define YT921X_LED_CTRL_MODE_M GENMASK(1, 0)
+#define YT921X_LED_CTRL_MODE(x) FIELD_PREP(YT921X_LED_CTRL_MODE_M, (x))
+#define YT921X_LED_CTRL_MODE_PARALLEL YT921X_LED_CTRL_MODE(0)
+#define YT921X_LED_CTRL_MODE_SERIAL YT921X_LED_CTRL_MODE(2)
+#define YT921X_LED0_PORTn(port) (0xd0004 + 4 * (port))
+#define YT921X_LED0_PORT_ACT_M GENMASK(17, 0)
+#define YT921X_LED0_PORT_ACT_LINK_TRY_DIS BIT(17)
+#define YT921X_LED0_PORT_ACT_COLLISION_BLINK_INDI BIT(16)
+#define YT921X_LED1_PORTn(port) (0xd0040 + 4 * (port))
+#define YT921X_LED1_PORT_OTHER_BLINK_M GENMASK(31, 30) /* cycle = 512 >> x ms */
+#define YT921X_LED1_PORT_OTHER_BLINK(x) FIELD_PREP(YT921X_LED1_PORT_OTHER_BLINK_M, (x))
+#define YT921X_LED1_PORT_EEE_BLINK_M GENMASK(29, 28) /* cycle = 512 >> x ms */
+#define YT921X_LED1_PORT_EEE_BLINK(x) FIELD_PREP(YT921X_LED1_PORT_EEE_BLINK_M, (x))
+#define YT921X_LED1_PORT_BLINK_DUTY_COMP BIT(27)
+#define YT921X_LED1_PORT_BLINK_DUTY_M GENMASK(26, 25)
+#define YT921X_LED1_PORT_BLINK_DUTY(x) FIELD_PREP(YT921X_LED1_PORT_BLINK_DUTY_M, (x))
+#define YT921X_LED1_PORT_BLINK_DUTY_1_2 YT921X_LED1_PORT_BLINK_DUTY(0)
+#define YT921X_LED1_PORT_BLINK_DUTY_2_3 YT921X_LED1_PORT_BLINK_DUTY(1)
+#define YT921X_LED1_PORT_BLINK_DUTY_3_4 YT921X_LED1_PORT_BLINK_DUTY(2)
+#define YT921X_LED1_PORT_BLINK_DUTY_5_6 YT921X_LED1_PORT_BLINK_DUTY(3)
+#define YT921X_LED2_PORTn(port) (0xd0080 + 4 * (port))
+#define YT921X_LED2_PORT_FORCEn_M(grp) GENMASK(4 * (grp) + 19, 4 * (grp) + 18)
+#define YT921X_LED2_PORT_FORCEn(grp, x) ((x) << (4 * (grp) + 18))
+#define YT921X_LED2_PORT_FORCEn_DONTCARE(grp) YT921X_LED2_PORT_FORCEn(grp, 0)
+#define YT921X_LED2_PORT_FORCEn_BLINK(grp) YT921X_LED2_PORT_FORCEn(grp, 1)
+#define YT921X_LED2_PORT_FORCEn_ON(grp) YT921X_LED2_PORT_FORCEn(grp, 2)
+#define YT921X_LED2_PORT_FORCEn_OFF(grp) YT921X_LED2_PORT_FORCEn(grp, 3)
+#define YT921X_LED2_PORT_FORCE_BLINKn_M(grp) GENMASK(4 * (grp) + 17, 4 * (grp) + 16) /* cycle = 512 << x ms */
+#define YT921X_LED2_PORT_FORCE_BLINKn(grp, x) ((x) << (4 * (grp) + 16))
+#define YT921X_LED2_PORT_FORCE_BLINKn_OTHER(grp) YT921X_LED2_PORT_FORCE_BLINKn(grp, 3)
+#define YT921X_LEDx_PORT_ACT_M GENMASK(15, 0)
+#define YT921X_LEDx_PORT_ACT_EEE_BLINK BIT(15)
+#define YT921X_LEDx_PORT_ACT_LOOPDETECT_BLINK BIT(14)
+#define YT921X_LEDx_PORT_ACT_ACTIVE_BLINK BIT(13)
+#define YT921X_LEDx_PORT_ACT_DUPLEX_FULL BIT(12)
+#define YT921X_LEDx_PORT_ACT_DUPLEX_HALF BIT(11)
+#define YT921X_LEDx_PORT_ACT_TX_BLINK BIT(10)
+#define YT921X_LEDx_PORT_ACT_RX_BLINK BIT(9)
+#define YT921X_LEDx_PORT_ACT_TX BIT(8)
+#define YT921X_LEDx_PORT_ACT_RX BIT(7)
+#define YT921X_LEDx_PORT_ACT_1000M BIT(6)
+#define YT921X_LEDx_PORT_ACT_100M BIT(5)
+#define YT921X_LEDx_PORT_ACT_10M BIT(4)
+#define YT921X_LEDx_PORT_ACT_COLLISION_BLINK BIT(3)
+#define YT921X_LEDx_PORT_ACT_1000M_BLINK BIT(2)
+#define YT921X_LEDx_PORT_ACT_100M_BLINK BIT(1)
+#define YT921X_LEDx_PORT_ACT_10M_BLINK BIT(0)
+#define YT921X_LED_SER_CTRL 0xd0100
+#define YT921X_LED_SER_CTRL_UNK GENMASK(25, 24) /* delay? */
+#define YT921X_LED_SER_CTRL_ACTIVE_LOW BIT(4)
+#define YT921X_LED_SER_CTRL_GRP_NUM_M GENMASK(1, 0) /* #grp - 1 */
+#define YT921X_LED_SER_CTRL_GRP_NUM(x) FIELD_PREP(YT921X_LED_SER_CTRL_GRP_NUM_M, (x))
+#define YT921X_LED_SER_MAPnm(grp, port) (0xd0104 + 8 * (2 - (grp)) + 4 * ((port) / 5))
+#define YT921X_LED_SER_MAP_DSTn_PORT_M(port) GENMASK(6 * ((port) % 5) + 5, 6 * ((port) % 5) + 2)
+#define YT921X_LED_SER_MAP_DSTn_PORT(port, x) ((x) << (6 * ((port) % 5) + 2))
+#define YT921X_LED_SER_MAP_DSTn_LED_M(port) GENMASK(6 * ((port) % 5) + 1, 6 * ((port) % 5))
+#define YT921X_LED_SER_MAP_DSTn_LED(port, x) ((x) << (6 * ((port) % 5)))
+#define YT921X_LED_PAR_PORTS 0xd01c4
+#define YT921X_LED_PAR_INV 0xd01c8
+#define YT921X_LED_PAR_INV_INVnm(grp, port) BIT(10 * (grp) + (port))
+#define YT921X_LED_PAR_MAPn(port) (0xd01d0 + 4 * (port))
+#define YT921X_LED_PAR_MAP_DSTn_PORT_M(grp) GENMASK(6 * (grp) + 5, 6 * (grp) + 2)
+#define YT921X_LED_PAR_MAP_DSTn_PORT(grp, x) ((x) << (6 * (grp) + 2))
+#define YT921X_LED_PAR_MAP_DSTn_LED_M(grp) GENMASK(6 * (grp) + 1, 6 * (grp))
+#define YT921X_LED_PAR_MAP_DSTn_LED(grp, x) ((x) << (6 * (grp)))
+
+#define YT921X_LED_BLINK_MIN 64
+#define YT921X_LED_BLINK_DEF 512
+#define YT921X_LED_BLINK_MAX 2048
+
+/* 2 * lcm(2, 3, 4, 6) */
+#define YT921X_LED_DUTY_DENOM 24
+#define YT921X_LED_DUTY(nom, denom) (YT921X_LED_DUTY_DENOM * (nom) / (denom))
+
+struct yt921x_priv;
+
+struct yt921x_led {
+ struct led_classdev cdev;
+ struct yt921x_port *port;
+ unsigned char group;
+};
+
+#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)
+
+void yt921x_leds_remove(struct yt921x_priv *priv);
+int yt921x_leds_setup(struct yt921x_priv *priv);
+
+#else
+
+static inline void yt921x_leds_remove(struct yt921x_priv *priv) {}
+
+static inline int yt921x_leds_setup(struct yt921x_priv *priv)
+{
+ return 0;
+}
+
+#endif
+
+#endif
--
2.53.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH net-next v11 4/5] net: dsa: motorcomm: Add LED support
2026-08-31 4:38 ` [PATCH net-next v11 4/5] net: dsa: motorcomm: Add LED support David Yang
@ 2026-09-01 4:49 ` sashiko-bot
2026-09-02 0:56 ` Jakub Kicinski
1 sibling, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-09-01 4:49 UTC (permalink / raw)
To: David Yang; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Lockless Time-of-Check to Time-of-Use (TOCTOU) race when accessing `dp->user` and `dp->conduit` leads to garbage pointers and Use-After-Free (UAF).
- [Medium] Flawed register state restoration in `yt921x_led_setup()` error path clobbers concurrent modifications and conflates valid register states with sentinels.
--
commit 47253868ab0bfc21ecc3dfb700f5599e47b8967c
Author: David Yang <mmyangfl@gmail.com>
Date: Mon Aug 31 04:38:57 2026 +0000
net: dsa: motorcomm: Add LED support
This patch adds LED support for the Motorcomm YT9215 ethernet switch chip,
allowing each port to declare up to 3 LEDs using the standard LEDs
structure. Currently, only parallel mode and strict 1:1 mapping are
supported.
> diff --git a/drivers/net/dsa/motorcomm/leds.c b/drivers/net/dsa/motorcomm/leds.c
> new file mode 100644
> index 0000000000000..7d3c0f68d8ffb
> --- /dev/null
> +++ b/drivers/net/dsa/motorcomm/leds.c
[ ... ]
> +static struct device * __maybe_unused
> +yt921x_cled_hw_control_get_device(struct led_classdev *led_cdev)
> +{
> + struct yt921x_led *led = to_yt921x_led(led_cdev);
> + struct yt921x_port *pp = to_yt921x_port(led);
> + struct yt921x_priv *priv = to_yt921x_priv(pp);
> + struct dsa_port *dp;
> +
> + dp = dsa_to_port(&priv->ds, pp->index);
> + if (!dp)
> + return NULL;
> +
> + if (dsa_port_is_user(dp))
> + return !dp->user ? NULL : &dp->user->dev;
> + if (dsa_port_is_cpu(dp))
> + return !dp->conduit ? NULL : &dp->conduit->dev;
[Severity: High]
Can accessing dp->user and dp->conduit twice without READ_ONCE() or lock
protection lead to a lockless Time-of-Check to Time-of-Use (TOCTOU) race?
If dp->user is concurrently set to NULL by the DSA core during teardown,
the compiler can emit two separate loads. The second load could evaluate
as NULL, yielding an invalid offset pointer (&NULL->dev) and potentially
causing a kernel crash or Use-After-Free.
During the teardown window, dsa_port_teardown() destroys and frees the user
netdev before dsa_switch_teardown() invokes yt921x_dsa_teardown() to
unregister the LEDs.
Would it be safer to read these pointers into local variables once or use
READ_ONCE()?
[ ... ]
> +static int
> +yt921x_led_setup(struct yt921x_priv *priv, int port,
> + struct fwnode_handle *fwnode)
> +{
[ ... ]
> + led2_val = U32_MAX;
> + res = yt921x_reg_read(priv, YT921X_LED2_PORTn(port), &led2_val);
> + if (res)
> + goto revoke_led2;
> + mask = YT921X_LED2_PORT_FORCEn_M(group);
[Severity: Medium]
Does using U32_MAX as a sentinel here prevent restoration if the hardware
register was legitimately set to 0xFFFFFFFF (all mask bits set)?
[ ... ]
> +revoke:
> + mutex_lock(&priv->reg_lock);
> +revoke_led2:
> + if (led2_val != U32_MAX) {
> + ret = yt921x_reg_write(priv, YT921X_LED2_PORTn(port), led2_val);
[Severity: Medium]
When restoring the register state here, the lock priv->reg_lock was
previously released prior to calling devm_led_classdev_register_ext().
Does the unconditional write-back of led2_val overwrite the entire register,
potentially destroying concurrent sysfs changes made to sibling LED groups
on the same port during the unlocked window?
Could we use yt921x_reg_update_bits() instead of a full register write to
only restore the specific mask bits modified by the failed initialization?
> + if (ret)
> + dev_err(dev,
> + "Failed to restore %s for LED %02d:%02u: %d\n",
> + "LED2_PORT", port, group, ret);
> + }
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260831044818.4103477-1-mmyangfl@gmail.com?part=4
^ permalink raw reply [flat|nested] 12+ messages in thread* Re: [PATCH net-next v11 4/5] net: dsa: motorcomm: Add LED support
2026-08-31 4:38 ` [PATCH net-next v11 4/5] net: dsa: motorcomm: Add LED support David Yang
2026-09-01 4:49 ` sashiko-bot
@ 2026-09-02 0:56 ` Jakub Kicinski
2026-09-02 3:04 ` David Yang
1 sibling, 1 reply; 12+ messages in thread
From: Jakub Kicinski @ 2026-09-02 0:56 UTC (permalink / raw)
To: David Yang
Cc: netdev, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, devicetree, linux-kernel
Thank you for your contribution! Sashiko AI review found 7 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 2 · Low: 5
- [Medium] `yt921x_led_blink_set()` (via the `blink_set` callback
`yt921x_cled_blink_set()`) special-cases only the both-delays-zero…
- [Medium] LED class devices are removed only from the DSA `.teardown`
callback (`yt921x_dsa_teardown()` → `yt921x_leds_remove()`), but…
- [Low] Dead error-handling plumbing in the LED setup chain:
`yt921x_leds_setup()` (drivers/net/dsa/motorcomm/leds.c) ends with an…
- [Low] Concern (assessed false positive): the driver programs only per-
port LED registers (YT921X_LED0/1/2_PORTn) plus YT921X_LED_PAR_INV and…
- [Low] Concern (assessed resolved later in the series): the driver
introduces an undocumented DT contract — a per-port `leds` child node,…
- [Low] `yt921x_led_trigger_get()` (hw_control_get) is not the inverse of
`yt921x_led_trigger_set()` (hw_control_set): TRIGGER_NETDEV_LINK is…
- [Low] The `revoke`/`revoke_led2` rollback in `yt921x_led_setup()`
writes back a whole-register snapshot of YT921X_LED2_PORTn(port)…
--
net: dsa: motorcomm: Add LED support
This adds LED class device support to the Motorcomm yt921x DSA driver, with
up to three LEDs per port described by the standard device tree LEDs
structure, plus hardware offload of the netdev trigger. Only the hardware
default (parallel mode, 1:1 mapping) is handled, and the LEDs are torn down
from a new DSA teardown callback.
A few questions on the new code below.
> diff --git a/drivers/net/dsa/motorcomm/chip.c b/drivers/net/dsa/motorcomm/chip.c
> index 6dee25b6754a..d663af010f43 100644
> --- a/drivers/net/dsa/motorcomm/chip.c
> +++ b/drivers/net/dsa/motorcomm/chip.c
[ ... ]
> @@ -4225,6 +4224,15 @@ static int yt921x_edata_read(struct yt921x_priv *priv, u8 addr, u8 *valp)
> return yt921x_edata_read_cont(priv, addr, valp);
> }
>
> +static void yt921x_dsa_teardown(struct dsa_switch *ds)
> +{
> + struct yt921x_priv *priv = to_yt921x_priv(ds);
> +
> +#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)
> + yt921x_leds_remove(priv);
> +#endif
> +}
> +
[Severity: Medium]
Can removing the LEDs only from the DSA teardown callback leave the LED
class devices registered after the port netdevs have been freed?
net/dsa/dsa.c tears the tree down in this order:
dsa_tree_teardown()
dsa_tree_teardown_ports()
dsa_port_teardown()
dsa_user_destroy(dp->user); /* ends in free_netdev() */
dp->user = NULL;
dsa_tree_teardown_switches()
dsa_switch_teardown()
ds->ops->teardown(ds) /* yt921x_leds_remove() */
So between free_netdev() and the dp->user = NULL assignment, dp->user is a
dangling pointer while the LEDs are still registered and reachable from
sysfs.
yt921x_cled_hw_control_get_device() reads dp->user / dp->conduit with no
rtnl_lock, no RCU and takes no reference:
if (dsa_port_is_user(dp))
return !dp->user ? NULL : &dp->user->dev;
and ledtrig-netdev dereferences the returned device immediately, with
dev_name() in netdev_trig_activate() and to_net_dev() in
validate_net_dev().
Would an "echo netdev > /sys/class/leds/<led>/trigger" racing with switch
unbind, or with the dsa_tree_setup() error path, touch a freed
net_device here? Would removing the LEDs per port before the netdevs go
away, or validating dp->user under rtnl_lock, avoid that?
> @@ -4581,6 +4589,12 @@ static int yt921x_dsa_setup(struct dsa_switch *ds)
> if (res)
> return res;
>
> +#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)
> + res = yt921x_leds_setup(priv);
> + if (res)
> + dev_warn(dev, "Failed to setup LEDs: %d\n", res);
> +#endif
> +
[Severity: Low]
Can this dev_warn() ever be reached? yt921x_leds_setup() in leds.c ends
with an unconditional return 0 and drops the result of
yt921x_leds_setup_port():
dsa_switch_for_each_port(dp, ds) {
...
yt921x_leds_setup_port(priv, port);
/* Allow partial configuration: LEDs are optional */
}
return 0;
yt921x_leds_setup_port() in turn discards every yt921x_led_setup() return
value, so the -EINVAL it produces for port >= YT921X_LED_PORT_NUM is also
thrown away.
Should the error be propagated, or should this branch in yt921x_dsa_setup()
be dropped?
> diff --git a/drivers/net/dsa/motorcomm/leds.c b/drivers/net/dsa/motorcomm/leds.c
> new file mode 100644
> index 000000000000..7d3c0f68d8ff
> --- /dev/null
> +++ b/drivers/net/dsa/motorcomm/leds.c
> @@ -0,0 +1,641 @@
[ ... ]
> +static int
> +yt921x_led_blink_select(const struct yt921x_priv *priv, unsigned long on,
> + unsigned long off, unsigned short *cyclep,
> + unsigned char *dutyp)
> +{
> + static const unsigned char dutys[] = {
> + YT921X_LED_DUTY(1, 6),
> + YT921X_LED_DUTY(1, 4),
> + YT921X_LED_DUTY(1, 3),
> + YT921X_LED_DUTY(1, 2),
> + };
[ ... ]
> + duty_req = DIV_ROUND_CLOSEST(YT921X_LED_DUTY_DENOM *
> + (on > off ? off : on), cycle_req);
> + for (unsigned int i = ARRAY_SIZE(dutys) - 1;; i--)
> + if (i == 0 || duty_req >= (dutys[i - 1] + dutys[i]) / 2) {
> + duty = dutys[i];
> + break;
> + }
[ ... ]
> + if (!*onp && !*offp) {
> + cycle = YT921X_LED_BLINK_DEF;
> + duty = (pp->led_duty_mask & ~BIT(group)) ? pp->led_duty :
> + YT921X_LED_DUTY(1, 2);
> +
> + use_cycle = false;
> + } else {
> + res = yt921x_led_blink_select(priv, *onp, *offp, &cycle, &duty);
> + if (res)
> + return res;
[Severity: Medium]
What happens when exactly one of *delay_on / *delay_off is zero? The LED
core defines delay_on == 0 as steady off and delay_off == 0 as steady on,
but drivers/leds/led-core.c led_blink_setup() returns as soon as
blink_set() succeeds:
if (!test_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags) &&
led_cdev->blink_set &&
!led_cdev->blink_set(led_cdev, delay_on, delay_off))
return;
so led_set_software_blink() and its zero-delay handling never run.
Only the both-zero case is special-cased here. For *onp == 0 and
*offp == 500, yt921x_led_blink_select() computes duty_req == 0, the search
bottoms out at dutys[0] (1/6), and yt921x_led_blink_set() then programs
YT921X_LED1_PORT_BLINK_DUTY_5_6 with _COMP plus
YT921X_LED2_PORT_FORCEn_BLINK(group) and returns 0.
Does the LED then blink at roughly 1/6 duty where a steady off was
requested (and roughly 5/6 for delay_off == 0)?
> + *onp = DIV_ROUND_CLOSEST(duty * cycle, YT921X_LED_DUTY_DENOM);
> + *offp = cycle - *onp;
These two lines also replace the requested zero with a non-zero value, so
the timer trigger's delay_on / delay_off files report timings user space
never asked for. Would driving the steady states through
YT921X_LED2_PORT_FORCEn_ON / _OFF, or returning -EOPNOTSUPP for a single
zero delay, be preferable?
[ ... ]
> +static const struct yt921x_led_trigger_map yt921x_led_trigger_maps[] = {
> + {BIT(TRIGGER_NETDEV_LINK),
> + YT921X_LEDx_PORT_ACT_DUPLEX_HALF | YT921X_LEDx_PORT_ACT_DUPLEX_FULL},
[ ... ]
> + {BIT(TRIGGER_NETDEV_HALF_DUPLEX), YT921X_LEDx_PORT_ACT_DUPLEX_HALF},
> + {BIT(TRIGGER_NETDEV_FULL_DUPLEX), YT921X_LEDx_PORT_ACT_DUPLEX_FULL},
[ ... ]
> + *flagsp = 0;
> + for (unsigned int i = 0; i < ARRAY_SIZE(yt921x_led_trigger_maps); i++) {
> + const struct yt921x_led_trigger_map *map = &yt921x_led_trigger_maps[i];
> +
> + if ((val & map->mask) == map->mask)
> + *flagsp |= map->flags;
> + }
[Severity: Low]
Is yt921x_led_trigger_get() intended to be the inverse of
yt921x_led_trigger_set()?
TRIGGER_NETDEV_LINK is encoded as the union of the two duplex masks, and
this read-back loop ORs in the flags of every entry whose mask bits are all
set, with no early break. yt921x_led_trigger_set() instead matches the LINK
entry first and breaks once the flags are consumed.
So a LED programmed with only BIT(TRIGGER_NETDEV_LINK) appears to read back
as LINK | HALF_DUPLEX | FULL_DUPLEX. ledtrig-netdev keeps that value as the
trigger state at activation:
rc = led_cdev->hw_control_get(led_cdev, &mode);
if (!rc)
trigger_data->mode = mode;
Would /sys/class/leds/<led>/half_duplex and full_duplex then report 1 even
though only link indication was requested?
[ ... ]
> + if (group >= YT921X_LED_GROUP_NUM) {
> + dev_err(dev, "Invalid LED reg %u for port %d\n", group, port);
> + return -EINVAL;
> + }
[Severity: Low]
This isn't a bug, but the device tree contract introduced here (a per-port
leds child node, a per-LED reg in 0..2, LEDs only on ports below
YT921X_LED_PORT_NUM) is not described in
Documentation/devicetree/bindings/net/dsa/motorcomm,yt921x.yaml by this
commit.
The follow-on patch in the same series, "dt-bindings: net: dsa: yt921x: Add
LEDs definition example", adds the description text and a worked leds
example, so this is only a question about ordering within the series.
What the schema still does not express is a maximum for reg, so a device
tree with led@3 validates cleanly and is rejected only at probe time by the
dev_err() above. Would adding that constraint to the binding be worthwhile?
[ ... ]
> + led2_val = U32_MAX;
> + res = yt921x_reg_read(priv, YT921X_LED2_PORTn(port), &led2_val);
> + if (res)
> + goto revoke_led2;
[ ... ]
> + mutex_unlock(&priv->reg_lock);
[ ... ]
> + res = devm_led_classdev_register_ext(dev, led_cdev, &init_data);
> + if (res)
> + goto revoke;
> +
> + return 0;
> +
> +revoke:
> + mutex_lock(&priv->reg_lock);
> +revoke_led2:
> + if (led2_val != U32_MAX) {
> + ret = yt921x_reg_write(priv, YT921X_LED2_PORTn(port), led2_val);
[Severity: Low]
Can this rollback clobber the other LED groups of the same port?
led2_val is a snapshot of the whole YT921X_LED2_PORTn(port) word, taken
before mutex_unlock(&priv->reg_lock) and before
devm_led_classdev_register_ext() runs. That word holds the FORCE and
FORCE_BLINK fields of all three groups, and the groups set up in earlier
loop iterations are already registered class devices reachable from sysfs.
If devm_led_classdev_register_ext() fails (for example -ENOMEM from
devres_alloc()) while a concurrent brightness or delay_on write for a
sibling LED has gone through yt921x_cled_brightness_set_blocking() or
yt921x_cled_blink_set(), does the revoke path write the stale word back and
silently revert that sibling's force/blink state, leaving pp->led_duty,
pp->led_cycle and the masks out of sync with the hardware?
Would re-reading the register under reg_lock and using
yt921x_reg_update_bits() with
YT921X_LED2_PORT_FORCEn_M(group) | YT921X_LED2_PORT_FORCE_BLINKn_M(group)
be safer here?
[ ... ]
> +int yt921x_leds_setup(struct yt921x_priv *priv)
> +{
> + struct dsa_switch *ds = &priv->ds;
> + struct dsa_port *dp;
> +
> + /* LEDs are always enabled. There is no way to disable them altogether
> + * (as far as I know).
> + */
[Severity: Low]
This isn't a bug, just a question about scope: the driver programs only the
per-port registers (YT921X_LED0/1/2_PORTn) and YT921X_LED_PAR_INV, and
never reads YT921X_LED_CTRL, YT921X_LED_PAR_PORTS or the parallel/serial
mapping registers declared in leds.h.
As far as I can tell that is fine, since yt921x_dsa_setup() runs
yt921x_chip_reset() (which writes YT921X_RST_HW, described in the code as
almost the same as a GPIO hard reset) before yt921x_leds_setup(), so the
LED block is back at the parallel mode plus 1:1 mapping default that the
commit message names.
Is a check of YT921X_LED_CTRL_MODE_M worth adding anyway, or is the reset
considered sufficient?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260831044818.4103477-1-mmyangfl%40gmail.com
^ permalink raw reply [flat|nested] 12+ messages in thread* Re: [PATCH net-next v11 4/5] net: dsa: motorcomm: Add LED support
2026-09-02 0:56 ` Jakub Kicinski
@ 2026-09-02 3:04 ` David Yang
2026-09-02 12:04 ` Andrew Lunn
0 siblings, 1 reply; 12+ messages in thread
From: David Yang @ 2026-09-02 3:04 UTC (permalink / raw)
To: Jakub Kicinski
Cc: netdev, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, devicetree, linux-kernel
On Wed, Sep 2, 2026 at 8:56 AM Jakub Kicinski <kuba@kernel.org> wrote:
> > @@ -4225,6 +4224,15 @@ static int yt921x_edata_read(struct yt921x_priv *priv, u8 addr, u8 *valp)
> > return yt921x_edata_read_cont(priv, addr, valp);
> > }
> >
> > +static void yt921x_dsa_teardown(struct dsa_switch *ds)
> > +{
> > + struct yt921x_priv *priv = to_yt921x_priv(ds);
> > +
> > +#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)
> > + yt921x_leds_remove(priv);
> > +#endif
> > +}
> > +
>
> [Severity: Medium]
>
> Can removing the LEDs only from the DSA teardown callback leave the LED
> class devices registered after the port netdevs have been freed?
>
> net/dsa/dsa.c tears the tree down in this order:
>
> dsa_tree_teardown()
> dsa_tree_teardown_ports()
> dsa_port_teardown()
> dsa_user_destroy(dp->user); /* ends in free_netdev() */
> dp->user = NULL;
> dsa_tree_teardown_switches()
> dsa_switch_teardown()
> ds->ops->teardown(ds) /* yt921x_leds_remove() */
>
> So between free_netdev() and the dp->user = NULL assignment, dp->user is a
> dangling pointer while the LEDs are still registered and reachable from
> sysfs.
>
> yt921x_cled_hw_control_get_device() reads dp->user / dp->conduit with no
> rtnl_lock, no RCU and takes no reference:
>
> if (dsa_port_is_user(dp))
> return !dp->user ? NULL : &dp->user->dev;
>
> and ledtrig-netdev dereferences the returned device immediately, with
> dev_name() in netdev_trig_activate() and to_net_dev() in
> validate_net_dev().
>
> Would an "echo netdev > /sys/class/leds/<led>/trigger" racing with switch
> unbind, or with the dsa_tree_setup() error path, touch a freed
> net_device here? Would removing the LEDs per port before the netdevs go
> away, or validating dp->user under rtnl_lock, avoid that?
>
> > @@ -4581,6 +4589,12 @@ static int yt921x_dsa_setup(struct dsa_switch *ds)
> > if (res)
> > return res;
> >
> > +#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)
> > + res = yt921x_leds_setup(priv);
> > + if (res)
> > + dev_warn(dev, "Failed to setup LEDs: %d\n", res);
> > +#endif
> > +
Just a question: I don't see any protection against this in other DSA
led drivers, like mv88e6 or qca8k.
> [Severity: Low]
>
> Can this dev_warn() ever be reached? yt921x_leds_setup() in leds.c ends
> with an unconditional return 0 and drops the result of
> yt921x_leds_setup_port():
>
> dsa_switch_for_each_port(dp, ds) {
> ...
> yt921x_leds_setup_port(priv, port);
> /* Allow partial configuration: LEDs are optional */
> }
>
> return 0;
>
> yt921x_leds_setup_port() in turn discards every yt921x_led_setup() return
> value, so the -EINVAL it produces for port >= YT921X_LED_PORT_NUM is also
> thrown away.
>
> Should the error be propagated, or should this branch in yt921x_dsa_setup()
> be dropped?
Intended, explained in the response for the previous version.
> [Severity: Low]
>
> Is yt921x_led_trigger_get() intended to be the inverse of
> yt921x_led_trigger_set()?
>
> TRIGGER_NETDEV_LINK is encoded as the union of the two duplex masks, and
> this read-back loop ORs in the flags of every entry whose mask bits are all
> set, with no early break. yt921x_led_trigger_set() instead matches the LINK
> entry first and breaks once the flags are consumed.
>
> So a LED programmed with only BIT(TRIGGER_NETDEV_LINK) appears to read back
> as LINK | HALF_DUPLEX | FULL_DUPLEX. ledtrig-netdev keeps that value as the
> trigger state at activation:
>
> rc = led_cdev->hw_control_get(led_cdev, &mode);
> if (!rc)
> trigger_data->mode = mode;
>
> Would /sys/class/leds/<led>/half_duplex and full_duplex then report 1 even
> though only link indication was requested?
Yes.
> > + if (group >= YT921X_LED_GROUP_NUM) {
> > + dev_err(dev, "Invalid LED reg %u for port %d\n", group, port);
> > + return -EINVAL;
> > + }
>
> [Severity: Low]
>
> This isn't a bug, but the device tree contract introduced here (a per-port
> leds child node, a per-LED reg in 0..2, LEDs only on ports below
> YT921X_LED_PORT_NUM) is not described in
> Documentation/devicetree/bindings/net/dsa/motorcomm,yt921x.yaml by this
> commit.
>
> The follow-on patch in the same series, "dt-bindings: net: dsa: yt921x: Add
> LEDs definition example", adds the description text and a worked leds
> example, so this is only a question about ordering within the series.
>
> What the schema still does not express is a maximum for reg, so a device
> tree with led@3 validates cleanly and is rejected only at probe time by the
> dev_err() above. Would adding that constraint to the binding be worthwhile?
I don't see a good notation to impose the constraint (from sibling DSA
bindings).
> > +int yt921x_leds_setup(struct yt921x_priv *priv)
> > +{
> > + struct dsa_switch *ds = &priv->ds;
> > + struct dsa_port *dp;
> > +
> > + /* LEDs are always enabled. There is no way to disable them altogether
> > + * (as far as I know).
> > + */
>
> [Severity: Low]
>
> This isn't a bug, just a question about scope: the driver programs only the
> per-port registers (YT921X_LED0/1/2_PORTn) and YT921X_LED_PAR_INV, and
> never reads YT921X_LED_CTRL, YT921X_LED_PAR_PORTS or the parallel/serial
> mapping registers declared in leds.h.
>
> As far as I can tell that is fine, since yt921x_dsa_setup() runs
> yt921x_chip_reset() (which writes YT921X_RST_HW, described in the code as
> almost the same as a GPIO hard reset) before yt921x_leds_setup(), so the
> LED block is back at the parallel mode plus 1:1 mapping default that the
> commit message names.
>
> Is a check of YT921X_LED_CTRL_MODE_M worth adding anyway, or is the reset
> considered sufficient?
Reset is sufficient.
^ permalink raw reply [flat|nested] 12+ messages in thread* Re: [PATCH net-next v11 4/5] net: dsa: motorcomm: Add LED support
2026-09-02 3:04 ` David Yang
@ 2026-09-02 12:04 ` Andrew Lunn
0 siblings, 0 replies; 12+ messages in thread
From: Andrew Lunn @ 2026-09-02 12:04 UTC (permalink / raw)
To: David Yang
Cc: Jakub Kicinski, netdev, Vladimir Oltean, David S. Miller,
Eric Dumazet, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, devicetree, linux-kernel
> > Would an "echo netdev > /sys/class/leds/<led>/trigger" racing with switch
> > unbind, or with the dsa_tree_setup() error path, touch a freed
> > net_device here? Would removing the LEDs per port before the netdevs go
> > away, or validating dp->user under rtnl_lock, avoid that?
> >
> > > @@ -4581,6 +4589,12 @@ static int yt921x_dsa_setup(struct dsa_switch *ds)
> > > if (res)
> > > return res;
> > >
> > > +#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)
> > > + res = yt921x_leds_setup(priv);
> > > + if (res)
> > > + dev_warn(dev, "Failed to setup LEDs: %d\n", res);
> > > +#endif
> > > +
>
> Just a question: I don't see any protection against this in other DSA
> led drivers, like mv88e6 or qca8k.
Registering the LEDs uses devm_ so the life of the LEDs is the life of
the chip. The LED trigger does however take a reference on the network
device, so it should not be possible for it to disappear while the
trigger is still bound to it.
Andrew
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH net-next v11 5/5] dt-bindings: net: dsa: yt921x: Add LEDs definition example
2026-08-31 4:38 [PATCH net-next v11 0/5] net: dsa: motorcomm: Add LED support David Yang
` (3 preceding siblings ...)
2026-08-31 4:38 ` [PATCH net-next v11 4/5] net: dsa: motorcomm: Add LED support David Yang
@ 2026-08-31 4:38 ` David Yang
4 siblings, 0 replies; 12+ messages in thread
From: David Yang @ 2026-08-31 4:38 UTC (permalink / raw)
To: netdev
Cc: David Yang, Krzysztof Kozlowski, Andrew Lunn, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, devicetree,
linux-kernel
Add LEDs definition example for yt921x to describe how they should be
defined for a correct usage.
Signed-off-by: David Yang <mmyangfl@gmail.com>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
---
.../bindings/net/dsa/motorcomm,yt921x.yaml | 23 +++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/Documentation/devicetree/bindings/net/dsa/motorcomm,yt921x.yaml b/Documentation/devicetree/bindings/net/dsa/motorcomm,yt921x.yaml
index 33a6552e46fc..93ba5b181205 100644
--- a/Documentation/devicetree/bindings/net/dsa/motorcomm,yt921x.yaml
+++ b/Documentation/devicetree/bindings/net/dsa/motorcomm,yt921x.yaml
@@ -21,6 +21,9 @@ description: |
Any port can be used as the CPU port.
+ Each port has at most 3 LEDs connected and can be declared using the standard
+ LEDs structure.
+
properties:
compatible:
const: motorcomm,yt9215
@@ -59,6 +62,7 @@ unevaluatedProperties: false
examples:
- |
#include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/leds/common.h>
mdio {
#address-cells = <1>;
@@ -113,6 +117,25 @@ examples:
label = "lan1";
phy-mode = "internal";
phy-handle = <&sw_phy0>;
+
+ leds {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ led@0 {
+ reg = <0>;
+ color = <LED_COLOR_ID_WHITE>;
+ function = LED_FUNCTION_LAN;
+ default-state = "keep";
+ };
+
+ led@1 {
+ reg = <1>;
+ color = <LED_COLOR_ID_AMBER>;
+ function = LED_FUNCTION_LAN;
+ default-state = "keep";
+ };
+ };
};
ethernet-port@1 {
--
2.53.0
^ permalink raw reply related [flat|nested] 12+ messages in thread