From patchwork Tue Nov 14 09:07:27 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Romain Gantois X-Patchwork-Id: 13454992 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 690A7C4167B for ; Tue, 14 Nov 2023 09:08:50 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender: Content-Transfer-Encoding:Content-Type:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:Cc:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=qPSx0y/vOHNIv0xW+YNApcRLHVYhHwtSL5lvqUx62H0=; b=VSCVShwjB294Hv rUwUNJPv0+ch1D81MlcI62NhGRFm8CvZaIZCzRijwpNZSNgFmm5azQ21GNKzpYjdKGTqv0cP/rysg UW3nlfI+OAzqAYYpMd//6jBcNCU936isr81YCELx0qg2hwHnNdOdvckLlNB6H84XmQVO9VVjOBbqQ vMW8v8oMTZAr/nawFmrWlvjxdKrFYTje6+ZkREebvO34GOvqV3K1cCkiJm2/ssY6TQ/K+f0IXVcNa dICD0mIOTiMBf8xAfnfEfhbpvCrlf0VpsaCRX0D+QujDQdId5hAO9SgsJcF3fnTkIop1n93FIa7sn n+UYRSmvO/S+Rensjo6A==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.96 #2 (Red Hat Linux)) id 1r2pP7-00FSRl-2I; Tue, 14 Nov 2023 09:08:17 +0000 Received: from relay6-d.mail.gandi.net ([2001:4b98:dc4:8::226]) by bombadil.infradead.org with esmtps (Exim 4.96 #2 (Red Hat Linux)) id 1r2pP2-00FSPp-2A for linux-arm-kernel@lists.infradead.org; Tue, 14 Nov 2023 09:08:14 +0000 Received: by mail.gandi.net (Postfix) with ESMTPSA id 9F5DBC0005; Tue, 14 Nov 2023 09:08:08 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=gm1; t=1699952889; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=MCcgkROjkqRmSaewuhx+Nh0bpW8Kj9/qV17c/F/G7C8=; b=bXPtkS/aWS+kQ+1mQID0hAEsnb+lPBQ1+iFqLPc3OEfvts5HaTNvj8MTjZy7cSJ4k63TC6 7l8pm46Bhn5AnYLC9ez4TrEDnUmbk5FhyiO5O8F+LMrlfwKMyXicBBaQ/qXJH535RyNbWX jTish5jhgYq03cXIVaD5W6Y9ua7opZPc/sjrhTnP7jbKXwWMBtRctAaBSEr2/uUrdYgYvR a+a2Rm7kNyxUCv2B/3sllVK45musfB4NlKHEM0jraulh/CNWcAa9uaMork1BzW89zEO/SB Lggc29D8BqlQC1MjcJbwuCN6l3ancx3FG/QvUooL5Fz9kwtdCwKKM7pxmPsZFw== From: Romain Gantois To: davem@davemloft.net, Rob Herring , Krzysztof Kozlowski Cc: Romain Gantois , Jakub Kicinski , Eric Dumazet , Paolo Abeni , netdev@vger.kernel.org, linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, thomas.petazzoni@bootlin.com, Andrew Lunn , Florian Fainelli , Heiner Kallweit , Russell King , linux-arm-kernel@lists.infradead.org, Vladimir Oltean , Luka Perkov , Robert Marko , Andy Gross , Bjorn Andersson , Konrad Dybcio Subject: [PATCH net-next v2 1/8] dt-bindings: net: Introduce the Qualcomm IPQESS Ethernet switch Date: Tue, 14 Nov 2023 10:07:27 +0100 Message-ID: <20231114090743.865453-2-romain.gantois@bootlin.com> X-Mailer: git-send-email 2.42.0 In-Reply-To: <20231114090743.865453-1-romain.gantois@bootlin.com> References: <20231114090743.865453-1-romain.gantois@bootlin.com> MIME-Version: 1.0 X-GND-Sasl: romain.gantois@bootlin.com X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20231114_010813_001279_36C7F2AD X-CRM114-Status: GOOD ( 14.30 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+linux-arm-kernel=archiver.kernel.org@lists.infradead.org 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 --- .../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 + +$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 + #include + 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 = , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + swport1: port@1 { /* MAC1 */ + reg = <1>; + label = "lan1"; + phy-handle = <ðphy0>; + phy-mode = "psgmii"; + }; + + swport2: port@2 { /* MAC2 */ + reg = <2>; + label = "lan2"; + phy-handle = <ðphy1>; + phy-mode = "psgmii"; + }; + + swport3: port@3 { /* MAC3 */ + reg = <3>; + label = "lan3"; + phy-handle = <ðphy2>; + phy-mode = "psgmii"; + }; + + swport4: port@4 { /* MAC4 */ + reg = <4>; + label = "lan4"; + phy-handle = <ðphy3>; + phy-mode = "psgmii"; + }; + + swport5: port@5 { /* MAC5 */ + reg = <5>; + label = "wan"; + phy-handle = <ðphy4>; + phy-mode = "psgmii"; + }; + }; + }; + +... From patchwork Tue Nov 14 09:07:28 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Romain Gantois X-Patchwork-Id: 13454995 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 2AC7DC074FE for ; Tue, 14 Nov 2023 09:08:58 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender: Content-Transfer-Encoding:Content-Type:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:Cc:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=Af55IvCJeE2Z4WQHtktqyjZQ2x8SMXOCSeoeaw5mWZ0=; b=uo+0gkaMQMfm9Y GCJxKUWp9iQOgxmGvF2Ut5lPzHz+0B4hDvIjfaLi6tbs6+Lyh+saz70cqFbHGyoeL1BCNL5YuW81k BH+H7XrVY0YfjodCqWGd/SCiqhNLMJFzbYu9FoZUaO3mHqqgM2o3Wc09w5Piqdw6K/6SURIoS+SF0 YejCm/GHVOL5IorDr/ZAo+F4z2OfrEBagC2nNwsbG2Md7zMKMT524ERp7tmhPwKuzNtkRdCOh5FBS 58PM8GyTIkoOjPjofuBQ3Jdgp/cvGs8dzHlERJRXTQrLlhg7dhqjtLLegMs0O1fvtbL/S4SrXMjlC g210tdYqOnUYzAelRKcA==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.96 #2 (Red Hat Linux)) id 1r2pPC-00FSTJ-06; Tue, 14 Nov 2023 09:08:22 +0000 Received: from relay6-d.mail.gandi.net ([2001:4b98:dc4:8::226]) by bombadil.infradead.org with esmtps (Exim 4.96 #2 (Red Hat Linux)) id 1r2pP8-00FSRX-08 for linux-arm-kernel@lists.infradead.org; Tue, 14 Nov 2023 09:08:20 +0000 Received: by mail.gandi.net (Postfix) with ESMTPSA id 7A7C2C0004; Tue, 14 Nov 2023 09:08:15 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=gm1; t=1699952896; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=OwGcN79xETKZy+jJlv+sJ6YAfHIqez7LK9WRloxu4/k=; b=gVxg3zCK47zqTl+ZakUfYgh+mnh0Dz0Pg2jTUnT62mEsqlqZFZ9Oqt7xMLyfo/EtwomTez qtiw+aUf15ZpYd/ASv3jLaSCnmsXWPno779ajXsfHmnRUp5prtwKqISX6bgtaduSiD6gBP ZO5YikMQQxnPIXPQZSjUGffeVSOYK7OuNr6Oj8a6Rz+r0M3ssdmaoZOF4xxYlwI5xaJqNb VXz2jqYszV+oXS8wALzEfvqjeeVZ3n16SM2zowrjkZNOTSG9481sMQhssoNsbJYTpFc+cr +JzFH53xsJhe4U9Vi8uP4kfpc7rkqnXuXOxCGX7ECuA5vfROexQzubPkTfL9Fg== From: Romain Gantois To: davem@davemloft.net, Rob Herring , Krzysztof Kozlowski Cc: Romain Gantois , Jakub Kicinski , Eric Dumazet , Paolo Abeni , netdev@vger.kernel.org, linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, thomas.petazzoni@bootlin.com, Andrew Lunn , Florian Fainelli , Heiner Kallweit , Russell King , linux-arm-kernel@lists.infradead.org, Vladimir Oltean , Luka Perkov , Robert Marko , Andy Gross , Bjorn Andersson , Konrad Dybcio Subject: [PATCH net-next v2 2/8] net: dsa: qca8k: Make the QCA8K hardware library available globally Date: Tue, 14 Nov 2023 10:07:28 +0100 Message-ID: <20231114090743.865453-3-romain.gantois@bootlin.com> X-Mailer: git-send-email 2.42.0 In-Reply-To: <20231114090743.865453-1-romain.gantois@bootlin.com> References: <20231114090743.865453-1-romain.gantois@bootlin.com> MIME-Version: 1.0 X-GND-Sasl: romain.gantois@bootlin.com X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20231114_010818_371954_8B4C7302 X-CRM114-Status: GOOD ( 18.58 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+linux-arm-kernel=archiver.kernel.org@lists.infradead.org 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 --- 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 #include #include +#include -#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 #include +#include #include -#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 #include +#include #include -#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 #include #include +#include #define QCA8K_ETHERNET_MDIO_PRIORITY 7 #define QCA8K_ETHERNET_PHY_PRIORITY 6 From patchwork Tue Nov 14 09:07:30 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Romain Gantois X-Patchwork-Id: 13454994 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 9CD1EC4167B for ; Tue, 14 Nov 2023 09:08:56 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender: Content-Transfer-Encoding:Content-Type:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:Cc:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=Zs+T99R/QNsEUmOCLoM+8JnQDXOL6nLiRD7lupzbgn4=; b=3OSU8YVecYbi2f CqTh9WBB0NiAmfrpmZuGmYmJuGTwPeFASQLk2NPHK6QGUGATwtD8UfN2kXiyNgVyve4SBAYBvV7u6 vpGb5pEUgUwaWruTdiXSngmDQQHGvH+CSIYBUSRhvmeaGRV5/aQoUn8kxY5+/LTrpYEY4tSfUBR6H ZIfvc8wH+YFik/jWQl+NszwCF07gNibyUIQv0/2vej71Eu9ZCXfjEkEEdlUapWX6pKYT0eqyu+6tF 5Qt6gEZ8/14x8eVWxfqcqu9hRcm09dyL6mLpAhZCS0QSpEZw3fcb0oMWiC7HQrsKG1Cz5DTW9OVoJ gpvELVIoOGLvT5VqSIDw==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.96 #2 (Red Hat Linux)) id 1r2pPF-00FSVV-2g; Tue, 14 Nov 2023 09:08:25 +0000 Received: from relay6-d.mail.gandi.net ([217.70.183.198]) by bombadil.infradead.org with esmtps (Exim 4.96 #2 (Red Hat Linux)) id 1r2pPB-00FST8-2w for linux-arm-kernel@lists.infradead.org; Tue, 14 Nov 2023 09:08:24 +0000 Received: by mail.gandi.net (Postfix) with ESMTPSA id 87161C0018; Tue, 14 Nov 2023 09:08:19 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=gm1; t=1699952900; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=AuuOCKjlqLcnvt3M5sD79QPuk+XlpLSx15DuBOECLAE=; b=BK2RXlYsmKdayp6U8Qutdtk4/Bah2kEhFKbP0xWe05piExxAsB3DMW1svMETHT8ahi0N7h VkMt/1/Z6H5rGC7LDILz+/Nr1KLxJmdjcdSAmby/lmBfXxTEpBWmmU/DDPPNqRjO9U4knU Gu9rSRfMJdM60DGcpXCjsdsF+yIzsml9JNlgk0xPtVItbTkBvrhZQOtzXpSYTcGx+kP0qB Ptkyqt/nLNXRZqmLMDORB2BYlw2qz3GeJZIwRzobVMI4+QZCqPcteJvlMjLKJhVDumLSEP VWEjFDaJ6ECtUH39WbWPqjKhQ1UtlXJDVIoqm+HKsIlsdwQ6HPHC1p4CAH+kDg== From: Romain Gantois To: davem@davemloft.net, Rob Herring , Krzysztof Kozlowski Cc: Romain Gantois , Jakub Kicinski , Eric Dumazet , Paolo Abeni , netdev@vger.kernel.org, linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, thomas.petazzoni@bootlin.com, Andrew Lunn , Florian Fainelli , Heiner Kallweit , Russell King , linux-arm-kernel@lists.infradead.org, Vladimir Oltean , Luka Perkov , Robert Marko , Andy Gross , Bjorn Andersson , Konrad Dybcio Subject: [PATCH net-next v2 4/8] net: qualcomm: ipqess: Add Ethtool ops to IPQESS port netdevices Date: Tue, 14 Nov 2023 10:07:30 +0100 Message-ID: <20231114090743.865453-5-romain.gantois@bootlin.com> X-Mailer: git-send-email 2.42.0 In-Reply-To: <20231114090743.865453-1-romain.gantois@bootlin.com> References: <20231114090743.865453-1-romain.gantois@bootlin.com> MIME-Version: 1.0 X-GND-Sasl: romain.gantois@bootlin.com X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20231114_010822_237965_D4EB4F9B X-CRM114-Status: GOOD ( 21.57 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+linux-arm-kernel=archiver.kernel.org@lists.infradead.org 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 --- 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 + * Based on net/dsa + */ + +#include + +#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 From patchwork Tue Nov 14 09:07:31 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Romain Gantois X-Patchwork-Id: 13454998 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 34449C4332F for ; Tue, 14 Nov 2023 09:09:10 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender: Content-Transfer-Encoding:Content-Type:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:Cc:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=YYbE218GYhflvZ3LVUgYp4arJH6ufGzAN5x1U59ZnwM=; b=1b44omc9eR6+7V d8pviZ+CuHG5+sTaPtg/Pgo8kVbzj4YN0jLBEA1+DK1ezxofZiylCLtblEEr/TSWq88mudw2tS+VT lIlx+lCXGtEONLgDWH1m0KUE30Yu74RRyETU5elQ3xNh9DgY4k3cd+vZ5cUrxDB3sDcAQQVUdUpaR Hv4SFZOtIriK6WtelCk8OjgnpoALG8rQO2DaeA1lcFrBa5FCXiR0t1GFKVi246tkL0KvVWKCy3Erb 88x5aczxOXLGjRn9xvsGUrIRZLs5AEEdVfowhftECmYOLPf6tGK5h5VZe3pVTRkxA1Yh6qH8xhVXK LF0BpNwh3rBrE4dUiA5g==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.96 #2 (Red Hat Linux)) id 1r2pPP-00FScF-0p; Tue, 14 Nov 2023 09:08:35 +0000 Received: from relay6-d.mail.gandi.net ([217.70.183.198]) by bombadil.infradead.org with esmtps (Exim 4.96 #2 (Red Hat Linux)) id 1r2pPE-00FSTw-0F for linux-arm-kernel@lists.infradead.org; Tue, 14 Nov 2023 09:08:30 +0000 Received: by mail.gandi.net (Postfix) with ESMTPSA id 7B138C0006; Tue, 14 Nov 2023 09:08:21 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=gm1; t=1699952902; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=LCp5bzOMm1SE9sHgkID7hCafvEXFQGl5Zu4RUxQpYXc=; b=o0Hbhk7puxx1sHXHfTMGCJnsYtPxr+95oxJn3cKbEIIFfxRwHQPxsZNGbsgDveqazj9xvt MB/1xNLiUPxhLyaBSjMr/Wnnj6m30gbgh6Uzy5/GRZlgxZotbSOUV3pyNgD7EjIEjPHCnr uDPkZyZYgQkJFirk+IJyhVkDms/bTwxj2rDerDuGN9z5H2L/oq9acMNtgT3TSfP/9dZVAZ Ip+3REt5HVJr1hMhHTZZ6I6ThffxiUsYlmscgl+CQykDIaKqej0mOx3toTdMhPlN7K1k5L B+3ghP5wJ0nPO37HmWKGeZKFlp7P4vUlx86jYbHfFKGbLIrPy4qLtf084CSgWg== From: Romain Gantois To: davem@davemloft.net, Rob Herring , Krzysztof Kozlowski Cc: Romain Gantois , Jakub Kicinski , Eric Dumazet , Paolo Abeni , netdev@vger.kernel.org, linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, thomas.petazzoni@bootlin.com, Andrew Lunn , Florian Fainelli , Heiner Kallweit , Russell King , linux-arm-kernel@lists.infradead.org, Vladimir Oltean , Luka Perkov , Robert Marko , Andy Gross , Bjorn Andersson , Konrad Dybcio Subject: [PATCH net-next v2 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver Date: Tue, 14 Nov 2023 10:07:31 +0100 Message-ID: <20231114090743.865453-6-romain.gantois@bootlin.com> X-Mailer: git-send-email 2.42.0 In-Reply-To: <20231114090743.865453-1-romain.gantois@bootlin.com> References: <20231114090743.865453-1-romain.gantois@bootlin.com> MIME-Version: 1.0 X-GND-Sasl: romain.gantois@bootlin.com X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20231114_010824_541102_0A30C4B1 X-CRM114-Status: GOOD ( 22.85 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+linux-arm-kernel=archiver.kernel.org@lists.infradead.org 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 --- 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 + * Based on net/dsa/slave.c + */ + +#include + +#include +#include +#include + +#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, From patchwork Tue Nov 14 09:07:32 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Romain Gantois X-Patchwork-Id: 13454993 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 7EEA2C4167D for ; Tue, 14 Nov 2023 09:08:55 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender: Content-Transfer-Encoding:Content-Type:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:Cc:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=/ydsA/J38ZPbaqPV0XEAHBIH5XMrttNKUQ/y5fh2gh0=; b=PvScrPJu9MV1Ka 4RQ/bk0umO8Xbn/rnh9iKKCG9Q+zgpQeDvYk84eY8BdMuk7dTCoNWSe0RdOBlH2XvPeodIp6m57L3 50aEen9mnv8vZzSqRyjNlduF37R3BBGt05ECQbxvUYv0XUw4qseztWkq1+dYGj2bGAnrhK3Zehs3+ W5QdNLgzu1RzBSRfgwSS0/YbAfg6+A6VIje9U2JsOUgUn9Bc+s4cc09bgsrJGejk8HB9L1kEBZ3O/ xL9YBqbmnMAspH8Sd5ufFwU4Oe1BQP/6Q1WGzQuNGIYvxoXSJQvV1aK3akiIoO6E76vEOulif8cH5 gPSMI/GyZR+wOSU6XtMw==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.96 #2 (Red Hat Linux)) id 1r2pPJ-00FSXn-2H; Tue, 14 Nov 2023 09:08:29 +0000 Received: from relay6-d.mail.gandi.net ([217.70.183.198]) by bombadil.infradead.org with esmtps (Exim 4.96 #2 (Red Hat Linux)) id 1r2pPF-00FSV3-38 for linux-arm-kernel@lists.infradead.org; Tue, 14 Nov 2023 09:08:27 +0000 Received: by mail.gandi.net (Postfix) with ESMTPSA id 78DC3C0017; Tue, 14 Nov 2023 09:08:23 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=gm1; t=1699952904; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=illYHKSC+nzt4aAs1/2/0DTKL0P6DwlH/jpdzwxEP8M=; b=X2gDbdo1ICewweuW+uThanxIizGRMAY8p9rXxQkk3tUXCSUqCkpHgx3nkiBofZGOGg3TQO BDQmqu2fHRNkECb+KRJd/6ecsnxIDT9OH6xARELHctS3wJuHgiAxwlNDoU7lvLNfOuMng9 BZhdfLiZDLP+AfDPVwrJZiP+lf9ppld/CigI8+35ukvE4YFPmkycq2ccb9kCgQFwn0IdAi Vazg+37Iu8pTODeNONrWJKpMZ70r6Ci9Wr8o84GypmP+QgI9ULUNUoYVjmrwZt+NnQaJoC Kty3yjvPWga0peAB3C62VbdGF5c6gEWY5RX25sV3CFVqnpSz63Hpk74zSCGd6Q== From: Romain Gantois To: davem@davemloft.net, Rob Herring , Krzysztof Kozlowski Cc: Romain Gantois , Jakub Kicinski , Eric Dumazet , Paolo Abeni , netdev@vger.kernel.org, linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, thomas.petazzoni@bootlin.com, Andrew Lunn , Florian Fainelli , Heiner Kallweit , Russell King , linux-arm-kernel@lists.infradead.org, Vladimir Oltean , Luka Perkov , Robert Marko , Andy Gross , Bjorn Andersson , Konrad Dybcio Subject: [PATCH net-next v2 6/8] net: phy: add calibration callbacks to phy_driver Date: Tue, 14 Nov 2023 10:07:32 +0100 Message-ID: <20231114090743.865453-7-romain.gantois@bootlin.com> X-Mailer: git-send-email 2.42.0 In-Reply-To: <20231114090743.865453-1-romain.gantois@bootlin.com> References: <20231114090743.865453-1-romain.gantois@bootlin.com> MIME-Version: 1.0 X-GND-Sasl: romain.gantois@bootlin.com X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20231114_010826_330784_D07CE6A6 X-CRM114-Status: GOOD ( 11.61 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+linux-arm-kernel=archiver.kernel.org@lists.infradead.org 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 --- 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); From patchwork Tue Nov 14 09:07:33 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Romain Gantois X-Patchwork-Id: 13454996 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 0B3AAC4332F for ; Tue, 14 Nov 2023 09:09:03 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender: Content-Transfer-Encoding:Content-Type:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:Cc:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=53yCzRFa+WWME7G9qVnVeuyEnEGSsIXd6GrZYun5fME=; b=dbYE9cZXrIEVxO DMQsP1x14ZpohxUgNIVHqQnFE0Y1MskhvBN/AsKns76xGMyI52opJ7K27L+iW9B1T4tgi2y/KGURl YoiWXUp72c0xyO5w+koMANJxVQf277yAaVEtPHps3KGDbnqCHj/PuKF8F2UWr3hkrWP1IqYN0NhPg udgv14ovT3qQsbeSKcOtEgG3vVuvZPhJU6fEghZeC6WvVijnV64oLE3fjRw07GO5fX8CwI6hfoWLa Sfpq8ucfUq21FHQ9cZy9apVAwFpJ88Ll1ZpS8bsdMWpySOe75pEA2lW5nvNi+jXWg86fDQG0i3FfA qGgWXk2P/gBWi+OgylnA==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.96 #2 (Red Hat Linux)) id 1r2pPQ-00FSdS-2N; Tue, 14 Nov 2023 09:08:36 +0000 Received: from relay6-d.mail.gandi.net ([2001:4b98:dc4:8::226]) by bombadil.infradead.org with esmtps (Exim 4.96 #2 (Red Hat Linux)) id 1r2pPH-00FSWB-2y for linux-arm-kernel@lists.infradead.org; Tue, 14 Nov 2023 09:08:31 +0000 Received: by mail.gandi.net (Postfix) with ESMTPSA id 63EDDC000F; Tue, 14 Nov 2023 09:08:25 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=gm1; t=1699952906; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=8Fa50vvn+duinPvSNUJkisW2bIzFpaD50oi5DY2jbeU=; b=AbvX2tn/bXd5sC+lEFQ0xbvvkaIB1xrfzmwNkKJnPUdYGc9G3/wPiGRN5I669nl4P2KABk pZvKKJrP2mS2MQSvHTk4apC9vKyG+w4iQiv61clhW1t7qq00T4u242qS2xoojWe8XkzRNt mDKwDZ6bAOcBxDNBeX5spExg0ZWWICPAmdeXMtto8ipZbUBb1iCnA3n4eQHxisnaI7cvUU w1oQSij4O/RDcPyYy8o/GdiQb3PxbFfgI0xZmSiZcHOztNiSwXhGeiHojLk48fvl5xcaVz jz2b5m4GRc5UFwnU2ncpi9bBBVf9sTXPhS+tWabDpoKnMXwmgRzMyfOEes5B3w== From: Romain Gantois To: davem@davemloft.net, Rob Herring , Krzysztof Kozlowski Cc: Romain Gantois , Jakub Kicinski , Eric Dumazet , Paolo Abeni , netdev@vger.kernel.org, linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, thomas.petazzoni@bootlin.com, Andrew Lunn , Florian Fainelli , Heiner Kallweit , Russell King , linux-arm-kernel@lists.infradead.org, Vladimir Oltean , Luka Perkov , Robert Marko , Andy Gross , Bjorn Andersson , Konrad Dybcio Subject: [PATCH net-next v2 7/8] net: qualcomm: ipqess: add a PSGMII calibration procedure to the IPQESS driver Date: Tue, 14 Nov 2023 10:07:33 +0100 Message-ID: <20231114090743.865453-8-romain.gantois@bootlin.com> X-Mailer: git-send-email 2.42.0 In-Reply-To: <20231114090743.865453-1-romain.gantois@bootlin.com> References: <20231114090743.865453-1-romain.gantois@bootlin.com> MIME-Version: 1.0 X-GND-Sasl: romain.gantois@bootlin.com X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20231114_010828_248806_4D88565F X-CRM114-Status: GOOD ( 25.10 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+linux-arm-kernel=archiver.kernel.org@lists.infradead.org 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 --- 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 + * Copyright (C) 2011-2012, 2020-2021 Gabor Juhos + * Copyright (c) 2015, 2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2016 John Crispin + * Copyright (c) 2022 Robert Marko + * Copyright (c) 2023 Romain Gantois + */ + +#include +#include +#include +#include +#include + +#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 { From patchwork Tue Nov 14 09:07:34 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Romain Gantois X-Patchwork-Id: 13454997 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 8408DC4167D for ; Tue, 14 Nov 2023 09:09:11 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender: Content-Transfer-Encoding:Content-Type:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:Cc:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=XmNGnjKS3yn29uIRqHf2qa+TqZ/XqdYmXd1sRUam+68=; b=kmDntHxBBxbL/n dIL2U0XnoN+IKN0MGAAmO1V+EwOVsZr/BzXV07FgGY8DGgZCDpYZr7KXWQ856y3CctIIR1e+Qy6S4 xb6gao/B7wJFHkCv4xUCMnerbvKUrXgOCkD91bdfXZdiUCEjITndDafclYI80zamHoEyXjGIOCk+k dYmwwlkzTPmHI4FplCJSTmOXpwDh6ZHskHIu7s2XN7oDUs7CPa9tO7dHTekuG5qki3Z4eH17dUekC cLrny0393X0v2XwJxuCanHHujtj7CBAySktYizh9NX2yxxB42VTO4SbTt7EwOhVXn60VxS65VWhsY aTnhP+o9dCJ5mc/8W6dg==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.96 #2 (Red Hat Linux)) id 1r2pPQ-00FScu-07; Tue, 14 Nov 2023 09:08:36 +0000 Received: from relay6-d.mail.gandi.net ([217.70.183.198]) by bombadil.infradead.org with esmtps (Exim 4.96 #2 (Red Hat Linux)) id 1r2pPJ-00FSXO-1W for linux-arm-kernel@lists.infradead.org; Tue, 14 Nov 2023 09:08:31 +0000 Received: by mail.gandi.net (Postfix) with ESMTPSA id 236A1C0003; Tue, 14 Nov 2023 09:08:27 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=gm1; t=1699952908; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=tZKAGIL7tCosSaUSk8d96o5LmNKTOtXF6OZIhSqTXQM=; b=M5cO2k5rZWYxj5QsM26BTYC1z73lncEpeg4Navg1hNV2Ozx5GKDb4UYLOmO8IUCteurC1X G/UQSPKjmVYASPUQ2QLaCXlTtpVAPOnAHwE/Amgmxrfe2xPm0HG/5/vDlYKzvAQBOVYWnQ EQVM+AD1fVuTIxQtqusIclrU0IBHnVS9wWe2/r49lkhbDJYDg+5y17QoOduAHABbxk43h8 KX+X6Mkz5qpxRhIlALbYx6UBE9IO142y4EhxHXcwHt9jy4THT7VYFH5U5MVtG2wGNz9vna 7kaY/iy3wVHCOvaEyqVDHMPOiPq0XNem8zPyUMQ2vOJwk3pkA1YLprQDSuTD+g== From: Romain Gantois To: davem@davemloft.net, Rob Herring , Krzysztof Kozlowski Cc: Romain Gantois , Jakub Kicinski , Eric Dumazet , Paolo Abeni , netdev@vger.kernel.org, linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, thomas.petazzoni@bootlin.com, Andrew Lunn , Florian Fainelli , Heiner Kallweit , Russell King , linux-arm-kernel@lists.infradead.org, Vladimir Oltean , Luka Perkov , Robert Marko , Andy Gross , Bjorn Andersson , Konrad Dybcio Subject: [PATCH net-next v2 8/8] ARM: dts: qcom: ipq4019: Add description for the IPQ4019 ESS EDMA and switch Date: Tue, 14 Nov 2023 10:07:34 +0100 Message-ID: <20231114090743.865453-9-romain.gantois@bootlin.com> X-Mailer: git-send-email 2.42.0 In-Reply-To: <20231114090743.865453-1-romain.gantois@bootlin.com> References: <20231114090743.865453-1-romain.gantois@bootlin.com> MIME-Version: 1.0 X-GND-Sasl: romain.gantois@bootlin.com X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20231114_010829_783328_08D4C5B7 X-CRM114-Status: GOOD ( 11.00 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+linux-arm-kernel=archiver.kernel.org@lists.infradead.org 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 --- .../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 = , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + swport1: port@1 { /* MAC1 */ + reg = <1>; + label = "lan1"; + phy-handle = <ðphy0>; + phy-mode = "psgmii"; + + status = "disabled"; + }; + + swport2: port@2 { /* MAC2 */ + reg = <2>; + label = "lan2"; + phy-handle = <ðphy1>; + phy-mode = "psgmii"; + + status = "disabled"; + }; + + swport3: port@3 { /* MAC3 */ + reg = <3>; + label = "lan3"; + phy-handle = <ðphy2>; + phy-mode = "psgmii"; + + status = "disabled"; + }; + + swport4: port@4 { /* MAC4 */ + reg = <4>; + label = "lan4"; + phy-handle = <ðphy3>; + phy-mode = "psgmii"; + + status = "disabled"; + }; + + swport5: port@5 { /* MAC5 */ + reg = <5>; + label = "wan"; + phy-handle = <ðphy4>; + phy-mode = "psgmii"; + + status = "disabled"; + }; + }; + }; + mdio: mdio@90000 { #address-cells = <1>; #size-cells = <0>;