public inbox for linux-arm-kernel@lists.infradead.org
 help / color / mirror / Atom feed
* [PATCH net-next v2 0/8] net: qualcomm: ipqess: introduce Qualcomm IPQESS driver
@ 2023-11-14  9:07 Romain Gantois
  2023-11-14  9:07 ` [PATCH net-next v2 1/8] dt-bindings: net: Introduce the Qualcomm IPQESS Ethernet switch Romain Gantois
                   ` (7 more replies)
  0 siblings, 8 replies; 9+ messages in thread
From: Romain Gantois @ 2023-11-14  9:07 UTC (permalink / raw)
  To: davem, Rob Herring, Krzysztof Kozlowski
  Cc: Romain Gantois, Jakub Kicinski, Eric Dumazet, Paolo Abeni, netdev,
	linux-kernel, devicetree, thomas.petazzoni, Andrew Lunn,
	Florian Fainelli, Heiner Kallweit, Russell King, linux-arm-kernel,
	Vladimir Oltean, Luka Perkov, Robert Marko, Andy Gross,
	Bjorn Andersson, Konrad Dybcio

Hello everyone,

This is the 2nd iteration on the Qualcomm IPQ4019 Ethernet Switch Subsystem
driver.

Notable changes in v2:
 - Refactored the PSGMII calibration procedure to exclude
   PHY-model-specific code from the switch driver. Added two new callbacks
   to the phy_driver struct to enable PHY-agnostic calibration control from
   the MAC driver.
 - Modified the EDMA Ethernet driver to use page_pool for skb handling.
 - Refactored several qca8k-common.c functions to enable calling them from
   the IPQESS driver rather than reimplementing them.

The IPQ4019 SoC integrates a modified version of the QCA8K Ethernet switch.
One major difference with the original switch IP is that port tags are
passed to the integrated Ethernet controller out-of-band.

Previous DSA versions of this driver were rejected because they required
adding out-of-band tagging support to the DSA subsystem. Therefore, we
rewrote the driver as a pure switchdev module, which shares a common
backend library with the current QCA8K driver.

The main driver components are:
 - ipqess_switch.c which registers and configures the integrated switch
 - ipqess_port.c which creates net devices for each one of the front-facing
   ports.
 - ipqess_edma.c which handles the integrated EDMA Ethernet controller
   linked to the CPU port.
 - drivers/net/dsa/qca/qca8k-common.c which defines low-level ESS access
   methods common to this driver and the original DSA QCA8K driver.

Thanks to the people from Sartura for providing us hardware and working on
the base QCA8K driver, and to Maxime for his work on the EDMA code.

Best regards,

Romain

Romain Gantois (8):
  dt-bindings: net: Introduce the Qualcomm IPQESS Ethernet switch
  net: dsa: qca8k: Make the QCA8K hardware library available globally
  net: qualcomm: ipqess: introduce the Qualcomm IPQESS driver
  net: qualcomm: ipqess: Add Ethtool ops to IPQESS port netdevices
  net: qualcomm: ipqess: add bridge offloading features to the IPQESS
    driver
  net: phy: add calibration callbacks to phy_driver
  net: qualcomm: ipqess: add a PSGMII calibration procedure to the
    IPQESS driver
  ARM: dts: qcom: ipq4019: Add description for the IPQ4019 ESS EDMA and
    switch

 .../bindings/net/qcom,ipq4019-ess.yaml        |  152 ++
 MAINTAINERS                                   |    7 +
 .../boot/dts/qcom/qcom-ipq4018-ap120c-ac.dtsi |   13 +
 arch/arm/boot/dts/qcom/qcom-ipq4019.dtsi      |   94 +
 drivers/net/dsa/qca/Kconfig                   |   10 +
 drivers/net/dsa/qca/Makefile                  |    5 +-
 drivers/net/dsa/qca/qca8k-8xxx.c              |   51 +-
 drivers/net/dsa/qca/qca8k-common.c            |  126 +-
 drivers/net/dsa/qca/qca8k-leds.c              |    2 +-
 drivers/net/ethernet/qualcomm/Kconfig         |   15 +
 drivers/net/ethernet/qualcomm/Makefile        |    2 +
 drivers/net/ethernet/qualcomm/ipqess/Makefile |    8 +
 .../ethernet/qualcomm/ipqess/ipqess_calib.c   |  156 ++
 .../ethernet/qualcomm/ipqess/ipqess_edma.c    | 1195 ++++++++++++
 .../ethernet/qualcomm/ipqess/ipqess_edma.h    |  488 +++++
 .../ethernet/qualcomm/ipqess/ipqess_ethtool.c |  245 +++
 .../qualcomm/ipqess/ipqess_notifiers.c        |  306 +++
 .../qualcomm/ipqess/ipqess_notifiers.h        |   29 +
 .../ethernet/qualcomm/ipqess/ipqess_port.c    | 1686 +++++++++++++++++
 .../ethernet/qualcomm/ipqess/ipqess_port.h    |  102 +
 .../ethernet/qualcomm/ipqess/ipqess_switch.c  |  533 ++++++
 .../ethernet/qualcomm/ipqess/ipqess_switch.h  |   36 +
 .../net/dsa/qca => include/linux/dsa}/qca8k.h |   61 +-
 include/linux/phy.h                           |   28 +
 24 files changed, 5296 insertions(+), 54 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/net/qcom,ipq4019-ess.yaml
 create mode 100644 drivers/net/ethernet/qualcomm/ipqess/Makefile
 create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_calib.c
 create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_edma.c
 create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_edma.h
 create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_ethtool.c
 create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_notifiers.c
 create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_notifiers.h
 create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
 create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
 create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.c
 create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.h
 rename {drivers/net/dsa/qca => include/linux/dsa}/qca8k.h (90%)

-- 
2.42.0


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

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

* [PATCH net-next v2 1/8] dt-bindings: net: Introduce the Qualcomm IPQESS Ethernet switch
  2023-11-14  9:07 [PATCH net-next v2 0/8] net: qualcomm: ipqess: introduce Qualcomm IPQESS driver Romain Gantois
@ 2023-11-14  9:07 ` Romain Gantois
  2023-11-14  9:07 ` [PATCH net-next v2 2/8] net: dsa: qca8k: Make the QCA8K hardware library available globally Romain Gantois
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Romain Gantois @ 2023-11-14  9:07 UTC (permalink / raw)
  To: davem, Rob Herring, Krzysztof Kozlowski
  Cc: Romain Gantois, Jakub Kicinski, Eric Dumazet, Paolo Abeni, netdev,
	linux-kernel, devicetree, thomas.petazzoni, Andrew Lunn,
	Florian Fainelli, Heiner Kallweit, Russell King, linux-arm-kernel,
	Vladimir Oltean, Luka Perkov, Robert Marko, Andy Gross,
	Bjorn Andersson, Konrad Dybcio

Add the DT binding for the IPQESS Ethernet switch subsystem, that
integrates a modified QCA8K switch and an EDMA MAC controller. It inherits
from a basic ethernet switch binding and adds three regmaps, a phandle and
reset line for the PSGMII, a phandle to the MDIO bus, a clock, and 32
interrupts.

Signed-off-by: Romain Gantois <romain.gantois@bootlin.com>
---
 .../bindings/net/qcom,ipq4019-ess.yaml        | 152 ++++++++++++++++++
 1 file changed, 152 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/net/qcom,ipq4019-ess.yaml

diff --git a/Documentation/devicetree/bindings/net/qcom,ipq4019-ess.yaml b/Documentation/devicetree/bindings/net/qcom,ipq4019-ess.yaml
new file mode 100644
index 000000000000..85dff85e50b5
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/qcom,ipq4019-ess.yaml
@@ -0,0 +1,152 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/qcom,ipq4019-ess.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm IPQ4019 Ethernet Switch Subsystem
+
+maintainers:
+  - Romain Gantois <romain.gantois@bootlin.com>
+
+$ref: ethernet-switch.yaml#
+
+properties:
+  compatible:
+    const: qcom,ipq4019-ess
+
+  reg:
+    items:
+      - description: Base ESS registers, which configure the integrated QCA8K switch.
+      - description: ESS PSGMII-related registers, which control VCO calibration and link
+                     modes.
+      - description: ESS EDMA controller registers. The EDMA controller is an Ethernet
+                     controller connected to the integrated switch's CPU port.
+  reg-names:
+    items:
+      - const: base
+      - const: psgmii_phy
+      - const: edma
+
+  resets:
+    items:
+      - description: Handle to the PSGMII reset line.
+      - description: Handle to the ESS reset line.
+
+  reset-names:
+    items:
+      - const: psgmii
+      - const: ess
+
+  clocks:
+    maxItems: 1
+    description: Handle to the GCC ESS clock
+
+  mdio:
+    maxItems: 1
+    description: Handle to the IPQ4019 MDIO Controller
+
+  interrupts:
+    maxItems: 32
+    description: One interrupt per tx and rx queue, the first 16 are rx queues
+                 and the last 16 are the tx queues
+
+required:
+  - compatible
+  - reg
+  - reg-names
+  - resets
+  - reset-names
+  - clocks
+  - mdio
+  - interrupts
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/qcom,gcc-ipq4019.h>
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+    switch: switch@c000000 {
+        compatible = "qcom,ipq4019-ess";
+        reg = <0xc000000 0x80000>, <0x98000 0x800>, <0xc080000 0x80000>;
+        reg-names = "base", "psgmii_phy", "edma";
+        resets = <&gcc ESS_PSGMII_ARES>, <&gcc ESS_RESET>;
+        reset-names = "psgmii", "ess";
+        clocks = <&gcc GCC_ESS_CLK>;
+        mdio = <&mdio>;
+        interrupts = <GIC_SPI  65 IRQ_TYPE_EDGE_RISING>,
+                     <GIC_SPI  66 IRQ_TYPE_EDGE_RISING>,
+                     <GIC_SPI  67 IRQ_TYPE_EDGE_RISING>,
+                     <GIC_SPI  68 IRQ_TYPE_EDGE_RISING>,
+                     <GIC_SPI  69 IRQ_TYPE_EDGE_RISING>,
+                     <GIC_SPI  70 IRQ_TYPE_EDGE_RISING>,
+                     <GIC_SPI  71 IRQ_TYPE_EDGE_RISING>,
+                     <GIC_SPI  72 IRQ_TYPE_EDGE_RISING>,
+                     <GIC_SPI  73 IRQ_TYPE_EDGE_RISING>,
+                     <GIC_SPI  74 IRQ_TYPE_EDGE_RISING>,
+                     <GIC_SPI  75 IRQ_TYPE_EDGE_RISING>,
+                     <GIC_SPI  76 IRQ_TYPE_EDGE_RISING>,
+                     <GIC_SPI  77 IRQ_TYPE_EDGE_RISING>,
+                     <GIC_SPI  78 IRQ_TYPE_EDGE_RISING>,
+                     <GIC_SPI  79 IRQ_TYPE_EDGE_RISING>,
+                     <GIC_SPI  80 IRQ_TYPE_EDGE_RISING>,
+                     <GIC_SPI 240 IRQ_TYPE_EDGE_RISING>,
+                     <GIC_SPI 241 IRQ_TYPE_EDGE_RISING>,
+                     <GIC_SPI 242 IRQ_TYPE_EDGE_RISING>,
+                     <GIC_SPI 243 IRQ_TYPE_EDGE_RISING>,
+                     <GIC_SPI 244 IRQ_TYPE_EDGE_RISING>,
+                     <GIC_SPI 245 IRQ_TYPE_EDGE_RISING>,
+                     <GIC_SPI 246 IRQ_TYPE_EDGE_RISING>,
+                     <GIC_SPI 247 IRQ_TYPE_EDGE_RISING>,
+                     <GIC_SPI 248 IRQ_TYPE_EDGE_RISING>,
+                     <GIC_SPI 249 IRQ_TYPE_EDGE_RISING>,
+                     <GIC_SPI 250 IRQ_TYPE_EDGE_RISING>,
+                     <GIC_SPI 251 IRQ_TYPE_EDGE_RISING>,
+                     <GIC_SPI 252 IRQ_TYPE_EDGE_RISING>,
+                     <GIC_SPI 253 IRQ_TYPE_EDGE_RISING>,
+                     <GIC_SPI 254 IRQ_TYPE_EDGE_RISING>,
+                     <GIC_SPI 255 IRQ_TYPE_EDGE_RISING>;
+
+        ports {
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            swport1: port@1 { /* MAC1 */
+                 reg = <1>;
+                 label = "lan1";
+                 phy-handle = <&ethphy0>;
+                 phy-mode = "psgmii";
+            };
+
+            swport2: port@2 { /* MAC2 */
+                 reg = <2>;
+                 label = "lan2";
+                 phy-handle = <&ethphy1>;
+                 phy-mode = "psgmii";
+            };
+
+            swport3: port@3 { /* MAC3 */
+                 reg = <3>;
+                 label = "lan3";
+                 phy-handle = <&ethphy2>;
+                 phy-mode = "psgmii";
+            };
+
+            swport4: port@4 { /* MAC4 */
+                 reg = <4>;
+                 label = "lan4";
+                 phy-handle = <&ethphy3>;
+                 phy-mode = "psgmii";
+            };
+
+            swport5: port@5 { /* MAC5 */
+                 reg = <5>;
+                 label = "wan";
+                 phy-handle = <&ethphy4>;
+                 phy-mode = "psgmii";
+            };
+        };
+    };
+
+...
-- 
2.42.0


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

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

* [PATCH net-next v2 2/8] net: dsa: qca8k: Make the QCA8K hardware library available globally
  2023-11-14  9:07 [PATCH net-next v2 0/8] net: qualcomm: ipqess: introduce Qualcomm IPQESS driver Romain Gantois
  2023-11-14  9:07 ` [PATCH net-next v2 1/8] dt-bindings: net: Introduce the Qualcomm IPQESS Ethernet switch Romain Gantois
@ 2023-11-14  9:07 ` Romain Gantois
  2023-11-14  9:07 ` [PATCH net-next v2 4/8] net: qualcomm: ipqess: Add Ethtool ops to IPQESS port netdevices Romain Gantois
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Romain Gantois @ 2023-11-14  9:07 UTC (permalink / raw)
  To: davem, Rob Herring, Krzysztof Kozlowski
  Cc: Romain Gantois, Jakub Kicinski, Eric Dumazet, Paolo Abeni, netdev,
	linux-kernel, devicetree, thomas.petazzoni, Andrew Lunn,
	Florian Fainelli, Heiner Kallweit, Russell King, linux-arm-kernel,
	Vladimir Oltean, Luka Perkov, Robert Marko, Andy Gross,
	Bjorn Andersson, Konrad Dybcio

The Qualcomm QCA8K Ethernet switch is already supported in the kernel, as a
DSA switch. However, the Qualcomm IPQ4019 SoC contains an internal modified
QCA8K switch that does not fit into the DSA model, since it uses an
out-of-band tagging protocol.

Move the qca8k.h header file out of the QCA8K DSA driver so that the
upcoming IPQ4019 QCA8K switchdev driver can include it.

Refactor qca8k-common into a separate module so that the IPQESS driver can
be built as a module.

Signed-off-by: Romain Gantois <romain.gantois@bootlin.com>
---
 drivers/net/dsa/qca/Kconfig                   | 10 +++++
 drivers/net/dsa/qca/Makefile                  |  5 ++-
 drivers/net/dsa/qca/qca8k-8xxx.c              |  2 +-
 drivers/net/dsa/qca/qca8k-common.c            | 45 +++++++++++++++++--
 drivers/net/dsa/qca/qca8k-leds.c              |  2 +-
 .../net/dsa/qca => include/linux/dsa}/qca8k.h |  1 +
 6 files changed, 58 insertions(+), 7 deletions(-)
 rename {drivers/net/dsa/qca => include/linux/dsa}/qca8k.h (99%)

diff --git a/drivers/net/dsa/qca/Kconfig b/drivers/net/dsa/qca/Kconfig
index de9da469908b..37b8d938a7fc 100644
--- a/drivers/net/dsa/qca/Kconfig
+++ b/drivers/net/dsa/qca/Kconfig
@@ -11,6 +11,7 @@ config NET_DSA_AR9331
 config NET_DSA_QCA8K
 	tristate "Qualcomm Atheros QCA8K Ethernet switch family support"
 	select NET_DSA_TAG_QCA
+	select NET_DSA_QCA8K_LIB
 	select REGMAP
 	help
 	  This enables support for the Qualcomm Atheros QCA8K Ethernet
@@ -24,3 +25,12 @@ config NET_DSA_QCA8K_LEDS_SUPPORT
 	help
 	  This enabled support for LEDs present on the Qualcomm Atheros
 	  QCA8K Ethernet switch chips.
+
+config NET_DSA_QCA8K_LIB
+	tristate "Qualcomm Atheros QCA8K hardware support library"
+	select REGMAP
+	help
+	  This enables the hardware support library for the Qualcomm
+	  Atheros QCA8K Ethernet switch. It is used by the switchdev-based
+	  IPQ4019 integrated switch driver and by the DSA QCA8K switch
+	  driver.
diff --git a/drivers/net/dsa/qca/Makefile b/drivers/net/dsa/qca/Makefile
index ce66b1984e5f..05990339c04e 100644
--- a/drivers/net/dsa/qca/Makefile
+++ b/drivers/net/dsa/qca/Makefile
@@ -1,7 +1,10 @@
 # SPDX-License-Identifier: GPL-2.0-only
 obj-$(CONFIG_NET_DSA_AR9331)	+= ar9331.o
 obj-$(CONFIG_NET_DSA_QCA8K)	+= qca8k.o
-qca8k-y 			+= qca8k-common.o qca8k-8xxx.o
+qca8k-y 			+= qca8k-8xxx.o
 ifdef CONFIG_NET_DSA_QCA8K_LEDS_SUPPORT
 qca8k-y				+= qca8k-leds.o
 endif
+
+obj-$(CONFIG_NET_DSA_QCA8K_LIB) += qca8k-lib.o
+qca8k-lib-y := qca8k-common.o
diff --git a/drivers/net/dsa/qca/qca8k-8xxx.c b/drivers/net/dsa/qca/qca8k-8xxx.c
index ec57d9d52072..210667755b00 100644
--- a/drivers/net/dsa/qca/qca8k-8xxx.c
+++ b/drivers/net/dsa/qca/qca8k-8xxx.c
@@ -20,8 +20,8 @@
 #include <linux/gpio/consumer.h>
 #include <linux/etherdevice.h>
 #include <linux/dsa/tag_qca.h>
+#include <linux/dsa/qca8k.h>
 
-#include "qca8k.h"
 #include "qca8k_leds.h"
 
 static void
diff --git a/drivers/net/dsa/qca/qca8k-common.c b/drivers/net/dsa/qca/qca8k-common.c
index 9243eff8918d..43a2fe05f73d 100644
--- a/drivers/net/dsa/qca/qca8k-common.c
+++ b/drivers/net/dsa/qca/qca8k-common.c
@@ -8,10 +8,9 @@
 
 #include <linux/netdevice.h>
 #include <net/dsa.h>
+#include <linux/dsa/qca8k.h>
 #include <linux/if_bridge.h>
 
-#include "qca8k.h"
-
 #define MIB_DESC(_s, _o, _n)	\
 	{			\
 		.size = (_s),	\
@@ -62,16 +61,19 @@ const struct qca8k_mib_desc ar8327_mib[] = {
 	MIB_DESC(1, 0xa8, "RXUnicast"),
 	MIB_DESC(1, 0xac, "TXUnicast"),
 };
+EXPORT_SYMBOL(ar8327_mib);
 
 int qca8k_read(struct qca8k_priv *priv, u32 reg, u32 *val)
 {
 	return regmap_read(priv->regmap, reg, val);
 }
+EXPORT_SYMBOL(qca8k_read);
 
 int qca8k_write(struct qca8k_priv *priv, u32 reg, u32 val)
 {
 	return regmap_write(priv->regmap, reg, val);
 }
+EXPORT_SYMBOL(qca8k_write);
 
 int qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val)
 {
@@ -100,6 +102,7 @@ const struct regmap_access_table qca8k_readable_table = {
 	.yes_ranges = qca8k_readable_ranges,
 	.n_yes_ranges = ARRAY_SIZE(qca8k_readable_ranges),
 };
+EXPORT_SYMBOL(qca8k_readable_table);
 
 static int qca8k_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask)
 {
@@ -462,6 +465,7 @@ int qca8k_mib_init(struct qca8k_priv *priv)
 	mutex_unlock(&priv->reg_mutex);
 	return ret;
 }
+EXPORT_SYMBOL(qca8k_mib_init);
 
 void qca8k_port_set_status(struct qca8k_priv *priv, int port, int enable)
 {
@@ -476,6 +480,7 @@ void qca8k_port_set_status(struct qca8k_priv *priv, int port, int enable)
 	else
 		regmap_clear_bits(priv->regmap, QCA8K_REG_PORT_STATUS(port), mask);
 }
+EXPORT_SYMBOL(qca8k_port_set_status);
 
 void qca8k_get_strings(struct dsa_switch *ds, int port, u32 stringset,
 		       uint8_t *data)
@@ -489,6 +494,7 @@ void qca8k_get_strings(struct dsa_switch *ds, int port, u32 stringset,
 	for (i = 0; i < priv->info->mib_count; i++)
 		ethtool_sprintf(&data, "%s", ar8327_mib[i].name);
 }
+EXPORT_SYMBOL(qca8k_get_strings);
 
 void qca8k_get_ethtool_stats(struct dsa_switch *ds, int port,
 			     uint64_t *data)
@@ -522,6 +528,7 @@ void qca8k_get_ethtool_stats(struct dsa_switch *ds, int port,
 			data[i] |= (u64)hi << 32;
 	}
 }
+EXPORT_SYMBOL(qca8k_get_ethtool_stats);
 
 int qca8k_get_sset_count(struct dsa_switch *ds, int port, int sset)
 {
@@ -532,6 +539,7 @@ int qca8k_get_sset_count(struct dsa_switch *ds, int port, int sset)
 
 	return priv->info->mib_count;
 }
+EXPORT_SYMBOL(qca8k_get_sset_count);
 
 int qca8k_set_mac_eee(struct dsa_switch *ds, int port,
 		      struct ethtool_eee *eee)
@@ -556,6 +564,7 @@ int qca8k_set_mac_eee(struct dsa_switch *ds, int port,
 	mutex_unlock(&priv->reg_mutex);
 	return ret;
 }
+EXPORT_SYMBOL(qca8k_set_mac_eee);
 
 int qca8k_get_mac_eee(struct dsa_switch *ds, int port,
 		      struct ethtool_eee *e)
@@ -563,6 +572,7 @@ int qca8k_get_mac_eee(struct dsa_switch *ds, int port,
 	/* Nothing to do on the port's MAC */
 	return 0;
 }
+EXPORT_SYMBOL(qca8k_get_mac_eee);
 
 static int qca8k_port_configure_learning(struct dsa_switch *ds, int port,
 					 bool learning)
@@ -613,6 +623,7 @@ void qca8k_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
 
 	qca8k_port_configure_learning(ds, port, learning);
 }
+EXPORT_SYMBOL(qca8k_port_stp_state_set);
 
 int qca8k_port_pre_bridge_flags(struct dsa_switch *ds, int port,
 				struct switchdev_brport_flags flags,
@@ -623,6 +634,7 @@ int qca8k_port_pre_bridge_flags(struct dsa_switch *ds, int port,
 
 	return 0;
 }
+EXPORT_SYMBOL(qca8k_port_pre_bridge_flags);
 
 int qca8k_port_bridge_flags(struct dsa_switch *ds, int port,
 			    struct switchdev_brport_flags flags,
@@ -639,6 +651,7 @@ int qca8k_port_bridge_flags(struct dsa_switch *ds, int port,
 
 	return 0;
 }
+EXPORT_SYMBOL(qca8k_port_bridge_flags);
 
 int qca8k_port_bridge_join(struct dsa_switch *ds, int port,
 			   struct dsa_bridge bridge,
@@ -675,6 +688,7 @@ int qca8k_port_bridge_join(struct dsa_switch *ds, int port,
 
 	return ret;
 }
+EXPORT_SYMBOL(qca8k_port_bridge_join);
 
 void qca8k_port_bridge_leave(struct dsa_switch *ds, int port,
 			     struct dsa_bridge bridge)
@@ -703,6 +717,7 @@ void qca8k_port_bridge_leave(struct dsa_switch *ds, int port,
 	qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
 		  QCA8K_PORT_LOOKUP_MEMBER, BIT(cpu_port));
 }
+EXPORT_SYMBOL(qca8k_port_bridge_leave);
 
 void qca8k_port_fast_age(struct dsa_switch *ds, int port)
 {
@@ -712,6 +727,7 @@ void qca8k_port_fast_age(struct dsa_switch *ds, int port)
 	qca8k_fdb_access(priv, QCA8K_FDB_FLUSH_PORT, port);
 	mutex_unlock(&priv->reg_mutex);
 }
+EXPORT_SYMBOL(qca8k_port_fast_age);
 
 int qca8k_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
 {
@@ -732,6 +748,7 @@ int qca8k_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
 				  QCA8K_ATU_AGE_TIME_MASK,
 				  QCA8K_ATU_AGE_TIME(val));
 }
+EXPORT_SYMBOL(qca8k_set_ageing_time);
 
 int qca8k_port_enable(struct dsa_switch *ds, int port,
 		      struct phy_device *phy)
@@ -746,6 +763,7 @@ int qca8k_port_enable(struct dsa_switch *ds, int port,
 
 	return 0;
 }
+EXPORT_SYMBOL(qca8k_port_enable);
 
 void qca8k_port_disable(struct dsa_switch *ds, int port)
 {
@@ -754,6 +772,7 @@ void qca8k_port_disable(struct dsa_switch *ds, int port)
 	qca8k_port_set_status(priv, port, 0);
 	priv->port_enabled_map &= ~BIT(port);
 }
+EXPORT_SYMBOL(qca8k_port_disable);
 
 int qca8k_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
 {
@@ -792,11 +811,13 @@ int qca8k_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
 
 	return ret;
 }
+EXPORT_SYMBOL(qca8k_port_change_mtu);
 
 int qca8k_port_max_mtu(struct dsa_switch *ds, int port)
 {
 	return QCA8K_MAX_MTU;
 }
+EXPORT_SYMBOL(qca8k_port_max_mtu);
 
 int qca8k_port_fdb_insert(struct qca8k_priv *priv, const u8 *addr,
 			  u16 port_mask, u16 vid)
@@ -808,6 +829,7 @@ int qca8k_port_fdb_insert(struct qca8k_priv *priv, const u8 *addr,
 	return qca8k_fdb_add(priv, addr, port_mask, vid,
 			     QCA8K_ATU_STATUS_STATIC);
 }
+EXPORT_SYMBOL(qca8k_port_fdb_insert);
 
 int qca8k_port_fdb_add(struct dsa_switch *ds, int port,
 		       const unsigned char *addr, u16 vid,
@@ -818,6 +840,7 @@ int qca8k_port_fdb_add(struct dsa_switch *ds, int port,
 
 	return qca8k_port_fdb_insert(priv, addr, port_mask, vid);
 }
+EXPORT_SYMBOL(qca8k_port_fdb_add);
 
 int qca8k_port_fdb_del(struct dsa_switch *ds, int port,
 		       const unsigned char *addr, u16 vid,
@@ -831,6 +854,7 @@ int qca8k_port_fdb_del(struct dsa_switch *ds, int port,
 
 	return qca8k_fdb_del(priv, addr, port_mask, vid);
 }
+EXPORT_SYMBOL(qca8k_port_fdb_del);
 
 int qca8k_port_fdb_dump(struct dsa_switch *ds, int port,
 			dsa_fdb_dump_cb_t *cb, void *data)
@@ -854,6 +878,7 @@ int qca8k_port_fdb_dump(struct dsa_switch *ds, int port,
 
 	return 0;
 }
+EXPORT_SYMBOL(qca8k_port_fdb_dump);
 
 int qca8k_port_mdb_add(struct dsa_switch *ds, int port,
 		       const struct switchdev_obj_port_mdb *mdb,
@@ -869,6 +894,7 @@ int qca8k_port_mdb_add(struct dsa_switch *ds, int port,
 	return qca8k_fdb_search_and_insert(priv, BIT(port), addr, vid,
 					   QCA8K_ATU_STATUS_STATIC);
 }
+EXPORT_SYMBOL(qca8k_port_mdb_add);
 
 int qca8k_port_mdb_del(struct dsa_switch *ds, int port,
 		       const struct switchdev_obj_port_mdb *mdb,
@@ -883,10 +909,11 @@ int qca8k_port_mdb_del(struct dsa_switch *ds, int port,
 
 	return qca8k_fdb_search_and_del(priv, BIT(port), addr, vid);
 }
+EXPORT_SYMBOL(qca8k_port_mdb_del);
 
 int qca8k_port_mirror_add(struct dsa_switch *ds, int port,
-			  struct dsa_mall_mirror_tc_entry *mirror,
-			  bool ingress, struct netlink_ext_ack *extack)
+				 struct dsa_mall_mirror_tc_entry *mirror,
+				 bool ingress, struct netlink_ext_ack *extack)
 {
 	struct qca8k_priv *priv = ds->priv;
 	int monitor_port, ret;
@@ -938,6 +965,7 @@ int qca8k_port_mirror_add(struct dsa_switch *ds, int port,
 
 	return 0;
 }
+EXPORT_SYMBOL(qca8k_port_mirror_add);
 
 void qca8k_port_mirror_del(struct dsa_switch *ds, int port,
 			   struct dsa_mall_mirror_tc_entry *mirror)
@@ -974,6 +1002,7 @@ void qca8k_port_mirror_del(struct dsa_switch *ds, int port,
 err:
 	dev_err(priv->dev, "Failed to del mirror port from %d", port);
 }
+EXPORT_SYMBOL(qca8k_port_mirror_del);
 
 int qca8k_port_vlan_filtering(struct dsa_switch *ds, int port,
 			      bool vlan_filtering,
@@ -994,6 +1023,7 @@ int qca8k_port_vlan_filtering(struct dsa_switch *ds, int port,
 
 	return ret;
 }
+EXPORT_SYMBOL(qca8k_port_vlan_filtering);
 
 int qca8k_port_vlan_add(struct dsa_switch *ds, int port,
 			const struct switchdev_obj_port_vlan *vlan,
@@ -1024,6 +1054,7 @@ int qca8k_port_vlan_add(struct dsa_switch *ds, int port,
 
 	return ret;
 }
+EXPORT_SYMBOL(qca8k_port_vlan_add);
 
 int qca8k_port_vlan_del(struct dsa_switch *ds, int port,
 			const struct switchdev_obj_port_vlan *vlan)
@@ -1037,6 +1068,7 @@ int qca8k_port_vlan_del(struct dsa_switch *ds, int port,
 
 	return ret;
 }
+EXPORT_SYMBOL(qca8k_port_vlan_del);
 
 static bool qca8k_lag_can_offload(struct dsa_switch *ds,
 				  struct dsa_lag lag,
@@ -1207,12 +1239,14 @@ int qca8k_port_lag_join(struct dsa_switch *ds, int port, struct dsa_lag lag,
 
 	return qca8k_lag_refresh_portmap(ds, port, lag, false);
 }
+EXPORT_SYMBOL(qca8k_port_lag_join);
 
 int qca8k_port_lag_leave(struct dsa_switch *ds, int port,
 			 struct dsa_lag lag)
 {
 	return qca8k_lag_refresh_portmap(ds, port, lag, true);
 }
+EXPORT_SYMBOL(qca8k_port_lag_leave);
 
 int qca8k_read_switch_id(struct qca8k_priv *priv)
 {
@@ -1242,3 +1276,6 @@ int qca8k_read_switch_id(struct qca8k_priv *priv)
 
 	return 0;
 }
+EXPORT_SYMBOL(qca8k_read_switch_id);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/dsa/qca/qca8k-leds.c b/drivers/net/dsa/qca/qca8k-leds.c
index 90e30c2909e4..eca5abda5e77 100644
--- a/drivers/net/dsa/qca/qca8k-leds.c
+++ b/drivers/net/dsa/qca/qca8k-leds.c
@@ -1,9 +1,9 @@
 // SPDX-License-Identifier: GPL-2.0
 #include <linux/property.h>
 #include <linux/regmap.h>
+#include <linux/dsa/qca8k.h>
 #include <net/dsa.h>
 
-#include "qca8k.h"
 #include "qca8k_leds.h"
 
 static u32 qca8k_phy_to_port(int phy)
diff --git a/drivers/net/dsa/qca/qca8k.h b/include/linux/dsa/qca8k.h
similarity index 99%
rename from drivers/net/dsa/qca/qca8k.h
rename to include/linux/dsa/qca8k.h
index 2ac7e88f8da5..3c75c3704fa0 100644
--- a/drivers/net/dsa/qca/qca8k.h
+++ b/include/linux/dsa/qca8k.h
@@ -13,6 +13,7 @@
 #include <linux/gpio.h>
 #include <linux/leds.h>
 #include <linux/dsa/tag_qca.h>
+#include <net/dsa.h>
 
 #define QCA8K_ETHERNET_MDIO_PRIORITY			7
 #define QCA8K_ETHERNET_PHY_PRIORITY			6
-- 
2.42.0


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

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

* [PATCH net-next v2 4/8] net: qualcomm: ipqess: Add Ethtool ops to IPQESS port netdevices
  2023-11-14  9:07 [PATCH net-next v2 0/8] net: qualcomm: ipqess: introduce Qualcomm IPQESS driver Romain Gantois
  2023-11-14  9:07 ` [PATCH net-next v2 1/8] dt-bindings: net: Introduce the Qualcomm IPQESS Ethernet switch Romain Gantois
  2023-11-14  9:07 ` [PATCH net-next v2 2/8] net: dsa: qca8k: Make the QCA8K hardware library available globally Romain Gantois
@ 2023-11-14  9:07 ` Romain Gantois
  2023-11-14  9:07 ` [PATCH net-next v2 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver Romain Gantois
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Romain Gantois @ 2023-11-14  9:07 UTC (permalink / raw)
  To: davem, Rob Herring, Krzysztof Kozlowski
  Cc: Romain Gantois, Jakub Kicinski, Eric Dumazet, Paolo Abeni, netdev,
	linux-kernel, devicetree, thomas.petazzoni, Andrew Lunn,
	Florian Fainelli, Heiner Kallweit, Russell King, linux-arm-kernel,
	Vladimir Oltean, Luka Perkov, Robert Marko, Andy Gross,
	Bjorn Andersson, Konrad Dybcio

The IPQESS driver registers one netdevice for each front-facing switch
port. Add support for several ethtool operations to these netdevices.

Signed-off-by: Romain Gantois <romain.gantois@bootlin.com>
---
 drivers/net/ethernet/qualcomm/ipqess/Makefile |   2 +-
 .../ethernet/qualcomm/ipqess/ipqess_ethtool.c | 245 ++++++++++++++++++
 .../ethernet/qualcomm/ipqess/ipqess_port.c    |   1 +
 .../ethernet/qualcomm/ipqess/ipqess_port.h    |   3 +
 4 files changed, 250 insertions(+), 1 deletion(-)
 create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_ethtool.c

diff --git a/drivers/net/ethernet/qualcomm/ipqess/Makefile b/drivers/net/ethernet/qualcomm/ipqess/Makefile
index f389080cc5aa..6253f1b0ffd2 100644
--- a/drivers/net/ethernet/qualcomm/ipqess/Makefile
+++ b/drivers/net/ethernet/qualcomm/ipqess/Makefile
@@ -5,4 +5,4 @@
 
 obj-$(CONFIG_QCOM_IPQ4019_ESS) += ipqess.o
 
-ipqess-objs := ipqess_port.o ipqess_switch.o ipqess_edma.o
+ipqess-objs := ipqess_port.o ipqess_switch.o ipqess_edma.o ipqess_ethtool.o
diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_ethtool.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_ethtool.c
new file mode 100644
index 000000000000..06a8f2cccc4e
--- /dev/null
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_ethtool.c
@@ -0,0 +1,245 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Ethtool operations for a single switch port
+ *
+ * Copyright (c) 2023, Romain Gantois <romain.gantois@bootlin.com>
+ * Based on net/dsa
+ */
+
+#include <net/selftests.h>
+
+#include "ipqess_port.h"
+
+static void ipqess_port_get_drvinfo(struct net_device *dev,
+				    struct ethtool_drvinfo *drvinfo)
+{
+	strscpy(drvinfo->driver, "qca8k-ipqess", sizeof(drvinfo->driver));
+	strscpy(drvinfo->bus_info, "platform", sizeof(drvinfo->bus_info));
+}
+
+static int ipqess_port_nway_reset(struct net_device *dev)
+{
+	struct ipqess_port *port = netdev_priv(dev);
+
+	return phylink_ethtool_nway_reset(port->pl);
+}
+
+static const char ipqess_gstrings_base_stats[][ETH_GSTRING_LEN] = {
+	"tx_packets",
+	"tx_bytes",
+	"rx_packets",
+	"rx_bytes",
+};
+
+static void ipqess_port_get_strings(struct net_device *dev,
+				    u32 stringset, u8 *data)
+{
+	struct ipqess_port *port = netdev_priv(dev);
+	struct qca8k_priv *priv = port->sw->priv;
+	int i;
+
+	if (stringset == ETH_SS_STATS) {
+		memcpy(data, &ipqess_gstrings_base_stats,
+		       sizeof(ipqess_gstrings_base_stats));
+
+		if (stringset != ETH_SS_STATS)
+			return;
+
+		for (i = 0; i < priv->info->mib_count; i++)
+			memcpy(data + (4 + i) * ETH_GSTRING_LEN,
+			       ar8327_mib[i].name,
+			       ETH_GSTRING_LEN);
+
+	} else if (stringset == ETH_SS_TEST) {
+		net_selftest_get_strings(data);
+	}
+}
+
+static void ipqess_port_get_ethtool_stats(struct net_device *dev,
+					  struct ethtool_stats *stats,
+					  uint64_t *data)
+{
+	struct ipqess_port *port = netdev_priv(dev);
+	struct qca8k_priv *priv = port->sw->priv;
+	const struct qca8k_mib_desc *mib;
+	struct pcpu_sw_netstats *s;
+	unsigned int start;
+	u32 reg, c, val;
+	u32 hi = 0;
+	int ret;
+	int i;
+
+	for_each_possible_cpu(i) {
+		u64 tx_packets, tx_bytes, rx_packets, rx_bytes;
+
+		s = per_cpu_ptr(dev->tstats, i);
+		do {
+			start = u64_stats_fetch_begin(&s->syncp);
+			tx_packets = u64_stats_read(&s->tx_packets);
+			tx_bytes = u64_stats_read(&s->tx_bytes);
+			rx_packets = u64_stats_read(&s->rx_packets);
+			rx_bytes = u64_stats_read(&s->rx_bytes);
+		} while (u64_stats_fetch_retry(&s->syncp, start));
+		data[0] += tx_packets;
+		data[1] += tx_bytes;
+		data[2] += rx_packets;
+		data[3] += rx_bytes;
+	}
+
+	for (c = 0; c < priv->info->mib_count; c++) {
+		mib = &ar8327_mib[c];
+		reg = QCA8K_PORT_MIB_COUNTER(port->index) + mib->offset;
+
+		ret = qca8k_read(priv, reg, &val);
+		if (ret < 0)
+			continue;
+
+		if (mib->size == 2) {
+			ret = qca8k_read(priv, reg + 4, &hi);
+			if (ret < 0)
+				continue;
+		}
+
+		data[4 + c] = val;
+		if (mib->size == 2)
+			data[4 + c] |= (u64)hi << 32;
+	}
+}
+
+static int ipqess_port_get_sset_count(struct net_device *dev, int sset)
+{
+	struct ipqess_port *port = netdev_priv(dev);
+	struct qca8k_priv *priv = port->sw->priv;
+
+	if (sset == ETH_SS_STATS) {
+		int count = 0;
+
+		if (sset != ETH_SS_STATS)
+			count = 0;
+		else
+			count = priv->info->mib_count;
+
+		if (count < 0)
+			return count;
+
+		return count + 4;
+	} else if (sset == ETH_SS_TEST) {
+		return net_selftest_get_count();
+	}
+
+	return -EOPNOTSUPP;
+}
+
+static int ipqess_port_set_wol(struct net_device *dev,
+			       struct ethtool_wolinfo *w)
+{
+	struct ipqess_port *port = netdev_priv(dev);
+
+	return phylink_ethtool_set_wol(port->pl, w);
+}
+
+static void ipqess_port_get_wol(struct net_device *dev,
+				struct ethtool_wolinfo *w)
+{
+	struct ipqess_port *port = netdev_priv(dev);
+
+	phylink_ethtool_get_wol(port->pl, w);
+}
+
+static int ipqess_port_set_eee(struct net_device *dev, struct ethtool_eee *eee)
+{
+	struct ipqess_port *port = netdev_priv(dev);
+	int ret;
+	u32 lpi_en = QCA8K_REG_EEE_CTRL_LPI_EN(port->index);
+	struct qca8k_priv *priv = port->sw->priv;
+	u32 lpi_ctl1;
+
+	/* Port's PHY and MAC both need to be EEE capable */
+	if (!dev->phydev || !port->pl)
+		return -ENODEV;
+
+	mutex_lock(&priv->reg_mutex);
+	lpi_ctl1 = qca8k_read(priv, QCA8K_REG_EEE_CTRL, &lpi_ctl1);
+	if (lpi_ctl1 < 0) {
+		mutex_unlock(&priv->reg_mutex);
+		return ret;
+	}
+
+	if (eee->tx_lpi_enabled && eee->eee_enabled)
+		lpi_ctl1 |= lpi_en;
+	else
+		lpi_ctl1 &= ~lpi_en;
+	ret = qca8k_write(priv, QCA8K_REG_EEE_CTRL, lpi_ctl1);
+	mutex_unlock(&priv->reg_mutex);
+
+	if (ret)
+		return ret;
+
+	return phylink_ethtool_set_eee(port->pl, eee);
+}
+
+static int ipqess_port_get_eee(struct net_device *dev, struct ethtool_eee *e)
+{
+	struct ipqess_port *port = netdev_priv(dev);
+
+	/* Port's PHY and MAC both need to be EEE capable */
+	if (!dev->phydev || !port->pl)
+		return -ENODEV;
+
+	return phylink_ethtool_get_eee(port->pl, e);
+}
+
+static int ipqess_port_get_link_ksettings(struct net_device *dev,
+					  struct ethtool_link_ksettings *cmd)
+{
+	struct ipqess_port *port = netdev_priv(dev);
+
+	return phylink_ethtool_ksettings_get(port->pl, cmd);
+}
+
+static int ipqess_port_set_link_ksettings(struct net_device *dev,
+					  const struct ethtool_link_ksettings *cmd)
+{
+	struct ipqess_port *port = netdev_priv(dev);
+
+	return phylink_ethtool_ksettings_set(port->pl, cmd);
+}
+
+static void ipqess_port_get_pauseparam(struct net_device *dev,
+				       struct ethtool_pauseparam *pause)
+{
+	struct ipqess_port *port = netdev_priv(dev);
+
+	phylink_ethtool_get_pauseparam(port->pl, pause);
+}
+
+static int ipqess_port_set_pauseparam(struct net_device *dev,
+				      struct ethtool_pauseparam *pause)
+{
+	struct ipqess_port *port = netdev_priv(dev);
+
+	return phylink_ethtool_set_pauseparam(port->pl, pause);
+}
+
+static const struct ethtool_ops ipqess_port_ethtool_ops = {
+	.get_drvinfo            = ipqess_port_get_drvinfo,
+	.nway_reset             = ipqess_port_nway_reset,
+	.get_link               = ethtool_op_get_link,
+	.get_strings            = ipqess_port_get_strings,
+	.get_ethtool_stats      = ipqess_port_get_ethtool_stats,
+	.get_sset_count         = ipqess_port_get_sset_count,
+	.self_test              = net_selftest,
+	.set_wol                = ipqess_port_set_wol,
+	.get_wol                = ipqess_port_get_wol,
+	.set_eee                = ipqess_port_set_eee,
+	.get_eee                = ipqess_port_get_eee,
+	.get_link_ksettings     = ipqess_port_get_link_ksettings,
+	.set_link_ksettings     = ipqess_port_set_link_ksettings,
+	.get_pauseparam         = ipqess_port_get_pauseparam,
+	.set_pauseparam         = ipqess_port_set_pauseparam,
+};
+
+void ipqess_port_set_ethtool_ops(struct net_device *netdev)
+{
+	netdev->ethtool_ops = &ipqess_port_ethtool_ops;
+}
diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
index f0f5fe3a7c24..52d7baa7cae0 100644
--- a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
@@ -684,6 +684,7 @@ int ipqess_port_register(struct ipqess_switch *sw,
 	netdev->dev.of_node = port->dn;
 
 	netdev->rtnl_link_ops = &ipqess_port_link_ops;
+	ipqess_port_set_ethtool_ops(netdev);
 
 	netdev->tstats = netdev_alloc_pcpu_stats(struct pcpu_sw_netstats);
 	if (!netdev->tstats) {
diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
index 26bac45dd811..19d4b5d73625 100644
--- a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
@@ -59,4 +59,7 @@ int ipqess_port_register(struct ipqess_switch *sw,
 			 struct device_node *port_node);
 void ipqess_port_unregister(struct ipqess_port *port);
 
+/* Defined in ipqess_ethtool.c */
+void ipqess_port_set_ethtool_ops(struct net_device *netdev);
+
 #endif
-- 
2.42.0


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

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

* [PATCH net-next v2 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver
  2023-11-14  9:07 [PATCH net-next v2 0/8] net: qualcomm: ipqess: introduce Qualcomm IPQESS driver Romain Gantois
                   ` (2 preceding siblings ...)
  2023-11-14  9:07 ` [PATCH net-next v2 4/8] net: qualcomm: ipqess: Add Ethtool ops to IPQESS port netdevices Romain Gantois
@ 2023-11-14  9:07 ` Romain Gantois
  2023-11-14  9:07 ` [PATCH net-next v2 6/8] net: phy: add calibration callbacks to phy_driver Romain Gantois
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Romain Gantois @ 2023-11-14  9:07 UTC (permalink / raw)
  To: davem, Rob Herring, Krzysztof Kozlowski
  Cc: Romain Gantois, Jakub Kicinski, Eric Dumazet, Paolo Abeni, netdev,
	linux-kernel, devicetree, thomas.petazzoni, Andrew Lunn,
	Florian Fainelli, Heiner Kallweit, Russell King, linux-arm-kernel,
	Vladimir Oltean, Luka Perkov, Robert Marko, Andy Gross,
	Bjorn Andersson, Konrad Dybcio

The IPQ4019 ESS switch is capable of offloading various bridge features.
Add netdev and switchdev notifiers to offload bridge uppers, link state
changes, FDB and MDB accesses and VLANs.

Signed-off-by: Romain Gantois <romain.gantois@bootlin.com>
---
 drivers/net/dsa/qca/qca8k-8xxx.c              |   49 +-
 drivers/net/dsa/qca/qca8k-common.c            |   42 +-
 drivers/net/ethernet/qualcomm/Kconfig         |    1 +
 drivers/net/ethernet/qualcomm/ipqess/Makefile |    2 +-
 .../ethernet/qualcomm/ipqess/ipqess_edma.c    |    7 +
 .../qualcomm/ipqess/ipqess_notifiers.c        |  306 +++++
 .../ethernet/qualcomm/ipqess/ipqess_port.c    | 1050 +++++++++++++++--
 .../ethernet/qualcomm/ipqess/ipqess_port.h    |   33 +
 .../ethernet/qualcomm/ipqess/ipqess_switch.c  |   15 +-
 include/linux/dsa/qca8k.h                     |   16 +-
 10 files changed, 1391 insertions(+), 130 deletions(-)
 create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_notifiers.c

diff --git a/drivers/net/dsa/qca/qca8k-8xxx.c b/drivers/net/dsa/qca/qca8k-8xxx.c
index 210667755b00..7f2bde8ed311 100644
--- a/drivers/net/dsa/qca/qca8k-8xxx.c
+++ b/drivers/net/dsa/qca/qca8k-8xxx.c
@@ -1979,34 +1979,71 @@ qca8k_setup(struct dsa_switch *ds)
 	return 0;
 }
 
+int qca8k_dsa_port_fdb_dump(struct dsa_switch *ds, int port,
+			    dsa_fdb_dump_cb_t *cb, void *data)
+{
+	return qca8k_port_fdb_dump(ds->priv, port, cb, data);
+}
+
+void qca8k_dsa_port_stp_state_set(struct dsa_switch *ds, int port,
+				  u8 state)
+{
+	qca8k_port_stp_state_set(ds->priv, port, state,
+				 dsa_to_port(ds, port)->learning, true);
+}
+
+void qca8k_dsa_port_fast_age(struct dsa_switch *ds, int port)
+{
+	qca8k_port_fast_age(ds->priv, port);
+}
+
+int qca8k_dsa_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
+{
+	return qca8k_set_ageing_time(ds->priv, msecs);
+}
+
+int qca8k_dsa_port_vlan_filtering(struct dsa_switch *ds, int port,
+				  bool vlan_filtering,
+				  struct netlink_ext_ack *extack)
+{
+	return qca8k_port_vlan_filtering(ds->priv, port, vlan_filtering);
+}
+
+int qca8k_dsa_vlan_add(struct dsa_switch *ds, int port,
+		       const struct switchdev_obj_port_vlan *vlan,
+		       struct netlink_ext_ack *extack)
+{
+	return qca8k_port_vlan_add(ds->priv, port, vlan, extack);
+}
+
 static const struct dsa_switch_ops qca8k_switch_ops = {
 	.get_tag_protocol	= qca8k_get_tag_protocol,
 	.setup			= qca8k_setup,
 	.get_strings		= qca8k_get_strings,
 	.get_ethtool_stats	= qca8k_get_ethtool_stats,
 	.get_sset_count		= qca8k_get_sset_count,
-	.set_ageing_time	= qca8k_set_ageing_time,
+	.set_ageing_time	= qca8k_dsa_set_ageing_time,
 	.get_mac_eee		= qca8k_get_mac_eee,
 	.set_mac_eee		= qca8k_set_mac_eee,
 	.port_enable		= qca8k_port_enable,
 	.port_disable		= qca8k_port_disable,
 	.port_change_mtu	= qca8k_port_change_mtu,
 	.port_max_mtu		= qca8k_port_max_mtu,
-	.port_stp_state_set	= qca8k_port_stp_state_set,
+	.port_stp_state_set	= qca8k_dsa_port_stp_state_set,
 	.port_pre_bridge_flags	= qca8k_port_pre_bridge_flags,
 	.port_bridge_flags	= qca8k_port_bridge_flags,
 	.port_bridge_join	= qca8k_port_bridge_join,
 	.port_bridge_leave	= qca8k_port_bridge_leave,
-	.port_fast_age		= qca8k_port_fast_age,
+	.port_fast_age		= qca8k_dsa_port_fast_age,
 	.port_fdb_add		= qca8k_port_fdb_add,
 	.port_fdb_del		= qca8k_port_fdb_del,
-	.port_fdb_dump		= qca8k_port_fdb_dump,
+	.port_fdb_dump		= qca8k_dsa_port_fdb_dump,
 	.port_mdb_add		= qca8k_port_mdb_add,
 	.port_mdb_del		= qca8k_port_mdb_del,
 	.port_mirror_add	= qca8k_port_mirror_add,
 	.port_mirror_del	= qca8k_port_mirror_del,
-	.port_vlan_filtering	= qca8k_port_vlan_filtering,
-	.port_vlan_add		= qca8k_port_vlan_add,
+	.port_vlan_filtering	= qca8k_dsa_port_vlan_filtering,
+	.port_vlan_add		= qca8k_dsa_vlan_add,
 	.port_vlan_del		= qca8k_port_vlan_del,
 	.phylink_get_caps	= qca8k_phylink_get_caps,
 	.phylink_mac_select_pcs	= qca8k_phylink_mac_select_pcs,
diff --git a/drivers/net/dsa/qca/qca8k-common.c b/drivers/net/dsa/qca/qca8k-common.c
index a66a821ce4d6..3d6ff6f24288 100644
--- a/drivers/net/dsa/qca/qca8k-common.c
+++ b/drivers/net/dsa/qca/qca8k-common.c
@@ -595,11 +595,9 @@ int qca8k_get_mac_eee(struct dsa_switch *ds, int port,
 }
 EXPORT_SYMBOL_GPL(qca8k_get_mac_eee);
 
-static int qca8k_port_configure_learning(struct dsa_switch *ds, int port,
+static int qca8k_port_configure_learning(struct qca8k_priv *priv, int port,
 					 bool learning)
 {
-	struct qca8k_priv *priv = ds->priv;
-
 	if (learning)
 		return regmap_set_bits(priv->regmap,
 				       QCA8K_PORT_LOOKUP_CTRL(port),
@@ -610,10 +608,10 @@ static int qca8k_port_configure_learning(struct dsa_switch *ds, int port,
 					 QCA8K_PORT_LOOKUP_LEARN);
 }
 
-void qca8k_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
+void qca8k_port_stp_state_set(struct qca8k_priv *priv,
+			      int port, u8 state,
+			      bool port_learning, int set_learning)
 {
-	struct dsa_port *dp = dsa_to_port(ds, port);
-	struct qca8k_priv *priv = ds->priv;
 	bool learning = false;
 	u32 stp_state;
 
@@ -629,10 +627,10 @@ void qca8k_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
 		break;
 	case BR_STATE_LEARNING:
 		stp_state = QCA8K_PORT_LOOKUP_STATE_LEARNING;
-		learning = dp->learning;
+		learning = port_learning;
 		break;
 	case BR_STATE_FORWARDING:
-		learning = dp->learning;
+		learning = port_learning;
 		fallthrough;
 	default:
 		stp_state = QCA8K_PORT_LOOKUP_STATE_FORWARD;
@@ -642,7 +640,8 @@ void qca8k_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
 	qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
 		  QCA8K_PORT_LOOKUP_STATE_MASK, stp_state);
 
-	qca8k_port_configure_learning(ds, port, learning);
+	if (set_learning)
+		qca8k_port_configure_learning(priv, port, learning);
 }
 EXPORT_SYMBOL_GPL(qca8k_port_stp_state_set);
 
@@ -664,7 +663,7 @@ int qca8k_port_bridge_flags(struct dsa_switch *ds, int port,
 	int ret;
 
 	if (flags.mask & BR_LEARNING) {
-		ret = qca8k_port_configure_learning(ds, port,
+		ret = qca8k_port_configure_learning(ds->priv, port,
 						    flags.val & BR_LEARNING);
 		if (ret)
 			return ret;
@@ -740,19 +739,16 @@ void qca8k_port_bridge_leave(struct dsa_switch *ds, int port,
 }
 EXPORT_SYMBOL_GPL(qca8k_port_bridge_leave);
 
-void qca8k_port_fast_age(struct dsa_switch *ds, int port)
+void qca8k_port_fast_age(struct qca8k_priv *priv, int port)
 {
-	struct qca8k_priv *priv = ds->priv;
-
 	mutex_lock(&priv->reg_mutex);
 	qca8k_fdb_access(priv, QCA8K_FDB_FLUSH_PORT, port);
 	mutex_unlock(&priv->reg_mutex);
 }
 EXPORT_SYMBOL_GPL(qca8k_port_fast_age);
 
-int qca8k_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
+int qca8k_set_ageing_time(struct qca8k_priv *priv, unsigned int msecs)
 {
-	struct qca8k_priv *priv = ds->priv;
 	unsigned int secs = msecs / 1000;
 	u32 val;
 
@@ -877,10 +873,9 @@ int qca8k_port_fdb_del(struct dsa_switch *ds, int port,
 }
 EXPORT_SYMBOL_GPL(qca8k_port_fdb_del);
 
-int qca8k_port_fdb_dump(struct dsa_switch *ds, int port,
+int qca8k_port_fdb_dump(struct qca8k_priv *priv, int port,
 			dsa_fdb_dump_cb_t *cb, void *data)
 {
-	struct qca8k_priv *priv = ds->priv;
 	struct qca8k_fdb _fdb = { 0 };
 	int cnt = QCA8K_NUM_FDB_RECORDS;
 	bool is_static;
@@ -933,8 +928,8 @@ int qca8k_port_mdb_del(struct dsa_switch *ds, int port,
 EXPORT_SYMBOL_GPL(qca8k_port_mdb_del);
 
 int qca8k_port_mirror_add(struct dsa_switch *ds, int port,
-				 struct dsa_mall_mirror_tc_entry *mirror,
-				 bool ingress, struct netlink_ext_ack *extack)
+			  struct dsa_mall_mirror_tc_entry *mirror,
+			  bool ingress, struct netlink_ext_ack *extack)
 {
 	struct qca8k_priv *priv = ds->priv;
 	int monitor_port, ret;
@@ -1025,11 +1020,9 @@ void qca8k_port_mirror_del(struct dsa_switch *ds, int port,
 }
 EXPORT_SYMBOL_GPL(qca8k_port_mirror_del);
 
-int qca8k_port_vlan_filtering(struct dsa_switch *ds, int port,
-			      bool vlan_filtering,
-			      struct netlink_ext_ack *extack)
+int qca8k_port_vlan_filtering(struct qca8k_priv *priv, int port,
+			      bool vlan_filtering)
 {
-	struct qca8k_priv *priv = ds->priv;
 	int ret;
 
 	if (vlan_filtering) {
@@ -1046,13 +1039,12 @@ int qca8k_port_vlan_filtering(struct dsa_switch *ds, int port,
 }
 EXPORT_SYMBOL_GPL(qca8k_port_vlan_filtering);
 
-int qca8k_port_vlan_add(struct dsa_switch *ds, int port,
+int qca8k_port_vlan_add(struct qca8k_priv *priv, int port,
 			const struct switchdev_obj_port_vlan *vlan,
 			struct netlink_ext_ack *extack)
 {
 	bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
 	bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
-	struct qca8k_priv *priv = ds->priv;
 	int ret;
 
 	ret = qca8k_vlan_add(priv, port, vlan->vid, untagged);
diff --git a/drivers/net/ethernet/qualcomm/Kconfig b/drivers/net/ethernet/qualcomm/Kconfig
index 008d20ec9eae..15d120d9e64a 100644
--- a/drivers/net/ethernet/qualcomm/Kconfig
+++ b/drivers/net/ethernet/qualcomm/Kconfig
@@ -66,6 +66,7 @@ config QCOM_IPQ4019_ESS
 	depends on (OF && ARCH_QCOM) || COMPILE_TEST
 	select PHYLINK
 	select NET_DSA
+	select NET_SWITCHDEV
 	select NET_DSA_QCA8K_LIB
 	select PAGE_POOL
 	help
diff --git a/drivers/net/ethernet/qualcomm/ipqess/Makefile b/drivers/net/ethernet/qualcomm/ipqess/Makefile
index 6253f1b0ffd2..b12142bbc7e5 100644
--- a/drivers/net/ethernet/qualcomm/ipqess/Makefile
+++ b/drivers/net/ethernet/qualcomm/ipqess/Makefile
@@ -5,4 +5,4 @@
 
 obj-$(CONFIG_QCOM_IPQ4019_ESS) += ipqess.o
 
-ipqess-objs := ipqess_port.o ipqess_switch.o ipqess_edma.o ipqess_ethtool.o
+ipqess-objs := ipqess_port.o ipqess_switch.o ipqess_edma.o ipqess_ethtool.o ipqess_notifiers.o
diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_edma.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_edma.c
index 008e92de9eb7..13be40c70750 100644
--- a/drivers/net/ethernet/qualcomm/ipqess/ipqess_edma.c
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_edma.c
@@ -22,6 +22,7 @@
 #include "ipqess_edma.h"
 #include "ipqess_port.h"
 #include "ipqess_switch.h"
+#include "ipqess_notifiers.h"
 
 static void ipqess_edma_w32(struct ipqess_edma *edma, u32 reg, u32 val)
 {
@@ -1152,6 +1153,10 @@ int ipqess_edma_init(struct platform_device *pdev, struct device_node *np)
 			port->edma = edma;
 	}
 
+	err = ipqess_notifiers_register();
+	if (err)
+		goto err_hw_stop;
+
 	return 0;
 
 err_hw_stop:
@@ -1172,6 +1177,8 @@ void ipqess_edma_uninit(struct ipqess_edma *edma)
 	struct qca8k_priv *priv = edma->sw->priv;
 	u32 val;
 
+	ipqess_notifiers_unregister();
+
 	ipqess_edma_irq_disable(edma);
 	ipqess_edma_hw_stop(edma);
 
diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_notifiers.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_notifiers.c
new file mode 100644
index 000000000000..77f6d79c2ff6
--- /dev/null
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_notifiers.c
@@ -0,0 +1,306 @@
+// SPDX-License-Identifier: GPL-2.0 OR ISC
+/*
+ * Copyright (c) 2023, Romain Gantois <romain.gantois@bootlin.com>
+ * Based on net/dsa/slave.c
+ */
+
+#include <net/switchdev.h>
+
+#include <linux/etherdevice.h>
+#include <linux/if_vlan.h>
+#include <linux/if_hsr.h>
+
+#include "ipqess_notifiers.h"
+#include "ipqess_port.h"
+
+static struct workqueue_struct *ipqess_owq;
+
+static bool ipqess_schedule_work(struct work_struct *work)
+{
+	return queue_work(ipqess_owq, work);
+}
+
+void ipqess_flush_workqueue(void)
+{
+	flush_workqueue(ipqess_owq);
+}
+
+/* switchdev */
+
+static int ipqess_port_fdb_event(struct net_device *netdev,
+				 struct net_device *orig_netdev,
+				 unsigned long event, const void *ctx,
+				 const struct switchdev_notifier_fdb_info *fdb_info)
+{
+	struct ipqess_switchdev_event_work *switchdev_work;
+	struct ipqess_port *port = netdev_priv(netdev);
+	bool host_addr = fdb_info->is_local;
+
+	if (ctx && ctx != port)
+		return 0;
+
+	if (!port->bridge)
+		return 0;
+
+	if (switchdev_fdb_is_dynamically_learned(fdb_info) &&
+	    ipqess_port_offloads_bridge_port(port, orig_netdev))
+		return 0;
+
+	/* Also treat FDB entries on foreign interfaces bridged with us as host
+	 * addresses.
+	 */
+	if (ipqess_port_dev_is_foreign(netdev, orig_netdev))
+		host_addr = true;
+
+	switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC);
+	if (!switchdev_work)
+		return -ENOMEM;
+
+	netdev_dbg(netdev, "%s FDB entry towards %s, addr %pM vid %d%s\n",
+		   event == SWITCHDEV_FDB_ADD_TO_DEVICE ? "Adding" : "Deleting",
+		   orig_netdev->name, fdb_info->addr, fdb_info->vid,
+		   host_addr ? " as host address" : "");
+
+	INIT_WORK(&switchdev_work->work, ipqess_port_switchdev_event_work);
+	switchdev_work->event = event;
+	switchdev_work->netdev = netdev;
+	switchdev_work->orig_netdev = orig_netdev;
+
+	ether_addr_copy(switchdev_work->addr, fdb_info->addr);
+	switchdev_work->vid = fdb_info->vid;
+	switchdev_work->host_addr = host_addr;
+
+	ipqess_schedule_work(&switchdev_work->work);
+
+	return 0;
+}
+
+/* Called under rcu_read_lock() */
+static int ipqess_switchdev_event(struct notifier_block *unused,
+				  unsigned long event, void *ptr)
+{
+	struct net_device *netdev = switchdev_notifier_info_to_dev(ptr);
+	int err;
+
+	switch (event) {
+	case SWITCHDEV_PORT_ATTR_SET:
+		err = switchdev_handle_port_attr_set(netdev, ptr,
+						     ipqess_port_recognize_netdev,
+						     ipqess_port_attr_set);
+		return notifier_from_errno(err);
+	case SWITCHDEV_FDB_ADD_TO_DEVICE:
+	case SWITCHDEV_FDB_DEL_TO_DEVICE:
+		err = switchdev_handle_fdb_event_to_device(netdev, event, ptr,
+							   ipqess_port_recognize_netdev,
+							   ipqess_port_dev_is_foreign,
+							   ipqess_port_fdb_event);
+		return notifier_from_errno(err);
+	default:
+		return NOTIFY_DONE;
+	}
+
+	return NOTIFY_OK;
+}
+
+static int ipqess_switchdev_blocking_event(struct notifier_block *unused,
+					   unsigned long event, void *ptr)
+{
+	struct net_device *netdev = switchdev_notifier_info_to_dev(ptr);
+	int err;
+
+	switch (event) {
+	case SWITCHDEV_PORT_OBJ_ADD:
+		err = switchdev_handle_port_obj_add_foreign(netdev, ptr,
+							    ipqess_port_recognize_netdev,
+							    ipqess_port_dev_is_foreign,
+							    ipqess_port_obj_add);
+		return notifier_from_errno(err);
+	case SWITCHDEV_PORT_OBJ_DEL:
+		err = switchdev_handle_port_obj_del_foreign(netdev, ptr,
+							    ipqess_port_recognize_netdev,
+							    ipqess_port_dev_is_foreign,
+							    ipqess_port_obj_del);
+		return notifier_from_errno(err);
+	case SWITCHDEV_PORT_ATTR_SET:
+		err = switchdev_handle_port_attr_set(netdev, ptr,
+						     ipqess_port_recognize_netdev,
+						     ipqess_port_attr_set);
+		return notifier_from_errno(err);
+	}
+
+	return NOTIFY_DONE;
+}
+
+/* netdevice */
+
+static int ipqess_port_changeupper(struct net_device *netdev,
+				   struct netdev_notifier_changeupper_info *info)
+{
+	struct ipqess_port *port = netdev_priv(netdev);
+	struct netlink_ext_ack *extack;
+	int err = NOTIFY_DONE;
+
+	if (!ipqess_port_recognize_netdev(netdev))
+		return err;
+
+	extack = netdev_notifier_info_to_extack(&info->info);
+
+	if (netif_is_bridge_master(info->upper_dev)) {
+		if (info->linking) {
+			err = ipqess_port_bridge_join(port, info->upper_dev, extack);
+			if (err == -EOPNOTSUPP) {
+				NL_SET_ERR_MSG_WEAK_MOD(extack,
+							"Offloading not supported");
+				err = NOTIFY_DONE;
+			}
+			err = notifier_from_errno(err);
+		} else {
+			ipqess_port_bridge_leave(port, info->upper_dev);
+			err = NOTIFY_OK;
+		}
+	} else if (netif_is_lag_master(info->upper_dev)) {
+		/* LAG offloading is not supported by this driver */
+		NL_SET_ERR_MSG_WEAK_MOD(extack,
+					"Offloading not supported");
+		err = NOTIFY_DONE;
+	} else if (is_hsr_master(info->upper_dev)) {
+		if (info->linking) {
+			NL_SET_ERR_MSG_WEAK_MOD(extack,
+						"Offloading not supported");
+			err = NOTIFY_DONE;
+		} else {
+			err = NOTIFY_OK;
+		}
+	}
+
+	return err;
+}
+
+static int ipqess_port_prechangeupper(struct net_device *netdev,
+				      struct netdev_notifier_changeupper_info *info)
+{
+	struct ipqess_port *port = netdev_priv(netdev);
+	struct net_device *brport_dev;
+	int err;
+
+	/* sanity check */
+	if (is_vlan_dev(info->upper_dev)) {
+		err = ipqess_port_check_8021q_upper(netdev, info);
+		if (notifier_to_errno(err))
+			return err;
+	}
+
+	/* prechangeupper */
+	if (netif_is_bridge_master(info->upper_dev) && !info->linking)
+		brport_dev = ipqess_port_get_bridged_netdev(port);
+	else
+		return NOTIFY_DONE;
+
+	if (!brport_dev)
+		return NOTIFY_DONE;
+
+	switchdev_bridge_port_unoffload(brport_dev, port,
+					&ipqess_switchdev_notifier,
+					&ipqess_switchdev_blocking_notifier);
+
+	ipqess_flush_workqueue();
+
+	return NOTIFY_DONE;
+}
+
+static int ipqess_netdevice_event(struct notifier_block *nb,
+				  unsigned long event, void *ptr)
+{
+	struct net_device *netdev = netdev_notifier_info_to_dev(ptr);
+	int err;
+
+	if (!ipqess_port_recognize_netdev(netdev))
+		return NOTIFY_DONE;
+
+	switch (event) {
+	case NETDEV_PRECHANGEUPPER: {
+		err = ipqess_port_prechangeupper(netdev, ptr);
+		if (notifier_to_errno(err))
+			return err;
+
+		break;
+	}
+
+	case NETDEV_CHANGEUPPER: {
+		err = ipqess_port_changeupper(netdev, ptr);
+		if (notifier_to_errno(err))
+			return err;
+
+		break;
+	}
+
+	/* Handling this is only useful for LAG offloading, which this driver
+	 * doesn't support
+	 */
+	case NETDEV_CHANGELOWERSTATE:
+		return NOTIFY_DONE;
+	case NETDEV_CHANGE:
+	case NETDEV_UP:
+	case NETDEV_GOING_DOWN:
+	default:
+		break;
+	}
+
+	return NOTIFY_OK;
+}
+
+struct notifier_block ipqess_switchdev_notifier = {
+	.notifier_call = ipqess_switchdev_event,
+};
+
+struct notifier_block ipqess_switchdev_blocking_notifier = {
+	.notifier_call = ipqess_switchdev_blocking_event,
+};
+
+static struct notifier_block ipqess_nb __read_mostly = {
+	.notifier_call = ipqess_netdevice_event,
+};
+
+int ipqess_notifiers_register(void)
+{
+	int err;
+
+	ipqess_owq = alloc_ordered_workqueue("ipqess_ordered",
+					     WQ_MEM_RECLAIM);
+	if (!ipqess_owq)
+		return -ENOMEM;
+
+	err = register_netdevice_notifier(&ipqess_nb);
+	if (err)
+		goto err_netdev_nb;
+
+	err = register_switchdev_notifier(&ipqess_switchdev_notifier);
+	if (err)
+		goto err_switchdev_nb;
+
+	err = register_switchdev_blocking_notifier(&ipqess_switchdev_blocking_notifier);
+	if (err)
+		goto err_switchdev_blocking_nb;
+
+	return 0;
+
+err_switchdev_blocking_nb:
+	unregister_switchdev_notifier(&ipqess_switchdev_notifier);
+err_switchdev_nb:
+	unregister_netdevice_notifier(&ipqess_nb);
+err_netdev_nb:
+	destroy_workqueue(ipqess_owq);
+
+	return err;
+}
+EXPORT_SYMBOL(ipqess_notifiers_register);
+
+void ipqess_notifiers_unregister(void)
+{
+	unregister_switchdev_blocking_notifier(&ipqess_switchdev_blocking_notifier);
+	unregister_switchdev_notifier(&ipqess_switchdev_notifier);
+	unregister_netdevice_notifier(&ipqess_nb);
+
+	destroy_workqueue(ipqess_owq);
+}
+EXPORT_SYMBOL(ipqess_notifiers_unregister);
diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
index 52d7baa7cae0..29420820c3d8 100644
--- a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
@@ -23,50 +23,50 @@ static struct device_type ipqess_port_type = {
 	.name	= "switch",
 };
 
+struct net_device *ipqess_port_get_bridged_netdev(const struct ipqess_port *port)
+{
+	if (!port->bridge)
+		return NULL;
+
+	return port->netdev;
+}
+
 /* netdev ops */
 
+static void ipqess_port_notify_bridge_fdb_flush(const struct ipqess_port *port,
+						u16 vid)
+{
+	struct net_device *brport_dev = ipqess_port_get_bridged_netdev(port);
+	struct switchdev_notifier_fdb_info info = {
+		.vid = vid,
+	};
+
+	/* When the port becomes standalone it has already left the bridge.
+	 * Don't notify the bridge in that case.
+	 */
+	if (!brport_dev)
+		return;
+
+	call_switchdev_notifiers(SWITCHDEV_FDB_FLUSH_TO_BRIDGE,
+				 brport_dev, &info.info, NULL);
+}
+
 static void ipqess_port_fast_age(const struct ipqess_port *port)
 {
 	struct qca8k_priv *priv = port->sw->priv;
 
-	mutex_lock(&priv->reg_mutex);
-	qca8k_fdb_access(priv, QCA8K_FDB_FLUSH_PORT, port->index);
-	mutex_unlock(&priv->reg_mutex);
+	qca8k_port_fast_age(priv, port->index);
+
+	/* Flush all VLANs */
+	ipqess_port_notify_bridge_fdb_flush(port, 0);
 }
 
 static void ipqess_port_stp_state_set(struct ipqess_port *port,
 				      u8 state)
 {
 	struct qca8k_priv *priv = port->sw->priv;
-	u32 stp_state;
-	int err;
 
-	switch (state) {
-	case BR_STATE_DISABLED:
-		stp_state = QCA8K_PORT_LOOKUP_STATE_DISABLED;
-		break;
-	case BR_STATE_BLOCKING:
-		stp_state = QCA8K_PORT_LOOKUP_STATE_BLOCKING;
-		break;
-	case BR_STATE_LISTENING:
-		stp_state = QCA8K_PORT_LOOKUP_STATE_LISTENING;
-		break;
-	case BR_STATE_LEARNING:
-		stp_state = QCA8K_PORT_LOOKUP_STATE_LEARNING;
-		break;
-	case BR_STATE_FORWARDING:
-	default:
-		stp_state = QCA8K_PORT_LOOKUP_STATE_FORWARD;
-		break;
-	}
-
-	err = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port->index),
-			QCA8K_PORT_LOOKUP_STATE_MASK, stp_state);
-
-	if (err)
-		dev_warn(priv->dev,
-			 "failed to set STP state %d for port %d: err %d\n",
-			 stp_state, port->index, err);
+	qca8k_port_stp_state_set(priv, port->index, state, false, false);
 }
 
 static void ipqess_port_set_state_now(struct ipqess_port *port,
@@ -93,7 +93,8 @@ static int ipqess_port_enable_rt(struct ipqess_port *port,
 
 	phy_support_asym_pause(phy);
 
-	ipqess_port_set_state_now(port, BR_STATE_FORWARDING, false);
+	if (!port->bridge)
+		ipqess_port_set_state_now(port, BR_STATE_FORWARDING, false);
 
 	if (port->pl)
 		phylink_start(port->pl);
@@ -108,7 +109,8 @@ static void ipqess_port_disable_rt(struct ipqess_port *port)
 	if (port->pl)
 		phylink_stop(port->pl);
 
-	ipqess_port_set_state_now(port, BR_STATE_DISABLED, false);
+	if (!port->bridge)
+		ipqess_port_set_state_now(port, BR_STATE_DISABLED, false);
 
 	qca8k_port_set_status(priv, port->index, 0);
 	priv->port_enabled_map &= ~BIT(port->index);
@@ -204,34 +206,9 @@ static int ipqess_port_change_mtu(struct net_device *dev, int new_mtu)
 	return 0;
 }
 
-static int ipqess_port_do_vlan_add(struct qca8k_priv *priv, int port_index,
-				   const struct switchdev_obj_port_vlan *vlan,
-				   struct netlink_ext_ack *extack)
+static inline struct net_device *ipqess_port_bridge_dev_get(struct ipqess_port *port)
 {
-	bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
-	bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
-	int ret;
-
-	ret = qca8k_vlan_add(priv, port_index, vlan->vid, untagged);
-	if (ret) {
-		dev_err(priv->dev, "Failed to add VLAN to port %d (%d)", port_index,
-			ret);
-		return ret;
-	}
-
-	if (pvid) {
-		ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(port_index),
-				QCA8K_EGREES_VLAN_PORT_MASK(port_index),
-				QCA8K_EGREES_VLAN_PORT(port_index, vlan->vid));
-		if (ret)
-			return ret;
-
-		ret = qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(port_index),
-				  QCA8K_PORT_VLAN_CVID(vlan->vid) |
-				  QCA8K_PORT_VLAN_SVID(vlan->vid));
-	}
-
-	return ret;
+	return port->bridge ? port->bridge->netdev : NULL;
 }
 
 static int ipqess_port_vlan_rx_add_vid(struct net_device *dev, __be16 proto,
@@ -248,7 +225,7 @@ static int ipqess_port_vlan_rx_add_vid(struct net_device *dev, __be16 proto,
 	int ret;
 
 	/* User port... */
-	ret = ipqess_port_do_vlan_add(port->sw->priv, port->index, &vlan, &extack);
+	ret = qca8k_port_vlan_add(port->sw->priv, port->index, &vlan, &extack);
 	if (ret) {
 		if (extack._msg)
 			netdev_err(dev, "%s\n", extack._msg);
@@ -256,7 +233,7 @@ static int ipqess_port_vlan_rx_add_vid(struct net_device *dev, __be16 proto,
 	}
 
 	/* And CPU port... */
-	ret = ipqess_port_do_vlan_add(port->sw->priv, 0, &vlan, &extack);
+	ret = qca8k_port_vlan_add(port->sw->priv, 0, &vlan, &extack);
 	if (ret) {
 		if (extack._msg)
 			netdev_err(dev, "CPU port %d: %s\n", 0, extack._msg);
@@ -340,24 +317,13 @@ ipqess_port_fdb_dump(struct sk_buff *skb, struct netlink_callback *cb,
 		.cb = cb,
 		.idx = *idx,
 	};
-	int cnt = QCA8K_NUM_FDB_RECORDS;
-	struct qca8k_fdb _fdb = { 0 };
-	bool is_static;
 	int ret = 0;
 
-	mutex_lock(&priv->reg_mutex);
-	while (cnt-- && !qca8k_fdb_next(priv, &_fdb, port->index)) {
-		if (!_fdb.aging)
-			break;
-		is_static = (_fdb.aging == QCA8K_ATU_STATUS_STATIC);
-		ret = ipqess_port_fdb_do_dump(_fdb.mac, _fdb.vid, is_static, &dump);
-		if (ret)
-			break;
-	}
-	mutex_unlock(&priv->reg_mutex);
-
 	*idx = dump.idx;
 
+	ret = qca8k_port_fdb_dump(priv, port->index, ipqess_port_fdb_do_dump,
+				  &dump);
+
 	return ret;
 }
 
@@ -374,6 +340,882 @@ static const struct net_device_ops ipqess_port_netdev_ops = {
 	.ndo_fdb_dump           = ipqess_port_fdb_dump,
 };
 
+/* Bridge ops */
+
+static int ipqess_port_bridge_alloc(struct ipqess_port *port,
+				    struct net_device *br,
+				    struct netlink_ext_ack *extack)
+{
+	struct ipqess_bridge *bridge;
+
+	bridge = kzalloc(sizeof(*bridge), GFP_KERNEL);
+	if (!bridge)
+		return -ENOMEM;
+
+	refcount_set(&bridge->refcount, 1);
+
+	bridge->netdev = br;
+
+	port->bridge = bridge;
+
+	return 0;
+}
+
+/* Must be called under rcu_read_lock() */
+static bool ipqess_port_can_apply_vlan_filtering(struct ipqess_port *port,
+						 bool vlan_filtering,
+						 struct netlink_ext_ack *extack)
+{
+	int err;
+
+	/* VLAN awareness was off, so the question is "can we turn it on".
+	 * We may have had 8021q uppers, those need to go. Make sure we don't
+	 * enter an inconsistent state: deny changing the VLAN awareness state
+	 * as long as we have 8021q uppers.
+	 */
+	if (vlan_filtering) {
+		struct net_device *br = ipqess_port_bridge_dev_get(port);
+		struct net_device *upper_dev, *netdev = port->netdev;
+		struct list_head *iter;
+
+		netdev_for_each_upper_dev_rcu(netdev, upper_dev, iter) {
+			struct bridge_vlan_info br_info;
+			u16 vid;
+
+			if (!is_vlan_dev(upper_dev))
+				continue;
+
+			vid = vlan_dev_vlan_id(upper_dev);
+
+			/* br_vlan_get_info() returns -EINVAL or -ENOENT if the
+			 * device, respectively the VID is not found, returning
+			 * 0 means success, which is a failure for us here.
+			 */
+			err = br_vlan_get_info(br, vid, &br_info);
+			if (err == 0) {
+				NL_SET_ERR_MSG_MOD(extack,
+						   "Must first remove VLAN uppers having VIDs also present in bridge");
+				return false;
+			}
+		}
+	}
+
+	/* VLAN filtering is not global so we can just return true here */
+	return true;
+}
+
+static int ipqess_port_restore_vlan(struct net_device *vdev, int vid, void *arg)
+{
+	__be16 proto = vdev ? vlan_dev_vlan_proto(vdev) : htons(ETH_P_8021Q);
+
+	return ipqess_port_vlan_rx_add_vid(arg, proto, vid);
+}
+
+static int ipqess_port_clear_vlan(struct net_device *vdev, int vid, void *arg)
+{
+	__be16 proto = vdev ? vlan_dev_vlan_proto(vdev) : htons(ETH_P_8021Q);
+
+	return ipqess_port_vlan_rx_kill_vid(arg, proto, vid);
+}
+
+/* Keep the VLAN RX filtering list in sync with the hardware only if VLAN
+ * filtering is enabled.
+ */
+static int ipqess_port_manage_vlan_filtering(struct net_device *netdev,
+					     bool vlan_filtering)
+{
+	int err;
+
+	if (vlan_filtering) {
+		netdev->features |= NETIF_F_HW_VLAN_CTAG_FILTER;
+
+		err = vlan_for_each(netdev, ipqess_port_restore_vlan, netdev);
+		if (err) {
+			netdev_err(netdev,
+				   "Failed to restore all VLAN's successfully, error %d\n",
+				   err);
+			vlan_for_each(netdev, ipqess_port_clear_vlan, netdev);
+			netdev->features &= ~NETIF_F_HW_VLAN_CTAG_FILTER;
+			return err;
+		}
+	} else {
+		err = vlan_for_each(netdev, ipqess_port_clear_vlan, netdev);
+		if (err)
+			return err;
+
+		netdev->features &= ~NETIF_F_HW_VLAN_CTAG_FILTER;
+	}
+
+	return 0;
+}
+
+static int ipqess_port_vlan_filtering(struct ipqess_port *port,
+				      bool vlan_filtering,
+				      struct netlink_ext_ack *extack)
+{
+	bool old_vlan_filtering = port->vlan_filtering;
+	bool apply;
+	int err;
+
+	/* We are called from ipqess_port_switchdev_blocking_event(),
+	 * which is not under rcu_read_lock(), unlike
+	 * ipqess_port_switchdev_event().
+	 */
+	rcu_read_lock();
+	apply = ipqess_port_can_apply_vlan_filtering(port, vlan_filtering, extack);
+	rcu_read_unlock();
+	if (!apply)
+		return -EINVAL;
+
+	if (old_vlan_filtering == vlan_filtering)
+		return 0;
+
+	err = qca8k_port_vlan_filtering(port->sw->priv, port->index,
+					vlan_filtering);
+
+	if (err)
+		return err;
+
+	port->vlan_filtering = vlan_filtering;
+
+	err = ipqess_port_manage_vlan_filtering(port->netdev,
+						vlan_filtering);
+	if (err)
+		goto restore;
+
+	return 0;
+
+restore:
+	err = qca8k_port_vlan_filtering(port->sw->priv, port->index,
+					old_vlan_filtering);
+	port->vlan_filtering = old_vlan_filtering;
+
+	return err;
+}
+
+static void ipqess_port_reset_vlan_filtering(struct ipqess_port *port,
+					     struct ipqess_bridge *bridge)
+{
+	struct netlink_ext_ack extack = {0};
+	bool change_vlan_filtering = false;
+	bool vlan_filtering;
+	int err;
+
+	if (br_vlan_enabled(bridge->netdev)) {
+		change_vlan_filtering = true;
+		vlan_filtering = false;
+	}
+
+	if (!change_vlan_filtering)
+		return;
+
+	err = ipqess_port_vlan_filtering(port, vlan_filtering, &extack);
+	if (extack._msg) {
+		dev_err(&port->netdev->dev, "port %d: %s\n", port->index,
+			extack._msg);
+	}
+	if (err && err != -EOPNOTSUPP) {
+		dev_err(&port->netdev->dev,
+			"port %d failed to reset VLAN filtering to %d: %pe\n",
+			port->index, vlan_filtering, ERR_PTR(err));
+	}
+}
+
+static int ipqess_port_ageing_time(struct ipqess_port *port,
+				   clock_t ageing_clock)
+{
+	unsigned long ageing_jiffies = clock_t_to_jiffies(ageing_clock);
+	unsigned int ageing_time = jiffies_to_msecs(ageing_jiffies);
+
+	if (ageing_time < IPQESS_SWITCH_AGEING_TIME_MIN ||
+	    ageing_time > IPQESS_SWITCH_AGEING_TIME_MAX)
+		return -ERANGE;
+
+	/* Program the fastest ageing time in case of multiple bridges */
+	ageing_time = ipqess_switch_fastest_ageing_time(port->sw, ageing_time);
+
+	port->ageing_time = ageing_time;
+	return ipqess_set_ageing_time(port->sw, ageing_time);
+}
+
+static int ipqess_port_switchdev_sync_attrs(struct ipqess_port *port,
+					    struct netlink_ext_ack *extack)
+{
+	struct net_device *brport_dev = ipqess_port_get_bridged_netdev(port);
+	struct net_device *br = ipqess_port_bridge_dev_get(port);
+	int err;
+
+	ipqess_port_set_state_now(port, br_port_get_stp_state(brport_dev), false);
+
+	err = ipqess_port_vlan_filtering(port, br_vlan_enabled(br), extack);
+	if (err)
+		return err;
+
+	err = ipqess_port_ageing_time(port, br_get_ageing_time(br));
+	if (err && err != -EOPNOTSUPP)
+		return err;
+
+	return 0;
+}
+
+static void ipqess_port_switchdev_unsync_attrs(struct ipqess_port *port,
+					       struct ipqess_bridge *bridge)
+{
+	/* Port left the bridge, put in BR_STATE_DISABLED by the bridge layer,
+	 * so allow it to be in BR_STATE_FORWARDING to be kept functional
+	 */
+	ipqess_port_set_state_now(port, BR_STATE_FORWARDING, true);
+
+	ipqess_port_reset_vlan_filtering(port, bridge);
+
+	/* Ageing time is global to the switch chip, so don't change it
+	 * here because we have no good reason (or value) to change it to.
+	 */
+}
+
+static inline bool ipqess_port_offloads_bridge(struct ipqess_port *port,
+					       const struct ipqess_bridge *bridge)
+{
+	return ipqess_port_bridge_dev_get(port) == bridge->netdev;
+}
+
+bool ipqess_port_offloads_bridge_port(struct ipqess_port *port,
+				      const struct net_device *netdev)
+{
+	return ipqess_port_get_bridged_netdev(port) == netdev;
+}
+
+static inline bool
+ipqess_port_offloads_bridge_dev(struct ipqess_port *port,
+				const struct net_device *bridge_dev)
+{
+	/* QCA8K ports connected to a bridge, and event was emitted
+	 * for the bridge.
+	 */
+	return ipqess_port_bridge_dev_get(port) == bridge_dev;
+}
+
+static void ipqess_port_bridge_destroy(struct ipqess_port *port,
+				       const struct net_device *br)
+{
+	struct ipqess_bridge *bridge = port->bridge;
+
+	port->bridge = NULL;
+
+	if (!refcount_dec_and_test(&bridge->refcount))
+		return;
+
+	kfree(bridge);
+}
+
+int ipqess_port_bridge_join(struct ipqess_port *port, struct net_device *br,
+			    struct netlink_ext_ack *extack)
+{
+	struct ipqess_switch *sw = port->sw;
+	struct ipqess_bridge *bridge = NULL;
+	struct qca8k_priv *priv = sw->priv;
+	struct ipqess_port *other_port;
+	struct net_device *brport_dev;
+	int port_id = port->index;
+	int port_mask = 0;
+	int i, err;
+
+	/* QCA8K doesn't support MST */
+	if (br_mst_enabled(br)) {
+		err = -EOPNOTSUPP;
+		goto out_err;
+	}
+
+	/* Check if we already registered this bridge with
+	 * another switch port
+	 */
+	for (i = 0; i < IPQESS_SWITCH_MAX_PORTS; i++) {
+		other_port = sw->port_list[i];
+		if (other_port && other_port->bridge &&
+		    other_port->bridge->netdev == br)
+			bridge = other_port->bridge;
+	}
+
+	if (bridge) {
+		refcount_inc(&bridge->refcount);
+		port->bridge = bridge;
+	} else {
+		err = ipqess_port_bridge_alloc(port, br, extack);
+		if (err)
+			goto out_err;
+	}
+	bridge = port->bridge;
+
+	for (i = 1; i <= IPQESS_SWITCH_MAX_PORTS; i++) {
+		other_port = sw->port_list[i - 1];
+		if (!other_port || !ipqess_port_offloads_bridge(other_port, bridge))
+			continue;
+		/* Add this port to the portvlan mask of the other ports
+		 * in the bridge
+		 */
+		err = regmap_set_bits(priv->regmap,
+				      QCA8K_PORT_LOOKUP_CTRL(i),
+				      BIT(port_id));
+		if (err)
+			goto out_rollback;
+		if (i != port_id)
+			port_mask |= BIT(i);
+	}
+	/* Also add the CPU port */
+	err = regmap_set_bits(priv->regmap,
+			      QCA8K_PORT_LOOKUP_CTRL(0),
+			      BIT(port_id));
+	port_mask |= BIT(0);
+
+	/* Add all other ports to this ports portvlan mask */
+	err = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port_id),
+			QCA8K_PORT_LOOKUP_MEMBER, port_mask);
+	if (err)
+		goto out_rollback;
+
+	brport_dev = ipqess_port_get_bridged_netdev(port);
+
+	err = switchdev_bridge_port_offload(brport_dev, port->netdev, port,
+					    &ipqess_switchdev_notifier,
+					    &ipqess_switchdev_blocking_notifier,
+					    false, extack);
+	if (err)
+		goto out_rollback_unbridge;
+
+	err = ipqess_port_switchdev_sync_attrs(port, extack);
+	if (err)
+		goto out_rollback_unoffload;
+
+	return 0;
+
+out_rollback_unoffload:
+	switchdev_bridge_port_unoffload(brport_dev, port,
+					&ipqess_switchdev_notifier,
+					&ipqess_switchdev_blocking_notifier);
+	ipqess_flush_workqueue();
+out_rollback_unbridge:
+	for (i = 1; i <= IPQESS_SWITCH_MAX_PORTS; i++) {
+		other_port = sw->port_list[i - 1];
+		if (!other_port ||
+		    !ipqess_port_offloads_bridge(other_port, port->bridge))
+			continue;
+		/* Remove this port from the portvlan mask of the other ports
+		 * in the bridge
+		 */
+		regmap_clear_bits(priv->regmap,
+				  QCA8K_PORT_LOOKUP_CTRL(i),
+				  BIT(port_id));
+	}
+
+	/* Set the cpu port to be the only one in the portvlan mask of
+	 * this port
+	 */
+	qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port_id),
+		  QCA8K_PORT_LOOKUP_MEMBER, BIT(0));
+out_rollback:
+	ipqess_port_bridge_destroy(port, br);
+out_err:
+	dev_err(&port->netdev->dev, "Failed to join bridge: errno %d\n", err);
+	return err;
+}
+
+void ipqess_port_bridge_leave(struct ipqess_port *port, struct net_device *br)
+{
+	struct ipqess_bridge *bridge = port->bridge;
+	struct ipqess_switch *sw = port->sw;
+	struct qca8k_priv *priv = sw->priv;
+	struct ipqess_port *other_port;
+	int port_id = port->index;
+	int i;
+
+	/* If the port could not be offloaded to begin with, then
+	 * there is nothing to do.
+	 */
+	if (!bridge)
+		return;
+
+	for (i = 1; i <= IPQESS_SWITCH_MAX_PORTS; i++) {
+		other_port = sw->port_list[i - 1];
+		if (!other_port || !ipqess_port_offloads_bridge(other_port, bridge))
+			continue;
+		/* Remove this port from the portvlan mask of the other ports
+		 * in the bridge
+		 */
+		regmap_clear_bits(priv->regmap,
+				  QCA8K_PORT_LOOKUP_CTRL(i),
+				  BIT(port_id));
+	}
+
+	/* Set the cpu port to be the only one in the portvlan mask of
+	 * this port
+	 */
+	qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port_id),
+		  QCA8K_PORT_LOOKUP_MEMBER, BIT(0));
+
+	ipqess_port_switchdev_unsync_attrs(port, bridge);
+
+	/* Here the port is already unbridged. Reflect the current configuration. */
+
+	ipqess_port_bridge_destroy(port, br);
+}
+
+int ipqess_port_attr_set(struct net_device *dev, const void *ctx,
+			 const struct switchdev_attr *attr,
+			 struct netlink_ext_ack *extack)
+{
+	struct ipqess_port *port = netdev_priv(dev);
+	int ret;
+
+	if (ctx && ctx != port)
+		return 0;
+
+	switch (attr->id) {
+	case SWITCHDEV_ATTR_ID_PORT_STP_STATE:
+		if (!ipqess_port_offloads_bridge_port(port, attr->orig_dev))
+			return -EOPNOTSUPP;
+
+		ipqess_port_set_state_now(port, attr->u.stp_state, true);
+		return 0;
+	case SWITCHDEV_ATTR_ID_BRIDGE_VLAN_FILTERING:
+		if (!ipqess_port_offloads_bridge_dev(port, attr->orig_dev))
+			return -EOPNOTSUPP;
+
+		ret = ipqess_port_vlan_filtering(port, attr->u.vlan_filtering,
+						 extack);
+		break;
+	case SWITCHDEV_ATTR_ID_BRIDGE_AGEING_TIME:
+		if (!ipqess_port_offloads_bridge_dev(port, attr->orig_dev))
+			return -EOPNOTSUPP;
+
+		ret = ipqess_port_ageing_time(port, attr->u.ageing_time);
+		break;
+	case SWITCHDEV_ATTR_ID_PORT_PRE_BRIDGE_FLAGS:
+		if (!ipqess_port_offloads_bridge_port(port, attr->orig_dev))
+			return -EOPNOTSUPP;
+
+		return -EINVAL;
+	case SWITCHDEV_ATTR_ID_BRIDGE_MST:
+	case SWITCHDEV_ATTR_ID_PORT_MST_STATE:
+	case SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS:
+	case SWITCHDEV_ATTR_ID_VLAN_MSTI:
+	default:
+		ret = -EOPNOTSUPP;
+		break;
+	}
+
+	return ret;
+}
+
+static int ipqess_port_vlan_check_for_8021q_uppers(struct net_device *netdev,
+						   const struct switchdev_obj_port_vlan *vlan)
+{
+	struct net_device *upper_dev;
+	struct list_head *iter;
+
+	netdev_for_each_upper_dev_rcu(netdev, upper_dev, iter) {
+		u16 vid;
+
+		if (!is_vlan_dev(upper_dev))
+			continue;
+
+		vid = vlan_dev_vlan_id(upper_dev);
+		if (vid == vlan->vid)
+			return -EBUSY;
+	}
+
+	return 0;
+}
+
+static int ipqess_port_host_vlan_del(struct net_device *netdev,
+				     const struct switchdev_obj *obj)
+{
+	struct ipqess_port *port = netdev_priv(netdev);
+	struct net_device *br = ipqess_port_bridge_dev_get(port);
+	struct switchdev_obj_port_vlan *vlan;
+
+	/* Do nothing if this is a software bridge */
+	if (!port->bridge)
+		return -EOPNOTSUPP;
+
+	if (br && !br_vlan_enabled(br))
+		return 0;
+
+	vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
+
+	return qca8k_vlan_del(port->sw->priv, 0, vlan->vid);
+}
+
+static int ipqess_port_vlan_del(struct net_device *netdev,
+				const struct switchdev_obj *obj)
+{
+	struct ipqess_port *port = netdev_priv(netdev);
+	struct net_device *br = ipqess_port_bridge_dev_get(port);
+	struct qca8k_priv *priv = port->sw->priv;
+	struct switchdev_obj_port_vlan *vlan;
+	int ret;
+
+	if (br && !br_vlan_enabled(br))
+		return 0;
+
+	vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
+
+	ret = qca8k_vlan_del(priv, port->index, vlan->vid);
+
+	if (ret)
+		dev_err(priv->dev, "Failed to delete VLAN from port %d (%d)\n",
+			port->index, ret);
+
+	return ret;
+}
+
+static int ipqess_port_host_vlan_add(struct net_device *netdev,
+				     const struct switchdev_obj *obj,
+				     struct netlink_ext_ack *extack)
+{
+	struct ipqess_port *port = netdev_priv(netdev);
+	struct switchdev_obj_port_vlan *vlan;
+	struct net_device *br;
+
+	br = ipqess_port_bridge_dev_get(port);
+	/* Do nothing is this is a software bridge */
+	if (!port->bridge)
+		return -EOPNOTSUPP;
+
+	if (br && !br_vlan_enabled(br)) {
+		NL_SET_ERR_MSG_MOD(extack, "skipping configuration of VLAN");
+		return 0;
+	}
+
+	vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
+
+	vlan->flags &= ~BRIDGE_VLAN_INFO_PVID;
+
+	/* Add vid to CPU port */
+	return qca8k_port_vlan_add(port->sw->priv, 0, vlan, extack);
+}
+
+static int ipqess_port_vlan_add(struct net_device *netdev,
+				const struct switchdev_obj *obj,
+				struct netlink_ext_ack *extack)
+{
+	struct ipqess_port *port = netdev_priv(netdev);
+	struct net_device *br = ipqess_port_bridge_dev_get(port);
+	struct switchdev_obj_port_vlan *vlan;
+	int err;
+
+	if (br && !br_vlan_enabled(br)) {
+		NL_SET_ERR_MSG_MOD(extack, "skipping configuration of VLAN");
+		return 0;
+	}
+
+	vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
+
+	/* Deny adding a bridge VLAN when there is already an 802.1Q upper with
+	 * the same VID.
+	 */
+	if (br && br_vlan_enabled(br)) {
+		rcu_read_lock();
+		err = ipqess_port_vlan_check_for_8021q_uppers(netdev, vlan);
+		rcu_read_unlock();
+		if (err) {
+			NL_SET_ERR_MSG_MOD(extack,
+					   "Port already has a VLAN upper with this VID");
+			return err;
+		}
+	}
+
+	err = qca8k_port_vlan_add(port->sw->priv, port->index, vlan, extack);
+	return err;
+}
+
+static int ipqess_port_host_mdb_del(struct ipqess_port *port,
+				    const struct switchdev_obj_port_mdb *mdb)
+{
+	struct qca8k_priv *priv = port->sw->priv;
+	const u8 *addr = mdb->addr;
+	u16 vid = mdb->vid;
+
+	return qca8k_fdb_search_and_del(priv, BIT(0), addr, vid);
+}
+
+static int ipqess_port_host_mdb_add(struct ipqess_port *port,
+				    const struct switchdev_obj_port_mdb *mdb)
+{
+	struct qca8k_priv *priv = port->sw->priv;
+	const u8 *addr = mdb->addr;
+	u16 vid = mdb->vid;
+
+	return qca8k_fdb_search_and_insert(priv, BIT(0), addr, vid,
+					   QCA8K_ATU_STATUS_STATIC);
+}
+
+static int ipqess_port_mdb_del(struct ipqess_port *port,
+			       const struct switchdev_obj_port_mdb *mdb)
+{
+	struct qca8k_priv *priv = port->sw->priv;
+	const u8 *addr = mdb->addr;
+	u16 vid = mdb->vid;
+
+	return qca8k_fdb_search_and_del(priv, BIT(port->index), addr, vid);
+}
+
+static int ipqess_port_mdb_add(struct ipqess_port *port,
+			       const struct switchdev_obj_port_mdb *mdb)
+{
+	struct qca8k_priv *priv = port->sw->priv;
+	const u8 *addr = mdb->addr;
+	u16 vid = mdb->vid;
+
+	return qca8k_fdb_search_and_insert(priv, BIT(port->index), addr, vid,
+					   QCA8K_ATU_STATUS_STATIC);
+}
+
+int ipqess_port_obj_add(struct net_device *netdev, const void *ctx,
+			const struct switchdev_obj *obj,
+			struct netlink_ext_ack *extack)
+{
+	struct ipqess_port *port = netdev_priv(netdev);
+	int err;
+
+	if (ctx && ctx != port)
+		return 0;
+
+	switch (obj->id) {
+	case SWITCHDEV_OBJ_ID_PORT_MDB:
+		if (!ipqess_port_offloads_bridge_port(port, obj->orig_dev))
+			return -EOPNOTSUPP;
+
+		err = ipqess_port_mdb_add(port, SWITCHDEV_OBJ_PORT_MDB(obj));
+		break;
+	case SWITCHDEV_OBJ_ID_HOST_MDB:
+		if (!ipqess_port_offloads_bridge_dev(port, obj->orig_dev))
+			return -EOPNOTSUPP;
+
+		err = ipqess_port_host_mdb_add(port, SWITCHDEV_OBJ_PORT_MDB(obj));
+		break;
+	case SWITCHDEV_OBJ_ID_PORT_VLAN:
+		if (ipqess_port_offloads_bridge_port(port, obj->orig_dev))
+			err = ipqess_port_vlan_add(netdev, obj, extack);
+		else
+			err = ipqess_port_host_vlan_add(netdev, obj, extack);
+		break;
+	case SWITCHDEV_OBJ_ID_MRP:
+	case SWITCHDEV_OBJ_ID_RING_ROLE_MRP:
+	default:
+		err = -EOPNOTSUPP;
+		break;
+	}
+
+	return err;
+}
+
+int ipqess_port_obj_del(struct net_device *netdev, const void *ctx,
+			const struct switchdev_obj *obj)
+{
+	struct ipqess_port *port = netdev_priv(netdev);
+	int err;
+
+	if (ctx && ctx != port)
+		return 0;
+
+	switch (obj->id) {
+	case SWITCHDEV_OBJ_ID_PORT_MDB:
+		if (!ipqess_port_offloads_bridge_port(port, obj->orig_dev))
+			return -EOPNOTSUPP;
+
+		err = ipqess_port_mdb_del(port, SWITCHDEV_OBJ_PORT_MDB(obj));
+		break;
+	case SWITCHDEV_OBJ_ID_HOST_MDB:
+		if (!ipqess_port_offloads_bridge_dev(port, obj->orig_dev))
+			return -EOPNOTSUPP;
+
+		err = ipqess_port_host_mdb_del(port, SWITCHDEV_OBJ_PORT_MDB(obj));
+		break;
+	case SWITCHDEV_OBJ_ID_PORT_VLAN:
+		if (ipqess_port_offloads_bridge_port(port, obj->orig_dev))
+			err = ipqess_port_vlan_del(netdev, obj);
+		else
+			err = ipqess_port_host_vlan_del(netdev, obj);
+		break;
+	case SWITCHDEV_OBJ_ID_MRP:
+	case SWITCHDEV_OBJ_ID_RING_ROLE_MRP:
+	default:
+		err = -EOPNOTSUPP;
+		break;
+	}
+
+	return err;
+}
+
+static int ipqess_cpu_port_fdb_del(struct ipqess_port *port,
+				   const unsigned char *addr, u16 vid)
+{
+	struct ipqess_mac_addr *mac_addr = NULL;
+	struct ipqess_mac_addr *other_mac_addr;
+	struct ipqess_switch *sw = port->sw;
+	int err = 0;
+
+	mutex_lock(&sw->addr_lists_lock);
+
+	list_for_each_entry(other_mac_addr, &sw->fdbs, list)
+		if (ether_addr_equal(other_mac_addr->addr, addr) && other_mac_addr->vid == vid)
+			mac_addr = other_mac_addr;
+
+	if (!mac_addr) {
+		err = -ENOENT;
+		goto out;
+	}
+
+	if (!refcount_dec_and_test(&mac_addr->refcount))
+		goto out;
+
+	err = qca8k_fdb_del(sw->priv, addr, BIT(IPQESS_SWITCH_CPU_PORT), vid);
+	if (err) {
+		refcount_set(&mac_addr->refcount, 1);
+		goto out;
+	}
+
+	list_del(&mac_addr->list);
+	kfree(mac_addr);
+
+out:
+	mutex_unlock(&sw->addr_lists_lock);
+
+	return err;
+}
+
+static int ipqess_cpu_port_fdb_add(struct ipqess_port *port,
+				   const unsigned char *addr, u16 vid)
+{
+	struct ipqess_switch *sw = port->sw;
+	struct ipqess_mac_addr *other_a = NULL;
+	struct ipqess_mac_addr *a = NULL;
+	int err = 0;
+
+	mutex_lock(&sw->addr_lists_lock);
+
+	list_for_each_entry(other_a, &sw->fdbs, list)
+		if (ether_addr_equal(other_a->addr, addr) && other_a->vid == vid)
+			a = other_a;
+
+	if (a) {
+		refcount_inc(&a->refcount);
+		goto out;
+	}
+
+	a = kzalloc(sizeof(*a), GFP_KERNEL);
+	if (!a) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	err = qca8k_port_fdb_insert(port->sw->priv, addr,
+				    BIT(IPQESS_SWITCH_CPU_PORT), vid);
+	if (err) {
+		kfree(a);
+		goto out;
+	}
+
+	ether_addr_copy(a->addr, addr);
+	a->vid = vid;
+	refcount_set(&a->refcount, 1);
+	list_add_tail(&a->list, &sw->fdbs);
+
+out:
+	mutex_unlock(&sw->addr_lists_lock);
+
+	return err;
+}
+
+static void
+ipqess_fdb_offload_notify(struct ipqess_switchdev_event_work *switchdev_work)
+{
+	struct switchdev_notifier_fdb_info info = {};
+
+	info.addr = switchdev_work->addr;
+	info.vid = switchdev_work->vid;
+	info.offloaded = true;
+	call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED,
+				 switchdev_work->orig_netdev, &info.info, NULL);
+}
+
+void ipqess_port_switchdev_event_work(struct work_struct *work)
+{
+	struct ipqess_switchdev_event_work *switchdev_work =
+		container_of(work, struct ipqess_switchdev_event_work, work);
+	struct net_device *netdev = switchdev_work->netdev;
+	const unsigned char *addr = switchdev_work->addr;
+	struct ipqess_port *port = netdev_priv(netdev);
+	struct ipqess_switch *sw = port->sw;
+	struct qca8k_priv *priv = sw->priv;
+	u16 vid = switchdev_work->vid;
+	int err;
+
+	if (!vid)
+		vid = QCA8K_PORT_VID_DEF;
+
+	switch (switchdev_work->event) {
+	case SWITCHDEV_FDB_ADD_TO_DEVICE:
+		if (switchdev_work->host_addr)
+			err = ipqess_cpu_port_fdb_add(port, addr, vid);
+		else
+			err = qca8k_port_fdb_insert(priv, addr, BIT(port->index), vid);
+		if (err) {
+			dev_err(&port->netdev->dev,
+				"port %d failed to add %pM vid %d to fdb: %d\n",
+				port->index, addr, vid, err);
+			break;
+		}
+		ipqess_fdb_offload_notify(switchdev_work);
+		break;
+
+	case SWITCHDEV_FDB_DEL_TO_DEVICE:
+		if (switchdev_work->host_addr)
+			err = ipqess_cpu_port_fdb_del(port, addr, vid);
+		else
+			err = qca8k_fdb_del(priv, addr, BIT(port->index), vid);
+		if (err) {
+			dev_err(&port->netdev->dev,
+				"port %d failed to delete %pM vid %d from fdb: %d\n",
+				port->index, addr, vid, err);
+		}
+
+		break;
+	}
+
+	kfree(switchdev_work);
+}
+
+int ipqess_port_check_8021q_upper(struct net_device *netdev,
+				  struct netdev_notifier_changeupper_info *info)
+{
+	struct ipqess_port *port = netdev_priv(netdev);
+	struct net_device *br = ipqess_port_bridge_dev_get(port);
+	struct bridge_vlan_info br_info;
+	struct netlink_ext_ack *extack;
+	int err = NOTIFY_DONE;
+	u16 vid;
+
+	if (!br || !br_vlan_enabled(br))
+		return NOTIFY_DONE;
+
+	extack = netdev_notifier_info_to_extack(&info->info);
+	vid = vlan_dev_vlan_id(info->upper_dev);
+
+	/* br_vlan_get_info() returns -EINVAL or -ENOENT if the
+	 * device, respectively the VID is not found, returning
+	 * 0 means success, which is a failure for us here.
+	 */
+	err = br_vlan_get_info(br, vid, &br_info);
+	if (err == 0) {
+		NL_SET_ERR_MSG_MOD(extack,
+				   "This VLAN is already configured by the bridge");
+		return notifier_from_errno(-EBUSY);
+	}
+
+	return NOTIFY_DONE;
+}
+
 /* phylink ops */
 
 static void
@@ -669,6 +1511,7 @@ int ipqess_port_register(struct ipqess_switch *sw,
 	port->edma = NULL; /* Assigned during edma initialization */
 	port->qid = port->index - 1;
 	port->sw = sw;
+	port->bridge = NULL;
 
 	of_get_mac_address(port_node, port->mac);
 	if (!is_zero_ether_addr(port->mac))
@@ -756,3 +1599,58 @@ void ipqess_port_unregister(struct ipqess_port *port)
 	free_netdev(netdev);
 }
 
+/* Utilities */
+
+/* Returns true if any port of this switch offloads the given net_device */
+static bool ipqess_switch_offloads_bridge_port(struct ipqess_switch *sw,
+					       const struct net_device *netdev)
+{
+	struct ipqess_port *port;
+	int i;
+
+	for (i = 0; i < IPQESS_SWITCH_MAX_PORTS; i++) {
+		port = sw->port_list[i];
+		if (port && ipqess_port_offloads_bridge_port(port, netdev))
+			return true;
+	}
+
+	return false;
+}
+
+/* Returns true if any port of this switch offloads the given bridge */
+static inline bool
+ipqess_switch_offloads_bridge_dev(struct ipqess_switch *sw,
+				  const struct net_device *bridge_dev)
+{
+	struct ipqess_port *port;
+	int i;
+
+	for (i = 0; i < IPQESS_SWITCH_MAX_PORTS; i++) {
+		port = sw->port_list[i];
+		if (port && ipqess_port_offloads_bridge_dev(port, bridge_dev))
+			return true;
+	}
+
+	return false;
+}
+
+bool ipqess_port_recognize_netdev(const struct net_device *netdev)
+{
+	return netdev->netdev_ops == &ipqess_port_netdev_ops;
+}
+
+bool ipqess_port_dev_is_foreign(const struct net_device *netdev,
+				const struct net_device *foreign_netdev)
+{
+	struct ipqess_port *port = netdev_priv(netdev);
+	struct ipqess_switch *sw = port->sw;
+
+	if (netif_is_bridge_master(foreign_netdev))
+		return !ipqess_switch_offloads_bridge_dev(sw, foreign_netdev);
+
+	if (netif_is_bridge_port(foreign_netdev))
+		return !ipqess_switch_offloads_bridge_port(sw, foreign_netdev);
+
+	/* Everything else is foreign */
+	return true;
+}
diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
index 19d4b5d73625..00f0dff9c39d 100644
--- a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
@@ -9,6 +9,11 @@
 #include "ipqess_edma.h"
 #include "ipqess_switch.h"
 
+struct ipqess_bridge {
+	struct net_device *netdev;
+	refcount_t refcount;
+};
+
 struct ipqess_port {
 	u16 index;
 	u16 qid;
@@ -20,6 +25,7 @@ struct ipqess_port {
 	struct device_node *dn;
 	struct mii_bus *mii_bus;
 	struct net_device *netdev;
+	struct ipqess_bridge *bridge;
 	struct devlink_port devlink_port;
 
 	u8       stp_state;
@@ -62,4 +68,31 @@ void ipqess_port_unregister(struct ipqess_port *port);
 /* Defined in ipqess_ethtool.c */
 void ipqess_port_set_ethtool_ops(struct net_device *netdev);
 
+bool ipqess_port_recognize_netdev(const struct net_device *netdev);
+bool ipqess_port_dev_is_foreign(const struct net_device *netdev,
+				const struct net_device *foreign_netdev);
+
+int ipqess_port_bridge_join(struct ipqess_port *port, struct net_device *br,
+			    struct netlink_ext_ack *extack);
+void ipqess_port_bridge_leave(struct ipqess_port *port, struct net_device *br);
+
+int ipqess_port_attr_set(struct net_device *dev, const void *ctx,
+			 const struct switchdev_attr *attr,
+			 struct netlink_ext_ack *extack);
+
+void ipqess_port_switchdev_event_work(struct work_struct *work);
+
+int ipqess_port_check_8021q_upper(struct net_device *netdev,
+				  struct netdev_notifier_changeupper_info *info);
+
+struct net_device *ipqess_port_get_bridged_netdev(const struct ipqess_port *port);
+
+int ipqess_port_obj_add(struct net_device *netdev, const void *ctx,
+			const struct switchdev_obj *obj,
+			struct netlink_ext_ack *extack);
+int ipqess_port_obj_del(struct net_device *netdev, const void *ctx,
+			const struct switchdev_obj *obj);
+
+bool ipqess_port_offloads_bridge_port(struct ipqess_port *port,
+				      const struct net_device *netdev);
 #endif
diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.c
index 927f834a62bc..d09d0aa8314f 100644
--- a/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.c
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.c
@@ -80,21 +80,8 @@ unsigned int ipqess_switch_fastest_ageing_time(struct ipqess_switch *sw,
 int ipqess_set_ageing_time(struct ipqess_switch *sw, unsigned int msecs)
 {
 	struct qca8k_priv *priv = sw->priv;
-	unsigned int secs = msecs / 1000;
-	u32 val;
 
-	/* AGE_TIME reg is set in 7s step */
-	val = secs / 7;
-
-	/* Handle case with 0 as val to NOT disable
-	 * learning
-	 */
-	if (!val)
-		val = 1;
-
-	return regmap_update_bits(priv->regmap, QCA8K_REG_ATU_CTRL,
-				  QCA8K_ATU_AGE_TIME_MASK,
-				  QCA8K_ATU_AGE_TIME(val));
+	return qca8k_set_ageing_time(priv, msecs);
 }
 
 static struct qca8k_pcs *pcs_to_qca8k_pcs(struct phylink_pcs *pcs)
diff --git a/include/linux/dsa/qca8k.h b/include/linux/dsa/qca8k.h
index cafb727f4e8b..9ad016f7201e 100644
--- a/include/linux/dsa/qca8k.h
+++ b/include/linux/dsa/qca8k.h
@@ -553,7 +553,8 @@ int qca8k_set_mac_eee(struct dsa_switch *ds, int port, struct ethtool_eee *eee);
 int qca8k_get_mac_eee(struct dsa_switch *ds, int port, struct ethtool_eee *e);
 
 /* Common bridge function */
-void qca8k_port_stp_state_set(struct dsa_switch *ds, int port, u8 state);
+void qca8k_port_stp_state_set(struct qca8k_priv *priv, int port, u8 state,
+			      bool port_learning, int set_learning);
 int qca8k_port_pre_bridge_flags(struct dsa_switch *ds, int port,
 				struct switchdev_brport_flags flags,
 				struct netlink_ext_ack *extack);
@@ -577,8 +578,8 @@ int qca8k_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu);
 int qca8k_port_max_mtu(struct dsa_switch *ds, int port);
 
 /* Common fast age function */
-void qca8k_port_fast_age(struct dsa_switch *ds, int port);
-int qca8k_set_ageing_time(struct dsa_switch *ds, unsigned int msecs);
+void qca8k_port_fast_age(struct qca8k_priv *priv, int port);
+int qca8k_set_ageing_time(struct qca8k_priv *priv, unsigned int msecs);
 
 /* Common FDB function */
 int qca8k_port_fdb_insert(struct qca8k_priv *priv, const u8 *addr,
@@ -589,7 +590,7 @@ int qca8k_port_fdb_add(struct dsa_switch *ds, int port,
 int qca8k_port_fdb_del(struct dsa_switch *ds, int port,
 		       const unsigned char *addr, u16 vid,
 		       struct dsa_db db);
-int qca8k_port_fdb_dump(struct dsa_switch *ds, int port,
+int qca8k_port_fdb_dump(struct qca8k_priv *priv, int port,
 			dsa_fdb_dump_cb_t *cb, void *data);
 int qca8k_fdb_del(struct qca8k_priv *priv, const u8 *mac,
 		  u16 port_mask, u16 vid);
@@ -618,13 +619,12 @@ void qca8k_port_mirror_del(struct dsa_switch *ds, int port,
 			   struct dsa_mall_mirror_tc_entry *mirror);
 
 /* Common port VLAN function */
-int qca8k_port_vlan_filtering(struct dsa_switch *ds, int port,
-			      bool vlan_filtering,
-			      struct netlink_ext_ack *extack);
+int qca8k_port_vlan_filtering(struct qca8k_priv *priv, int port,
+			      bool vlan_filtering);
 int qca8k_vlan_add(struct qca8k_priv *priv, u8 port, u16 vid,
 		   bool untagged);
 int qca8k_vlan_del(struct qca8k_priv *priv, u8 port, u16 vid);
-int qca8k_port_vlan_add(struct dsa_switch *ds, int port,
+int qca8k_port_vlan_add(struct qca8k_priv *priv, int port,
 			const struct switchdev_obj_port_vlan *vlan,
 			struct netlink_ext_ack *extack);
 int qca8k_port_vlan_del(struct dsa_switch *ds, int port,
-- 
2.42.0


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

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

* [PATCH net-next v2 6/8] net: phy: add calibration callbacks to phy_driver
  2023-11-14  9:07 [PATCH net-next v2 0/8] net: qualcomm: ipqess: introduce Qualcomm IPQESS driver Romain Gantois
                   ` (3 preceding siblings ...)
  2023-11-14  9:07 ` [PATCH net-next v2 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver Romain Gantois
@ 2023-11-14  9:07 ` Romain Gantois
  2023-11-14  9:07 ` [PATCH net-next v2 7/8] net: qualcomm: ipqess: add a PSGMII calibration procedure to the IPQESS driver Romain Gantois
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Romain Gantois @ 2023-11-14  9:07 UTC (permalink / raw)
  To: davem, Rob Herring, Krzysztof Kozlowski
  Cc: Romain Gantois, Jakub Kicinski, Eric Dumazet, Paolo Abeni, netdev,
	linux-kernel, devicetree, thomas.petazzoni, Andrew Lunn,
	Florian Fainelli, Heiner Kallweit, Russell King, linux-arm-kernel,
	Vladimir Oltean, Luka Perkov, Robert Marko, Andy Gross,
	Bjorn Andersson, Konrad Dybcio

The IPQESS integrated Ethernet switch found in the IPQ4019 SoC requires
calibration of the PHY link when its ports are brought up. This calibration
procedure requires knowledge of precise timings and vendor-specific
registers on both the PHY and MAC side.

The existing PHY abstraction layer does not allow coordinating this kind of
calibration operation between MAC drivers and PHY drivers. As a
consequence, PHY-specific calibration information has to be included in
Ethernet drivers, since it has to schedule the entire calibration procedure
on it's own.

Add two callbacks that extend the PHY abstraction layer to allow MAC
drivers to start and stop PHY calibration runs in a PHY-model-independent
manner.

Signed-off-by: Romain Gantois <romain.gantois@bootlin.com>
---
 include/linux/phy.h | 28 ++++++++++++++++++++++++++++
 1 file changed, 28 insertions(+)

diff --git a/include/linux/phy.h b/include/linux/phy.h
index 3cc52826f18e..b1092b2ecee3 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -1142,6 +1142,13 @@ struct phy_driver {
 	int (*led_hw_control_get)(struct phy_device *dev, u8 index,
 				  unsigned long *rules);
 
+	/* @calibration_start: Start calibrating the MAC-to-PHY link. */
+	int (*calibration_start)(struct phy_device *dev);
+
+	/* @calibration_start: Finalize MAC-to-PHY link calibration
+	 * and run tests. Returns 0 if the calibration tests are successful.
+	 */
+	int (*calibration_stop)(struct phy_device *dev);
 };
 #define to_phy_driver(d) container_of(to_mdio_common_driver(d),		\
 				      struct phy_driver, mdiodrv)
@@ -1770,6 +1777,27 @@ int phy_start_cable_test_tdr(struct phy_device *phydev,
 }
 #endif
 
+static inline
+int phy_start_calibration(struct phy_device *phydev)
+{
+	if (!(phydev->drv &&
+	      phydev->drv->calibration_start &&
+	      phydev->drv->calibration_stop))
+		return -EOPNOTSUPP;
+
+	return phydev->drv->calibration_start(phydev);
+}
+
+static inline
+int phy_stop_calibration(struct phy_device *phydev)
+{
+	if (!(phydev->drv &&
+	      phydev->drv->calibration_stop))
+		return -EOPNOTSUPP;
+
+	return phydev->drv->calibration_stop(phydev);
+}
+
 static inline void phy_device_reset(struct phy_device *phydev, int value)
 {
 	mdio_device_reset(&phydev->mdio, value);
-- 
2.42.0


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

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

* [PATCH net-next v2 7/8] net: qualcomm: ipqess: add a PSGMII calibration procedure to the IPQESS driver
  2023-11-14  9:07 [PATCH net-next v2 0/8] net: qualcomm: ipqess: introduce Qualcomm IPQESS driver Romain Gantois
                   ` (4 preceding siblings ...)
  2023-11-14  9:07 ` [PATCH net-next v2 6/8] net: phy: add calibration callbacks to phy_driver Romain Gantois
@ 2023-11-14  9:07 ` Romain Gantois
  2023-11-14  9:07 ` [PATCH net-next v2 8/8] ARM: dts: qcom: ipq4019: Add description for the IPQ4019 ESS EDMA and switch Romain Gantois
       [not found] ` <20231114090743.865453-4-romain.gantois@bootlin.com>
  7 siblings, 0 replies; 9+ messages in thread
From: Romain Gantois @ 2023-11-14  9:07 UTC (permalink / raw)
  To: davem, Rob Herring, Krzysztof Kozlowski
  Cc: Romain Gantois, Jakub Kicinski, Eric Dumazet, Paolo Abeni, netdev,
	linux-kernel, devicetree, thomas.petazzoni, Andrew Lunn,
	Florian Fainelli, Heiner Kallweit, Russell King, linux-arm-kernel,
	Vladimir Oltean, Luka Perkov, Robert Marko, Andy Gross,
	Bjorn Andersson, Konrad Dybcio

The IPQ4019 Ethernet Switch Subsystem uses a PSGMII link to communicate
with a QCA8075 5-port PHY. This 1G link requires calibration before it can
be used reliably.

This commit introduces a calibration procedure followed by thourough
testing of the link between each switch port and its corresponding PHY
port.

Signed-off-by: Romain Gantois <romain.gantois@bootlin.com>
---
 drivers/net/ethernet/qualcomm/ipqess/Makefile |   2 +-
 .../ethernet/qualcomm/ipqess/ipqess_calib.c   | 156 ++++++++++++++++++
 .../ethernet/qualcomm/ipqess/ipqess_port.c    |  30 ++++
 .../ethernet/qualcomm/ipqess/ipqess_port.h    |   4 +
 include/linux/dsa/qca8k.h                     |   1 +
 5 files changed, 192 insertions(+), 1 deletion(-)
 create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_calib.c

diff --git a/drivers/net/ethernet/qualcomm/ipqess/Makefile b/drivers/net/ethernet/qualcomm/ipqess/Makefile
index b12142bbc7e5..5e755af579ae 100644
--- a/drivers/net/ethernet/qualcomm/ipqess/Makefile
+++ b/drivers/net/ethernet/qualcomm/ipqess/Makefile
@@ -5,4 +5,4 @@
 
 obj-$(CONFIG_QCOM_IPQ4019_ESS) += ipqess.o
 
-ipqess-objs := ipqess_port.o ipqess_switch.o ipqess_edma.o ipqess_ethtool.o ipqess_notifiers.o
+ipqess-objs := ipqess_port.o ipqess_switch.o ipqess_edma.o ipqess_ethtool.o ipqess_notifiers.o ipqess_calib.o
diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_calib.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_calib.c
new file mode 100644
index 000000000000..da9d492ca7eb
--- /dev/null
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_calib.c
@@ -0,0 +1,156 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Calibration procedure for the IPQ4019 PSGMII link
+ *
+ * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
+ * Copyright (C) 2011-2012, 2020-2021 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (c) 2015, 2019, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2016 John Crispin <john@phrozen.org>
+ * Copyright (c) 2022 Robert Marko <robert.marko@sartura.hr>
+ * Copyright (c) 2023 Romain Gantois <romain.gantois@bootlin.com>
+ */
+
+#include <linux/dsa/qca8k.h>
+#include <linux/phylink.h>
+#include <linux/of_net.h>
+#include <linux/of_mdio.h>
+#include <linux/regmap.h>
+
+#include "ipqess_port.h"
+#include "ipqess_switch.h"
+
+/* Nonstandard MII registers for the psgmii
+ * device on the IPQ4019 MDIO bus.
+ */
+
+#define PSGMII_RSTCTRL      0x0     /* Reset control register */
+#define PSGMII_RSTCTRL_RST  BIT(6)
+#define PSGMII_RSTCTRL_RX20 BIT(2)  /* Fix/release RX 20 bit */
+
+#define PSGMII_CDRCTRL         0x1a /* Clock and data recovery control register */
+#define PSGMII_CDRCTRL_RELEASE BIT(12)
+
+/* Retry policy */
+
+#define PSGMII_CALIB_RETRIES        50
+#define PSGMII_CALIB_RETRIES_BURST  5
+#define PSGMII_CALIB_RETRY_DELAY    100
+
+/* PSGMII PHY specific definitions */
+#define PSGMII_VCO_CALIB_INTERVAL   1000000
+#define PSGMII_VCO_CALIB_TIMEOUT    10000
+
+static void ipqess_port_unprep_test(struct ipqess_port *port)
+{
+	struct qca8k_priv *priv = port->sw->priv;
+	/* disable loopback on switch port */
+	qca8k_clear_bits(priv, QCA8K_PORT_LOOKUP_CTRL(port->index),
+			 QCA8K_PORT_LOOKUP_LOOPBACK_EN);
+
+	qca8k_write(priv, QCA8K_REG_PORT_STATUS(port->index), 0);
+	qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port->index),
+		  QCA8K_PORT_LOOKUP_STATE_DISABLED,
+		  QCA8K_PORT_LOOKUP_STATE_DISABLED);
+}
+
+static void ipqess_port_prep_test(struct ipqess_port *port)
+{
+	struct qca8k_priv *priv = port->sw->priv;
+
+	qca8k_write(priv, QCA8K_REG_PORT_STATUS(port->index),
+		    QCA8K_PORT_STATUS_SPEED_1000
+		    | QCA8K_PORT_STATUS_TXMAC
+		    | QCA8K_PORT_STATUS_RXMAC
+		    | QCA8K_PORT_STATUS_DUPLEX);
+
+	qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port->index),
+		  QCA8K_PORT_LOOKUP_STATE_FORWARD,
+		  QCA8K_PORT_LOOKUP_STATE_FORWARD);
+
+	/* put switch port in loopback */
+	qca8k_set_bits(priv, QCA8K_PORT_LOOKUP_CTRL(port->index),
+		       QCA8K_PORT_LOOKUP_LOOPBACK_EN);
+}
+
+static int psgmii_vco_calibrate(struct ipqess_port *port)
+{
+	struct ipqess_switch *sw = port->sw;
+	struct qca8k_priv *priv = sw->priv;
+	struct ipqess_port *other_port;
+	int val, ret, i;
+
+	ret = phy_start_calibration(port->netdev->phydev);
+	if (ret) {
+		dev_err(priv->dev,
+			"PHY VCO calibration PLL not ready\n");
+		return ret;
+	}
+
+	/* Start PSGMIIPHY VCO PLL calibration */
+	ret = regmap_set_bits(priv->psgmii,
+			      PSGMIIPHY_VCO_CALIBRATION_CONTROL_REGISTER_1,
+			      PSGMIIPHY_REG_PLL_VCO_CALIB_RESTART);
+
+	/* Poll for PSGMIIPHY PLL calibration finish - Dakota(IPQ40xx) */
+	ret = regmap_read_poll_timeout(priv->psgmii,
+				       PSGMIIPHY_VCO_CALIBRATION_CONTROL_REGISTER_2,
+				       val,
+				       val & PSGMIIPHY_REG_PLL_VCO_CALIB_READY,
+				       PSGMII_VCO_CALIB_INTERVAL,
+				       PSGMII_VCO_CALIB_TIMEOUT);
+	if (ret) {
+		dev_err(priv->dev,
+			"IPQ PSGMIIPHY VCO calibration PLL not ready\n");
+		return ret;
+	}
+
+	/* Prepare all switch ports, in case we're dealing with a multiport PHY */
+	for (i = 0; i < IPQESS_SWITCH_MAX_PORTS; i++) {
+		other_port = sw->port_list[i];
+		if (!other_port)
+			continue;
+		ipqess_port_prep_test(other_port);
+	}
+
+	ret = phy_stop_calibration(port->netdev->phydev);
+
+	for (i = 0; i < IPQESS_SWITCH_MAX_PORTS; i++) {
+		other_port = sw->port_list[i];
+		if (!other_port)
+			continue;
+		ipqess_port_unprep_test(other_port);
+	}
+
+	qca8k_fdb_flush(priv);
+
+	return ret;
+}
+
+int psgmii_calibrate_and_test(struct ipqess_port *port)
+{
+	int ret, attempt;
+
+	for (attempt = 0; attempt <= PSGMII_CALIB_RETRIES; attempt++) {
+		ret = psgmii_vco_calibrate(port);
+		if (!ret) {
+			netdev_dbg(port->netdev,
+				   "PSGMII link stabilized after %d attempts\n",
+				   attempt + 1);
+			return 0;
+		}
+
+		/* On tested hardware, the link often stabilizes in 4 or 5 retries.
+		 * If it still isn't stable, we wait a bit, then try another set
+		 * of calibration attempts.
+		 */
+		netdev_dbg(port->netdev, "PSGMII link is unstable! Retrying... %d/QCA8K_PSGMII_CALIB_RETRIES\n",
+			   attempt + 1);
+		if (attempt % PSGMII_CALIB_RETRIES_BURST == 0)
+			schedule_timeout_interruptible(msecs_to_jiffies(PSGMII_CALIB_RETRY_DELAY));
+		else
+			schedule();
+	}
+
+	netdev_err(port->netdev, "PSGMII work is unstable! Repeated recalibration attempts did not help!\n");
+	return -EFAULT;
+}
diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
index 29420820c3d8..ea759707335a 100644
--- a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
@@ -1218,6 +1218,26 @@ int ipqess_port_check_8021q_upper(struct net_device *netdev,
 
 /* phylink ops */
 
+static int
+ipqess_psgmii_configure(struct ipqess_port *port)
+{
+	int ret = 0;
+
+	/* For this multi-port PHY, we only need one calibration run,
+	 * don't rerun it for other ports
+	 */
+	if (!atomic_cmpxchg(&port->sw->priv->psgmii_calibrated, 0, 1)) {
+		psgmii_calibrate_and_test(port);
+
+		ret = regmap_clear_bits(port->sw->priv->psgmii, PSGMIIPHY_MODE_CONTROL,
+					PSGMIIPHY_MODE_ATHR_CSCO_MODE_25M);
+		ret = regmap_write(port->sw->priv->psgmii, PSGMIIPHY_TX_CONTROL,
+				   PSGMIIPHY_TX_CONTROL_MAGIC_VALUE);
+	}
+
+	return ret;
+}
+
 static void
 ipqess_phylink_mac_config(struct phylink_config *config,
 			  unsigned int mode,
@@ -1233,12 +1253,22 @@ ipqess_phylink_mac_config(struct phylink_config *config,
 	case 1:
 	case 2:
 	case 3:
+		if (state->interface == PHY_INTERFACE_MODE_PSGMII)
+			if (ipqess_psgmii_configure(port))
+				dev_err(priv->dev,
+					"PSGMII configuration failed!\n");
+		return;
 	case 4:
 	case 5:
 		if (phy_interface_mode_is_rgmii(state->interface))
 			regmap_set_bits(priv->regmap,
 					QCA8K_IPQ4019_REG_RGMII_CTRL,
 					QCA8K_IPQ4019_RGMII_CTRL_CLK);
+
+		if (state->interface == PHY_INTERFACE_MODE_PSGMII)
+			if (ipqess_psgmii_configure(port))
+				dev_err(priv->dev,
+					"PSGMII configuration failed!\n");
 		return;
 	default:
 		dev_err(priv->dev, "%s: unsupported port: %i\n", __func__,
diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
index 00f0dff9c39d..bc24a0507702 100644
--- a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
+++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
@@ -95,4 +95,8 @@ int ipqess_port_obj_del(struct net_device *netdev, const void *ctx,
 
 bool ipqess_port_offloads_bridge_port(struct ipqess_port *port,
 				      const struct net_device *netdev);
+
+/* Defined in ipqess_calib.c */
+int psgmii_calibrate_and_test(struct ipqess_port *port);
+
 #endif
diff --git a/include/linux/dsa/qca8k.h b/include/linux/dsa/qca8k.h
index 9ad016f7201e..72c488e7c498 100644
--- a/include/linux/dsa/qca8k.h
+++ b/include/linux/dsa/qca8k.h
@@ -496,6 +496,7 @@ struct qca8k_priv {
 
 	/* IPQ4019 specific */
 	struct regmap *psgmii;
+	atomic_t psgmii_calibrated;
 };
 
 struct qca8k_mib_desc {
-- 
2.42.0


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

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

* [PATCH net-next v2 8/8] ARM: dts: qcom: ipq4019: Add description for the IPQ4019 ESS EDMA and switch
  2023-11-14  9:07 [PATCH net-next v2 0/8] net: qualcomm: ipqess: introduce Qualcomm IPQESS driver Romain Gantois
                   ` (5 preceding siblings ...)
  2023-11-14  9:07 ` [PATCH net-next v2 7/8] net: qualcomm: ipqess: add a PSGMII calibration procedure to the IPQESS driver Romain Gantois
@ 2023-11-14  9:07 ` Romain Gantois
       [not found] ` <20231114090743.865453-4-romain.gantois@bootlin.com>
  7 siblings, 0 replies; 9+ messages in thread
From: Romain Gantois @ 2023-11-14  9:07 UTC (permalink / raw)
  To: davem, Rob Herring, Krzysztof Kozlowski
  Cc: Romain Gantois, Jakub Kicinski, Eric Dumazet, Paolo Abeni, netdev,
	linux-kernel, devicetree, thomas.petazzoni, Andrew Lunn,
	Florian Fainelli, Heiner Kallweit, Russell King, linux-arm-kernel,
	Vladimir Oltean, Luka Perkov, Robert Marko, Andy Gross,
	Bjorn Andersson, Konrad Dybcio

The Qualcomm IPQ4019 includes a modified version of the QCA8K Ethernet
switch. The switch's CPU port is connected to the SoC through the internal
EDMA Ethernet controller. Add support for these two devices, which are
coupled tightly enough to justify treating them as a single device.

Signed-off-by: Romain Gantois <romain.gantois@bootlin.com>
---
 .../boot/dts/qcom/qcom-ipq4018-ap120c-ac.dtsi | 13 +++
 arch/arm/boot/dts/qcom/qcom-ipq4019.dtsi      | 94 +++++++++++++++++++
 2 files changed, 107 insertions(+)

diff --git a/arch/arm/boot/dts/qcom/qcom-ipq4018-ap120c-ac.dtsi b/arch/arm/boot/dts/qcom/qcom-ipq4018-ap120c-ac.dtsi
index da67d55fa557..6a185b8b31c6 100644
--- a/arch/arm/boot/dts/qcom/qcom-ipq4018-ap120c-ac.dtsi
+++ b/arch/arm/boot/dts/qcom/qcom-ipq4018-ap120c-ac.dtsi
@@ -242,6 +242,19 @@ &mdio {
 	pinctrl-names = "default";
 };
 
+&switch {
+	status = "okay";
+};
+
+&swport4 {
+	status = "okay";
+	label = "lan";
+};
+
+&swport5 {
+	status = "okay";
+};
+
 &wifi0 {
 	status = "okay";
 	nvmem-cell-names = "pre-calibration";
diff --git a/arch/arm/boot/dts/qcom/qcom-ipq4019.dtsi b/arch/arm/boot/dts/qcom/qcom-ipq4019.dtsi
index 9844e0b7cff9..5a4e5d408f72 100644
--- a/arch/arm/boot/dts/qcom/qcom-ipq4019.dtsi
+++ b/arch/arm/boot/dts/qcom/qcom-ipq4019.dtsi
@@ -596,6 +596,100 @@ wifi1: wifi@a800000 {
 			status = "disabled";
 		};
 
+		switch: switch@c000000 {
+			compatible = "qcom,ipq4019-ess";
+			reg = <0xc000000 0x80000>, <0x98000 0x800>, <0xc080000 0x8000>;
+			reg-names = "base", "psgmii_phy", "edma";
+			resets = <&gcc ESS_PSGMII_ARES>, <&gcc ESS_RESET>;
+			reset-names = "psgmii", "ess";
+			clocks = <&gcc GCC_ESS_CLK>;
+			clock-names = "ess";
+			mdio = <&mdio>;
+			interrupts = <GIC_SPI  65 IRQ_TYPE_EDGE_RISING>,
+				     <GIC_SPI  66 IRQ_TYPE_EDGE_RISING>,
+				     <GIC_SPI  67 IRQ_TYPE_EDGE_RISING>,
+				     <GIC_SPI  68 IRQ_TYPE_EDGE_RISING>,
+				     <GIC_SPI  69 IRQ_TYPE_EDGE_RISING>,
+				     <GIC_SPI  70 IRQ_TYPE_EDGE_RISING>,
+				     <GIC_SPI  71 IRQ_TYPE_EDGE_RISING>,
+				     <GIC_SPI  72 IRQ_TYPE_EDGE_RISING>,
+				     <GIC_SPI  73 IRQ_TYPE_EDGE_RISING>,
+				     <GIC_SPI  74 IRQ_TYPE_EDGE_RISING>,
+				     <GIC_SPI  75 IRQ_TYPE_EDGE_RISING>,
+				     <GIC_SPI  76 IRQ_TYPE_EDGE_RISING>,
+				     <GIC_SPI  77 IRQ_TYPE_EDGE_RISING>,
+				     <GIC_SPI  78 IRQ_TYPE_EDGE_RISING>,
+				     <GIC_SPI  79 IRQ_TYPE_EDGE_RISING>,
+				     <GIC_SPI  80 IRQ_TYPE_EDGE_RISING>,
+				     <GIC_SPI 240 IRQ_TYPE_EDGE_RISING>,
+				     <GIC_SPI 241 IRQ_TYPE_EDGE_RISING>,
+				     <GIC_SPI 242 IRQ_TYPE_EDGE_RISING>,
+				     <GIC_SPI 243 IRQ_TYPE_EDGE_RISING>,
+				     <GIC_SPI 244 IRQ_TYPE_EDGE_RISING>,
+				     <GIC_SPI 245 IRQ_TYPE_EDGE_RISING>,
+				     <GIC_SPI 246 IRQ_TYPE_EDGE_RISING>,
+				     <GIC_SPI 247 IRQ_TYPE_EDGE_RISING>,
+				     <GIC_SPI 248 IRQ_TYPE_EDGE_RISING>,
+				     <GIC_SPI 249 IRQ_TYPE_EDGE_RISING>,
+				     <GIC_SPI 250 IRQ_TYPE_EDGE_RISING>,
+				     <GIC_SPI 251 IRQ_TYPE_EDGE_RISING>,
+				     <GIC_SPI 252 IRQ_TYPE_EDGE_RISING>,
+				     <GIC_SPI 253 IRQ_TYPE_EDGE_RISING>,
+				     <GIC_SPI 254 IRQ_TYPE_EDGE_RISING>,
+				     <GIC_SPI 255 IRQ_TYPE_EDGE_RISING>;
+			status = "disabled";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+
+				swport1: port@1 { /* MAC1 */
+					reg = <1>;
+					label = "lan1";
+					phy-handle = <&ethphy0>;
+					phy-mode = "psgmii";
+
+					status = "disabled";
+				};
+
+				swport2: port@2 { /* MAC2 */
+					reg = <2>;
+					label = "lan2";
+					phy-handle = <&ethphy1>;
+					phy-mode = "psgmii";
+
+					status = "disabled";
+				};
+
+				swport3: port@3 { /* MAC3 */
+					reg = <3>;
+					label = "lan3";
+					phy-handle = <&ethphy2>;
+					phy-mode = "psgmii";
+
+					status = "disabled";
+				};
+
+				swport4: port@4 { /* MAC4 */
+					reg = <4>;
+					label = "lan4";
+					phy-handle = <&ethphy3>;
+					phy-mode = "psgmii";
+
+					status = "disabled";
+				};
+
+				swport5: port@5 { /* MAC5 */
+					reg = <5>;
+					label = "wan";
+					phy-handle = <&ethphy4>;
+					phy-mode = "psgmii";
+
+					status = "disabled";
+				};
+			};
+		};
+
 		mdio: mdio@90000 {
 			#address-cells = <1>;
 			#size-cells = <0>;
-- 
2.42.0


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

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

* Re: [PATCH net-next v2 3/8] net: qualcomm: ipqess: introduce the Qualcomm IPQESS driver
       [not found] ` <20231114090743.865453-4-romain.gantois@bootlin.com>
@ 2023-11-14 10:24   ` Thomas Petazzoni
  0 siblings, 0 replies; 9+ messages in thread
From: Thomas Petazzoni @ 2023-11-14 10:24 UTC (permalink / raw)
  To: Romain Gantois
  Cc: davem, Rob Herring, Krzysztof Kozlowski, Jakub Kicinski,
	Eric Dumazet, Paolo Abeni, netdev, linux-kernel, devicetree,
	Andrew Lunn, Florian Fainelli, Heiner Kallweit, Russell King,
	linux-arm-kernel, Vladimir Oltean, Luka Perkov, Robert Marko,
	Andy Gross, Bjorn Andersson, Konrad Dybcio

Hello,

On Tue, 14 Nov 2023 10:07:29 +0100
Romain Gantois <romain.gantois@bootlin.com> wrote:

> The Qualcomm IPQ4019 Ethernet Switch Subsystem for the IPQ4019 chip
> includes an internal Ethernet switch based on the QCA8K IP.
> 
> The CPU-to-switch port data plane depends on the IPQESS EDMA Controller,
> a simple 1G Ethernet controller. It is connected to the switch through an
> internal link, and doesn't expose directly any external interface.
> 
> The EDMA controller has 16 RX and TX queues, with a very basic RSS fanout
> configured at init time.
> 
> Signed-off-by: Romain Gantois <romain.gantois@bootlin.com>
> ---
>  MAINTAINERS                                   |    7 +
>  drivers/net/dsa/qca/qca8k-common.c            |  119 +-

I don't think the changes to this file should be in this patch, they
should most likely be in PATCH 2/8. Could you double check this?

>  include/linux/dsa/qca8k.h                     |   47 +-

Ditto.

Thomas
-- 
Thomas Petazzoni, co-owner and CEO, Bootlin
Embedded Linux and Kernel engineering and training
https://bootlin.com

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

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

end of thread, other threads:[~2023-11-14 10:24 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2023-11-14  9:07 [PATCH net-next v2 0/8] net: qualcomm: ipqess: introduce Qualcomm IPQESS driver Romain Gantois
2023-11-14  9:07 ` [PATCH net-next v2 1/8] dt-bindings: net: Introduce the Qualcomm IPQESS Ethernet switch Romain Gantois
2023-11-14  9:07 ` [PATCH net-next v2 2/8] net: dsa: qca8k: Make the QCA8K hardware library available globally Romain Gantois
2023-11-14  9:07 ` [PATCH net-next v2 4/8] net: qualcomm: ipqess: Add Ethtool ops to IPQESS port netdevices Romain Gantois
2023-11-14  9:07 ` [PATCH net-next v2 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver Romain Gantois
2023-11-14  9:07 ` [PATCH net-next v2 6/8] net: phy: add calibration callbacks to phy_driver Romain Gantois
2023-11-14  9:07 ` [PATCH net-next v2 7/8] net: qualcomm: ipqess: add a PSGMII calibration procedure to the IPQESS driver Romain Gantois
2023-11-14  9:07 ` [PATCH net-next v2 8/8] ARM: dts: qcom: ipq4019: Add description for the IPQ4019 ESS EDMA and switch Romain Gantois
     [not found] ` <20231114090743.865453-4-romain.gantois@bootlin.com>
2023-11-14 10:24   ` [PATCH net-next v2 3/8] net: qualcomm: ipqess: introduce the Qualcomm IPQESS driver Thomas Petazzoni

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox