netdev.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [net-next v5 0/3] net: dsa: yt921x: Add support for Motorcomm YT921x
@ 2025-08-20  7:54 David Yang
  2025-08-20  7:54 ` [net-next v5 1/3] dt-bindings: net: dsa: yt921x: Add Motorcomm YT921x switch support David Yang
                   ` (2 more replies)
  0 siblings, 3 replies; 15+ messages in thread
From: David Yang @ 2025-08-20  7:54 UTC (permalink / raw)
  To: netdev
  Cc: David Yang, Andrew Lunn, Vladimir Oltean, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King,
	devicetree, linux-kernel

Motorcomm YT921x is a series of ethernet switches developed by Shanghai
Motorcomm Electronic Technology, including:

  - YT9215S / YT9215RB / YT9215SC: 5 GbE phys
  - YT9213NB / YT9214NB: 2 GbE phys
  - YT9218N / YT9218MB: 8 GbE phys

and up to 2 serdes interfaces.

This patch adds basic support for a working DSA switch.

v4: https://lore.kernel.org/r/20250818162445.1317670-1-mmyangfl@gmail.com
  - remove switchid from dt binding
  - remove hsr from tag driver
  - use ratelimited log in tag driver
v3: https://lore.kernel.org/r/20250816052323.360788-1-mmyangfl@gmail.com
  - fix words and warnings in dt binding
  - remove unnecessary dev_warn_ratelimited and u64_from_u32
  - remove lag and mst
  - check for mdio results and fix a unlocked write in conduit_state_change
v2: https://lore.kernel.org/r/20250814065032.3766988-1-mmyangfl@gmail.com
  - fix words in dt binding
  - add support for lag and mst
v1: https://lore.kernel.org/r/20250808173808.273774-1-mmyangfl@gmail.com
  - fix coding style
  - add dt binding
  - add support for fdb, vlan and bridge

David Yang (3):
  dt-bindings: net: dsa: yt921x: Add Motorcomm YT921x switch support
  net: dsa: tag_yt921x: add support for Motorcomm YT921x tags
  net: dsa: yt921x: Add support for Motorcomm YT921x

 .../bindings/net/dsa/motorcomm,yt921x.yaml    |  150 +
 drivers/net/dsa/Kconfig                       |    7 +
 drivers/net/dsa/Makefile                      |    1 +
 drivers/net/dsa/yt921x.c                      | 3584 +++++++++++++++++
 include/net/dsa.h                             |    2 +
 net/dsa/Kconfig                               |    6 +
 net/dsa/Makefile                              |    1 +
 net/dsa/tag_yt921x.c                          |  126 +
 8 files changed, 3877 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/net/dsa/motorcomm,yt921x.yaml
 create mode 100644 drivers/net/dsa/yt921x.c
 create mode 100644 net/dsa/tag_yt921x.c

-- 
2.50.1


^ permalink raw reply	[flat|nested] 15+ messages in thread

* [net-next v5 1/3] dt-bindings: net: dsa: yt921x: Add Motorcomm YT921x switch support
  2025-08-20  7:54 [net-next v5 0/3] net: dsa: yt921x: Add support for Motorcomm YT921x David Yang
@ 2025-08-20  7:54 ` David Yang
  2025-08-21  7:22   ` Krzysztof Kozlowski
  2025-08-21 12:20   ` Andrew Lunn
  2025-08-20  7:54 ` [net-next v5 2/3] net: dsa: tag_yt921x: add support for Motorcomm YT921x tags David Yang
  2025-08-20  7:54 ` [net-next v5 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x David Yang
  2 siblings, 2 replies; 15+ messages in thread
From: David Yang @ 2025-08-20  7:54 UTC (permalink / raw)
  To: netdev
  Cc: David Yang, Andrew Lunn, Vladimir Oltean, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King,
	devicetree, linux-kernel

The Motorcomm YT921x series is a family of Ethernet switches with up to
8 internal GbE PHYs and up to 2 GMACs.

Signed-off-by: David Yang <mmyangfl@gmail.com>
---
 .../bindings/net/dsa/motorcomm,yt921x.yaml    | 150 ++++++++++++++++++
 1 file changed, 150 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/net/dsa/motorcomm,yt921x.yaml

diff --git a/Documentation/devicetree/bindings/net/dsa/motorcomm,yt921x.yaml b/Documentation/devicetree/bindings/net/dsa/motorcomm,yt921x.yaml
new file mode 100644
index 000000000000..a0c8a6fd6bdc
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/dsa/motorcomm,yt921x.yaml
@@ -0,0 +1,150 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/dsa/motorcomm,yt921x.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Motorcomm YT921x Ethernet switch family
+
+maintainers:
+  - David Yang <mmyangfl@gmail.com>
+
+description: |
+  The Motorcomm YT921x series is a family of Ethernet switches with up to 8
+  internal GbE PHYs and up to 2 GMACs, including:
+
+    - YT9215S / YT9215RB / YT9215SC: 5 GbE PHYs (Port 0-4) + 2 GMACs (Port 8-9)
+    - YT9213NB: 2 GbE PHYs (Port 1/3) + 1 GMAC (Port 9)
+    - YT9214NB: 2 GbE PHYs (Port 1/3) + 2 GMACs (Port 8-9)
+    - YT9218N: 8 GbE PHYs (Port 0-7)
+    - YT9218MB: 8 GbE PHYs (Port 0-7) + 2 GMACs (Port 8-9)
+
+  Any port can be used as the CPU port.
+
+properties:
+  compatible:
+    const: motorcomm,yt9215
+
+  reg:
+    maxItems: 1
+
+  reset-gpios:
+    maxItems: 1
+
+  mdio:
+    $ref: /schemas/net/mdio.yaml#
+    unevaluatedProperties: false
+    description: |
+      Internal MDIO bus for the internal GbE PHYs. PHYs 0-7 are used for Port
+      0-7 respectively.
+
+  mdio-external:
+    $ref: /schemas/net/mdio.yaml#
+    unevaluatedProperties: false
+    description: |
+      External MDIO bus to access external components. External PHYs for GMACs
+      (Port 8-9) are expected to be connected to the external MDIO bus in
+      vendor's reference design, but that is not a hard limitation from the
+      chip.
+
+allOf:
+  - $ref: dsa.yaml#/$defs/ethernet-ports
+
+required:
+  - compatible
+  - reg
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+
+    mdio {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        switch@1d {
+            compatible = "motorcomm,yt9215";
+            /* default 0x1d, alternate 0x0 */
+            reg = <0x1d>;
+            reset-gpios = <&tlmm 39 GPIO_ACTIVE_LOW>;
+
+            mdio {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                sw_phy0: phy@0 {
+                    reg = <0x0>;
+                };
+            };
+
+            mdio-external {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                phy1: phy@b {
+                    reg = <0xb>;
+                };
+            };
+
+            ports {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                /* phy-handle is optional for internal PHYs */
+                port@0 {
+                    reg = <0>;
+                    label = "lan1";
+                    phy-mode = "internal";
+                    phy-handle = <&sw_phy0>;
+                };
+
+                port@1 {
+                    reg = <1>;
+                    label = "lan2";
+                    phy-mode = "internal";
+                };
+
+                port@2 {
+                    reg = <2>;
+                    label = "lan3";
+                    phy-mode = "internal";
+                };
+
+                port@3 {
+                    reg = <3>;
+                    label = "lan4";
+                    phy-mode = "internal";
+                };
+
+                port@4 {
+                    reg = <4>;
+                    label = "lan5";
+                    phy-mode = "internal";
+                };
+
+                /* CPU port */
+                port@8 {
+                    reg = <8>;
+                    phy-mode = "sgmii";
+                    ethernet = <&eth0>;
+
+                    fixed-link {
+                        speed = <1000>;
+                        full-duplex;
+                        pause;
+                        asym-pause;
+                    };
+                };
+
+                /* if external phy is connected to a MAC */
+                port@9 {
+                    reg = <9>;
+                    label = "wan";
+                    phy-mode = "rgmii";
+                    phy-handle = <&phy1>;
+                };
+            };
+        };
+    };
-- 
2.50.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [net-next v5 2/3] net: dsa: tag_yt921x: add support for Motorcomm YT921x tags
  2025-08-20  7:54 [net-next v5 0/3] net: dsa: yt921x: Add support for Motorcomm YT921x David Yang
  2025-08-20  7:54 ` [net-next v5 1/3] dt-bindings: net: dsa: yt921x: Add Motorcomm YT921x switch support David Yang
@ 2025-08-20  7:54 ` David Yang
  2025-08-21 12:23   ` Andrew Lunn
  2025-08-20  7:54 ` [net-next v5 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x David Yang
  2 siblings, 1 reply; 15+ messages in thread
From: David Yang @ 2025-08-20  7:54 UTC (permalink / raw)
  To: netdev
  Cc: David Yang, Andrew Lunn, Vladimir Oltean, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King,
	devicetree, linux-kernel

Add support for Motorcomm YT921x tags, which includes a proper
configurable ethertype field (default to 0x9988).

Signed-off-by: David Yang <mmyangfl@gmail.com>
---
 include/net/dsa.h    |   2 +
 net/dsa/Kconfig      |   6 +++
 net/dsa/Makefile     |   1 +
 net/dsa/tag_yt921x.c | 126 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 135 insertions(+)
 create mode 100644 net/dsa/tag_yt921x.c

diff --git a/include/net/dsa.h b/include/net/dsa.h
index d73ea0880066..67762fdaf3c7 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -55,6 +55,7 @@ struct tc_action;
 #define DSA_TAG_PROTO_LAN937X_VALUE		27
 #define DSA_TAG_PROTO_VSC73XX_8021Q_VALUE	28
 #define DSA_TAG_PROTO_BRCM_LEGACY_FCS_VALUE	29
+#define DSA_TAG_PROTO_YT921X_VALUE		30
 
 enum dsa_tag_protocol {
 	DSA_TAG_PROTO_NONE		= DSA_TAG_PROTO_NONE_VALUE,
@@ -87,6 +88,7 @@ enum dsa_tag_protocol {
 	DSA_TAG_PROTO_RZN1_A5PSW	= DSA_TAG_PROTO_RZN1_A5PSW_VALUE,
 	DSA_TAG_PROTO_LAN937X		= DSA_TAG_PROTO_LAN937X_VALUE,
 	DSA_TAG_PROTO_VSC73XX_8021Q	= DSA_TAG_PROTO_VSC73XX_8021Q_VALUE,
+	DSA_TAG_PROTO_YT921X		= DSA_TAG_PROTO_YT921X_VALUE,
 };
 
 struct dsa_switch;
diff --git a/net/dsa/Kconfig b/net/dsa/Kconfig
index 869cbe57162f..6b94028b1fcc 100644
--- a/net/dsa/Kconfig
+++ b/net/dsa/Kconfig
@@ -190,4 +190,10 @@ config NET_DSA_TAG_XRS700X
 	  Say Y or M if you want to enable support for tagging frames for
 	  Arrow SpeedChips XRS700x switches that use a single byte tag trailer.
 
+config NET_DSA_TAG_YT921X
+	tristate "Tag driver for Motorcomm YT921x switches"
+	help
+	  Say Y or M if you want to enable support for tagging frames for
+	  Motorcomm YT921x switches.
+
 endif
diff --git a/net/dsa/Makefile b/net/dsa/Makefile
index 555c07cfeb71..4b011a1d5c87 100644
--- a/net/dsa/Makefile
+++ b/net/dsa/Makefile
@@ -39,6 +39,7 @@ obj-$(CONFIG_NET_DSA_TAG_SJA1105) += tag_sja1105.o
 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
 
 # for tracing framework to find trace.h
 CFLAGS_trace.o := -I$(src)
diff --git a/net/dsa/tag_yt921x.c b/net/dsa/tag_yt921x.c
new file mode 100644
index 000000000000..94a4c1cb61ff
--- /dev/null
+++ b/net/dsa/tag_yt921x.c
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Motorcomm YT921x Switch Extend CPU Port
+ *
+ * Copyright (c) 2025 David Yang <mmyangfl@gmail.com>
+ *
+ * +----+----+-------+-----+----+---------
+ * | DA | SA | TagET | Tag | ET | Payload ...
+ * +----+----+-------+-----+----+---------
+ *   6    6      2      6    2       N
+ *
+ * Tag Ethertype: CPU_TAG_TPID_TPID (default: 0x9988)
+ * Tag:
+ *   2: Service VLAN Tag
+ *   2: Rx Port
+ *     15b: Rx Port Valid
+ *     14b-11b: Rx Port
+ *     10b-0b: Unknown value 0x80
+ *   2: Tx Port(s)
+ *     15b: Tx Port(s) Valid
+ *     10b-0b: Tx Port(s) Mask
+ */
+
+#include <linux/etherdevice.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+
+#include "tag.h"
+
+#define YT921X_NAME	"yt921x"
+
+#define YT921X_TAG_LEN	8
+
+#define ETH_P_YT921X	0x9988
+
+#define YT921X_TAG_PORT_EN		BIT(15)
+#define YT921X_TAG_RX_PORT_M		GENMASK(14, 11)
+#define YT921X_TAG_RX_CMD_M		GENMASK(10, 0)
+#define  YT921X_TAG_RX_CMD(x)			FIELD_PREP(YT921X_TAG_RX_CMD_M, (x))
+#define   YT921X_TAG_RX_CMD_UNK_NORMAL			0x80
+#define YT921X_TAG_TX_PORTS_M		GENMASK(10, 0)
+#define  YT921X_TAG_TX_PORTn(port)		BIT(port)
+
+static struct sk_buff *
+yt921x_tag_xmit(struct sk_buff *skb, struct net_device *netdev)
+{
+	struct dsa_port *dp = dsa_user_to_port(netdev);
+	unsigned int port = dp->index;
+	__be16 *tag;
+	u16 tx;
+
+	skb_push(skb, YT921X_TAG_LEN);
+	dsa_alloc_etype_header(skb, YT921X_TAG_LEN);
+
+	tag = dsa_etype_header_pos_tx(skb);
+
+	/* We might use yt921x_priv::tag_eth_p, but
+	 * 1. CPU_TAG_TPID could be configured anyway;
+	 * 2. Are you using the right chip?
+	 */
+	tag[0] = htons(ETH_P_YT921X);
+	/* Service VLAN tag not used */
+	tag[1] = 0;
+	tag[2] = 0;
+	tx = YT921X_TAG_PORT_EN | YT921X_TAG_TX_PORTn(port);
+	tag[3] = htons(tx);
+
+	/* Now tell the conduit network device about the desired output queue
+	 * as well
+	 */
+	skb_set_queue_mapping(skb, port);
+
+	return skb;
+}
+
+static struct sk_buff *
+yt921x_tag_rcv(struct sk_buff *skb, struct net_device *netdev)
+{
+	unsigned int port;
+	__be16 *tag;
+	u16 rx;
+
+	if (unlikely(!pskb_may_pull(skb, YT921X_TAG_LEN)))
+		return NULL;
+
+	tag = (__be16 *)skb->data;
+
+	/* Locate which port this is coming from */
+	rx = ntohs(tag[1]);
+	if (unlikely((rx & YT921X_TAG_PORT_EN) == 0)) {
+		dev_warn_ratelimited(&netdev->dev,
+				     "Unexpected rx tag 0x%04x\n", rx);
+		return NULL;
+	}
+
+	port = FIELD_GET(YT921X_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);
+		return NULL;
+	}
+
+	/* Remove YT921x tag and update checksum */
+	skb_pull_rcsum(skb, YT921X_TAG_LEN);
+
+	dsa_default_offload_fwd_mark(skb);
+
+	dsa_strip_etype_header(skb, YT921X_TAG_LEN);
+
+	return skb;
+}
+
+static const struct dsa_device_ops yt921x_netdev_ops = {
+	.name	= YT921X_NAME,
+	.proto	= DSA_TAG_PROTO_YT921X,
+	.xmit	= yt921x_tag_xmit,
+	.rcv	= yt921x_tag_rcv,
+	.needed_headroom = YT921X_TAG_LEN,
+};
+
+MODULE_DESCRIPTION("DSA tag driver for Motorcomm YT921x switches");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_YT921X, YT921X_NAME);
+
+module_dsa_tag_driver(yt921x_netdev_ops);
-- 
2.50.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [net-next v5 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x
  2025-08-20  7:54 [net-next v5 0/3] net: dsa: yt921x: Add support for Motorcomm YT921x David Yang
  2025-08-20  7:54 ` [net-next v5 1/3] dt-bindings: net: dsa: yt921x: Add Motorcomm YT921x switch support David Yang
  2025-08-20  7:54 ` [net-next v5 2/3] net: dsa: tag_yt921x: add support for Motorcomm YT921x tags David Yang
@ 2025-08-20  7:54 ` David Yang
  2025-08-21  8:30   ` Russell King (Oracle)
  2025-08-21 12:41   ` Andrew Lunn
  2 siblings, 2 replies; 15+ messages in thread
From: David Yang @ 2025-08-20  7:54 UTC (permalink / raw)
  To: netdev
  Cc: David Yang, Andrew Lunn, Vladimir Oltean, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King,
	devicetree, linux-kernel

Motorcomm YT921x is a series of ethernet switches developed by Shanghai
Motorcomm Electronic Technology, including:

  - YT9215S / YT9215RB / YT9215SC: 5 GbE PHYs
  - YT9213NB / YT9214NB: 2 GbE PHYs
  - YT9218N / YT9218MB: 8 GbE PHYs

and up to 2 GMACs.

Driver verified on a stock wireless router with IPQ5018 + YT9215S.

Signed-off-by: David Yang <mmyangfl@gmail.com>
---
 drivers/net/dsa/Kconfig  |    7 +
 drivers/net/dsa/Makefile |    1 +
 drivers/net/dsa/yt921x.c | 3584 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 3592 insertions(+)
 create mode 100644 drivers/net/dsa/yt921x.c

diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig
index ec759f8cb0e2..1b789daab34c 100644
--- a/drivers/net/dsa/Kconfig
+++ b/drivers/net/dsa/Kconfig
@@ -152,4 +152,11 @@ config NET_DSA_VITESSE_VSC73XX_PLATFORM
 	  This enables support for the Vitesse VSC7385, VSC7388, VSC7395
 	  and VSC7398 SparX integrated ethernet switches, connected over
 	  a CPU-attached address bus and work in memory-mapped I/O mode.
+
+config NET_DSA_YT921X
+	tristate "Motorcomm YT9215 ethernet switch chip support"
+	select NET_DSA_TAG_YT921X
+	help
+	  This enables support for the Motorcomm YT9215 ethernet switch
+	  chip.
 endmenu
diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile
index cb9a97340e58..dc81769fa92b 100644
--- a/drivers/net/dsa/Makefile
+++ b/drivers/net/dsa/Makefile
@@ -17,6 +17,7 @@ obj-$(CONFIG_NET_DSA_SMSC_LAN9303_MDIO) += lan9303_mdio.o
 obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX) += vitesse-vsc73xx-core.o
 obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX_PLATFORM) += vitesse-vsc73xx-platform.o
 obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX_SPI) += vitesse-vsc73xx-spi.o
+obj-$(CONFIG_NET_DSA_YT921X) += yt921x.o
 obj-y				+= b53/
 obj-y				+= hirschmann/
 obj-y				+= microchip/
diff --git a/drivers/net/dsa/yt921x.c b/drivers/net/dsa/yt921x.c
new file mode 100644
index 000000000000..c60ac6ae7923
--- /dev/null
+++ b/drivers/net/dsa/yt921x.c
@@ -0,0 +1,3584 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for Motorcomm YT921x 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.
+ *
+ * Copyright (c) 2025 David Yang
+ */
+
+#include <linux/if_bridge.h>
+#include <linux/if_hsr.h>
+#include <linux/if_vlan.h>
+#include <linux/iopoll.h>
+#include <linux/mdio.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_mdio.h>
+#include <linux/of_net.h>
+#include <net/dsa.h>
+
+/******** hardware definitions ********/
+
+#define YT921X_SMI_SWITCHID_M		GENMASK(3, 2)
+#define  YT921X_SMI_SWITCHID(x)			FIELD_PREP(YT921X_SMI_SWITCHID_M, (x))
+#define YT921X_SMI_AD			BIT(1)
+#define  YT921X_SMI_ADDR			0
+#define  YT921X_SMI_DATA			YT921X_SMI_AD
+#define YT921X_SMI_RW			BIT(0)
+#define  YT921X_SMI_WRITE			0
+#define  YT921X_SMI_READ			YT921X_SMI_RW
+
+#define YT921X_SWITCHID_NUM		4
+
+#define YT921X_RST			0x80000
+#define  YT921X_RST_HW				BIT(31)
+#define  YT921X_RST_SW				BIT(1)
+#define YT921X_FUNC			0x80004
+#define  YT921X_FUNC_MIB			BIT(1)
+#define YT921X_CHIP_ID			0x80008
+#define  YT921X_CHIP_ID_MAJOR			GENMASK(31, 16)
+#define YT921X_EXT_CPU_PORT		0x8000c
+#define  YT921X_EXT_CPU_PORT_TAG_EN		BIT(15)
+#define  YT921X_EXT_CPU_PORT_PORT_EN		BIT(14)
+#define  YT921X_EXT_CPU_PORT_PORT_M		GENMASK(3, 0)
+#define   YT921X_EXT_CPU_PORT_PORT(x)			FIELD_PREP(YT921X_EXT_CPU_PORT_PORT_M, (x))
+#define YT921X_CPU_TAG_TPID		0x80010
+#define  YT921X_CPU_TAG_TPID_TPID_M		GENMASK(15, 0)
+#define   YT921X_CPU_TAG_TPID_TPID_DEFAULT		0x9988
+#define YT921X_SGMII_CTRL		0x80028
+#define  YT921X_SGMII_CTRL_EXTIFn_TEST(port)	BIT((port) - 3)
+#define  YT921X_SGMII_CTRL_EXTIFn(port)		BIT((port) - 8)
+#define YT921X_SGMIIn(port)		(0x8008c + 4 * ((port) - 8))
+#define  YT921X_SGMII_MODE_M			GENMASK(9, 7)
+#define   YT921X_SGMII_MODE(x)				FIELD_PREP(YT921X_SGMII_MODE_M, (x))
+#define   YT921X_SGMII_MODE_SGMII_MAC			YT921X_SGMII_MODE(0)
+#define   YT921X_SGMII_MODE_SGMII_PHY			YT921X_SGMII_MODE(1)
+#define   YT921X_SGMII_MODE_1000BASEX			YT921X_SGMII_MODE(2)
+#define   YT921X_SGMII_MODE_100BASEX			YT921X_SGMII_MODE(3)
+#define   YT921X_SGMII_MODE_2500BASEX			YT921X_SGMII_MODE(4)
+#define   YT921X_SGMII_MODE_BASEX			YT921X_SGMII_MODE(5)
+#define   YT921X_SGMII_MODE_DISABLE			YT921X_SGMII_MODE(6)
+#define  YT921X_SGMII_RX_PAUSE			BIT(6)
+#define  YT921X_SGMII_TX_PAUSE			BIT(5)
+#define  YT921X_SGMII_LINK			BIT(4)  /* force link */
+#define  YT921X_SGMII_DUPLEX_FULL		BIT(3)
+#define  YT921X_SGMII_SPEED_M			GENMASK(2, 0)
+#define   YT921X_SGMII_SPEED(x)				FIELD_PREP(YT921X_SGMII_SPEED_M, (x))
+#define   YT921X_SGMII_SPEED_10				YT921X_SGMII_SPEED(0)
+#define   YT921X_SGMII_SPEED_100			YT921X_SGMII_SPEED(1)
+#define   YT921X_SGMII_SPEED_1000			YT921X_SGMII_SPEED(2)
+#define   YT921X_SGMII_SPEED_2500			YT921X_SGMII_SPEED(4)
+#define YT921X_PORTn_CTRL(port)		(0x80100 + 4 * (port))
+#define  YT921X_PORT_CTRL_PAUSE_AN		BIT(10)
+#define YT921X_PORTn_STATUS(port)	(0x80200 + 4 * (port))
+#define  YT921X_PORT_LINK			BIT(9)  /* CTRL: auto negotiation */
+#define  YT921X_PORT_HALF_PAUSE			BIT(8)  /* Half-duplex back pressure mode */
+#define  YT921X_PORT_DUPLEX_FULL		BIT(7)
+#define  YT921X_PORT_RX_PAUSE			BIT(6)
+#define  YT921X_PORT_TX_PAUSE			BIT(5)
+#define  YT921X_PORT_RX_MAC_EN			BIT(4)
+#define  YT921X_PORT_TX_MAC_EN			BIT(3)
+#define  YT921X_PORT_SPEED_M			GENMASK(2, 0)
+#define   YT921X_PORT_SPEED(x)				FIELD_PREP(YT921X_PORT_SPEED_M, (x))
+#define   YT921X_PORT_SPEED_10				YT921X_PORT_SPEED(0)
+#define   YT921X_PORT_SPEED_100				YT921X_PORT_SPEED(1)
+#define   YT921X_PORT_SPEED_1000			YT921X_PORT_SPEED(2)
+#define   YT921X_PORT_SPEED_2500			YT921X_PORT_SPEED(4)
+#define YT921X_PON_STRAP_FUNC		0x80320
+#define YT921X_PON_STRAP_VAL		0x80324
+#define YT921X_PON_STRAP_CAP		0x80328
+#define  YT921X_PON_STRAP_EEE			BIT(16)
+#define  YT921X_PON_STRAP_LOOP_DETECT		BIT(7)
+#define YT921X_MDIO_POLLINGn(port)	(0x80364 + 4 * ((port) - 8))
+#define  YT921X_MDIO_POLLING_DUPLEX_FULL	BIT(4)
+#define  YT921X_MDIO_POLLING_LINK		BIT(3)
+#define  YT921X_MDIO_POLLING_SPEED_M		GENMASK(2, 0)
+#define   YT921X_MDIO_POLLING_SPEED(x)			FIELD_PREP(YT921X_MDIO_POLLING_SPEED_M, (x))
+#define   YT921X_MDIO_POLLING_SPEED_10			YT921X_MDIO_POLLING_SPEED(0)
+#define   YT921X_MDIO_POLLING_SPEED_100			YT921X_MDIO_POLLING_SPEED(1)
+#define   YT921X_MDIO_POLLING_SPEED_1000		YT921X_MDIO_POLLING_SPEED(2)
+#define   YT921X_MDIO_POLLING_SPEED_2500		YT921X_MDIO_POLLING_SPEED(4)
+#define YT921X_SENSOR			0x8036c
+#define  YT921X_SENSOR_TEMP			BIT(18)
+#define YT921X_TEMP			0x80374
+#define YT921X_CHIP_MODE		0x80388
+#define  YT921X_CHIP_MODE_MODE			GENMASK(1, 0)
+#define YT921X_XMII_CTRL		0x80394
+#define  YT921X_XMII_CTRL_EXTIFn(port)		BIT(9 - (port))  /* Yes, it's reversed */
+#define YT921X_XMIIn(port)		(0x80400 + 8 * ((port) - 8))
+#define  YT921X_XMII_MODE_M			GENMASK(31, 29)
+#define   YT921X_XMII_MODE(x)				FIELD_PREP(YT921X_XMII_MODE_M, (x))
+#define   YT921X_XMII_MODE_MII				YT921X_XMII_MODE(0)
+#define   YT921X_XMII_MODE_REVMII			YT921X_XMII_MODE(1)
+#define   YT921X_XMII_MODE_RMII				YT921X_XMII_MODE(2)
+#define   YT921X_XMII_MODE_REVRMII			YT921X_XMII_MODE(3)
+#define   YT921X_XMII_MODE_RGMII			YT921X_XMII_MODE(4)
+#define   YT921X_XMII_MODE_DISABLE			YT921X_XMII_MODE(5)
+#define  YT921X_XMII_LINK			BIT(19)  /* force link */
+#define  YT921X_XMII_EN				BIT(18)
+#define  YT921X_XMII_SOFT_RST			BIT(17)
+#define  YT921X_XMII_RGMII_TX_DELAY_150PS_M	GENMASK(16, 13)
+#define   YT921X_XMII_RGMII_TX_DELAY_150PS(x)		FIELD_PREP(YT921X_XMII_RGMII_TX_DELAY_150PS_M, (x))
+#define  YT921X_XMII_TX_CLK_IN			BIT(11)
+#define  YT921X_XMII_RX_CLK_IN			BIT(10)
+#define  YT921X_XMII_RGMII_TX_DELAY_2NS		BIT(8)
+#define  YT921X_XMII_RGMII_TX_CLK_OUT		BIT(7)
+#define  YT921X_XMII_RGMII_RX_DELAY_150PS_M	GENMASK(6, 3)
+#define   YT921X_XMII_RGMII_RX_DELAY_150PS(x)		FIELD_PREP(YT921X_XMII_RGMII_RX_DELAY_150PS_M, (x))
+#define  YT921X_XMII_RMII_PHY_TX_CLK_OUT	BIT(2)
+#define  YT921X_XMII_REVMII_TX_CLK_OUT		BIT(1)
+#define  YT921X_XMII_REVMII_RX_CLK_OUT		BIT(0)
+
+#define YT921X_MACn_FRAME(port)		(0x81008 + 0x1000 * (port))
+#define  YT921X_MAC_FRAME_SIZE_M		GENMASK(21, 8)
+#define   YT921X_MAC_FRAME_SIZE(x)			FIELD_PREP(YT921X_MAC_FRAME_SIZE_M, (x))
+
+#define YT921X_EEEn_VAL(port)		(0xa0000 + 4 * (port))
+#define  YT921X_EEE_VAL_DATA			BIT(1)
+
+#define YT921X_EEE_CTRL			0xb0000
+#define  YT921X_EEE_CTRL_ENn(port)		BIT(port)
+
+#define YT921X_MIB_CTRL			0xc0004
+#define  YT921X_MIB_CTRL_CLEAN			BIT(30)
+#define  YT921X_MIB_CTRL_PORT_M			GENMASK(6, 3)
+#define   YT921X_MIB_CTRL_PORT(x)			FIELD_PREP(YT921X_MIB_CTRL_PORT_M, (x))
+#define  YT921X_MIB_CTRL_ONE_PORT		BIT(1)
+#define  YT921X_MIB_CTRL_ALL_PORT		BIT(0)
+
+#define YT921X_MIBn_DATA0(port)		(0xc0100 + 0x100 * (port))
+#define YT921X_MIBn_DATAm(port, x)	(YT921X_MIBn_DATA0(port) + 4 * (x))
+
+#define YT921X_EDATA_CTRL		0xe0000
+#define  YT921X_EDATA_CTRL_ADDR_M		GENMASK(15, 8)
+#define   YT921X_EDATA_CTRL_ADDR(x)			FIELD_PREP(YT921X_EDATA_CTRL_ADDR_M, (x))
+#define  YT921X_EDATA_CTRL_OP_M			GENMASK(3, 0)
+#define   YT921X_EDATA_CTRL_OP(x)			FIELD_PREP(YT921X_EDATA_CTRL_OP_M, (x))
+#define   YT921X_EDATA_CTRL_READ			YT921X_EDATA_CTRL_OP(5)
+#define YT921X_EDATA_DATA		0xe0004
+#define  YT921X_EDATA_DATA_DATA_M			GENMASK(31, 24)
+#define  YT921X_EDATA_DATA_STATUS_M		GENMASK(3, 0)
+#define   YT921X_EDATA_DATA_STATUS(x)			FIELD_PREP(YT921X_EDATA_DATA_STATUS_M, (x))
+#define   YT921X_EDATA_DATA_IDLE			YT921X_EDATA_DATA_STATUS(3)
+
+#define YT921X_EXT_MBUS_OP		0x6a000
+#define YT921X_INT_MBUS_OP		0xf0000
+#define  YT921X_MBUS_OP_START			BIT(0)
+#define YT921X_EXT_MBUS_CTRL		0x6a004
+#define YT921X_INT_MBUS_CTRL		0xf0004
+#define  YT921X_MBUS_CTRL_PORT_M		GENMASK(25, 21)
+#define   YT921X_MBUS_CTRL_PORT(x)			FIELD_PREP(YT921X_MBUS_CTRL_PORT_M, (x))
+#define  YT921X_MBUS_CTRL_REG_M			GENMASK(20, 16)
+#define   YT921X_MBUS_CTRL_REG(x)			FIELD_PREP(YT921X_MBUS_CTRL_REG_M, (x))
+#define  YT921X_MBUS_CTRL_TYPE_M		GENMASK(11, 8)  /* wild guess */
+#define   YT921X_MBUS_CTRL_TYPE(x)			FIELD_PREP(YT921X_MBUS_CTRL_TYPE_M, (x))
+#define   YT921X_MBUS_CTRL_TYPE_C22			YT921X_MBUS_CTRL_TYPE(4)
+#define  YT921X_MBUS_CTRL_OP_M			GENMASK(3, 2)  /* wild guess */
+#define   YT921X_MBUS_CTRL_OP(x)			FIELD_PREP(YT921X_MBUS_CTRL_OP_M, (x))
+#define   YT921X_MBUS_CTRL_WRITE			YT921X_MBUS_CTRL_OP(1)
+#define   YT921X_MBUS_CTRL_READ				YT921X_MBUS_CTRL_OP(2)
+#define YT921X_EXT_MBUS_DOUT		0x6a008
+#define YT921X_INT_MBUS_DOUT		0xf0008
+#define YT921X_EXT_MBUS_DIN		0x6a00c
+#define YT921X_INT_MBUS_DIN		0xf000c
+
+#define YT921X_VLAN_IGR_FILTER		0x180280
+#define  YT921X_VLAN_IGR_FILTER_PORTn_IGMP_BYPASS(port)	BIT((port) + 11)
+#define  YT921X_VLAN_IGR_FILTER_PORTn_EN(port)	BIT(port)
+#define YT921X_PORTn_ISOLATION(port)	(0x180294 + 4 * (port))
+#define  YT921X_PORT_ISOLATION_BLOCKn(port)	BIT(port)
+#define YT921X_PORTn_LEARN(port)	(0x1803d0 + 4 * (port))
+#define  YT921X_PORT_LEARN_VID_LEARN_MULTI_EN	BIT(22)
+#define  YT921X_PORT_LEARN_VID_LEARN_MODE	BIT(21)
+#define  YT921X_PORT_LEARN_VID_LEARN_EN		BIT(20)
+#define  YT921X_PORT_LEARN_SUSPEND_COPY_EN	BIT(19)
+#define  YT921X_PORT_LEARN_SUSPEND_DROP_EN	BIT(18)
+#define  YT921X_PORT_LEARN_DIS			BIT(17)
+#define  YT921X_PORT_LEARN_LIMIT_EN		BIT(16)
+#define  YT921X_PORT_LEARN_LIMIT_M		GENMASK(15, 8)
+#define  YT921X_PORT_LEARN_LIMIT_EXCEED_DROP	BIT(2)
+#define  YT921X_PORT_LEARN_MODE_M		GENMASK(1, 0)
+#define   YT921X_PORT_LEARN_MODE(x)			FIELD_PREP(YT921X_PORT_LEARN_MODE_M, (x))
+#define   YT921X_PORT_LEARN_MODE_AUTO			YT921X_PORT_LEARN_MODE(0)
+#define   YT921X_PORT_LEARN_MODE_AUTO_AND_COPY		YT921X_PORT_LEARN_MODE(1)
+#define   YT921X_PORT_LEARN_MODE_CPU_CONTROL		YT921X_PORT_LEARN_MODE(2)
+#define YT921X_AGEING			0x180440
+#define  YT921X_AGEING_INTERVAL_M		GENMASK(15, 0)
+#define YT921X_FDB_IN0			0x180454
+#define YT921X_FDB_IN1			0x180458
+#define YT921X_FDB_IN2			0x18045c
+#define YT921X_FDB_OP			0x180460
+#define  YT921X_FDB_OP_INDEX_M			GENMASK(22, 11)
+#define   YT921X_FDB_OP_INDEX(x)			FIELD_PREP(YT921X_FDB_OP_INDEX_M, (x))
+#define  YT921X_FDB_OP_MODE_INDEX		BIT(10)  /* mac+fid / index */
+#define  YT921X_FDB_OP_FLUSH_MCAST		BIT(9)  /* ucast / mcast */
+#define  YT921X_FDB_OP_FLUSH_M			GENMASK(8, 7)
+#define   YT921X_FDB_OP_FLUSH(x)			FIELD_PREP(YT921X_FDB_OP_FLUSH_M, (x))
+#define   YT921X_FDB_OP_FLUSH_ALL			YT921X_FDB_OP_FLUSH(0)
+#define   YT921X_FDB_OP_FLUSH_PORT			YT921X_FDB_OP_FLUSH(1)
+#define   YT921X_FDB_OP_FLUSH_PORT_VID			YT921X_FDB_OP_FLUSH(2)
+#define   YT921X_FDB_OP_FLUSH_VID			YT921X_FDB_OP_FLUSH(3)
+#define  YT921X_FDB_OP_FLUSH_STATIC		BIT(6)
+#define  YT921X_FDB_OP_NEXT_TYPE_M		GENMASK(5, 4)
+#define   YT921X_FDB_OP_NEXT_TYPE(x)			FIELD_PREP(YT921X_FDB_OP_NEXT_TYPE_M, (x))
+#define   YT921X_FDB_OP_NEXT_TYPE_UCAST_PORT		YT921X_FDB_OP_NEXT_TYPE(0)
+#define   YT921X_FDB_OP_NEXT_TYPE_UCAST_VID		YT921X_FDB_OP_NEXT_TYPE(1)
+#define   YT921X_FDB_OP_NEXT_TYPE_UCAST			YT921X_FDB_OP_NEXT_TYPE(2)
+#define   YT921X_FDB_OP_NEXT_TYPE_MCAST			YT921X_FDB_OP_NEXT_TYPE(3)
+#define  YT921X_FDB_OP_OP_M			GENMASK(3, 1)
+#define   YT921X_FDB_OP_OP(x)				FIELD_PREP(YT921X_FDB_OP_OP_M, (x))
+#define   YT921X_FDB_OP_OP_ADD				YT921X_FDB_OP_OP(0)
+#define   YT921X_FDB_OP_OP_DEL				YT921X_FDB_OP_OP(1)
+#define   YT921X_FDB_OP_OP_GET_ONE			YT921X_FDB_OP_OP(2)
+#define   YT921X_FDB_OP_OP_GET_NEXT			YT921X_FDB_OP_OP(3)
+#define   YT921X_FDB_OP_OP_FLUSH			YT921X_FDB_OP_OP(4)
+#define  YT921X_FDB_OP_START			BIT(0)
+#define YT921X_FDB_RESULT		0x180464
+#define  YT921X_FDB_RESULT_DONE			BIT(15)
+#define  YT921X_FDB_RESULT_NOTFOUND		BIT(14)
+#define  YT921X_FDB_RESULT_OVERWRITED		BIT(13)
+#define  YT921X_FDB_RESULT_INDEX_M		GENMASK(11, 0)
+#define   YT921X_FDB_RESULT_INDEX(x)			FIELD_PREP(YT921X_FDB_RESULT_INDEX_M, (x))
+#define YT921X_FDB_OUT0			0x1804b0
+#define  YT921X_FDB_IO0_ADDR_HI4_M		GENMASK(31, 0)
+#define YT921X_FDB_OUT1			0x1804b4
+#define  YT921X_FDB_IO1_EGR_INT_PRI_EN		BIT(31)
+#define  YT921X_FDB_IO1_STATUS_M		GENMASK(30, 28)
+#define   YT921X_FDB_IO1_STATUS(x)			FIELD_PREP(YT921X_FDB_IO1_STATUS_M, (x))
+#define   YT921X_FDB_IO1_STATUS_INVALID			YT921X_FDB_IO1_STATUS(0)
+#define   YT921X_FDB_IO1_STATUS_MIN_TIME		YT921X_FDB_IO1_STATUS(1)
+#define   YT921X_FDB_IO1_STATUS_MOVE_AGING_MAX_TIME	YT921X_FDB_IO1_STATUS(3)
+#define   YT921X_FDB_IO1_STATUS_MAX_TIME		YT921X_FDB_IO1_STATUS(5)
+#define   YT921X_FDB_IO1_STATUS_PENDING			YT921X_FDB_IO1_STATUS(6)
+#define   YT921X_FDB_IO1_STATUS_STATIC			YT921X_FDB_IO1_STATUS(7)
+#define  YT921X_FDB_IO1_FID_M			GENMASK(27, 16)  /* filtering ID (VID) */
+#define   YT921X_FDB_IO1_FID(x)				FIELD_PREP(YT921X_FDB_IO1_FID_M, (x))
+#define  YT921X_FDB_IO1_ADDR_LO2_M		GENMASK(15, 0)
+#define YT921X_FDB_OUT2			0x1804b8
+#define  YT921X_FDB_IO2_MOVE_AGING_STATUS_M	GENMASK(31, 30)
+#define  YT921X_FDB_IO2_IGR_DROP		BIT(29)
+#define  YT921X_FDB_IO2_EGR_PORTS_M		GENMASK(28, 18)
+#define   YT921X_FDB_IO2_EGR_PORTS(x)			FIELD_PREP(YT921X_FDB_IO2_EGR_PORTS_M, (x))
+#define  YT921X_FDB_IO2_EGR_DROP		BIT(17)
+#define  YT921X_FDB_IO2_COPY_TO_CPU		BIT(16)
+#define  YT921X_FDB_IO2_IGR_INT_PRI_EN		BIT(15)
+#define  YT921X_FDB_IO2_INT_PRI_M		GENMASK(14, 12)
+#define   YT921X_FDB_IO2_INT_PRI(x)			FIELD_PREP(YT921X_FDB_IO2_INT_PRI_M, (x))
+#define  YT921X_FDB_IO2_NEW_VID_M		GENMASK(11, 0)
+#define   YT921X_FDB_IO2_NEW_VID(x)			FIELD_PREP(YT921X_FDB_IO2_NEW_VID_M, (x))
+#define YT921X_FILTER_UNK_UCAST		0x180508
+#define YT921X_FILTER_UNK_MCAST		0x18050c
+#define YT921X_FILTER_MCAST		0x180510
+#define YT921X_FILTER_BCAST		0x180514
+#define  YT921X_FILTER_PORTS_M			GENMASK(10, 0)
+#define   YT921X_FILTER_PORTS(x)			FIELD_PREP(YT921X_FILTER_PORTS_M, (x))
+#define  YT921X_FILTER_PORTn(port)		BIT(port)
+#define YT921X_CPU_COPY			0x180690
+#define  YT921X_CPU_COPY_FORCE_INT_PORT		BIT(2)
+#define  YT921X_CPU_COPY_TO_INT_CPU		BIT(1)
+#define  YT921X_CPU_COPY_TO_EXT_CPU		BIT(0)
+#define YT921X_ACT_UNK_UCAST		0x180734
+#define YT921X_ACT_UNK_MCAST		0x180738
+#define  YT921X_ACT_UNK_MCAST_BYPASS_DROP_RMA	BIT(23)
+#define  YT921X_ACT_UNK_MCAST_BYPASS_DROP_IGMP	BIT(22)
+#define  YT921X_ACT_UNK_ACTn_M(port)		GENMASK(2 * (port) + 1, 2 * (port))
+#define   YT921X_ACT_UNK_ACTn(port, x)			((x) << (2 * (port)))
+#define   YT921X_ACT_UNK_ACTn_FORWARD(port)		YT921X_ACT_UNK_ACTn(port, 0)
+#define   YT921X_ACT_UNK_ACTn_TRAP(port)		YT921X_ACT_UNK_ACTn(port, 1)  /* interrupt CPU */
+#define   YT921X_ACT_UNK_ACTn_DROP(port)		YT921X_ACT_UNK_ACTn(port, 2)
+#define   YT921X_ACT_UNK_ACTn_COPY(port)		YT921X_ACT_UNK_ACTn(port, 3)
+#define YT921X_FDB_HW_FLUSH		0x180958
+#define  YT921X_FDB_HW_FLUSH_ON_LINKDOWN	BIT(0)
+
+#define YT921X_VLANn_CTRL(vlan)		(0x188000 + 8 * (vlan))
+#define  YT921X_VLAN_CTRL_FID_LO9_M		GENMASK(31, 23)
+#define  YT921X_VLAN_CTRL_LEARN_DIS		BIT(22)
+#define  YT921X_VLAN_CTRL_INT_PRI_EN		BIT(21)
+#define  YT921X_VLAN_CTRL_INT_PRI_M		GENMASK(20, 18)
+#define  YT921X_VLAN_CTRL_PORTS_M		GENMASK(17, 7)
+#define   YT921X_VLAN_CTRL_PORTS(x)			FIELD_PREP(YT921X_VLAN_CTRL_PORTS_M, (x))
+#define  YT921X_VLAN_CTRL_PORTn(port)		BIT((port) + 7)
+#define  YT921X_VLAN_CTRL_BYPASS_1X_AC		BIT(6)
+#define  YT921X_VLAN_CTRL_METER_EN		BIT(5)
+#define  YT921X_VLAN_CTRL_METER_ID_M		GENMASK(4, 0)
+#define YT921X_VLANn_CTRL1(vlan)	(0x188004 + 8 * (vlan))
+#define  YT921X_VLAN_CTRL1_UNTAG_PORTS_M	GENMASK(18, 8)
+#define   YT921X_VLAN_CTRL1_UNTAG_PORTS(x)		FIELD_PREP(YT921X_VLAN_CTRL1_UNTAG_PORTS_M, (x))
+#define  YT921X_VLAN_CTRL1_UNTAG_PORTn(port)	BIT((port) + 8)
+#define  YT921X_VLAN_CTRL1_STP_ID_M		GENMASK(7, 4)
+#define   YT921X_VLAN_CTRL1_STP_ID(x)			FIELD_PREP(YT921X_VLAN_CTRL1_STP_ID_M, (x))
+#define  YT921X_VLAN_CTRL1_SVLAN_EN		BIT(3)
+#define  YT921X_VLAN_CTRL1_FID_HI3_M		GENMASK(2, 0)
+
+#define YT921X_PORTn_VLAN_CTRL(port)	(0x230010 + 4 * (port))
+#define  YT921X_PORT_VLAN_CTRL_SVLAN_PRI_EN	BIT(31)
+#define  YT921X_PORT_VLAN_CTRL_CVLAN_PRI_EN	BIT(30)
+#define  YT921X_PORT_VLAN_CTRL_SVID_M		GENMASK(29, 18)
+#define   YT921X_PORT_VLAN_CTRL_SVID(x)			FIELD_PREP(YT921X_PORT_VLAN_CTRL_SVID_M, (x))
+#define  YT921X_PORT_VLAN_CTRL_CVID_M		GENMASK(17, 6)
+#define   YT921X_PORT_VLAN_CTRL_CVID(x)			FIELD_PREP(YT921X_PORT_VLAN_CTRL_CVID_M, (x))
+#define  YT921X_PORT_VLAN_CTRL_SVLAN_PRI_M	GENMASK(5, 3)
+#define  YT921X_PORT_VLAN_CTRL_CVLAN_PRI_M	GENMASK(2, 0)
+#define YT921X_PORTn_VLAN_CTRL1(port)	(0x230080 + 4 * (port))
+#define  YT921X_PORT_VLAN_CTRL1_VLAN_RANGE_EN	BIT(8)
+#define  YT921X_PORT_VLAN_CTRL1_VLAN_RANGE_PROFILE_ID_M	GENMASK(7, 4)
+#define  YT921X_PORT_VLAN_CTRL1_SVLAN_DROP_TAGGED	BIT(3)
+#define  YT921X_PORT_VLAN_CTRL1_SVLAN_DROP_UNTAGGED	BIT(2)
+#define  YT921X_PORT_VLAN_CTRL1_CVLAN_DROP_TAGGED	BIT(1)
+#define  YT921X_PORT_VLAN_CTRL1_CVLAN_DROP_UNTAGGED	BIT(0)
+
+#define YT921X_MIRROR			0x300300
+#define  YT921X_MIRROR_IGR_PORTS_M		GENMASK(26, 16)
+#define   YT921X_MIRROR_IGR_PORTS(x)			FIELD_PREP(YT921X_MIRROR_IGR_PORTS_M, (x))
+#define  YT921X_MIRROR_IGR_PORTn(port)		BIT((port) + 16)
+#define  YT921X_MIRROR_EGR_PORTS_M		GENMASK(14, 4)
+#define   YT921X_MIRROR_EGR_PORTS(x)			FIELD_PREP(YT921X_MIRROR_EGR_PORTS_M, (x))
+#define  YT921X_MIRROR_EGR_PORTn(port)		BIT((port) + 4)
+#define  YT921X_MIRROR_PORT_M			GENMASK(3, 0)
+#define   YT921X_MIRROR_PORT(x)				FIELD_PREP(YT921X_MIRROR_PORT_M, (x))
+
+#define YT921X_REG_END			0x400000  /* as long as reg space is below this */
+
+#define YT921X_TAG_LEN			8
+
+#define YT921X_EDATA_EXTMODE		0xfb
+#define YT921X_EDATA_LEN		0x100
+
+#define YT921X_FDB_NUM		4096
+
+enum yt921x_fdb_entry_status {
+	YT921X_FDB_ENTRY_STATUS_INVALID = 0,
+	YT921X_FDB_ENTRY_STATUS_MIN_TIME = 1,
+	YT921X_FDB_ENTRY_STATUS_MOVE_AGING_MAX_TIME = 3,
+	YT921X_FDB_ENTRY_STATUS_MAX_TIME = 5,
+	YT921X_FDB_ENTRY_STATUS_PENDING = 6,
+	YT921X_FDB_ENTRY_STATUS_STATIC = 7,
+};
+
+#define YT921X_PVID_DEFAULT		1
+
+#define YT921X_FRAME_SIZE_MAX		0x2400  /* 9216 */
+
+#define YT921X_RST_DELAY_US		10000
+
+struct yt921x_mib_desc {
+	unsigned int size;
+	unsigned int offset;
+	const char *name;
+};
+
+#define MIB_DESC(_size, _offset, _name) {_size, _offset, _name}
+
+static const struct yt921x_mib_desc yt921x_mib_descs[] = {
+	MIB_DESC(1, 0x00, "RxBroadcast"),	/* rx broadcast pkts */
+	MIB_DESC(1, 0x04, "RxPause"),		/* rx pause pkts */
+	MIB_DESC(1, 0x08, "RxMulticast"),	/* rx multicast pkts, excluding pause and OAM */
+	MIB_DESC(1, 0x0c, "RxCrcErr"),		/* rx crc err pkts, len >= 64B */
+
+	MIB_DESC(1, 0x10, "RxAlignErr"),	/* rx pkts with odd number of bytes */
+	MIB_DESC(1, 0x14, "RxUnderSizeErr"),	/* rx crc ok pkts, len < 64B */
+	MIB_DESC(1, 0x18, "RxFragErr"),		/* rx crc err pkts, len < 64B */
+	MIB_DESC(1, 0x1c, "RxPktSz64"),		/* rx pkts, len == 64B */
+
+	MIB_DESC(1, 0x20, "RxPktSz65To127"),	/* rx pkts, len >= 65B and <= 127B */
+	MIB_DESC(1, 0x24, "RxPktSz128To255"),	/* rx pkts, len >= 128B and <= 255B */
+	MIB_DESC(1, 0x28, "RxPktSz256To511"),	/* rx pkts, len >= 256B and <= 511B */
+	MIB_DESC(1, 0x2c, "RxPktSz512To1023"),	/* rx pkts, len >= 512B and <= 1023B */
+
+	MIB_DESC(1, 0x30, "RxPktSz1024To1518"),	/* rx pkts, len >= 1024B and <= 1518B */
+	MIB_DESC(1, 0x34, "RxPktSz1519ToMax"),	/* rx pkts, len >= 1519B */
+	MIB_DESC(2, 0x38, "RxGoodBytes"),	/* total bytes of rx ok pkts */
+	/* 0x3c */
+
+	MIB_DESC(2, 0x40, "RxBadBytes"),	/* total bytes of rx err pkts */
+	/* 0x44 */
+	MIB_DESC(2, 0x48, "RxOverSzErr"),	/* rx pkts, len > mac frame size */
+	/* 0x4c */
+
+	MIB_DESC(1, 0x50, "RxDropped"),		/* rx dropped pkts, excluding crc err and pause */
+	MIB_DESC(1, 0x54, "TxBroadcast"),	/* tx broadcast pkts */
+	MIB_DESC(1, 0x58, "TxPause"),		/* tx pause pkts */
+	MIB_DESC(1, 0x5c, "TxMulticast"),	/* tx multicast pkts, excluding pause and OAM */
+
+	MIB_DESC(1, 0x60, "TxUnderSizeErr"),	/* tx pkts, len < 64B */
+	MIB_DESC(1, 0x64, "TxPktSz64"),		/* tx pkts, len == 64B */
+	MIB_DESC(1, 0x68, "TxPktSz65To127"),	/* tx pkts, len >= 65B and <= 127B */
+	MIB_DESC(1, 0x6c, "TxPktSz128To255"),	/* tx pkts, len >= 128B and <= 255B */
+
+	MIB_DESC(1, 0x70, "TxPktSz256To511"),	/* tx pkts, len >= 256B and <= 511B */
+	MIB_DESC(1, 0x74, "TxPktSz512To1023"),	/* tx pkts, len >= 512B and <= 1023B */
+	MIB_DESC(1, 0x78, "TxPktSz1024To1518"),	/* tx pkts, len >= 1024B and <= 1518B */
+	MIB_DESC(1, 0x7c, "TxPktSz1519ToMax"),	/* tx pkts, len >= 1519B */
+
+	MIB_DESC(2, 0x80, "TxGoodBytes"),	/* total bytes of tx ok pkts */
+	/* 0x84 */
+	MIB_DESC(2, 0x88, "TxCollision"),	/* collisions before 64B */
+	/* 0x8c */
+
+	MIB_DESC(1, 0x90, "TxExcessiveCollistion"),	/* aborted pkts due to too many colls */
+	MIB_DESC(1, 0x94, "TxMultipleCollision"),	/* multiple collision for one mac */
+	MIB_DESC(1, 0x98, "TxSingleCollision"),	/* one collision for one mac */
+	MIB_DESC(1, 0x9c, "TxPkt"),		/* tx ok pkts */
+
+	MIB_DESC(1, 0xa0, "TxDeferred"),	/* delayed pkts due to defer signal */
+	MIB_DESC(1, 0xa4, "TxLateCollision"),	/* collisions after 64B */
+	MIB_DESC(1, 0xa8, "RxOAM"),		/* rx OAM pkts */
+	MIB_DESC(1, 0xac, "TxOAM"),		/* tx OAM pkts */
+};
+
+struct yt921x_mib_raw {
+	u32 rx_broadcast;
+	u32 rx_pause;
+	u32 rx_multicast;
+	u32 rx_crc_errors;
+
+	u32 rx_frame_errors;
+	u32 rx_undersize_errors;
+	u32 rx_fragment_errors;
+	u32 rx_64byte;
+
+	u32 rx_65_127byte;
+	u32 rx_128_255byte;
+	u32 rx_256_511byte;
+	u32 rx_512_1023byte;
+
+	u32 rx_1024_1518byte;
+	u32 rx_jumbo;
+	u32 rx_good_bytes_hi;
+	u32 rx_good_bytes_lo;
+
+	u32 rx_bad_bytes_hi;
+	u32 rx_bad_bytes_lo;
+	u32 rx_over_errors_hi;
+	u32 rx_over_errors_lo;
+
+	u32 rx_dropped;
+	u32 tx_broadcast;
+	u32 tx_pause;
+	u32 tx_multicast;
+
+	u32 tx_undersize_errors;
+	u32 tx_64byte;
+	u32 tx_65_127byte;
+	u32 tx_128_255byte;
+
+	u32 tx_256_511byte;
+	u32 tx_512_1023byte;
+	u32 tx_1024_1518byte;
+	u32 tx_jumbo;
+
+	u32 tx_good_bytes_hi;
+	u32 tx_good_bytes_lo;
+	u32 tx_collisions_hi;
+	u32 tx_collisions_lo;
+
+	u32 tx_aborted_errors;
+	u32 tx_multiple_collisions;
+	u32 tx_single_collisions;
+	u32 tx_good;
+
+	u32 tx_defer;
+	u32 tx_window_errors;
+	u32 rx_oam;
+	u32 tx_oam;
+};
+
+#define YT921X_MIBn_name(port, name) \
+	(YT921X_MIBn_DATA0(port) + offsetof(struct yt921x_mib_raw, name))
+
+#define yt921x_mib_raw_u64(mib, name) \
+	(((u64)(mib)->name##_hi << 32) | (mib)->name##_lo)
+
+struct yt921x_info {
+	const char *name;
+	u16 major;
+	/* Unknown, seems to be plain enumeration */
+	u8 mode;
+	u8 extmode;
+	u16 internal_mask;
+	/* TODO: see comments in yt921x_dsa_phylink_get_caps() */
+	u16 external_mask;
+};
+
+#define YT9215_MAJOR			0x9002
+#define YT9218_MAJOR			0x9001
+
+#define YT921X_PORT_MASK_INTn(port)	BIT(port)
+#define YT921X_PORT_MASK_INT0_n(n)	GENMASK((n) - 1, 0)
+#define YT921X_PORT_MASK_EXT0		BIT(8)
+#define YT921X_PORT_MASK_EXT1		BIT(9)
+
+#define yt921x_port_is_internal(port) ((port) < 8)
+#define yt921x_port_is_external(port) (8 <= (port) && (port) < 9)
+
+/* 8 internal + 2 external + 1 mcu */
+#define YT921X_PORT_NUM			11
+
+static const struct yt921x_info yt921x_infos[] = {
+	{
+		"YT9215SC", YT9215_MAJOR, 1, 0,
+		YT921X_PORT_MASK_INT0_n(5),
+		YT921X_PORT_MASK_EXT0 | YT921X_PORT_MASK_EXT1,
+	},
+	{
+		"YT9215S", YT9215_MAJOR, 2, 0,
+		YT921X_PORT_MASK_INT0_n(5),
+		YT921X_PORT_MASK_EXT0 | YT921X_PORT_MASK_EXT1,
+	},
+	{
+		"YT9215RB", YT9215_MAJOR, 3, 0,
+		YT921X_PORT_MASK_INT0_n(5),
+		YT921X_PORT_MASK_EXT0 | YT921X_PORT_MASK_EXT1,
+	},
+	{
+		"YT9214NB", YT9215_MAJOR, 3, 2,
+		YT921X_PORT_MASK_INTn(1) | YT921X_PORT_MASK_INTn(3),
+		YT921X_PORT_MASK_EXT0 | YT921X_PORT_MASK_EXT1,
+	},
+	{
+		"YT9213NB", YT9215_MAJOR, 3, 3,
+		YT921X_PORT_MASK_INTn(1) | YT921X_PORT_MASK_INTn(3),
+		YT921X_PORT_MASK_EXT1,
+	},
+	{
+		"YT9218N", YT9218_MAJOR, 0, 0,
+		YT921X_PORT_MASK_INT0_n(8),
+		0,
+	},
+	{
+		"YT9218MB", YT9218_MAJOR, 1, 0,
+		YT921X_PORT_MASK_INT0_n(8),
+		YT921X_PORT_MASK_EXT0 | YT921X_PORT_MASK_EXT1,
+	},
+	{}
+};
+
+#define yt921x_info_port_is_internal(info, port) \
+	((info)->internal_mask & BIT(port))
+#define yt921x_info_port_is_external(info, port) \
+	((info)->external_mask & BIT(port))
+
+/******** driver definitions ********/
+
+#define YT921X_NAME	"yt921x"
+
+#define YT921X_MDIO_SLEEP_US	10000
+#define YT921X_MDIO_TIMEOUT_US	100000
+#define YT921X_RST_TIMEOUT_US	100000
+
+enum yt921x_fixed {
+	YT921X_FIXED_NOTFIXED = 0,
+	YT921X_FIXED_10,
+	YT921X_FIXED_100,
+	YT921X_FIXED_1000,
+	YT921X_FIXED_2500,
+};
+
+struct yt921x_smi_ops {
+	void (*acquire)(void *context);
+	void (*release)(void *context);
+	int (*read)(void *context, u32 reg, u32 *valp);
+	int (*write)(void *context, u32 reg, u32 val);
+};
+
+struct yt921x_smi_mdio {
+	struct mii_bus *bus;
+	int addr;
+	/* SWITCH_ID_1 / SWITCH_ID_0 of the device
+	 *
+	 * This is a way to multiplex multiple devices on the same MII phyaddr
+	 * and should be configurable in DT. However, MDIO core simply doesn't
+	 * allow multiple devices over one reg addr, so this is a fixed value
+	 * for now until a solution is found.
+	 *
+	 * Keep this because we need switchid to form MII regaddrs anyway.
+	 */
+	unsigned char switchid;
+};
+
+/* TODO: SPI/I2C */
+
+struct yt921x_port {
+	struct yt921x_mib_raw mib;
+
+	u32 tx_delay_150ps;
+	u32 rx_delay_150ps;
+	bool tx_delay_2ns;
+
+	bool vlan_filtering;
+	/* drop untagged frames if pvid is not set */
+	u16 pvid;
+	/* drop tagged frames if vid is not set */
+	u16 vids_cnt;
+
+	bool hairpin;
+	bool isolated;
+	u16 bridge_mask;
+};
+
+struct yt921x_priv {
+	struct dsa_switch ds;
+
+	const struct yt921x_info *info;
+	u32 pon_strap_cap;
+	u16 tag_eth_p;
+	u16 cpu_ports_mask;
+
+	/* slave bus */
+	const struct yt921x_smi_ops *smi_ops;
+	void *smi_ctx;
+
+	/* mdio master bus */
+	struct mii_bus *mbus_int;
+	struct mii_bus *mbus_ext;
+
+	struct yt921x_port ports[YT921X_PORT_NUM];
+
+	u16 active_cpu_ports_mask;
+	u16 eee_ports_mask;
+
+	/* register prober */
+	u32 reg_addr;
+	u32 reg_val;
+	bool reg_valid;
+};
+
+#define to_yt921x_priv(_ds) container_of_const(_ds, struct yt921x_priv, ds)
+#define to_device(priv) ((priv)->ds.dev)
+
+#define should_unreachable() \
+	pr_err("%s: !!unreachable %d, please report a bug!\n", \
+	       __func__, __LINE__)
+#define consume_retval(res) do { \
+	if (unlikely(res)) \
+		pr_err("%s: %i\n", __func__, (res)); \
+} while (0)
+
+/******** smi ********/
+
+static void yt921x_smi_acquire(struct yt921x_priv *priv)
+{
+	if (priv->smi_ops->acquire)
+		priv->smi_ops->acquire(priv->smi_ctx);
+}
+
+static void yt921x_smi_release(struct yt921x_priv *priv)
+{
+	if (priv->smi_ops->release)
+		priv->smi_ops->release(priv->smi_ctx);
+}
+
+static int yt921x_smi_read(struct yt921x_priv *priv, u32 reg, u32 *valp)
+{
+	return priv->smi_ops->read(priv->smi_ctx, reg, valp);
+}
+
+static int yt921x_smi_read_burst(struct yt921x_priv *priv, u32 reg, u32 *valp)
+{
+	int res;
+
+	yt921x_smi_acquire(priv);
+	res = yt921x_smi_read(priv, reg, valp);
+	yt921x_smi_release(priv);
+
+	return res;
+}
+
+static int yt921x_smi_write(struct yt921x_priv *priv, u32 reg, u32 val)
+{
+	return priv->smi_ops->write(priv->smi_ctx, reg, val);
+}
+
+static int yt921x_smi_write_burst(struct yt921x_priv *priv, u32 reg, u32 val)
+{
+	int res;
+
+	yt921x_smi_acquire(priv);
+	res = yt921x_smi_write(priv, reg, val);
+	yt921x_smi_release(priv);
+
+	return res;
+}
+
+static int
+yt921x_smi_update_bits(struct yt921x_priv *priv, u32 reg, u32 mask, u32 val)
+{
+	int res;
+	u32 v;
+	u32 u;
+
+	res = yt921x_smi_read(priv, reg, &v);
+	if (res)
+		return res;
+
+	u = v;
+	u &= ~mask;
+	u |= val;
+	if (u == v)
+		return 0;
+
+	return yt921x_smi_write(priv, reg, u);
+}
+
+static int
+yt921x_smi_update_bits_burst(struct yt921x_priv *priv, u32 reg, u32 mask,
+			     u32 val)
+{
+	int res;
+
+	yt921x_smi_acquire(priv);
+	res = yt921x_smi_update_bits(priv, reg, mask, val);
+	yt921x_smi_release(priv);
+
+	return res;
+}
+
+static int yt921x_smi_set_bits(struct yt921x_priv *priv, u32 reg, u32 mask)
+{
+	return yt921x_smi_update_bits(priv, reg, 0, mask);
+}
+
+static int
+yt921x_smi_set_bits_burst(struct yt921x_priv *priv, u32 reg, u32 mask)
+{
+	return yt921x_smi_update_bits_burst(priv, reg, 0, mask);
+}
+
+static int yt921x_smi_clear_bits(struct yt921x_priv *priv, u32 reg, u32 mask)
+{
+	return yt921x_smi_update_bits(priv, reg, mask, 0);
+}
+
+static int
+yt921x_smi_clear_bits_burst(struct yt921x_priv *priv, u32 reg, u32 mask)
+{
+	return yt921x_smi_update_bits_burst(priv, reg, mask, 0);
+}
+
+static int
+yt921x_smi_toggle_bits(struct yt921x_priv *priv, u32 reg, u32 mask, bool set)
+{
+	return yt921x_smi_update_bits(priv, reg, mask, !set ? 0 : mask);
+}
+
+/******** smi via mdio ********/
+
+static void yt921x_smi_mdio_acquire(void *context)
+{
+	struct yt921x_smi_mdio *mdio = context;
+	struct mii_bus *bus = mdio->bus;
+
+	mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+}
+
+static void yt921x_smi_mdio_release(void *context)
+{
+	struct yt921x_smi_mdio *mdio = context;
+	struct mii_bus *bus = mdio->bus;
+
+	mutex_unlock(&bus->mdio_lock);
+}
+
+static void yt921x_smi_mdio_verify(u32 reg, u16 val)
+{
+	if (val == 0xfade)
+		pr_warn("%s: Read 0xfade at 0x%x, which is a sign of "
+			"non-existent register; consider reporting a bug if "
+			"this happens again\n", __func__, reg);
+	else if (val == 0xdead)
+		pr_warn("%s: Read 0xdead at 0x%x, which is usually a serious "
+			"data race condition; consider reporting a bug if "
+			"this happens again\n", __func__, reg);
+}
+
+static int yt921x_smi_mdio_read(void *context, u32 reg, u32 *valp)
+{
+	struct yt921x_smi_mdio *mdio = context;
+	struct mii_bus *bus = mdio->bus;
+	int addr = mdio->addr;
+	u32 reg_addr;
+	u32 reg_data;
+	u32 val;
+	int res;
+
+	reg_addr = YT921X_SMI_SWITCHID(mdio->switchid) | YT921X_SMI_ADDR |
+		   YT921X_SMI_READ;
+	res = __mdiobus_write(bus, addr, reg_addr, (reg >> 16) & 0xffff);
+	if (res)
+		return res;
+	res = __mdiobus_write(bus, addr, reg_addr, (reg >>  0) & 0xffff);
+	if (res)
+		return res;
+
+	reg_data = YT921X_SMI_SWITCHID(mdio->switchid) | YT921X_SMI_DATA |
+		   YT921X_SMI_READ;
+	res = __mdiobus_read(bus, addr, reg_data);
+	if (res < 0)
+		return res;
+	yt921x_smi_mdio_verify(reg, res);
+	val = res & 0xffff;
+	res = __mdiobus_read(bus, addr, reg_data);
+	if (res < 0)
+		return res;
+	yt921x_smi_mdio_verify(reg, res);
+	val = (val << 16) | (res & 0xffff);
+
+	*valp = val;
+	return 0;
+}
+
+static int yt921x_smi_mdio_write(void *context, u32 reg, u32 val)
+{
+	struct yt921x_smi_mdio *mdio = context;
+	struct mii_bus *bus = mdio->bus;
+	int addr = mdio->addr;
+	u32 reg_addr;
+	u32 reg_data;
+	int res;
+
+	reg_addr = YT921X_SMI_SWITCHID(mdio->switchid) | YT921X_SMI_ADDR |
+		   YT921X_SMI_WRITE;
+	res = __mdiobus_write(bus, addr, reg_addr, (reg >> 16) & 0xffff);
+	if (res)
+		return res;
+	res = __mdiobus_write(bus, addr, reg_addr, (reg >>  0) & 0xffff);
+	if (res)
+		return res;
+
+	reg_data = YT921X_SMI_SWITCHID(mdio->switchid) | YT921X_SMI_DATA |
+		   YT921X_SMI_WRITE;
+	res = __mdiobus_write(bus, addr, reg_data, (val >> 16) & 0xffff);
+	if (res)
+		return res;
+	res = __mdiobus_write(bus, addr, reg_data, (val >>  0) & 0xffff);
+	if (res)
+		return res;
+
+	return 0;
+}
+
+static const struct yt921x_smi_ops yt921x_smi_ops_mdio = {
+	.acquire = yt921x_smi_mdio_acquire,
+	.release = yt921x_smi_mdio_release,
+	.read = yt921x_smi_mdio_read,
+	.write = yt921x_smi_mdio_write,
+};
+
+/* TODO: SPI/I2C */
+
+/******** edata ********/
+
+static int yt921x_edata_out(struct yt921x_priv *priv, u32 *valp)
+{
+	u32 val;
+	int res;
+
+	res = yt921x_smi_read(priv, YT921X_EDATA_DATA, &val);
+	if (res)
+		return res;
+	if ((val & YT921X_EDATA_DATA_STATUS_M) != YT921X_EDATA_DATA_IDLE) {
+		yt921x_smi_release(priv);
+		res = read_poll_timeout(yt921x_smi_read_burst, res,
+					(val & YT921X_EDATA_DATA_STATUS_M) ==
+					YT921X_EDATA_DATA_IDLE,
+					YT921X_MDIO_SLEEP_US,
+					YT921X_MDIO_TIMEOUT_US,
+					true, priv, YT921X_EDATA_DATA, &val);
+		yt921x_smi_acquire(priv);
+		if (res)
+			return res;
+	}
+
+	*valp = val;
+	return 0;
+}
+
+static int yt921x_edata_wait(struct yt921x_priv *priv)
+{
+	u32 val;
+
+	return yt921x_edata_out(priv, &val);
+}
+
+static int
+yt921x_edata_read_cont(struct yt921x_priv *priv, u8 addr, u8 *valp)
+{
+	u32 ctrl;
+	u32 val;
+	int res;
+
+	ctrl = YT921X_EDATA_CTRL_ADDR(addr) | YT921X_EDATA_CTRL_READ;
+	res = yt921x_smi_write(priv, YT921X_EDATA_CTRL, ctrl);
+	if (res)
+		return res;
+	res = yt921x_edata_out(priv, &val);
+	if (res)
+		return res;
+
+	*valp = FIELD_GET(YT921X_EDATA_DATA_DATA_M, val);
+	return 0;
+}
+
+static int yt921x_edata_read(struct yt921x_priv *priv, u8 addr, u8 *valp)
+{
+	int res;
+
+	res = yt921x_edata_wait(priv);
+	if (res)
+		return res;
+	return yt921x_edata_read_cont(priv, addr, valp);
+}
+
+/******** internal interface mdio ********/
+
+static int yt921x_intif_wait(struct yt921x_priv *priv)
+{
+	u32 val;
+	int res;
+
+	res = yt921x_smi_read(priv, YT921X_INT_MBUS_OP, &val);
+	if (res)
+		return res;
+	if ((val & YT921X_MBUS_OP_START) != 0) {
+		yt921x_smi_release(priv);
+		res = read_poll_timeout(yt921x_smi_read_burst, res,
+					(val & YT921X_MBUS_OP_START) == 0,
+					YT921X_MDIO_SLEEP_US,
+					YT921X_MDIO_TIMEOUT_US,
+					true, priv, YT921X_INT_MBUS_OP, &val);
+		yt921x_smi_acquire(priv);
+		if (res)
+			return res;
+	}
+
+	return 0;
+}
+
+static int
+yt921x_intif_read(struct yt921x_priv *priv, int port, int reg, u16 *valp)
+{
+	struct device *dev = to_device(priv);
+	u32 mask;
+	u32 ctrl;
+	u32 val;
+	int res;
+
+	res = yt921x_intif_wait(priv);
+	if (res)
+		return res;
+
+	mask = YT921X_MBUS_CTRL_PORT_M | YT921X_MBUS_CTRL_REG_M |
+	       YT921X_MBUS_CTRL_OP_M;
+	ctrl = YT921X_MBUS_CTRL_PORT(port) | YT921X_MBUS_CTRL_REG(reg) |
+	       YT921X_MBUS_CTRL_READ;
+	res = yt921x_smi_update_bits(priv, YT921X_INT_MBUS_CTRL, mask, ctrl);
+	if (res)
+		return res;
+	res = yt921x_smi_write(priv, YT921X_INT_MBUS_OP, YT921X_MBUS_OP_START);
+	if (res)
+		return res;
+
+	res = yt921x_intif_wait(priv);
+	if (res)
+		return res;
+	res = yt921x_smi_read(priv, YT921X_INT_MBUS_DIN, &val);
+	if (res)
+		return res;
+
+	if ((u16)val != val)
+		dev_err(dev,
+			"%s: port %d, reg 0x%x: Expected u16, got 0x%08x\n",
+			__func__, port, reg, val);
+	*valp = (u16)val;
+	return 0;
+}
+
+static int
+yt921x_intif_write(struct yt921x_priv *priv, int port, int reg, u16 val)
+{
+	u32 mask;
+	u32 ctrl;
+	int res;
+
+	res = yt921x_intif_wait(priv);
+	if (res)
+		return res;
+
+	mask = YT921X_MBUS_CTRL_PORT_M | YT921X_MBUS_CTRL_REG_M |
+	       YT921X_MBUS_CTRL_OP_M;
+	ctrl = YT921X_MBUS_CTRL_PORT(port) | YT921X_MBUS_CTRL_REG(reg) |
+	       YT921X_MBUS_CTRL_WRITE;
+	res = yt921x_smi_update_bits(priv, YT921X_INT_MBUS_CTRL, mask, ctrl);
+	if (res)
+		return res;
+	res = yt921x_smi_write(priv, YT921X_INT_MBUS_DOUT, val);
+	if (res)
+		return res;
+	res = yt921x_smi_write(priv, YT921X_INT_MBUS_OP, YT921X_MBUS_OP_START);
+	if (res)
+		return res;
+
+	return yt921x_intif_wait(priv);
+}
+
+static int yt921x_mbus_int_read(struct mii_bus *mbus, int port, int reg)
+{
+	struct yt921x_priv *priv = mbus->priv;
+	u16 val;
+	int res;
+
+	if (port >= YT921X_PORT_NUM)
+		return 0xffff;
+
+	yt921x_smi_acquire(priv);
+	res = yt921x_intif_read(priv, port, reg, &val);
+	yt921x_smi_release(priv);
+
+	if (res)
+		return res;
+	return val;
+}
+
+static int
+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)
+		return 0;
+
+	yt921x_smi_acquire(priv);
+	res = yt921x_intif_write(priv, port, reg, data);
+	yt921x_smi_release(priv);
+
+	return res;
+}
+
+static int
+yt921x_mbus_int_init(struct yt921x_priv *priv, struct device_node *mnp)
+{
+	struct device *dev = to_device(priv);
+	struct mii_bus *mbus;
+	int res;
+
+	mbus = devm_mdiobus_alloc(dev);
+	if (!mbus)
+		return -ENOMEM;
+
+	mbus->name = "YT921x internal MDIO bus";
+	snprintf(mbus->id, MII_BUS_ID_SIZE, "%s", dev_name(dev));
+	mbus->priv = priv;
+	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);
+
+	if (!mnp)
+		res = devm_mdiobus_register(dev, mbus);
+	else
+		res = devm_of_mdiobus_register(dev, mbus, mnp);
+	if (res)
+		return res;
+
+	priv->mbus_int = mbus;
+
+	return 0;
+}
+
+/******** external interface mdio ********/
+
+static int yt921x_extif_wait(struct yt921x_priv *priv)
+{
+	u32 val;
+	int res;
+
+	res = yt921x_smi_read(priv, YT921X_EXT_MBUS_OP, &val);
+	if (res)
+		return res;
+	if ((val & YT921X_MBUS_OP_START) != 0) {
+		yt921x_smi_release(priv);
+		res = read_poll_timeout(yt921x_smi_read_burst, res,
+					(val & YT921X_MBUS_OP_START) == 0,
+					YT921X_MDIO_SLEEP_US,
+					YT921X_MDIO_TIMEOUT_US,
+					true, priv, YT921X_EXT_MBUS_OP, &val);
+		yt921x_smi_acquire(priv);
+		if (res)
+			return res;
+	}
+
+	return 0;
+}
+
+static int
+yt921x_extif_read(struct yt921x_priv *priv, int port, int reg, u16 *valp)
+{
+	struct device *dev = to_device(priv);
+	u32 mask;
+	u32 ctrl;
+	u32 val;
+	int res;
+
+	res = yt921x_extif_wait(priv);
+	if (res)
+		return res;
+
+	mask = YT921X_MBUS_CTRL_PORT_M | YT921X_MBUS_CTRL_REG_M |
+	       YT921X_MBUS_CTRL_TYPE_M | YT921X_MBUS_CTRL_OP_M;
+	ctrl = YT921X_MBUS_CTRL_PORT(port) | YT921X_MBUS_CTRL_REG(reg) |
+	       YT921X_MBUS_CTRL_TYPE_C22 | YT921X_MBUS_CTRL_READ;
+	res = yt921x_smi_update_bits(priv, YT921X_EXT_MBUS_CTRL, mask, ctrl);
+	if (res)
+		return res;
+	res = yt921x_smi_write(priv, YT921X_EXT_MBUS_OP, YT921X_MBUS_OP_START);
+	if (res)
+		return res;
+
+	res = yt921x_extif_wait(priv);
+	if (res)
+		return res;
+	res = yt921x_smi_read(priv, YT921X_EXT_MBUS_DIN, &val);
+	if (res)
+		return res;
+
+	if ((u16)val != val)
+		dev_err(dev,
+			"%s: port %d, reg 0x%x: Expected u16, got 0x%08x\n",
+			__func__, port, reg, val);
+	*valp = (u16)val;
+	return 0;
+}
+
+static int
+yt921x_extif_write(struct yt921x_priv *priv, int port, int reg, u16 val)
+{
+	u32 mask;
+	u32 ctrl;
+	int res;
+
+	res = yt921x_extif_wait(priv);
+	if (res)
+		return res;
+
+	mask = YT921X_MBUS_CTRL_PORT_M | YT921X_MBUS_CTRL_REG_M |
+	       YT921X_MBUS_CTRL_TYPE_M | YT921X_MBUS_CTRL_OP_M;
+	ctrl = YT921X_MBUS_CTRL_PORT(port) | YT921X_MBUS_CTRL_REG(reg) |
+	       YT921X_MBUS_CTRL_TYPE_C22 | YT921X_MBUS_CTRL_WRITE;
+	res = yt921x_smi_update_bits(priv, YT921X_EXT_MBUS_CTRL, mask, ctrl);
+	if (res)
+		return res;
+	res = yt921x_smi_write(priv, YT921X_EXT_MBUS_DOUT, val);
+	if (res)
+		return res;
+	res = yt921x_smi_write(priv, YT921X_EXT_MBUS_OP, YT921X_MBUS_OP_START);
+	if (res)
+		return res;
+
+	return yt921x_extif_wait(priv);
+}
+
+static int yt921x_mbus_ext_read(struct mii_bus *mbus, int port, int reg)
+{
+	struct yt921x_priv *priv = mbus->priv;
+	u16 val;
+	int res;
+
+	yt921x_smi_acquire(priv);
+	res = yt921x_extif_read(priv, port, reg, &val);
+	yt921x_smi_release(priv);
+
+	if (res)
+		return res;
+	return val;
+}
+
+static int
+yt921x_mbus_ext_write(struct mii_bus *mbus, int port, int reg, u16 data)
+{
+	struct yt921x_priv *priv = mbus->priv;
+	int res;
+
+	yt921x_smi_acquire(priv);
+	res = yt921x_extif_write(priv, port, reg, data);
+	yt921x_smi_release(priv);
+
+	return res;
+}
+
+static int
+yt921x_mbus_ext_init(struct yt921x_priv *priv, struct device_node *mnp)
+{
+	struct device *dev = to_device(priv);
+	struct mii_bus *mbus;
+	int res;
+
+	mbus = devm_mdiobus_alloc(dev);
+	if (!mbus)
+		return -ENOMEM;
+
+	mbus->name = "YT921x external MDIO bus";
+	snprintf(mbus->id, MII_BUS_ID_SIZE, "%s@ext", dev_name(dev));
+	mbus->priv = priv;
+	/* TODO: c45? */
+	mbus->read = yt921x_mbus_ext_read;
+	mbus->write = yt921x_mbus_ext_write;
+	mbus->parent = dev;
+
+	if (!mnp)
+		res = devm_mdiobus_register(dev, mbus);
+	else
+		res = devm_of_mdiobus_register(dev, mbus, mnp);
+	if (res)
+		return res;
+
+	priv->mbus_ext = mbus;
+
+	return 0;
+}
+
+/******** mib ********/
+
+static int yt921x_mib_read(struct yt921x_priv *priv, int port, void *data)
+{
+	unsigned char *buf = data;
+	int res = 0;
+
+	for (size_t i = 0; i < sizeof(struct yt921x_mib_raw);
+	     i += sizeof(u32)) {
+		res = yt921x_smi_read(priv, YT921X_MIBn_DATA0(port) + i,
+				      (u32 *)&buf[i]);
+		if (res)
+			break;
+	}
+	return res;
+}
+
+static int yt921x_read_mib_burst(struct yt921x_priv *priv, int port)
+{
+	struct yt921x_mib_raw *mib = &priv->ports[port].mib;
+	int res;
+
+	yt921x_smi_acquire(priv);
+	res = yt921x_mib_read(priv, port, mib);
+	yt921x_smi_release(priv);
+
+	return res;
+}
+
+static void
+yt921x_dsa_get_strings(struct dsa_switch *ds, int port, u32 stringset,
+		       uint8_t *data)
+{
+	if (stringset != ETH_SS_STATS)
+		return;
+
+	for (size_t i = 0; i < ARRAY_SIZE(yt921x_mib_descs); i++)
+		ethtool_puts(&data, yt921x_mib_descs[i].name);
+}
+
+static void
+yt921x_dsa_get_ethtool_stats(struct dsa_switch *ds, int port, uint64_t *data)
+{
+	struct yt921x_priv *priv = to_yt921x_priv(ds);
+	struct yt921x_mib_raw *mib = &priv->ports[port].mib;
+	int res;
+
+	res = yt921x_read_mib_burst(priv, port);
+	consume_retval(res);
+
+	for (size_t i = 0; i < ARRAY_SIZE(yt921x_mib_descs); i++) {
+		const struct yt921x_mib_desc *desc = &yt921x_mib_descs[i];
+		u32 *valp = (u32 *)((u8 *)mib + desc->offset);
+
+		data[i] = valp[0];
+		if (desc->size > 1) {
+			data[i] <<= 32;
+			data[i] |= valp[1];
+		}
+	}
+}
+
+static int yt921x_dsa_get_sset_count(struct dsa_switch *ds, int port, int sset)
+{
+	if (sset != ETH_SS_STATS)
+		return 0;
+
+	return ARRAY_SIZE(yt921x_mib_descs);
+}
+
+static void
+yt921x_dsa_get_stats64(struct dsa_switch *ds, int port,
+		       struct rtnl_link_stats64 *s)
+{
+	struct yt921x_priv *priv = to_yt921x_priv(ds);
+	struct yt921x_mib_raw *mib = &priv->ports[port].mib;
+	int res;
+
+	res = yt921x_read_mib_burst(priv, port);
+	consume_retval(res);
+
+	s->rx_length_errors = (u64)mib->rx_undersize_errors +
+			      mib->rx_fragment_errors;
+	s->rx_over_errors = yt921x_mib_raw_u64(mib, rx_over_errors);
+	s->rx_crc_errors = mib->rx_crc_errors;
+	s->rx_frame_errors = mib->rx_frame_errors;
+	/* s->rx_fifo_errors */
+	/* s->rx_missed_errors */
+
+	s->tx_aborted_errors = mib->tx_aborted_errors;
+	/* s->tx_carrier_errors */
+	s->tx_fifo_errors = mib->tx_undersize_errors;
+	/* s->tx_heartbeat_errors */
+	s->tx_window_errors = mib->tx_window_errors;
+
+	s->rx_packets = (u64)mib->rx_64byte + mib->rx_65_127byte +
+			mib->rx_128_255byte + mib->rx_256_511byte +
+			mib->rx_512_1023byte + mib->rx_1024_1518byte +
+			mib->rx_jumbo;
+	s->tx_packets = (u64)mib->tx_64byte + mib->tx_65_127byte +
+			mib->tx_128_255byte + mib->tx_256_511byte +
+			mib->tx_512_1023byte + mib->tx_1024_1518byte +
+			mib->tx_jumbo;
+	s->rx_bytes = yt921x_mib_raw_u64(mib, rx_good_bytes) -
+		      ETH_FCS_LEN * s->rx_packets;
+	s->tx_bytes = yt921x_mib_raw_u64(mib, tx_good_bytes) -
+		      ETH_FCS_LEN * s->tx_packets;
+	s->rx_errors = (u64)mib->rx_crc_errors + s->rx_length_errors +
+		       s->rx_over_errors;
+	s->tx_errors = (u64)mib->tx_undersize_errors + mib->tx_aborted_errors +
+		       mib->tx_window_errors;
+	s->rx_dropped = mib->rx_dropped;
+	/* s->tx_dropped */
+	s->multicast = mib->rx_multicast;
+	s->collisions = yt921x_mib_raw_u64(mib, tx_collisions);
+}
+
+static void
+yt921x_dsa_get_pause_stats(struct dsa_switch *ds, int port,
+			   struct ethtool_pause_stats *pause_stats)
+{
+	struct yt921x_priv *priv = to_yt921x_priv(ds);
+	u32 tx_pause = 0;
+	u32 rx_pause = 0;
+	int res;
+
+	yt921x_smi_acquire(priv);
+	do {
+		res = yt921x_smi_read(priv, YT921X_MIBn_name(port, tx_pause),
+				      &tx_pause);
+		if (res)
+			break;
+		res = yt921x_smi_read(priv, YT921X_MIBn_name(port, rx_pause),
+				      &rx_pause);
+	} while (0);
+	yt921x_smi_release(priv);
+
+	consume_retval(res);
+
+	pause_stats->tx_pause_frames = tx_pause;
+	pause_stats->rx_pause_frames = rx_pause;
+}
+
+/******** eee ********/
+
+static int
+yt921x_set_eee(struct yt921x_priv *priv, int port, struct ethtool_keee *e)
+{
+	struct device *dev = to_device(priv);
+	bool enable = e->eee_enabled;
+	u16 new_mask;
+	int res;
+
+	dev_dbg(dev, "%s: port %d, enable %d\n", __func__, port, enable);
+
+	/* Enable / disable global EEE */
+	new_mask = priv->eee_ports_mask;
+	new_mask &= ~BIT(port);
+	new_mask |= !enable ? 0 : BIT(port);
+
+	if (!!new_mask != !!priv->eee_ports_mask) {
+		dev_dbg(dev, "%s: toggle %d\n", __func__, !!new_mask);
+
+		res = yt921x_smi_toggle_bits(priv, YT921X_PON_STRAP_FUNC,
+					     YT921X_PON_STRAP_EEE, !!new_mask);
+		if (res)
+			return res;
+		res = yt921x_smi_toggle_bits(priv, YT921X_PON_STRAP_VAL,
+					     YT921X_PON_STRAP_EEE, !!new_mask);
+		if (res)
+			return res;
+	}
+
+	priv->eee_ports_mask = new_mask;
+
+	/* Enable / disable port EEE */
+	res = yt921x_smi_toggle_bits(priv, YT921X_EEE_CTRL,
+				     YT921X_EEE_CTRL_ENn(port), enable);
+	if (res)
+		return res;
+	res = yt921x_smi_toggle_bits(priv, YT921X_EEEn_VAL(port),
+				     YT921X_EEE_VAL_DATA, enable);
+	if (res)
+		return res;
+
+	return 0;
+}
+
+static bool yt921x_dsa_support_eee(struct dsa_switch *ds, int port)
+{
+	struct yt921x_priv *priv = to_yt921x_priv(ds);
+
+	return (priv->pon_strap_cap & YT921X_PON_STRAP_EEE) != 0;
+}
+
+static int
+yt921x_dsa_set_mac_eee(struct dsa_switch *ds, int port, struct ethtool_keee *e)
+{
+	struct yt921x_priv *priv = to_yt921x_priv(ds);
+	int res;
+
+	yt921x_smi_acquire(priv);
+	res = yt921x_set_eee(priv, port, e);
+	yt921x_smi_release(priv);
+
+	return res;
+}
+
+/******** mtu ********/
+
+static int
+yt921x_dsa_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
+{
+	/* Only serves as packet filter, since the frame size is always set to
+	 * maximum after reset
+	 */
+
+	struct yt921x_priv *priv = to_yt921x_priv(ds);
+	struct dsa_port *dp = dsa_to_port(ds, port);
+	struct device *dev = to_device(priv);
+	int frame_size;
+
+	frame_size = new_mtu + ETH_HLEN + ETH_FCS_LEN;
+	if (dsa_port_is_cpu(dp))
+		frame_size += YT921X_TAG_LEN;
+
+	dev_dbg(dev, "%s: port %d, mtu %d, frame size %d\n", __func__,
+		port, new_mtu, frame_size);
+
+	return yt921x_smi_update_bits_burst(priv, YT921X_MACn_FRAME(port),
+					    YT921X_MAC_FRAME_SIZE_M,
+					    YT921X_MAC_FRAME_SIZE(frame_size));
+}
+
+static int yt921x_dsa_port_max_mtu(struct dsa_switch *ds, int port)
+{
+	/* Don't want to brother dsa_port_is_cpu() here, so use a fixed value */
+	return YT921X_FRAME_SIZE_MAX - ETH_HLEN - ETH_FCS_LEN - YT921X_TAG_LEN;
+}
+
+/******** mirror ********/
+
+static void
+yt921x_dsa_port_mirror_del(struct dsa_switch *ds, int port,
+			   struct dsa_mall_mirror_tc_entry *mirror)
+{
+	struct yt921x_priv *priv = to_yt921x_priv(ds);
+	struct device *dev = to_device(priv);
+	u32 mask;
+	int res;
+
+	dev_dbg(dev, "%s: port %d, ingress %d\n", __func__, port,
+		mirror->ingress);
+
+	yt921x_smi_acquire(priv);
+	if (mirror->ingress)
+		mask = YT921X_MIRROR_IGR_PORTn(port);
+	else
+		mask = YT921X_MIRROR_EGR_PORTn(port);
+	res = yt921x_smi_clear_bits(priv, YT921X_MIRROR, mask);
+	yt921x_smi_release(priv);
+
+	consume_retval(res);
+}
+
+static int
+yt921x_dsa_port_mirror_add(struct dsa_switch *ds, int port,
+			   struct dsa_mall_mirror_tc_entry *mirror,
+			   bool ingress, struct netlink_ext_ack *extack)
+{
+	struct yt921x_priv *priv = to_yt921x_priv(ds);
+	struct device *dev = to_device(priv);
+	u32 ctrl;
+	u32 val;
+	int res;
+
+	dev_dbg(dev, "%s: port %d, ingress %d\n", __func__, port, ingress);
+
+	yt921x_smi_acquire(priv);
+	do {
+		u32 srcs;
+
+		res = yt921x_smi_read(priv, YT921X_MIRROR, &val);
+		if (res)
+			break;
+
+		if (ingress)
+			srcs = YT921X_MIRROR_IGR_PORTn(port);
+		else
+			srcs = YT921X_MIRROR_EGR_PORTn(port);
+
+		ctrl = val;
+		ctrl |= srcs;
+		ctrl &= ~YT921X_MIRROR_PORT_M;
+		ctrl |= YT921X_MIRROR_PORT(mirror->to_local_port);
+		if (ctrl == val)
+			break;
+
+		/* other mirror tasks & different dst port -> conflict */
+		if ((val & ~srcs & (YT921X_MIRROR_EGR_PORTS_M |
+				    YT921X_MIRROR_IGR_PORTS_M)) != 0 &&
+		    ((ctrl ^ val) & YT921X_MIRROR_PORT_M) != 0) {
+			res = -EEXIST;
+			break;
+		}
+
+		res = yt921x_smi_write(priv, YT921X_MIRROR, ctrl);
+	} while (0);
+	yt921x_smi_release(priv);
+
+	return res;
+}
+
+/******** vlan ********/
+
+static int
+yt921x_vlan_filtering(struct yt921x_priv *priv, int port, bool vlan_filtering)
+{
+	struct yt921x_port *pp = &priv->ports[port];
+	u32 mask;
+	u32 ctrl;
+	int res;
+
+	res = yt921x_smi_toggle_bits(priv, YT921X_VLAN_IGR_FILTER,
+				     YT921X_VLAN_IGR_FILTER_PORTn_EN(port),
+				     vlan_filtering);
+	if (res)
+		return res;
+
+	mask = YT921X_PORT_VLAN_CTRL1_CVLAN_DROP_TAGGED |
+	       YT921X_PORT_VLAN_CTRL1_CVLAN_DROP_UNTAGGED;
+	ctrl = 0;
+	if (vlan_filtering) {
+		if (!pp->pvid)
+			ctrl |= YT921X_PORT_VLAN_CTRL1_CVLAN_DROP_UNTAGGED;
+		if (!pp->vids_cnt)
+			ctrl |= YT921X_PORT_VLAN_CTRL1_CVLAN_DROP_TAGGED;
+	}
+	res = yt921x_smi_update_bits(priv, YT921X_PORTn_VLAN_CTRL1(port),
+				     mask, ctrl);
+	if (res)
+		return res;
+
+	pp->vlan_filtering = vlan_filtering;
+	return 0;
+}
+
+static int
+yt921x_vid_del(struct yt921x_priv *priv, int port, u16 vid)
+{
+	struct yt921x_port *pp = &priv->ports[port];
+	u32 mask;
+	u32 ctrl;
+	u32 val;
+	int res;
+
+	res = yt921x_smi_read(priv, YT921X_VLANn_CTRL(port), &val);
+	if (res)
+		return res;
+	ctrl = val & ~YT921X_VLAN_CTRL_PORTn(port);
+	if (ctrl == val)
+		return 0;
+	res = yt921x_smi_write(priv, YT921X_VLANn_CTRL(port), ctrl);
+	if (res)
+		return res;
+
+	mask = YT921X_VLAN_CTRL1_UNTAG_PORTn(port);
+	res = yt921x_smi_clear_bits(priv, YT921X_VLANn_CTRL1(port), mask);
+	if (res)
+		return res;
+
+	if (pp->vlan_filtering && pp->vids_cnt <= 1) {
+		mask = YT921X_PORT_VLAN_CTRL1_CVLAN_DROP_TAGGED;
+		res = yt921x_smi_set_bits(priv, YT921X_PORTn_VLAN_CTRL1(port),
+					  mask);
+		if (res)
+			return res;
+	}
+
+	if (pp->vids_cnt <= 0)
+		should_unreachable();
+	else
+		pp->vids_cnt--;
+	return 0;
+}
+
+/* Seems port_vlan_add() is not state transition method... */
+static int
+yt921x_vid_set(struct yt921x_priv *priv, int port, u16 vid, bool untagged)
+{
+	struct yt921x_port *pp = &priv->ports[port];
+	bool already_added;
+	u32 mask;
+	u32 ctrl;
+	u32 val;
+	int res;
+
+	res = yt921x_smi_read(priv, YT921X_VLANn_CTRL(port), &val);
+	if (res)
+		return res;
+	ctrl = val | YT921X_VLAN_CTRL_PORTn(port);
+	if (ctrl == val) {
+		already_added = true;
+	} else {
+		already_added = false;
+		res = yt921x_smi_write(priv, YT921X_VLANn_CTRL(port), ctrl);
+		if (res)
+			return res;
+	}
+
+	mask = YT921X_VLAN_CTRL1_UNTAG_PORTn(port);
+	res = yt921x_smi_toggle_bits(priv, YT921X_VLANn_CTRL1(port), mask,
+				     untagged);
+	if (res)
+		return res;
+
+	mask = YT921X_PORT_VLAN_CTRL1_CVLAN_DROP_TAGGED;
+	res = yt921x_smi_clear_bits(priv, YT921X_PORTn_VLAN_CTRL1(port), mask);
+	if (res)
+		return res;
+
+	if (!already_added) {
+		if (pp->vids_cnt >= VLAN_N_VID - 1)
+			should_unreachable();
+		else
+			pp->vids_cnt++;
+	}
+	return 0;
+}
+
+static int
+yt921x_pvid_clear(struct yt921x_priv *priv, int port)
+{
+	struct yt921x_port *pp = &priv->ports[port];
+	u32 mask;
+	u32 ctrl;
+	int res;
+
+	mask = YT921X_PORT_VLAN_CTRL_CVID_M;
+	ctrl = YT921X_PORT_VLAN_CTRL_CVID(YT921X_PVID_DEFAULT);
+	res = yt921x_smi_update_bits(priv, YT921X_PORTn_VLAN_CTRL(port),
+				     mask, ctrl);
+	if (res)
+		return res;
+
+	if (pp->vlan_filtering) {
+		mask = YT921X_PORT_VLAN_CTRL1_CVLAN_DROP_UNTAGGED;
+		res = yt921x_smi_set_bits(priv, YT921X_PORTn_VLAN_CTRL1(port),
+					  mask);
+		if (res)
+			return res;
+	}
+
+	pp->pvid = 0;
+	return 0;
+}
+
+static int
+yt921x_pvid_set(struct yt921x_priv *priv, int port, u16 vid)
+{
+	struct yt921x_port *pp = &priv->ports[port];
+	u32 mask;
+	u32 ctrl;
+	int res;
+
+	mask = YT921X_PORT_VLAN_CTRL_CVID_M;
+	ctrl = YT921X_PORT_VLAN_CTRL_CVID(vid);
+	res = yt921x_smi_update_bits(priv, YT921X_PORTn_VLAN_CTRL(port),
+				     mask, ctrl);
+	if (res)
+		return res;
+
+	mask = YT921X_PORT_VLAN_CTRL1_CVLAN_DROP_UNTAGGED;
+	res = yt921x_smi_clear_bits(priv, YT921X_PORTn_VLAN_CTRL1(port), mask);
+	if (res)
+		return res;
+
+	pp->pvid = vid;
+	return 0;
+}
+
+static int
+yt921x_dsa_port_vlan_filtering(struct dsa_switch *ds, int port,
+			       bool vlan_filtering,
+			       struct netlink_ext_ack *extack)
+{
+	struct yt921x_priv *priv = to_yt921x_priv(ds);
+	struct device *dev = to_device(priv);
+	int res;
+
+	dev_dbg(dev, "%s: port %d, enable %d\n", __func__, port,
+		vlan_filtering);
+
+	yt921x_smi_acquire(priv);
+	res = yt921x_vlan_filtering(priv, port, vlan_filtering);
+	yt921x_smi_release(priv);
+
+	return res;
+}
+
+static int
+yt921x_dsa_port_vlan_del(struct dsa_switch *ds, int port,
+			 const struct switchdev_obj_port_vlan *vlan)
+{
+	struct yt921x_priv *priv = to_yt921x_priv(ds);
+	struct yt921x_port *pp = &priv->ports[port];
+	struct device *dev = to_device(priv);
+	u16 vid = vlan->vid;
+	int res;
+
+	dev_dbg(dev, "%s: port %d, vid %d\n", __func__, port, vid);
+
+	yt921x_smi_acquire(priv);
+	do {
+		res = yt921x_vid_del(priv, port, vid);
+		if (res)
+			break;
+
+		if (pp->pvid == vid) {
+			res = yt921x_pvid_clear(priv, port);
+			if (res)
+				break;
+		}
+	} while (0);
+	yt921x_smi_release(priv);
+
+	return res;
+}
+
+static int
+yt921x_dsa_port_vlan_add(struct dsa_switch *ds, int port,
+			 const struct switchdev_obj_port_vlan *vlan,
+			 struct netlink_ext_ack *extack)
+{
+	struct yt921x_priv *priv = to_yt921x_priv(ds);
+	struct yt921x_port *pp = &priv->ports[port];
+	struct device *dev = to_device(priv);
+	u16 vid = vlan->vid;
+	int res;
+
+	dev_dbg(dev, "%s: port %d, vid %d, flags 0x%x\n", __func__, port, vid,
+		vlan->flags);
+
+	yt921x_smi_acquire(priv);
+	do {
+		res = yt921x_vid_set(priv, port, vid,
+				     (vlan->flags &
+				      BRIDGE_VLAN_INFO_UNTAGGED) != 0);
+		if (res)
+			break;
+
+		if ((vlan->flags & BRIDGE_VLAN_INFO_PVID) != 0) {
+			res = yt921x_pvid_set(priv, port, vid);
+			if (res)
+				break;
+		} else if (pp->pvid == vid) {
+			res = yt921x_pvid_clear(priv, port);
+			if (res)
+				break;
+		}
+	} while (0);
+	yt921x_smi_release(priv);
+
+	return res;
+}
+
+/******** bridge ********/
+
+/* It's caller's responsibility to decide whether ports_mask contains cpu
+ * ports
+ */
+static int yt921x_bridge(struct yt921x_priv *priv, u16 ports_mask)
+{
+	u16 targets_mask = ports_mask & ~priv->cpu_ports_mask;
+	u32 isolated_mask;
+	u32 ctrl;
+	int res;
+
+	isolated_mask = 0;
+	for (u16 pm = targets_mask; pm ; ) {
+		struct yt921x_port *pp;
+		int port = __fls(pm);
+
+		pm &= ~BIT(port);
+		pp = &priv->ports[port];
+
+		if (pp->isolated)
+			isolated_mask |= BIT(port);
+	}
+
+	/* Block from non-cpu bridge ports ... */
+	for (u16 pm = targets_mask; pm ; ) {
+		struct yt921x_port *pp;
+		int port = __fls(pm);
+
+		pm &= ~BIT(port);
+		pp = &priv->ports[port];
+
+		/* to non-bridge ports */
+		ctrl = ~ports_mask;
+		/* to isolated ports when isolated */
+		if (pp->isolated)
+			ctrl |= isolated_mask;
+		/* to itself when non-hairpin */
+		if (!pp->hairpin)
+			ctrl |= BIT(port);
+		else
+			ctrl &= ~BIT(port);
+
+		res = yt921x_smi_write(priv, YT921X_PORTn_ISOLATION(port),
+				       ctrl);
+		if (res)
+			return res;
+	}
+
+	for (u16 pm = targets_mask; pm ; ) {
+		struct yt921x_port *pp;
+		int port = __fls(pm);
+
+		pm &= ~BIT(port);
+		pp = &priv->ports[port];
+
+		pp->bridge_mask = ports_mask;
+	}
+
+	return 0;
+}
+
+static int yt921x_bridge_force(struct yt921x_priv *priv, u16 ports_mask)
+{
+	u16 targets_mask = ~ports_mask & (BIT(YT921X_PORT_NUM) - 1) &
+			   ~priv->cpu_ports_mask;
+	u32 mask;
+	int res;
+
+	res = yt921x_bridge(priv, ports_mask);
+	if (res)
+		return res;
+
+	/* Block ... to non-cpu bridge ports */
+	mask = ports_mask & ~priv->cpu_ports_mask;
+	/* from non-cpu non-bridge ports */
+	for (u16 pm = targets_mask; pm ; ) {
+		int port = __fls(pm);
+
+		pm &= ~BIT(port);
+		res = yt921x_smi_set_bits(priv, YT921X_PORTn_ISOLATION(port),
+					  mask);
+		if (res)
+			return res;
+	}
+
+	for (u16 pm = targets_mask; pm ; ) {
+		struct yt921x_port *pp;
+		int port = __fls(pm);
+
+		pm &= ~BIT(port);
+		pp = &priv->ports[port];
+
+		pp->bridge_mask &= ~mask;
+	}
+
+	return 0;
+}
+
+static int
+yt921x_bridge_flags(struct yt921x_priv *priv, int port,
+		    struct switchdev_brport_flags flags)
+{
+	struct yt921x_port *pp = &priv->ports[port];
+	bool do_flush;
+	u32 mask;
+	u32 ctrl;
+	int res;
+
+	if ((flags.mask & BR_LEARNING) != 0) {
+		mask = YT921X_PORT_LEARN_DIS;
+		ctrl = (flags.val & BR_LEARNING) == 0 ? YT921X_PORT_LEARN_DIS :
+		       0;
+		res = yt921x_smi_update_bits(priv, YT921X_PORTn_LEARN(port),
+					     mask, ctrl);
+		if (res)
+			return res;
+	}
+
+	if ((flags.mask & BR_FLOOD) != 0) {
+		mask = YT921X_ACT_UNK_ACTn_M(port);
+		ctrl = (flags.val & BR_FLOOD) == 0 ?
+		       YT921X_ACT_UNK_ACTn_DROP(port) :
+		       YT921X_ACT_UNK_ACTn_FORWARD(port);
+		res = yt921x_smi_update_bits(priv, YT921X_ACT_UNK_UCAST,
+					     mask, ctrl);
+		if (res)
+			return res;
+	}
+
+	if ((flags.mask & BR_MCAST_FLOOD) != 0) {
+		mask = YT921X_FILTER_PORTn(port);
+		res = yt921x_smi_toggle_bits(priv, YT921X_FILTER_MCAST,
+					     mask,
+					     (flags.val & BR_MCAST_FLOOD) != 0);
+		if (res)
+			return res;
+	}
+
+	if ((flags.mask & BR_BCAST_FLOOD) != 0) {
+		mask = YT921X_FILTER_PORTn(port);
+		res = yt921x_smi_toggle_bits(priv, YT921X_FILTER_BCAST,
+					     mask,
+					     (flags.val & BR_BCAST_FLOOD) != 0);
+		if (res)
+			return res;
+	}
+
+	do_flush = false;
+	if ((flags.mask & BR_HAIRPIN_MODE) != 0) {
+		pp->hairpin = (flags.val & BR_HAIRPIN_MODE) != 0;
+		do_flush = true;
+	}
+	if ((flags.mask & BR_ISOLATED) != 0) {
+		pp->isolated = (flags.val & BR_ISOLATED) != 0;
+		do_flush = true;
+	}
+	if (do_flush) {
+		res = yt921x_bridge(priv, pp->bridge_mask);
+		if (res)
+			return res;
+	}
+
+	return 0;
+}
+
+static int
+yt921x_dsa_port_pre_bridge_flags(struct dsa_switch *ds, int port,
+				 struct switchdev_brport_flags flags,
+				 struct netlink_ext_ack *extack)
+{
+	if ((flags.mask & ~(BR_HAIRPIN_MODE | BR_LEARNING | BR_FLOOD |
+			    BR_MCAST_FLOOD | BR_BCAST_FLOOD |
+			    BR_ISOLATED)) != 0)
+		return -EINVAL;
+	return 0;
+}
+
+static int
+yt921x_dsa_port_bridge_flags(struct dsa_switch *ds, int port,
+			     struct switchdev_brport_flags flags,
+			     struct netlink_ext_ack *extack)
+{
+	struct yt921x_priv *priv = to_yt921x_priv(ds);
+	struct device *dev = to_device(priv);
+	int res;
+
+	dev_dbg(dev, "%s: port %d, mask 0x%lx, flags 0x%lx\n", __func__, port,
+		flags.mask, flags.val);
+
+	yt921x_smi_acquire(priv);
+	res = yt921x_bridge_flags(priv, port, flags);
+	yt921x_smi_release(priv);
+
+	return res;
+}
+
+static u32
+dsa_bridge_ports(struct dsa_switch *ds, const struct net_device *bridge_dev)
+{
+	struct dsa_port *dp;
+	u32 mask = 0;
+
+	dsa_switch_for_each_user_port(dp, ds)
+		if (dsa_port_offloads_bridge_dev(dp, bridge_dev))
+			mask |= BIT(dp->index);
+
+	return mask;
+}
+
+static void
+yt921x_dsa_port_bridge_leave(struct dsa_switch *ds, int port,
+			     struct dsa_bridge bridge)
+{
+	struct yt921x_priv *priv = to_yt921x_priv(ds);
+	struct device *dev = to_device(priv);
+	u16 ports_mask;
+	int res;
+
+	ports_mask = dsa_bridge_ports(ds, bridge.dev);
+
+	dev_dbg(dev, "%s: port %d, mask 0x%x\n", __func__, port, ports_mask);
+
+	ports_mask |= priv->cpu_ports_mask;
+
+	yt921x_smi_acquire(priv);
+	res = yt921x_bridge_force(priv, ports_mask);
+	yt921x_smi_release(priv);
+
+	consume_retval(res);
+}
+
+static int
+yt921x_dsa_port_bridge_join(struct dsa_switch *ds, int port,
+			    struct dsa_bridge bridge, bool *tx_fwd_offload,
+			    struct netlink_ext_ack *extack)
+{
+	struct yt921x_priv *priv = to_yt921x_priv(ds);
+	struct device *dev = to_device(priv);
+	u16 ports_mask;
+	int res;
+
+	ports_mask = dsa_bridge_ports(ds, bridge.dev);
+
+	dev_dbg(dev, "%s: port %d, mask 0x%x\n", __func__, port, ports_mask);
+
+	ports_mask |= priv->cpu_ports_mask;
+
+	yt921x_smi_acquire(priv);
+	res = yt921x_bridge(priv, ports_mask);
+	yt921x_smi_release(priv);
+
+	return res;
+}
+
+/******** fdb ********/
+
+static int yt921x_fdb_wait(struct yt921x_priv *priv, u32 *valp)
+{
+	struct device *dev = to_device(priv);
+	u32 val;
+	int res;
+
+	res = yt921x_smi_read(priv, YT921X_FDB_RESULT, &val);
+	if (res)
+		return res;
+	if ((val & YT921X_FDB_RESULT_DONE) == 0) {
+		yt921x_smi_release(priv);
+		res = read_poll_timeout(yt921x_smi_read_burst, res,
+					(val & YT921X_FDB_RESULT_DONE) != 0,
+					YT921X_MDIO_SLEEP_US,
+					YT921X_MDIO_TIMEOUT_US,
+					true, priv, YT921X_FDB_RESULT,
+					&val);
+		yt921x_smi_acquire(priv);
+		if (res) {
+			dev_warn(dev, "Probably an FDB hang happened\n");
+			return res;
+		}
+	}
+
+	*valp = val;
+	return 0;
+}
+
+static int
+yt921x_fdb_in01(struct yt921x_priv *priv, const unsigned char *addr,
+		u16 vid, u32 ctrl1)
+{
+	u32 ctrl;
+	int res;
+
+	ctrl = (addr[0] << 24) | (addr[1] << 16) | (addr[2] << 8) | addr[3];
+	res = yt921x_smi_write(priv, YT921X_FDB_IN0, ctrl);
+	if (res)
+		return res;
+
+	ctrl = ctrl1 | YT921X_FDB_IO1_FID(vid) | (addr[4] << 8) | addr[5];
+	return yt921x_smi_write(priv, YT921X_FDB_IN1, ctrl);
+}
+
+static int
+yt921x_fdb_has(struct yt921x_priv *priv, const unsigned char *addr, u16 vid,
+	       u16 *indexp)
+{
+	u32 ctrl;
+	u32 val;
+	int res;
+
+	res = yt921x_fdb_in01(priv, addr, vid, 0);
+	if (res)
+		return res;
+
+	ctrl = 0;
+	res = yt921x_smi_write(priv, YT921X_FDB_IN2, ctrl);
+	if (res)
+		return res;
+
+	ctrl = YT921X_FDB_OP_OP_GET_ONE | YT921X_FDB_OP_START;
+	res = yt921x_smi_write(priv, YT921X_FDB_OP, ctrl);
+	if (res)
+		return res;
+
+	res = yt921x_fdb_wait(priv, &val);
+	if (res)
+		return res;
+	if ((val & YT921X_FDB_RESULT_NOTFOUND) == 0) {
+		*indexp = YT921X_FDB_NUM;
+		return 0;
+	}
+
+	*indexp = FIELD_GET(YT921X_FDB_RESULT_INDEX_M, val);
+	return 0;
+}
+
+static int
+yt921x_fdb_read(struct yt921x_priv *priv, unsigned char *addr, u16 *vidp,
+		u16 *ports_maskp, u16 *indexp, u8 *statusp)
+{
+	struct device *dev = to_device(priv);
+	u16 index;
+	u32 data0;
+	u32 data1;
+	u32 data2;
+	u32 val;
+	int res;
+
+	res = yt921x_fdb_wait(priv, &val);
+	if (res)
+		return res;
+	if ((val & YT921X_FDB_RESULT_NOTFOUND) != 0) {
+		*ports_maskp = 0;
+		return 0;
+	}
+	index = FIELD_GET(YT921X_FDB_RESULT_INDEX_M, val);
+
+	res = yt921x_smi_read(priv, YT921X_FDB_OUT1, &data1);
+	if (res)
+		return res;
+	if ((data1 & YT921X_FDB_IO1_STATUS_M) ==
+	    YT921X_FDB_IO1_STATUS_INVALID) {
+		*ports_maskp = 0;
+		return 0;
+	}
+
+	res = yt921x_smi_read(priv, YT921X_FDB_OUT0, &data0);
+	if (res)
+		return res;
+	res = yt921x_smi_read(priv, YT921X_FDB_OUT2, &data2);
+	if (res)
+		return res;
+
+	addr[0] = data0 >> 24;
+	addr[1] = data0 >> 16;
+	addr[2] = data0 >> 8;
+	addr[3] = data0;
+	addr[4] = data1 >> 8;
+	addr[5] = data1;
+	*vidp = FIELD_GET(YT921X_FDB_IO1_FID_M, data1);
+	*indexp = index;
+	*ports_maskp = FIELD_GET(YT921X_FDB_IO2_EGR_PORTS_M, data2);
+	*statusp = FIELD_GET(YT921X_FDB_IO1_STATUS_M, data1);
+
+	dev_dbg(dev,
+		"%s: index 0x%x, mac %02x:%02x:%02x:%02x:%02x:%02x, "
+		"vid %d, ports 0x%x, status %d\n",
+		__func__, *indexp, addr[0], addr[1], addr[2], addr[3],
+		addr[4], addr[5], *vidp, *ports_maskp, *statusp);
+	return 0;
+}
+
+static int
+yt921x_fdb_dump(struct yt921x_priv *priv, u16 ports_mask,
+		dsa_fdb_dump_cb_t *cb, void *data)
+{
+	unsigned char addr[ETH_ALEN];
+	u8 status;
+	u16 pmask;
+	u16 index;
+	u32 ctrl;
+	u16 vid;
+	int res;
+
+	ctrl = YT921X_FDB_OP_INDEX(0) | YT921X_FDB_OP_MODE_INDEX |
+	       YT921X_FDB_OP_OP_GET_ONE | YT921X_FDB_OP_START;
+	res = yt921x_smi_write(priv, YT921X_FDB_OP, ctrl);
+	if (res)
+		return res;
+	res = yt921x_fdb_read(priv, addr, &vid, &pmask, &index, &status);
+	if (res)
+		return res;
+	if ((pmask & ports_mask) != 0 && (addr[0] & 1) == 0) {
+		res = cb(addr, vid,
+			 status == YT921X_FDB_ENTRY_STATUS_STATIC, data);
+		if (res)
+			return res;
+	}
+
+	ctrl = YT921X_FDB_IO2_EGR_PORTS(ports_mask);
+	res = yt921x_smi_write(priv, YT921X_FDB_IN2, ctrl);
+	if (res)
+		return res;
+
+	index = 0;
+	do {
+		ctrl = YT921X_FDB_OP_INDEX(index) | YT921X_FDB_OP_MODE_INDEX |
+		       YT921X_FDB_OP_NEXT_TYPE_UCAST_PORT |
+		       YT921X_FDB_OP_OP_GET_NEXT | YT921X_FDB_OP_START;
+		res = yt921x_smi_write(priv, YT921X_FDB_OP, ctrl);
+		if (res)
+			return res;
+
+		res = yt921x_fdb_read(priv, addr, &vid, &pmask, &index,
+				      &status);
+		if (res)
+			return res;
+		if (!pmask)
+			break;
+
+		if ((pmask & ports_mask) != 0 && (addr[0] & 1) == 0) {
+			res = cb(addr, vid,
+				 status == YT921X_FDB_ENTRY_STATUS_STATIC,
+				 data);
+			if (res)
+				return res;
+		}
+
+		/* Never call GET_NEXT with 4095, otherwise it will hang
+		 * forever until a reset!
+		 */
+	} while (index < YT921X_FDB_NUM - 1);
+
+	return 0;
+}
+
+static int
+yt921x_mdb_dump(struct yt921x_priv *priv, u16 ports_mask,
+		dsa_fdb_dump_cb_t *cb, void *data)
+{
+	unsigned char addr[ETH_ALEN];
+	u8 status;
+	u16 pmask;
+	u16 index;
+	u32 ctrl;
+	u16 vid;
+	int res;
+
+	ctrl = YT921X_FDB_OP_INDEX(0) | YT921X_FDB_OP_MODE_INDEX |
+	       YT921X_FDB_OP_OP_GET_ONE | YT921X_FDB_OP_START;
+	res = yt921x_smi_write(priv, YT921X_FDB_OP, ctrl);
+	if (res)
+		return res;
+	res = yt921x_fdb_read(priv, addr, &vid, &pmask, &index, &status);
+	if (res)
+		return res;
+	if ((pmask & ports_mask) != 0 && (addr[0] & 1) != 0) {
+		res = cb(addr, vid,
+			 status == YT921X_FDB_ENTRY_STATUS_STATIC, data);
+		if (res)
+			return res;
+	}
+
+	index = 0;
+	do {
+		ctrl = YT921X_FDB_OP_INDEX(index) | YT921X_FDB_OP_MODE_INDEX |
+		       YT921X_FDB_OP_NEXT_TYPE_MCAST |
+		       YT921X_FDB_OP_OP_GET_NEXT | YT921X_FDB_OP_START;
+		res = yt921x_smi_write(priv, YT921X_FDB_OP, ctrl);
+		if (res)
+			return res;
+
+		res = yt921x_fdb_read(priv, addr, &vid, &pmask, &index,
+				      &status);
+		if (res)
+			return res;
+		if (!pmask)
+			break;
+
+		if ((pmask & ports_mask) != 0 && (addr[0] & 1) != 0) {
+			res = cb(addr, vid,
+				 status == YT921X_FDB_ENTRY_STATUS_STATIC,
+				 data);
+			if (res)
+				return res;
+		}
+
+		/* Never call GET_NEXT with 4095, otherwise it will hang
+		 * forever until a reset!
+		 */
+	} while (index < YT921X_FDB_NUM - 1);
+
+	return 0;
+}
+
+static int
+yt921x_fdb_flush(struct yt921x_priv *priv, u16 ports_mask, u16 vid,
+		 bool flush_static)
+{
+	u32 ctrl;
+	u32 val;
+	int res;
+
+	if (vid) {
+		ctrl = YT921X_FDB_IO1_FID(vid);
+		res = yt921x_smi_write(priv, YT921X_FDB_IN1, ctrl);
+		if (res)
+			return res;
+	}
+
+	ctrl = YT921X_FDB_IO2_EGR_PORTS(ports_mask);
+	res = yt921x_smi_write(priv, YT921X_FDB_IN2, ctrl);
+	if (res)
+		return res;
+
+	ctrl = YT921X_FDB_OP_OP_FLUSH | YT921X_FDB_OP_START;
+	if (vid)
+		ctrl |= YT921X_FDB_OP_FLUSH_PORT;
+	else
+		ctrl |= YT921X_FDB_OP_FLUSH_PORT_VID;
+	if (flush_static)
+		ctrl |= YT921X_FDB_OP_FLUSH_STATIC;
+	res = yt921x_smi_write(priv, YT921X_FDB_OP, ctrl);
+	if (res)
+		return res;
+
+	res = yt921x_fdb_wait(priv, &val);
+	if (res)
+		return res;
+
+	return 0;
+}
+
+static int yt921x_fdb_del_index(struct yt921x_priv *priv, u16 index)
+{
+	u32 ctrl;
+	u32 val;
+	int res;
+
+	ctrl = YT921X_FDB_OP_INDEX(index) | YT921X_FDB_OP_MODE_INDEX |
+	       YT921X_FDB_OP_OP_DEL | YT921X_FDB_OP_START;
+	res = yt921x_smi_write(priv, YT921X_FDB_OP, ctrl);
+	if (res)
+		return res;
+
+	res = yt921x_fdb_wait(priv, &val);
+	if (res)
+		return res;
+
+	return 0;
+}
+
+static int yt921x_fdb_add_index(struct yt921x_priv *priv, u16 index)
+{
+	u32 ctrl;
+	u32 val;
+	int res;
+
+	ctrl = YT921X_FDB_OP_INDEX(index) | YT921X_FDB_OP_MODE_INDEX |
+	       YT921X_FDB_OP_OP_ADD | YT921X_FDB_OP_START;
+	res = yt921x_smi_write(priv, YT921X_FDB_OP, ctrl);
+	if (res)
+		return res;
+
+	res = yt921x_fdb_wait(priv, &val);
+	if (res)
+		return res;
+
+	return 0;
+}
+
+static int
+yt921x_fdb_del(struct yt921x_priv *priv, const unsigned char *addr, u16 vid)
+{
+	u32 ctrl;
+	u32 val;
+	int res;
+
+	ctrl = 0;
+	res = yt921x_fdb_in01(priv, addr, vid, ctrl);
+	if (res)
+		return res;
+
+	ctrl = YT921X_FDB_OP_OP_DEL | YT921X_FDB_OP_START;
+	res = yt921x_smi_write(priv, YT921X_FDB_OP, ctrl);
+	if (res)
+		return res;
+
+	res = yt921x_fdb_wait(priv, &val);
+	if (res)
+		return res;
+
+	return 0;
+}
+
+static int
+yt921x_fdb_add(struct yt921x_priv *priv, const unsigned char *addr, u16 vid,
+	       u16 ports_mask)
+{
+	u32 ctrl;
+	u32 val;
+	int res;
+
+	ctrl = YT921X_FDB_IO1_STATUS_STATIC;
+	res = yt921x_fdb_in01(priv, addr, vid, ctrl);
+	if (res)
+		return res;
+
+	ctrl = YT921X_FDB_IO2_EGR_PORTS(ports_mask);
+	res = yt921x_smi_write(priv, YT921X_FDB_IN2, ctrl);
+	if (res)
+		return res;
+
+	ctrl = YT921X_FDB_OP_OP_ADD | YT921X_FDB_OP_START;
+	res = yt921x_smi_write(priv, YT921X_FDB_OP, ctrl);
+	if (res)
+		return res;
+
+	res = yt921x_fdb_wait(priv, &val);
+	if (res)
+		return res;
+
+	return 0;
+}
+
+static int
+yt921x_fdb_leave(struct yt921x_priv *priv, const unsigned char *addr,
+		 u16 vid, u16 ports_mask)
+{
+	u16 index;
+	u32 data1;
+	u32 data2;
+	u32 val;
+	int res;
+
+	/* Check for presence */
+	res = yt921x_fdb_has(priv, addr, vid, &index);
+	if (res)
+		return res;
+	if (index >= YT921X_FDB_NUM)
+		return 0;
+
+	/* Check if action required */
+	res = yt921x_smi_read(priv, YT921X_FDB_OUT2, &data2);
+	if (res)
+		return res;
+	val = data2 & ~YT921X_FDB_IO2_EGR_PORTS(ports_mask);
+	if (val == data2)
+		return 0;
+	if ((val & YT921X_FDB_IO2_EGR_PORTS_M) == 0)
+		return yt921x_fdb_del_index(priv, index);
+	data2 = val;
+
+	/* Overwrite entry */
+	res = yt921x_smi_write(priv, YT921X_FDB_IN2, data2);
+	if (res)
+		return res;
+
+	res = yt921x_smi_read(priv, YT921X_FDB_OUT1, &data1);
+	if (res)
+		return res;
+	res = yt921x_smi_write(priv, YT921X_FDB_IN1, data1);
+	if (res)
+		return res;
+
+	return yt921x_fdb_add_index(priv, index);
+}
+
+static int
+yt921x_fdb_join(struct yt921x_priv *priv, const unsigned char *addr, u16 vid,
+		u16 ports_mask)
+{
+	u16 index;
+	u32 data1;
+	u32 data2;
+	u32 val;
+	int res;
+
+	/* Check for presence */
+	res = yt921x_fdb_has(priv, addr, vid, &index);
+	if (res)
+		return res;
+	if (index >= YT921X_FDB_NUM)
+		return yt921x_fdb_add(priv, addr, vid, ports_mask);
+
+	/* Check if action required */
+	res = yt921x_smi_read(priv, YT921X_FDB_OUT2, &data2);
+	if (res)
+		return res;
+	val = data2 | YT921X_FDB_IO2_EGR_PORTS(ports_mask);
+	if (val == data2)
+		return 0;
+	data2 = val;
+
+	/* Overwrite entry */
+	res = yt921x_smi_write(priv, YT921X_FDB_IN2, data2);
+	if (res)
+		return res;
+
+	res = yt921x_smi_read(priv, YT921X_FDB_OUT1, &data1);
+	if (res)
+		return res;
+	res = yt921x_smi_write(priv, YT921X_FDB_IN1, data1);
+	if (res)
+		return res;
+
+	return yt921x_fdb_add_index(priv, index);
+}
+
+static int
+yt921x_dsa_port_fdb_dump(struct dsa_switch *ds, int port,
+			 dsa_fdb_dump_cb_t *cb, void *data)
+{
+	struct yt921x_priv *priv = to_yt921x_priv(ds);
+	struct device *dev = to_device(priv);
+	int res;
+
+	dev_dbg(dev, "%s: port %d\n", __func__, port);
+
+	yt921x_smi_acquire(priv);
+	do {
+		res = yt921x_fdb_dump(priv, BIT(port), cb, data);
+		if (res)
+			break;
+		res = yt921x_mdb_dump(priv, BIT(port), cb, data);
+	} while (0);
+	yt921x_smi_release(priv);
+
+	return res;
+}
+
+static void yt921x_dsa_port_fast_age(struct dsa_switch *ds, int port)
+{
+	struct yt921x_priv *priv = to_yt921x_priv(ds);
+	struct device *dev = to_device(priv);
+	int res;
+
+	dev_dbg(dev, "%s: port %d\n", __func__, port);
+
+	yt921x_smi_acquire(priv);
+	res = yt921x_fdb_flush(priv, BIT(port), 0, false);
+	yt921x_smi_release(priv);
+
+	consume_retval(res);
+}
+
+static int
+yt921x_dsa_port_vlan_fast_age(struct dsa_switch *ds, int port, u16 vid)
+{
+	struct yt921x_priv *priv = to_yt921x_priv(ds);
+	struct device *dev = to_device(priv);
+	int res;
+
+	dev_dbg(dev, "%s: port %d, vlan %d\n", __func__, port, vid);
+
+	yt921x_smi_acquire(priv);
+	res = yt921x_fdb_flush(priv, BIT(port), vid, false);
+	yt921x_smi_release(priv);
+
+	return res;
+}
+
+static int
+yt921x_dsa_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
+{
+	struct yt921x_priv *priv = to_yt921x_priv(ds);
+	struct device *dev = to_device(priv);
+	u32 ctrl;
+	int res;
+
+	dev_dbg(dev, "%s: %d\n", __func__, msecs);
+
+	/* AGEING reg is set in 5s step */
+	ctrl = msecs / 5000;
+
+	/* Handle case with 0 as val to NOT disable learning */
+	if (!ctrl)
+		ctrl = 1;
+	else if (ctrl > 0xffff)
+		ctrl = 0xffff;
+
+	yt921x_smi_acquire(priv);
+	res = yt921x_smi_write(priv, YT921X_AGEING, ctrl);
+	yt921x_smi_release(priv);
+
+	return res;
+}
+
+static int
+yt921x_dsa_port_fdb_del(struct dsa_switch *ds, int port,
+			const unsigned char *addr, u16 vid, struct dsa_db db)
+{
+	struct yt921x_priv *priv = to_yt921x_priv(ds);
+	struct device *dev = to_device(priv);
+	int res;
+
+	dev_dbg(dev, "%s: port %d\n", __func__, port);
+
+	yt921x_smi_acquire(priv);
+	/* assume correct port mask */
+	res = yt921x_fdb_del(priv, addr, vid);
+	yt921x_smi_release(priv);
+
+	return res;
+}
+
+static int
+yt921x_dsa_port_fdb_add(struct dsa_switch *ds, int port,
+			const unsigned char *addr, u16 vid, struct dsa_db db)
+{
+	struct yt921x_priv *priv = to_yt921x_priv(ds);
+	struct device *dev = to_device(priv);
+	int res;
+
+	dev_dbg(dev, "%s: port %d\n", __func__, port);
+
+	yt921x_smi_acquire(priv);
+	res = yt921x_fdb_add(priv, addr, vid, BIT(port));
+	yt921x_smi_release(priv);
+
+	return res;
+}
+
+static int
+yt921x_dsa_port_mdb_del(struct dsa_switch *ds, int port,
+			const struct switchdev_obj_port_mdb *mdb,
+			struct dsa_db db)
+{
+	struct yt921x_priv *priv = to_yt921x_priv(ds);
+	const unsigned char *addr = mdb->addr;
+	struct device *dev = to_device(priv);
+	u16 vid = mdb->vid;
+	int res;
+
+	dev_dbg(dev, "%s: port %d\n", __func__, port);
+
+	yt921x_smi_acquire(priv);
+	res = yt921x_fdb_leave(priv, addr, vid, BIT(port));
+	yt921x_smi_release(priv);
+
+	return res;
+}
+
+static int
+yt921x_dsa_port_mdb_add(struct dsa_switch *ds, int port,
+			const struct switchdev_obj_port_mdb *mdb,
+			struct dsa_db db)
+{
+	struct yt921x_priv *priv = to_yt921x_priv(ds);
+	const unsigned char *addr = mdb->addr;
+	struct device *dev = to_device(priv);
+	u16 vid = mdb->vid;
+	int res;
+
+	dev_dbg(dev, "%s: port %d\n", __func__, port);
+
+	yt921x_smi_acquire(priv);
+	res = yt921x_fdb_join(priv, addr, vid, BIT(port));
+	yt921x_smi_release(priv);
+
+	return res;
+}
+
+/******** port ********/
+
+static int
+yt921x_port_config(struct yt921x_priv *priv, int port, unsigned int mode,
+		   const struct phylink_link_state *state)
+{
+	const struct yt921x_info *info = priv->info;
+	struct device *dev = to_device(priv);
+	enum yt921x_fixed fixed;
+	bool duplex_full;
+	u32 mask;
+	u32 ctrl;
+	int res;
+
+	if (state->interface != PHY_INTERFACE_MODE_INTERNAL &&
+	    !yt921x_info_port_is_external(info, port)) {
+		dev_err(dev, "Wrong mode %d on port %d\n",
+			state->interface, port);
+		return -EINVAL;
+	}
+
+	fixed = YT921X_FIXED_NOTFIXED;
+	ctrl = YT921X_PORT_LINK;
+	if (mode == MLO_AN_FIXED)
+		switch (state->speed) {
+		case 10:
+			fixed = YT921X_FIXED_10;
+			ctrl = YT921X_PORT_SPEED_10;
+			break;
+		case 100:
+			fixed = YT921X_FIXED_100;
+			ctrl = YT921X_PORT_SPEED_100;
+			break;
+		case 1000:
+			fixed = YT921X_FIXED_1000;
+			ctrl = YT921X_PORT_SPEED_1000;
+			break;
+		case 2500:
+			fixed = YT921X_FIXED_2500;
+			ctrl = YT921X_PORT_SPEED_2500;
+			break;
+		default:
+			if (state->speed >= 0)
+				dev_err(dev, "Unsupported speed %d\n",
+					state->speed);
+			break;
+		}
+
+	if (fixed != YT921X_FIXED_NOTFIXED) {
+		switch (state->duplex) {
+		case DUPLEX_FULL:
+			duplex_full = true;
+			ctrl |= YT921X_PORT_DUPLEX_FULL;
+			break;
+		case DUPLEX_HALF:
+			duplex_full = false;
+			break;
+		default:
+			dev_err(dev,
+				"Unspecified duplex while fixed speed %d\n",
+				state->speed);
+			duplex_full = true;
+			break;
+		}
+	}
+
+	if ((state->pause & MLO_PAUSE_RX) != 0)
+		ctrl |= YT921X_PORT_RX_PAUSE;
+	if ((state->pause & MLO_PAUSE_TX) != 0)
+		ctrl |= YT921X_PORT_TX_PAUSE;
+	if ((state->pause & MLO_PAUSE_AN) != 0)
+		ctrl |= YT921X_PORT_CTRL_PAUSE_AN;
+
+	res = yt921x_smi_write(priv, YT921X_PORTn_CTRL(port), ctrl);
+	if (res)
+		return res;
+
+	switch (state->interface) {
+	/* SGMII */
+	case PHY_INTERFACE_MODE_SGMII:
+	case PHY_INTERFACE_MODE_100BASEX:
+	case PHY_INTERFACE_MODE_1000BASEX:
+	case PHY_INTERFACE_MODE_2500BASEX:
+		res = yt921x_smi_set_bits(priv, YT921X_SGMII_CTRL,
+					  YT921X_SGMII_CTRL_EXTIFn(port));
+		if (res)
+			return res;
+		res = yt921x_smi_clear_bits(priv, YT921X_XMII_CTRL,
+					    YT921X_XMII_CTRL_EXTIFn(port));
+		if (res)
+			return res;
+
+		switch (state->interface) {
+		case PHY_INTERFACE_MODE_SGMII:
+			ctrl = YT921X_SGMII_MODE_SGMII_PHY;
+			break;
+		case PHY_INTERFACE_MODE_100BASEX:
+			ctrl = YT921X_SGMII_MODE_100BASEX;
+			break;
+		case PHY_INTERFACE_MODE_1000BASEX:
+			ctrl = YT921X_SGMII_MODE_1000BASEX;
+			break;
+		case PHY_INTERFACE_MODE_2500BASEX:
+			ctrl = YT921X_SGMII_MODE_2500BASEX;
+			break;
+		default:
+			should_unreachable();
+			break;
+		}
+		mask = YT921X_SGMII_MODE_M;
+
+		if (fixed != YT921X_FIXED_NOTFIXED) {
+			ctrl |= YT921X_SGMII_LINK;
+
+			switch (fixed) {
+			case YT921X_FIXED_10:
+				ctrl |= YT921X_SGMII_SPEED_10;
+				break;
+			case YT921X_FIXED_100:
+				ctrl |= YT921X_SGMII_SPEED_100;
+				break;
+			case YT921X_FIXED_1000:
+				ctrl |= YT921X_SGMII_SPEED_1000;
+				break;
+			case YT921X_FIXED_2500:
+				ctrl |= YT921X_SGMII_SPEED_2500;
+				break;
+			default:
+				should_unreachable();
+				break;
+			}
+
+			if (duplex_full)
+				ctrl |= YT921X_SGMII_DUPLEX_FULL;
+			if ((state->pause & MLO_PAUSE_RX) != 0)
+				ctrl |= YT921X_SGMII_RX_PAUSE;
+			if ((state->pause & MLO_PAUSE_TX) != 0)
+				ctrl |= YT921X_SGMII_TX_PAUSE;
+
+			mask |= YT921X_SGMII_LINK;
+			mask |= YT921X_SGMII_SPEED_M;
+			mask |= YT921X_PORT_DUPLEX_FULL;
+			mask |= YT921X_SGMII_RX_PAUSE;
+			mask |= YT921X_SGMII_TX_PAUSE;
+		}
+
+		res = yt921x_smi_update_bits(priv, YT921X_SGMIIn(port),
+					     mask, ctrl);
+		if (res)
+			return res;
+
+		break;
+	/* XMII */
+	case PHY_INTERFACE_MODE_MII:
+	case PHY_INTERFACE_MODE_REVMII:
+	case PHY_INTERFACE_MODE_RMII:
+	case PHY_INTERFACE_MODE_REVRMII:
+	case PHY_INTERFACE_MODE_RGMII:
+	case PHY_INTERFACE_MODE_RGMII_ID:
+	case PHY_INTERFACE_MODE_RGMII_RXID:
+	case PHY_INTERFACE_MODE_RGMII_TXID:
+		/* TODO */
+		dev_err(dev, "Untested mode %d\n", state->interface);
+		return -EINVAL;
+
+		res = yt921x_smi_clear_bits(priv, YT921X_SGMII_CTRL,
+					    YT921X_SGMII_CTRL_EXTIFn(port));
+		if (res)
+			return res;
+		res = yt921x_smi_set_bits(priv, YT921X_XMII_CTRL,
+					  YT921X_XMII_CTRL_EXTIFn(port));
+		if (res)
+			return res;
+
+		ctrl = YT921X_XMII_EN;
+		switch (state->interface) {
+		case PHY_INTERFACE_MODE_MII:
+			ctrl |= YT921X_XMII_MODE_MII;
+			break;
+		case PHY_INTERFACE_MODE_REVMII:
+			ctrl |= YT921X_XMII_MODE_REVMII;
+			break;
+		case PHY_INTERFACE_MODE_RMII:
+			ctrl |= YT921X_XMII_MODE_RMII;
+			break;
+		case PHY_INTERFACE_MODE_REVRMII:
+			ctrl |= YT921X_XMII_MODE_REVRMII;
+			break;
+		case PHY_INTERFACE_MODE_RGMII:
+		case PHY_INTERFACE_MODE_RGMII_ID:
+		case PHY_INTERFACE_MODE_RGMII_RXID:
+		case PHY_INTERFACE_MODE_RGMII_TXID:
+			ctrl |= YT921X_XMII_MODE_RGMII;
+			break;
+		default:
+			should_unreachable();
+			break;
+		}
+		mask = YT921X_XMII_EN | YT921X_XMII_MODE_M;
+		res = yt921x_smi_update_bits(priv, YT921X_XMIIn(port),
+					     mask, ctrl);
+		if (res)
+			return res;
+
+		/* TODO: RGMII delay */
+
+		if (fixed != YT921X_FIXED_NOTFIXED) {
+			ctrl = YT921X_MDIO_POLLING_LINK;
+
+			switch (fixed) {
+			case YT921X_FIXED_10:
+				ctrl |= YT921X_MDIO_POLLING_SPEED_10;
+				break;
+			case YT921X_FIXED_100:
+				ctrl |= YT921X_MDIO_POLLING_SPEED_100;
+				break;
+			case YT921X_FIXED_1000:
+				ctrl |= YT921X_MDIO_POLLING_SPEED_1000;
+				break;
+			case YT921X_FIXED_2500:
+				ctrl |= YT921X_MDIO_POLLING_SPEED_2500;
+				break;
+			default:
+				should_unreachable();
+				break;
+			}
+			if (duplex_full)
+				ctrl |= YT921X_MDIO_POLLING_DUPLEX_FULL;
+
+			res = yt921x_smi_write(priv, YT921X_MDIO_POLLINGn(port),
+					       ctrl);
+			if (res)
+				return res;
+		}
+
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static void
+yt921x_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 dsa_switch *ds = dp->ds;
+	struct yt921x_priv *priv = to_yt921x_priv(ds);
+	struct device *dev = to_device(priv);
+	int port = dp->index;
+	u32 ctrl;
+	int res;
+
+	dev_dbg(dev, "%s: port %d\n", __func__, port);
+
+	ctrl = YT921X_PORT_RX_MAC_EN | YT921X_PORT_TX_MAC_EN;
+	res = yt921x_smi_clear_bits_burst(priv, YT921X_PORTn_CTRL(port), ctrl);
+	if (res)
+		dev_err(dev, "Cannot disable port %d: %i\n", port, res);
+}
+
+static void
+yt921x_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 dsa_switch *ds = dp->ds;
+	struct yt921x_priv *priv = to_yt921x_priv(ds);
+	struct device *dev = to_device(priv);
+	int port = dp->index;
+	u32 ctrl;
+	int res;
+
+	dev_dbg(dev, "%s: port %d\n", __func__, port);
+
+	ctrl = YT921X_PORT_RX_MAC_EN | YT921X_PORT_TX_MAC_EN;
+	res = yt921x_smi_set_bits_burst(priv, YT921X_PORTn_CTRL(port), ctrl);
+	if (res)
+		dev_err(dev, "Cannot enable port %d: %i\n", port, res);
+}
+
+static void
+yt921x_phylink_mac_config(struct phylink_config *config, unsigned int mode,
+			  const struct phylink_link_state *state)
+{
+	struct dsa_port *dp = dsa_phylink_to_port(config);
+	struct dsa_switch *ds = dp->ds;
+	struct yt921x_priv *priv = to_yt921x_priv(ds);
+	struct device *dev = to_device(priv);
+	int port = dp->index;
+	int res;
+
+	dev_dbg(dev,
+		"%s: port %d, mode %u, interface %d, speed %d, duplex %d, "
+		"pause %d, advertising %lx\n", __func__,
+		port, mode, state->interface, state->speed, state->duplex,
+		state->pause, *state->advertising);
+
+	yt921x_smi_acquire(priv);
+	res = yt921x_port_config(priv, port, mode, state);
+	yt921x_smi_release(priv);
+
+	if (res)
+		dev_err(dev, "Cannot configure port %d: %i\n", port, res);
+}
+
+static void
+yt921x_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;
+
+	if (yt921x_info_port_is_internal(info, port)) {
+		__set_bit(PHY_INTERFACE_MODE_INTERNAL,
+			  config->supported_interfaces);
+		config->mac_capabilities = 0;
+	} else if (yt921x_info_port_is_external(info, port)) {
+		/* TODO: external port may support SGMII only, XMII only, or
+		 * SGMII + XMII depending on the chip. However, we can't get
+		 * the accurate config table due to lack of document, thus
+		 * we simply declare SGMII + XMII and rely on the correctness
+		 * of devicetree for now.
+		 */
+
+		/* SGMII */
+		__set_bit(PHY_INTERFACE_MODE_SGMII,
+			  config->supported_interfaces);
+		__set_bit(PHY_INTERFACE_MODE_100BASEX,
+			  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;
+
+		/* XMII */
+		__set_bit(PHY_INTERFACE_MODE_MII,
+			  config->supported_interfaces);
+		__set_bit(PHY_INTERFACE_MODE_REVMII,
+			  config->supported_interfaces);
+		__set_bit(PHY_INTERFACE_MODE_RMII,
+			  config->supported_interfaces);
+		__set_bit(PHY_INTERFACE_MODE_REVRMII,
+			  config->supported_interfaces);
+		phy_interface_set_rgmii(config->supported_interfaces);
+	} else {
+		return;
+	}
+
+	config->mac_capabilities |= MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
+				    MAC_10 | MAC_100 | MAC_1000;
+}
+
+static int yt921x_port_setup(struct yt921x_priv *priv, int port)
+{
+	u32 mask;
+	u32 ctrl;
+	int res;
+
+	/* Enable port isolation */
+	if ((BIT(port) & priv->cpu_ports_mask) != 0)
+		ctrl = (u32)~priv->cpu_ports_mask;
+	else
+		ctrl = 0;
+	res = yt921x_smi_write(priv, YT921X_PORTn_ISOLATION(port), ctrl);
+	if (res)
+		return res;
+
+	/* Set PVID */
+	mask = YT921X_PORT_VLAN_CTRL_SVID_M | YT921X_PORT_VLAN_CTRL_CVID_M;
+	ctrl = YT921X_PORT_VLAN_CTRL_SVID(YT921X_PVID_DEFAULT) |
+	       YT921X_PORT_VLAN_CTRL_CVID(YT921X_PVID_DEFAULT);
+	res = yt921x_smi_update_bits(priv, YT921X_PORTn_VLAN_CTRL(port),
+				     mask, ctrl);
+	if (res)
+		return res;
+
+	return 0;
+}
+
+static enum dsa_tag_protocol
+yt921x_dsa_get_tag_protocol(struct dsa_switch *ds, int port,
+			    enum dsa_tag_protocol m)
+{
+	return DSA_TAG_PROTO_YT921X;
+}
+
+static int yt921x_dsa_port_setup(struct dsa_switch *ds, int port)
+{
+	struct yt921x_priv *priv = to_yt921x_priv(ds);
+	struct device *dev = to_device(priv);
+	int res;
+
+	dev_dbg(dev, "%s: port %d\n", __func__, port);
+
+	yt921x_smi_acquire(priv);
+	res = yt921x_port_setup(priv, port);
+	yt921x_smi_release(priv);
+
+	return res;
+}
+
+/******** chip ********/
+
+static int yt921x_detect(struct yt921x_priv *priv)
+{
+	struct device *dev = to_device(priv);
+	const struct yt921x_info *info;
+	u32 chipid;
+	u32 major;
+	u32 mode;
+	u8 extmode;
+	int res;
+
+	res = yt921x_smi_read_burst(priv, YT921X_CHIP_ID, &chipid);
+	if (res)
+		return res;
+
+	major = FIELD_GET(YT921X_CHIP_ID_MAJOR, chipid);
+
+	for (info = yt921x_infos; info->name; info++)
+		if (info->major == major)
+			goto found_major;
+
+	dev_err(dev, "Unexpected chipid 0x%x\n", chipid);
+	return -ENODEV;
+
+found_major:
+	yt921x_smi_acquire(priv);
+	do {
+		res = yt921x_smi_read(priv, YT921X_CHIP_MODE, &mode);
+		if (res)
+			break;
+		res = yt921x_edata_read(priv, YT921X_EDATA_EXTMODE, &extmode);
+	} while (0);
+	yt921x_smi_release(priv);
+	if (res)
+		return res;
+
+	for (; info->name; info++)
+		if (info->major == major && info->mode == mode &&
+		    info->extmode == extmode)
+			goto found_chip;
+
+	dev_err(dev, "Unsupported chipid 0x%x chipmode 0x%x 0x%x\n",
+		chipid, mode, extmode);
+	return -ENODEV;
+
+found_chip:
+	dev_info(dev,
+		 "Motorcomm %s switch, chipid: 0x%x, chipmode: 0x%x 0x%x\n",
+		 info->name, chipid, mode, extmode);
+
+	res = yt921x_smi_read_burst(priv, YT921X_PON_STRAP_CAP,
+				    &priv->pon_strap_cap);
+	if (res)
+		return res;
+
+	priv->info = info;
+	return 0;
+}
+
+static int yt921x_dsa_get_eeprom_len(struct dsa_switch *ds)
+{
+	return YT921X_EDATA_LEN;
+}
+
+static int
+yt921x_dsa_get_eeprom(struct dsa_switch *ds, struct ethtool_eeprom *eeprom,
+		      u8 *data)
+{
+	struct yt921x_priv *priv = to_yt921x_priv(ds);
+	unsigned int i = 0;
+	int res;
+
+	yt921x_smi_acquire(priv);
+	do {
+		res = yt921x_edata_wait(priv);
+		if (res)
+			break;
+		for (; i < eeprom->len; i++) {
+			unsigned int offset = eeprom->offset + i;
+
+			res = yt921x_edata_read_cont(priv, offset, &data[i]);
+			if (res)
+				break;
+		}
+	} while (0);
+	yt921x_smi_release(priv);
+
+	eeprom->len = i;
+	return res;
+}
+
+static struct dsa_port *
+yt921x_dsa_preferred_default_local_cpu_port(struct dsa_switch *ds)
+{
+	struct dsa_port *ext_dp = NULL;
+	struct dsa_port *cpu_dp;
+
+	dsa_switch_for_each_cpu_port(cpu_dp, ds)
+		if (yt921x_port_is_external(cpu_dp->index)) {
+			ext_dp = cpu_dp;
+			break;
+		}
+
+	return ext_dp;
+}
+
+static void
+yt921x_dsa_conduit_state_change(struct dsa_switch *ds,
+				const struct net_device *conduit,
+				bool operational)
+{
+	struct yt921x_priv *priv = to_yt921x_priv(ds);
+	struct dsa_port *cpu_dp = conduit->dsa_ptr;
+	struct device *dev = to_device(priv);
+	int port = cpu_dp->index;
+	u32 ctrl;
+	int res;
+
+	dev_dbg(dev, "%s: port %d, up %d\n", __func__, port, operational);
+
+	if (operational)
+		priv->active_cpu_ports_mask |= BIT(port);
+	else
+		priv->active_cpu_ports_mask &= ~BIT(port);
+
+	if (priv->active_cpu_ports_mask) {
+		ctrl = YT921X_EXT_CPU_PORT_TAG_EN |
+		       YT921X_EXT_CPU_PORT_PORT_EN |
+		       YT921X_EXT_CPU_PORT_PORT(__fls(priv->active_cpu_ports_mask));
+		res = yt921x_smi_write_burst(priv, YT921X_EXT_CPU_PORT, ctrl);
+		consume_retval(res);
+	}
+}
+
+static int yt921x_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;
+	u32 ctrl;
+	u32 val;
+	int res;
+
+	priv->cpu_ports_mask = dsa_cpu_ports(ds);
+	if (!priv->cpu_ports_mask) {
+		dev_err(dev, "No CPU port\n");
+		return -ENODEV;
+	}
+
+	res = yt921x_detect(priv);
+	if (res)
+		return res;
+
+	/* Reset */
+	res = yt921x_smi_write_burst(priv, YT921X_RST, YT921X_RST_HW);
+	if (res)
+		return res;
+
+	/* YT921X_RST_HW is almost same as GPIO hard reset, so we need
+	 * this delay.
+	 */
+	fsleep(YT921X_RST_DELAY_US);
+
+	res = read_poll_timeout(yt921x_smi_read_burst, res, val == 0,
+				YT921X_MDIO_SLEEP_US, YT921X_RST_TIMEOUT_US,
+				false, priv, YT921X_RST, &val);
+	if (res) {
+		dev_err(dev, "Reset timeout\n");
+		return res;
+	}
+
+	/* Always register one mdio bus for the internal/default mdio bus. This
+	 * maybe represented in the device tree, but is optional.
+	 */
+	child = of_get_child_by_name(np, "mdio");
+	res = yt921x_mbus_int_init(priv, child);
+	of_node_put(child);
+	if (res)
+		return res;
+	ds->user_mii_bus = priv->mbus_int;
+
+	/* Walk the device tree, and see if there are any other nodes which say
+	 * they are compatible with the external mdio bus.
+	 */
+	child = of_get_child_by_name(np, "mdio-external");
+	if (!child) {
+		priv->mbus_ext = NULL;
+	} else {
+		res = yt921x_mbus_ext_init(priv, child);
+		of_node_put(child);
+		if (res)
+			return res;
+
+		dev_err(dev, "Untested external mdio bus\n");
+		return -ENODEV;
+	}
+
+	yt921x_smi_acquire(priv);
+	do {
+		/* Enable DSA */
+		res = yt921x_smi_read(priv, YT921X_CPU_TAG_TPID, &val);
+		if (res)
+			break;
+		priv->tag_eth_p = FIELD_GET(YT921X_CPU_TAG_TPID_TPID_M, val);
+		if (priv->tag_eth_p != YT921X_CPU_TAG_TPID_TPID_DEFAULT) {
+			dev_warn(dev, "Tag type 0x%x != 0x%x\n",
+				 priv->tag_eth_p,
+				 YT921X_CPU_TAG_TPID_TPID_DEFAULT);
+			/* Although CPU_TAG_TPID could be configured, we choose
+			 * not to do so, since there is no way it could be
+			 * different from the default, unless you are using the
+			 * wrong chip.
+			 */
+			res = -EINVAL;
+			break;
+		}
+
+		/* Unconditionally bring up one CPU port, to avoid warnings from
+		 * yt921x_tag_rcv() before conduit_state_change() is called.
+		 */
+		ctrl = YT921X_EXT_CPU_PORT_TAG_EN |
+		       YT921X_EXT_CPU_PORT_PORT_EN |
+		       YT921X_EXT_CPU_PORT_PORT(__fls(priv->cpu_ports_mask));
+		res = yt921x_smi_write(priv, YT921X_EXT_CPU_PORT, ctrl);
+		if (res)
+			break;
+
+		/* Enable MIB */
+		res = yt921x_smi_set_bits(priv, YT921X_FUNC, YT921X_FUNC_MIB);
+		if (res)
+			break;
+
+		ctrl = YT921X_MIB_CTRL_CLEAN | YT921X_MIB_CTRL_ALL_PORT;
+		res = yt921x_smi_write(priv, YT921X_MIB_CTRL, ctrl);
+		if (res)
+			break;
+
+		/* Port flags */
+		ctrl = ~0;
+		res = yt921x_smi_write(priv, YT921X_FILTER_UNK_UCAST, ctrl);
+		if (res)
+			break;
+
+		ctrl = YT921X_CPU_COPY_TO_EXT_CPU;
+		res = yt921x_smi_write(priv, YT921X_CPU_COPY, ctrl);
+		if (res)
+			break;
+
+		/* Temperature sensor */
+		res = yt921x_smi_set_bits(priv, YT921X_SENSOR,
+					  YT921X_SENSOR_TEMP);
+	} while (0);
+	yt921x_smi_release(priv);
+	if (res)
+		return res;
+
+	return 0;
+}
+
+static const struct phylink_mac_ops yt921x_phylink_mac_ops = {
+	.mac_link_down	= yt921x_phylink_mac_link_down,
+	.mac_link_up	= yt921x_phylink_mac_link_up,
+	.mac_config	= yt921x_phylink_mac_config,
+};
+
+static const struct dsa_switch_ops yt921x_dsa_switch_ops = {
+	/* mib */
+	.get_strings		= yt921x_dsa_get_strings,
+	.get_ethtool_stats	= yt921x_dsa_get_ethtool_stats,
+	.get_sset_count		= yt921x_dsa_get_sset_count,
+	.get_stats64		= yt921x_dsa_get_stats64,
+	.get_pause_stats	= yt921x_dsa_get_pause_stats,
+	/* eee */
+	.support_eee		= yt921x_dsa_support_eee,
+	.set_mac_eee		= yt921x_dsa_set_mac_eee,
+	/* mtu */
+	.port_change_mtu	= yt921x_dsa_port_change_mtu,
+	.port_max_mtu		= yt921x_dsa_port_max_mtu,
+	/* mirror */
+	.port_mirror_del	= yt921x_dsa_port_mirror_del,
+	.port_mirror_add	= yt921x_dsa_port_mirror_add,
+	/* vlan */
+	.port_vlan_filtering	= yt921x_dsa_port_vlan_filtering,
+	.port_vlan_del		= yt921x_dsa_port_vlan_del,
+	.port_vlan_add		= yt921x_dsa_port_vlan_add,
+	/* bridge */
+	.port_pre_bridge_flags	= yt921x_dsa_port_pre_bridge_flags,
+	.port_bridge_flags	= yt921x_dsa_port_bridge_flags,
+	.port_bridge_leave	= yt921x_dsa_port_bridge_leave,
+	.port_bridge_join	= yt921x_dsa_port_bridge_join,
+	/* fdb */
+	.port_fdb_dump		= yt921x_dsa_port_fdb_dump,
+	.port_fast_age		= yt921x_dsa_port_fast_age,
+	.port_vlan_fast_age	= yt921x_dsa_port_vlan_fast_age,
+	.set_ageing_time	= yt921x_dsa_set_ageing_time,
+	.port_fdb_del		= yt921x_dsa_port_fdb_del,
+	.port_fdb_add		= yt921x_dsa_port_fdb_add,
+	.port_mdb_del		= yt921x_dsa_port_mdb_del,
+	.port_mdb_add		= yt921x_dsa_port_mdb_add,
+	/* port */
+	.get_tag_protocol	= yt921x_dsa_get_tag_protocol,
+	.phylink_get_caps	= yt921x_dsa_phylink_get_caps,
+	.port_setup		= yt921x_dsa_port_setup,
+	/* chip */
+	.get_eeprom_len		= yt921x_dsa_get_eeprom_len,
+	.get_eeprom		= yt921x_dsa_get_eeprom,
+	.preferred_default_local_cpu_port	= yt921x_dsa_preferred_default_local_cpu_port,
+	.conduit_state_change	= yt921x_dsa_conduit_state_change,
+	.setup			= yt921x_dsa_setup,
+};
+
+/******** debug ********/
+
+static ssize_t
+reg_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct yt921x_priv *priv = dev_get_drvdata(dev);
+
+	if (!priv->reg_valid)
+		return sprintf(buf, "0x%x: -\n", priv->reg_addr);
+
+	return sprintf(buf, "0x%x: 0x%08x\n", priv->reg_addr, priv->reg_val);
+}
+
+/* Convenience sysfs attribute to read/write switch internal registers, since
+ * user-space tools cannot gain exclusive access to the device, which is
+ * required for any register operations.
+ */
+static ssize_t
+reg_store(struct device *dev, struct device_attribute *attr,
+	  const char *buf, size_t count)
+{
+	struct yt921x_priv *priv = dev_get_drvdata(dev);
+	const char *end = buf + count;
+	const char *p = buf;
+	bool is_write;
+	u32 reg;
+	u32 val;
+	int res;
+
+	do {
+		unsigned long v;
+		char *e;
+
+		while (p < end && isspace(*p))
+			p++;
+		if (p >= end)
+			return -EINVAL;
+
+		v = simple_strntoul(p, &e, 0, end - p);
+		if (v >= YT921X_REG_END)
+			return -EPERM;
+		reg = v;
+		is_write = false;
+
+		p = e;
+		if (p >= end)
+			break;
+		if (!isspace(*p))
+			return -EINVAL;
+
+		do
+			p++;
+		while (p < end && isspace(*p));
+		if (p >= end)
+			break;
+
+		v = simple_strntoul(p, &e, 0, end - p);
+		if ((u32)v != v)
+			return -EINVAL;
+		val = v;
+		is_write = true;
+
+		p = e;
+		if (p >= end)
+			break;
+		if (!isspace(*p))
+			return -EINVAL;
+	} while (0);
+
+	yt921x_smi_acquire(priv);
+	if (!is_write)
+		res = yt921x_smi_read(priv, reg, &val);
+	else
+		res = yt921x_smi_write(priv, reg, val);
+	yt921x_smi_release(priv);
+
+	if (res) {
+		dev_err(dev, "Cannot access register 0x%x: %i\n", reg, res);
+		return -EIO;
+	}
+
+	priv->reg_addr = reg;
+	priv->reg_val = val;
+	priv->reg_valid = !is_write;
+
+	return count;
+}
+
+static DEVICE_ATTR_RW(reg);
+
+static struct attribute *yt921x_attrs[] = {
+	&dev_attr_reg.attr,
+	NULL,
+};
+
+ATTRIBUTE_GROUPS(yt921x);
+
+/******** device ********/
+
+static void yt921x_mdio_shutdown(struct mdio_device *mdiodev)
+{
+	struct device *dev = &mdiodev->dev;
+	struct yt921x_priv *priv = dev_get_drvdata(dev);
+	struct dsa_switch *ds = &priv->ds;
+
+	dsa_switch_shutdown(ds);
+}
+
+static void yt921x_mdio_remove(struct mdio_device *mdiodev)
+{
+	struct device *dev = &mdiodev->dev;
+	struct yt921x_priv *priv = dev_get_drvdata(dev);
+	struct dsa_switch *ds = &priv->ds;
+
+	dsa_unregister_switch(ds);
+}
+
+static int yt921x_mdio_probe(struct mdio_device *mdiodev)
+{
+	struct device *dev = &mdiodev->dev;
+	struct yt921x_smi_mdio *mdio;
+	struct yt921x_priv *priv;
+	struct dsa_switch *ds;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	mdio = devm_kzalloc(dev, sizeof(*mdio), GFP_KERNEL);
+	if (!mdio)
+		return -ENOMEM;
+
+	mdio->bus = mdiodev->bus;
+	mdio->addr = mdiodev->addr;
+	mdio->switchid = 0;
+
+	priv->smi_ops = &yt921x_smi_ops_mdio;
+	priv->smi_ctx = mdio;
+
+	ds = &priv->ds;
+	ds->dev = dev;
+	ds->configure_vlan_while_not_filtering = true;
+	ds->assisted_learning_on_cpu_port = true;
+	ds->priv = priv;
+	ds->ops = &yt921x_dsa_switch_ops;
+	ds->phylink_mac_ops = &yt921x_phylink_mac_ops;
+	ds->num_ports = YT921X_PORT_NUM;
+
+	dev_set_drvdata(dev, priv);
+
+	return dsa_register_switch(ds);
+}
+
+static const struct of_device_id yt921x_of_match[] = {
+	{ .compatible = "motorcomm,yt9215" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, yt921x_of_match);
+
+static struct mdio_driver yt921x_mdio_driver = {
+	.probe = yt921x_mdio_probe,
+	.remove = yt921x_mdio_remove,
+	.shutdown = yt921x_mdio_shutdown,
+	.mdiodrv.driver = {
+		.name = YT921X_NAME,
+		.of_match_table = yt921x_of_match,
+		.dev_groups = yt921x_groups,
+	},
+};
+
+mdio_module_driver(yt921x_mdio_driver);
+
+MODULE_AUTHOR("David Yang <mmyangfl@gmail.com>");
+MODULE_DESCRIPTION("Driver for Motorcomm YT921x Switch");
+MODULE_LICENSE("GPL");
-- 
2.50.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* Re: [net-next v5 1/3] dt-bindings: net: dsa: yt921x: Add Motorcomm YT921x switch support
  2025-08-20  7:54 ` [net-next v5 1/3] dt-bindings: net: dsa: yt921x: Add Motorcomm YT921x switch support David Yang
@ 2025-08-21  7:22   ` Krzysztof Kozlowski
  2025-08-21 12:20   ` Andrew Lunn
  1 sibling, 0 replies; 15+ messages in thread
From: Krzysztof Kozlowski @ 2025-08-21  7:22 UTC (permalink / raw)
  To: David Yang
  Cc: netdev, Andrew Lunn, Vladimir Oltean, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King,
	devicetree, linux-kernel

On Wed, Aug 20, 2025 at 03:54:14PM +0800, David Yang wrote:
> The Motorcomm YT921x series is a family of Ethernet switches with up to
> 8 internal GbE PHYs and up to 2 GMACs.

Please use standard email subjects, so with the PATCH keyword in the
title. 'git format-patch -vX' helps here to create proper versioned patches.
Another useful tool is b4. Skipping the PATCH keyword makes filtering of
emails more difficult thus making the review process less convenient.

> 
> Signed-off-by: David Yang <mmyangfl@gmail.com>
> ---
>  .../bindings/net/dsa/motorcomm,yt921x.yaml    | 150 ++++++++++++++++++
>  1 file changed, 150 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/net/dsa/motorcomm,yt921x.yaml

...

> +  mdio-external:
> +    $ref: /schemas/net/mdio.yaml#
> +    unevaluatedProperties: false
> +    description: |
> +      External MDIO bus to access external components. External PHYs for GMACs
> +      (Port 8-9) are expected to be connected to the external MDIO bus in
> +      vendor's reference design, but that is not a hard limitation from the
> +      chip.
> +
> +allOf:

This goes after required: block.

But don't resend just for that.

Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>

Best regards,
Krzysztof


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [net-next v5 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x
  2025-08-20  7:54 ` [net-next v5 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x David Yang
@ 2025-08-21  8:30   ` Russell King (Oracle)
  2025-08-21  9:25     ` Yangfl
  2025-08-21 12:41   ` Andrew Lunn
  1 sibling, 1 reply; 15+ messages in thread
From: Russell King (Oracle) @ 2025-08-21  8:30 UTC (permalink / raw)
  To: David Yang
  Cc: netdev, Andrew Lunn, Vladimir Oltean, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Simon Horman, devicetree,
	linux-kernel

On Wed, Aug 20, 2025 at 03:54:16PM +0800, David Yang wrote:
> +static int
> +yt921x_port_config(struct yt921x_priv *priv, int port, unsigned int mode,
> +		   const struct phylink_link_state *state)
> +{
> +	const struct yt921x_info *info = priv->info;
> +	struct device *dev = to_device(priv);
> +	enum yt921x_fixed fixed;
> +	bool duplex_full;
> +	u32 mask;
> +	u32 ctrl;
> +	int res;
> +
> +	if (state->interface != PHY_INTERFACE_MODE_INTERNAL &&
> +	    !yt921x_info_port_is_external(info, port)) {
> +		dev_err(dev, "Wrong mode %d on port %d\n",
> +			state->interface, port);
> +		return -EINVAL;
> +	}
> +
> +	fixed = YT921X_FIXED_NOTFIXED;
> +	ctrl = YT921X_PORT_LINK;
> +	if (mode == MLO_AN_FIXED)
> +		switch (state->speed) {

Someone clearly doesn't believe in reading the documentation before
writing code. This also hasn't been tested in any way. Sorry, but
I'm going to put as much effort into this review as you have into
understanding the phylink API, and thus my review ends here.

NAK.

-- 
RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [net-next v5 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x
  2025-08-21  8:30   ` Russell King (Oracle)
@ 2025-08-21  9:25     ` Yangfl
  2025-08-21 10:00       ` Russell King (Oracle)
  0 siblings, 1 reply; 15+ messages in thread
From: Yangfl @ 2025-08-21  9:25 UTC (permalink / raw)
  To: Russell King (Oracle)
  Cc: netdev, Andrew Lunn, Vladimir Oltean, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Simon Horman, devicetree,
	linux-kernel

On Thu, Aug 21, 2025 at 4:30 PM Russell King (Oracle)
<linux@armlinux.org.uk> wrote:
>
> On Wed, Aug 20, 2025 at 03:54:16PM +0800, David Yang wrote:
> > +static int
> > +yt921x_port_config(struct yt921x_priv *priv, int port, unsigned int mode,
> > +                const struct phylink_link_state *state)
> > +{
> > +     const struct yt921x_info *info = priv->info;
> > +     struct device *dev = to_device(priv);
> > +     enum yt921x_fixed fixed;
> > +     bool duplex_full;
> > +     u32 mask;
> > +     u32 ctrl;
> > +     int res;
> > +
> > +     if (state->interface != PHY_INTERFACE_MODE_INTERNAL &&
> > +         !yt921x_info_port_is_external(info, port)) {
> > +             dev_err(dev, "Wrong mode %d on port %d\n",
> > +                     state->interface, port);
> > +             return -EINVAL;
> > +     }
> > +
> > +     fixed = YT921X_FIXED_NOTFIXED;
> > +     ctrl = YT921X_PORT_LINK;
> > +     if (mode == MLO_AN_FIXED)
> > +             switch (state->speed) {
>
> Someone clearly doesn't believe in reading the documentation before
> writing code. This also hasn't been tested in any way. Sorry, but
> I'm going to put as much effort into this review as you have into
> understanding the phylink API, and thus my review ends here.
>
> NAK.
>
> --
> RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
> FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!

Sorry I'm quite new here. I don't understand very clearly why a
different set of calls is involved in dsa_switch_ops, so I referred to
other dsa drivers and made a working driver (at least tested on my
device), but I would appreciate it much if you could point it out in
an earlier version of series.

Sorry for any disturbance.

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [net-next v5 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x
  2025-08-21  9:25     ` Yangfl
@ 2025-08-21 10:00       ` Russell King (Oracle)
  2025-08-21 10:14         ` Russell King (Oracle)
  0 siblings, 1 reply; 15+ messages in thread
From: Russell King (Oracle) @ 2025-08-21 10:00 UTC (permalink / raw)
  To: Yangfl
  Cc: netdev, Andrew Lunn, Vladimir Oltean, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Simon Horman, devicetree,
	linux-kernel

On Thu, Aug 21, 2025 at 05:25:46PM +0800, Yangfl wrote:
> On Thu, Aug 21, 2025 at 4:30 PM Russell King (Oracle)
> <linux@armlinux.org.uk> wrote:
> >
> > On Wed, Aug 20, 2025 at 03:54:16PM +0800, David Yang wrote:
> > > +static int
> > > +yt921x_port_config(struct yt921x_priv *priv, int port, unsigned int mode,
> > > +                const struct phylink_link_state *state)
> > > +{
> > > +     const struct yt921x_info *info = priv->info;
> > > +     struct device *dev = to_device(priv);
> > > +     enum yt921x_fixed fixed;
> > > +     bool duplex_full;
> > > +     u32 mask;
> > > +     u32 ctrl;
> > > +     int res;
> > > +
> > > +     if (state->interface != PHY_INTERFACE_MODE_INTERNAL &&
> > > +         !yt921x_info_port_is_external(info, port)) {
> > > +             dev_err(dev, "Wrong mode %d on port %d\n",
> > > +                     state->interface, port);
> > > +             return -EINVAL;
> > > +     }
> > > +
> > > +     fixed = YT921X_FIXED_NOTFIXED;
> > > +     ctrl = YT921X_PORT_LINK;
> > > +     if (mode == MLO_AN_FIXED)
> > > +             switch (state->speed) {
> >
> > Someone clearly doesn't believe in reading the documentation before
> > writing code. This also hasn't been tested in any way. Sorry, but
> > I'm going to put as much effort into this review as you have into
> > understanding the phylink API, and thus my review ends here.
> >
> > NAK.
> >
> > --
> > RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
> > FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!
> 
> Sorry I'm quite new here. I don't understand very clearly why a
> different set of calls is involved in dsa_switch_ops, so I referred to
> other dsa drivers and made a working driver (at least tested on my
> device), but I would appreciate it much if you could point it out in
> an earlier version of series.

This isn't dsa_switch_ops, but phylink_mac_ops, which are well
documented in include/linux/phylink.h. Please read the documentation
found in that header file detailing the phylink_mac_ops methods.
You'll find a brief overview before the struct, and then in the #if 0
section, detailed per-method documentation.

Thanks.

-- 
RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [net-next v5 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x
  2025-08-21 10:00       ` Russell King (Oracle)
@ 2025-08-21 10:14         ` Russell King (Oracle)
  2025-08-21 12:19           ` Yangfl
  0 siblings, 1 reply; 15+ messages in thread
From: Russell King (Oracle) @ 2025-08-21 10:14 UTC (permalink / raw)
  To: Yangfl
  Cc: netdev, Andrew Lunn, Vladimir Oltean, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Simon Horman, devicetree,
	linux-kernel

On Thu, Aug 21, 2025 at 11:00:35AM +0100, Russell King (Oracle) wrote:
> On Thu, Aug 21, 2025 at 05:25:46PM +0800, Yangfl wrote:
> > On Thu, Aug 21, 2025 at 4:30 PM Russell King (Oracle)
> > <linux@armlinux.org.uk> wrote:
> > > Someone clearly doesn't believe in reading the documentation before
> > > writing code. This also hasn't been tested in any way. Sorry, but
> > > I'm going to put as much effort into this review as you have into
> > > understanding the phylink API, and thus my review ends here.
> > >
> > > NAK.
> > 
> > Sorry I'm quite new here. I don't understand very clearly why a
> > different set of calls is involved in dsa_switch_ops, so I referred to
> > other dsa drivers and made a working driver (at least tested on my
> > device), but I would appreciate it much if you could point it out in
> > an earlier version of series.
> 
> This isn't dsa_switch_ops, but phylink_mac_ops, which are well
> documented in include/linux/phylink.h. Please read the documentation
> found in that header file detailing the phylink_mac_ops methods.
> You'll find a brief overview before the struct, and then in the #if 0
> section, detailed per-method documentation.

Also, the reason I state that it hasn't been tested is because when
your mac_config method is invoked, and print debug information which
includes state->speed and state->duplex, and then go on to use these.
Phylink's sole call path to mac_config() does this:

        /* Stop drivers incorrectly using these */
        linkmode_zero(st.lp_advertising);
        st.speed = SPEED_UNKNOWN;
        st.duplex = DUPLEX_UNKNOWN;
        st.an_complete = false;
        st.link = false;

        phylink_dbg(pl,
                    "%s: mode=%s/%s/%s adv=%*pb pause=%02x\n",
                    __func__, phylink_an_mode_str(pl->act_link_an_mode),
                    phy_modes(st.interface),
                    phy_rate_matching_to_str(st.rate_matching),
                    __ETHTOOL_LINK_MODE_MASK_NBITS, st.advertising,
                    st.pause);

        pl->mac_ops->mac_config(pl->config, pl->act_link_an_mode, &st);

and you would've noticed in your debug print that e.g. state->speed and
state->duplex are both always -1, and thus are not useful. Note also the
debugging that phylink includes.

Note that no other mac_config() implementations refer to state->speed
and state->duplex. The only time drivers _write_ to these is in the
pcs_get_state() method if they support a PCS.

Therefore, I think your code is completely untested.

I'm also concerned about the SMI locking, which looks to me like you
haven't realised that the MDIO bus layer has locking which guarantees
that all invocations of the MDIO bus read* and write* methods are
serialised.

Thanks.

-- 
RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [net-next v5 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x
  2025-08-21 10:14         ` Russell King (Oracle)
@ 2025-08-21 12:19           ` Yangfl
  0 siblings, 0 replies; 15+ messages in thread
From: Yangfl @ 2025-08-21 12:19 UTC (permalink / raw)
  To: Russell King (Oracle)
  Cc: netdev, Andrew Lunn, Vladimir Oltean, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Simon Horman, devicetree,
	linux-kernel

On Thu, Aug 21, 2025 at 6:14 PM Russell King (Oracle)
<linux@armlinux.org.uk> wrote:
>
> On Thu, Aug 21, 2025 at 11:00:35AM +0100, Russell King (Oracle) wrote:
> > On Thu, Aug 21, 2025 at 05:25:46PM +0800, Yangfl wrote:
> > > On Thu, Aug 21, 2025 at 4:30 PM Russell King (Oracle)
> > > <linux@armlinux.org.uk> wrote:
> > > > Someone clearly doesn't believe in reading the documentation before
> > > > writing code. This also hasn't been tested in any way. Sorry, but
> > > > I'm going to put as much effort into this review as you have into
> > > > understanding the phylink API, and thus my review ends here.
> > > >
> > > > NAK.
> > >
> > > Sorry I'm quite new here. I don't understand very clearly why a
> > > different set of calls is involved in dsa_switch_ops, so I referred to
> > > other dsa drivers and made a working driver (at least tested on my
> > > device), but I would appreciate it much if you could point it out in
> > > an earlier version of series.
> >
> > This isn't dsa_switch_ops, but phylink_mac_ops, which are well
> > documented in include/linux/phylink.h. Please read the documentation
> > found in that header file detailing the phylink_mac_ops methods.
> > You'll find a brief overview before the struct, and then in the #if 0
> > section, detailed per-method documentation.
>
> Also, the reason I state that it hasn't been tested is because when
> your mac_config method is invoked, and print debug information which
> includes state->speed and state->duplex, and then go on to use these.
> Phylink's sole call path to mac_config() does this:
>
>         /* Stop drivers incorrectly using these */
>         linkmode_zero(st.lp_advertising);
>         st.speed = SPEED_UNKNOWN;
>         st.duplex = DUPLEX_UNKNOWN;
>         st.an_complete = false;
>         st.link = false;
>
>         phylink_dbg(pl,
>                     "%s: mode=%s/%s/%s adv=%*pb pause=%02x\n",
>                     __func__, phylink_an_mode_str(pl->act_link_an_mode),
>                     phy_modes(st.interface),
>                     phy_rate_matching_to_str(st.rate_matching),
>                     __ETHTOOL_LINK_MODE_MASK_NBITS, st.advertising,
>                     st.pause);
>
>         pl->mac_ops->mac_config(pl->config, pl->act_link_an_mode, &st);
>
> and you would've noticed in your debug print that e.g. state->speed and
> state->duplex are both always -1, and thus are not useful. Note also the
> debugging that phylink includes.
>
> Note that no other mac_config() implementations refer to state->speed
> and state->duplex. The only time drivers _write_ to these is in the
> pcs_get_state() method if they support a PCS.
>
> Therefore, I think your code is completely untested.
>

Thanks for your help. I didn't notice that since it accidentally
configures autonegotiation for me. Anyway I'll take a closer look at
relevant apis.

> I'm also concerned about the SMI locking, which looks to me like you
> haven't realised that the MDIO bus layer has locking which guarantees
> that all invocations of the MDIO bus read* and write* methods are
> serialised.

The device takes two sequential u16 MDIO r/w into one op on its
internal 32b regs, so we need to serialise SMI ops to avoid race
conditions. Strictly speaking only locking the target phyaddr is
needed, but I think it won't hurt to lock the MDIO bus as long as I
don't perform busy wait while holding the bus lock.

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [net-next v5 1/3] dt-bindings: net: dsa: yt921x: Add Motorcomm YT921x switch support
  2025-08-20  7:54 ` [net-next v5 1/3] dt-bindings: net: dsa: yt921x: Add Motorcomm YT921x switch support David Yang
  2025-08-21  7:22   ` Krzysztof Kozlowski
@ 2025-08-21 12:20   ` Andrew Lunn
  2025-08-21 12:50     ` Yangfl
  1 sibling, 1 reply; 15+ messages in thread
From: Andrew Lunn @ 2025-08-21 12:20 UTC (permalink / raw)
  To: David Yang
  Cc: netdev, Vladimir Oltean, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Simon Horman, Russell King, devicetree,
	linux-kernel

> +        switch@1d {
> +            compatible = "motorcomm,yt9215";
> +            /* default 0x1d, alternate 0x0 */
> +            reg = <0x1d>;

Just curious, what does alternative 0x0 mean? Does this switch have
only one strapping pin for address, so it either uses address 0x1d or
0x0?

	Andrew

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [net-next v5 2/3] net: dsa: tag_yt921x: add support for Motorcomm YT921x tags
  2025-08-20  7:54 ` [net-next v5 2/3] net: dsa: tag_yt921x: add support for Motorcomm YT921x tags David Yang
@ 2025-08-21 12:23   ` Andrew Lunn
  0 siblings, 0 replies; 15+ messages in thread
From: Andrew Lunn @ 2025-08-21 12:23 UTC (permalink / raw)
  To: David Yang
  Cc: netdev, Vladimir Oltean, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Simon Horman, Russell King, devicetree,
	linux-kernel

On Wed, Aug 20, 2025 at 03:54:15PM +0800, David Yang wrote:
> Add support for Motorcomm YT921x tags, which includes a proper
> configurable ethertype field (default to 0x9988).
> 
> Signed-off-by: David Yang <mmyangfl@gmail.com>

Reviewed-by: Andrew Lunn <andrew@lunn.ch>

    Andrew

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [net-next v5 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x
  2025-08-20  7:54 ` [net-next v5 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x David Yang
  2025-08-21  8:30   ` Russell King (Oracle)
@ 2025-08-21 12:41   ` Andrew Lunn
  1 sibling, 0 replies; 15+ messages in thread
From: Andrew Lunn @ 2025-08-21 12:41 UTC (permalink / raw)
  To: David Yang
  Cc: netdev, Vladimir Oltean, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Simon Horman, Russell King, devicetree,
	linux-kernel

> +#define should_unreachable() \
> +	pr_err("%s: !!unreachable %d, please report a bug!\n", \
> +	       __func__, __LINE__)
> +#define consume_retval(res) do { \
> +	if (unlikely(res)) \
> +		pr_err("%s: %i\n", __func__, (res)); \
> +} while (0)
> +

> +static int
> +yt921x_vid_del(struct yt921x_priv *priv, int port, u16 vid)
> +{
> +	struct yt921x_port *pp = &priv->ports[port];
> +	u32 mask;
> +	u32 ctrl;
> +	u32 val;
> +	int res;

...

> +
> +	if (pp->vids_cnt <= 0)
> +		should_unreachable();
> +	else
> +		pp->vids_cnt--;
> +	return 0;

Have you seen other drivers do this? If you are doing something which
other drivers don't do, it is probably wrong.

What you are more likely to see is WARN_ON(pp->vids_cnt <= 0); You
then get a stack trace, to help debug what happened. Kernel developers
know what WARN_ON() does, so it is easy to
understand. should_unreachable() is unique, it is unclear what it
does, making it harder to understand and review.

> +static void
> +yt921x_dsa_port_bridge_leave(struct dsa_switch *ds, int port,
> +			     struct dsa_bridge bridge)
> +{
> +	struct yt921x_priv *priv = to_yt921x_priv(ds);
> +	struct device *dev = to_device(priv);
> +	u16 ports_mask;
> +	int res;
> +
> +	ports_mask = dsa_bridge_ports(ds, bridge.dev);
> +
> +	dev_dbg(dev, "%s: port %d, mask 0x%x\n", __func__, port, ports_mask);
> +
> +	ports_mask |= priv->cpu_ports_mask;
> +
> +	yt921x_smi_acquire(priv);
> +	res = yt921x_bridge_force(priv, ports_mask);
> +	yt921x_smi_release(priv);
> +
> +	consume_retval(res);
> +}

And this is the same. Every driver you look at would just have the if
statement, not a macro.

In order to make drivers easy to maintain, easier to review, easy to
debug, they should all look similar.

	Andrew

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [net-next v5 1/3] dt-bindings: net: dsa: yt921x: Add Motorcomm YT921x switch support
  2025-08-21 12:20   ` Andrew Lunn
@ 2025-08-21 12:50     ` Yangfl
  2025-08-21 13:42       ` Andrew Lunn
  0 siblings, 1 reply; 15+ messages in thread
From: Yangfl @ 2025-08-21 12:50 UTC (permalink / raw)
  To: Andrew Lunn
  Cc: netdev, Vladimir Oltean, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Simon Horman, Russell King, devicetree,
	linux-kernel

On Thu, Aug 21, 2025 at 8:41 PM Andrew Lunn <andrew@lunn.ch> wrote:
>
> > +        switch@1d {
> > +            compatible = "motorcomm,yt9215";
> > +            /* default 0x1d, alternate 0x0 */
> > +            reg = <0x1d>;
>
> Just curious, what does alternative 0x0 mean? Does this switch have
> only one strapping pin for address, so it either uses address 0x1d or
> 0x0?
>
>         Andrew

Yes. I've seen this approach on other chips (offering two MDIO
phyaddrs), so this should be a common practice.

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [net-next v5 1/3] dt-bindings: net: dsa: yt921x: Add Motorcomm YT921x switch support
  2025-08-21 12:50     ` Yangfl
@ 2025-08-21 13:42       ` Andrew Lunn
  0 siblings, 0 replies; 15+ messages in thread
From: Andrew Lunn @ 2025-08-21 13:42 UTC (permalink / raw)
  To: Yangfl
  Cc: netdev, Vladimir Oltean, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Simon Horman, Russell King, devicetree,
	linux-kernel

On Thu, Aug 21, 2025 at 08:50:18PM +0800, Yangfl wrote:
> On Thu, Aug 21, 2025 at 8:41 PM Andrew Lunn <andrew@lunn.ch> wrote:
> >
> > > +        switch@1d {
> > > +            compatible = "motorcomm,yt9215";
> > > +            /* default 0x1d, alternate 0x0 */
> > > +            reg = <0x1d>;
> >
> > Just curious, what does alternative 0x0 mean? Does this switch have
> > only one strapping pin for address, so it either uses address 0x1d or
> > 0x0?
> >
> >         Andrew
> 
> Yes. I've seen this approach on other chips (offering two MDIO
> phyaddrs), so this should be a common practice.

If it only supports two addresses, you could add a constrain in the
binding that reg is [0x0, 0x1d].

	Andrew

^ permalink raw reply	[flat|nested] 15+ messages in thread

end of thread, other threads:[~2025-08-21 13:42 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-08-20  7:54 [net-next v5 0/3] net: dsa: yt921x: Add support for Motorcomm YT921x David Yang
2025-08-20  7:54 ` [net-next v5 1/3] dt-bindings: net: dsa: yt921x: Add Motorcomm YT921x switch support David Yang
2025-08-21  7:22   ` Krzysztof Kozlowski
2025-08-21 12:20   ` Andrew Lunn
2025-08-21 12:50     ` Yangfl
2025-08-21 13:42       ` Andrew Lunn
2025-08-20  7:54 ` [net-next v5 2/3] net: dsa: tag_yt921x: add support for Motorcomm YT921x tags David Yang
2025-08-21 12:23   ` Andrew Lunn
2025-08-20  7:54 ` [net-next v5 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x David Yang
2025-08-21  8:30   ` Russell King (Oracle)
2025-08-21  9:25     ` Yangfl
2025-08-21 10:00       ` Russell King (Oracle)
2025-08-21 10:14         ` Russell King (Oracle)
2025-08-21 12:19           ` Yangfl
2025-08-21 12:41   ` Andrew Lunn

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).