* [PATCH net-next v9 0/7] net: dsa: motorcomm: add support yt922x driver
@ 2026-09-24 9:41 Kyle Switch
2026-09-24 9:41 ` [PATCH net-next v9 1/7] net: dsa: motorcomm: initialize dsa_switch based on chipid Kyle Switch
` (6 more replies)
0 siblings, 7 replies; 15+ messages in thread
From: Kyle Switch @ 2026-09-24 9:41 UTC (permalink / raw)
To: andrew, olteanv, davem, edumazet, kuba, pabeni, mmyangfl, horms,
linux, netdev, linux-kernel
Cc: ming.xu, xiaolin.xu, jianmin.wang, wei.zhang, sijia.huang
This patchset mainly adds support for the yt922x series to the dsa driver.
Currently, only the yt9224 in the yt922x series is supported, and the
corresponding tag handling for yt922x is added.
At the same time, it optimizes the max ports boundary check in the
existing code, and initializes the dsa switch based on the chipid.
changes in v9:
patch 1: 1) Add a dev_err() when chipid mismatch.
patch 3: 1) Using GENMASK_U* to replace GENMASK
changes in v8:
patch 1: 1) Add a lock operation to acquire the chip ID during the probe()
stage.
2) Remove meaningless changes.
patch 3: Modify the changelog description.
patch 6: 1) Remove sds_init() in this patch.
2) Code refactoring, including yt922x_sds_phyaddr_get() and
yt921x_needs_extmode_check()
3) In yt921x_intif_ext_write() and yt921x_intif_ext_read(),
add a boundary check for phyaddr. The reason is that the
phyaddr of the top ext register is 9, so max_ports should
be used for the check to include it.
4) In the yt922x driver, added initialization of yt921x_port
for exporting priv.
changes in v7:
patch 1: Fix the series_lookup() redundancy issue.
Fix the dsa->priv initialization and the code style issue.
patch 6: Move psc definition to yt921x_port
Fix psc_get_state() and an_restart().
Use the existing yt921x driver interface for the duplicated
logic for chip_reset().
Move the yt922x_cpu_port_set() to chip_setup_dsa() to align
with yt921x function.
Kyle Switch (7):
net: dsa: motorcomm: initialize dsa_switch based on chipid
net: dsa: motorcomm: use max_ports in series for port bounds checking
net: dsa: motorcomm: use fixed-width GENMASK_U*() helpers
net: dsa: motorcomm: move mib start from probe() to dsa_setup()
net: dsa: motorcomm: move mib stop from remove() to teardown()
net: dsa: tag_yt922x: add support for Motorcomm YT922x tags
net: dsa: motorcomm: Add support for Motorcomm YT922x
drivers/net/dsa/motorcomm/Kconfig | 3 +-
drivers/net/dsa/motorcomm/chip.c | 798 ++++++++++++++++++++++++++++--
drivers/net/dsa/motorcomm/chip.h | 113 +++++
include/net/dsa.h | 2 +
net/dsa/Kconfig | 6 +
net/dsa/Makefile | 1 +
net/dsa/tag_yt922x.c | 110 ++++
7 files changed, 1002 insertions(+), 31 deletions(-)
create mode 100644 net/dsa/tag_yt922x.c
--
2.25.1
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH net-next v9 1/7] net: dsa: motorcomm: initialize dsa_switch based on chipid
2026-09-24 9:41 [PATCH net-next v9 0/7] net: dsa: motorcomm: add support yt922x driver Kyle Switch
@ 2026-09-24 9:41 ` Kyle Switch
2026-09-28 10:39 ` netdev-bot+sashiko
2026-09-24 9:41 ` [PATCH net-next v9 2/7] net: dsa: motorcomm: use max_ports in series for port bounds checking Kyle Switch
` (5 subsequent siblings)
6 siblings, 1 reply; 15+ messages in thread
From: Kyle Switch @ 2026-09-24 9:41 UTC (permalink / raw)
To: andrew, olteanv, davem, edumazet, kuba, pabeni, mmyangfl, horms,
linux, netdev, linux-kernel
Cc: ming.xu, xiaolin.xu, jianmin.wang, wei.zhang, sijia.huang
Replace the hardcoded chip handling in yt92xx_register_switch() with a
data-driven loopup based on the chip ID.
Signed-off-by: Kyle Switch <kyle.switch@motor-comm.com>
---
drivers/net/dsa/motorcomm/chip.c | 74 ++++++++++++++++++++++++++++----
drivers/net/dsa/motorcomm/chip.h | 20 +++++++++
2 files changed, 86 insertions(+), 8 deletions(-)
diff --git a/drivers/net/dsa/motorcomm/chip.c b/drivers/net/dsa/motorcomm/chip.c
index 8b0ac5fc8c8b..d4a565994940 100644
--- a/drivers/net/dsa/motorcomm/chip.c
+++ b/drivers/net/dsa/motorcomm/chip.c
@@ -4679,6 +4679,68 @@ static const struct dsa_switch_ops yt921x_dsa_switch_ops = {
.setup = yt921x_dsa_setup,
};
+static const struct yt92xx_series yt92xx_series_table[] = {
+ [YT92XX_MODE_YT921X] = {
+ .mode = YT92XX_MODE_YT921X,
+ .name = "YT921x",
+ .max_ports = YT921X_PORT_NUM,
+ .num_lag_ids = YT921X_LAG_NUM,
+ .ageing_time_min = 1 * 5000,
+ .ageing_time_max = U16_MAX * 5000,
+ .dscp_prio_mapping_is_global = true,
+ .assisted_learning_on_cpu_port = true,
+ .switch_ops = &yt921x_dsa_switch_ops,
+ .mac_ops = &yt921x_phylink_mac_ops
+ },
+};
+
+static const struct yt92xx_series *yt92xx_series_lookup(u32 major)
+{
+ if (major == YT9215_MAJOR || major == YT9218_MAJOR)
+ return &yt92xx_series_table[YT92XX_MODE_YT921X];
+ else
+ return NULL;
+}
+
+static int yt92xx_register_switch(struct dsa_switch *ds)
+{
+ struct yt921x_priv *priv = to_yt921x_priv(ds);
+ struct device *dev = to_device(priv);
+ const struct yt92xx_series *series;
+ u32 chipid;
+ u32 major;
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_reg_read(priv, YT921X_CHIP_ID, &chipid);
+ mutex_unlock(&priv->reg_lock);
+ if (res) {
+ dev_err(dev, "Unexpected chipid 0x%x\n", chipid);
+ return res;
+ }
+
+ major = FIELD_GET(YT921X_CHIP_ID_MAJOR, chipid);
+ series = yt92xx_series_lookup(major);
+ if (!series) {
+ dev_err(dev, "Undefined series info for the corresponding major\n");
+ return -ENODEV;
+ }
+ priv->series = series;
+
+ ds->assisted_learning_on_cpu_port =
+ priv->series->assisted_learning_on_cpu_port;
+ ds->dscp_prio_mapping_is_global =
+ priv->series->dscp_prio_mapping_is_global;
+ ds->ageing_time_min = priv->series->ageing_time_min;
+ ds->ageing_time_max = priv->series->ageing_time_max;
+ ds->num_lag_ids = priv->series->num_lag_ids;
+ ds->num_ports = priv->series->max_ports;
+ ds->ops = priv->series->switch_ops;
+ ds->phylink_mac_ops = priv->series->mac_ops;
+
+ return 0;
+}
+
static void yt921x_mdio_shutdown(struct mdio_device *mdiodev)
{
struct yt921x_priv *priv = mdiodev_get_drvdata(mdiodev);
@@ -4727,6 +4789,7 @@ static int yt921x_mdio_probe(struct mdio_device *mdiodev)
struct yt921x_reg_mdio *mdio;
struct yt921x_priv *priv;
struct dsa_switch *ds;
+ int res;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
@@ -4754,15 +4817,10 @@ static int yt921x_mdio_probe(struct mdio_device *mdiodev)
ds = &priv->ds;
ds->dev = dev;
- ds->assisted_learning_on_cpu_port = true;
- ds->dscp_prio_mapping_is_global = true;
ds->priv = priv;
- ds->ops = &yt921x_dsa_switch_ops;
- ds->ageing_time_min = 1 * 5000;
- ds->ageing_time_max = U16_MAX * 5000;
- ds->phylink_mac_ops = &yt921x_phylink_mac_ops;
- ds->num_lag_ids = YT921X_LAG_NUM;
- ds->num_ports = YT921X_PORT_NUM;
+ res = yt92xx_register_switch(ds);
+ if (res)
+ return res;
mdiodev_set_drvdata(mdiodev, priv);
diff --git a/drivers/net/dsa/motorcomm/chip.h b/drivers/net/dsa/motorcomm/chip.h
index 83cd454955dd..c446aea449ed 100644
--- a/drivers/net/dsa/motorcomm/chip.h
+++ b/drivers/net/dsa/motorcomm/chip.h
@@ -960,9 +960,29 @@ struct yt921x_reg_ops {
int (*write)(void *context, u32 reg, u32 val);
};
+enum yt92xx_mode {
+ YT92XX_MODE_YT921X,
+ YT92XX_MODE_YT922X,
+ YT92XX_MODE_MAX,
+};
+
+struct yt92xx_series {
+ enum yt92xx_mode mode;
+ const char *name;
+ unsigned int max_ports;
+ unsigned int num_lag_ids;
+ unsigned int ageing_time_min;
+ unsigned int ageing_time_max;
+ u32 dscp_prio_mapping_is_global;
+ u32 assisted_learning_on_cpu_port;
+ const struct dsa_switch_ops *switch_ops;
+ const struct phylink_mac_ops *mac_ops;
+};
+
struct yt921x_priv {
struct dsa_switch ds;
+ const struct yt92xx_series *series;
const struct yt921x_info *info;
unsigned int meter_slot_ns;
unsigned int port_shape_slot_ns;
--
2.25.1
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH net-next v9 2/7] net: dsa: motorcomm: use max_ports in series for port bounds checking
2026-09-24 9:41 [PATCH net-next v9 0/7] net: dsa: motorcomm: add support yt922x driver Kyle Switch
2026-09-24 9:41 ` [PATCH net-next v9 1/7] net: dsa: motorcomm: initialize dsa_switch based on chipid Kyle Switch
@ 2026-09-24 9:41 ` Kyle Switch
2026-09-24 15:32 ` Andrew Lunn
2026-09-28 10:39 ` netdev-bot+sashiko
2026-09-24 9:41 ` [PATCH net-next v9 3/7] net: dsa: motorcomm: use fixed-width GENMASK_U*() helpers Kyle Switch
` (4 subsequent siblings)
6 siblings, 2 replies; 15+ messages in thread
From: Kyle Switch @ 2026-09-24 9:41 UTC (permalink / raw)
To: andrew, olteanv, davem, edumazet, kuba, pabeni, mmyangfl, horms,
linux, netdev, linux-kernel
Cc: ming.xu, xiaolin.xu, jianmin.wang, wei.zhang, sijia.huang
Replace the hardcoded YT921X_PORT_NUM macro with the per-series
max_ports field in port validation. This removes family-specific
constants from the common code path and simplifies adding new
switch families with different port counts.
No functional change for existing YT921X devices.
Signed-off-by: Kyle Switch <kyle.switch@motor-comm.com>
---
drivers/net/dsa/motorcomm/chip.c | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/drivers/net/dsa/motorcomm/chip.c b/drivers/net/dsa/motorcomm/chip.c
index d4a565994940..b95f3c355511 100644
--- a/drivers/net/dsa/motorcomm/chip.c
+++ b/drivers/net/dsa/motorcomm/chip.c
@@ -357,7 +357,7 @@ static int yt921x_mbus_int_read(struct mii_bus *mbus, int port, int reg)
u16 val;
int res;
- if (port >= YT921X_PORT_NUM)
+ if (port >= priv->series->max_ports)
return U16_MAX;
mutex_lock(&priv->reg_lock);
@@ -375,7 +375,7 @@ yt921x_mbus_int_write(struct mii_bus *mbus, int port, int reg, u16 data)
struct yt921x_priv *priv = mbus->priv;
int res;
- if (port >= YT921X_PORT_NUM)
+ if (port >= priv->series->max_ports)
return -ENODEV;
mutex_lock(&priv->reg_lock);
@@ -390,6 +390,7 @@ yt921x_mbus_int_init(struct yt921x_priv *priv, struct device_node *mnp)
{
struct device *dev = to_device(priv);
struct mii_bus *mbus;
+ u32 max_ports;
int res;
mbus = devm_mdiobus_alloc(dev);
@@ -402,7 +403,8 @@ yt921x_mbus_int_init(struct yt921x_priv *priv, struct device_node *mnp)
mbus->read = yt921x_mbus_int_read;
mbus->write = yt921x_mbus_int_write;
mbus->parent = dev;
- mbus->phy_mask = (u32)~GENMASK(YT921X_PORT_NUM - 1, 0);
+ max_ports = priv->series->max_ports;
+ mbus->phy_mask = (u32)~GENMASK(max_ports - 1, 0);
res = devm_of_mdiobus_register(dev, mbus, mnp);
if (res)
--
2.25.1
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH net-next v9 3/7] net: dsa: motorcomm: use fixed-width GENMASK_U*() helpers
2026-09-24 9:41 [PATCH net-next v9 0/7] net: dsa: motorcomm: add support yt922x driver Kyle Switch
2026-09-24 9:41 ` [PATCH net-next v9 1/7] net: dsa: motorcomm: initialize dsa_switch based on chipid Kyle Switch
2026-09-24 9:41 ` [PATCH net-next v9 2/7] net: dsa: motorcomm: use max_ports in series for port bounds checking Kyle Switch
@ 2026-09-24 9:41 ` Kyle Switch
2026-09-24 9:41 ` [PATCH net-next v9 4/7] net: dsa: motorcomm: move mib start from probe() to dsa_setup() Kyle Switch
` (3 subsequent siblings)
6 siblings, 0 replies; 15+ messages in thread
From: Kyle Switch @ 2026-09-24 9:41 UTC (permalink / raw)
To: andrew, olteanv, davem, edumazet, kuba, pabeni, mmyangfl, horms,
linux, netdev, linux-kernel
Cc: ming.xu, xiaolin.xu, jianmin.wang, wei.zhang, sijia.huang
Replace the plain GENMASK() uses with the fixed-width GENMASK_U16()
and GENMASK_U32() variants.
Signed-off-by: Kyle Switch <kyle.switch@motor-comm.com>
---
drivers/net/dsa/motorcomm/chip.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/net/dsa/motorcomm/chip.c b/drivers/net/dsa/motorcomm/chip.c
index b95f3c355511..6f2606fd7807 100644
--- a/drivers/net/dsa/motorcomm/chip.c
+++ b/drivers/net/dsa/motorcomm/chip.c
@@ -109,7 +109,7 @@ struct yt921x_info {
};
#define YT921X_PORT_MASK_INTn(port) BIT(port)
-#define YT921X_PORT_MASK_INT0_n(n) GENMASK((n) - 1, 0)
+#define YT921X_PORT_MASK_INT0_n(n) GENMASK_U16((n) - 1, 0)
#define YT921X_PORT_MASK_EXT0 BIT(8)
#define YT921X_PORT_MASK_EXT1 BIT(9)
@@ -404,7 +404,7 @@ yt921x_mbus_int_init(struct yt921x_priv *priv, struct device_node *mnp)
mbus->write = yt921x_mbus_int_write;
mbus->parent = dev;
max_ports = priv->series->max_ports;
- mbus->phy_mask = (u32)~GENMASK(max_ports - 1, 0);
+ mbus->phy_mask = ~GENMASK_U32(max_ports - 1, 0);
res = devm_of_mdiobus_register(dev, mbus, mnp);
if (res)
@@ -4370,7 +4370,7 @@ static int yt921x_chip_setup_dsa(struct yt921x_priv *priv)
if (res)
return res;
- ctrl = GENMASK(10, 0);
+ ctrl = GENMASK_U32(10, 0);
res = yt921x_reg_write(priv, YT921X_FILTER_UNK_UCAST, ctrl);
if (res)
return res;
--
2.25.1
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH net-next v9 4/7] net: dsa: motorcomm: move mib start from probe() to dsa_setup()
2026-09-24 9:41 [PATCH net-next v9 0/7] net: dsa: motorcomm: add support yt922x driver Kyle Switch
` (2 preceding siblings ...)
2026-09-24 9:41 ` [PATCH net-next v9 3/7] net: dsa: motorcomm: use fixed-width GENMASK_U*() helpers Kyle Switch
@ 2026-09-24 9:41 ` Kyle Switch
2026-09-28 10:39 ` netdev-bot+sashiko
2026-09-24 9:41 ` [PATCH net-next v9 5/7] net: dsa: motorcomm: move mib stop from remove() to teardown() Kyle Switch
` (2 subsequent siblings)
6 siblings, 1 reply; 15+ messages in thread
From: Kyle Switch @ 2026-09-24 9:41 UTC (permalink / raw)
To: andrew, olteanv, davem, edumazet, kuba, pabeni, mmyangfl, horms,
linux, netdev, linux-kernel
Cc: ming.xu, xiaolin.xu, jianmin.wang, wei.zhang, sijia.huang
Move the delayed work initialization of the per-port MIB polling
from yt921x_mdio_probe() into the DSA .setup() callback, changing
its lifetime from once per probe() to once per DSA .setup().
Signed-off-by: Kyle Switch <kyle.switch@motor-comm.com>
---
drivers/net/dsa/motorcomm/chip.c | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/drivers/net/dsa/motorcomm/chip.c b/drivers/net/dsa/motorcomm/chip.c
index 6f2606fd7807..d9d73bb7cb23 100644
--- a/drivers/net/dsa/motorcomm/chip.c
+++ b/drivers/net/dsa/motorcomm/chip.c
@@ -4553,6 +4553,14 @@ static int yt921x_dsa_setup(struct dsa_switch *ds)
struct device_node *child;
int res;
+ /* mib polling init */
+ for (size_t i = 0; i < ARRAY_SIZE(priv->ports); i++) {
+ struct yt921x_port *pp = &priv->ports[i];
+
+ pp->index = i;
+ INIT_DELAYED_WORK(&pp->mib_read, yt921x_poll_mib);
+ }
+
mutex_lock(&priv->reg_lock);
res = yt921x_chip_reset(priv);
mutex_unlock(&priv->reg_lock);
@@ -4810,13 +4818,6 @@ static int yt921x_mdio_probe(struct mdio_device *mdiodev)
priv->reg_ops = &yt921x_reg_ops_mdio;
priv->reg_ctx = mdio;
- for (size_t i = 0; i < ARRAY_SIZE(priv->ports); i++) {
- struct yt921x_port *pp = &priv->ports[i];
-
- pp->index = i;
- INIT_DELAYED_WORK(&pp->mib_read, yt921x_poll_mib);
- }
-
ds = &priv->ds;
ds->dev = dev;
ds->priv = priv;
--
2.25.1
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH net-next v9 5/7] net: dsa: motorcomm: move mib stop from remove() to teardown()
2026-09-24 9:41 [PATCH net-next v9 0/7] net: dsa: motorcomm: add support yt922x driver Kyle Switch
` (3 preceding siblings ...)
2026-09-24 9:41 ` [PATCH net-next v9 4/7] net: dsa: motorcomm: move mib start from probe() to dsa_setup() Kyle Switch
@ 2026-09-24 9:41 ` Kyle Switch
2026-09-28 10:39 ` netdev-bot+sashiko
2026-09-24 9:41 ` [PATCH net-next v9 6/7] net: dsa: tag_yt922x: add support for Motorcomm YT922x tags Kyle Switch
2026-09-24 9:41 ` [PATCH net-next v9 7/7] net: dsa: motorcomm: Add support for Motorcomm YT922x Kyle Switch
6 siblings, 1 reply; 15+ messages in thread
From: Kyle Switch @ 2026-09-24 9:41 UTC (permalink / raw)
To: andrew, olteanv, davem, edumazet, kuba, pabeni, mmyangfl, horms,
linux, netdev, linux-kernel
Cc: ming.xu, xiaolin.xu, jianmin.wang, wei.zhang, sijia.huang
This change ensures symmetry with the MIB start operation,
which is already invoked in the setup(). Additionally, it
consolidates per-switch operation into the appropriate DSA ops,
improving code organization and maintainability.
Signed-off-by: Kyle Switch <kyle.switch@motor-comm.com>
---
drivers/net/dsa/motorcomm/chip.c | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/drivers/net/dsa/motorcomm/chip.c b/drivers/net/dsa/motorcomm/chip.c
index d9d73bb7cb23..46f7b94469ea 100644
--- a/drivers/net/dsa/motorcomm/chip.c
+++ b/drivers/net/dsa/motorcomm/chip.c
@@ -4233,6 +4233,11 @@ static void yt921x_dsa_teardown(struct dsa_switch *ds)
yt921x_leds_remove(priv);
#endif
+ for (size_t i = ARRAY_SIZE(priv->ports); i-- > 0; ) {
+ struct yt921x_port *pp = &priv->ports[i];
+
+ disable_delayed_work_sync(&pp->mib_read);
+ }
}
static int yt921x_chip_detect(struct yt921x_priv *priv)
@@ -4768,12 +4773,6 @@ static void yt921x_mdio_remove(struct mdio_device *mdiodev)
if (!priv)
return;
- for (size_t i = ARRAY_SIZE(priv->ports); i-- > 0; ) {
- struct yt921x_port *pp = &priv->ports[i];
-
- disable_delayed_work_sync(&pp->mib_read);
- }
-
dsa_unregister_switch(&priv->ds);
for (unsigned int i = 0; i < ARRAY_SIZE(priv->acl_blks); i++) {
--
2.25.1
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH net-next v9 6/7] net: dsa: tag_yt922x: add support for Motorcomm YT922x tags
2026-09-24 9:41 [PATCH net-next v9 0/7] net: dsa: motorcomm: add support yt922x driver Kyle Switch
` (4 preceding siblings ...)
2026-09-24 9:41 ` [PATCH net-next v9 5/7] net: dsa: motorcomm: move mib stop from remove() to teardown() Kyle Switch
@ 2026-09-24 9:41 ` Kyle Switch
2026-09-28 10:39 ` netdev-bot+sashiko
2026-09-24 9:41 ` [PATCH net-next v9 7/7] net: dsa: motorcomm: Add support for Motorcomm YT922x Kyle Switch
6 siblings, 1 reply; 15+ messages in thread
From: Kyle Switch @ 2026-09-24 9:41 UTC (permalink / raw)
To: andrew, olteanv, davem, edumazet, kuba, pabeni, mmyangfl, horms,
linux, netdev, linux-kernel
Cc: ming.xu, xiaolin.xu, jianmin.wang, wei.zhang, sijia.huang
Add support for Motorcomm YT922x tags with 8bytes. which includes
ethertype field (default to 0x9988).
Signed-off-by: Kyle Switch <kyle.switch@motor-comm.com>
---
include/net/dsa.h | 2 +
net/dsa/Kconfig | 6 +++
net/dsa/Makefile | 1 +
net/dsa/tag_yt922x.c | 110 +++++++++++++++++++++++++++++++++++++++++++
4 files changed, 119 insertions(+)
create mode 100644 net/dsa/tag_yt922x.c
diff --git a/include/net/dsa.h b/include/net/dsa.h
index 5d12191b6f6f..5a173fe2bb87 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -62,6 +62,7 @@ struct tc_action;
#define DSA_TAG_PROTO_KSZ8463_VALUE 34
#define DSA_TAG_PROTO_MT7628_VALUE 35
#define DSA_TAG_PROTO_KS8995_VALUE 36
+#define DSA_TAG_PROTO_YT922X_VALUE 37
enum dsa_tag_protocol {
DSA_TAG_PROTO_NONE = DSA_TAG_PROTO_NONE_VALUE,
@@ -101,6 +102,7 @@ enum dsa_tag_protocol {
DSA_TAG_PROTO_KSZ8463 = DSA_TAG_PROTO_KSZ8463_VALUE,
DSA_TAG_PROTO_MT7628 = DSA_TAG_PROTO_MT7628_VALUE,
DSA_TAG_PROTO_KS8995 = DSA_TAG_PROTO_KS8995_VALUE,
+ DSA_TAG_PROTO_YT922X = DSA_TAG_PROTO_YT922X_VALUE,
};
struct dsa_switch;
diff --git a/net/dsa/Kconfig b/net/dsa/Kconfig
index 4f44bf3ede23..06d8d6526054 100644
--- a/net/dsa/Kconfig
+++ b/net/dsa/Kconfig
@@ -233,4 +233,10 @@ config NET_DSA_TAG_YT921X
Say Y or M if you want to enable support for tagging frames for
Motorcomm YT921x switches.
+config NET_DSA_TAG_YT922X
+ tristate "Tag driver for Motorcomm YT922x switches"
+ help
+ Say Y or M if you want to enable support for tagging frames for
+ Motorcomm YT922x switches.
+
endif
diff --git a/net/dsa/Makefile b/net/dsa/Makefile
index 1f9cc30e9988..0ef6dfce3b92 100644
--- a/net/dsa/Makefile
+++ b/net/dsa/Makefile
@@ -45,6 +45,7 @@ obj-$(CONFIG_NET_DSA_TAG_TRAILER) += tag_trailer.o
obj-$(CONFIG_NET_DSA_TAG_VSC73XX_8021Q) += tag_vsc73xx_8021q.o
obj-$(CONFIG_NET_DSA_TAG_XRS700X) += tag_xrs700x.o
obj-$(CONFIG_NET_DSA_TAG_YT921X) += tag_yt921x.o
+obj-$(CONFIG_NET_DSA_TAG_YT922X) += tag_yt922x.o
# for tracing framework to find trace.h
CFLAGS_trace.o := -I$(src)
diff --git a/net/dsa/tag_yt922x.c b/net/dsa/tag_yt922x.c
new file mode 100644
index 000000000000..006a6bda90eb
--- /dev/null
+++ b/net/dsa/tag_yt922x.c
@@ -0,0 +1,110 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Motorcomm YT922x Switch Extended CPU Port Tagging
+ *
+ * Copyright (c) 2026 Kyle switch <kyle.switch@motor-comm.com>
+ *
+ */
+
+#include <linux/etherdevice.h>
+
+#include "tag.h"
+
+#define YT922X_TAG_LEN 8
+
+/*
+ * To define the from cpu tag format 8 bytes:
+ */
+#define YT922X_TAG_NAME "yt922x"
+#define YT922X_TAG_PORTMASK_0 BIT(15)
+#define YT922X_TAG_PORTMASK_M GENMASK(8, 0)
+#define YT922X_TAG_PORTS(x) FIELD_PREP(YT922X_TAG_PORTMASK_M, (x))
+#define YT922X_TAG_FORCE_DST BIT(9)
+#define YT922X_TAG_PRIO_M GENMASK(12, 10)
+#define YT922X_TAG_PRIO_EN BIT(13)
+#define YT922X_TAG_PRIO(x) (FIELD_PREP(YT922X_TAG_PRIO_M, (x)) | YT922X_TAG_PRIO_EN)
+#define YT922X_TAG_RX_PORT_M GENMASK(5, 2)
+
+static struct sk_buff *
+yt922x_tag_xmit(struct sk_buff *skb, struct net_device *netdev)
+{
+ unsigned long ports;
+ __be16 *tag;
+ u16 ctrl;
+
+ skb_push(skb, YT922X_TAG_LEN);
+ dsa_alloc_etype_header(skb, YT922X_TAG_LEN);
+ tag = dsa_etype_header_pos_tx(skb);
+
+ tag[0] = htons(ETH_P_YT921X);
+ ports = dsa_xmit_port_mask(skb, netdev);
+ /*To fill in the case where the port index is not 0 */
+ ctrl = YT922X_TAG_PRIO(skb->priority) | YT922X_TAG_FORCE_DST |
+ YT922X_TAG_PORTS(ports >> 1);
+ tag[1] = htons(ctrl);
+ if (ports & BIT(0)) {
+ /* To fill in the case where the port index is 0 */
+ ctrl = YT922X_TAG_PORTMASK_0;
+ tag[2] = htons(ctrl);
+ } else {
+ tag[2] = 0;
+ }
+ tag[3] = 0;
+
+ return skb;
+}
+
+static struct sk_buff *
+yt922x_tag_rcv(struct sk_buff *skb, struct net_device *netdev)
+{
+ unsigned int port;
+ __be16 *tag;
+ u16 rx;
+
+ if (unlikely(!pskb_may_pull(skb, YT922X_TAG_LEN))) {
+ kfree_skb(skb);
+ return NULL;
+ }
+
+ tag = dsa_etype_header_pos_rx(skb);
+
+ if (unlikely(tag[0] != htons(ETH_P_YT921X))) {
+ dev_warn_ratelimited(&netdev->dev,
+ "Unexpected EtherType 0x%04x\n",
+ ntohs(tag[0]));
+ kfree_skb(skb);
+ return NULL;
+ }
+
+ /* Locate which port this is coming from */
+ rx = ntohs(tag[2]);
+ port = FIELD_GET(YT922X_TAG_RX_PORT_M, rx);
+ skb->dev = dsa_conduit_find_user(netdev, 0, port);
+ if (unlikely(!skb->dev)) {
+ dev_warn_ratelimited(&netdev->dev,
+ "Couldn't decode source port %u\n", port);
+ kfree_skb(skb);
+ return NULL;
+ }
+
+ /* Remove tag and update checksum */
+ skb_pull_rcsum(skb, YT922X_TAG_LEN);
+ dsa_strip_etype_header(skb, YT922X_TAG_LEN);
+
+ return skb;
+}
+
+static const struct dsa_device_ops yt922x_netdev_ops = {
+ .name = YT922X_TAG_NAME,
+ .proto = DSA_TAG_PROTO_YT922X,
+ .xmit = yt922x_tag_xmit,
+ .rcv = yt922x_tag_rcv,
+ .needed_headroom = YT922X_TAG_LEN,
+};
+
+MODULE_DESCRIPTION("DSA tag driver for Motorcomm YT922x switches");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_YT922X, YT922X_TAG_NAME);
+
+module_dsa_tag_driver(yt922x_netdev_ops);
+
--
2.25.1
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH net-next v9 7/7] net: dsa: motorcomm: Add support for Motorcomm YT922x
2026-09-24 9:41 [PATCH net-next v9 0/7] net: dsa: motorcomm: add support yt922x driver Kyle Switch
` (5 preceding siblings ...)
2026-09-24 9:41 ` [PATCH net-next v9 6/7] net: dsa: tag_yt922x: add support for Motorcomm YT922x tags Kyle Switch
@ 2026-09-24 9:41 ` Kyle Switch
2026-09-28 10:39 ` netdev-bot+sashiko
6 siblings, 1 reply; 15+ messages in thread
From: Kyle Switch @ 2026-09-24 9:41 UTC (permalink / raw)
To: andrew, olteanv, davem, edumazet, kuba, pabeni, mmyangfl, horms,
linux, netdev, linux-kernel
Cc: ming.xu, xiaolin.xu, jianmin.wang, wei.zhang, sijia.huang
Add support for Motorcomm YT922X, which is series of
ethernet switches developed by Motorcomm Electronic
Technology, includes YT9224 and YT9228.
This patch only adds support for the YT9224 variant.
YT9228 is not supported yet.
This patch adds basic support for a working DSA switch,
includes .port_setup, .setup, .phylink_get_caps,
.get_tag_protocol.
Signed-off-by: Kyle Switch <kyle.switch@motor-comm.com>
---
drivers/net/dsa/motorcomm/Kconfig | 3 +-
drivers/net/dsa/motorcomm/chip.c | 686 +++++++++++++++++++++++++++++-
drivers/net/dsa/motorcomm/chip.h | 93 ++++
3 files changed, 777 insertions(+), 5 deletions(-)
diff --git a/drivers/net/dsa/motorcomm/Kconfig b/drivers/net/dsa/motorcomm/Kconfig
index 79cdd79a1fd2..ab2b548c216f 100644
--- a/drivers/net/dsa/motorcomm/Kconfig
+++ b/drivers/net/dsa/motorcomm/Kconfig
@@ -1,7 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-only
config NET_DSA_YT921X
- tristate "Motorcomm YT9215 ethernet switch chip support"
+ tristate "Motorcomm YT9215 and YT9224 ethernet switch chip support"
select NET_DSA_TAG_YT921X
+ select NET_DSA_TAG_YT922X
select NET_IEEE8021Q_HELPERS if DCB
help
This enables support for the Motorcomm YT9215 ethernet switch
diff --git a/drivers/net/dsa/motorcomm/chip.c b/drivers/net/dsa/motorcomm/chip.c
index 46f7b94469ea..b0fc2cdbc158 100644
--- a/drivers/net/dsa/motorcomm/chip.c
+++ b/drivers/net/dsa/motorcomm/chip.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
- * Driver for Motorcomm YT921x Switch
+ * Driver for Motorcomm YT921x and YT922x Switch
*
* Should work on YT9213/YT9214/YT9215/YT9218, but only tested on YT9215+SGMII,
* be sure to do your own checks before porting to another chip.
@@ -38,6 +38,9 @@ struct yt921x_mib_desc {
#define MIB_DESC(_size, _offset, _name) \
{_size, _offset, _name}
+#define pcs_to_yt921x_port(_pcs) container_of((_pcs), struct yt921x_port, pcs)
+#define yt921x_port_to_priv(pp) \
+ container_of_const((pp), struct yt921x_priv, ports[(pp)->index])
/* Must agree with yt921x_mib
*
* Unstructured fields (name != NULL) will appear in get_ethtool_stats(),
@@ -112,6 +115,9 @@ struct yt921x_info {
#define YT921X_PORT_MASK_INT0_n(n) GENMASK_U16((n) - 1, 0)
#define YT921X_PORT_MASK_EXT0 BIT(8)
#define YT921X_PORT_MASK_EXT1 BIT(9)
+#define YT922X_PORT_MASK_INTm_n(m, n) GENMASK_U16((n), (m))
+#define YT922X_PORT_MASK_EXT0 BIT(0)
+#define YT922X_PORT_MASK_EXT1 BIT(8)
static const struct yt921x_info yt921x_infos[] = {
{
@@ -149,9 +155,17 @@ static const struct yt921x_info yt921x_infos[] = {
YT921X_PORT_MASK_INT0_n(8),
YT921X_PORT_MASK_EXT0 | YT921X_PORT_MASK_EXT1,
},
+ {
+ "YT9224", YT9224_MAJOR, 0, 0,
+ YT922X_PORT_MASK_INTm_n(4, 7),
+ YT922X_PORT_MASK_EXT0 | YT922X_PORT_MASK_EXT1,
+ },
{}
};
+/* Define top ext addr */
+#define YT922X_COMMON_EXT_PHYADDR 9
+
#define YT921X_VID_UNWARE 4095
/* The interval should be small enough to avoid overflow of 32bit MIBs.
@@ -4240,6 +4254,11 @@ static void yt921x_dsa_teardown(struct dsa_switch *ds)
}
}
+static bool yt921x_needs_extmode_check(u32 major)
+{
+ return (major == YT9224_MAJOR) ? false : true;
+}
+
static int yt921x_chip_detect(struct yt921x_priv *priv)
{
struct device *dev = to_device(priv);
@@ -4265,6 +4284,9 @@ static int yt921x_chip_detect(struct yt921x_priv *priv)
return -ENODEV;
}
+ if (!yt921x_needs_extmode_check(major))
+ goto skip_extmode_check;
+
res = yt921x_reg_read(priv, YT921X_CHIP_MODE, &mode);
if (res)
return res;
@@ -4304,6 +4326,15 @@ static int yt921x_chip_detect(struct yt921x_priv *priv)
priv->info = info;
+ return 0;
+
+skip_extmode_check:
+ dev_info(dev,
+ "Motorcomm %s ethernet switch, chipid: 0x%x\n",
+ info->name, chipid);
+
+ priv->info = info;
+
return 0;
}
@@ -4694,6 +4725,637 @@ static const struct dsa_switch_ops yt921x_dsa_switch_ops = {
.setup = yt921x_dsa_setup,
};
+static int yt922x_port_down(struct yt921x_priv *priv, int port)
+{
+ u32 mask;
+ int res;
+
+ /* mac force down */
+ mask = YT922X_PORT_LINK | YT922X_PORT_RX_MAC_EN |
+ YT922X_PORT_TX_MAC_EN | YT922X_PORT_LINK_AN;
+ res = yt921x_reg_clear_bits(priv, YT922X_PORTn_CTRL(port), mask);
+ if (res)
+ return res;
+ /* Need force op to make soft configuration effective */
+ mask = YT922X_PORT_FORCE_OP;
+ res = yt921x_reg_set_bits(priv, YT922X_PORTn_CTRL(port), mask);
+ if (res)
+ return res;
+
+ /* disable en_phy */
+ res = yt921x_reg_clear_bits(priv, YT922X_EN_PHY_VALUE, BIT(port));
+ if (res)
+ return res;
+ res = yt921x_reg_set_bits(priv, YT922X_EN_PHY_OVERWRITE, BIT(port));
+ if (res)
+ return res;
+
+ return 0;
+}
+
+static void
+yt922x_phylink_mac_link_down(struct phylink_config *config, unsigned int mode,
+ phy_interface_t interface)
+{
+ struct dsa_port *dp = dsa_phylink_to_port(config);
+ struct yt921x_priv *priv = to_yt921x_priv(dp->ds);
+ int port = dp->index;
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt922x_port_down(priv, port);
+ mutex_unlock(&priv->reg_lock);
+
+ if (res)
+ dev_err(dp->ds->dev, "Failed to %s port %d: %i\n", "bring down",
+ port, res);
+}
+
+static int
+yt922x_port_up(struct yt921x_priv *priv, int port, unsigned int mode,
+ phy_interface_t interface, int speed, int duplex,
+ bool tx_pause, bool rx_pause)
+{
+ u32 mask;
+ u32 ctrl;
+ int res;
+
+ switch (speed) {
+ case SPEED_10:
+ ctrl = YT921X_PORT_SPEED_10;
+ break;
+ case SPEED_100:
+ ctrl = YT921X_PORT_SPEED_100;
+ break;
+ case SPEED_1000:
+ ctrl = YT921X_PORT_SPEED_1000;
+ break;
+ case SPEED_2500:
+ ctrl = YT921X_PORT_SPEED_2500;
+ break;
+ case SPEED_5000:
+ ctrl = YT921X_PORT_SPEED_5000;
+ break;
+ case SPEED_10000:
+ ctrl = YT921X_PORT_SPEED_10000;
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (duplex == DUPLEX_FULL)
+ ctrl |= YT922X_PORT_DUPLEX_FULL;
+ if (tx_pause)
+ ctrl |= YT922X_PORT_TX_PAUSE;
+ if (rx_pause)
+ ctrl |= YT922X_PORT_RX_PAUSE;
+ ctrl |= YT922X_PORT_RX_MAC_EN | YT922X_PORT_TX_MAC_EN |
+ YT922X_PORT_CFG_TX_EN | YT922X_PORT_LINK |
+ YT922X_PORT_CFG_RX_EN;
+ ctrl &= ~(YT922X_PORT_FC_AN | YT922X_PORT_LINK_AN);
+ res = yt921x_reg_write(priv, YT922X_PORTn_CTRL(port), ctrl);
+ if (res)
+ return res;
+
+ /* force op */
+ mask = YT922X_PORT_FORCE_OP;
+ res = yt921x_reg_set_bits(priv, YT922X_PORTn_CTRL(port), mask);
+ if (res)
+ return res;
+
+ /* enable en_phy */
+ res = yt921x_reg_set_bits(priv, YT922X_EN_PHY_VALUE, BIT(port));
+ if (res)
+ return res;
+ res = yt921x_reg_set_bits(priv, YT922X_EN_PHY_OVERWRITE, BIT(port));
+ if (res)
+ return res;
+
+ return 0;
+}
+
+static void
+yt922x_phylink_mac_link_up(struct phylink_config *config,
+ struct phy_device *phydev, unsigned int mode,
+ phy_interface_t interface, int speed, int duplex,
+ bool tx_pause, bool rx_pause)
+{
+ struct dsa_port *dp = dsa_phylink_to_port(config);
+ struct yt921x_priv *priv = to_yt921x_priv(dp->ds);
+ int port = dp->index;
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt922x_port_up(priv, port, mode, interface, speed, duplex,
+ tx_pause, rx_pause);
+ mutex_unlock(&priv->reg_lock);
+
+ if (res)
+ dev_err(dp->ds->dev, "Failed to %s port %d: %i\n", "bring up",
+ port, res);
+}
+
+static int
+yt921x_intif_ext_write(struct yt921x_priv *priv, int port, int reg, u16 val)
+{
+ int res;
+
+ if (port > priv->series->max_ports)
+ return -ENODEV;
+
+ res = yt921x_intif_write(priv, port, YT92XX_PAGE_SELECT, reg);
+ if (res)
+ return res;
+
+ res = yt921x_intif_write(priv, port, YT92XX_PAGE, val);
+ if (res)
+ return res;
+
+ return 0;
+}
+
+static int
+yt921x_intif_ext_read(struct yt921x_priv *priv, int port, int reg, u16 *valp)
+{
+ int res;
+
+ if (port > priv->series->max_ports)
+ return -ENODEV;
+
+ res = yt921x_intif_write(priv, port, YT92XX_PAGE_SELECT, reg);
+ if (res)
+ return res;
+
+ res = yt921x_intif_read(priv, port, YT92XX_PAGE, valp);
+ if (res)
+ return res;
+
+ return 0;
+}
+
+static int yt922x_sds_phyaddr_get(int port,
+ enum yt922x_phy_reg_type reg_type)
+{
+ /*
+ * sds phyaddr mapping depend on reg_type
+ */
+ if (!yt922x_port_is_internal_sds(port))
+ return -EOPNOTSUPP;
+ if (reg_type == YT922X_PHY_REG_TYPE_COMMON_EXT)
+ return YT922X_COMMON_EXT_PHYADDR;
+
+ return port;
+}
+
+static void
+yt922x_phylink_mac_config(struct phylink_config *config, unsigned int mode,
+ const struct phylink_link_state *state)
+{
+}
+
+static struct phylink_pcs *
+yt922x_phylink_mac_select_pcs(struct phylink_config *config,
+ phy_interface_t interface)
+{
+ struct dsa_port *dp = dsa_phylink_to_port(config);
+ struct yt921x_priv *priv = to_yt921x_priv(dp->ds);
+
+ switch (interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_1000BASEX:
+ case PHY_INTERFACE_MODE_2500BASEX:
+ case PHY_INTERFACE_MODE_USXGMII:
+ return &priv->ports[dp->index].pcs;
+
+ default:
+ return NULL;
+ }
+}
+
+static const struct phylink_mac_ops yt922x_phylink_mac_ops = {
+ .mac_select_pcs = yt922x_phylink_mac_select_pcs,
+ .mac_link_down = yt922x_phylink_mac_link_down,
+ .mac_link_up = yt922x_phylink_mac_link_up,
+ .mac_config = yt922x_phylink_mac_config,
+};
+
+static void yt922x_pcs_get_state(struct phylink_pcs *pcs, unsigned int neg_mode,
+ struct phylink_link_state *state)
+{
+ struct yt921x_port *pp = pcs_to_yt921x_port(pcs);
+ struct yt921x_priv *priv = yt921x_port_to_priv(pp);
+ int port = pp->index;
+ int res = 0;
+ u16 data;
+ int addr;
+ u16 lp;
+
+ addr = yt922x_sds_phyaddr_get(port, YT922X_PHY_REG_TYPE_MII);
+ if (addr < 0) {
+ state->link = false;
+ return;
+ }
+
+ mutex_lock(&priv->reg_lock);
+ switch (state->interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_1000BASEX:
+ case PHY_INTERFACE_MODE_2500BASEX:
+ res = yt921x_intif_read(priv, addr, MII_BMSR, &data);
+ if (res)
+ goto err;
+ res = yt921x_intif_read(priv, addr, MII_LPA, &lp);
+ if (res)
+ goto err;
+ phylink_mii_c22_pcs_decode_state(state, neg_mode, data, lp);
+ break;
+ case PHY_INTERFACE_MODE_USXGMII:
+ res = yt921x_intif_read(priv, addr, YT922X_PCS_LINK_CTRL,
+ &data);
+ if (res)
+ goto err;
+ state->link = FIELD_GET(YT922X_PCS_LINK_STATUS, data);
+ state->an_complete = FIELD_GET(YT922X_PCS_AN_COMPLETE, data);
+ res = yt921x_intif_read(priv, addr, MII_LPA, &lp);
+ if (res)
+ goto err;
+ if (state->link)
+ phylink_decode_usxgmii_word(state, lp);
+ break;
+ default:
+ state->link = false;
+ break;
+ }
+ mutex_unlock(&priv->reg_lock);
+ return;
+err:
+ mutex_unlock(&priv->reg_lock);
+ state->link = false;
+}
+
+static void yt922x_pcs_an_restart(struct phylink_pcs *pcs)
+{
+ struct yt921x_port *pp = pcs_to_yt921x_port(pcs);
+ struct yt921x_priv *priv = yt921x_port_to_priv(pp);
+ struct device *dev = to_device(priv);
+ int port = pp->index;
+ u16 data;
+ int addr;
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ addr = yt922x_sds_phyaddr_get
+ (port, YT922X_PHY_REG_TYPE_MII);
+ if (addr < 0) {
+ res = addr;
+ goto err;
+ }
+ res = yt921x_intif_read(priv, addr, MII_BMCR, &data);
+ if (res)
+ goto err;
+ data |= BMCR_ANRESTART;
+ res = yt921x_intif_write(priv, addr, MII_BMCR, data);
+ if (res)
+ goto err;
+ mutex_unlock(&priv->reg_lock);
+ return;
+
+err:
+ mutex_unlock(&priv->reg_lock);
+ if (res)
+ dev_err(dev, "Failed to %s PCS port %d: %i\n", "an restart",
+ port, res);
+}
+
+static int yt922x_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
+ phy_interface_t interface,
+ const unsigned long *advertising,
+ bool permit_pause_to_mac)
+{
+ struct yt921x_port *pp = pcs_to_yt921x_port(pcs);
+ struct yt921x_priv *priv = yt921x_port_to_priv(pp);
+ int res, port;
+ u16 data;
+ u16 ctrl;
+ int addr;
+
+ port = pp->index;
+ if (!yt922x_port_is_internal_sds(port))
+ return -EINVAL;
+
+ mutex_lock(&priv->reg_lock);
+ addr = yt922x_sds_phyaddr_get
+ (port, YT922X_PHY_REG_TYPE_SDS_COMMON_EXT);
+ if (addr < 0) {
+ res = addr;
+ goto err;
+ }
+ /* write protect */
+ res = yt921x_intif_ext_write(priv, addr, 0x4be, 0xd);
+ if (res)
+ goto err;
+ switch (interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ ctrl = YT922X_SERDES_MODE_SGMII;
+ break;
+ case PHY_INTERFACE_MODE_1000BASEX:
+ ctrl = YT922X_SERDES_MODE_1000BASEX;
+ break;
+ case PHY_INTERFACE_MODE_2500BASEX:
+ ctrl = YT922X_SERDES_MODE_2500BASEX;
+ break;
+ case PHY_INTERFACE_MODE_USXGMII:
+ ctrl = YT922X_SERDES_MODE_USXGMII;
+ break;
+ default:
+ res = -EINVAL;
+ goto err;
+ }
+ res = yt921x_intif_ext_read(priv, addr, YT922X_PORT_SDSn, &data);
+ if (res)
+ goto err;
+ data &= ~YT922X_SERDES_MODE_M;
+ data |= ctrl;
+ res = yt921x_intif_ext_write(priv, addr, YT922X_PORT_SDSn, data);
+ if (res)
+ goto err;
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+
+err:
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static const struct phylink_pcs_ops yt922x_pcs_ops = {
+ .pcs_get_state = yt922x_pcs_get_state,
+ .pcs_config = yt922x_pcs_config,
+ .pcs_an_restart = yt922x_pcs_an_restart,
+};
+
+static enum dsa_tag_protocol
+yt922x_dsa_get_tag_protocol(struct dsa_switch *ds, int port,
+ enum dsa_tag_protocol m)
+{
+ return DSA_TAG_PROTO_YT922X;
+}
+
+static void
+yt922x_dsa_phylink_get_caps(struct dsa_switch *ds, int port,
+ struct phylink_config *config)
+{
+ struct yt921x_priv *priv = to_yt921x_priv(ds);
+ const struct yt921x_info *info = priv->info;
+
+ config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
+ MAC_10 | MAC_100 | MAC_1000;
+
+ if (info->internal_mask & BIT(port)) {
+ /* port 4 to port 7, internal utp */
+ __set_bit(PHY_INTERFACE_MODE_INTERNAL,
+ config->supported_interfaces);
+ config->mac_capabilities |= MAC_2500FD;
+ }
+ if (info->external_mask & BIT(port)) {
+ /* serdes */
+ __set_bit(PHY_INTERFACE_MODE_SGMII,
+ config->supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_1000BASEX,
+ config->supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_2500BASEX,
+ config->supported_interfaces);
+ config->mac_capabilities |= MAC_2500FD;
+ __set_bit(PHY_INTERFACE_MODE_USXGMII,
+ config->supported_interfaces);
+ config->mac_capabilities |= MAC_5000FD;
+ config->mac_capabilities |= MAC_10000FD;
+ }
+}
+
+static int yt922x_port_setup(struct yt921x_priv *priv, int port)
+{
+ struct dsa_switch *ds = &priv->ds;
+ u32 mask;
+ u32 ctrl;
+ int res;
+
+ /* enable user port isolation and disable fdb learning */
+ ctrl = ~priv->cpu_ports_mask;
+ res = yt921x_reg_write(priv, YT922X_PORTn_ISOLATION(port), ctrl);
+ if (res)
+ return res;
+
+ mask = YT922X_PORT_LEARN_DIS;
+ res = yt921x_reg_set_bits(priv, YT922X_PORTn_LEARN(port), mask);
+ if (res)
+ return res;
+
+ if (dsa_is_cpu_port(ds, port)) {
+ ctrl = ~(u32)0;
+ res = yt921x_reg_write(priv, YT922X_PORTn_ISOLATION(port),
+ ctrl);
+ if (res)
+ return res;
+ }
+
+ return 0;
+}
+
+static int yt922x_dsa_port_setup(struct dsa_switch *ds, int port)
+{
+ struct yt921x_priv *priv = to_yt921x_priv(ds);
+ int res;
+
+ mutex_lock(&priv->reg_lock);
+ res = yt922x_port_setup(priv, port);
+ mutex_unlock(&priv->reg_lock);
+
+ return res;
+}
+
+static int yt922x_cpu_tag_mode_set_8b(struct yt921x_priv *priv)
+{
+ u32 val;
+ u32 val1;
+ int res;
+
+ /* cpu tag mode set to 8b */
+ res = yt921x_reg_read(priv, YT922X_CPU_TAG_RX_CTRL, &val);
+ if (res)
+ return res;
+ res = yt921x_reg_read(priv, YT922X_CPU_TAG_TX_CTRL, &val1);
+ if (res)
+ return res;
+ val &= ~YT922X_CPU_TAG_RX_MODE;
+ val1 &= ~YT922X_CPU_TAG_TX_MODE;
+ val1 &= ~YT922X_CPU_TAG_TX_TYPE;
+ res = yt921x_reg_write(priv, YT922X_CPU_TAG_RX_CTRL, val);
+ if (res)
+ return res;
+ res = yt921x_reg_write(priv, YT922X_CPU_TAG_TX_CTRL, val1);
+ if (res)
+ return res;
+
+ return 0;
+}
+
+static int yt922x_cpu_port_set(struct yt921x_priv *priv)
+{
+ struct dsa_switch *ds = &priv->ds;
+ u32 ctrl;
+ int res;
+
+ /* cpu tag mode */
+ res = yt922x_cpu_tag_mode_set_8b(priv);
+ if (res)
+ return res;
+
+ /* Enable DSA */
+ priv->cpu_ports_mask = dsa_cpu_ports(ds);
+ ctrl = YT921X_EXT_CPU_PORT_TAG_EN | YT921X_EXT_CPU_PORT_PORT_EN |
+ YT921X_EXT_CPU_PORT_PORT(__ffs(priv->cpu_ports_mask));
+ res = yt921x_reg_write(priv, YT921X_EXT_CPU_PORT, ctrl);
+ if (res)
+ return res;
+
+ /* Setup software switch */
+ ctrl = YT922X_CPU_COPY_TO_EXT_CPU;
+ res = yt921x_reg_write(priv, YT922X_CPU_COPY, ctrl);
+ if (res)
+ return res;
+
+ return res;
+}
+
+static int yt922x_chip_setup_dsa(struct yt921x_priv *priv)
+{
+ unsigned long cpu_ports_mask;
+ u32 ctrl;
+ int port;
+ int res;
+
+ /* cpu port set */
+ res = yt922x_cpu_port_set(priv);
+ if (res)
+ return res;
+
+ ctrl = GENMASK_U32(8, 0);
+ res = yt921x_reg_write(priv, YT922X_FILTER_UNK_UCAST, ctrl);
+ if (res)
+ return res;
+
+ ctrl = 0;
+ for (int i = 0; i < priv->series->max_ports; i++)
+ ctrl |= YT922X_ACT_UNK_ACTn_TRAP(i);
+ cpu_ports_mask = priv->cpu_ports_mask;
+ for_each_set_bit(port, &cpu_ports_mask, priv->series->max_ports) {
+ ctrl &= ~YT922X_ACT_UNK_ACTn_M(port);
+ ctrl |= YT922X_ACT_UNK_ACTn_DROP(port);
+ }
+ res = yt921x_reg_write(priv, YT922X_ACT_UNK_UCAST, ctrl);
+ if (res)
+ return res;
+ res = yt921x_reg_write(priv, YT922X_ACT_UNK_MCAST, ctrl);
+ if (res)
+ return res;
+
+ return 0;
+}
+
+static int yt922x_chip_setup(struct yt921x_priv *priv)
+{
+ u32 ctrl;
+ int res;
+
+ ctrl = YT922X_FUNC_MIB | YT922X_FUNC_ACL;
+ res = yt921x_reg_set_bits(priv, YT921X_FUNC, ctrl);
+ if (res)
+ return res;
+
+ res = yt922x_chip_setup_dsa(priv);
+ if (res)
+ return res;
+
+ return 0;
+}
+
+static void yt922x_pcs_setup(struct dsa_switch *ds)
+{
+ struct yt921x_priv *priv = to_yt921x_priv(ds);
+ const struct yt921x_info *info = priv->info;
+ unsigned long mask;
+ int port;
+
+ mask = info->external_mask;
+ for_each_set_bit(port, &mask, priv->series->max_ports) {
+ struct yt921x_port *pp = &priv->ports[port];
+
+ pp->pcs.ops = &yt922x_pcs_ops;
+ pp->pcs.poll = true;
+
+ __set_bit(PHY_INTERFACE_MODE_SGMII,
+ pp->pcs.supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_1000BASEX,
+ pp->pcs.supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_2500BASEX,
+ pp->pcs.supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_USXGMII,
+ pp->pcs.supported_interfaces);
+ }
+}
+
+static int yt922x_dsa_setup(struct dsa_switch *ds)
+{
+ struct yt921x_priv *priv = to_yt921x_priv(ds);
+ struct device *dev = to_device(priv);
+ struct device_node *np = dev->of_node;
+ struct device_node *child;
+ int res;
+
+ /* ports index init */
+ for (size_t i = 0; i < ARRAY_SIZE(priv->ports); i++) {
+ struct yt921x_port *pp = &priv->ports[i];
+
+ pp->index = i;
+ }
+
+ mutex_lock(&priv->reg_lock);
+ res = yt921x_chip_reset(priv);
+ mutex_unlock(&priv->reg_lock);
+ if (res)
+ return res;
+
+ /* Register the internal mdio bus. */
+ child = of_get_child_by_name(np, "mdio");
+ if (child) {
+ res = yt921x_mbus_int_init(priv, child);
+ of_node_put(child);
+ if (res)
+ return res;
+ }
+
+ mutex_lock(&priv->reg_lock);
+ res = yt922x_chip_setup(priv);
+ mutex_unlock(&priv->reg_lock);
+ if (res)
+ return res;
+
+ /* switch sds pcs setup */
+ yt922x_pcs_setup(ds);
+
+ return 0;
+}
+
+static const struct dsa_switch_ops yt922x_dsa_switch_ops = {
+ /* port */
+ .get_tag_protocol = yt922x_dsa_get_tag_protocol,
+ .phylink_get_caps = yt922x_dsa_phylink_get_caps,
+ .port_setup = yt922x_dsa_port_setup,
+ /* chip */
+ .setup = yt922x_dsa_setup,
+};
+
static const struct yt92xx_series yt92xx_series_table[] = {
[YT92XX_MODE_YT921X] = {
.mode = YT92XX_MODE_YT921X,
@@ -4707,12 +5369,26 @@ static const struct yt92xx_series yt92xx_series_table[] = {
.switch_ops = &yt921x_dsa_switch_ops,
.mac_ops = &yt921x_phylink_mac_ops
},
+ [YT92XX_MODE_YT922X] = {
+ .mode = YT92XX_MODE_YT922X,
+ .name = "YT922x",
+ .max_ports = YT922X_PORT_NUM,
+ .num_lag_ids = YT922X_LAG_NUM,
+ .ageing_time_min = 1 * 6000,
+ .ageing_time_max = U16_MAX * 6000,
+ .dscp_prio_mapping_is_global = true,
+ .assisted_learning_on_cpu_port = true,
+ .switch_ops = &yt922x_dsa_switch_ops,
+ .mac_ops = &yt922x_phylink_mac_ops,
+ },
};
static const struct yt92xx_series *yt92xx_series_lookup(u32 major)
{
if (major == YT9215_MAJOR || major == YT9218_MAJOR)
return &yt92xx_series_table[YT92XX_MODE_YT921X];
+ else if (major == YT9224_MAJOR)
+ return &yt92xx_series_table[YT92XX_MODE_YT922X];
else
return NULL;
}
@@ -4830,8 +5506,9 @@ static int yt921x_mdio_probe(struct mdio_device *mdiodev)
}
static const struct of_device_id yt921x_of_match[] = {
- { .compatible = "motorcomm,yt9215" },
- {}
+ { .compatible = "motorcomm,yt9215", },
+ { .compatible = "motorcomm,yt9224", },
+ { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, yt921x_of_match);
@@ -4848,5 +5525,6 @@ static struct mdio_driver yt921x_mdio_driver = {
mdio_module_driver(yt921x_mdio_driver);
MODULE_AUTHOR("David Yang <mmyangfl@gmail.com>");
-MODULE_DESCRIPTION("Driver for Motorcomm YT921x Switch");
+MODULE_AUTHOR("Kyle Switch <kyle.switch@motor-comm.com>");
+MODULE_DESCRIPTION("Driver for Motorcomm YT921x and YT922x Switch");
MODULE_LICENSE("GPL");
diff --git a/drivers/net/dsa/motorcomm/chip.h b/drivers/net/dsa/motorcomm/chip.h
index c446aea449ed..dc8ef6dd4bd5 100644
--- a/drivers/net/dsa/motorcomm/chip.h
+++ b/drivers/net/dsa/motorcomm/chip.h
@@ -111,6 +111,7 @@
#define YT921X_PORT_SPEED_1000 YT921X_PORT_SPEED(2)
#define YT921X_PORT_SPEED_10000 YT921X_PORT_SPEED(3)
#define YT921X_PORT_SPEED_2500 YT921X_PORT_SPEED(4)
+#define YT921X_PORT_SPEED_5000 YT921X_PORT_SPEED(5)
#define YT921X_PON_STRAP_FUNC 0x80320
#define YT921X_PON_STRAP_VAL 0x80324
#define YT921X_PON_STRAP_CAP 0x80328
@@ -837,6 +838,7 @@ enum yt921x_fdb_entry_status {
#define YT9215_MAJOR 0x9002
#define YT9218_MAJOR 0x9001
+#define YT9224_MAJOR 0x9004
/* required for a hard reset */
#define YT921X_RST_DELAY_US 10000
@@ -861,6 +863,96 @@ enum yt921x_fdb_entry_status {
#define yt921x_port_is_internal(port) ((port) < 8)
#define yt921x_port_is_external(port) ((port) == 8 || (port) == 9)
+/* yt922x register lists */
+#define YT92XX_PAGE_SELECT 0x1e
+#define YT92XX_PAGE 0x1f
+#define YT922X_PORTn_STATUS(port) (0x80200 + 4 * (port))
+#define YT922X_PORT_LINK_STATE BIT(8)
+#define YT922X_PORT_LINK_DUPLEX BIT(7)
+#define YT922X_PORT_RX_FC_EN BIT(6)
+#define YT922X_PORT_TX_FC_EN BIT(5)
+#define YT922X_PORT_SPEED_10 0
+#define YT922X_PORT_SPEED_100 1
+#define YT922X_PORT_SPEED_1000 2
+#define YT922X_PORT_SPEED_10000 3
+#define YT922X_PORT_SPEED_2500 4
+#define YT922X_PORT_SPEED_5000 5
+#define YT922X_EN_PHY_OVERWRITE (0x80040)
+#define YT922X_EN_PHY_VALUE (0x8003c)
+/* CTRL: force op to make soft configuration effective */
+#define YT922X_PORTn_CTRL(port) (0x80080 + 4 * (port))
+#define YT922X_PORT_FORCE_OP BIT(14)
+#define YT922X_PORT_CFG_TX_EN BIT(13)
+#define YT922X_PORT_CFG_RX_EN BIT(12)
+#define YT922X_PORT_FC_AN BIT(11)
+#define YT922X_PORT_LINK_AN BIT(10) /* CTRL: auto negotiation */
+#define YT922X_PORT_LINK BIT(9) /* CTRL: link status */
+#define YT922X_PORT_HALF_PAUSE BIT(8) /* Half-duplex back pressure mode */
+#define YT922X_PORT_DUPLEX_FULL BIT(7)
+#define YT922X_PORT_RX_PAUSE BIT(6)
+#define YT922X_PORT_TX_PAUSE BIT(5)
+#define YT922X_PORT_RX_MAC_EN BIT(4)
+#define YT922X_PORT_TX_MAC_EN BIT(3)
+#define YT922X_PORT_SPEED_M GENMASK(2, 0)
+#define YT922X_PORT_SDSn 0x400
+#define YT922X_SERDES_MODE_M GENMASK(6, 4)
+#define YT922X_SERDES_MODE(x) FIELD_PREP(YT922X_SERDES_MODE_M, (x))
+#define YT922X_SERDES_MODE_SGMII YT922X_SERDES_MODE(0)
+#define YT922X_SERDES_MODE_REVSGMII YT922X_SERDES_MODE(1)
+#define YT922X_SERDES_MODE_1000BASEX YT922X_SERDES_MODE(2)
+#define YT922X_SERDES_MODE_100BASEX YT922X_SERDES_MODE(3)
+#define YT922X_SERDES_MODE_2500BASEX YT922X_SERDES_MODE(4)
+#define YT922X_SERDES_MODE_USXGMII YT922X_SERDES_MODE(6)
+#define YT922X_PORT_NUM 9
+#define YT922X_PCS_LINK_CTRL 0x11
+#define YT922X_PCS_LINK_STATUS BIT(10)
+#define YT922X_PCS_AN_COMPLETE BIT(11)
+
+/* LAG */
+#define YT922X_LAG_NUM 4
+/* ISO */
+#define YT922X_PORTn_ISOLATION(port) (0x4 * (port) + 0x180d80)
+/* FDB */
+#define YT922X_PORTn_LEARN(port) (0x180300 + 4 * (port))
+#define YT922X_PORT_LEARN_DIS BIT(18)
+/* GLOBAL CTRL */
+#define YT922X_FUNC_ACL BIT(5)
+#define YT922X_FUNC_MIB BIT(4)
+/* CTRL PKT */
+#define YT922X_FILTER_UNK_UCAST 0x180ec8
+#define YT922X_ACT_UNK_UCAST 0x180ed8
+#define YT922X_ACT_UNK_MCAST 0x180ee0
+#define YT922X_ACT_UNK_MCAST_BYPASS_DROP_PIM BIT(22)
+#define YT922X_ACT_UNK_MCAST_BYPASS_DROP_MLD BIT(21)
+#define YT922X_ACT_UNK_MCAST_BYPASS_DROP_IGMP BIT(20)
+#define YT922X_ACT_UNK_ACTn_M(port) GENMASK(2 * (port) + 1, 2 * (port))
+#define YT922X_ACT_UNK_ACTn(port, x) ((x) << (2 * (port)))
+#define YT922X_ACT_UNK_ACTn_FORWARD(port) YT922X_ACT_UNK_ACTn(port, 0) /* flood */
+#define YT922X_ACT_UNK_ACTn_DROP(port) YT922X_ACT_UNK_ACTn(port, 1) /* discard */
+#define YT922X_ACT_UNK_ACTn_TRAP(port) YT922X_ACT_UNK_ACTn(port, 3) /* steer to CPU */
+
+/* CPU PORT */
+#define YT922X_CPU_COPY 0x181100
+#define YT922X_CPU_COPY_TO_INT_CPU BIT(1)
+#define YT922X_CPU_COPY_TO_EXT_CPU BIT(0)
+#define YT922X_CPU_TAG_RX_CTRL 0x80504
+#define YT922X_CPU_TAG_RX_MODE BIT(0)
+#define YT922X_CPU_TAG_TX_CTRL 0x100710
+#define YT922X_CPU_TAG_TX_TYPE BIT(0)
+#define YT922X_CPU_TAG_TX_MODE BIT(1)
+#define YT922X_CPU_TAG_TX_CTAG_OP BIT(2)
+#define YT922X_CPU_TAG_TX_STAG_OP BIT(3)
+
+#define yt922x_port_is_internal_sds(port) ((port) == 0 || (port) == 8)
+
+enum yt922x_phy_reg_type {
+ YT922X_PHY_REG_TYPE_COMMON_EXT,
+ YT922X_PHY_REG_TYPE_SDS_COMMON_EXT,
+ YT922X_PHY_REG_TYPE_MII,
+ YT922X_PHY_REG_TYPE_EXT,
+ YT922X_PHY_REG_TYPE_MAX
+};
+
struct yt921x_mib {
u64 rx_broadcast;
u64 rx_pause;
@@ -953,6 +1045,7 @@ struct yt921x_port {
struct yt921x_led *leds[YT921X_LED_GROUP_NUM];
#endif
+ struct phylink_pcs pcs;
};
struct yt921x_reg_ops {
--
2.25.1
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH net-next v9 2/7] net: dsa: motorcomm: use max_ports in series for port bounds checking
2026-09-24 9:41 ` [PATCH net-next v9 2/7] net: dsa: motorcomm: use max_ports in series for port bounds checking Kyle Switch
@ 2026-09-24 15:32 ` Andrew Lunn
2026-09-28 10:39 ` netdev-bot+sashiko
1 sibling, 0 replies; 15+ messages in thread
From: Andrew Lunn @ 2026-09-24 15:32 UTC (permalink / raw)
To: Kyle Switch
Cc: olteanv, davem, edumazet, kuba, pabeni, mmyangfl, horms, linux,
netdev, linux-kernel, ming.xu, xiaolin.xu, jianmin.wang,
wei.zhang, sijia.huang
On Thu, Sep 24, 2026 at 05:41:41PM +0800, Kyle Switch wrote:
> Replace the hardcoded YT921X_PORT_NUM macro with the per-series
> max_ports field in port validation. This removes family-specific
> constants from the common code path and simplifies adding new
> switch families with different port counts.
>
> No functional change for existing YT921X devices.
>
> Signed-off-by: Kyle Switch <kyle.switch@motor-comm.com>
For version 8 i gave a Reviewed-by: Andrew Lunn <andrew@lunn.ch>. You
are supposed to add this here. That then tells me i don't need to look
at it again, and it helpers those Maintainers who do the actual merge
decide if the patchset is ready.
b4 will append these for you.
Andrew
---
pw-bot: cr
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH net-next v9 1/7] net: dsa: motorcomm: initialize dsa_switch based on chipid
2026-09-24 9:41 ` [PATCH net-next v9 1/7] net: dsa: motorcomm: initialize dsa_switch based on chipid Kyle Switch
@ 2026-09-28 10:39 ` netdev-bot+sashiko
0 siblings, 0 replies; 15+ messages in thread
From: netdev-bot+sashiko @ 2026-09-28 10:39 UTC (permalink / raw)
To: kyle.switch
Cc: andrew, olteanv, davem, edumazet, kuba, pabeni, mmyangfl, horms,
linux, netdev, linux-kernel, ming.xu, xiaolin.xu, jianmin.wang,
wei.zhang, sijia.huang
Thank you for your contribution! Sashiko AI review found 4 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 3
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] yt92xx_register_switch() in drivers/net/dsa/motorcomm/chip.c
declares `u32 chipid;` with no initializer.
- [Low] The commit message does not match the patch.
- [Low] At this commit, enum yt92xx_mode declares YT92XX_MODE_YT922X and
YT92XX_MODE_MAX, but yt92xx_series_table[] fills only…
- [Low] yt921x_mdio_probe() calls mutex_init(&priv->reg_lock)
(chip.c:4806) and now returns early if yt92xx_register_switch() fails…
--
net: dsa: motorcomm: initialize dsa_switch based on chipid
This patch moves the dsa_switch setup out of yt921x_mdio_probe() into a
new yt92xx_register_switch() helper. The helper reads the chip ID at probe
time and fills in the dsa_switch fields from a per-series table entry.
> Replace the hardcoded chip handling in yt92xx_register_switch() with a
> data-driven loopup based on the chip ID.
[Severity: Low]
Is this description accurate? yt92xx_register_switch() is added by this
patch. The hardcoded ds-> assignments it removes were in
yt921x_mdio_probe().
There's also a typo: "loopup" should be "lookup".
The lookup is also not really data-driven. yt92xx_series_lookup() writes
the majors into an if-statement:
if (major == YT9215_MAJOR || major == YT9218_MAJOR)
return &yt92xx_series_table[YT92XX_MODE_YT921X];
yt921x_mdio_probe() now reads YT921X_CHIP_ID over MDIO. It can fail with
an I/O error or -ENODEV before dsa_register_switch() is reached. Before
this patch, the chip ID was only read later, from yt921x_chip_detect() in
the DSA setup path. Could the message mention this change?
It could also say this is groundwork for YT922X, since nothing in this
patch uses YT92XX_MODE_YT922X or YT92XX_MODE_MAX.
> diff --git a/drivers/net/dsa/motorcomm/chip.c b/drivers/net/dsa/motorcomm/chip.c
> index 8b0ac5fc8c8b..d4a565994940 100644
> --- a/drivers/net/dsa/motorcomm/chip.c
> +++ b/drivers/net/dsa/motorcomm/chip.c
> @@ -4679,6 +4679,68 @@ static const struct dsa_switch_ops yt921x_dsa_switch_ops = {
[ ... ]
> +static int yt92xx_register_switch(struct dsa_switch *ds)
> +{
> + struct yt921x_priv *priv = to_yt921x_priv(ds);
> + struct device *dev = to_device(priv);
> + const struct yt92xx_series *series;
> + u32 chipid;
> + u32 major;
> + int res;
> +
> + mutex_lock(&priv->reg_lock);
> + res = yt921x_reg_read(priv, YT921X_CHIP_ID, &chipid);
> + mutex_unlock(&priv->reg_lock);
> + if (res) {
> + dev_err(dev, "Unexpected chipid 0x%x\n", chipid);
[Severity: Medium]
Can chipid be uninitialized when it is printed here? yt921x_reg_read()
ends up in yt921x_reg_mdio_read(), which stores to *valp only on success:
drivers/net/dsa/motorcomm/chip.c:yt921x_reg_mdio_read() {
...
res = __mdiobus_read(bus, addr, reg_data);
if (res < 0)
goto end;
val = (val << 16) | (u16)res;
*valp = val;
res = 0;
end:
...
}
If any of the MDIO accesses fail, this prints leftover stack contents as
the chip ID, and a bus failure looks like an ID mismatch.
The message seems to come from yt921x_chip_detect(). There it is printed
only after a successful read with an unknown major:
drivers/net/dsa/motorcomm/chip.c:yt921x_chip_detect() {
...
res = yt921x_reg_read(priv, YT921X_CHIP_ID, &chipid);
if (res)
return res;
...
if (!info->name) {
dev_err(dev, "Unexpected chipid 0x%x\n", chipid);
return -ENODEV;
}
...
}
Should the read failure get its own message? The chip ID could then be
printed in the unknown-major branch below, which currently leaves it out.
This code is unchanged at the end of the series, in "net: dsa: motorcomm:
Add support for Motorcomm YT922x".
> + return res;
> + }
> +
> + major = FIELD_GET(YT921X_CHIP_ID_MAJOR, chipid);
> + series = yt92xx_series_lookup(major);
> + if (!series) {
> + dev_err(dev, "Undefined series info for the corresponding major\n");
> + return -ENODEV;
> + }
[ ... ]
> @@ -4754,15 +4817,10 @@ static int yt921x_mdio_probe(struct mdio_device *mdiodev)
>
> ds = &priv->ds;
> ds->dev = dev;
> - ds->assisted_learning_on_cpu_port = true;
> - ds->dscp_prio_mapping_is_global = true;
> ds->priv = priv;
> - ds->ops = &yt921x_dsa_switch_ops;
> - ds->ageing_time_min = 1 * 5000;
> - ds->ageing_time_max = U16_MAX * 5000;
> - ds->phylink_mac_ops = &yt921x_phylink_mac_ops;
> - ds->num_lag_ids = YT921X_LAG_NUM;
> - ds->num_ports = YT921X_PORT_NUM;
> + res = yt92xx_register_switch(ds);
> + if (res)
> + return res;
[Severity: Low]
This isn't a bug, but this new early return comes after
mutex_init(&priv->reg_lock) and skips mutex_destroy(). The only
mutex_destroy() call is in yt921x_mdio_remove(), which doesn't run when
probe fails.
Nothing leaks. priv is devm allocated, and mutex_destroy() only poisons
the magic field when CONFIG_DEBUG_MUTEXES is set. The existing
dsa_register_switch() failure path already skips it too.
Is it worth adding mutex_destroy() to the probe error paths for symmetry?
[ ... ]
> diff --git a/drivers/net/dsa/motorcomm/chip.h b/drivers/net/dsa/motorcomm/chip.h
> index 83cd454955dd..c446aea449ed 100644
> --- a/drivers/net/dsa/motorcomm/chip.h
> +++ b/drivers/net/dsa/motorcomm/chip.h
> @@ -960,9 +960,29 @@ struct yt921x_reg_ops {
> int (*write)(void *context, u32 reg, u32 val);
> };
>
> +enum yt92xx_mode {
> + YT92XX_MODE_YT921X,
> + YT92XX_MODE_YT922X,
> + YT92XX_MODE_MAX,
> +};
> +
> +struct yt92xx_series {
> + enum yt92xx_mode mode;
> + const char *name;
[Severity: Low]
At this commit, yt92xx_series_table[] fills only [YT92XX_MODE_YT921X].
There is no table entry for YT92XX_MODE_YT922X, and no BUILD_BUG_ON ties
the table size to YT92XX_MODE_MAX. The later patch "net: dsa: motorcomm:
Add support for Motorcomm YT922x" adds the YT922X entry, so this part is
resolved by the end of the series.
Are the mode and name fields needed? Nothing reads series->mode or
series->name, even at the end of the series. The name field also
duplicates yt921x_info.name, which is the name that gets printed.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260924094146.4128703-1-kyle.switch%40motor-comm.com
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH net-next v9 2/7] net: dsa: motorcomm: use max_ports in series for port bounds checking
2026-09-24 9:41 ` [PATCH net-next v9 2/7] net: dsa: motorcomm: use max_ports in series for port bounds checking Kyle Switch
2026-09-24 15:32 ` Andrew Lunn
@ 2026-09-28 10:39 ` netdev-bot+sashiko
1 sibling, 0 replies; 15+ messages in thread
From: netdev-bot+sashiko @ 2026-09-28 10:39 UTC (permalink / raw)
To: kyle.switch
Cc: andrew, olteanv, davem, edumazet, kuba, pabeni, mmyangfl, horms,
linux, netdev, linux-kernel, ming.xu, xiaolin.xu, jianmin.wang,
wei.zhang, sijia.huang
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] Concern: the commit overclaims, and nothing ties
series->max_ports to the fixed-size port storage.
Pre-existing issues:
- [Low] Concern: yt921x_mbus_int_read()/write() and phy_mask bound the
internal MDIO bus by the total DSA port count (series->max_ports = 11…
--
net: dsa: motorcomm: use max_ports in series for port bounds checking
This patch replaces the hardcoded YT921X_PORT_NUM with the per-series
priv->series->max_ports in three places: the internal MDIO bus bounds
checks in yt921x_mbus_int_read() and yt921x_mbus_int_write(), and the
phy_mask computed in yt921x_mbus_int_init().
> diff --git a/drivers/net/dsa/motorcomm/chip.c b/drivers/net/dsa/motorcomm/chip.c
> index d4a565994940..b95f3c355511 100644
> --- a/drivers/net/dsa/motorcomm/chip.c
> +++ b/drivers/net/dsa/motorcomm/chip.c
> @@ -357,7 +357,7 @@ static int yt921x_mbus_int_read(struct mii_bus *mbus, int port, int reg)
> u16 val;
> int res;
>
> - if (port >= YT921X_PORT_NUM)
> + if (port >= priv->series->max_ports)
> return U16_MAX;
[Severity: Low]
The commit message says this "removes family-specific constants from the
common code path". Nothing in the patch ties series->max_ports to the size
of the per-port storage, though. Is that intended?
struct yt921x_priv still sizes its port array with the YT921x constant:
drivers/net/dsa/motorcomm/chip.h:struct yt921x_priv {
...
struct yt921x_port ports[YT921X_PORT_NUM];
...
};
YT921X_PORT_NUM also still bounds loops in these functions:
yt921x_lag_set()
yt921x_isolate()
yt921x_bridge() (twice)
yt921x_chip_setup_dsa() (twice)
Meanwhile ds->num_ports now comes from priv->series->max_ports.
With the current table this can't happen. At the end of the series,
YT921X has max_ports = 11 and YT922X has max_ports = 9, so both fit. The
remaining YT921X_PORT_NUM loops are also only reachable from YT921x-only
code.
If a future series entry had max_ports greater than YT921X_PORT_NUM,
though, could priv->ports[] be indexed out of bounds? Would a
BUILD_BUG_ON() or a probe-time check that max_ports is no larger than
ARRAY_SIZE(priv->ports) be worth adding?
[ ... ]
> @@ -402,7 +403,8 @@ yt921x_mbus_int_init(struct yt921x_priv *priv, struct device_node *mnp)
> mbus->read = yt921x_mbus_int_read;
> mbus->write = yt921x_mbus_int_write;
> mbus->parent = dev;
> - mbus->phy_mask = (u32)~GENMASK(YT921X_PORT_NUM - 1, 0);
> + max_ports = priv->series->max_ports;
> + mbus->phy_mask = (u32)~GENMASK(max_ports - 1, 0);
[Severity: Low]
This is a pre-existing issue, and this patch didn't introduce it. It
shows up here because the bound is being changed. max_ports counts every
DSA port, not only the integrated PHYs:
drivers/net/dsa/motorcomm/chip.h:
/* 8 internal + 2 external + 1 mcu */
#define YT921X_PORT_NUM 11
As a result, yt921x_mbus_int_read() and yt921x_mbus_int_write() still
allow internal MDIO bus transactions to addresses 8-10 if the DT puts a
PHY there. The phy_mask computed here doesn't limit scanning either,
because __of_mdiobus_register() overwrites it with ~0.
The baseline used the same value, so "No functional change for existing
YT921X devices" is accurate.
Bounding by info->internal_mask may not be right either. Later in the
series, YT922x uses internal bus addresses that are not integrated UTP
PHYs, via yt922x_sds_phyaddr_get(): SerDes addresses 0 and 8, and
YT922X_COMMON_EXT_PHYADDR (9).
Should the internal bus bound describe the addresses that actually exist
on that bus, rather than the total port count? Or is using max_ports here
intentional?
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260924094146.4128703-1-kyle.switch%40motor-comm.com
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH net-next v9 4/7] net: dsa: motorcomm: move mib start from probe() to dsa_setup()
2026-09-24 9:41 ` [PATCH net-next v9 4/7] net: dsa: motorcomm: move mib start from probe() to dsa_setup() Kyle Switch
@ 2026-09-28 10:39 ` netdev-bot+sashiko
0 siblings, 0 replies; 15+ messages in thread
From: netdev-bot+sashiko @ 2026-09-28 10:39 UTC (permalink / raw)
To: kyle.switch
Cc: andrew, olteanv, davem, edumazet, kuba, pabeni, mmyangfl, horms,
linux, netdev, linux-kernel, ming.xu, xiaolin.xu, jianmin.wang,
wei.zhang, sijia.huang
Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider.
Critical: 0 · High: 1 · Medium: 1 (1) · Low: 1 (1)
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] With this patch, INIT_DELAYED_WORK() runs on every
yt921x_dsa_setup() call.
- [Medium] After this patch, INIT_DELAYED_WORK(&pp->mib_read,
yt921x_poll_mib) runs only in yt921x_dsa_setup() (chip.c:4556-4562).
- [Low] The subject line, "move mib start from probe() to dsa_setup()",
does not describe the change.
Pre-existing issues:
- [Medium] yt921x_dsa_setup() hard-resets the chip (yt921x_chip_reset)
and yt921x_chip_setup() then clears all hardware MIB counters…
- [Low] yt921x_phylink_mac_link_down() stops MIB polling with non-sync
cancel_delayed_work(&priv->ports[port].mib_read) (chip.c:3962).
--
net: dsa: motorcomm: move mib start from probe() to dsa_setup()
This patch moves the per-port pp->index assignment and the
INIT_DELAYED_WORK() of the MIB polling work out of yt921x_mdio_probe()
and into the DSA .setup() callback, yt921x_dsa_setup(). The works are
now initialized once per .setup() call rather than once per probe.
> net: dsa: motorcomm: move mib start from probe() to dsa_setup()
[Severity: Low]
This isn't a bug, but does the subject line match the change? Neither
probe() nor .setup() starts MIB polling. The patch only moves
INIT_DELAYED_WORK(). Polling starts in yt921x_phylink_mac_link_up(),
which this patch leaves alone:
schedule_delayed_work(&priv->ports[port].mib_read, 0);
> Move the delayed work initialization of the per-port MIB polling
> from yt921x_mdio_probe() into the DSA .setup() callback, changing
> its lifetime from once per probe() to once per DSA .setup().
The body repeats what the diff does but doesn't say why the change is
needed. It also doesn't mention that yt921x_mdio_remove() still expects
the works to be initialized at probe time until the next patch in the
series.
Could the message give the reason for the change, and either mention
that dependency or squash this with "net: dsa: motorcomm: move mib stop
from remove() to teardown()"?
> diff --git a/drivers/net/dsa/motorcomm/chip.c b/drivers/net/dsa/motorcomm/chip.c
> index 6f2606fd7807..d9d73bb7cb23 100644
> --- a/drivers/net/dsa/motorcomm/chip.c
> +++ b/drivers/net/dsa/motorcomm/chip.c
> @@ -4553,6 +4553,14 @@ static int yt921x_dsa_setup(struct dsa_switch *ds)
> struct device_node *child;
> int res;
>
> + /* mib polling init */
> + for (size_t i = 0; i < ARRAY_SIZE(priv->ports); i++) {
> + struct yt921x_port *pp = &priv->ports[i];
> +
> + pp->index = i;
> + INIT_DELAYED_WORK(&pp->mib_read, yt921x_poll_mib);
[Severity: High]
Now that this runs on every yt921x_dsa_setup() call, can it
re-initialize a mib_read work that is still armed or queued?
In a multi-switch tree, removing one member goes through
dsa_switch_remove()->dsa_tree_teardown() for the whole tree. When that
member registers again, ds->ops->setup() runs again on the switches
that stayed.
At this commit, nothing on the teardown side stops mib_read
synchronously. yt921x_dsa_teardown() doesn't touch the work.
yt921x_phylink_mac_link_down() only does a non-sync cancel:
/* No need to sync; port control block is hold until device remove */
cancel_delayed_work(&priv->ports[port].mib_read);
yt921x_poll_mib() also always re-arms itself:
schedule_delayed_work(&pp->mib_read, delay);
If the work is still pending when .setup() runs again,
INIT_DELAYED_WORK() resets timer->entry.pprev, work->entry and
work->data while they are still linked into the timer wheel or the pool
worklist. Could that corrupt those lists?
The next patch, "net: dsa: motorcomm: move mib stop from remove() to
teardown()", adds disable_delayed_work_sync() for every port to
yt921x_dsa_teardown(). That fixes this in the final state of the
series, but this intermediate commit is still affected.
[Severity: Low]
This is a pre-existing issue, and this patch doesn't introduce it.
Related to the above, can MIB polling keep running on a port whose link
is down?
If yt921x_phylink_mac_link_down() runs while yt921x_poll_mib() is
executing, the pending bit is already clear, so cancel_delayed_work()
does nothing. The handler then re-queues itself with
schedule_delayed_work() after dropping reg_lock. It never checks link
state first.
Polling on that port would then go on until the next teardown or
remove.
> + }
> +
> mutex_lock(&priv->reg_lock);
> res = yt921x_chip_reset(priv);
> mutex_unlock(&priv->reg_lock);
[Severity: Medium]
This isn't a bug introduced by this patch, but what happens to the
software MIB counters on a second .setup() for the same probe?
yt921x_chip_reset() is followed by yt921x_chip_setup(), which clears
the hardware counters:
/* Clear MIB */
ctrl = YT921X_MIB_CTRL_CLEAN | YT921X_MIB_CTRL_ALL_PORT;
pp->mib, pp->rx_frames and pp->tx_frames are only zeroed once, by
devm_kzalloc() in probe. yt921x_read_mib() says "MIB buffer must be
zeroed before", and it extends 32-bit counters like this:
val = (old_val & ~(u64)U32_MAX) | val0;
if (val < old_val)
val += 1ull << 32;
Would the first poll after re-setup read the hardware clear as a wrap
and add 2^32 to each 32-bit counter? The 64-bit counters would go
backwards instead.
Since this loop now runs on every .setup(), could it also reset the
software accumulators?
> @@ -4810,13 +4818,6 @@ static int yt921x_mdio_probe(struct mdio_device *mdiodev)
> priv->reg_ops = &yt921x_reg_ops_mdio;
> priv->reg_ctx = mdio;
>
> - for (size_t i = 0; i < ARRAY_SIZE(priv->ports); i++) {
> - struct yt921x_port *pp = &priv->ports[i];
> -
> - pp->index = i;
> - INIT_DELAYED_WORK(&pp->mib_read, yt921x_poll_mib);
> - }
> -
[Severity: Medium]
With this loop gone from yt921x_mdio_probe(), can yt921x_mdio_remove()
run on delayed works that were never initialized?
dsa_register_switch() can succeed without ever calling
ds->ops->setup(), because dsa_tree_setup() returns early when the tree
is incomplete:
net/dsa/dsa.c:dsa_tree_setup() {
...
complete = dsa_tree_setup_routing_table(dst);
if (!complete)
return 0;
...
}
yt921x_mdio_remove() still does this for every port:
for (size_t i = ARRAY_SIZE(priv->ports); i-- > 0; ) {
struct yt921x_port *pp = &priv->ports[i];
disable_delayed_work_sync(&pp->mib_read);
}
If the device is unbound in that state, the path
disable_delayed_work_sync()->__cancel_work_sync()->__flush_work() would
hit WARN_ON(!work->func) once for each port.
The follow-up patch, "net: dsa: motorcomm: move mib stop from remove()
to teardown()", moves this loop into yt921x_dsa_teardown(). DSA skips
that callback when ds->setup is false, so the final state of the series
doesn't have this problem. Only this intermediate commit breaks
bisection.
Also, in that follow-up patch, yt921x_dsa_teardown() seems to use priv
outside the #if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS) block where priv
is declared. Does it build with CONFIG_NET_DSA_YT921X_LEDS=n?
> ds = &priv->ds;
> ds->dev = dev;
> ds->priv = priv;
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260924094146.4128703-1-kyle.switch%40motor-comm.com
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH net-next v9 5/7] net: dsa: motorcomm: move mib stop from remove() to teardown()
2026-09-24 9:41 ` [PATCH net-next v9 5/7] net: dsa: motorcomm: move mib stop from remove() to teardown() Kyle Switch
@ 2026-09-28 10:39 ` netdev-bot+sashiko
0 siblings, 0 replies; 15+ messages in thread
From: netdev-bot+sashiko @ 2026-09-28 10:39 UTC (permalink / raw)
To: kyle.switch
Cc: andrew, olteanv, davem, edumazet, kuba, pabeni, mmyangfl, horms,
linux, netdev, linux-kernel, ming.xu, xiaolin.xu, jianmin.wang,
wei.zhang, sijia.huang
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] Build failure when CONFIG_NET_DSA_YT921X_LEDS is off.
--
net: dsa: motorcomm: move mib stop from remove() to teardown()
This commit moves the loop that disables the per-port mib_read delayed
work out of yt921x_mdio_remove() and into yt921x_dsa_teardown(). The goal
is to match the MIB start, which already happens in setup().
> diff --git a/drivers/net/dsa/motorcomm/chip.c b/drivers/net/dsa/motorcomm/chip.c
> index d9d73bb7cb23..46f7b94469ea 100644
> --- a/drivers/net/dsa/motorcomm/chip.c
> +++ b/drivers/net/dsa/motorcomm/chip.c
> @@ -4233,6 +4233,11 @@ static void yt921x_dsa_teardown(struct dsa_switch *ds)
>
> yt921x_leds_remove(priv);
> #endif
> + for (size_t i = ARRAY_SIZE(priv->ports); i-- > 0; ) {
> + struct yt921x_port *pp = &priv->ports[i];
> +
> + disable_delayed_work_sync(&pp->mib_read);
> + }
> }
[Severity: Medium]
Does this build when CONFIG_NET_DSA_YT921X_LEDS is disabled?
In yt921x_dsa_teardown(), priv is only declared inside the conditional
block:
static void yt921x_dsa_teardown(struct dsa_switch *ds)
{
#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)
struct yt921x_priv *priv = to_yt921x_priv(ds);
yt921x_leds_remove(priv);
#endif
for (size_t i = ARRAY_SIZE(priv->ports); i-- > 0; ) {
...
The new loop comes after the #endif and uses priv in both
ARRAY_SIZE(priv->ports) and &priv->ports[i]. With LEDS=n, priv is not
declared, so this looks like it fails with "'priv' undeclared".
NET_DSA_YT921X_LEDS is an optional bool in
drivers/net/dsa/motorcomm/Kconfig:
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
So it is off when LEDS_CLASS=n, when LEDS_CLASS=m with NET_DSA_YT921X=y,
or when a user turns it off. allmodconfig and allyesconfig still enable
it, so they would not catch this. Randconfig or minimal configs could.
The teardown code is still the same at the end of the series, in "net:
dsa: motorcomm: Add support for Motorcomm YT922x".
Could priv be declared before the #if, with only the yt921x_leds_remove()
call kept inside the conditional?
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260924094146.4128703-1-kyle.switch%40motor-comm.com
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH net-next v9 6/7] net: dsa: tag_yt922x: add support for Motorcomm YT922x tags
2026-09-24 9:41 ` [PATCH net-next v9 6/7] net: dsa: tag_yt922x: add support for Motorcomm YT922x tags Kyle Switch
@ 2026-09-28 10:39 ` netdev-bot+sashiko
0 siblings, 0 replies; 15+ messages in thread
From: netdev-bot+sashiko @ 2026-09-28 10:39 UTC (permalink / raw)
To: kyle.switch
Cc: andrew, olteanv, davem, edumazet, kuba, pabeni, mmyangfl, horms,
linux, netdev, linux-kernel, ming.xu, xiaolin.xu, jianmin.wang,
wei.zhang, sijia.huang
Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 2
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] Claimed: yt922x_tag_rcv() never calls
dsa_default_offload_fwd_mark() or decodes a forward/trap code, so…
- [Low] net/dsa/tag_yt922x.c adds a tag driver whose file is not listed
in any vendor MAINTAINERS entry.
- [Low] The comment block in net/dsa/tag_yt922x.c says 'To define the
from cpu tag format 8 bytes:' and then describes nothing; the #defines…
--
net: dsa: tag_yt922x: add support for Motorcomm YT922x tags
This commit adds a DSA tag driver for Motorcomm YT922x switches. The 8
byte tag reuses the ETH_P_YT921X EtherType (0x9988). On transmit it
carries the destination port mask and priority, and on receive it
carries the source port.
> diff --git a/net/dsa/tag_yt922x.c b/net/dsa/tag_yt922x.c
> new file mode 100644
> index 000000000000..006a6bda90eb
> --- /dev/null
> +++ b/net/dsa/tag_yt922x.c
> @@ -0,0 +1,110 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Motorcomm YT922x Switch Extended CPU Port Tagging
> + *
> + * Copyright (c) 2026 Kyle switch <kyle.switch@motor-comm.com>
> + *
> + */
[Severity: Low]
Should this new file be added to MAINTAINERS?
The Motorcomm switch entry lists drivers/net/dsa/motorcomm/ and the
YT921x tagger, but not net/dsa/tag_yt922x.c:
MAINTAINERS:
MOTORCOMM YT921X ETHERNET SWITCH DRIVER
...
F: drivers/net/dsa/motorcomm/
F: net/dsa/tag_yt921x.c
Right now only the generic net/dsa/ entry covers the file, so
get_maintainer.pl won't send patches for it to the Motorcomm
maintainers. The author of the new file isn't listed in that entry
either.
> +
> +#include <linux/etherdevice.h>
> +
> +#include "tag.h"
> +
> +#define YT922X_TAG_LEN 8
> +
> +/*
> + * To define the from cpu tag format 8 bytes:
> + */
> +#define YT922X_TAG_NAME "yt922x"
> +#define YT922X_TAG_PORTMASK_0 BIT(15)
> +#define YT922X_TAG_PORTMASK_M GENMASK(8, 0)
> +#define YT922X_TAG_PORTS(x) FIELD_PREP(YT922X_TAG_PORTMASK_M, (x))
> +#define YT922X_TAG_FORCE_DST BIT(9)
> +#define YT922X_TAG_PRIO_M GENMASK(12, 10)
> +#define YT922X_TAG_PRIO_EN BIT(13)
> +#define YT922X_TAG_PRIO(x) (FIELD_PREP(YT922X_TAG_PRIO_M, (x)) | YT922X_TAG_PRIO_EN)
> +#define YT922X_TAG_RX_PORT_M GENMASK(5, 2)
[Severity: Low]
This isn't a bug, but the comment says "To define the from cpu tag
format 8 bytes:" and then no format description follows.
YT922X_TAG_RX_PORT_M is also under the from cpu heading, but only
yt922x_tag_rcv() uses it, to parse the to cpu direction.
Could this get a layout description like the one in tag_yt921x.c (the
DA/SA/TagET/Tag diagram plus a description of each field), covering
both directions?
With that, the TX encoding could be checked against the hardware
format: ports 1..8 in tag[1] bits 8:0, port 0 in tag[2] bit 15, and
the FORCE_DST, PRIO and PRIO_EN bits. The same goes for the RX port
field in tag[2] bits 5:2.
[ ... ]
> +static struct sk_buff *
> +yt922x_tag_rcv(struct sk_buff *skb, struct net_device *netdev)
> +{
[ ... ]
> + /* Remove tag and update checksum */
> + skb_pull_rcsum(skb, YT922X_TAG_LEN);
> + dsa_strip_etype_header(skb, YT922X_TAG_LEN);
> +
> + return skb;
> +}
[Severity: Medium]
yt922x_tag_rcv() never calls dsa_default_offload_fwd_mark(), and it
doesn't decode a forward or trap reason from the tag. The sibling
tagger does this for frames the hardware has already forwarded:
net/dsa/tag_yt921x.c:yt921x_tag_rcv() {
...
case YT921X_TAG_CODE_FORWARD:
case YT921X_TAG_CODE_PORT_COPY:
case YT921X_TAG_CODE_FDB_COPY:
/* Already forwarded by hardware */
dsa_default_offload_fwd_mark(skb);
...
}
With the current series this doesn't seem to be reachable. The
yt922x_dsa_switch_ops added later in the series ("net: dsa: motorcomm:
Add support for Motorcomm YT922x") has no .port_bridge_join. That means
dsa_switch_bridge_join() returns -EOPNOTSUPP and bridging falls back to
software.
yt922x_port_setup() also limits each user port to the CPU port:
drivers/net/dsa/motorcomm/chip.c:yt922x_port_setup() {
...
ctrl = ~priv->cpu_ports_mask;
res = yt921x_reg_write(priv, YT922X_PORTn_ISOLATION(port), ctrl);
...
}
Learning is also disabled, and unknown unicast and multicast are
trapped to the CPU. So leaving offload_fwd_mark at 0 is correct for now.
If bridge offload is added for YT922x later, would the software bridge
forward frames the switch has already forwarded, and send duplicate
flooded frames on bridged ports? If the tag carries a YT922x forward
reason, would it make sense to decode it before bridge offload is
added?
skb->priority is also not set on receive, but that is optional and many
taggers don't set it.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260924094146.4128703-1-kyle.switch%40motor-comm.com
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH net-next v9 7/7] net: dsa: motorcomm: Add support for Motorcomm YT922x
2026-09-24 9:41 ` [PATCH net-next v9 7/7] net: dsa: motorcomm: Add support for Motorcomm YT922x Kyle Switch
@ 2026-09-28 10:39 ` netdev-bot+sashiko
0 siblings, 0 replies; 15+ messages in thread
From: netdev-bot+sashiko @ 2026-09-28 10:39 UTC (permalink / raw)
To: kyle.switch
Cc: andrew, olteanv, davem, edumazet, kuba, pabeni, mmyangfl, horms,
linux, netdev, linux-kernel, ming.xu, xiaolin.xu, jianmin.wang,
wei.zhang, sijia.huang
Thank you for your contribution! Sashiko AI review found 6 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 3 · Low: 3
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] The driver now matches the compatible "motorcomm,yt9224" in
yt921x_of_match, but the patch does not document it.
- [Medium] yt922x_pcs_config() does not follow the phylink pcs_config()
contract.
- [Medium] net: dsa: motorcomm: register overwrite in yt922x_port_up()
- [Low] The YT92XX_MODE_YT922X entry in yt92xx_series_table sets
assisted_learning_on_cpu_port, dscp_prio_mapping_is_global,…
- [Low] Several strings touched or reused by this patch still describe
YT921x only: - Kconfig: the NET_DSA_YT921X prompt now says 'YT9215 and…
- [Low] yt921x_dsa_teardown() fails to compile when
CONFIG_NET_DSA_YT921X_LEDS=n.
--
net: dsa: motorcomm: Add support for Motorcomm YT922x
This adds basic DSA support for the Motorcomm YT9224 switch to the
existing yt921x driver. YT9224 gets its own dsa_switch_ops, its own
phylink MAC ops and a SerDes PCS on ports 0 and 8. YT9228 is not
supported yet.
> diff --git a/drivers/net/dsa/motorcomm/Kconfig b/drivers/net/dsa/motorcomm/Kconfig
> index 79cdd79a1fd25..ab2b548c216f3 100644
> --- a/drivers/net/dsa/motorcomm/Kconfig
> +++ b/drivers/net/dsa/motorcomm/Kconfig
> @@ -1,7 +1,8 @@
> # SPDX-License-Identifier: GPL-2.0-only
> config NET_DSA_YT921X
> - tristate "Motorcomm YT9215 ethernet switch chip support"
> + tristate "Motorcomm YT9215 and YT9224 ethernet switch chip support"
> select NET_DSA_TAG_YT921X
> + select NET_DSA_TAG_YT922X
> select NET_IEEE8021Q_HELPERS if DCB
> help
> This enables support for the Motorcomm YT9215 ethernet switch
[Severity: Low]
This isn't a bug, but the prompt now mentions YT9224 while the help text
still says "This enables support for the Motorcomm YT9215 ethernet switch
chip."
A few other strings still describe only YT921x:
- The chip.c file header now says "Driver for Motorcomm YT921x and YT922x
Switch". It is still followed by "Should work on
YT9213/YT9214/YT9215/YT9218, but only tested on YT9215+SGMII".
- The internal_mask comment in struct yt921x_info still reads "Ports with
integral GbE PHYs". yt922x_dsa_phylink_get_caps() advertises MAC_2500FD
on those ports.
- yt921x_mbus_int_init() still names the bus "YT921x internal MDIO bus"
when it runs on YT922x.
> diff --git a/drivers/net/dsa/motorcomm/chip.c b/drivers/net/dsa/motorcomm/chip.c
> index 46f7b94469ead..b0fc2cdbc1580 100644
> --- a/drivers/net/dsa/motorcomm/chip.c
> +++ b/drivers/net/dsa/motorcomm/chip.c
[ ... ]
> @@ -4240,6 +4254,11 @@ static void yt921x_dsa_teardown(struct dsa_switch *ds)
> }
> }
>
[Severity: Low]
This patch doesn't touch yt921x_dsa_teardown(), but at this commit it
looks like it won't build with CONFIG_NET_DSA_YT921X_LEDS=n:
static void yt921x_dsa_teardown(struct dsa_switch *ds)
{
#if IS_ENABLED(CONFIG_NET_DSA_YT921X_LEDS)
struct yt921x_priv *priv = to_yt921x_priv(ds);
yt921x_leds_remove(priv);
#endif
for (size_t i = ARRAY_SIZE(priv->ports); i-- > 0; ) {
struct yt921x_port *pp = &priv->ports[i];
disable_delayed_work_sync(&pp->mib_read);
}
}
Is priv undeclared here when the LED option is off? The option is a bool
with this dependency:
depends on LEDS_CLASS=y || LEDS_CLASS=NET_DSA_YT921X
so it is n when LEDS_CLASS=n, when LEDS_CLASS=m with NET_DSA_YT921X=y, or
when the user disables it.
The baseline doesn't have this. It came in earlier in this series with
"net: dsa: motorcomm: move mib stop from remove() to teardown()" and is
still present here.
> +static bool yt921x_needs_extmode_check(u32 major)
[ ... ]
> @@ -4694,6 +4725,637 @@ static const struct dsa_switch_ops yt921x_dsa_switch_ops = {
[ ... ]
> +static int yt922x_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
> + phy_interface_t interface,
> + const unsigned long *advertising,
> + bool permit_pause_to_mac)
> +{
[ ... ]
> + res = yt921x_intif_ext_read(priv, addr, YT922X_PORT_SDSn, &data);
> + if (res)
> + goto err;
> + data &= ~YT922X_SERDES_MODE_M;
> + data |= ctrl;
> + res = yt921x_intif_ext_write(priv, addr, YT922X_PORT_SDSn, data);
> + if (res)
> + goto err;
> + mutex_unlock(&priv->reg_lock);
> +
> + return res;
> +
> +err:
> + mutex_unlock(&priv->reg_lock);
> +
> + return res;
> +}
> +
> +static const struct phylink_pcs_ops yt922x_pcs_ops = {
> + .pcs_get_state = yt922x_pcs_get_state,
> + .pcs_config = yt922x_pcs_config,
> + .pcs_an_restart = yt922x_pcs_an_restart,
> +};
[Severity: Medium]
yt922x_pcs_config() never reads neg_mode or advertising. It updates only
the SERDES_MODE field of YT922X_PORT_SDSn and returns 0. It never sets or
clears BMCR_ANENABLE and never writes MII_ADVERTISE.
The pcs_config kernel-doc in include/linux/phylink.h says:
* When operating in %MLO_AN_INBAND, inband should always be enabled,
* otherwise inband should be disabled.
It also says the 1000BASE-X advertisement should be programmed into the
PCS.
Does this leave the SerDes in whatever AN state it had after reset?
yt922x_pcs_an_restart() only ORs in BMCR_ANRESTART, so it keeps the
existing AN-enable setting.
yt922x_pcs_ops also has no .pcs_link_up, and yt922x_port_up() programs
only YT922X_PORTn_CTRL. By contrast, yt921x_port_up() programs the
SerDes speed and duplex (YT921X_SERDES_SPEED_*) for external ports.
Take a fixed-link or out-of-band SGMII, 1000BASE-X or 2500BASE-X port,
such as a CPU port with a fixed 2500base-x link like the binding example.
How does its SerDes get the speed and duplex? Could SGMII at 10/100
without in-band status run at the wrong rate?
There is no .pcs_inband_caps either, so unsupported in-band modes are
never rejected.
An ethtool autoneg or advertisement change goes through:
phylink_change_inband_advert()->yt922x_pcs_config()
That path does the same writes and returns 0. phylink then never requests
an AN restart, and it records settings the PCS never applied.
The exact effect depends on the SerDes reset defaults.
[ ... ]
> @@ -4707,12 +5369,26 @@ static const struct yt92xx_series yt92xx_series_table[] = {
> .switch_ops = &yt921x_dsa_switch_ops,
> .mac_ops = &yt921x_phylink_mac_ops
> },
> + [YT92XX_MODE_YT922X] = {
> + .mode = YT92XX_MODE_YT922X,
> + .name = "YT922x",
> + .max_ports = YT922X_PORT_NUM,
> + .num_lag_ids = YT922X_LAG_NUM,
> + .ageing_time_min = 1 * 6000,
> + .ageing_time_max = U16_MAX * 6000,
> + .dscp_prio_mapping_is_global = true,
> + .assisted_learning_on_cpu_port = true,
> + .switch_ops = &yt922x_dsa_switch_ops,
> + .mac_ops = &yt922x_phylink_mac_ops,
> + },
> };
[Severity: Low]
This isn't a bug, but this entry sets num_lag_ids, the ageing time range,
dscp_prio_mapping_is_global and assisted_learning_on_cpu_port.
yt922x_dsa_switch_ops has none of the matching callbacks (port_fdb_add/del,
port_lag_join/leave, set_ageing_time, or the DSCP ops).
Nothing changes at runtime today:
- With no port_bridge_join, dp->bridge stays NULL, so dsa_user_fdb_event()
returns early before it checks assisted_learning_on_cpu_port.
- A LAG join falls back to software through the -EOPNOTSUPP handling in
dsa_user_changeupper().
- The ageing time is only sent to ports that offload the bridge.
Would it be cleaner to leave these unset until the matching ops are
added?
[ ... ]
> @@ -4830,8 +5506,9 @@ static int yt921x_mdio_probe(struct mdio_device *mdiodev)
> }
>
> static const struct of_device_id yt921x_of_match[] = {
> - { .compatible = "motorcomm,yt9215" },
> - {}
> + { .compatible = "motorcomm,yt9215", },
> + { .compatible = "motorcomm,yt9224", },
> + { /* sentinel */ }
> };
[Severity: Medium]
Is "motorcomm,yt9224" documented anywhere? The only binding for this
driver, Documentation/devicetree/bindings/net/dsa/motorcomm,yt921x.yaml,
still has:
compatible:
const: motorcomm,yt9215
Won't a DT node using the new compatible fail dt_binding_check and
dtbs_check? checkpatch will also flag it as undocumented.
The binding description also covers only the YT921x port layout ("up to 8
internal GbE PHYs", "GMACs (Port 8-9)"). The YT9224 setup here uses
internal PHYs on ports 4-7 and SerDes on ports 0 and 8.
Should a binding update go with this patch?
[ ... ]
> @@ -4694,6 +4725,637 @@ static const struct dsa_switch_ops yt921x_dsa_switch_ops = {
> .setup = yt921x_dsa_setup,
> };
>
> +static int yt922x_port_down(struct yt921x_priv *priv, int port)
> +{
> + u32 mask;
> + int res;
> +
> + /* mac force down */
> + mask = YT922X_PORT_LINK | YT922X_PORT_RX_MAC_EN |
> + YT922X_PORT_TX_MAC_EN | YT922X_PORT_LINK_AN;
> + res = yt921x_reg_clear_bits(priv, YT922X_PORTn_CTRL(port), mask);
> + if (res)
> + return res;
> + /* Need force op to make soft configuration effective */
> + mask = YT922X_PORT_FORCE_OP;
> + res = yt921x_reg_set_bits(priv, YT922X_PORTn_CTRL(port), mask);
> + if (res)
> + return res;
> +
> + /* disable en_phy */
> + res = yt921x_reg_clear_bits(priv, YT922X_EN_PHY_VALUE, BIT(port));
> + if (res)
> + return res;
> + res = yt921x_reg_set_bits(priv, YT922X_EN_PHY_OVERWRITE, BIT(port));
> + if (res)
> + return res;
> +
> + return 0;
> +}
> +
> +static void
> +yt922x_phylink_mac_link_down(struct phylink_config *config, unsigned int mode,
> + phy_interface_t interface)
> +{
> + struct dsa_port *dp = dsa_phylink_to_port(config);
> + struct yt921x_priv *priv = to_yt921x_priv(dp->ds);
> + int port = dp->index;
> + int res;
> +
> + mutex_lock(&priv->reg_lock);
> + res = yt922x_port_down(priv, port);
> + mutex_unlock(&priv->reg_lock);
> +
> + if (res)
> + dev_err(dp->ds->dev, "Failed to %s port %d: %i\n", "bring down",
> + port, res);
> +}
> +
> +static int
> +yt922x_port_up(struct yt921x_priv *priv, int port, unsigned int mode,
> + phy_interface_t interface, int speed, int duplex,
> + bool tx_pause, bool rx_pause)
> +{
> + u32 mask;
> + u32 ctrl;
> + int res;
> +
> + switch (speed) {
> + case SPEED_10:
> + ctrl = YT921X_PORT_SPEED_10;
> + break;
> + case SPEED_100:
> + ctrl = YT921X_PORT_SPEED_100;
> + break;
> + case SPEED_1000:
> + ctrl = YT921X_PORT_SPEED_1000;
> + break;
> + case SPEED_2500:
> + ctrl = YT921X_PORT_SPEED_2500;
> + break;
> + case SPEED_5000:
> + ctrl = YT921X_PORT_SPEED_5000;
> + break;
> + case SPEED_10000:
> + ctrl = YT921X_PORT_SPEED_10000;
> + break;
> + default:
> + return -EINVAL;
> + }
> + if (duplex == DUPLEX_FULL)
> + ctrl |= YT922X_PORT_DUPLEX_FULL;
> + if (tx_pause)
> + ctrl |= YT922X_PORT_TX_PAUSE;
> + if (rx_pause)
> + ctrl |= YT922X_PORT_RX_PAUSE;
> + ctrl |= YT922X_PORT_RX_MAC_EN | YT922X_PORT_TX_MAC_EN |
> + YT922X_PORT_CFG_TX_EN | YT922X_PORT_LINK |
> + YT922X_PORT_CFG_RX_EN;
> + ctrl &= ~(YT922X_PORT_FC_AN | YT922X_PORT_LINK_AN);
> + res = yt921x_reg_write(priv, YT922X_PORTn_CTRL(port), ctrl);
> + if (res)
> + return res;
> +
> + /* force op */
> + mask = YT922X_PORT_FORCE_OP;
> + res = yt921x_reg_set_bits(priv, YT922X_PORTn_CTRL(port), mask);
> + if (res)
> + return res;
> +
> + /* enable en_phy */
> + res = yt921x_reg_set_bits(priv, YT922X_EN_PHY_VALUE, BIT(port));
> + if (res)
> + return res;
> + res = yt921x_reg_set_bits(priv, YT922X_EN_PHY_OVERWRITE, BIT(port));
> + if (res)
> + return res;
> +
> + return 0;
> +}
> +
> +static void
> +yt922x_phylink_mac_link_up(struct phylink_config *config,
> + struct phy_device *phydev, unsigned int mode,
> + phy_interface_t interface, int speed, int duplex,
> + bool tx_pause, bool rx_pause)
> +{
> + struct dsa_port *dp = dsa_phylink_to_port(config);
> + struct yt921x_priv *priv = to_yt921x_priv(dp->ds);
> + int port = dp->index;
> + int res;
> +
> + mutex_lock(&priv->reg_lock);
> + res = yt922x_port_up(priv, port, mode, interface, speed, duplex,
> + tx_pause, rx_pause);
> + mutex_unlock(&priv->reg_lock);
> +
> + if (res)
> + dev_err(dp->ds->dev, "Failed to %s port %d: %i\n", "bring up",
> + port, res);
> +}
> +
> +static int
> +yt921x_intif_ext_write(struct yt921x_priv *priv, int port, int reg, u16 val)
> +{
> + int res;
> +
> + if (port > priv->series->max_ports)
> + return -ENODEV;
> +
> + res = yt921x_intif_write(priv, port, YT92XX_PAGE_SELECT, reg);
> + if (res)
> + return res;
> +
> + res = yt921x_intif_write(priv, port, YT92XX_PAGE, val);
> + if (res)
> + return res;
> +
> + return 0;
> +}
> +
> +static int
> +yt921x_intif_ext_read(struct yt921x_priv *priv, int port, int reg, u16 *valp)
> +{
> + int res;
> +
> + if (port > priv->series->max_ports)
> + return -ENODEV;
> +
> + res = yt921x_intif_write(priv, port, YT92XX_PAGE_SELECT, reg);
> + if (res)
> + return res;
> +
> + res = yt921x_intif_read(priv, port, YT92XX_PAGE, valp);
> + if (res)
> + return res;
> +
> + return 0;
> +}
> +
> +static int yt922x_sds_phyaddr_get(int port,
> + enum yt922x_phy_reg_type reg_type)
> +{
> + /*
> + * sds phyaddr mapping depend on reg_type
> + */
> + if (!yt922x_port_is_internal_sds(port))
> + return -EOPNOTSUPP;
> + if (reg_type == YT922X_PHY_REG_TYPE_COMMON_EXT)
> + return YT922X_COMMON_EXT_PHYADDR;
> +
> + return port;
> +}
> +
> +static void
> +yt922x_phylink_mac_config(struct phylink_config *config, unsigned int mode,
> + const struct phylink_link_state *state)
> +{
> +}
> +
> +static struct phylink_pcs *
> +yt922x_phylink_mac_select_pcs(struct phylink_config *config,
> + phy_interface_t interface)
> +{
> + struct dsa_port *dp = dsa_phylink_to_port(config);
> + struct yt921x_priv *priv = to_yt921x_priv(dp->ds);
> +
> + switch (interface) {
> + case PHY_INTERFACE_MODE_SGMII:
> + case PHY_INTERFACE_MODE_1000BASEX:
> + case PHY_INTERFACE_MODE_2500BASEX:
> + case PHY_INTERFACE_MODE_USXGMII:
> + return &priv->ports[dp->index].pcs;
> +
> + default:
> + return NULL;
> + }
> +}
> +
> +static const struct phylink_mac_ops yt922x_phylink_mac_ops = {
> + .mac_select_pcs = yt922x_phylink_mac_select_pcs,
> + .mac_link_down = yt922x_phylink_mac_link_down,
> + .mac_link_up = yt922x_phylink_mac_link_up,
> + .mac_config = yt922x_phylink_mac_config,
> +};
> +
> +static void yt922x_pcs_get_state(struct phylink_pcs *pcs, unsigned int neg_mode,
> + struct phylink_link_state *state)
> +{
> + struct yt921x_port *pp = pcs_to_yt921x_port(pcs);
> + struct yt921x_priv *priv = yt921x_port_to_priv(pp);
> + int port = pp->index;
> + int res = 0;
> + u16 data;
> + int addr;
> + u16 lp;
> +
> + addr = yt922x_sds_phyaddr_get(port, YT922X_PHY_REG_TYPE_MII);
> + if (addr < 0) {
> + state->link = false;
> + return;
> + }
> +
> + mutex_lock(&priv->reg_lock);
> + switch (state->interface) {
> + case PHY_INTERFACE_MODE_SGMII:
> + case PHY_INTERFACE_MODE_1000BASEX:
> + case PHY_INTERFACE_MODE_2500BASEX:
> + res = yt921x_intif_read(priv, addr, MII_BMSR, &data);
> + if (res)
> + goto err;
> + res = yt921x_intif_read(priv, addr, MII_LPA, &lp);
> + if (res)
> + goto err;
> + phylink_mii_c22_pcs_decode_state(state, neg_mode, data, lp);
> + break;
> + case PHY_INTERFACE_MODE_USXGMII:
> + res = yt921x_intif_read(priv, addr, YT922X_PCS_LINK_CTRL,
> + &data);
> + if (res)
> + goto err;
> + state->link = FIELD_GET(YT922X_PCS_LINK_STATUS, data);
> + state->an_complete = FIELD_GET(YT922X_PCS_AN_COMPLETE, data);
> + res = yt921x_intif_read(priv, addr, MII_LPA, &lp);
> + if (res)
> + goto err;
> + if (state->link)
> + phylink_decode_usxgmii_word(state, lp);
> + break;
> + default:
> + state->link = false;
> + break;
> + }
> + mutex_unlock(&priv->reg_lock);
> + return;
> +err:
> + mutex_unlock(&priv->reg_lock);
> + state->link = false;
> +}
> +
> +static void yt922x_pcs_an_restart(struct phylink_pcs *pcs)
> +{
> + struct yt921x_port *pp = pcs_to_yt921x_port(pcs);
> + struct yt921x_priv *priv = yt921x_port_to_priv(pp);
> + struct device *dev = to_device(priv);
> + int port = pp->index;
> + u16 data;
> + int addr;
> + int res;
> +
> + mutex_lock(&priv->reg_lock);
> + addr = yt922x_sds_phyaddr_get
> + (port, YT922X_PHY_REG_TYPE_MII);
> + if (addr < 0) {
> + res = addr;
> + goto err;
> + }
> + res = yt921x_intif_read(priv, addr, MII_BMCR, &data);
> + if (res)
> + goto err;
> + data |= BMCR_ANRESTART;
> + res = yt921x_intif_write(priv, addr, MII_BMCR, data);
> + if (res)
> + goto err;
> + mutex_unlock(&priv->reg_lock);
> + return;
> +
> +err:
> + mutex_unlock(&priv->reg_lock);
> + if (res)
> + dev_err(dev, "Failed to %s PCS port %d: %i\n", "an restart",
> + port, res);
> +}
> +
> +static int yt922x_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
> + phy_interface_t interface,
> + const unsigned long *advertising,
> + bool permit_pause_to_mac)
> +{
> + struct yt921x_port *pp = pcs_to_yt921x_port(pcs);
> + struct yt921x_priv *priv = yt921x_port_to_priv(pp);
> + int res, port;
> + u16 data;
> + u16 ctrl;
> + int addr;
> +
> + port = pp->index;
> + if (!yt922x_port_is_internal_sds(port))
> + return -EINVAL;
> +
> + mutex_lock(&priv->reg_lock);
> + addr = yt922x_sds_phyaddr_get
> + (port, YT922X_PHY_REG_TYPE_SDS_COMMON_EXT);
> + if (addr < 0) {
> + res = addr;
> + goto err;
> + }
> + /* write protect */
> + res = yt921x_intif_ext_write(priv, addr, 0x4be, 0xd);
> + if (res)
> + goto err;
> + switch (interface) {
> + case PHY_INTERFACE_MODE_SGMII:
> + ctrl = YT922X_SERDES_MODE_SGMII;
> + break;
> + case PHY_INTERFACE_MODE_1000BASEX:
> + ctrl = YT922X_SERDES_MODE_1000BASEX;
> + break;
> + case PHY_INTERFACE_MODE_2500BASEX:
> + ctrl = YT922X_SERDES_MODE_2500BASEX;
> + break;
> + case PHY_INTERFACE_MODE_USXGMII:
> + ctrl = YT922X_SERDES_MODE_USXGMII;
> + break;
> + default:
> + res = -EINVAL;
> + goto err;
> + }
> + res = yt921x_intif_ext_read(priv, addr, YT922X_PORT_SDSn, &data);
> + if (res)
> + goto err;
> + data &= ~YT922X_SERDES_MODE_M;
> + data |= ctrl;
> + res = yt921x_intif_ext_write(priv, addr, YT922X_PORT_SDSn, data);
> + if (res)
> + goto err;
> + mutex_unlock(&priv->reg_lock);
> +
> + return res;
> +
> +err:
> + mutex_unlock(&priv->reg_lock);
> +
> + return res;
> +}
> +
> +static const struct phylink_pcs_ops yt922x_pcs_ops = {
> + .pcs_get_state = yt922x_pcs_get_state,
> + .pcs_config = yt922x_pcs_config,
> + .pcs_an_restart = yt922x_pcs_an_restart,
> +};
> +
> +static enum dsa_tag_protocol
> +yt922x_dsa_get_tag_protocol(struct dsa_switch *ds, int port,
> + enum dsa_tag_protocol m)
> +{
> + return DSA_TAG_PROTO_YT922X;
> +}
> +
> +static void
> +yt922x_dsa_phylink_get_caps(struct dsa_switch *ds, int port,
> + struct phylink_config *config)
> +{
> + struct yt921x_priv *priv = to_yt921x_priv(ds);
> + const struct yt921x_info *info = priv->info;
> +
> + config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
> + MAC_10 | MAC_100 | MAC_1000;
> +
> + if (info->internal_mask & BIT(port)) {
> + /* port 4 to port 7, internal utp */
> + __set_bit(PHY_INTERFACE_MODE_INTERNAL,
> + config->supported_interfaces);
> + config->mac_capabilities |= MAC_2500FD;
> + }
> + if (info->external_mask & BIT(port)) {
> + /* serdes */
> + __set_bit(PHY_INTERFACE_MODE_SGMII,
> + config->supported_interfaces);
> + __set_bit(PHY_INTERFACE_MODE_1000BASEX,
> + config->supported_interfaces);
> + __set_bit(PHY_INTERFACE_MODE_2500BASEX,
> + config->supported_interfaces);
> + config->mac_capabilities |= MAC_2500FD;
> + __set_bit(PHY_INTERFACE_MODE_USXGMII,
> + config->supported_interfaces);
> + config->mac_capabilities |= MAC_5000FD;
> + config->mac_capabilities |= MAC_10000FD;
> + }
> +}
> +
> +static int yt922x_port_setup(struct yt921x_priv *priv, int port)
> +{
> + struct dsa_switch *ds = &priv->ds;
> + u32 mask;
> + u32 ctrl;
> + int res;
> +
> + /* enable user port isolation and disable fdb learning */
> + ctrl = ~priv->cpu_ports_mask;
> + res = yt921x_reg_write(priv, YT922X_PORTn_ISOLATION(port), ctrl);
> + if (res)
> + return res;
> +
> + mask = YT922X_PORT_LEARN_DIS;
> + res = yt921x_reg_set_bits(priv, YT922X_PORTn_LEARN(port), mask);
> + if (res)
> + return res;
> +
> + if (dsa_is_cpu_port(ds, port)) {
> + ctrl = ~(u32)0;
> + res = yt921x_reg_write(priv, YT922X_PORTn_ISOLATION(port),
> + ctrl);
> + if (res)
> + return res;
> + }
> +
> + return 0;
> +}
> +
> +static int yt922x_dsa_port_setup(struct dsa_switch *ds, int port)
> +{
> + struct yt921x_priv *priv = to_yt921x_priv(ds);
> + int res;
> +
> + mutex_lock(&priv->reg_lock);
> + res = yt922x_port_setup(priv, port);
> + mutex_unlock(&priv->reg_lock);
> +
> + return res;
> +}
> +
> +static int yt922x_cpu_tag_mode_set_8b(struct yt921x_priv *priv)
> +{
> + u32 val;
> + u32 val1;
> + int res;
> +
> + /* cpu tag mode set to 8b */
> + res = yt921x_reg_read(priv, YT922X_CPU_TAG_RX_CTRL, &val);
> + if (res)
> + return res;
> + res = yt921x_reg_read(priv, YT922X_CPU_TAG_TX_CTRL, &val1);
> + if (res)
> + return res;
> + val &= ~YT922X_CPU_TAG_RX_MODE;
> + val1 &= ~YT922X_CPU_TAG_TX_MODE;
> + val1 &= ~YT922X_CPU_TAG_TX_TYPE;
> + res = yt921x_reg_write(priv, YT922X_CPU_TAG_RX_CTRL, val);
> + if (res)
> + return res;
> + res = yt921x_reg_write(priv, YT922X_CPU_TAG_TX_CTRL, val1);
> + if (res)
> + return res;
> +
> + return 0;
> +}
> +
> +static int yt922x_cpu_port_set(struct yt921x_priv *priv)
> +{
> + struct dsa_switch *ds = &priv->ds;
> + u32 ctrl;
> + int res;
> +
> + /* cpu tag mode */
> + res = yt922x_cpu_tag_mode_set_8b(priv);
> + if (res)
> + return res;
> +
> + /* Enable DSA */
> + priv->cpu_ports_mask = dsa_cpu_ports(ds);
> + ctrl = YT921X_EXT_CPU_PORT_TAG_EN | YT921X_EXT_CPU_PORT_PORT_EN |
> + YT921X_EXT_CPU_PORT_PORT(__ffs(priv->cpu_ports_mask));
> + res = yt921x_reg_write(priv, YT921X_EXT_CPU_PORT, ctrl);
> + if (res)
> + return res;
> +
> + /* Setup software switch */
> + ctrl = YT922X_CPU_COPY_TO_EXT_CPU;
> + res = yt921x_reg_write(priv, YT922X_CPU_COPY, ctrl);
> + if (res)
> + return res;
> +
> + return res;
> +}
> +
> +static int yt922x_chip_setup_dsa(struct yt921x_priv *priv)
> +{
> + unsigned long cpu_ports_mask;
> + u32 ctrl;
> + int port;
> + int res;
> +
> + /* cpu port set */
> + res = yt922x_cpu_port_set(priv);
> + if (res)
> + return res;
> +
> + ctrl = GENMASK_U32(8, 0);
> + res = yt921x_reg_write(priv, YT922X_FILTER_UNK_UCAST, ctrl);
> + if (res)
> + return res;
> +
> + ctrl = 0;
> + for (int i = 0; i < priv->series->max_ports; i++)
> + ctrl |= YT922X_ACT_UNK_ACTn_TRAP(i);
> + cpu_ports_mask = priv->cpu_ports_mask;
> + for_each_set_bit(port, &cpu_ports_mask, priv->series->max_ports) {
> + ctrl &= ~YT922X_ACT_UNK_ACTn_M(port);
> + ctrl |= YT922X_ACT_UNK_ACTn_DROP(port);
> + }
> + res = yt921x_reg_write(priv, YT922X_ACT_UNK_UCAST, ctrl);
> + if (res)
> + return res;
> + res = yt921x_reg_write(priv, YT922X_ACT_UNK_MCAST, ctrl);
> + if (res)
> + return res;
> +
> + return 0;
> +}
> +
> +static int yt922x_chip_setup(struct yt921x_priv *priv)
> +{
> + u32 ctrl;
> + int res;
> +
> + ctrl = YT922X_FUNC_MIB | YT922X_FUNC_ACL;
> + res = yt921x_reg_set_bits(priv, YT921X_FUNC, ctrl);
> + if (res)
> + return res;
> +
> + res = yt922x_chip_setup_dsa(priv);
> + if (res)
> + return res;
> +
> + return 0;
> +}
> +
> +static void yt922x_pcs_setup(struct dsa_switch *ds)
> +{
> + struct yt921x_priv *priv = to_yt921x_priv(ds);
> + const struct yt921x_info *info = priv->info;
> + unsigned long mask;
> + int port;
> +
> + mask = info->external_mask;
> + for_each_set_bit(port, &mask, priv->series->max_ports) {
> + struct yt921x_port *pp = &priv->ports[port];
> +
> + pp->pcs.ops = &yt922x_pcs_ops;
> + pp->pcs.poll = true;
> +
> + __set_bit(PHY_INTERFACE_MODE_SGMII,
> + pp->pcs.supported_interfaces);
> + __set_bit(PHY_INTERFACE_MODE_1000BASEX,
> + pp->pcs.supported_interfaces);
> + __set_bit(PHY_INTERFACE_MODE_2500BASEX,
> + pp->pcs.supported_interfaces);
> + __set_bit(PHY_INTERFACE_MODE_USXGMII,
> + pp->pcs.supported_interfaces);
> + }
> +}
> +
> +static int yt922x_dsa_setup(struct dsa_switch *ds)
> +{
> + struct yt921x_priv *priv = to_yt921x_priv(ds);
> + struct device *dev = to_device(priv);
> + struct device_node *np = dev->of_node;
> + struct device_node *child;
> + int res;
> +
> + /* ports index init */
> + for (size_t i = 0; i < ARRAY_SIZE(priv->ports); i++) {
> + struct yt921x_port *pp = &priv->ports[i];
> +
> + pp->index = i;
> + }
> +
> + mutex_lock(&priv->reg_lock);
> + res = yt921x_chip_reset(priv);
> + mutex_unlock(&priv->reg_lock);
> + if (res)
> + return res;
> +
> + /* Register the internal mdio bus. */
> + child = of_get_child_by_name(np, "mdio");
> + if (child) {
> + res = yt921x_mbus_int_init(priv, child);
> + of_node_put(child);
> + if (res)
> + return res;
> + }
> +
> + mutex_lock(&priv->reg_lock);
> + res = yt922x_chip_setup(priv);
> + mutex_unlock(&priv->reg_lock);
> + if (res)
> + return res;
> +
> + /* switch sds pcs setup */
> + yt922x_pcs_setup(ds);
> +
> + return 0;
> +}
> +
> +static const struct dsa_switch_ops yt922x_dsa_switch_ops = {
> + /* port */
> + .get_tag_protocol = yt922x_dsa_get_tag_protocol,
> + .phylink_get_caps = yt922x_dsa_phylink_get_caps,
> + .port_setup = yt922x_dsa_port_setup,
> + /* chip */
> + .setup = yt922x_dsa_setup,
> +};
> +
> static const struct yt92xx_series yt92xx_series_table[] = {
> [YT92XX_MODE_YT921X] = {
> .mode = YT92XX_MODE_YT921X,
[Severity: Medium]
yt922x_port_up() builds ctrl from scratch, starting with the speed
value. It then writes it to YT922X_PORTn_CTRL(port) with a plain
yt921x_reg_write(). Every bit not explicitly set here is cleared on
each link up. That includes YT922X_PORT_HALF_PAUSE and any reserved
or undocumented bits above YT922X_PORT_FORCE_OP.
The ctrl &= ~(YT922X_PORT_FC_AN | YT922X_PORT_LINK_AN) line suggests
a read-modify-write was intended. ctrl never held the register
contents, though, so that line does nothing.
The full write also clears YT922X_PORT_FORCE_OP, which
yt922x_port_down() had set. Only the following yt921x_reg_set_bits()
sets it again. yt922x_port_down() uses yt921x_reg_clear_bits() on the
same register, so the two paths treat it differently.
Is the full overwrite intended? If not, would yt921x_reg_update_bits()
do what you want here, with a mask covering speed, duplex, pause, the
MAC and CFG enables, FC_AN and LINK_AN? If it is intended, please drop
the dead mask and add a comment on why the other bits can be
discarded.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260924094146.4128703-1-kyle.switch%40motor-comm.com
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-09-28 10:39 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-24 9:41 [PATCH net-next v9 0/7] net: dsa: motorcomm: add support yt922x driver Kyle Switch
2026-09-24 9:41 ` [PATCH net-next v9 1/7] net: dsa: motorcomm: initialize dsa_switch based on chipid Kyle Switch
2026-09-28 10:39 ` netdev-bot+sashiko
2026-09-24 9:41 ` [PATCH net-next v9 2/7] net: dsa: motorcomm: use max_ports in series for port bounds checking Kyle Switch
2026-09-24 15:32 ` Andrew Lunn
2026-09-28 10:39 ` netdev-bot+sashiko
2026-09-24 9:41 ` [PATCH net-next v9 3/7] net: dsa: motorcomm: use fixed-width GENMASK_U*() helpers Kyle Switch
2026-09-24 9:41 ` [PATCH net-next v9 4/7] net: dsa: motorcomm: move mib start from probe() to dsa_setup() Kyle Switch
2026-09-28 10:39 ` netdev-bot+sashiko
2026-09-24 9:41 ` [PATCH net-next v9 5/7] net: dsa: motorcomm: move mib stop from remove() to teardown() Kyle Switch
2026-09-28 10:39 ` netdev-bot+sashiko
2026-09-24 9:41 ` [PATCH net-next v9 6/7] net: dsa: tag_yt922x: add support for Motorcomm YT922x tags Kyle Switch
2026-09-28 10:39 ` netdev-bot+sashiko
2026-09-24 9:41 ` [PATCH net-next v9 7/7] net: dsa: motorcomm: Add support for Motorcomm YT922x Kyle Switch
2026-09-28 10:39 ` netdev-bot+sashiko
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®