* [PATCH net-next v6 0/3] net: dsa: yt921x: Add support for Motorcomm YT921x
@ 2025-08-24 0:51 David Yang
2025-08-24 0:51 ` [PATCH net-next v6 1/3] dt-bindings: net: dsa: yt921x: Add Motorcomm YT921x switch support David Yang
` (2 more replies)
0 siblings, 3 replies; 31+ messages in thread
From: David Yang @ 2025-08-24 0:51 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.
v5: https://lore.kernel.org/r/20250820075420.1601068-1-mmyangfl@gmail.com
- use enum for reg in dt binding
- fix phylink_mac_ops in the driver
- fix coding style
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 | 3596 +++++++++++++++++
include/net/dsa.h | 2 +
net/dsa/Kconfig | 6 +
net/dsa/Makefile | 1 +
net/dsa/tag_yt921x.c | 126 +
8 files changed, 3889 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] 31+ messages in thread
* [PATCH net-next v6 1/3] dt-bindings: net: dsa: yt921x: Add Motorcomm YT921x switch support
2025-08-24 0:51 [PATCH net-next v6 0/3] net: dsa: yt921x: Add support for Motorcomm YT921x David Yang
@ 2025-08-24 0:51 ` David Yang
2025-08-24 9:20 ` Krzysztof Kozlowski
2025-08-24 0:51 ` [PATCH net-next v6 2/3] net: dsa: tag_yt921x: add support for Motorcomm YT921x tags David Yang
2025-08-24 0:51 ` [PATCH net-next v6 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x David Yang
2 siblings, 1 reply; 31+ messages in thread
From: David Yang @ 2025-08-24 0:51 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..3f625de49910
--- /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:
+ enum: [0x0, 0x1d]
+
+ 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.
+
+required:
+ - compatible
+ - reg
+
+allOf:
+ - $ref: dsa.yaml#/$defs/ethernet-ports
+
+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 = <ð0>;
+
+ 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] 31+ messages in thread
* [PATCH net-next v6 2/3] net: dsa: tag_yt921x: add support for Motorcomm YT921x tags
2025-08-24 0:51 [PATCH net-next v6 0/3] net: dsa: yt921x: Add support for Motorcomm YT921x David Yang
2025-08-24 0:51 ` [PATCH net-next v6 1/3] dt-bindings: net: dsa: yt921x: Add Motorcomm YT921x switch support David Yang
@ 2025-08-24 0:51 ` David Yang
2025-08-25 22:15 ` Vladimir Oltean
2025-08-24 0:51 ` [PATCH net-next v6 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x David Yang
2 siblings, 1 reply; 31+ messages in thread
From: David Yang @ 2025-08-24 0:51 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>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
---
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..ab7f97367e76
--- /dev/null
+++ b/net/dsa/tag_yt921x.c
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Motorcomm YT921x Switch Extended CPU Port Tagging
+ *
+ * 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] 31+ messages in thread
* [PATCH net-next v6 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x
2025-08-24 0:51 [PATCH net-next v6 0/3] net: dsa: yt921x: Add support for Motorcomm YT921x David Yang
2025-08-24 0:51 ` [PATCH net-next v6 1/3] dt-bindings: net: dsa: yt921x: Add Motorcomm YT921x switch support David Yang
2025-08-24 0:51 ` [PATCH net-next v6 2/3] net: dsa: tag_yt921x: add support for Motorcomm YT921x tags David Yang
@ 2025-08-24 0:51 ` David Yang
2025-08-24 8:42 ` Russell King (Oracle)
` (6 more replies)
2 siblings, 7 replies; 31+ messages in thread
From: David Yang @ 2025-08-24 0:51 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 | 3596 ++++++++++++++++++++++++++++++++++++++
3 files changed, 3604 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..9296def2b917
--- /dev/null
+++ b/drivers/net/dsa/yt921x.c
@@ -0,0 +1,3596 @@
+// 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_PORTn_TEST(port) BIT((port) - 3)
+#define YT921X_SGMII_CTRL_PORTn(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_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_10000 YT921X_SGMII_SPEED(3)
+#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_10000 YT921X_PORT_SPEED(3)
+#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_10000 YT921X_MDIO_POLLING_SPEED(3)
+#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_PORTn(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)
+
+/******** 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_clear_bits(struct yt921x_priv *priv, u32 reg, u32 mask)
+{
+ return yt921x_smi_update_bits(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)
+ /* Do not use WARN here; we are likely to be the victim, not
+ * the cause of the problem
+ */
+ 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;
+ struct device *dev = to_device(priv);
+ int res;
+
+ res = yt921x_read_mib_burst(priv, port);
+ if (res)
+ dev_dbg(dev, "%s: %i\n", __func__, 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;
+ struct device *dev = to_device(priv);
+ int res;
+
+ res = yt921x_read_mib_burst(priv, port);
+ if (res)
+ dev_dbg(dev, "%s: %i\n", __func__, 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);
+ struct device *dev = to_device(priv);
+ 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);
+
+ if (res)
+ dev_dbg(dev, "%s: %i\n", __func__, 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);
+
+ if (res)
+ dev_dbg(dev, "%s: %i\n", __func__, 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 (!WARN_ON(pp->vids_cnt <= 0))
+ 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 (!WARN_ON(pp->vids_cnt >= VLAN_N_VID - 1))
+ 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);
+
+ if (res)
+ dev_dbg(dev, "%s: %i\n", __func__, 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);
+
+ if (res)
+ dev_dbg(dev, "%s: %i\n", __func__, 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_down(struct yt921x_priv *priv, int port)
+{
+ u32 mask;
+ u32 ctrl;
+ int res;
+
+ ctrl = YT921X_PORT_LINK | YT921X_PORT_RX_MAC_EN | YT921X_PORT_TX_MAC_EN;
+ res = yt921x_smi_clear_bits(priv, YT921X_PORTn_CTRL(port), ctrl);
+ if (res)
+ return res;
+
+ if (yt921x_port_is_external(port)) {
+ ctrl = YT921X_SGMII_LINK;
+ res = yt921x_smi_clear_bits(priv, YT921X_SGMIIn(port), ctrl);
+ if (res)
+ return res;
+
+ mask = YT921X_XMII_LINK;
+ res = yt921x_smi_clear_bits(priv, YT921X_XMIIn(port), ctrl);
+ if (res)
+ return res;
+ }
+
+ return 0;
+}
+
+static int
+yt921x_port_up(struct yt921x_priv *priv, int port, unsigned int mode,
+ phy_interface_t interface, int speed, int duplex,
+ bool tx_pause, bool rx_pause)
+{
+ struct device *dev = to_device(priv);
+ u32 mask;
+ u32 ctrl;
+ int res;
+
+ switch (speed) {
+ case SPEED_10:
+ ctrl = YT921X_PORT_SPEED_10;
+ break;
+ case SPEED_100:
+ ctrl = YT921X_PORT_SPEED_100;
+ break;
+ case SPEED_1000:
+ ctrl = YT921X_PORT_SPEED_1000;
+ break;
+ case SPEED_10000:
+ ctrl = YT921X_PORT_SPEED_10000;
+ break;
+ case SPEED_2500:
+ ctrl = YT921X_PORT_SPEED_2500;
+ break;
+ default:
+ if (speed >= 0)
+ dev_err(dev, "Unsupported speed %d\n", speed);
+ break;
+ }
+ if (duplex == DUPLEX_FULL)
+ ctrl |= YT921X_PORT_DUPLEX_FULL;
+ if (tx_pause)
+ ctrl |= YT921X_PORT_TX_PAUSE;
+ if (rx_pause)
+ ctrl |= YT921X_PORT_RX_PAUSE;
+ ctrl |= YT921X_PORT_LINK | YT921X_PORT_RX_MAC_EN |
+ YT921X_PORT_TX_MAC_EN;
+ res = yt921x_smi_write(priv, YT921X_PORTn_CTRL(port), ctrl);
+ if (res)
+ return res;
+
+ if (yt921x_port_is_external(port)) {
+ mask = YT921X_SGMII_SPEED_M;
+ switch (speed) {
+ case SPEED_10:
+ ctrl = YT921X_SGMII_SPEED_10;
+ break;
+ case SPEED_100:
+ ctrl = YT921X_SGMII_SPEED_100;
+ break;
+ case SPEED_1000:
+ ctrl = YT921X_SGMII_SPEED_1000;
+ break;
+ case SPEED_10000:
+ ctrl = YT921X_SGMII_SPEED_10000;
+ break;
+ case SPEED_2500:
+ ctrl = YT921X_SGMII_SPEED_2500;
+ break;
+ }
+ mask |= YT921X_SGMII_DUPLEX_FULL;
+ if (duplex == DUPLEX_FULL)
+ ctrl |= YT921X_SGMII_DUPLEX_FULL;
+ mask |= YT921X_SGMII_TX_PAUSE;
+ if (tx_pause)
+ ctrl |= YT921X_SGMII_TX_PAUSE;
+ mask |= YT921X_SGMII_RX_PAUSE;
+ if (rx_pause)
+ ctrl |= YT921X_SGMII_RX_PAUSE;
+ mask |= YT921X_SGMII_LINK;
+ ctrl |= YT921X_SGMII_LINK;
+ res = yt921x_smi_update_bits(priv, YT921X_SGMIIn(port),
+ mask, ctrl);
+ if (res)
+ return res;
+
+ ctrl = YT921X_XMII_LINK;
+ res = yt921x_smi_set_bits(priv, YT921X_XMIIn(port), ctrl);
+ if (res)
+ return res;
+
+ switch (speed) {
+ case SPEED_10:
+ ctrl = YT921X_MDIO_POLLING_SPEED_10;
+ break;
+ case SPEED_100:
+ ctrl = YT921X_MDIO_POLLING_SPEED_100;
+ break;
+ case SPEED_1000:
+ ctrl = YT921X_MDIO_POLLING_SPEED_1000;
+ break;
+ case SPEED_10000:
+ ctrl = YT921X_MDIO_POLLING_SPEED_10000;
+ break;
+ case SPEED_2500:
+ ctrl = YT921X_MDIO_POLLING_SPEED_2500;
+ break;
+ }
+ if (duplex == DUPLEX_FULL)
+ ctrl |= YT921X_MDIO_POLLING_DUPLEX_FULL;
+ ctrl |= YT921X_MDIO_POLLING_LINK;
+ res = yt921x_smi_write(priv, YT921X_MDIO_POLLINGn(port), ctrl);
+ if (res)
+ return res;
+ }
+
+ return 0;
+}
+
+static int
+yt921x_port_config(struct yt921x_priv *priv, int port, unsigned int mode,
+ phy_interface_t interface)
+{
+ struct device *dev = to_device(priv);
+ u32 mask;
+ u32 ctrl;
+ int res;
+
+ if (!yt921x_port_is_external(port)) {
+ if (interface != PHY_INTERFACE_MODE_INTERNAL) {
+ dev_err(dev, "Wrong mode %d on port %d\n",
+ interface, port);
+ return -EINVAL;
+ }
+ return 0;
+ }
+
+ switch (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_PORTn(port));
+ if (res)
+ return res;
+ res = yt921x_smi_clear_bits(priv, YT921X_XMII_CTRL,
+ YT921X_XMII_CTRL_PORTn(port));
+ if (res)
+ return res;
+
+ mask = YT921X_SGMII_MODE_M;
+ switch (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:
+ WARN_ON(1);
+ break;
+ }
+ 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:
+ case PHY_INTERFACE_MODE_INTERNAL:
+ /* TODO */
+ dev_err(dev, "Untested mode %d\n", interface);
+ return -EINVAL;
+
+ res = yt921x_smi_clear_bits(priv, YT921X_SGMII_CTRL,
+ YT921X_SGMII_CTRL_PORTn(port));
+ if (res)
+ return res;
+ res = yt921x_smi_set_bits(priv, YT921X_XMII_CTRL,
+ YT921X_XMII_CTRL_PORTn(port));
+ if (res)
+ return res;
+
+ mask = YT921X_XMII_EN | YT921X_XMII_MODE_M;
+ ctrl = YT921X_XMII_EN;
+ switch (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;
+ case PHY_INTERFACE_MODE_INTERNAL:
+ ctrl |= YT921X_XMII_MODE_DISABLE;
+ break;
+ default:
+ WARN_ON(1);
+ break;
+ }
+ res = yt921x_smi_update_bits(priv, YT921X_XMIIn(port),
+ mask, ctrl);
+ if (res)
+ return res;
+
+ /* TODO: RGMII delay */
+
+ break;
+ default:
+ WARN_ON(1);
+ 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;
+ int res;
+
+ dev_dbg(dev, "%s: port %d\n", __func__, port);
+
+ yt921x_smi_acquire(priv);
+ res = yt921x_port_down(priv, port);
+ yt921x_smi_release(priv);
+
+ if (res)
+ dev_err(dev, "%s: port %d: %i\n", __func__, 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;
+ int res;
+
+ dev_dbg(dev,
+ "%s: port %d, mode %u, interface %d, speed %d, duplex %d, "
+ "tx_pause %d, rx_pause %d\n", __func__, port, mode, interface,
+ speed, duplex, tx_pause, rx_pause);
+
+ yt921x_smi_acquire(priv);
+ res = yt921x_port_up(priv, port, mode, interface, speed, duplex,
+ tx_pause, rx_pause);
+ yt921x_smi_release(priv);
+
+ if (res)
+ dev_err(dev, "%s: port %d: %i\n", __func__, 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\n",
+ __func__, port, mode, state->interface);
+
+ yt921x_smi_acquire(priv);
+ res = yt921x_port_config(priv, port, mode, state->interface);
+ yt921x_smi_release(priv);
+ if (res)
+ dev_err(dev, "%s: port %d: %i\n", __func__, 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);
+ if (res)
+ dev_dbg(dev, "%s: %i\n", __func__, 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] 31+ messages in thread
* Re: [PATCH net-next v6 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x
2025-08-24 0:51 ` [PATCH net-next v6 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x David Yang
@ 2025-08-24 8:42 ` Russell King (Oracle)
2025-08-24 9:31 ` Yangfl
2025-08-24 15:22 ` Andrew Lunn
` (5 subsequent siblings)
6 siblings, 1 reply; 31+ messages in thread
From: Russell King (Oracle) @ 2025-08-24 8:42 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
Hi,
Thanks for fixing the major phylink implementation errors. There are
further issues that need addressing.
On Sun, Aug 24, 2025 at 08:51:11AM +0800, David Yang wrote:
> +/******** hardware definitions ********/
...
> +#define YT921X_REG_END 0x400000 /* as long as reg space is below this */
Please consider moving the above register definitions to, e.g.
drivers/net/dsa/yt921x-hw.h Also consider whether some of the below
should also be moved there.
> +#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;
> +};
Maybe consider moving the struct definitions (of which there are
several) to drivers/net/dsa/yt921x.h ?
> +/******** 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;
Here, if EEE is completely disabled, you clear the YT921X_PON_STRAP_EEE
bit...
> +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;
... and if this bit is clear, you report that EEE is unsupported by the
device - which means the device has no hardware for EEE support, and
the ethtool EEE operations will be blocked and return -EOPNOTSUPP. This
means that once all ports have EEE disabled, EEE can not be re-enabled
except through hardware reset.
Please see the code in net/dsa/user.c::dsa_user_set_eee().
> +/******** port ********/
> +
> +static int yt921x_port_down(struct yt921x_priv *priv, int port)
> +{
> + u32 mask;
> + u32 ctrl;
> + int res;
> +
> + ctrl = YT921X_PORT_LINK | YT921X_PORT_RX_MAC_EN | YT921X_PORT_TX_MAC_EN;
> + res = yt921x_smi_clear_bits(priv, YT921X_PORTn_CTRL(port), ctrl);
> + if (res)
> + return res;
> +
> + if (yt921x_port_is_external(port)) {
> + ctrl = YT921X_SGMII_LINK;
> + res = yt921x_smi_clear_bits(priv, YT921X_SGMIIn(port), ctrl);
> + if (res)
> + return res;
> +
> + mask = YT921X_XMII_LINK;
> + res = yt921x_smi_clear_bits(priv, YT921X_XMIIn(port), ctrl);
> + if (res)
> + return res;
> + }
> +
> + return 0;
> +}
> +
> +static int
> +yt921x_port_up(struct yt921x_priv *priv, int port, unsigned int mode,
> + phy_interface_t interface, int speed, int duplex,
> + bool tx_pause, bool rx_pause)
> +{
> + struct device *dev = to_device(priv);
> + u32 mask;
> + u32 ctrl;
> + int res;
> +
> + switch (speed) {
> + case SPEED_10:
> + ctrl = YT921X_PORT_SPEED_10;
> + break;
> + case SPEED_100:
> + ctrl = YT921X_PORT_SPEED_100;
> + break;
> + case SPEED_1000:
> + ctrl = YT921X_PORT_SPEED_1000;
> + break;
> + case SPEED_10000:
> + ctrl = YT921X_PORT_SPEED_10000;
> + break;
> + case SPEED_2500:
> + ctrl = YT921X_PORT_SPEED_2500;
> + break;
> + default:
> + if (speed >= 0)
> + dev_err(dev, "Unsupported speed %d\n", speed);
> + break;
> + }
> + if (duplex == DUPLEX_FULL)
> + ctrl |= YT921X_PORT_DUPLEX_FULL;
> + if (tx_pause)
> + ctrl |= YT921X_PORT_TX_PAUSE;
> + if (rx_pause)
> + ctrl |= YT921X_PORT_RX_PAUSE;
> + ctrl |= YT921X_PORT_LINK | YT921X_PORT_RX_MAC_EN |
> + YT921X_PORT_TX_MAC_EN;
> + res = yt921x_smi_write(priv, YT921X_PORTn_CTRL(port), ctrl);
> + if (res)
> + return res;
> +
> + if (yt921x_port_is_external(port)) {
> + mask = YT921X_SGMII_SPEED_M;
> + switch (speed) {
> + case SPEED_10:
> + ctrl = YT921X_SGMII_SPEED_10;
> + break;
> + case SPEED_100:
> + ctrl = YT921X_SGMII_SPEED_100;
> + break;
> + case SPEED_1000:
> + ctrl = YT921X_SGMII_SPEED_1000;
> + break;
> + case SPEED_10000:
> + ctrl = YT921X_SGMII_SPEED_10000;
> + break;
> + case SPEED_2500:
> + ctrl = YT921X_SGMII_SPEED_2500;
> + break;
> + }
> + mask |= YT921X_SGMII_DUPLEX_FULL;
> + if (duplex == DUPLEX_FULL)
> + ctrl |= YT921X_SGMII_DUPLEX_FULL;
> + mask |= YT921X_SGMII_TX_PAUSE;
> + if (tx_pause)
> + ctrl |= YT921X_SGMII_TX_PAUSE;
> + mask |= YT921X_SGMII_RX_PAUSE;
> + if (rx_pause)
> + ctrl |= YT921X_SGMII_RX_PAUSE;
> + mask |= YT921X_SGMII_LINK;
> + ctrl |= YT921X_SGMII_LINK;
> + res = yt921x_smi_update_bits(priv, YT921X_SGMIIn(port),
> + mask, ctrl);
> + if (res)
> + return res;
> +
> + ctrl = YT921X_XMII_LINK;
> + res = yt921x_smi_set_bits(priv, YT921X_XMIIn(port), ctrl);
> + if (res)
> + return res;
> +
> + switch (speed) {
> + case SPEED_10:
> + ctrl = YT921X_MDIO_POLLING_SPEED_10;
> + break;
> + case SPEED_100:
> + ctrl = YT921X_MDIO_POLLING_SPEED_100;
> + break;
> + case SPEED_1000:
> + ctrl = YT921X_MDIO_POLLING_SPEED_1000;
> + break;
> + case SPEED_10000:
> + ctrl = YT921X_MDIO_POLLING_SPEED_10000;
> + break;
> + case SPEED_2500:
> + ctrl = YT921X_MDIO_POLLING_SPEED_2500;
> + break;
> + }
> + if (duplex == DUPLEX_FULL)
> + ctrl |= YT921X_MDIO_POLLING_DUPLEX_FULL;
> + ctrl |= YT921X_MDIO_POLLING_LINK;
> + res = yt921x_smi_write(priv, YT921X_MDIO_POLLINGn(port), ctrl);
> + if (res)
> + return res;
> + }
> +
> + return 0;
> +}
> +
> +static int
> +yt921x_port_config(struct yt921x_priv *priv, int port, unsigned int mode,
> + phy_interface_t interface)
> +{
> + struct device *dev = to_device(priv);
> + u32 mask;
> + u32 ctrl;
> + int res;
> +
> + if (!yt921x_port_is_external(port)) {
> + if (interface != PHY_INTERFACE_MODE_INTERNAL) {
> + dev_err(dev, "Wrong mode %d on port %d\n",
> + interface, port);
> + return -EINVAL;
> + }
> + return 0;
> + }
> +
> + switch (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_PORTn(port));
> + if (res)
> + return res;
> + res = yt921x_smi_clear_bits(priv, YT921X_XMII_CTRL,
> + YT921X_XMII_CTRL_PORTn(port));
> + if (res)
> + return res;
> +
> + mask = YT921X_SGMII_MODE_M;
> + switch (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:
> + WARN_ON(1);
> + break;
> + }
> + 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:
> + case PHY_INTERFACE_MODE_INTERNAL:
> + /* TODO */
> + dev_err(dev, "Untested mode %d\n", interface);
> + return -EINVAL;
> +
> + res = yt921x_smi_clear_bits(priv, YT921X_SGMII_CTRL,
> + YT921X_SGMII_CTRL_PORTn(port));
> + if (res)
> + return res;
> + res = yt921x_smi_set_bits(priv, YT921X_XMII_CTRL,
> + YT921X_XMII_CTRL_PORTn(port));
> + if (res)
> + return res;
> +
> + mask = YT921X_XMII_EN | YT921X_XMII_MODE_M;
> + ctrl = YT921X_XMII_EN;
> + switch (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;
> + case PHY_INTERFACE_MODE_INTERNAL:
> + ctrl |= YT921X_XMII_MODE_DISABLE;
> + break;
> + default:
> + WARN_ON(1);
> + break;
> + }
> + res = yt921x_smi_update_bits(priv, YT921X_XMIIn(port),
> + mask, ctrl);
> + if (res)
> + return res;
> +
> + /* TODO: RGMII delay */
> +
> + break;
> + default:
> + WARN_ON(1);
> + 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;
> + int res;
> +
> + dev_dbg(dev, "%s: port %d\n", __func__, port);
> +
> + yt921x_smi_acquire(priv);
> + res = yt921x_port_down(priv, port);
> + yt921x_smi_release(priv);
> +
> + if (res)
> + dev_err(dev, "%s: port %d: %i\n", __func__, 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;
> + int res;
> +
> + dev_dbg(dev,
> + "%s: port %d, mode %u, interface %d, speed %d, duplex %d, "
> + "tx_pause %d, rx_pause %d\n", __func__, port, mode, interface,
> + speed, duplex, tx_pause, rx_pause);
> +
> + yt921x_smi_acquire(priv);
> + res = yt921x_port_up(priv, port, mode, interface, speed, duplex,
> + tx_pause, rx_pause);
> + yt921x_smi_release(priv);
> +
> + if (res)
> + dev_err(dev, "%s: port %d: %i\n", __func__, 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\n",
> + __func__, port, mode, state->interface);
> +
> + yt921x_smi_acquire(priv);
> + res = yt921x_port_config(priv, port, mode, state->interface);
> + yt921x_smi_release(priv);
> + if (res)
> + dev_err(dev, "%s: port %d: %i\n", __func__, 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;
You can set this at the top of the function, which will eliminate the
need for the final "else { return; }" and the need for the weird
looking "config->mac_capabilities = 0;" in the internal case. Don't
forget to update the external case to use |=.
This is safe because if config->supported_interfaces is empty, phylink
will report an error.
> +static void yt921x_mdio_shutdown(struct mdio_device *mdiodev)
> +{
> + struct device *dev = &mdiodev->dev;
> + struct yt921x_priv *priv = dev_get_drvdata(dev);
struct yt921x_priv *priv = mdiodev_get_drvdata(mdiodev0;
> + 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 yt921x_priv *priv = mdiodev_get_drvdata(mdiodev0;
> + 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);
mdiodev_set_drvdata(mdiodev, priv);
--
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] 31+ messages in thread
* Re: [PATCH net-next v6 1/3] dt-bindings: net: dsa: yt921x: Add Motorcomm YT921x switch support
2025-08-24 0:51 ` [PATCH net-next v6 1/3] dt-bindings: net: dsa: yt921x: Add Motorcomm YT921x switch support David Yang
@ 2025-08-24 9:20 ` Krzysztof Kozlowski
2025-08-24 9:25 ` Yangfl
0 siblings, 1 reply; 31+ messages in thread
From: Krzysztof Kozlowski @ 2025-08-24 9:20 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 Sun, Aug 24, 2025 at 08:51:09AM +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.
>
> Signed-off-by: David Yang <mmyangfl@gmail.com>
> ---
<form letter>
This is a friendly reminder during the review process.
It looks like you received a tag and forgot to add it.
If you do not know the process, here is a short explanation:
Please add Acked-by/Reviewed-by/Tested-by tags when posting new
versions of patchset, under or above your Signed-off-by tag, unless
patch changed significantly (e.g. new properties added to the DT
bindings). Tag is "received", when provided in a message replied to you
on the mailing list. Tools like b4 can help here. However, there's no
need to repost patches *only* to add the tags. The upstream maintainer
will do that for tags received on the version they apply.
Please read:
https://elixir.bootlin.com/linux/v6.12-rc3/source/Documentation/process/submitting-patches.rst#L577
*If a tag was not added on purpose, please state why* and what changed.
</form letter>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH net-next v6 1/3] dt-bindings: net: dsa: yt921x: Add Motorcomm YT921x switch support
2025-08-24 9:20 ` Krzysztof Kozlowski
@ 2025-08-24 9:25 ` Yangfl
2025-08-24 17:16 ` Krzysztof Kozlowski
0 siblings, 1 reply; 31+ messages in thread
From: Yangfl @ 2025-08-24 9:25 UTC (permalink / raw)
To: Krzysztof Kozlowski
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 Sun, Aug 24, 2025 at 5:20 PM Krzysztof Kozlowski <krzk@kernel.org> wrote:
>
> On Sun, Aug 24, 2025 at 08:51:09AM +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.
> >
> > Signed-off-by: David Yang <mmyangfl@gmail.com>
> > ---
>
> <form letter>
> This is a friendly reminder during the review process.
>
> It looks like you received a tag and forgot to add it.
>
> If you do not know the process, here is a short explanation:
> Please add Acked-by/Reviewed-by/Tested-by tags when posting new
> versions of patchset, under or above your Signed-off-by tag, unless
> patch changed significantly (e.g. new properties added to the DT
> bindings). Tag is "received", when provided in a message replied to you
> on the mailing list. Tools like b4 can help here. However, there's no
> need to repost patches *only* to add the tags. The upstream maintainer
> will do that for tags received on the version they apply.
>
> Please read:
> https://elixir.bootlin.com/linux/v6.12-rc3/source/Documentation/process/submitting-patches.rst#L577
>
> *If a tag was not added on purpose, please state why* and what changed.
> </form letter>
>
>
> Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
>
> Best regards,
> Krzysztof
>
Thanks.
> - use enum for reg in dt binding
I made a change in dt binding. If you are fine with that change, I'll
add the tag in the following versions (if any).
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH net-next v6 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x
2025-08-24 8:42 ` Russell King (Oracle)
@ 2025-08-24 9:31 ` Yangfl
0 siblings, 0 replies; 31+ messages in thread
From: Yangfl @ 2025-08-24 9:31 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 Sun, Aug 24, 2025 at 4:42 PM Russell King (Oracle)
<linux@armlinux.org.uk> wrote:
>
> Hi,
>
> Thanks for fixing the major phylink implementation errors. There are
> further issues that need addressing.
>
> On Sun, Aug 24, 2025 at 08:51:11AM +0800, David Yang wrote:
> > +/******** hardware definitions ********/
>
> ...
>
> > +#define YT921X_REG_END 0x400000 /* as long as reg space is below this */
>
> Please consider moving the above register definitions to, e.g.
> drivers/net/dsa/yt921x-hw.h Also consider whether some of the below
> should also be moved there.
>
> > +#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;
> > +};
>
> Maybe consider moving the struct definitions (of which there are
> several) to drivers/net/dsa/yt921x.h ?
I checked other drivers under dsa and most of them use a single header
for both register and struct definitions, so it might be a more common
practice?
>
> > +/******** 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;
>
> Here, if EEE is completely disabled, you clear the YT921X_PON_STRAP_EEE
> bit...
>
> > +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;
>
> ... and if this bit is clear, you report that EEE is unsupported by the
> device - which means the device has no hardware for EEE support, and
> the ethtool EEE operations will be blocked and return -EOPNOTSUPP. This
> means that once all ports have EEE disabled, EEE can not be re-enabled
> except through hardware reset.
>
> Please see the code in net/dsa/user.c::dsa_user_set_eee().
priv->pon_strap_cap is read from a different readonly register, and
cached globally at startup (yt921x_detect) to reduce SMI ops. It
should not affect reported EEE status.
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH net-next v6 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x
2025-08-24 0:51 ` [PATCH net-next v6 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x David Yang
2025-08-24 8:42 ` Russell King (Oracle)
@ 2025-08-24 15:22 ` Andrew Lunn
2025-08-24 15:26 ` Andrew Lunn
` (4 subsequent siblings)
6 siblings, 0 replies; 31+ messages in thread
From: Andrew Lunn @ 2025-08-24 15:22 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
> +
> +/******** hardware definitions ********/
> +/******** driver definitions ********/
> +/******** smi ********/
Comments like this don't add any value. And sometimes you need to move
functions around because we don't allow forward references.
Andrew
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH net-next v6 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x
2025-08-24 0:51 ` [PATCH net-next v6 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x David Yang
2025-08-24 8:42 ` Russell King (Oracle)
2025-08-24 15:22 ` Andrew Lunn
@ 2025-08-24 15:26 ` Andrew Lunn
2025-08-24 15:34 ` Andrew Lunn
` (3 subsequent siblings)
6 siblings, 0 replies; 31+ messages in thread
From: Andrew Lunn @ 2025-08-24 15:26 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 yt921x_port_is_internal(port) ((port) < 8)
> +#define yt921x_port_is_external(port) (8 <= (port) && (port) < 9)
> +#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))
Do we really need two sets of macros?
And is there a third state? Can a port be not internal and not
external?
Maybe the code can just use !yt921x_info_port_is_internal(info, port)
Andrew
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH net-next v6 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x
2025-08-24 0:51 ` [PATCH net-next v6 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x David Yang
` (2 preceding siblings ...)
2025-08-24 15:26 ` Andrew Lunn
@ 2025-08-24 15:34 ` Andrew Lunn
2025-08-24 15:51 ` Andrew Lunn
` (2 subsequent siblings)
6 siblings, 0 replies; 31+ messages in thread
From: Andrew Lunn @ 2025-08-24 15:34 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
> +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);
> +}
What happens if priv->smi_ops->acquire and priv->smi_ops->release are
not implemented? Very likely, it will mostly work, but have subtle bug
which are going to be hard to observe and find.
You want bugs to be obvious, so they are quick and easy to find. The
best way to make the bug of missing locking obvious is to jump through
a NULL pointer and get an Opps. The stack trace will make it obvious
what has happened.
> +
> +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);
I don't understand the name _burst here? Why is it called
that. Looking at other drivers, _u32 would be more common, especially
if you have functions to read a _u16, _u8 etc.
Andrew
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH net-next v6 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x
2025-08-24 0:51 ` [PATCH net-next v6 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x David Yang
` (3 preceding siblings ...)
2025-08-24 15:34 ` Andrew Lunn
@ 2025-08-24 15:51 ` Andrew Lunn
2025-08-24 16:38 ` Yangfl
2025-08-24 16:40 ` kernel test robot
2025-08-25 21:23 ` Vladimir Oltean
6 siblings, 1 reply; 31+ messages in thread
From: Andrew Lunn @ 2025-08-24 15:51 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
> +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_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;
> +}
Going back to comment from Russell in an older version:
> > 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.
You comment is partially correct, but also wrong. As you can see here,
you hold the lock for a number of read/writes, not just one u32 write
split into two MDIO bus transactions.
They way you currently do locking is error prone.
1) Are you sure you actually hold the lock on all paths?
2) Are you sure you hold the lock long enough for all code which
requires multiple reads/writes?
The mv88e6xxx driver does things differently:
Every function assigned to struct dsa_switch_ops first takes the lock,
does what needs doing, and then releases the lock just before the
return.
The lowest level read/write function does a mutex_is_locked() to test
that the lock is held. If it is not, it prints an error message.
The first part makes it easy to see the lock is held, and it makes it
clear all operations the driver is doing is covered by the lock, there
is no need worry about two threads racing.
The second part makes bugs about missing locks obvious, an error
message is printed.
Please reconsider your locking. Also, please think about, do you need
a different lock for MDIO, I2C and SPI? Do you need the current
acquire/release abstract?
Andrew
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH net-next v6 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x
2025-08-24 15:51 ` Andrew Lunn
@ 2025-08-24 16:38 ` Yangfl
2025-08-24 18:14 ` Russell King (Oracle)
2025-08-24 19:14 ` Andrew Lunn
0 siblings, 2 replies; 31+ messages in thread
From: Yangfl @ 2025-08-24 16:38 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 Sun, Aug 24, 2025 at 11:26 PM Andrew Lunn <andrew@lunn.ch> wrote:
>
> > +#define yt921x_port_is_internal(port) ((port) < 8)
> > +#define yt921x_port_is_external(port) (8 <= (port) && (port) < 9)
>
> > +#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))
>
> Do we really need two sets of macros?
>
> And is there a third state? Can a port be not internal and not
> external?
>
> Maybe the code can just use !yt921x_info_port_is_internal(info, port)
>
> Andrew
They are used in phylink_get_caps(), since I don't want to declare a
port which we know it does not exist on some chips. But the info_* set
might be inlined and removed since it is not used elsewhere.
Port 10 is dedicated to the internal MCU. Although I could not use it,
anyone familiar with the chips would know it's Port 10 that is neither
internal nor external.
On Sun, Aug 24, 2025 at 11:34 PM Andrew Lunn <andrew@lunn.ch> wrote:
>
> > +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);
> > +}
>
> What happens if priv->smi_ops->acquire and priv->smi_ops->release are
> not implemented? Very likely, it will mostly work, but have subtle bug
> which are going to be hard to observe and find.
>
> You want bugs to be obvious, so they are quick and easy to find. The
> best way to make the bug of missing locking obvious is to jump through
> a NULL pointer and get an Opps. The stack trace will make it obvious
> what has happened.
>
> > +
> > +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);
>
> I don't understand the name _burst here? Why is it called
> that. Looking at other drivers, _u32 would be more common, especially
> if you have functions to read a _u16, _u8 etc.
>
> Andrew
They are locked wrappers for their unlocked counterparts. I'd like to
name the unlocked versions __yt921x_smi_read just like __mdiobus_read,
but that was turned down in the previous version, so I have to give
the locked versions a stranger marker since we use unlocked versions
more often.
On Sun, Aug 24, 2025 at 11:51 PM Andrew Lunn <andrew@lunn.ch> wrote:
>
> > +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_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;
> > +}
>
> Going back to comment from Russell in an older version:
>
> > > 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.
>
> You comment is partially correct, but also wrong. As you can see here,
> you hold the lock for a number of read/writes, not just one u32 write
> split into two MDIO bus transactions.
>
> They way you currently do locking is error prone.
>
> 1) Are you sure you actually hold the lock on all paths?
>
> 2) Are you sure you hold the lock long enough for all code which
> requires multiple reads/writes?
>
> The mv88e6xxx driver does things differently:
>
> Every function assigned to struct dsa_switch_ops first takes the lock,
> does what needs doing, and then releases the lock just before the
> return.
>
> The lowest level read/write function does a mutex_is_locked() to test
> that the lock is held. If it is not, it prints an error message.
>
> The first part makes it easy to see the lock is held, and it makes it
> clear all operations the driver is doing is covered by the lock, there
> is no need worry about two threads racing.
>
> The second part makes bugs about missing locks obvious, an error
> message is printed.
>
> Please reconsider your locking. Also, please think about, do you need
> a different lock for MDIO, I2C and SPI? Do you need the current
> acquire/release abstract?
>
> Andrew
That's exactly what I've done: every exposed virtual function
yt921x_dsa_* and yt921x_mbus_* is guarded by an acquire/release pair
(except for few exceptions). But I might add a check for locking
status in mdio_read and mdio_write.
The driver itself does not need an explicit lock (so long as dsa
framework does not call two conflicting methods on the same port), and
holding two locks, driver lock and bus lock makes things even worse,
thus I left the acquire/release method to SMI implementations. If the
I2C / SPI / GPIO bitbang / etc interface supports native atomic
transactions, it can choose not to implement acquire/release and
leaves them NULL.
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH net-next v6 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x
2025-08-24 0:51 ` [PATCH net-next v6 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x David Yang
` (4 preceding siblings ...)
2025-08-24 15:51 ` Andrew Lunn
@ 2025-08-24 16:40 ` kernel test robot
2025-08-25 21:23 ` Vladimir Oltean
6 siblings, 0 replies; 31+ messages in thread
From: kernel test robot @ 2025-08-24 16:40 UTC (permalink / raw)
To: David Yang, netdev
Cc: oe-kbuild-all, David Yang, Andrew Lunn, Vladimir Oltean,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King,
devicetree, linux-kernel
Hi David,
kernel test robot noticed the following build warnings:
[auto build test WARNING on net-next/main]
url: https://github.com/intel-lab-lkp/linux/commits/David-Yang/dt-bindings-net-dsa-yt921x-Add-Motorcomm-YT921x-switch-support/20250824-085750
base: net-next/main
patch link: https://lore.kernel.org/r/20250824005116.2434998-4-mmyangfl%40gmail.com
patch subject: [PATCH net-next v6 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x
config: sh-allmodconfig (https://download.01.org/0day-ci/archive/20250825/202508250002.afAgRCmk-lkp@intel.com/config)
compiler: sh4-linux-gcc (GCC) 15.1.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250825/202508250002.afAgRCmk-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202508250002.afAgRCmk-lkp@intel.com/
All warnings (new ones prefixed by >>):
drivers/net/dsa/yt921x.c: In function 'yt921x_port_down':
>> drivers/net/dsa/yt921x.c:2683:13: warning: variable 'mask' set but not used [-Wunused-but-set-variable]
2683 | u32 mask;
| ^~~~
vim +/mask +2683 drivers/net/dsa/yt921x.c
2680
2681 static int yt921x_port_down(struct yt921x_priv *priv, int port)
2682 {
> 2683 u32 mask;
2684 u32 ctrl;
2685 int res;
2686
2687 ctrl = YT921X_PORT_LINK | YT921X_PORT_RX_MAC_EN | YT921X_PORT_TX_MAC_EN;
2688 res = yt921x_smi_clear_bits(priv, YT921X_PORTn_CTRL(port), ctrl);
2689 if (res)
2690 return res;
2691
2692 if (yt921x_port_is_external(port)) {
2693 ctrl = YT921X_SGMII_LINK;
2694 res = yt921x_smi_clear_bits(priv, YT921X_SGMIIn(port), ctrl);
2695 if (res)
2696 return res;
2697
2698 mask = YT921X_XMII_LINK;
2699 res = yt921x_smi_clear_bits(priv, YT921X_XMIIn(port), ctrl);
2700 if (res)
2701 return res;
2702 }
2703
2704 return 0;
2705 }
2706
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH net-next v6 1/3] dt-bindings: net: dsa: yt921x: Add Motorcomm YT921x switch support
2025-08-24 9:25 ` Yangfl
@ 2025-08-24 17:16 ` Krzysztof Kozlowski
2025-08-25 2:39 ` Yangfl
0 siblings, 1 reply; 31+ messages in thread
From: Krzysztof Kozlowski @ 2025-08-24 17:16 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, Russell King,
devicetree, linux-kernel
On 24/08/2025 11:25, Yangfl wrote:
> On Sun, Aug 24, 2025 at 5:20 PM Krzysztof Kozlowski <krzk@kernel.org> wrote:
>>
>> On Sun, Aug 24, 2025 at 08:51:09AM +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.
>>>
>>> Signed-off-by: David Yang <mmyangfl@gmail.com>
>>> ---
>>
>> <form letter>
>> This is a friendly reminder during the review process.
>>
>> It looks like you received a tag and forgot to add it.
>>
>> If you do not know the process, here is a short explanation:
>> Please add Acked-by/Reviewed-by/Tested-by tags when posting new
>> versions of patchset, under or above your Signed-off-by tag, unless
>> patch changed significantly (e.g. new properties added to the DT
>> bindings). Tag is "received", when provided in a message replied to you
>> on the mailing list. Tools like b4 can help here. However, there's no
>> need to repost patches *only* to add the tags. The upstream maintainer
>> will do that for tags received on the version they apply.
>>
>> Please read:
>> https://elixir.bootlin.com/linux/v6.12-rc3/source/Documentation/process/submitting-patches.rst#L577
>>
>> *If a tag was not added on purpose, please state why* and what changed.
>> </form letter>
>>
>>
>> Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
>>
>> Best regards,
>> Krzysztof
>>
>
> Thanks.
>
>> - use enum for reg in dt binding
>
> I made a change in dt binding. If you are fine with that change, I'll
> add the tag in the following versions (if any).
Cover letter must state the reason.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH net-next v6 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x
2025-08-24 16:38 ` Yangfl
@ 2025-08-24 18:14 ` Russell King (Oracle)
2025-08-24 19:16 ` Andrew Lunn
2025-08-25 14:14 ` Yangfl
2025-08-24 19:14 ` Andrew Lunn
1 sibling, 2 replies; 31+ messages in thread
From: Russell King (Oracle) @ 2025-08-24 18:14 UTC (permalink / raw)
To: Yangfl
Cc: Andrew Lunn, netdev, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Simon Horman, devicetree,
linux-kernel
On Mon, Aug 25, 2025 at 12:38:20AM +0800, Yangfl wrote:
> They are used in phylink_get_caps(), since I don't want to declare a
> port which we know it does not exist on some chips. But the info_* set
> might be inlined and removed since it is not used elsewhere.
The problem is... if you have a port in 0..N that DSA thinks should be
used, but is neither internal or external, DSA's initialisation of it
will fail, because without any caps declared for it, phylink_create()
will return an error, causing dsa_port_phylink_create() to fail,
dsa_shared_port_phylink_register() or dsa_user_phy_setup(),
dsa_shared_port_link_register_of() or dsa_user_create()... etc. It
eventually gets propagated up causing the entire switch probe to fail.
Again... read the code!
> > I don't understand the name _burst here? Why is it called
> > that. Looking at other drivers, _u32 would be more common, especially
> > if you have functions to read a _u16, _u8 etc.
>
> They are locked wrappers for their unlocked counterparts. I'd like to
> name the unlocked versions __yt921x_smi_read just like __mdiobus_read,
> but that was turned down in the previous version, so I have to give
> the locked versions a stranger marker since we use unlocked versions
> more often.
Who turned it down, and what reason did they give, given that it's an
established pattern in the phylib, mdiobus and mdiodev APIs.
--
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] 31+ messages in thread
* Re: [PATCH net-next v6 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x
2025-08-24 16:38 ` Yangfl
2025-08-24 18:14 ` Russell King (Oracle)
@ 2025-08-24 19:14 ` Andrew Lunn
1 sibling, 0 replies; 31+ messages in thread
From: Andrew Lunn @ 2025-08-24 19:14 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
> Port 10 is dedicated to the internal MCU. Although I could not use it,
> anyone familiar with the chips would know it's Port 10 that is neither
> internal nor external.
The mv88e639X family has an internal Z80 CPU and its own port on the
switch. You will see code like:
.num_ports = 11, /* 10 + Z80 */
.num_internal_phys = 8,
It is considered external, in that there is no internal PHY attached
In theory you could list it in DT, to instantiate the port, and then i
guess bad things to happen :-(
The good thing about this is, if you actually try use both the Z80 and
DSA to manage the switch, bad things are going to happen anyway.
So i would say there is some flexibility here, given how other drivers
handle this.
Andrew
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH net-next v6 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x
2025-08-24 18:14 ` Russell King (Oracle)
@ 2025-08-24 19:16 ` Andrew Lunn
2025-08-25 14:14 ` Yangfl
1 sibling, 0 replies; 31+ messages in thread
From: Andrew Lunn @ 2025-08-24 19:16 UTC (permalink / raw)
To: Russell King (Oracle)
Cc: Yangfl, netdev, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Simon Horman, devicetree, linux-kernel
> > They are locked wrappers for their unlocked counterparts. I'd like to
> > name the unlocked versions __yt921x_smi_read just like __mdiobus_read,
> > but that was turned down in the previous version, so I have to give
> > the locked versions a stranger marker since we use unlocked versions
> > more often.
>
> Who turned it down, and what reason did they give, given that it's an
> established pattern in the phylib, mdiobus and mdiodev APIs.
I did. In general, DSA drivers don't use __ for their own functions,
but there are exceptions.
I also think this will all go away once the current problems with
locking in this driver are solved.
Andrew
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH net-next v6 1/3] dt-bindings: net: dsa: yt921x: Add Motorcomm YT921x switch support
2025-08-24 17:16 ` Krzysztof Kozlowski
@ 2025-08-25 2:39 ` Yangfl
2025-08-25 7:37 ` Krzysztof Kozlowski
0 siblings, 1 reply; 31+ messages in thread
From: Yangfl @ 2025-08-25 2:39 UTC (permalink / raw)
To: Krzysztof Kozlowski
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 Mon, Aug 25, 2025 at 2:09 AM Krzysztof Kozlowski <krzk@kernel.org> wrote:
>
> On 24/08/2025 11:25, Yangfl wrote:
> > On Sun, Aug 24, 2025 at 5:20 PM Krzysztof Kozlowski <krzk@kernel.org> wrote:
> >>
> >> On Sun, Aug 24, 2025 at 08:51:09AM +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.
> >>>
> >>> Signed-off-by: David Yang <mmyangfl@gmail.com>
> >>> ---
> >>
> >> <form letter>
> >> This is a friendly reminder during the review process.
> >>
> >> It looks like you received a tag and forgot to add it.
> >>
> >> If you do not know the process, here is a short explanation:
> >> Please add Acked-by/Reviewed-by/Tested-by tags when posting new
> >> versions of patchset, under or above your Signed-off-by tag, unless
> >> patch changed significantly (e.g. new properties added to the DT
> >> bindings). Tag is "received", when provided in a message replied to you
> >> on the mailing list. Tools like b4 can help here. However, there's no
> >> need to repost patches *only* to add the tags. The upstream maintainer
> >> will do that for tags received on the version they apply.
> >>
> >> Please read:
> >> https://elixir.bootlin.com/linux/v6.12-rc3/source/Documentation/process/submitting-patches.rst#L577
> >>
> >> *If a tag was not added on purpose, please state why* and what changed.
> >> </form letter>
> >>
> >>
> >> Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
> >>
> >> Best regards,
> >> Krzysztof
> >>
> >
> > Thanks.
> >
> >> - use enum for reg in dt binding
> >
> > I made a change in dt binding. If you are fine with that change, I'll
> > add the tag in the following versions (if any).
>
>
> Cover letter must state the reason.
>
>
> Best regards,
> Krzysztof
as requested in the previous version
https://lore.kernel.org/r/f76df98e-f743-4dc2-9f10-93b97f69addb@lunn.ch
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH net-next v6 1/3] dt-bindings: net: dsa: yt921x: Add Motorcomm YT921x switch support
2025-08-25 2:39 ` Yangfl
@ 2025-08-25 7:37 ` Krzysztof Kozlowski
0 siblings, 0 replies; 31+ messages in thread
From: Krzysztof Kozlowski @ 2025-08-25 7:37 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, Russell King,
devicetree, linux-kernel
On 25/08/2025 04:39, Yangfl wrote:
> On Mon, Aug 25, 2025 at 2:09 AM Krzysztof Kozlowski <krzk@kernel.org> wrote:
>>
>> On 24/08/2025 11:25, Yangfl wrote:
>>> On Sun, Aug 24, 2025 at 5:20 PM Krzysztof Kozlowski <krzk@kernel.org> wrote:
>>>>
>>>> On Sun, Aug 24, 2025 at 08:51:09AM +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.
>>>>>
>>>>> Signed-off-by: David Yang <mmyangfl@gmail.com>
>>>>> ---
>>>>
>>>> <form letter>
>>>> This is a friendly reminder during the review process.
>>>>
>>>> It looks like you received a tag and forgot to add it.
>>>>
>>>> If you do not know the process, here is a short explanation:
>>>> Please add Acked-by/Reviewed-by/Tested-by tags when posting new
>>>> versions of patchset, under or above your Signed-off-by tag, unless
>>>> patch changed significantly (e.g. new properties added to the DT
>>>> bindings). Tag is "received", when provided in a message replied to you
>>>> on the mailing list. Tools like b4 can help here. However, there's no
>>>> need to repost patches *only* to add the tags. The upstream maintainer
>>>> will do that for tags received on the version they apply.
>>>>
>>>> Please read:
>>>> https://elixir.bootlin.com/linux/v6.12-rc3/source/Documentation/process/submitting-patches.rst#L577
>>>>
>>>> *If a tag was not added on purpose, please state why* and what changed.
>>>> </form letter>
>>>>
>>>>
>>>> Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
>>>>
>>>> Best regards,
>>>> Krzysztof
>>>>
>>>
>>> Thanks.
>>>
>>>> - use enum for reg in dt binding
>>>
>>> I made a change in dt binding. If you are fine with that change, I'll
>>> add the tag in the following versions (if any).
>>
>>
>> Cover letter must state the reason.
>>
>>
>> Best regards,
>> Krzysztof
>
> as requested in the previous version
>
> https://lore.kernel.org/r/f76df98e-f743-4dc2-9f10-93b97f69addb@lunn.ch
Hm? Where is there a statement that you remove review because of this
and that?
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH net-next v6 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x
2025-08-24 18:14 ` Russell King (Oracle)
2025-08-24 19:16 ` Andrew Lunn
@ 2025-08-25 14:14 ` Yangfl
2025-08-26 19:07 ` Vladimir Oltean
1 sibling, 1 reply; 31+ messages in thread
From: Yangfl @ 2025-08-25 14:14 UTC (permalink / raw)
To: Russell King (Oracle)
Cc: Andrew Lunn, netdev, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Simon Horman, devicetree,
linux-kernel
On Mon, Aug 25, 2025 at 2:14 AM Russell King (Oracle)
<linux@armlinux.org.uk> wrote:
>
> On Mon, Aug 25, 2025 at 12:38:20AM +0800, Yangfl wrote:
> > They are used in phylink_get_caps(), since I don't want to declare a
> > port which we know it does not exist on some chips. But the info_* set
> > might be inlined and removed since it is not used elsewhere.
>
> The problem is... if you have a port in 0..N that DSA thinks should be
> used, but is neither internal or external, DSA's initialisation of it
> will fail, because without any caps declared for it, phylink_create()
> will return an error, causing dsa_port_phylink_create() to fail,
> dsa_shared_port_phylink_register() or dsa_user_phy_setup(),
> dsa_shared_port_link_register_of() or dsa_user_create()... etc. It
> eventually gets propagated up causing the entire switch probe to fail.
>
> Again... read the code!
What would you expect when you specify Port 0 in DT when only Port 1,
3, 8 are available on the chip (YT9213NB)? Probe error.
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH net-next v6 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x
2025-08-24 0:51 ` [PATCH net-next v6 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x David Yang
` (5 preceding siblings ...)
2025-08-24 16:40 ` kernel test robot
@ 2025-08-25 21:23 ` Vladimir Oltean
2025-08-26 5:19 ` Yangfl
6 siblings, 1 reply; 31+ messages in thread
From: Vladimir Oltean @ 2025-08-25 21:23 UTC (permalink / raw)
To: David Yang
Cc: netdev, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Simon Horman, Russell King, devicetree,
linux-kernel
Hi David,
On Sun, Aug 24, 2025 at 08:51:11AM +0800, David Yang wrote:
> +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
If you have a VLAN-unaware bridge with learning enabled, this will
learn MAC addresses in VID 1. If you also have a VLAN-aware bridge with
default_pvid 1 on a different port group, this will also learn MAC
addresses in VID 1.
Are the two FDB databases isolated, or if two stations with the same MAC
address are present in both bridges, will the switch attempt to forward
packets from one bridge to another (leading to packet drops)?
This can happen, for example, with the MAC addresses of user ports,
which all inherit the conduit's MAC address when there is no address
specified in the device tree.
There is an even simpler variant of this, which is a standalone port A
and a VLAN-aware bridge port B. Port A won't have learning enabled, but
it will still try to forward packets to known destinations, which may
well be towards port B. The latter situation is very commonly seen in
the tools/testing/selftests/drivers/net/dsa/ suite, where there are
cables in loopback between switch ports. See
tools/testing/selftests/net/forwarding/README for the topology.
Have you ever run the DSA selftests for this new driver? Please include
test results in the new patch submission, so we can take a look at what
doesn't pass, and see where we should concentrate our attention.
> +
> +#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 */
Please implement:
void (*get_eth_phy_stats)(struct dsa_switch *ds, int port,
struct ethtool_eth_phy_stats *phy_stats);
void (*get_eth_mac_stats)(struct dsa_switch *ds, int port,
struct ethtool_eth_mac_stats *mac_stats);
void (*get_eth_ctrl_stats)(struct dsa_switch *ds, int port,
struct ethtool_eth_ctrl_stats *ctrl_stats);
void (*get_rmon_stats)(struct dsa_switch *ds, int port,
struct ethtool_rmon_stats *rmon_stats,
const struct ethtool_rmon_hist_range **ranges);
and only the remainder (if any) go through the unstructured .get_ethtool_stats().
> +};
> +
> +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;
> +}
You might want to schedule a periodic work item which catches 32-bit
wraparounds of those MIB counters which lack _hi/_lo, and turns them
into 64-bit values.
> +
> +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;
> + struct device *dev = to_device(priv);
> + int res;
> +
> + res = yt921x_read_mib_burst(priv, port);
> + if (res)
> + dev_dbg(dev, "%s: %i\n", __func__, res);
An error in yt921x_read_mib_burst() deserves a more severe log level
than dev_dbg() IMO. (not to mention a cleaned up error message)
> +
> + 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;
> + struct device *dev = to_device(priv);
> + int res;
> +
> + res = yt921x_read_mib_burst(priv, port);
> + if (res)
> + dev_dbg(dev, "%s: %i\n", __func__, res);
Running with CONFIG_DEBUG_ATOMIC_SLEEP=y would have shown that you are
calling a sleepable function from atomic context (ndo_get_stats64) which
risks deadlocking the kernel.
> +
> + 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 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 */
s/brother/bother/
Also, .port_max_mtu() is only called for user ports, so the comment is irrelevant.
> + 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);
> +
> + if (res)
> + dev_dbg(dev, "%s: %i\n", __func__, 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;
yt921x_smi_update_bits() would have helped here, this is a bit hard to
follow.
> +
> + /* 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;
Use NL_SET_ERR_MSG_MOD(extack) to signal this.
> + 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;
The PVID depends on the VLAN filtering state of the port, so this
function needs to update the PVID.
> + 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 (!WARN_ON(pp->vids_cnt <= 0))
> + pp->vids_cnt--;
> + return 0;
> +}
> +
> +/* Seems port_vlan_add() is not state transition method... */
I'm not sure what's a state transition method, but .port_vlan_add() is
called if and only if a VLAN is added or the flags (PVID, untagged) of
an existing VLAN are changed.
> +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 (!WARN_ON(pp->vids_cnt >= VLAN_N_VID - 1))
> + 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;
A bit confusing that the hardware is programmed with YT921X_PVID_DEFAULT
(1) but pp->pvid is 0. You might want to rename this to vlan_aware_pvid,
for clarity. Though on user ports, you can always query it directly
using br_vlan_get_pvid().
> + return 0;
> +}
> +
> +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);
Please get rid of these development leftovers. Also, for VLAN
operations, DSA already has trace events (net/dsa/trace.c).
> +
> + 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
> + */
I don't understand this comment. What did the caller decide after all?
> +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);
for_each_set_bit() would be easier to follow.
> + 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);
Doesn't this write ones outside of the YT921X_PORT_ISOLATION_BLOCKn
field? I suppose there are some reserved high-order values which should
be preserved when writing?
> + 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)
The bridge_force() function name is a bit unconventional, what does it
mean?
> +{
> + u16 targets_mask = ~ports_mask & (BIT(YT921X_PORT_NUM) - 1) &
> + ~priv->cpu_ports_mask;
What is the intention here? Assume you have one bridge with ports A and B,
and another with ports C and D.
Port D leaves the bridge, then targets_mask, the mask of ports which
will become standalone, is comprised of ports A, B and D. Why? This
sounds like a bug, ports A and B should remain untouched, as ports of an
unrelated bridge.
> + 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 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;
> +}
If this is useful, you should place this in include/net/dsa.h, and you
can use a const struct dsa_bridge pointer as the second argument.
Although I am not yet sure whether it is useful or not. I would like to
see a non-buggy implementation of bridge offloading using it, first.
> +
> +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);
> +
> + if (res)
> + dev_dbg(dev, "%s: %i\n", __func__, 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);
yuck, why is it ok to release the SMI lock here? What's the point of the
lock in the first place?
As a matter of fact, .port_fdb_add() and .port_fdb_del() are one of the
few operations which DSA emits which aren't serialized by rtnl_lock().
They can absolutely be concurrent with .port_fdb_dump(), .port_mdb_add(),
.port_mdb_del() etc. It seems like higher-level locking is needed.
> + 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 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);
> +
> + if (res)
> + dev_dbg(dev, "%s: %i\n", __func__, 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;
> +}
.port_vlan_fast_age() is dead code until dsa_port_supports_mst() returns
true. I advise you to delete it.
> +
> +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 */
learning? maybe ageing?
> + 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_port_config(struct yt921x_priv *priv, int port, unsigned int mode,
> + phy_interface_t interface)
> +{
> + struct device *dev = to_device(priv);
> + u32 mask;
> + u32 ctrl;
> + int res;
> +
> + if (!yt921x_port_is_external(port)) {
> + if (interface != PHY_INTERFACE_MODE_INTERNAL) {
> + dev_err(dev, "Wrong mode %d on port %d\n",
> + interface, port);
> + return -EINVAL;
> + }
> + return 0;
> + }
> +
> + switch (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_PORTn(port));
> + if (res)
> + return res;
> + res = yt921x_smi_clear_bits(priv, YT921X_XMII_CTRL,
> + YT921X_XMII_CTRL_PORTn(port));
> + if (res)
> + return res;
> +
> + mask = YT921X_SGMII_MODE_M;
> + switch (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:
> + WARN_ON(1);
> + break;
> + }
> + 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:
> + case PHY_INTERFACE_MODE_INTERNAL:
> + /* TODO */
> + dev_err(dev, "Untested mode %d\n", interface);
> + return -EINVAL;
Delete the code after return below, and add it when it's tested.
Though I fail to see why you'd add to supported_interfaces an
unsupported interface.
> + res = yt921x_smi_clear_bits(priv, YT921X_SGMII_CTRL,
> + YT921X_SGMII_CTRL_PORTn(port));
> + if (res)
> + return res;
> + res = yt921x_smi_set_bits(priv, YT921X_XMII_CTRL,
> + YT921X_XMII_CTRL_PORTn(port));
> + if (res)
> + return res;
> +
> + mask = YT921X_XMII_EN | YT921X_XMII_MODE_M;
> + ctrl = YT921X_XMII_EN;
> + switch (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;
> + case PHY_INTERFACE_MODE_INTERNAL:
> + ctrl |= YT921X_XMII_MODE_DISABLE;
> + break;
> + default:
> + WARN_ON(1);
> + break;
> + }
> + res = yt921x_smi_update_bits(priv, YT921X_XMIIn(port),
> + mask, ctrl);
> + if (res)
> + return res;
> +
> + /* TODO: RGMII delay */
> +
> + break;
> + default:
> + WARN_ON(1);
> + 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;
> + int res;
> +
> + dev_dbg(dev, "%s: port %d\n", __func__, port);
> +
> + yt921x_smi_acquire(priv);
> + res = yt921x_port_down(priv, port);
> + yt921x_smi_release(priv);
> +
> + if (res)
> + dev_err(dev, "%s: port %d: %i\n", __func__, port, res);
"Failed to bring port %d down: %pe\n", port, ERR_PTR(ret) seems a bit
more elegant.
> +}
> +
> +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;
What is contained in this EEPROM?
> +}
> +
> +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;
> +}
Handling .preferred_default_local_cpu_port() is not necessary if the
hardware does not support multiple CPU ports, please drop this.
> +
> +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);
> + if (res)
> + dev_dbg(dev, "%s: %i\n", __func__, res);
> + }
> +}
Handling .conduit_state_change() is not necessary. The hardware supports
a single CPU port, and nothing is done when the conduit interface becomes
inactive. priv->active_cpu_ports_mask becomes zero and the hardware is
not updated in any way. The initialization done in yt921x_dsa_setup() is
sufficient, please remove this.
> +
> +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");
dsa_tree_setup_default_cpu() ensures a CPU port exists and fails probing
otherwise. This is needlessly defensive.
> + 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;
I guess you got this snippet from mv88e6xxx_mdios_register(), which is
different from your case because it is an old driver which has to
support older device trees, before conventions changed.
Please only register the internal MDIO bus if it is present in the
device tree. This simplifies the driver and dt-bindings by having a
single way of describing ports with internal PHYs. Also, remove the
ds->user_mii_bus assignment after you do that.
> +
> + /* 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.
> + */
Wouldn't have yt921x_detect() seen that you are using the wrong chip?
Testing CPU_TAG_TPID for an unexpected value after reset seems like a
strikingly odd thing to do, and a waste of CPU cycles at boot time.
(also, priv->tag_eth_p could have been a local variable)
> + res = -EINVAL;
> + break;
> + }
> +
> + /* Unconditionally bring up one CPU port, to avoid warnings from
> + * yt921x_tag_rcv() before conduit_state_change() is called.
> + */
What warnings? (not that you shouldn't bring the CPU port up)
> + 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,
No STP state handling?
> +};
> +
> +/******** 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)
NACK for exposing a sysfs which allows user space to modify switch
registers without the driver updating its internal state.
For reading -- also not so sure. There are many higher-level debug
interfaces which you should prefer implementing before resorting to raw
register reads. If there's any latching register in hardware, user space
can mess it up. What use case do you have?
> +{
> + 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);
This is an area where we don't want diverging implementations. Please
test the dev_get_drvdata() for NULL here and in remove(), and return
without doing anything, like the other DSA drivers. You can consult
"git log" for the detailed context.
> +}
> +
> +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;
This is the default for configure_vlan_while_not_filtering, so please
remove this assignment.
> + 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);
> +}
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH net-next v6 2/3] net: dsa: tag_yt921x: add support for Motorcomm YT921x tags
2025-08-24 0:51 ` [PATCH net-next v6 2/3] net: dsa: tag_yt921x: add support for Motorcomm YT921x tags David Yang
@ 2025-08-25 22:15 ` Vladimir Oltean
2025-08-26 1:47 ` Yangfl
0 siblings, 1 reply; 31+ messages in thread
From: Vladimir Oltean @ 2025-08-25 22:15 UTC (permalink / raw)
To: David Yang
Cc: netdev, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Simon Horman, Russell King, devicetree,
linux-kernel
On Sun, Aug 24, 2025 at 08:51:10AM +0800, David Yang wrote:
> 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..ab7f97367e76
> --- /dev/null
> +++ b/net/dsa/tag_yt921x.c
> @@ -0,0 +1,126 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Motorcomm YT921x Switch Extended CPU Port Tagging
> + *
> + * 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>
Why include list.h and slab.h?
> +
> +#include "tag.h"
> +
> +#define YT921X_NAME "yt921x"
> +
> +#define YT921X_TAG_LEN 8
> +
> +#define ETH_P_YT921X 0x9988
You can add a header in include/linux/dsa/ which is shared with the
switch driver, to avoid duplicate definitions.
> +
> +#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?
The tag format sort of becomes fixed ABI as soon as user space is able
to run "cat /sys/class/net/eth0/dsa/tagging", see "yt921x", and record
it to a pcap file. Unless the EtherType bears some other meaning rather
than being a fixed value, then if you change it later to some other
value than 0x9988, you'd better also change the protocol name to
distinguish it from "yt921x".
Also, you can _not_ use yt921x_priv :: tag_eth_p, because doing so would
assume that typeof(ds->priv) == struct yt921x_priv. In principle we
would like to be able to run the tagging protocols on the dsa_loop
driver as well, which can be attached to any network interface. Very
few, if any, tagging protocol drivers don't work on dsa_loop.
> + */
> + 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);
This is generally used for integrated DSA switches, for lossless
backpressure during CPU transmission, where the conduit interface driver
is known, and has set up its queues in a special way, as a result of the
fact that it is attached to a known DSA switch (made by the same vendor).
What do you need it for, in a discrete MDIO-controlled switch?
> +
> + 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;
Use dsa_etype_header_pos_rx() and validate the CPU_TAG_TPID_TPID as well.
> +
> + /* 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 [flat|nested] 31+ messages in thread
* Re: [PATCH net-next v6 2/3] net: dsa: tag_yt921x: add support for Motorcomm YT921x tags
2025-08-25 22:15 ` Vladimir Oltean
@ 2025-08-26 1:47 ` Yangfl
2025-08-26 2:18 ` Andrew Lunn
2025-08-26 14:38 ` Vladimir Oltean
0 siblings, 2 replies; 31+ messages in thread
From: Yangfl @ 2025-08-26 1:47 UTC (permalink / raw)
To: Vladimir Oltean
Cc: netdev, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Simon Horman, Russell King, devicetree,
linux-kernel
On Tue, Aug 26, 2025 at 6:15 AM Vladimir Oltean <olteanv@gmail.com> wrote:
>
> On Sun, Aug 24, 2025 at 08:51:10AM +0800, David Yang wrote:
> > 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..ab7f97367e76
> > --- /dev/null
> > +++ b/net/dsa/tag_yt921x.c
> > @@ -0,0 +1,126 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +/*
> > + * Motorcomm YT921x Switch Extended CPU Port Tagging
> > + *
> > + * 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>
>
> Why include list.h and slab.h?
>
> > +
> > +#include "tag.h"
> > +
> > +#define YT921X_NAME "yt921x"
> > +
> > +#define YT921X_TAG_LEN 8
> > +
> > +#define ETH_P_YT921X 0x9988
>
> You can add a header in include/linux/dsa/ which is shared with the
> switch driver, to avoid duplicate definitions.
>
> > +
> > +#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?
>
> The tag format sort of becomes fixed ABI as soon as user space is able
> to run "cat /sys/class/net/eth0/dsa/tagging", see "yt921x", and record
> it to a pcap file. Unless the EtherType bears some other meaning rather
> than being a fixed value, then if you change it later to some other
> value than 0x9988, you'd better also change the protocol name to
> distinguish it from "yt921x".
>
"EtherType" here does not necessarily become EtherType; better to
think it is a key to enable port control over the switch. It could be
a dynamic random value as long as everyone gets the same value all
over the kernel, see the setup process of the switch driver. Ideally
only the remaining content of the tag should become the ABI (and is
actually enforced by the switch), but making a dynamic "EtherType" is
clearly a worse idea so I don't know how to clarify the fact...
> Also, you can _not_ use yt921x_priv :: tag_eth_p, because doing so would
> assume that typeof(ds->priv) == struct yt921x_priv. In principle we
> would like to be able to run the tagging protocols on the dsa_loop
> driver as well, which can be attached to any network interface. Very
> few, if any, tagging protocol drivers don't work on dsa_loop.
>
> > + */
> > + 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);
>
> This is generally used for integrated DSA switches, for lossless
> backpressure during CPU transmission, where the conduit interface driver
> is known, and has set up its queues in a special way, as a result of the
> fact that it is attached to a known DSA switch (made by the same vendor).
> What do you need it for, in a discrete MDIO-controlled switch?
>
> > +
> > + 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;
>
> Use dsa_etype_header_pos_rx() and validate the CPU_TAG_TPID_TPID as well.
>
See the above explanation why rx "EtherType" is not considered part of ABI.
> > +
> > + /* 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 [flat|nested] 31+ messages in thread
* Re: [PATCH net-next v6 2/3] net: dsa: tag_yt921x: add support for Motorcomm YT921x tags
2025-08-26 1:47 ` Yangfl
@ 2025-08-26 2:18 ` Andrew Lunn
2025-08-26 2:44 ` Yangfl
2025-08-26 14:38 ` Vladimir Oltean
1 sibling, 1 reply; 31+ messages in thread
From: Andrew Lunn @ 2025-08-26 2:18 UTC (permalink / raw)
To: Yangfl
Cc: Vladimir Oltean, netdev, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Simon Horman, Russell King, devicetree,
linux-kernel
> > > +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?
> >
> > The tag format sort of becomes fixed ABI as soon as user space is able
> > to run "cat /sys/class/net/eth0/dsa/tagging", see "yt921x", and record
> > it to a pcap file. Unless the EtherType bears some other meaning rather
> > than being a fixed value, then if you change it later to some other
> > value than 0x9988, you'd better also change the protocol name to
> > distinguish it from "yt921x".
> >
>
> "EtherType" here does not necessarily become EtherType; better to
> think it is a key to enable port control over the switch. It could be
> a dynamic random value as long as everyone gets the same value all
> over the kernel, see the setup process of the switch driver. Ideally
> only the remaining content of the tag should become the ABI (and is
> actually enforced by the switch), but making a dynamic "EtherType" is
> clearly a worse idea so I don't know how to clarify the fact...
If i remember correctly, the Marvell switches allow you to set the
EtherType they use. We just use the reset default value. It has been
like this since somewhere around 2008, and nobody has needed another
value.
What use case do you have for using a different value?
Andrew
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH net-next v6 2/3] net: dsa: tag_yt921x: add support for Motorcomm YT921x tags
2025-08-26 2:18 ` Andrew Lunn
@ 2025-08-26 2:44 ` Yangfl
0 siblings, 0 replies; 31+ messages in thread
From: Yangfl @ 2025-08-26 2:44 UTC (permalink / raw)
To: Andrew Lunn
Cc: Vladimir Oltean, netdev, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Simon Horman, Russell King, devicetree,
linux-kernel
On Tue, Aug 26, 2025 at 10:18 AM Andrew Lunn <andrew@lunn.ch> wrote:
>
> > > > +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?
> > >
> > > The tag format sort of becomes fixed ABI as soon as user space is able
> > > to run "cat /sys/class/net/eth0/dsa/tagging", see "yt921x", and record
> > > it to a pcap file. Unless the EtherType bears some other meaning rather
> > > than being a fixed value, then if you change it later to some other
> > > value than 0x9988, you'd better also change the protocol name to
> > > distinguish it from "yt921x".
> > >
> >
> > "EtherType" here does not necessarily become EtherType; better to
> > think it is a key to enable port control over the switch. It could be
> > a dynamic random value as long as everyone gets the same value all
> > over the kernel, see the setup process of the switch driver. Ideally
> > only the remaining content of the tag should become the ABI (and is
> > actually enforced by the switch), but making a dynamic "EtherType" is
> > clearly a worse idea so I don't know how to clarify the fact...
>
> If i remember correctly, the Marvell switches allow you to set the
> EtherType they use. We just use the reset default value. It has been
> like this since somewhere around 2008, and nobody has needed another
> value.
>
> What use case do you have for using a different value?
>
> Andrew
I don't. I'm just giving reasons why EtherType should not be
considered "fixed ABI" (or it is the reason it can be ABI if all of
you are fine with the reset default value).
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH net-next v6 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x
2025-08-25 21:23 ` Vladimir Oltean
@ 2025-08-26 5:19 ` Yangfl
2025-08-26 12:15 ` Andrew Lunn
2025-08-26 13:51 ` Vladimir Oltean
0 siblings, 2 replies; 31+ messages in thread
From: Yangfl @ 2025-08-26 5:19 UTC (permalink / raw)
To: Vladimir Oltean
Cc: netdev, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Simon Horman, Russell King, devicetree,
linux-kernel
On Tue, Aug 26, 2025 at 5:24 AM Vladimir Oltean <olteanv@gmail.com> wrote:
>
> Hi David,
>
> On Sun, Aug 24, 2025 at 08:51:11AM +0800, David Yang wrote:
> > +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
>
> If you have a VLAN-unaware bridge with learning enabled, this will
> learn MAC addresses in VID 1. If you also have a VLAN-aware bridge with
> default_pvid 1 on a different port group, this will also learn MAC
> addresses in VID 1.
>
I could set PVID to 0, but I checked my mv88e6352 device and it learns
MAC addresses in VID 4095 when egress is untagged, so I might place a
PVID here anyway.
> Are the two FDB databases isolated, or if two stations with the same MAC
> address are present in both bridges, will the switch attempt to forward
> packets from one bridge to another (leading to packet drops)?
> This can happen, for example, with the MAC addresses of user ports,
> which all inherit the conduit's MAC address when there is no address
> specified in the device tree.
>
FDB isolation is not supported, but port isolation is enforced here.
> There is an even simpler variant of this, which is a standalone port A
> and a VLAN-aware bridge port B. Port A won't have learning enabled, but
> it will still try to forward packets to known destinations, which may
> well be towards port B. The latter situation is very commonly seen in
> the tools/testing/selftests/drivers/net/dsa/ suite, where there are
> cables in loopback between switch ports. See
> tools/testing/selftests/net/forwarding/README for the topology.
>
> Have you ever run the DSA selftests for this new driver? Please include
> test results in the new patch submission, so we can take a look at what
> doesn't pass, and see where we should concentrate our attention.
>
> > +
> > +#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 */
>
> Please implement:
> void (*get_eth_phy_stats)(struct dsa_switch *ds, int port,
> struct ethtool_eth_phy_stats *phy_stats);
> void (*get_eth_mac_stats)(struct dsa_switch *ds, int port,
> struct ethtool_eth_mac_stats *mac_stats);
> void (*get_eth_ctrl_stats)(struct dsa_switch *ds, int port,
> struct ethtool_eth_ctrl_stats *ctrl_stats);
> void (*get_rmon_stats)(struct dsa_switch *ds, int port,
> struct ethtool_rmon_stats *rmon_stats,
> const struct ethtool_rmon_hist_range **ranges);
>
> and only the remainder (if any) go through the unstructured .get_ethtool_stats().
>
> > +};
> > +
> > +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;
> > +}
>
> You might want to schedule a periodic work item which catches 32-bit
> wraparounds of those MIB counters which lack _hi/_lo, and turns them
> into 64-bit values.
>
> > +
> > +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;
> > + struct device *dev = to_device(priv);
> > + int res;
> > +
> > + res = yt921x_read_mib_burst(priv, port);
> > + if (res)
> > + dev_dbg(dev, "%s: %i\n", __func__, res);
>
> An error in yt921x_read_mib_burst() deserves a more severe log level
> than dev_dbg() IMO. (not to mention a cleaned up error message)
>
> > +
> > + 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;
> > + struct device *dev = to_device(priv);
> > + int res;
> > +
> > + res = yt921x_read_mib_burst(priv, port);
> > + if (res)
> > + dev_dbg(dev, "%s: %i\n", __func__, res);
>
> Running with CONFIG_DEBUG_ATOMIC_SLEEP=y would have shown that you are
> calling a sleepable function from atomic context (ndo_get_stats64) which
> risks deadlocking the kernel.
>
> > +
> > + 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 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 */
>
> s/brother/bother/
>
> Also, .port_max_mtu() is only called for user ports, so the comment is irrelevant.
>
> > + 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);
> > +
> > + if (res)
> > + dev_dbg(dev, "%s: %i\n", __func__, 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;
>
> yt921x_smi_update_bits() would have helped here, this is a bit hard to
> follow.
>
I need the original value of the register which cannot be returned by
yt921x_smi_update_bits(), but I could make it nicer.
> > +
> > + /* 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;
>
> Use NL_SET_ERR_MSG_MOD(extack) to signal this.
>
> > + 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;
>
> The PVID depends on the VLAN filtering state of the port, so this
> function needs to update the PVID.
>
If I got it right the PVID is already set and programmed below by pvid_set().
> > + 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 (!WARN_ON(pp->vids_cnt <= 0))
> > + pp->vids_cnt--;
> > + return 0;
> > +}
> > +
> > +/* Seems port_vlan_add() is not state transition method... */
>
> I'm not sure what's a state transition method, but .port_vlan_add() is
> called if and only if a VLAN is added or the flags (PVID, untagged) of
> an existing VLAN are changed.
>
So it's not "adding" VLANs and I cannot always pp->vids_cnt++.
> > +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 (!WARN_ON(pp->vids_cnt >= VLAN_N_VID - 1))
> > + 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;
>
> A bit confusing that the hardware is programmed with YT921X_PVID_DEFAULT
> (1) but pp->pvid is 0. You might want to rename this to vlan_aware_pvid,
> for clarity. Though on user ports, you can always query it directly
> using br_vlan_get_pvid().
>
See above, if there is no consequence programming to 0.
> > + return 0;
> > +}
> > +
> > +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);
>
> Please get rid of these development leftovers. Also, for VLAN
> operations, DSA already has trace events (net/dsa/trace.c).
>
> > +
> > + 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
> > + */
>
> I don't understand this comment. What did the caller decide after all?
>
They can make a bridge without cpu ports. Of course it's never the
case in DSA, but it also serves as a reminder bitwise-oring your cpu
ports after collecting user 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);
>
> for_each_set_bit() would be easier to follow.
>
> > + 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);
>
> Doesn't this write ones outside of the YT921X_PORT_ISOLATION_BLOCKn
> field? I suppose there are some reserved high-order values which should
> be preserved when writing?
>
No, it's safe to write excessive 1s here.
> > + 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)
>
> The bridge_force() function name is a bit unconventional, what does it
> mean?
>
> > +{
> > + u16 targets_mask = ~ports_mask & (BIT(YT921X_PORT_NUM) - 1) &
> > + ~priv->cpu_ports_mask;
>
> What is the intention here? Assume you have one bridge with ports A and B,
> and another with ports C and D.
>
> Port D leaves the bridge, then targets_mask, the mask of ports which
> will become standalone, is comprised of ports A, B and D. Why? This
> sounds like a bug, ports A and B should remain untouched, as ports of an
> unrelated bridge.
>
bridge_force() does not know it's Port D/E/F... leaving the bridge.
Although DSA does tell us which port is leaving, we choose to rely on
hardware registers as much as possible, and wipe (force) configs on
all (non-bridge) ports. This also helps killing any unintentional port
leftovers if that (bug) does exist, otherwise errors would accumulate
until a reset is unavoidable. (Should errors emerge in bridge(),
bridge_force() would take care of them.)
> > + 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 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;
> > +}
>
> If this is useful, you should place this in include/net/dsa.h, and you
> can use a const struct dsa_bridge pointer as the second argument.
> Although I am not yet sure whether it is useful or not. I would like to
> see a non-buggy implementation of bridge offloading using it, first.
>
> > +
> > +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);
> > +
> > + if (res)
> > + dev_dbg(dev, "%s: %i\n", __func__, 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);
>
> yuck, why is it ok to release the SMI lock here? What's the point of the
> lock in the first place?
>
It's the bus lock, not the driver lock. We need to release the bus
lock when we want to sleep.
> As a matter of fact, .port_fdb_add() and .port_fdb_del() are one of the
> few operations which DSA emits which aren't serialized by rtnl_lock().
> They can absolutely be concurrent with .port_fdb_dump(), .port_mdb_add(),
> .port_mdb_del() etc. It seems like higher-level locking is needed.
>
> > + 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 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);
> > +
> > + if (res)
> > + dev_dbg(dev, "%s: %i\n", __func__, 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;
> > +}
>
> .port_vlan_fast_age() is dead code until dsa_port_supports_mst() returns
> true. I advise you to delete it.
>
> > +
> > +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 */
>
> learning? maybe ageing?
>
0 = immediate aged = not 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_port_config(struct yt921x_priv *priv, int port, unsigned int mode,
> > + phy_interface_t interface)
> > +{
> > + struct device *dev = to_device(priv);
> > + u32 mask;
> > + u32 ctrl;
> > + int res;
> > +
> > + if (!yt921x_port_is_external(port)) {
> > + if (interface != PHY_INTERFACE_MODE_INTERNAL) {
> > + dev_err(dev, "Wrong mode %d on port %d\n",
> > + interface, port);
> > + return -EINVAL;
> > + }
> > + return 0;
> > + }
> > +
> > + switch (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_PORTn(port));
> > + if (res)
> > + return res;
> > + res = yt921x_smi_clear_bits(priv, YT921X_XMII_CTRL,
> > + YT921X_XMII_CTRL_PORTn(port));
> > + if (res)
> > + return res;
> > +
> > + mask = YT921X_SGMII_MODE_M;
> > + switch (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:
> > + WARN_ON(1);
> > + break;
> > + }
> > + 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:
> > + case PHY_INTERFACE_MODE_INTERNAL:
> > + /* TODO */
> > + dev_err(dev, "Untested mode %d\n", interface);
> > + return -EINVAL;
>
> Delete the code after return below, and add it when it's tested.
> Though I fail to see why you'd add to supported_interfaces an
> unsupported interface.
>
> > + res = yt921x_smi_clear_bits(priv, YT921X_SGMII_CTRL,
> > + YT921X_SGMII_CTRL_PORTn(port));
> > + if (res)
> > + return res;
> > + res = yt921x_smi_set_bits(priv, YT921X_XMII_CTRL,
> > + YT921X_XMII_CTRL_PORTn(port));
> > + if (res)
> > + return res;
> > +
> > + mask = YT921X_XMII_EN | YT921X_XMII_MODE_M;
> > + ctrl = YT921X_XMII_EN;
> > + switch (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;
> > + case PHY_INTERFACE_MODE_INTERNAL:
> > + ctrl |= YT921X_XMII_MODE_DISABLE;
> > + break;
> > + default:
> > + WARN_ON(1);
> > + break;
> > + }
> > + res = yt921x_smi_update_bits(priv, YT921X_XMIIn(port),
> > + mask, ctrl);
> > + if (res)
> > + return res;
> > +
> > + /* TODO: RGMII delay */
> > +
> > + break;
> > + default:
> > + WARN_ON(1);
> > + 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;
> > + int res;
> > +
> > + dev_dbg(dev, "%s: port %d\n", __func__, port);
> > +
> > + yt921x_smi_acquire(priv);
> > + res = yt921x_port_down(priv, port);
> > + yt921x_smi_release(priv);
> > +
> > + if (res)
> > + dev_err(dev, "%s: port %d: %i\n", __func__, port, res);
>
> "Failed to bring port %d down: %pe\n", port, ERR_PTR(ret) seems a bit
> more elegant.
>
> > +}
> > +
> > +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;
>
> What is contained in this EEPROM?
>
No description, sorry.
> > +}
> > +
> > +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;
> > +}
>
> Handling .preferred_default_local_cpu_port() is not necessary if the
> hardware does not support multiple CPU ports, please drop this.
>
> > +
> > +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);
> > + if (res)
> > + dev_dbg(dev, "%s: %i\n", __func__, res);
> > + }
> > +}
>
> Handling .conduit_state_change() is not necessary. The hardware supports
> a single CPU port, and nothing is done when the conduit interface becomes
> inactive. priv->active_cpu_ports_mask becomes zero and the hardware is
> not updated in any way. The initialization done in yt921x_dsa_setup() is
> sufficient, please remove this.
>
> > +
> > +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");
>
> dsa_tree_setup_default_cpu() ensures a CPU port exists and fails probing
> otherwise. This is needlessly defensive.
>
> > + 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;
>
> I guess you got this snippet from mv88e6xxx_mdios_register(), which is
> different from your case because it is an old driver which has to
> support older device trees, before conventions changed.
>
> Please only register the internal MDIO bus if it is present in the
> device tree. This simplifies the driver and dt-bindings by having a
> single way of describing ports with internal PHYs. Also, remove the
> ds->user_mii_bus assignment after you do that.
>
How to access internal PHYs without registering the internal MDIO bus?
I know .phy_read and .phy_write, but all dsa_user_mii_bus_init() does
is to register and assign to ds->user_mii_bus for you using
phy_read/phy_write. Giving that we have an external bus to register,
it's pretty wired to register buses in two different ways. Also
mt7530.c does so for its internal bus.
> > +
> > + /* 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.
> > + */
>
> Wouldn't have yt921x_detect() seen that you are using the wrong chip?
> Testing CPU_TAG_TPID for an unexpected value after reset seems like a
> strikingly odd thing to do, and a waste of CPU cycles at boot time.
> (also, priv->tag_eth_p could have been a local variable)
>
> > + res = -EINVAL;
> > + break;
> > + }
> > +
> > + /* Unconditionally bring up one CPU port, to avoid warnings from
> > + * yt921x_tag_rcv() before conduit_state_change() is called.
> > + */
>
> What warnings? (not that you shouldn't bring the CPU port up)
>
> > + 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,
>
> No STP state handling?
>
Support for (M)STP was suggested for a future series in previous reviews.
> > +};
> > +
> > +/******** 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)
>
> NACK for exposing a sysfs which allows user space to modify switch
> registers without the driver updating its internal state.
>
> For reading -- also not so sure. There are many higher-level debug
> interfaces which you should prefer implementing before resorting to raw
> register reads. If there's any latching register in hardware, user space
> can mess it up. What use case do you have?
>
It is not possible to debug the device without a register sysfs, see
previous replies for why we need to lock the bus (but not necessarily
the MDIO bus). And if they are to modify switch registers, that's what
they want.
So it's also kind of development leftover, but if anyone wants to
improve the driver, they would come up with pretty the same debug
solution as this, thus I consider it useful in the future. If that
effort is still not appreciated, it might be wrapped inside a #ifdef
DEBUG block so it will not be enabled to end users.
> > +{
> > + 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);
>
> This is an area where we don't want diverging implementations. Please
> test the dev_get_drvdata() for NULL here and in remove(), and return
> without doing anything, like the other DSA drivers. You can consult
> "git log" for the detailed context.
>
> > +}
> > +
> > +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;
>
> This is the default for configure_vlan_while_not_filtering, so please
> remove this assignment.
>
> > + 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);
> > +}
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH net-next v6 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x
2025-08-26 5:19 ` Yangfl
@ 2025-08-26 12:15 ` Andrew Lunn
2025-08-26 13:51 ` Vladimir Oltean
1 sibling, 0 replies; 31+ messages in thread
From: Andrew Lunn @ 2025-08-26 12:15 UTC (permalink / raw)
To: Yangfl
Cc: Vladimir Oltean, netdev, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Simon Horman, Russell King, devicetree,
linux-kernel
> > > +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);
> >
> > yuck, why is it ok to release the SMI lock here? What's the point of the
> > lock in the first place?
> >
>
> It's the bus lock, not the driver lock. We need to release the bus
> lock when we want to sleep.
Why do you need to release the lock when you sleep? It is not a
spinlock, you can sleep while holding it. How do you prevent other fdb
operations running in parallel if you don't hold the lock?
> > > +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;
> >
> > What is contained in this EEPROM?
> >
>
> No description, sorry.
Even if you don't have a detailed description, can you tell us what it
can be used for?
The Marvell switches have a little application specific language which
allows you to write values into any register, and poll waiting for
events. It allows you to setup some aspects of the switch without
software. Using it is not a good idea, because DSA has no idea what
the EEPROM has done, and it could invalidate the assumptions about
reset default values.
In theory it is also possible to put code in the EEPROM for the Z80.
But i've never seen that does, probably because nobody combines DSA
with using the Z80.
> > I guess you got this snippet from mv88e6xxx_mdios_register(), which is
> > different from your case because it is an old driver which has to
> > support older device trees, before conventions changed.
> >
> > Please only register the internal MDIO bus if it is present in the
> > device tree. This simplifies the driver and dt-bindings by having a
> > single way of describing ports with internal PHYs. Also, remove the
> > ds->user_mii_bus assignment after you do that.
> >
>
> How to access internal PHYs without registering the internal MDIO bus?
phy-handle in each port nodes pointing to the PHY. This is standard
Ethernet DT. It just makes it more verbose.
> > > + /* 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,
> >
> > No STP state handling?
> >
>
> Support for (M)STP was suggested for a future series in previous reviews.
It is a pretty important feature to have, otherwise your network dies
in a broadcast storm when there are loops. I personally would of added
STP before VLANS. So please don't wait too long with this feature.
> > > +/******** 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)
> >
> > NACK for exposing a sysfs which allows user space to modify switch
> > registers without the driver updating its internal state.
> >
> > For reading -- also not so sure. There are many higher-level debug
> > interfaces which you should prefer implementing before resorting to raw
> > register reads. If there's any latching register in hardware, user space
> > can mess it up. What use case do you have?
> >
>
> It is not possible to debug the device without a register sysfs, see
> previous replies for why we need to lock the bus (but not necessarily
> the MDIO bus). And if they are to modify switch registers, that's what
> they want.
>
> So it's also kind of development leftover, but if anyone wants to
> improve the driver, they would come up with pretty the same debug
> solution as this, thus I consider it useful in the future. If that
> effort is still not appreciated, it might be wrapped inside a #ifdef
> DEBUG block so it will not be enabled to end users.
Write access in any form is definitely NACK.
For read access, please look at mv88e6xxx devlink regions, and
https://github.com/lunn/mv88e6xxx_dump
Andrew
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH net-next v6 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x
2025-08-26 5:19 ` Yangfl
2025-08-26 12:15 ` Andrew Lunn
@ 2025-08-26 13:51 ` Vladimir Oltean
1 sibling, 0 replies; 31+ messages in thread
From: Vladimir Oltean @ 2025-08-26 13:51 UTC (permalink / raw)
To: Yangfl
Cc: netdev, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Simon Horman, Russell King, devicetree,
linux-kernel
On Tue, Aug 26, 2025 at 01:19:58PM +0800, Yangfl wrote:
> On Tue, Aug 26, 2025 at 5:24 AM Vladimir Oltean <olteanv@gmail.com> wrote:
> > > +#define YT921X_PVID_DEFAULT 1
> >
> > If you have a VLAN-unaware bridge with learning enabled, this will
> > learn MAC addresses in VID 1. If you also have a VLAN-aware bridge with
> > default_pvid 1 on a different port group, this will also learn MAC
> > addresses in VID 1.
>
> I could set PVID to 0, but I checked my mv88e6352 device and it learns
> MAC addresses in VID 4095 when egress is untagged, so I might place a
> PVID here anyway.
>
> > Are the two FDB databases isolated, or if two stations with the same MAC
> > address are present in both bridges, will the switch attempt to forward
> > packets from one bridge to another (leading to packet drops)?
> > This can happen, for example, with the MAC addresses of user ports,
> > which all inherit the conduit's MAC address when there is no address
> > specified in the device tree.
>
> FDB isolation is not supported, but port isolation is enforced here.
Somehow, I fail to see how this answer addresses what I said.
When writing a new device driver, you have to think about FDB isolation.
At the most basic level, that can be achieved by not reusing the same
VLAN across multiple forwarding domains where MAC address namespace
collisions might exist. That is all.
There are things I don't understand in what you're trying to transmit,
such as:
- "I might place a PVID here anyway" - do you have the option of _not_
having a default port PVID, for when the port is standalone? I don't
understand the optionality of "I might".
- "my mv88e6352 device (...) learns MAC addresses in VID 4095 when egress
is untagged" - actually it learns in MV88E6XXX_VID_BRIDGED when under a
VLAN-unaware bridge, not when "egress is untagged".
but I strongly suggest taking a look at the selftests before you follow
up to this part again.
> > > +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;
> >
> > yt921x_smi_update_bits() would have helped here, this is a bit hard to
> > follow.
>
> I need the original value of the register which cannot be returned by
> yt921x_smi_update_bits(), but I could make it nicer.
Some of the API functions like regmap_update_bits_base() or
phy_modify_changed() have a way of telling the caller whether there was
any change in the original value or not. Maybe you can draw some
inspiration from them.
> > > +
> > > + /* 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;
> >
> > Use NL_SET_ERR_MSG_MOD(extack) to signal this.
> >
> > > + 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;
> >
> > The PVID depends on the VLAN filtering state of the port, so this
> > function needs to update the PVID.
>
> If I got it right the PVID is already set and programmed below by pvid_set().
Yes, and yt921x_pvid_set() is not called from yt921x_vlan_filtering().
Have a look at Documentation/networking/switchdev.rst section, it
explains that the PVID configured by the bridge should only take effect
if the port is currently VLAN-aware, which is a state that
yt921x_vlan_filtering() changes. If the hardware PVID remains the same
when the port is VLAN-aware and VLAN-unaware, there is probably a bug
somewhere.
> > > + return 0;
> > > +}
> > > +
> > > +/* Seems port_vlan_add() is not state transition method... */
> >
> > I'm not sure what's a state transition method, but .port_vlan_add() is
> > called if and only if a VLAN is added or the flags (PVID, untagged) of
> > an existing VLAN are changed.
>
> So it's not "adding" VLANs and I cannot always pp->vids_cnt++.
In theory, it's adding VLANs as long as struct switchdev_obj_port_vlan :: changed
is false. Though, because switchdev_obj_port_vlan objects are added from
two sources (switchdev and .ndo_vlan_rx_add_vid()), there might still be
bugs which allow a switchdev VLAN to change an .ndo_vlan_rx_add_vid()
VLAN and not set the "changed" flag, or vice versa. Those would be just
bugs that need to be better sealed, though, since the goal is to not
permit multiple sources from adding the same VLAN ID to a port. One
would need to delete the first before adding the second.
This is just for your general information. I am wondering when will your
logic of tracking the number of VLAN IDs, and setting YT921X_PORT_VLAN_CTRL1_CVLAN_DROP_TAGGED,
make a difference? If there is no VLAN ID configured and the port is
VLAN-aware, won't the port drop all tagged packets without this flag set?
It seems extra complexity with no clear benefit.
> > > +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;
> >
> > A bit confusing that the hardware is programmed with YT921X_PVID_DEFAULT
> > (1) but pp->pvid is 0. You might want to rename this to vlan_aware_pvid,
> > for clarity. Though on user ports, you can always query it directly
> > using br_vlan_get_pvid().
>
> See above, if there is no consequence programming to 0.
I think we're talking past each other, and my previous comment wasn't
expressed in the clearest way.
You have to decide what pp->pvid means - whether it tracks only the PVID
from the bridge layer (as it currently does), or the PVID which the port
is programmed with, and which will have a different value from the
above, in the situations that the port is standalone or under a bridge
that is currently VLAN-unaware.
The bridge layer indeed will not attempt to add VID 0 or VID 4095, so
you could use any of these values as an indication that the port has no
bridge PVID (and should drop untagged and VID0-tagged packets). You are
currently using pp->pvid == 0 as an indication of "no bridge PVID", and
that is functionally fine.
I suggested that you rename pp->pvid to pp->vlan_aware_pvid, in order to
make it clearer that the two uses of the number 0 are different. It is
valid for the hardware PVID programmed to YT921X_PORT_VLAN_CTRL_CVID to
be 0, and it is valid for the bridge VLAN-aware PVID to be absent, but
the two are absolutely independent and separate conditions.
Whereas you are responding to this comment by saying (rephrased) "to
solve the inconsistency, I can program YT921X_PORT_VLAN_CTRL_CVID to 0
and that shouldn't have any consequences", but this further blurs the
line between the two PVID concepts instead of separating them.
I don't believe that when the bridge PVID gets deleted and the port is
VLAN-aware, the port should be reconfigured to YT921X_PVID_DEFAULT.
It should be sufficient that YT921X_PORT_VLAN_CTRL1_CVLAN_DROP_UNTAGGED
gets set - the hardware PVID in this case can be whatever it may.
> > > + return 0;
> > > +}
> > > +
> > > +/******** bridge ********/
> > > +
> > > +/* It's caller's responsibility to decide whether ports_mask contains cpu
> > > + * ports
> > > + */
> >
> > I don't understand this comment. What did the caller decide after all?
>
> They can make a bridge without cpu ports. Of course it's never the
> case in DSA, but it also serves as a reminder bitwise-oring your cpu
> ports after collecting user ports.
Perhaps for you it's clear, but for me it still isn't. If, as you say,
DSA always expects the CPU port to be in the forwarding mask, then what
decision is left in the caller's responsibility? To do what it has to do?
It would be, for me, much clearer to express what you just said as:
"The caller must make sure that the CPU port is part of the ports_mask".
> > > +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);
> >
> > for_each_set_bit() would be easier to follow.
> >
> > > + 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);
> >
> > Doesn't this write ones outside of the YT921X_PORT_ISOLATION_BLOCKn
> > field? I suppose there are some reserved high-order values which should
> > be preserved when writing?
>
> No, it's safe to write excessive 1s here.
Still not the best practice, but ok.
> > > + 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)
> >
> > The bridge_force() function name is a bit unconventional, what does it
> > mean?
> >
> > > +{
> > > + u16 targets_mask = ~ports_mask & (BIT(YT921X_PORT_NUM) - 1) &
> > > + ~priv->cpu_ports_mask;
> >
> > What is the intention here? Assume you have one bridge with ports A and B,
> > and another with ports C and D.
> >
> > Port D leaves the bridge, then targets_mask, the mask of ports which
> > will become standalone, is comprised of ports A, B and D. Why? This
> > sounds like a bug, ports A and B should remain untouched, as ports of an
> > unrelated bridge.
>
> bridge_force() does not know it's Port D/E/F... leaving the bridge.
>
> Although DSA does tell us which port is leaving, we choose to rely on
> hardware registers as much as possible, and wipe (force) configs on
> all (non-bridge) ports. This also helps killing any unintentional port
> leftovers if that (bug) does exist, otherwise errors would accumulate
> until a reset is unavoidable. (Should errors emerge in bridge(),
> bridge_force() would take care of them.)
Did you understand what I said? It doesn't look like it, or I don't
understand in which way what you said is related to it.
In yt921x_bridge_force(), targets_mask holds the user ports which are
not part of this bridge (all of them). You force them all as standalone.
But in the example I gave with two bridges, you are configuring the
ports of br0 as standalone, when an unrelated port leaves br1. According
to my understanding of the code, that isolates the ports of br0 from
each other, which is not what the user has asked for.
> > > +/******** 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);
> >
> > yuck, why is it ok to release the SMI lock here? What's the point of the
> > lock in the first place?
>
> It's the bus lock, not the driver lock. We need to release the bus
> lock when we want to sleep.
What Andrew said, basically.
> > As a matter of fact, .port_fdb_add() and .port_fdb_del() are one of the
> > few operations which DSA emits which aren't serialized by rtnl_lock().
> > They can absolutely be concurrent with .port_fdb_dump(), .port_mdb_add(),
> > .port_mdb_del() etc. It seems like higher-level locking is needed.
> >
> > > + 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 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);
> > > +
> > > + if (res)
> > > + dev_dbg(dev, "%s: %i\n", __func__, 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;
> > > +}
> >
> > .port_vlan_fast_age() is dead code until dsa_port_supports_mst() returns
> > true. I advise you to delete it.
> >
> > > +
> > > +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 */
> >
> > learning? maybe ageing?
>
> 0 = immediate aged = not learning
Ok, I had other hardware in mind, where ageing_time==0 was a special
value for "never age".
> > > + 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_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;
> >
> > What is contained in this EEPROM?
> >
>
> No description, sorry.
So why do you dump it?
> > I guess you got this snippet from mv88e6xxx_mdios_register(), which is
> > different from your case because it is an old driver which has to
> > support older device trees, before conventions changed.
> >
> > Please only register the internal MDIO bus if it is present in the
> > device tree. This simplifies the driver and dt-bindings by having a
> > single way of describing ports with internal PHYs. Also, remove the
> > ds->user_mii_bus assignment after you do that.
>
> How to access internal PHYs without registering the internal MDIO bus?
> I know .phy_read and .phy_write, but all dsa_user_mii_bus_init() does
> is to register and assign to ds->user_mii_bus for you using
> phy_read/phy_write. Giving that we have an external bus to register,
> it's pretty wired to register buses in two different ways. Also
> mt7530.c does so for its internal bus.
What Andrew said. If you need the internal MDIO bus, you describe it in
the device tree.
> > No STP state handling?
>
> Support for (M)STP was suggested for a future series in previous reviews.
Ok, I expect yt921x_dsa_port_vlan_fast_age() will be removed from next
submissions until it isn't dead code anymore.
> > NACK for exposing a sysfs which allows user space to modify switch
> > registers without the driver updating its internal state.
> >
> > For reading -- also not so sure. There are many higher-level debug
> > interfaces which you should prefer implementing before resorting to raw
> > register reads. If there's any latching register in hardware, user space
> > can mess it up. What use case do you have?
>
> It is not possible to debug the device without a register sysfs, see
> previous replies for why we need to lock the bus (but not necessarily
> the MDIO bus). And if they are to modify switch registers, that's what
> they want.
When I said "higher level debugging interfaces", I had in mind "higher
level" in the sense Andrew mentioned - devlink regions, etc for
functional areas of interest that the developer has foreseen - and not
"lower level" like MDIO bus level access, which is even worse than the
sysfs due to the non-atomic accesses that you mention.
> So it's also kind of development leftover, but if anyone wants to
> improve the driver, they would come up with pretty the same debug
> solution as this, thus I consider it useful in the future. If that
> effort is still not appreciated, it might be wrapped inside a #ifdef
> DEBUG block so it will not be enabled to end users.
I see no problem with people who want to debug building their own
modified kernel with extra yt921x_smi_read() and yt921x_smi_write()
calls for the register of interest. That's pretty far from "anybody who
wants to debug has to have the sysfs".
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH net-next v6 2/3] net: dsa: tag_yt921x: add support for Motorcomm YT921x tags
2025-08-26 1:47 ` Yangfl
2025-08-26 2:18 ` Andrew Lunn
@ 2025-08-26 14:38 ` Vladimir Oltean
1 sibling, 0 replies; 31+ messages in thread
From: Vladimir Oltean @ 2025-08-26 14:38 UTC (permalink / raw)
To: Yangfl
Cc: netdev, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Simon Horman, Russell King, devicetree,
linux-kernel
On Tue, Aug 26, 2025 at 09:47:34AM +0800, Yangfl wrote:
> > The tag format sort of becomes fixed ABI as soon as user space is able
> > to run "cat /sys/class/net/eth0/dsa/tagging", see "yt921x", and record
> > it to a pcap file. Unless the EtherType bears some other meaning rather
> > than being a fixed value, then if you change it later to some other
> > value than 0x9988, you'd better also change the protocol name to
> > distinguish it from "yt921x".
>
> "EtherType" here does not necessarily become EtherType; better to
> think it is a key to enable port control over the switch. It could be
> a dynamic random value as long as everyone gets the same value all
> over the kernel, see the setup process of the switch driver. Ideally
> only the remaining content of the tag should become the ABI (and is
> actually enforced by the switch), but making a dynamic "EtherType" is
> clearly a worse idea so I don't know how to clarify the fact...
>
> > Also, you can _not_ use yt921x_priv :: tag_eth_p, because doing so would
> > assume that typeof(ds->priv) == struct yt921x_priv. In principle we
> > would like to be able to run the tagging protocols on the dsa_loop
> > driver as well, which can be attached to any network interface. Very
> > few, if any, tagging protocol drivers don't work on dsa_loop.
> > > +
> > > +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;
> >
> > Use dsa_etype_header_pos_rx() and validate the CPU_TAG_TPID_TPID as well.
>
> See the above explanation why rx "EtherType" is not considered part of ABI.
So what I don't understand is: it's impossible to separate RX from TX in
current DSA taggers. If TX hardcodes the assumption that the switch was
configured to use EtherType 0x9988, why would we expect RX to use anything else?
What valid configuration would we prevent, if we ensured that RX packets
have the same EtherType?
It should be ok if you documented that the EtherType is self-assigned
and must be hardcoded to 0x9988 by local convention for the moment, but
is otherwise configurable, and the meaning of a different value is
undefined. Even if self-assigned, I suppose you could still add the
value to include/uapi/linux/if_ether.h to avoid conflicts with other
uses.
Even better if you clarify the expectations by writing a libpcap
dissector for the new tagging protocol, which you need to do anyway for
recent tcpdump versions to work on the conduit interface (otherwise it
fails with "unsupported protocol"). See
https://github.com/the-tcpdump-group/libpcap/pull/1463
The libpcap dissector uses the /sys/class/net/eth0/dsa/tagging text to
identify the protocol in use, not the EtherType.
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH net-next v6 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x
2025-08-25 14:14 ` Yangfl
@ 2025-08-26 19:07 ` Vladimir Oltean
0 siblings, 0 replies; 31+ messages in thread
From: Vladimir Oltean @ 2025-08-26 19:07 UTC (permalink / raw)
To: Yangfl
Cc: Russell King (Oracle), Andrew Lunn, netdev, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Simon Horman, devicetree,
linux-kernel
On Mon, Aug 25, 2025 at 10:14:58PM +0800, Yangfl wrote:
> On Mon, Aug 25, 2025 at 2:14 AM Russell King (Oracle)
> <linux@armlinux.org.uk> wrote:
> >
> > On Mon, Aug 25, 2025 at 12:38:20AM +0800, Yangfl wrote:
> > > They are used in phylink_get_caps(), since I don't want to declare a
> > > port which we know it does not exist on some chips. But the info_* set
> > > might be inlined and removed since it is not used elsewhere.
> >
> > The problem is... if you have a port in 0..N that DSA thinks should be
> > used, but is neither internal or external, DSA's initialisation of it
> > will fail, because without any caps declared for it, phylink_create()
> > will return an error, causing dsa_port_phylink_create() to fail,
> > dsa_shared_port_phylink_register() or dsa_user_phy_setup(),
> > dsa_shared_port_link_register_of() or dsa_user_create()... etc. It
> > eventually gets propagated up causing the entire switch probe to fail.
> >
> > Again... read the code!
>
> What would you expect when you specify Port 0 in DT when only Port 1,
> 3, 8 are available on the chip (YT9213NB)? Probe error.
It depends. Unless the driver has logic which behaves otherwise, the
setup of user ports can fail and DSA will just skip them and bring up
the rest. See commit 86f8b1c01a0a ("net: dsa: Do not make user port
errors fatal"). The shared ports are more important and their setup
failures do lead to a switch setup abort though.
^ permalink raw reply [flat|nested] 31+ messages in thread
end of thread, other threads:[~2025-08-26 19:07 UTC | newest]
Thread overview: 31+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-08-24 0:51 [PATCH net-next v6 0/3] net: dsa: yt921x: Add support for Motorcomm YT921x David Yang
2025-08-24 0:51 ` [PATCH net-next v6 1/3] dt-bindings: net: dsa: yt921x: Add Motorcomm YT921x switch support David Yang
2025-08-24 9:20 ` Krzysztof Kozlowski
2025-08-24 9:25 ` Yangfl
2025-08-24 17:16 ` Krzysztof Kozlowski
2025-08-25 2:39 ` Yangfl
2025-08-25 7:37 ` Krzysztof Kozlowski
2025-08-24 0:51 ` [PATCH net-next v6 2/3] net: dsa: tag_yt921x: add support for Motorcomm YT921x tags David Yang
2025-08-25 22:15 ` Vladimir Oltean
2025-08-26 1:47 ` Yangfl
2025-08-26 2:18 ` Andrew Lunn
2025-08-26 2:44 ` Yangfl
2025-08-26 14:38 ` Vladimir Oltean
2025-08-24 0:51 ` [PATCH net-next v6 3/3] net: dsa: yt921x: Add support for Motorcomm YT921x David Yang
2025-08-24 8:42 ` Russell King (Oracle)
2025-08-24 9:31 ` Yangfl
2025-08-24 15:22 ` Andrew Lunn
2025-08-24 15:26 ` Andrew Lunn
2025-08-24 15:34 ` Andrew Lunn
2025-08-24 15:51 ` Andrew Lunn
2025-08-24 16:38 ` Yangfl
2025-08-24 18:14 ` Russell King (Oracle)
2025-08-24 19:16 ` Andrew Lunn
2025-08-25 14:14 ` Yangfl
2025-08-26 19:07 ` Vladimir Oltean
2025-08-24 19:14 ` Andrew Lunn
2025-08-24 16:40 ` kernel test robot
2025-08-25 21:23 ` Vladimir Oltean
2025-08-26 5:19 ` Yangfl
2025-08-26 12:15 ` Andrew Lunn
2025-08-26 13:51 ` Vladimir Oltean
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).