* [PATCH net-next v9 0/4] net: dsa: initial support for MaxLinear MxL862xx switches
@ 2026-01-27 21:37 Daniel Golle
2026-01-27 21:37 ` [PATCH net-next v9 1/4] dt-bindings: net: dsa: add MaxLinear MxL862xx Daniel Golle
` (4 more replies)
0 siblings, 5 replies; 10+ messages in thread
From: Daniel Golle @ 2026-01-27 21:37 UTC (permalink / raw)
To: Daniel Golle, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Heiner Kallweit, Russell King,
Simon Horman, netdev, devicetree, linux-kernel
Cc: Frank Wunderlich, Chad Monroe, Cezary Wilmanski,
Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec,
Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu,
John Crispin
This series adds very basic DSA support for the MaxLinear MxL86252
(5 PHY ports) and MxL86282 (8 PHY ports) switches.
MxL862xx integrates a firmware running on an embedded processor (running
Zephyr RTOS). Host interaction uses a simple netlink-like API transported
over MDIO/MMD.
This series includes only what's needed to pass traffic between user
ports and the CPU port: relayed MDIO to internal PHYs, basic port
enable/disable, and CPU-port special tagging.
Follow up series will bring bridge, VLAN, ... offloading, and support
for using a 802.1Q-based special tag instead of the proprietary 8-byte
tag.
---
basic DSA selftests were run, results:
* no_forwarding.sh: all tests PASS
* bridge_vlan_unaware.sh: all tests PASS
* bridge_vlan_mcast.sh: all tests PASS
* bridge_vlan_aware.sh: all tests PASS
* local_termination.sh: all tests PASS or XFAIL, except for
TEST: VLAN over vlan_filtering=1 bridged port: Unicast IPv4 to unknown MAC address [FAIL]
reception succeeded, but should have failed
TEST: VLAN over vlan_filtering=1 bridged port: Unicast IPv4 to unknown MAC address, allmulti [FAIL]
reception succeeded, but should have failed
As obviously this is mostly testing the Linux software bridge at this point
so I didn't bother to run any of the FDB or MDB related tests.
Changes since v8:
1/4 dt-bindings: net: dsa: add MaxLinear MxL862xx
* no changes
2/4 net: dsa: add tag format for MxL862xx switches
* no changes
3/4 net: mdio: add unlocked mdiodev C45 bus accessors
* no changes
4/4 net: dsa: add basic initial driver for MxL862xx switches
* remove practically unused struct hw_info
* lots of kerneldoc improvements in mxl862xx-api.h
* drop .mac_select_pcs() stub
* better handling for firmware error return value
* apply reverse xmas tree in mxl862xx_api_wrap
* guard headers with #ifdef macro
* include net/dsa.h and linux/mdio.h in mxl862xx.h
* call mxl862xx_port_fast_age() only once in .port_setup
* don't create isolation bridges for unused ports
* replace errornous cast with correct range of values denoting firmware errors
Changes since v7
1/4 dt-bindings: net: dsa: add MaxLinear MxL862xx
* no changes
2/4 net: dsa: add tag format for MxL862xx switches
* no changes
3/4 net: mdio: add unlocked mdiodev C45 bus accessors
* no changes
4/4 net: dsa: add basic initial driver for MxL862xx switches
* use little-endian in bridge_port_config API
* remove duplciate assignment of br_port_cfg.bridge_port_id when setting
up CPU port
Changes since v6
1/4 dt-bindings: net: dsa: add MaxLinear MxL862xx
* no changes
2/4 net: dsa: add tag format for MxL862xx switches
* no changes
3/4 net: mdio: add unlocked mdiodev C45 bus accessors
* no changes
4/4 net: dsa: add basic initial driver for MxL862xx switches
* fix kerneldoc style
Changes since RFC v5
1/4 dt-bindings: net: dsa: add MaxLinear MxL862xx
* no changes
2/4 net: dsa: add tag format for MxL862xx switches
* remove unnecessary check for skb != NULL
* merge consecutively printed warnings into single dev_warn_ratelimited
3/4 net: mdio: add unlocked mdiodev C45 bus accessors
* no changes
4/4 net: dsa: add basic initial driver for MxL862xx switches
* include bridge and bridgeport API needed to isolate ports
* remove warning in .setup as ports are now isolated
* make ready-after-reset check more robust by adding delay
* sort structs in order of struct definitions
* best effort to sort functions without introducing additional prototypes
* always use enums with kerneldoc comments in mxl862xx-api.h
* remove bogus .phy_read and .phy_write DSA ops as the driver anyway registers
a user MDIO bus with Clause-22 and Clause-45 operations
* various small style fixes
Changes since RFC v4
1/4 dt-bindings: net: dsa: add MaxLinear MxL862xx
* no changes
2/4 net: dsa: add tag format for MxL862xx switches
* drop unused precompiler macros
3/4 net: mdio: add unlocked mdiodev C45 bus accessors
* fix indentation
4/4 net: dsa: add basic initial driver for MxL862xx switches
* output warning in .setup regarding unknown pre-configuration
* add comment explaining why CFGGET is used in reset function
Changes since RFC v3
1/4 dt-bindings: net: dsa: add MaxLinear MxL862xx
* remove labels from example
* remove 'bindings for' from commit title
2/4 net: dsa: add tag format for MxL862xx switches
* describe fields and variables with comments
* sub-interface is only 5 bits
* harmonize Kconfig symbol name
* maintain alphabetic order in Kconfig
* fix typo s/beginnig/beginning/
* fix typo s/swtiches/switches/
* arrange local variables in reverse xmas tree order
3/4 net: mdio: add unlocked mdiodev C45 bus accessors
* unchanged
4/4 net: dsa: add basic initial driver for MxL862xx switches
* poll switch readiness after reset
* implement driver shutdown
* added port_fast_aging API call and driver op
* unified port setup in new .port_setup op
* improve comment explaining special handlign for unaligned API read
* various typos and formatting improvements
Changes since RFC v2
1/4, 2/4, 3/4: unchanged
4/4 net: dsa: add basic initial driver for MxL862xx switches
* fix return value being uninitialized on error in mxl862xx_api_wrap()
* add missing description in kerneldoc comment of
struct mxl862xx_ss_sp_tag
Changes since initial RFC
1/4 dt-bindings: net: dsa: add bindings for MaxLinear MxL862xx
* better description in dt-bindings doc
2/4 net: dsa: add tag formats for MxL862xx switches
* make sure all tag fields are initialized
3/4 net: mdio: add unlocked mdiodev C45 bus accessors
* new patch
4/4 net: dsa: add basic initial driver for MxL862xx switches
* make use of struct mdio_device
* add phylink_mac_ops stubs
* drop leftover nonsense from mxl862xx_phylink_get_caps()
* fix endian conversions
* use __le32 instead of enum types in over-the-wire structs
* use existing MDIO_* macros whenever possible
* simplify API constants to be more readable
* use readx_poll_timeout instead of open-coding poll timeout loop
* add mxl862xx_reg_read() and mxl862xx_reg_write() helpers
* demystify error codes returned by the firmware
* add #defines for mxl862xx_ss_sp_tag member values
* move reset to dedicated function, clarify magic number being the
reset command ID
Daniel Golle (4):
dt-bindings: net: dsa: add MaxLinear MxL862xx
net: dsa: add tag format for MxL862xx switches
net: mdio: add unlocked mdiodev C45 bus accessors
net: dsa: add basic initial driver for MxL862xx switches
.../bindings/net/dsa/maxlinear,mxl862xx.yaml | 154 ++++
MAINTAINERS | 8 +
drivers/net/dsa/Kconfig | 2 +
drivers/net/dsa/Makefile | 1 +
drivers/net/dsa/mxl862xx/Kconfig | 12 +
drivers/net/dsa/mxl862xx/Makefile | 3 +
drivers/net/dsa/mxl862xx/mxl862xx-api.h | 675 ++++++++++++++++++
drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 49 ++
drivers/net/dsa/mxl862xx/mxl862xx-host.c | 245 +++++++
drivers/net/dsa/mxl862xx/mxl862xx-host.h | 12 +
drivers/net/dsa/mxl862xx/mxl862xx.c | 475 ++++++++++++
drivers/net/dsa/mxl862xx/mxl862xx.h | 19 +
include/linux/mdio.h | 13 +
include/net/dsa.h | 2 +
net/dsa/Kconfig | 7 +
net/dsa/Makefile | 1 +
net/dsa/tag_mxl862xx.c | 112 +++
17 files changed, 1790 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/dsa/maxlinear,mxl862xx.yaml
create mode 100644 drivers/net/dsa/mxl862xx/Kconfig
create mode 100644 drivers/net/dsa/mxl862xx/Makefile
create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-api.h
create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-host.c
create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-host.h
create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx.c
create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx.h
create mode 100644 net/dsa/tag_mxl862xx.c
--
2.52.0
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH net-next v9 1/4] dt-bindings: net: dsa: add MaxLinear MxL862xx 2026-01-27 21:37 [PATCH net-next v9 0/4] net: dsa: initial support for MaxLinear MxL862xx switches Daniel Golle @ 2026-01-27 21:37 ` Daniel Golle 2026-01-27 21:37 ` [PATCH net-next v9 2/4] net: dsa: add tag format for MxL862xx switches Daniel Golle ` (3 subsequent siblings) 4 siblings, 0 replies; 10+ messages in thread From: Daniel Golle @ 2026-01-27 21:37 UTC (permalink / raw) To: Daniel Golle, Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiner Kallweit, Russell King, Simon Horman, netdev, devicetree, linux-kernel Cc: Frank Wunderlich, Chad Monroe, Cezary Wilmanski, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin Add documentation and an example for MaxLinear MxL86282 and MxL86252 switches. Signed-off-by: Daniel Golle <daniel@makrotopia.org> Reviewed-by: Rob Herring (Arm) <robh@kernel.org> --- v9: no changes v8: no changes v7: no changes v6: no changes v5: no changes RFC v4: * remove labels from example * remove 'bindings for' from commit title RFC v3: no changes RFC v2: better description in dt-bindings doc --- .../bindings/net/dsa/maxlinear,mxl862xx.yaml | 154 ++++++++++++++++++ MAINTAINERS | 6 + 2 files changed, 160 insertions(+) create mode 100644 Documentation/devicetree/bindings/net/dsa/maxlinear,mxl862xx.yaml diff --git a/Documentation/devicetree/bindings/net/dsa/maxlinear,mxl862xx.yaml b/Documentation/devicetree/bindings/net/dsa/maxlinear,mxl862xx.yaml new file mode 100644 index 000000000000..77e48f0c7b1e --- /dev/null +++ b/Documentation/devicetree/bindings/net/dsa/maxlinear,mxl862xx.yaml @@ -0,0 +1,154 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/dsa/maxlinear,mxl862xx.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: MaxLinear MxL862xx Ethernet Switch Family + +maintainers: + - Daniel Golle <daniel@makrotopia.org> + +description: + The MaxLinear MxL862xx switch family are multi-port Ethernet switches with + integrated 2.5GE PHYs. The MxL86252 has five PHY ports and the MxL86282 + has eight PHY ports. Both models come with two 10 Gigabit/s SerDes + interfaces to be used to connect external PHYs or SFP cages, or as CPU + port. + +allOf: + - $ref: dsa.yaml#/$defs/ethernet-ports + +properties: + compatible: + enum: + - maxlinear,mxl86252 + - maxlinear,mxl86282 + + reg: + maxItems: 1 + description: MDIO address of the switch + + mdio: + $ref: /schemas/net/mdio.yaml# + unevaluatedProperties: false + +required: + - compatible + - reg + +unevaluatedProperties: false + +examples: + - | + mdio { + #address-cells = <1>; + #size-cells = <0>; + + switch@0 { + compatible = "maxlinear,mxl86282"; + reg = <0>; + + ethernet-ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + phy-handle = <&phy0>; + phy-mode = "internal"; + }; + + port@1 { + reg = <1>; + phy-handle = <&phy1>; + phy-mode = "internal"; + }; + + port@2 { + reg = <2>; + phy-handle = <&phy2>; + phy-mode = "internal"; + }; + + port@3 { + reg = <3>; + phy-handle = <&phy3>; + phy-mode = "internal"; + }; + + port@4 { + reg = <4>; + phy-handle = <&phy4>; + phy-mode = "internal"; + }; + + port@5 { + reg = <5>; + phy-handle = <&phy5>; + phy-mode = "internal"; + }; + + port@6 { + reg = <6>; + phy-handle = <&phy6>; + phy-mode = "internal"; + }; + + port@7 { + reg = <7>; + phy-handle = <&phy7>; + phy-mode = "internal"; + }; + + port@8 { + reg = <8>; + label = "cpu"; + ethernet = <&gmac0>; + phy-mode = "usxgmii"; + + fixed-link { + speed = <10000>; + full-duplex; + }; + }; + }; + + mdio { + #address-cells = <1>; + #size-cells = <0>; + + phy0: ethernet-phy@0 { + reg = <0>; + }; + + phy1: ethernet-phy@1 { + reg = <1>; + }; + + phy2: ethernet-phy@2 { + reg = <2>; + }; + + phy3: ethernet-phy@3 { + reg = <3>; + }; + + phy4: ethernet-phy@4 { + reg = <4>; + }; + + phy5: ethernet-phy@5 { + reg = <5>; + }; + + phy6: ethernet-phy@6 { + reg = <6>; + }; + + phy7: ethernet-phy@7 { + reg = <7>; + }; + }; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index c3df85fd5acd..8a0cc2609a88 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15619,6 +15619,12 @@ S: Supported F: drivers/net/phy/mxl-86110.c F: drivers/net/phy/mxl-gpy.c +MAXLINEAR MXL862XX SWITCH DRIVER +M: Daniel Golle <daniel@makrotopia.org> +L: netdev@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/net/dsa/maxlinear,mxl862xx.yaml + MCAN DEVICE DRIVER M: Markus Schneider-Pargmann <msp@baylibre.com> L: linux-can@vger.kernel.org -- 2.52.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH net-next v9 2/4] net: dsa: add tag format for MxL862xx switches 2026-01-27 21:37 [PATCH net-next v9 0/4] net: dsa: initial support for MaxLinear MxL862xx switches Daniel Golle 2026-01-27 21:37 ` [PATCH net-next v9 1/4] dt-bindings: net: dsa: add MaxLinear MxL862xx Daniel Golle @ 2026-01-27 21:37 ` Daniel Golle 2026-01-27 21:38 ` [PATCH net-next v9 3/4] net: mdio: add unlocked mdiodev C45 bus accessors Daniel Golle ` (2 subsequent siblings) 4 siblings, 0 replies; 10+ messages in thread From: Daniel Golle @ 2026-01-27 21:37 UTC (permalink / raw) To: Daniel Golle, Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiner Kallweit, Russell King, Simon Horman, netdev, devicetree, linux-kernel Cc: Frank Wunderlich, Chad Monroe, Cezary Wilmanski, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin Add proprietary special tag format for the MaxLinear MXL862xx family of switches. While using the same Ethertype as MaxLinear's GSW1xx switches, the actual tag format differs significantly, hence we need a dedicated tag driver for that. Signed-off-by: Daniel Golle <daniel@makrotopia.org> --- v9: no changes v8: no changes v7: no changes v6: * remove unnecessary check for skb != NULL * merge consecutively printed warnings into single dev_warn_ratelimited v5: * remove unused macro definitions RFC v4: * describe fields and variables with comments * sub-interface is only 5 bits * harmonize Kconfig symbol name * maintain alphabetic order in Kconfig * fix typo s/beginnig/beginning/ * fix typo s/swtiches/switches/ * arrange local variables in reverse xmas tree order RFC v3: no changes RFC v2: make sure all tag fields are initialized --- MAINTAINERS | 1 + include/net/dsa.h | 2 + net/dsa/Kconfig | 7 +++ net/dsa/Makefile | 1 + net/dsa/tag_mxl862xx.c | 112 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 123 insertions(+) create mode 100644 net/dsa/tag_mxl862xx.c diff --git a/MAINTAINERS b/MAINTAINERS index 8a0cc2609a88..bd33de42604e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15624,6 +15624,7 @@ M: Daniel Golle <daniel@makrotopia.org> L: netdev@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/net/dsa/maxlinear,mxl862xx.yaml +F: net/dsa/tag_mxl862xx.c MCAN DEVICE DRIVER M: Markus Schneider-Pargmann <msp@baylibre.com> diff --git a/include/net/dsa.h b/include/net/dsa.h index 6b2b5ed64ea4..1e33242b6d94 100644 --- a/include/net/dsa.h +++ b/include/net/dsa.h @@ -57,6 +57,7 @@ struct tc_action; #define DSA_TAG_PROTO_BRCM_LEGACY_FCS_VALUE 29 #define DSA_TAG_PROTO_YT921X_VALUE 30 #define DSA_TAG_PROTO_MXL_GSW1XX_VALUE 31 +#define DSA_TAG_PROTO_MXL862_VALUE 32 enum dsa_tag_protocol { DSA_TAG_PROTO_NONE = DSA_TAG_PROTO_NONE_VALUE, @@ -91,6 +92,7 @@ enum dsa_tag_protocol { DSA_TAG_PROTO_VSC73XX_8021Q = DSA_TAG_PROTO_VSC73XX_8021Q_VALUE, DSA_TAG_PROTO_YT921X = DSA_TAG_PROTO_YT921X_VALUE, DSA_TAG_PROTO_MXL_GSW1XX = DSA_TAG_PROTO_MXL_GSW1XX_VALUE, + DSA_TAG_PROTO_MXL862 = DSA_TAG_PROTO_MXL862_VALUE, }; struct dsa_switch; diff --git a/net/dsa/Kconfig b/net/dsa/Kconfig index f86b30742122..efc95759a10e 100644 --- a/net/dsa/Kconfig +++ b/net/dsa/Kconfig @@ -104,6 +104,13 @@ config NET_DSA_TAG_MTK Say Y or M if you want to enable support for tagging frames for Mediatek switches. +config NET_DSA_TAG_MXL_862XX + tristate "Tag driver for MxL862xx switches" + help + Say Y or M if you want to enable support for tagging frames for the + Maxlinear MxL86252 and MxL86282 switches using their native 8-byte + tagging protocol. + config NET_DSA_TAG_MXL_GSW1XX tristate "Tag driver for MaxLinear GSW1xx switches" help diff --git a/net/dsa/Makefile b/net/dsa/Makefile index 42d173f5a701..bf7247759a64 100644 --- a/net/dsa/Makefile +++ b/net/dsa/Makefile @@ -28,6 +28,7 @@ obj-$(CONFIG_NET_DSA_TAG_HELLCREEK) += tag_hellcreek.o obj-$(CONFIG_NET_DSA_TAG_KSZ) += tag_ksz.o obj-$(CONFIG_NET_DSA_TAG_LAN9303) += tag_lan9303.o obj-$(CONFIG_NET_DSA_TAG_MTK) += tag_mtk.o +obj-$(CONFIG_NET_DSA_TAG_MXL_862XX) += tag_mxl862xx.o obj-$(CONFIG_NET_DSA_TAG_MXL_GSW1XX) += tag_mxl-gsw1xx.o obj-$(CONFIG_NET_DSA_TAG_NONE) += tag_none.o obj-$(CONFIG_NET_DSA_TAG_OCELOT) += tag_ocelot.o diff --git a/net/dsa/tag_mxl862xx.c b/net/dsa/tag_mxl862xx.c new file mode 100644 index 000000000000..def7be0d5d15 --- /dev/null +++ b/net/dsa/tag_mxl862xx.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * DSA Special Tag for MaxLinear 862xx switch chips + * + * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org> + * Copyright (C) 2024 MaxLinear Inc. + */ + +#include <linux/bitops.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <net/dsa.h> +#include "tag.h" + +#define MXL862_NAME "mxl862xx" + +#define MXL862_HEADER_LEN 8 + +/* Word 0 -> EtherType */ + +/* Word 2 */ +#define MXL862_SUBIF_ID GENMASK(4, 0) + +/* Word 3 */ +#define MXL862_IGP_EGP GENMASK(3, 0) + +static struct sk_buff *mxl862_tag_xmit(struct sk_buff *skb, + struct net_device *dev) +{ + unsigned int cpu_port, usr_port, sub_interface; + struct dsa_port *dp = dsa_user_to_port(dev); + struct dsa_port *cpu_dp = dp->cpu_dp; + __be16 *mxl862_tag; + + /* switch firmware expects ports to be counted starting from 1 */ + cpu_port = cpu_dp->index + 1; + usr_port = dp->index + 1; + + /* target port sub-interface ID relative to the CPU port */ + sub_interface = usr_port + 16 - cpu_port; + + /* provide additional space 'MXL862_HEADER_LEN' bytes */ + skb_push(skb, MXL862_HEADER_LEN); + + /* shift MAC address to the beginning of the enlarged buffer, + * releasing the space required for DSA tag (between MAC address and + * Ethertype) + */ + dsa_alloc_etype_header(skb, MXL862_HEADER_LEN); + + /* special tag ingress */ + mxl862_tag = dsa_etype_header_pos_tx(skb); + mxl862_tag[0] = htons(ETH_P_MXLGSW); + mxl862_tag[1] = 0; + mxl862_tag[2] = htons(FIELD_PREP(MXL862_SUBIF_ID, sub_interface)); + mxl862_tag[3] = htons(FIELD_PREP(MXL862_IGP_EGP, cpu_port)); + + return skb; +} + +static struct sk_buff *mxl862_tag_rcv(struct sk_buff *skb, + struct net_device *dev) +{ + __be16 *mxl862_tag; + int port; + + if (unlikely(!pskb_may_pull(skb, MXL862_HEADER_LEN))) { + dev_warn_ratelimited(&dev->dev, "Cannot pull SKB, packet dropped\n"); + return NULL; + } + + mxl862_tag = dsa_etype_header_pos_rx(skb); + + if (unlikely(mxl862_tag[0] != htons(ETH_P_MXLGSW))) { + dev_warn_ratelimited(&dev->dev, + "Invalid special tag marker, packet dropped, tag: %8ph\n", + mxl862_tag); + return NULL; + } + + /* Get source port information */ + port = FIELD_GET(MXL862_IGP_EGP, ntohs(mxl862_tag[3])) - 1; + skb->dev = dsa_conduit_find_user(dev, 0, port); + if (unlikely(!skb->dev)) { + dev_warn_ratelimited(&dev->dev, + "Invalid source port, packet dropped, tag: %8ph\n", + mxl862_tag); + return NULL; + } + + /* remove the MxL862xx special tag between the MAC addresses and the + * current ethertype field. + */ + skb_pull_rcsum(skb, MXL862_HEADER_LEN); + dsa_strip_etype_header(skb, MXL862_HEADER_LEN); + + return skb; +} + +static const struct dsa_device_ops mxl862_netdev_ops = { + .name = "mxl862", + .proto = DSA_TAG_PROTO_MXL862, + .xmit = mxl862_tag_xmit, + .rcv = mxl862_tag_rcv, + .needed_headroom = MXL862_HEADER_LEN, +}; + +MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_MXL862, MXL862_NAME); +MODULE_DESCRIPTION("DSA tag driver for MaxLinear MxL862xx switches"); +MODULE_LICENSE("GPL"); + +module_dsa_tag_driver(mxl862_netdev_ops); -- 2.52.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH net-next v9 3/4] net: mdio: add unlocked mdiodev C45 bus accessors 2026-01-27 21:37 [PATCH net-next v9 0/4] net: dsa: initial support for MaxLinear MxL862xx switches Daniel Golle 2026-01-27 21:37 ` [PATCH net-next v9 1/4] dt-bindings: net: dsa: add MaxLinear MxL862xx Daniel Golle 2026-01-27 21:37 ` [PATCH net-next v9 2/4] net: dsa: add tag format for MxL862xx switches Daniel Golle @ 2026-01-27 21:38 ` Daniel Golle 2026-01-27 21:38 ` [PATCH net-next v9 4/4] net: dsa: add basic initial driver for MxL862xx switches Daniel Golle 2026-01-27 22:56 ` [PATCH net-next v9 0/4] net: dsa: initial support for MaxLinear " Vladimir Oltean 4 siblings, 0 replies; 10+ messages in thread From: Daniel Golle @ 2026-01-27 21:38 UTC (permalink / raw) To: Daniel Golle, Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiner Kallweit, Russell King, Simon Horman, netdev, devicetree, linux-kernel Cc: Frank Wunderlich, Chad Monroe, Cezary Wilmanski, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin Add helper inline functions __mdiodev_c45_read() and __mdiodev_c45_write(), which are the C45 equivalents of the existing __mdiodev_read() and __mdiodev_write() added by commit e6a45700e7e1 ("net: mdio: add unlocked mdiobus and mdiodev bus accessors") Signed-off-by: Daniel Golle <daniel@makrotopia.org> --- v9: no changes v8: no changes v7: no changes v6: no changes v5: fix indentation RFC v4: no changes RFC v3: no changes RFC v2: add this patch, initial submission --- include/linux/mdio.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/include/linux/mdio.h b/include/linux/mdio.h index 42d6d47e445b..52d94b8ae371 100644 --- a/include/linux/mdio.h +++ b/include/linux/mdio.h @@ -648,6 +648,19 @@ static inline int mdiodev_modify_changed(struct mdio_device *mdiodev, mask, set); } +static inline int __mdiodev_c45_read(struct mdio_device *mdiodev, int devad, + u16 regnum) +{ + return __mdiobus_c45_read(mdiodev->bus, mdiodev->addr, devad, regnum); +} + +static inline int __mdiodev_c45_write(struct mdio_device *mdiodev, u32 devad, + u16 regnum, u16 val) +{ + return __mdiobus_c45_write(mdiodev->bus, mdiodev->addr, devad, regnum, + val); +} + static inline int mdiodev_c45_modify(struct mdio_device *mdiodev, int devad, u32 regnum, u16 mask, u16 set) { -- 2.52.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH net-next v9 4/4] net: dsa: add basic initial driver for MxL862xx switches 2026-01-27 21:37 [PATCH net-next v9 0/4] net: dsa: initial support for MaxLinear MxL862xx switches Daniel Golle ` (2 preceding siblings ...) 2026-01-27 21:38 ` [PATCH net-next v9 3/4] net: mdio: add unlocked mdiodev C45 bus accessors Daniel Golle @ 2026-01-27 21:38 ` Daniel Golle 2026-01-28 11:29 ` Vladimir Oltean 2026-01-27 22:56 ` [PATCH net-next v9 0/4] net: dsa: initial support for MaxLinear " Vladimir Oltean 4 siblings, 1 reply; 10+ messages in thread From: Daniel Golle @ 2026-01-27 21:38 UTC (permalink / raw) To: Daniel Golle, Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiner Kallweit, Russell King, Simon Horman, netdev, devicetree, linux-kernel Cc: Frank Wunderlich, Chad Monroe, Cezary Wilmanski, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin Add very basic DSA driver for MaxLinear's MxL862xx switches. In contrast to previous MaxLinear switches the MxL862xx has a built-in processor that runs a sophisticated firmware based on Zephyr RTOS. Interaction between the host and the switch hence is organized using a software API of that firmware rather than accessing hardware registers directly. Add descriptions of the most basic firmware API calls to access the built-in MDIO bus hosting the 2.5GE PHYs, basic port control as well as setting up the CPU port. Implement a very basic DSA driver using that API which is sufficient to get packets flowing between the user ports and the CPU port. The firmware offers all features one would expect from a modern switch hardware, they will be added one by one in follow-up patch series. Signed-off-by: Daniel Golle <daniel@makrotopia.org> --- v9: * remove practically unused struct hw_info * lots of kerneldoc improvements in mxl862xx-api.h * drop .mac_select_pcs() stub * better handling for firmware error return value * apply reverse xmas tree in mxl862xx_api_wrap * guard headers with #ifdef macro * include net/dsa.h and linux/mdio.h in mxl862xx.h * call mxl862xx_port_fast_age() only once in .port_setup * don't create isolation bridges for unused ports * replace errornous cast with correct range of values denoting firmware errors v8: * use le32 endian in bridge_port_config API * remove duplciate assignment of br_port_cfg.bridge_port_id when setting up CPU port v7: * fix kerneldoc style v6: * include bridge and bridgeport API needed to isolate ports * remove warning in .setup as ports are now isolated * make ready-after-reset check more robust by adding delay * sort structs in order of struct definitions * best effort to sort functions without introducing additional prototypes * always use enums with kerneldoc comments in mxl862xx-api.h * remove bogus .phy_read and .phy_write DSA ops as the driver anyway registers a user MDIO bus with Clause-22 and Clause-45 operations * various small style fixes v5: * output warning in .setup regarding unknown pre-configuration * add comment explaining why CFGGET is used in reset function RFC v4: * poll switch readiness after reset * implement driver shutdown * added port_fast_aging API call and driver op * unified port setup in new .port_setup op * improve comment explaining special handlign for unaligned API read * various typos RFC v3: * fix return value being uninitialized on error in mxl862xx_api_wrap() * add missing descrition in kerneldoc comment of struct mxl862xx_ss_sp_tag RFC v2: * make use of struct mdio_device * add phylink_mac_ops stubs * drop leftover nonsense from mxl862xx_phylink_get_caps() * use __le32 instead of enum types in over-the-wire structs * use existing MDIO_* macros whenever possible * simplify API constants to be more readable * use readx_poll_timeout instead of open-coding poll timeout loop * add mxl862xx_reg_read() and mxl862xx_reg_write() helpers * demystify error codes returned by the firmware * add #defines for mxl862xx_ss_sp_tag member values * move reset to dedicated function, clarify magic number being the reset command ID --- MAINTAINERS | 1 + drivers/net/dsa/Kconfig | 2 + drivers/net/dsa/Makefile | 1 + drivers/net/dsa/mxl862xx/Kconfig | 12 + drivers/net/dsa/mxl862xx/Makefile | 3 + drivers/net/dsa/mxl862xx/mxl862xx-api.h | 675 +++++++++++++++++++++++ drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 49 ++ drivers/net/dsa/mxl862xx/mxl862xx-host.c | 245 ++++++++ drivers/net/dsa/mxl862xx/mxl862xx-host.h | 12 + drivers/net/dsa/mxl862xx/mxl862xx.c | 475 ++++++++++++++++ drivers/net/dsa/mxl862xx/mxl862xx.h | 19 + 11 files changed, 1494 insertions(+) create mode 100644 drivers/net/dsa/mxl862xx/Kconfig create mode 100644 drivers/net/dsa/mxl862xx/Makefile create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-api.h create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-cmd.h create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-host.c create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-host.h create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx.c create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx.h diff --git a/MAINTAINERS b/MAINTAINERS index bd33de42604e..ca941ca5ddf1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15624,6 +15624,7 @@ M: Daniel Golle <daniel@makrotopia.org> L: netdev@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/net/dsa/maxlinear,mxl862xx.yaml +F: drivers/net/dsa/mxl862xx/ F: net/dsa/tag_mxl862xx.c MCAN DEVICE DRIVER diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig index 7eb301fd987d..18f6e8b7f4cb 100644 --- a/drivers/net/dsa/Kconfig +++ b/drivers/net/dsa/Kconfig @@ -74,6 +74,8 @@ source "drivers/net/dsa/microchip/Kconfig" source "drivers/net/dsa/mv88e6xxx/Kconfig" +source "drivers/net/dsa/mxl862xx/Kconfig" + source "drivers/net/dsa/ocelot/Kconfig" source "drivers/net/dsa/qca/Kconfig" diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile index 16de4ba3fa38..f5a463b87ec2 100644 --- a/drivers/net/dsa/Makefile +++ b/drivers/net/dsa/Makefile @@ -20,6 +20,7 @@ obj-y += hirschmann/ obj-y += lantiq/ obj-y += microchip/ obj-y += mv88e6xxx/ +obj-y += mxl862xx/ obj-y += ocelot/ obj-y += qca/ obj-y += realtek/ diff --git a/drivers/net/dsa/mxl862xx/Kconfig b/drivers/net/dsa/mxl862xx/Kconfig new file mode 100644 index 000000000000..3722260da7d8 --- /dev/null +++ b/drivers/net/dsa/mxl862xx/Kconfig @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-only +config NET_DSA_MXL862 + tristate "MaxLinear MxL862xx" + depends on NET_DSA + select MAXLINEAR_GPHY + select NET_DSA_TAG_MXL_862XX + help + This enables support for the MaxLinear MxL862xx switch family. + These switches have two 10GE SerDes interfaces, one typically + used as CPU port. + MxL86282 has eight 2.5 Gigabit PHYs + MxL86252 has five 2.5 Gigabit PHYs diff --git a/drivers/net/dsa/mxl862xx/Makefile b/drivers/net/dsa/mxl862xx/Makefile new file mode 100644 index 000000000000..d23dd3cd511d --- /dev/null +++ b/drivers/net/dsa/mxl862xx/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_NET_DSA_MXL862) += mxl862xx_dsa.o +mxl862xx_dsa-y := mxl862xx.o mxl862xx-host.o diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-api.h b/drivers/net/dsa/mxl862xx/mxl862xx-api.h new file mode 100644 index 000000000000..84863717728e --- /dev/null +++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h @@ -0,0 +1,675 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef __MXL862XX_API_H +#define __MXL862XX_API_H + +#include <linux/if_ether.h> + +/** + * struct mdio_relay_data - relayed access to the switch internal MDIO bus + * @data: data to be read or written + * @phy: PHY index + * @mmd: MMD device + * @reg: register index + */ +struct mdio_relay_data { + __le16 data; + u8 phy; + u8 mmd; + __le16 reg; +} __packed; + +/** + * struct mxl862xx_register_mod - Register access parameter to directly + * modify internal registers + * @addr: Register address offset for modification + * @data: Value to write to the register address + * @mask: Mask of bits to be modified (1 to modify, 0 to ignore) + * + * Used for direct register modification operations. + */ +struct mxl862xx_register_mod { + __le16 addr; + __le16 data; + __le16 mask; +} __packed; + +/** + * enum mxl862xx_mac_clear_type - MAC table clear type + * @MXL862XX_MAC_CLEAR_PHY_PORT: clear dynamic entries based on port_id + * @MXL862XX_MAC_CLEAR_DYNAMIC: clear all dynamic entries + */ +enum mxl862xx_mac_clear_type { + MXL862XX_MAC_CLEAR_PHY_PORT = 0, + MXL862XX_MAC_CLEAR_DYNAMIC, +}; + +/** + * struct mxl862xx_mac_table_clear - MAC table clear + * @type: see &enum mxl862xx_mac_clear_type + * @port_id: physical port id + */ +struct mxl862xx_mac_table_clear { + u8 type; + u8 port_id; +} __packed; + +/** + * enum mxl862xx_age_timer - Aging Timer Value. + * @MXL862XX_AGETIMER_1_SEC: 1 second aging time + * @MXL862XX_AGETIMER_10_SEC: 10 seconds aging time + * @MXL862XX_AGETIMER_300_SEC: 300 seconds aging time + * @MXL862XX_AGETIMER_1_HOUR: 1 hour aging time + * @MXL862XX_AGETIMER_1_DAY: 24 hours aging time + * @MXL862XX_AGETIMER_CUSTOM: Custom aging time in seconds + */ +enum mxl862xx_age_timer { + MXL862XX_AGETIMER_1_SEC = 1, + MXL862XX_AGETIMER_10_SEC, + MXL862XX_AGETIMER_300_SEC, + MXL862XX_AGETIMER_1_HOUR, + MXL862XX_AGETIMER_1_DAY, + MXL862XX_AGETIMER_CUSTOM, +}; + +/** + * struct mxl862xx_bridge_alloc - Bridge Allocation + * @bridge_id: If the bridge allocation is successful, a valid ID will be + * returned in this field. Otherwise, INVALID_HANDLE is + * returned. For bridge free, this field should contain a + * valid ID returned by the bridge allocation. + * + * Used by MXL862XX_BRIDGE_ALLOC and MXL862XX_BRIDGE_FREE. + */ +struct mxl862xx_bridge_alloc { + __le16 bridge_id; +} __packed; + +/** + * enum mxl862xx_bridge_config_mask - Bridge configuration mask + * @MXL862XX_BRIDGE_CONFIG_MASK_MAC_LEARNING_LIMIT: + * Mask for mac_learning_limit_enable and mac_learning_limit. + * @MXL862XX_BRIDGE_CONFIG_MASK_MAC_LEARNED_COUNT: + * Mask for mac_learning_count + * @MXL862XX_BRIDGE_CONFIG_MASK_MAC_DISCARD_COUNT: + * Mask for learning_discard_event + * @MXL862XX_BRIDGE_CONFIG_MASK_SUB_METER: + * Mask for sub_metering_enable and traffic_sub_meter_id + * @MXL862XX_BRIDGE_CONFIG_MASK_FORWARDING_MODE: + * Mask for forward_broadcast, forward_unknown_multicast_ip, + * forward_unknown_multicast_non_ip and forward_unknown_unicast. + * @MXL862XX_BRIDGE_CONFIG_MASK_ALL: Enable all + * @MXL862XX_BRIDGE_CONFIG_MASK_FORCE: Bypass any check for debug purpose + */ +enum mxl862xx_bridge_config_mask { + MXL862XX_BRIDGE_CONFIG_MASK_MAC_LEARNING_LIMIT = BIT(0), + MXL862XX_BRIDGE_CONFIG_MASK_MAC_LEARNED_COUNT = BIT(1), + MXL862XX_BRIDGE_CONFIG_MASK_MAC_DISCARD_COUNT = BIT(2), + MXL862XX_BRIDGE_CONFIG_MASK_SUB_METER = BIT(3), + MXL862XX_BRIDGE_CONFIG_MASK_FORWARDING_MODE = BIT(4), + MXL862XX_BRIDGE_CONFIG_MASK_ALL = 0x7FFFFFFF, + MXL862XX_BRIDGE_CONFIG_MASK_FORCE = BIT(31) +}; + +/** + * enum mxl862xx_bridge_port_egress_meter - Meters for egress traffic type + * @MXL862XX_BRIDGE_PORT_EGRESS_METER_BROADCAST: + * Index of broadcast traffic meter + * @MXL862XX_BRIDGE_PORT_EGRESS_METER_MULTICAST: + * Index of known multicast traffic meter + * @MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_IP: + * Index of unknown multicast IP traffic meter + * @MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_NON_IP: + * Index of unknown multicast non-IP traffic meter + * @MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_UC: + * Index of unknown unicast traffic meter + * @MXL862XX_BRIDGE_PORT_EGRESS_METER_OTHERS: + * Index of traffic meter for other types + * @MXL862XX_BRIDGE_PORT_EGRESS_METER_MAX: Number of index + */ +enum mxl862xx_bridge_port_egress_meter { + MXL862XX_BRIDGE_PORT_EGRESS_METER_BROADCAST = 0, + MXL862XX_BRIDGE_PORT_EGRESS_METER_MULTICAST, + MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_IP, + MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_NON_IP, + MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_UC, + MXL862XX_BRIDGE_PORT_EGRESS_METER_OTHERS, + MXL862XX_BRIDGE_PORT_EGRESS_METER_MAX, +}; + +/** + * enum mxl862xx_bridge_forward_mode - Bridge forwarding type of packet + * @MXL862XX_BRIDGE_FORWARD_FLOOD: Packet is flooded to port members of + * ingress bridge port + * @MXL862XX_BRIDGE_FORWARD_DISCARD: Packet is discarded + * @MXL862XX_BRIDGE_FORWARD_CPU: Packet is forwarded to logical port 0 CTP + * port 0 bridge port 0 + */ +enum mxl862xx_bridge_forward_mode { + MXL862XX_BRIDGE_FORWARD_FLOOD = 0, + MXL862XX_BRIDGE_FORWARD_DISCARD, + MXL862XX_BRIDGE_FORWARD_CPU, +}; + +/** + * struct mxl862xx_bridge_config - Bridge Configuration + * @bridge_id: Bridge ID (FID) + * @mask: See &enum mxl862xx_bridge_config_mask + * @mac_learning_limit_enable: Enable MAC learning limitation + * @mac_learning_limit: Max number of MAC addresses that can be learned in + * this bridge (all bridge ports) + * @mac_learning_count: Number of MAC addresses learned from this bridge + * @learning_discard_event: Number of learning discard events due to + * hardware resource not available + * @sub_metering_enable: Traffic metering on type of traffic (such as + * broadcast, multicast, unknown unicast, etc) applies + * @traffic_sub_meter_id: Meter for bridge process with specific type (such + * as broadcast, multicast, unknown unicast, etc) + * @forward_broadcast: Forwarding mode of broadcast traffic. See + * &enum mxl862xx_bridge_forward_mode + * @forward_unknown_multicast_ip: Forwarding mode of unknown multicast IP + * traffic. See &enum mxl862xx_bridge_forward_mode + * @forward_unknown_multicast_non_ip: Forwarding mode of unknown multicast + * non-IP traffic. See &enum mxl862xx_bridge_forward_mode + * @forward_unknown_unicast: Forwarding mode of unknown unicast traffic. See + * &enum mxl862xx_bridge_forward_mode + */ +struct mxl862xx_bridge_config { + __le16 bridge_id; + __le32 mask; /* enum mxl862xx_bridge_config_mask */ + u8 mac_learning_limit_enable; + __le16 mac_learning_limit; + __le16 mac_learning_count; + __le32 learning_discard_event; + u8 sub_metering_enable[MXL862XX_BRIDGE_PORT_EGRESS_METER_MAX]; + __le16 traffic_sub_meter_id[MXL862XX_BRIDGE_PORT_EGRESS_METER_MAX]; + __le32 forward_broadcast; /* enum mxl862xx_bridge_forward_mode */ + __le32 forward_unknown_multicast_ip; /* enum mxl862xx_bridge_forward_mode */ + __le32 forward_unknown_multicast_non_ip; /* enum mxl862xx_bridge_forward_mode */ + __le32 forward_unknown_unicast; /* enum mxl862xx_bridge_forward_mode */ +} __packed; + +/** + * struct mxl862xx_bridge_port_alloc - Bridge Port Allocation + * @bridge_port_id: If the bridge port allocation is successful, a valid ID + * will be returned in this field. Otherwise, INVALID_HANDLE is returned. + * For bridge port free, this field should contain a valid ID returned by + * the bridge port allocation. ID 0 is special for the CPU port in + * PRX300, mapping to CTP (Connectivity Termination Port) 0 (Logical Port + * 0 with Sub-interface ID 0), and is pre-allocated during initialization. + * + * Used by MXL862XX_BRIDGE_PORT_ALLOC and MXL862XX_BRIDGE_PORT_FREE. + */ +struct mxl862xx_bridge_port_alloc { + __le16 bridge_port_id; +}; + +/** + * enum mxl862xx_bridge_port_config_mask - Bridge Port configuration mask + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_ID: + * Mask for bridge_id + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_VLAN: + * Mask for ingress_extended_vlan_enable, + * ingress_extended_vlan_block_id and ingress_extended_vlan_block_size + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_VLAN: + * Mask for egress_extended_vlan_enable, egress_extended_vlan_block_id + * and egress_extended_vlan_block_size + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_MARKING: + * Mask for ingress_marking_mode + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_REMARKING: + * Mask for egress_remarking_mode + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_METER: + * Mask for ingress_metering_enable and ingress_traffic_meter_id + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_SUB_METER: + * Mask for egress_sub_metering_enable and egress_traffic_sub_meter_id + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_CTP_MAPPING: + * Mask for dest_logical_port_id, pmapper_enable, dest_sub_if_id_group, + * pmapper_mapping_mode, pmapper_id_valid and pmapper + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP: + * Mask for bridge_port_map + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_DEST_IP_LOOKUP: + * Mask for mc_dest_ip_lookup_disable + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_IP_LOOKUP: + * Mask for mc_src_ip_lookup_enable + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_DEST_MAC_LOOKUP: + * Mask for dest_mac_lookup_disable + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING: + * Mask for src_mac_learning_disable + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_MAC_SPOOFING: + * Mask for mac_spoofing_detect_enable + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_PORT_LOCK: + * Mask for port_lock_enable + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_MAC_LEARNING_LIMIT: + * Mask for mac_learning_limit_enable and mac_learning_limit + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_MAC_LEARNED_COUNT: + * Mask for mac_learning_count + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_VLAN_FILTER: + * Mask for ingress_vlan_filter_enable, ingress_vlan_filter_block_id + * and ingress_vlan_filter_block_size + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_VLAN_FILTER1: + * Mask for bypass_egress_vlan_filter1, egress_vlan_filter1enable, + * egress_vlan_filter1block_id and egress_vlan_filter1block_size + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_VLAN_FILTER2: + * Mask for egress_vlan_filter2enable, egress_vlan_filter2block_id and + * egress_vlan_filter2block_size + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MAC_LEARNING: + * Mask for vlan_tag_selection, vlan_src_mac_priority_enable, + * vlan_src_mac_dei_enable, vlan_src_mac_vid_enable, + * vlan_dst_mac_priority_enable, vlan_dst_mac_dei_enable and + * vlan_dst_mac_vid_enable + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MULTICAST_LOOKUP: + * Mask for vlan_multicast_priority_enable, + * vlan_multicast_dei_enable and vlan_multicast_vid_enable + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_LOOP_VIOLATION_COUNTER: + * Mask for loop_violation_count + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_ALL: Enable all + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_FORCE: Bypass any check for debug purpose + */ +enum mxl862xx_bridge_port_config_mask { + MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_ID = BIT(0), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_VLAN = BIT(1), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_VLAN = BIT(2), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_MARKING = BIT(3), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_REMARKING = BIT(4), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_METER = BIT(5), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_SUB_METER = BIT(6), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_CTP_MAPPING = BIT(7), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP = BIT(8), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_DEST_IP_LOOKUP = BIT(9), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_IP_LOOKUP = BIT(10), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_DEST_MAC_LOOKUP = BIT(11), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING = BIT(12), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_MAC_SPOOFING = BIT(13), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_PORT_LOCK = BIT(14), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_MAC_LEARNING_LIMIT = BIT(15), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_MAC_LEARNED_COUNT = BIT(16), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_VLAN_FILTER = BIT(17), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_VLAN_FILTER1 = BIT(18), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_VLAN_FILTER2 = BIT(19), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MAC_LEARNING = BIT(20), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MULTICAST_LOOKUP = BIT(21), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_LOOP_VIOLATION_COUNTER = BIT(22), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_ALL = 0x7FFFFFFF, + MXL862XX_BRIDGE_PORT_CONFIG_MASK_FORCE = BIT(31) +}; + +/** + * enum mxl862xx_color_marking_mode - Color Marking Mode + * @MXL862XX_MARKING_ALL_GREEN: mark packets (except critical) to green + * @MXL862XX_MARKING_INTERNAL_MARKING: do not change color and priority + * @MXL862XX_MARKING_DEI: DEI mark mode + * @MXL862XX_MARKING_PCP_8P0D: PCP 8P0D mark mode + * @MXL862XX_MARKING_PCP_7P1D: PCP 7P1D mark mode + * @MXL862XX_MARKING_PCP_6P2D: PCP 6P2D mark mode + * @MXL862XX_MARKING_PCP_5P3D: PCP 5P3D mark mode + * @MXL862XX_MARKING_DSCP_AF: DSCP AF class + */ +enum mxl862xx_color_marking_mode { + MXL862XX_MARKING_ALL_GREEN = 0, + MXL862XX_MARKING_INTERNAL_MARKING, + MXL862XX_MARKING_DEI, + MXL862XX_MARKING_PCP_8P0D, + MXL862XX_MARKING_PCP_7P1D, + MXL862XX_MARKING_PCP_6P2D, + MXL862XX_MARKING_PCP_5P3D, + MXL862XX_MARKING_DSCP_AF, +}; + +/** + * enum mxl862xx_color_remarking_mode - Color Remarking Mode + * @MXL862XX_REMARKING_NONE: values from last process stage + * @MXL862XX_REMARKING_DEI: DEI mark mode + * @MXL862XX_REMARKING_PCP_8P0D: PCP 8P0D mark mode + * @MXL862XX_REMARKING_PCP_7P1D: PCP 7P1D mark mode + * @MXL862XX_REMARKING_PCP_6P2D: PCP 6P2D mark mode + * @MXL862XX_REMARKING_PCP_5P3D: PCP 5P3D mark mode + * @MXL862XX_REMARKING_DSCP_AF: DSCP AF class + */ +enum mxl862xx_color_remarking_mode { + MXL862XX_REMARKING_NONE = 0, + MXL862XX_REMARKING_DEI = 2, + MXL862XX_REMARKING_PCP_8P0D, + MXL862XX_REMARKING_PCP_7P1D, + MXL862XX_REMARKING_PCP_6P2D, + MXL862XX_REMARKING_PCP_5P3D, + MXL862XX_REMARKING_DSCP_AF, +}; + +/** + * enum mxl862xx_pmapper_mapping_mode - P-mapper Mapping Mode + * @MXL862XX_PMAPPER_MAPPING_PCP: Use PCP for VLAN tagged packets to derive + * sub interface ID group + * @MXL862XX_PMAPPER_MAPPING_LAG: Use LAG Index for Pmapper access + * regardless of IP and VLAN packet + * @MXL862XX_PMAPPER_MAPPING_DSCP: Use DSCP for VLAN tagged IP packets to + * derive sub interface ID group + */ +enum mxl862xx_pmapper_mapping_mode { + MXL862XX_PMAPPER_MAPPING_PCP = 0, + MXL862XX_PMAPPER_MAPPING_LAG, + MXL862XX_PMAPPER_MAPPING_DSCP, +}; + +/** + * struct mxl862xx_pmapper - P-mapper Configuration + * @pmapper_id: Index of P-mapper (0-31) + * @dest_sub_if_id_group: Sub interface ID group. Entry 0 is for non-IP and + * non-VLAN tagged packets. Entries 1-8 are PCP mapping entries for VLAN + * tagged packets. Entries 9-72 are DSCP or LAG mapping entries. + * + * Used by CTP port config and bridge port config. In case of LAG, it is + * user's responsibility to provide the mapped entries in given P-mapper + * table. In other modes the entries are auto mapped from input packet. + */ +struct mxl862xx_pmapper { + __le16 pmapper_id; + u8 dest_sub_if_id_group[73]; +} __packed; + +/** + * struct mxl862xx_bridge_port_config - Bridge Port Configuration + * @bridge_port_id: Bridge Port ID allocated by bridge port allocation + * @mask: See &enum mxl862xx_bridge_port_config_mask + * @bridge_id: Bridge ID (FID) to which this bridge port is associated + * @ingress_extended_vlan_enable: Enable extended VLAN processing for + * ingress traffic + * @ingress_extended_vlan_block_id: Extended VLAN block allocated for + * ingress traffic + * @ingress_extended_vlan_block_size: Extended VLAN block size for ingress + * traffic + * @egress_extended_vlan_enable: Enable extended VLAN processing for egress + * traffic + * @egress_extended_vlan_block_id: Extended VLAN block allocated for egress + * traffic + * @egress_extended_vlan_block_size: Extended VLAN block size for egress + * traffic + * @ingress_marking_mode: Ingress color marking mode. See + * &enum mxl862xx_color_marking_mode + * @egress_remarking_mode: Color remarking for egress traffic. See + * &enum mxl862xx_color_remarking_mode + * @ingress_metering_enable: Traffic metering on ingress traffic applies + * @ingress_traffic_meter_id: Meter for ingress Bridge Port process + * @egress_sub_metering_enable: Traffic metering on various types of egress + * traffic + * @egress_traffic_sub_meter_id: Meter for egress Bridge Port process with + * specific type + * @dest_logical_port_id: Destination logical port + * @pmapper_enable: Enable P-mapper + * @dest_sub_if_id_group: Destination sub interface ID group when + * pmapper_enable is false + * @pmapper_mapping_mode: P-mapper mapping mode. See + * &enum mxl862xx_pmapper_mapping_mode + * @pmapper_id_valid: When true, P-mapper is re-used; when false, + * allocation is handled by API + * @pmapper: P-mapper configuration used when pmapper_enable is true + * @bridge_port_map: Port map defining broadcast domain. Each bit + * represents one bridge port. Bridge port ID is index * 16 + bit + * offset. + * @mc_dest_ip_lookup_disable: Disable multicast IP destination table + * lookup + * @mc_src_ip_lookup_enable: Enable multicast IP source table lookup + * @dest_mac_lookup_disable: Disable destination MAC lookup; packet treated + * as unknown + * @src_mac_learning_disable: Disable source MAC address learning + * @mac_spoofing_detect_enable: Enable MAC spoofing detection + * @port_lock_enable: Enable port locking + * @mac_learning_limit_enable: Enable MAC learning limitation + * @mac_learning_limit: Maximum number of MAC addresses that can be learned + * from this bridge port + * @loop_violation_count: Number of loop violation events from this bridge + * port + * @mac_learning_count: Number of MAC addresses learned from this bridge + * port + * @ingress_vlan_filter_enable: Enable ingress VLAN filter + * @ingress_vlan_filter_block_id: VLAN filter block of ingress traffic + * @ingress_vlan_filter_block_size: VLAN filter block size for ingress + * traffic + * @bypass_egress_vlan_filter1: For ingress traffic, bypass VLAN filter 1 + * at egress bridge port processing + * @egress_vlan_filter1enable: Enable egress VLAN filter 1 + * @egress_vlan_filter1block_id: VLAN filter block 1 of egress traffic + * @egress_vlan_filter1block_size: VLAN filter block 1 size + * @egress_vlan_filter2enable: Enable egress VLAN filter 2 + * @egress_vlan_filter2block_id: VLAN filter block 2 of egress traffic + * @egress_vlan_filter2block_size: VLAN filter block 2 size + * @vlan_tag_selection: VLAN tag selection for MAC address/multicast + * learning, lookup and filtering. 0 - Intermediate outer VLAN tag is + * used. 1 - Original outer VLAN tag is used. + * @vlan_src_mac_priority_enable: Enable VLAN Priority field for source MAC + * learning and filtering + * @vlan_src_mac_dei_enable: Enable VLAN DEI/CFI field for source MAC + * learning and filtering + * @vlan_src_mac_vid_enable: Enable VLAN ID field for source MAC learning + * and filtering + * @vlan_dst_mac_priority_enable: Enable VLAN Priority field for destination + * MAC lookup and filtering + * @vlan_dst_mac_dei_enable: Enable VLAN CFI/DEI field for destination MAC + * lookup and filtering + * @vlan_dst_mac_vid_enable: Enable VLAN ID field for destination MAC lookup + * and filtering + * @vlan_multicast_priority_enable: Enable VLAN Priority field for IP + * multicast lookup + * @vlan_multicast_dei_enable: Enable VLAN CFI/DEI field for IP multicast + * lookup + * @vlan_multicast_vid_enable: Enable VLAN ID field for IP multicast lookup + */ +struct mxl862xx_bridge_port_config { + __le16 bridge_port_id; + __le32 mask; /* enum mxl862xx_bridge_port_config_mask */ + __le16 bridge_id; + u8 ingress_extended_vlan_enable; + __le16 ingress_extended_vlan_block_id; + __le16 ingress_extended_vlan_block_size; + u8 egress_extended_vlan_enable; + __le16 egress_extended_vlan_block_id; + __le16 egress_extended_vlan_block_size; + __le32 ingress_marking_mode; /* enum mxl862xx_color_marking_mode */ + __le32 egress_remarking_mode; /* enum mxl862xx_color_remarking_mode */ + u8 ingress_metering_enable; + __le16 ingress_traffic_meter_id; + u8 egress_sub_metering_enable[MXL862XX_BRIDGE_PORT_EGRESS_METER_MAX]; + __le16 egress_traffic_sub_meter_id[MXL862XX_BRIDGE_PORT_EGRESS_METER_MAX]; + u8 dest_logical_port_id; + u8 pmapper_enable; + __le16 dest_sub_if_id_group; + __le32 pmapper_mapping_mode; /* enum mxl862xx_pmapper_mapping_mode */ + u8 pmapper_id_valid; + struct mxl862xx_pmapper pmapper; + __le16 bridge_port_map[8]; + u8 mc_dest_ip_lookup_disable; + u8 mc_src_ip_lookup_enable; + u8 dest_mac_lookup_disable; + u8 src_mac_learning_disable; + u8 mac_spoofing_detect_enable; + u8 port_lock_enable; + u8 mac_learning_limit_enable; + __le16 mac_learning_limit; + __le16 loop_violation_count; + __le16 mac_learning_count; + u8 ingress_vlan_filter_enable; + __le16 ingress_vlan_filter_block_id; + __le16 ingress_vlan_filter_block_size; + u8 bypass_egress_vlan_filter1; + u8 egress_vlan_filter1enable; + __le16 egress_vlan_filter1block_id; + __le16 egress_vlan_filter1block_size; + u8 egress_vlan_filter2enable; + __le16 egress_vlan_filter2block_id; + __le16 egress_vlan_filter2block_size; + u8 vlan_tag_selection; + u8 vlan_src_mac_priority_enable; + u8 vlan_src_mac_dei_enable; + u8 vlan_src_mac_vid_enable; + u8 vlan_dst_mac_priority_enable; + u8 vlan_dst_mac_dei_enable; + u8 vlan_dst_mac_vid_enable; + u8 vlan_multicast_priority_enable; + u8 vlan_multicast_dei_enable; + u8 vlan_multicast_vid_enable; +} __packed; + +/** + * struct mxl862xx_cfg - Global Switch configuration Attributes + * @mac_table_age_timer: See &enum mxl862xx_age_timer + * @age_timer: Custom MAC table aging timer in seconds + * @max_packet_len: Maximum Ethernet packet length + * @learning_limit_action: Automatic MAC address table learning limitation + * consecutive action + * @mac_locking_action: Accept or discard MAC port locking violation + * packets + * @mac_spoofing_action: Accept or discard MAC spoofing and port MAC locking + * violation packets + * @pause_mac_mode_src: Pause frame MAC source address mode + * @pause_mac_src: Pause frame MAC source address + */ +struct mxl862xx_cfg { + __le32 mac_table_age_timer; /* enum mxl862xx_age_timer */ + __le32 age_timer; + __le16 max_packet_len; + u8 learning_limit_action; + u8 mac_locking_action; + u8 mac_spoofing_action; + u8 pause_mac_mode_src; + u8 pause_mac_src[ETH_ALEN]; +} __packed; + +/** + * enum mxl862xx_ss_sp_tag_mask - Special tag valid field indicator bits + * @MXL862XX_SS_SP_TAG_MASK_RX: valid RX special tag mode + * @MXL862XX_SS_SP_TAG_MASK_TX: valid TX special tag mode + * @MXL862XX_SS_SP_TAG_MASK_RX_PEN: valid RX special tag info over preamble + * @MXL862XX_SS_SP_TAG_MASK_TX_PEN: valid TX special tag info over preamble + */ +enum mxl862xx_ss_sp_tag_mask { + MXL862XX_SS_SP_TAG_MASK_RX = BIT(0), + MXL862XX_SS_SP_TAG_MASK_TX = BIT(1), + MXL862XX_SS_SP_TAG_MASK_RX_PEN = BIT(2), + MXL862XX_SS_SP_TAG_MASK_TX_PEN = BIT(3), +}; + +/** + * enum mxl862xx_ss_sp_tag_rx - RX special tag mode + * @MXL862XX_SS_SP_TAG_RX_NO_TAG_NO_INSERT: packet does NOT have special + * tag and special tag is NOT inserted + * @MXL862XX_SS_SP_TAG_RX_NO_TAG_INSERT: packet does NOT have special tag + * and special tag is inserted + * @MXL862XX_SS_SP_TAG_RX_TAG_NO_INSERT: packet has special tag and special + * tag is NOT inserted + */ +enum mxl862xx_ss_sp_tag_rx { + MXL862XX_SS_SP_TAG_RX_NO_TAG_NO_INSERT = 0, + MXL862XX_SS_SP_TAG_RX_NO_TAG_INSERT = 1, + MXL862XX_SS_SP_TAG_RX_TAG_NO_INSERT = 2, +}; + +/** + * enum mxl862xx_ss_sp_tag_tx - TX special tag mode + * @MXL862XX_SS_SP_TAG_TX_NO_TAG_NO_REMOVE: packet does NOT have special + * tag and special tag is NOT removed + * @MXL862XX_SS_SP_TAG_TX_TAG_REPLACE: packet has special tag and special + * tag is replaced + * @MXL862XX_SS_SP_TAG_TX_TAG_NO_REMOVE: packet has special tag and special + * tag is NOT removed + * @MXL862XX_SS_SP_TAG_TX_TAG_REMOVE: packet has special tag and special + * tag is removed + */ +enum mxl862xx_ss_sp_tag_tx { + MXL862XX_SS_SP_TAG_TX_NO_TAG_NO_REMOVE = 0, + MXL862XX_SS_SP_TAG_TX_TAG_REPLACE = 1, + MXL862XX_SS_SP_TAG_TX_TAG_NO_REMOVE = 2, + MXL862XX_SS_SP_TAG_TX_TAG_REMOVE = 3, +}; + +/** + * enum mxl862xx_ss_sp_tag_rx_pen - RX special tag info over preamble + * @MXL862XX_SS_SP_TAG_RX_PEN_ALL_0: special tag info inserted from byte 2 + * to 7 are all 0 + * @MXL862XX_SS_SP_TAG_RX_PEN_BYTE_5_IS_16: special tag byte 5 is 16, other + * bytes from 2 to 7 are 0 + * @MXL862XX_SS_SP_TAG_RX_PEN_BYTE_5_FROM_PREAMBLE: special tag byte 5 is + * from preamble field, others are 0 + * @MXL862XX_SS_SP_TAG_RX_PEN_BYTE_2_TO_7_FROM_PREAMBLE: special tag byte 2 + * to 7 are from preamble field + */ +enum mxl862xx_ss_sp_tag_rx_pen { + MXL862XX_SS_SP_TAG_RX_PEN_ALL_0 = 0, + MXL862XX_SS_SP_TAG_RX_PEN_BYTE_5_IS_16 = 1, + MXL862XX_SS_SP_TAG_RX_PEN_BYTE_5_FROM_PREAMBLE = 2, + MXL862XX_SS_SP_TAG_RX_PEN_BYTE_2_TO_7_FROM_PREAMBLE = 3, +}; + +/** + * struct mxl862xx_ss_sp_tag - Special tag port settings + * @pid: port ID (1~16) + * @mask: See &enum mxl862xx_ss_sp_tag_mask + * @rx: See &enum mxl862xx_ss_sp_tag_rx + * @tx: See &enum mxl862xx_ss_sp_tag_tx + * @rx_pen: See &enum mxl862xx_ss_sp_tag_rx_pen + * @tx_pen: TX special tag info over preamble + * 0 - disabled + * 1 - enabled + */ +struct mxl862xx_ss_sp_tag { + u8 pid; + u8 mask; /* enum mxl862xx_ss_sp_tag_mask */ + u8 rx; /* enum mxl862xx_ss_sp_tag_rx */ + u8 tx; /* enum mxl862xx_ss_sp_tag_tx */ + u8 rx_pen; /* enum mxl862xx_ss_sp_tag_rx_pen */ + u8 tx_pen; /* boolean */ +} __packed; + +/** + * enum mxl862xx_logical_port_mode - Logical port mode + * @MXL862XX_LOGICAL_PORT_8BIT_WLAN: WLAN with 8-bit station ID + * @MXL862XX_LOGICAL_PORT_9BIT_WLAN: WLAN with 9-bit station ID + * @MXL862XX_LOGICAL_PORT_ETHERNET: Ethernet port + * @MXL862XX_LOGICAL_PORT_OTHER: Others + */ +enum mxl862xx_logical_port_mode { + MXL862XX_LOGICAL_PORT_8BIT_WLAN = 0, + MXL862XX_LOGICAL_PORT_9BIT_WLAN, + MXL862XX_LOGICAL_PORT_ETHERNET, + MXL862XX_LOGICAL_PORT_OTHER = 0xFF, +}; + +/** + * struct mxl862xx_ctp_port_assignment - CTP Port Assignment/association + * with logical port + * @logical_port_id: Logical Port Id. The valid range is hardware dependent + * @first_ctp_port_id: First CTP (Connectivity Termination Port) ID mapped + * to above logical port ID + * @number_of_ctp_port: Total number of CTP Ports mapped above logical port + * ID + * @mode: Logical port mode to define sub interface ID format. See + * &enum mxl862xx_logical_port_mode + * @bridge_port_id: Bridge Port ID (not FID). For allocation, each CTP + * allocated is mapped to the Bridge Port given by this + * field. The Bridge Port will be configured to use first CTP + * as egress CTP. + * + * A CTP (Connectivity Termination Port) is equivalent to + * Logical Port + Sub-Interface-ID (capped at 31). + */ +struct mxl862xx_ctp_port_assignment { + u8 logical_port_id; + __le16 first_ctp_port_id; + __le16 number_of_ctp_port; + __le32 mode; /* enum mxl862xx_logical_port_mode */ + __le16 bridge_port_id; +} __packed; + +/** + * struct mxl862xx_sys_fw_image_version - Firmware version information + * @iv_major: firmware major version + * @iv_minor: firmware minor version + * @iv_revision: firmware revision + * @iv_build_num: firmware build number + */ +struct mxl862xx_sys_fw_image_version { + u8 iv_major; + u8 iv_minor; + __le16 iv_revision; + __le32 iv_build_num; +} __packed; + +#endif /* __MXL862XX_API_H */ diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h new file mode 100644 index 000000000000..f6852ade64e7 --- /dev/null +++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef __MXL862XX_CMD_H +#define __MXL862XX_CMD_H + +#define MXL862XX_MMD_DEV 30 +#define MXL862XX_MMD_REG_CTRL 0 +#define MXL862XX_MMD_REG_LEN_RET 1 +#define MXL862XX_MMD_REG_DATA_FIRST 2 +#define MXL862XX_MMD_REG_DATA_LAST 95 +#define MXL862XX_MMD_REG_DATA_MAX_SIZE \ + (MXL862XX_MMD_REG_DATA_LAST - MXL862XX_MMD_REG_DATA_FIRST + 1) + +#define MXL862XX_COMMON_MAGIC 0x100 +#define MXL862XX_BRDG_MAGIC 0x300 +#define MXL862XX_BRDGPORT_MAGIC 0x400 +#define MXL862XX_CTP_MAGIC 0x500 +#define MXL862XX_SWMAC_MAGIC 0xa00 +#define MXL862XX_SS_MAGIC 0x1600 +#define GPY_GPY2XX_MAGIC 0x1800 +#define SYS_MISC_MAGIC 0x1900 + +#define MXL862XX_COMMON_CFGGET (MXL862XX_COMMON_MAGIC + 0x9) +#define MXL862XX_COMMON_REGISTERMOD (MXL862XX_COMMON_MAGIC + 0x11) + +#define MXL862XX_BRIDGE_ALLOC (MXL862XX_BRDG_MAGIC + 0x1) +#define MXL862XX_BRIDGE_CONFIGSET (MXL862XX_BRDG_MAGIC + 0x2) +#define MXL862XX_BRIDGE_CONFIGGET (MXL862XX_BRDG_MAGIC + 0x3) +#define MXL862XX_BRIDGE_FREE (MXL862XX_BRDG_MAGIC + 0x4) + +#define MXL862XX_BRIDGEPORT_ALLOC (MXL862XX_BRDGPORT_MAGIC + 0x1) +#define MXL862XX_BRIDGEPORT_CONFIGSET (MXL862XX_BRDGPORT_MAGIC + 0x2) +#define MXL862XX_BRIDGEPORT_CONFIGGET (MXL862XX_BRDGPORT_MAGIC + 0x3) +#define MXL862XX_BRIDGEPORT_FREE (MXL862XX_BRDGPORT_MAGIC + 0x4) + +#define MXL862XX_CTP_PORTASSIGNMENTSET (MXL862XX_CTP_MAGIC + 0x3) + +#define MXL862XX_MAC_TABLECLEARCOND (MXL862XX_SWMAC_MAGIC + 0x8) + +#define MXL862XX_SS_SPTAG_SET (MXL862XX_SS_MAGIC + 0x02) + +#define INT_GPHY_READ (GPY_GPY2XX_MAGIC + 0x01) +#define INT_GPHY_WRITE (GPY_GPY2XX_MAGIC + 0x02) + +#define SYS_MISC_FW_VERSION (SYS_MISC_MAGIC + 0x02) + +#define MMD_API_MAXIMUM_ID 0x7fff + +#endif /* __MXL862XX_CMD_H */ diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-host.c b/drivers/net/dsa/mxl862xx/mxl862xx-host.c new file mode 100644 index 000000000000..f2b3c0b1dff1 --- /dev/null +++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Based upon the Maxlinear SDK driver + * + * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org> + * Copyright (C) 2025 John Crispin <john@phrozen.org> + * Copyright (C) 2024 MaxLinear Inc. + */ + +#include <linux/bits.h> +#include <linux/iopoll.h> +#include <linux/limits.h> +#include <net/dsa.h> +#include "mxl862xx.h" +#include "mxl862xx-host.h" + +#define CTRL_BUSY_MASK BIT(15) + +#define MXL862XX_MMD_REG_CTRL 0 +#define MXL862XX_MMD_REG_LEN_RET 1 +#define MXL862XX_MMD_REG_DATA_FIRST 2 +#define MXL862XX_MMD_REG_DATA_LAST 95 +#define MXL862XX_MMD_REG_DATA_MAX_SIZE \ + (MXL862XX_MMD_REG_DATA_LAST - MXL862XX_MMD_REG_DATA_FIRST + 1) + +#define MMD_API_SET_DATA_0 2 +#define MMD_API_GET_DATA_0 5 +#define MMD_API_RST_DATA 8 + +#define MXL862XX_SWITCH_RESET 0x9907 + +static int mxl862xx_reg_read(struct mxl862xx_priv *priv, u32 addr) +{ + return __mdiodev_c45_read(priv->mdiodev, MDIO_MMD_VEND1, addr); +} + +static int mxl862xx_reg_write(struct mxl862xx_priv *priv, u32 addr, u16 data) +{ + return __mdiodev_c45_write(priv->mdiodev, MDIO_MMD_VEND1, addr, data); +} + +static int mxl862xx_ctrl_read(struct mxl862xx_priv *priv) +{ + return mxl862xx_reg_read(priv, MXL862XX_MMD_REG_CTRL); +} + +static int mxl862xx_busy_wait(struct mxl862xx_priv *priv) +{ + int val; + + return readx_poll_timeout(mxl862xx_ctrl_read, priv, val, + !(val & CTRL_BUSY_MASK), 15, 10000); +} + +static int mxl862xx_set_data(struct mxl862xx_priv *priv, u16 words) +{ + int ret; + u16 cmd; + + ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_LEN_RET, + MXL862XX_MMD_REG_DATA_MAX_SIZE * sizeof(u16)); + if (ret < 0) + return ret; + + cmd = words / MXL862XX_MMD_REG_DATA_MAX_SIZE - 1; + if (!(cmd < 2)) + return -EINVAL; + + cmd += MMD_API_SET_DATA_0; + ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_CTRL, + cmd | CTRL_BUSY_MASK); + if (ret < 0) + return ret; + + return mxl862xx_busy_wait(priv); +} + +static int mxl862xx_get_data(struct mxl862xx_priv *priv, u16 words) +{ + int ret; + u16 cmd; + + ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_LEN_RET, + MXL862XX_MMD_REG_DATA_MAX_SIZE * sizeof(u16)); + if (ret < 0) + return ret; + + cmd = words / MXL862XX_MMD_REG_DATA_MAX_SIZE; + if (!(cmd > 0 && cmd < 3)) + return -EINVAL; + + cmd += MMD_API_GET_DATA_0; + ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_CTRL, + cmd | CTRL_BUSY_MASK); + if (ret < 0) + return ret; + + return mxl862xx_busy_wait(priv); +} + +static int mxl862xx_firmware_return(int ret) +{ + /* Only 16-bit values are valid. */ + if (WARN_ON(ret & GENMASK(31, 16))) + return -EINVAL; + + /* Interpret value as signed 16-bit integer. */ + return (s16)ret; +} + +static int mxl862xx_send_cmd(struct mxl862xx_priv *priv, u16 cmd, u16 size, + bool quiet) +{ + int ret; + + ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_LEN_RET, size); + if (ret) + return ret; + + ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_CTRL, + cmd | CTRL_BUSY_MASK); + if (ret) + return ret; + + ret = mxl862xx_busy_wait(priv); + if (ret) + return ret; + + ret = mxl862xx_reg_read(priv, MXL862XX_MMD_REG_LEN_RET); + if (ret < 0) + return ret; + + /* handle errors returned by the firmware as -EIO + * The firmware is based on Zephyr OS and uses the errors as + * defined in errno.h of Zephyr OS. See + * https://github.com/zephyrproject-rtos/zephyr/blob/v3.7.0/lib/libc/minimal/include/errno.h + */ + ret = mxl862xx_firmware_return(ret); + if (ret < 0) { + if (!quiet) + dev_err(&priv->mdiodev->dev, + "CMD %04x returned error %d\n", cmd, ret); + return -EIO; + } + + return ret; +} + +int mxl862xx_api_wrap(struct mxl862xx_priv *priv, u16 cmd, void *_data, + u16 size, bool read, bool quiet) +{ + __le16 *data = _data; + int ret, cmd_ret; + u16 max, i; + + dev_dbg(&priv->mdiodev->dev, "CMD %04x DATA %*ph\n", cmd, size, data); + + mutex_lock_nested(&priv->mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED); + + max = (size + 1) / 2; + + ret = mxl862xx_busy_wait(priv); + if (ret < 0) + goto out; + + for (i = 0; i < max; i++) { + u16 off = i % MXL862XX_MMD_REG_DATA_MAX_SIZE; + + if (i && off == 0) { + /* Send command to set data when every + * MXL862XX_MMD_REG_DATA_MAX_SIZE of WORDs are written. + */ + ret = mxl862xx_set_data(priv, i); + if (ret < 0) + goto out; + } + + ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_DATA_FIRST + off, + le16_to_cpu(data[i])); + if (ret < 0) + goto out; + } + + ret = mxl862xx_send_cmd(priv, cmd, size, quiet); + if (ret < 0 || !read) + goto out; + + /* store result of mxl862xx_send_cmd() */ + cmd_ret = ret; + + for (i = 0; i < max; i++) { + u16 off = i % MXL862XX_MMD_REG_DATA_MAX_SIZE; + + if (i && off == 0) { + /* Send command to fetch next batch of data when every + * MXL862XX_MMD_REG_DATA_MAX_SIZE of WORDs are read. + */ + ret = mxl862xx_get_data(priv, i); + if (ret < 0) + goto out; + } + + ret = mxl862xx_reg_read(priv, MXL862XX_MMD_REG_DATA_FIRST + off); + if (ret < 0) + goto out; + + if ((i * 2 + 1) == size) { + /* Special handling for last BYTE if it's not WORD + * aligned to avoid writing beyond the allocated data + * structure. + */ + *(uint8_t *)&data[i] = ret & 0xff; + } else { + data[i] = cpu_to_le16((u16)ret); + } + } + + /* on success return the result of the mxl862xx_send_cmd() */ + ret = cmd_ret; + + dev_dbg(&priv->mdiodev->dev, "RET %d DATA %*ph\n", ret, size, data); + +out: + mutex_unlock(&priv->mdiodev->bus->mdio_lock); + + return ret; +} + +int mxl862xx_reset(struct mxl862xx_priv *priv) +{ + int ret; + + mutex_lock_nested(&priv->mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED); + + /* Software reset */ + ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_LEN_RET, 0); + if (ret) + goto out; + + ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_CTRL, MXL862XX_SWITCH_RESET); +out: + mutex_unlock(&priv->mdiodev->bus->mdio_lock); + + return ret; +} diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-host.h b/drivers/net/dsa/mxl862xx/mxl862xx-host.h new file mode 100644 index 000000000000..7cc496f6be5c --- /dev/null +++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef __MXL862XX_HOST_H +#define __MXL862XX_HOST_H + +#include "mxl862xx.h" + +int mxl862xx_api_wrap(struct mxl862xx_priv *priv, u16 cmd, void *data, u16 size, + bool read, bool quiet); +int mxl862xx_reset(struct mxl862xx_priv *priv); + +#endif /* __MXL862XX_HOST_H */ diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.c b/drivers/net/dsa/mxl862xx/mxl862xx.c new file mode 100644 index 000000000000..2ccfb53e6dcb --- /dev/null +++ b/drivers/net/dsa/mxl862xx/mxl862xx.c @@ -0,0 +1,475 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for MaxLinear MxL862xx switch family + * + * Copyright (C) 2024 MaxLinear Inc. + * Copyright (C) 2025 John Crispin <john@phrozen.org> + * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org> + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/of_device.h> +#include <linux/of_mdio.h> +#include <linux/phy.h> +#include <linux/phylink.h> +#include <net/dsa.h> + +#include "mxl862xx.h" +#include "mxl862xx-api.h" +#include "mxl862xx-cmd.h" +#include "mxl862xx-host.h" + +#define MXL862XX_API_WRITE(dev, cmd, data) \ + mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), false, false) +#define MXL862XX_API_READ(dev, cmd, data) \ + mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), true, false) +#define MXL862XX_API_READ_QUIET(dev, cmd, data) \ + mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), true, true) + +#define DSA_MXL_PORT(port) ((port) + 1) +#define DSA_MXL_CPU_PORTS(ds) (dsa_cpu_ports(ds) << 1) + +#define MXL862XX_SDMA_PCTRLP(p) (0xbc0 + ((p) * 0x6)) +#define MXL862XX_SDMA_PCTRL_EN BIT(0) + +#define MXL862XX_FDMA_PCTRLP(p) (0xa80 + ((p) * 0x6)) +#define MXL862XX_FDMA_PCTRL_EN BIT(0) + +#define MXL862XX_READY_TIMEOUT_MS 10000 +#define MXL862XX_READY_POLL_MS 100 + +static enum dsa_tag_protocol mxl862xx_get_tag_protocol(struct dsa_switch *ds, + int port, + enum dsa_tag_protocol m) +{ + return DSA_TAG_PROTO_MXL862; +} + +/* PHY access via firmware relay */ +static int mxl862xx_phy_read_mmd(struct mxl862xx_priv *priv, int port, + int devadd, int reg) +{ + struct mdio_relay_data param = { + .phy = port, + .mmd = devadd, + .reg = cpu_to_le16(reg), + }; + int ret; + + ret = MXL862XX_API_READ(priv, INT_GPHY_READ, param); + if (ret) + return ret; + + return le16_to_cpu(param.data); +} + +static int mxl862xx_phy_write_mmd(struct mxl862xx_priv *priv, int port, + int devadd, int reg, u16 data) +{ + struct mdio_relay_data param = { + .phy = port, + .mmd = devadd, + .reg = cpu_to_le16(reg), + .data = cpu_to_le16(data), + }; + + return MXL862XX_API_WRITE(priv, INT_GPHY_WRITE, param); +} + +static int mxl862xx_phy_read_mii_bus(struct mii_bus *bus, int port, int regnum) +{ + return mxl862xx_phy_read_mmd(bus->priv, port, 0, regnum); +} + +static int mxl862xx_phy_write_mii_bus(struct mii_bus *bus, int port, + int regnum, u16 val) +{ + return mxl862xx_phy_write_mmd(bus->priv, port, 0, regnum, val); +} + +static int mxl862xx_phy_read_c45_mii_bus(struct mii_bus *bus, int port, + int devadd, int regnum) +{ + return mxl862xx_phy_read_mmd(bus->priv, port, devadd, regnum); +} + +static int mxl862xx_phy_write_c45_mii_bus(struct mii_bus *bus, int port, + int devadd, int regnum, u16 val) +{ + return mxl862xx_phy_write_mmd(bus->priv, port, devadd, regnum, val); +} + +static int mxl862xx_configure_tag_proto(struct dsa_switch *ds, int port, bool enable) +{ + struct mxl862xx_ctp_port_assignment assign = { + .number_of_ctp_port = cpu_to_le16(enable ? (32 - DSA_MXL_PORT(port)) : 1), + .logical_port_id = DSA_MXL_PORT(port), + .first_ctp_port_id = cpu_to_le16(DSA_MXL_PORT(port)), + .mode = cpu_to_le32(MXL862XX_LOGICAL_PORT_ETHERNET), + }; + struct mxl862xx_ss_sp_tag tag = { + .pid = DSA_MXL_PORT(port), + .mask = MXL862XX_SS_SP_TAG_MASK_RX | MXL862XX_SS_SP_TAG_MASK_TX, + .rx = enable ? MXL862XX_SS_SP_TAG_RX_TAG_NO_INSERT : + MXL862XX_SS_SP_TAG_RX_NO_TAG_INSERT, + .tx = enable ? MXL862XX_SS_SP_TAG_TX_TAG_NO_REMOVE : + MXL862XX_SS_SP_TAG_TX_TAG_REMOVE, + }; + int ret; + + ret = MXL862XX_API_WRITE(ds->priv, MXL862XX_SS_SPTAG_SET, tag); + if (ret) + return ret; + + return MXL862XX_API_WRITE(ds->priv, MXL862XX_CTP_PORTASSIGNMENTSET, assign); +} + +static int mxl862xx_port_state(struct dsa_switch *ds, int port, bool enable) +{ + struct mxl862xx_register_mod sdma = { + .addr = cpu_to_le16(MXL862XX_SDMA_PCTRLP(DSA_MXL_PORT(port))), + .data = cpu_to_le16(enable ? MXL862XX_SDMA_PCTRL_EN : 0), + .mask = cpu_to_le16(MXL862XX_SDMA_PCTRL_EN), + }; + struct mxl862xx_register_mod fdma = { + .addr = cpu_to_le16(MXL862XX_FDMA_PCTRLP(DSA_MXL_PORT(port))), + .data = cpu_to_le16(enable ? MXL862XX_FDMA_PCTRL_EN : 0), + .mask = cpu_to_le16(MXL862XX_FDMA_PCTRL_EN), + }; + int ret; + + ret = MXL862XX_API_WRITE(ds->priv, MXL862XX_COMMON_REGISTERMOD, sdma); + if (ret) + return ret; + + return MXL862XX_API_WRITE(ds->priv, MXL862XX_COMMON_REGISTERMOD, fdma); +} + +static int mxl862xx_port_enable(struct dsa_switch *ds, int port, + struct phy_device *phydev) +{ + return mxl862xx_port_state(ds, port, true); +} + +static void mxl862xx_port_disable(struct dsa_switch *ds, int port) +{ + mxl862xx_port_state(ds, port, false); +} + +static void mxl862xx_port_fast_age(struct dsa_switch *ds, int port) +{ + struct mxl862xx_mac_table_clear param = { + .type = MXL862XX_MAC_CLEAR_PHY_PORT, + .port_id = DSA_MXL_PORT(port), + }; + + if (MXL862XX_API_WRITE(ds->priv, MXL862XX_MAC_TABLECLEARCOND, param)) + dev_err(ds->dev, "failed to clear fdb on port %d\n", port); +} + +static int mxl862xx_isolate_port(struct dsa_switch *ds, int port) +{ + struct mxl862xx_bridge_port_config br_port_cfg = {}; + struct mxl862xx_bridge_alloc br_alloc = {}; + int ret; + + ret = MXL862XX_API_READ(ds->priv, MXL862XX_BRIDGE_ALLOC, br_alloc); + if (ret) { + dev_err(ds->dev, "failed to allocate a bridge for port %d\n", port); + return ret; + } + + br_port_cfg.bridge_id = br_alloc.bridge_id; + br_port_cfg.bridge_port_id = cpu_to_le16(DSA_MXL_PORT(port)); + br_port_cfg.mask = cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_ID | + MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP | + MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING | + MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MAC_LEARNING); + br_port_cfg.src_mac_learning_disable = true; + br_port_cfg.vlan_src_mac_vid_enable = false; + br_port_cfg.vlan_dst_mac_vid_enable = false; + br_port_cfg.bridge_port_map[0] = cpu_to_le16(DSA_MXL_CPU_PORTS(ds)); + + return MXL862XX_API_WRITE(ds->priv, MXL862XX_BRIDGEPORT_CONFIGSET, br_port_cfg); +} + +static int mxl862xx_setup_mdio(struct dsa_switch *ds) +{ + struct mxl862xx_priv *priv = ds->priv; + struct device *dev = ds->dev; + struct device_node *mdio_np; + struct mii_bus *bus; + static int idx; + int ret; + + bus = devm_mdiobus_alloc(dev); + if (!bus) + return -ENOMEM; + + bus->priv = priv; + ds->user_mii_bus = bus; + bus->name = KBUILD_MODNAME "-mii"; + snprintf(bus->id, MII_BUS_ID_SIZE, KBUILD_MODNAME "-%d", idx++); + bus->read_c45 = mxl862xx_phy_read_c45_mii_bus; + bus->write_c45 = mxl862xx_phy_write_c45_mii_bus; + bus->read = mxl862xx_phy_read_mii_bus; + bus->write = mxl862xx_phy_write_mii_bus; + bus->parent = dev; + bus->phy_mask = ~ds->phys_mii_mask; + + mdio_np = of_get_child_by_name(dev->of_node, "mdio"); + if (!mdio_np) + return -ENODEV; + + ret = devm_of_mdiobus_register(dev, bus, mdio_np); + of_node_put(mdio_np); + + return ret; +} + +static int mxl862xx_wait_ready(struct dsa_switch *ds) +{ + struct mxl862xx_sys_fw_image_version ver = {}; + unsigned long start = jiffies, timeout; + struct mxl862xx_priv *priv = ds->priv; + struct mxl862xx_cfg cfg = {}; + int ret; + + timeout = start + msecs_to_jiffies(MXL862XX_READY_TIMEOUT_MS); + msleep(2000); /* it always takes at least 2 seconds */ + do { + ret = MXL862XX_API_READ_QUIET(priv, SYS_MISC_FW_VERSION, ver); + if (ret || !ver.iv_major) + goto not_ready_yet; + + /* being able to perform CFGGET indicates that + * the firmware is ready + */ + ret = MXL862XX_API_READ_QUIET(priv, + MXL862XX_COMMON_CFGGET, + cfg); + if (ret) + goto not_ready_yet; + + dev_info(ds->dev, "switch ready after %ums, firmware %u.%u.%u (build %u)\n", + jiffies_to_msecs(jiffies - start), + ver.iv_major, ver.iv_minor, + le16_to_cpu(ver.iv_revision), + le32_to_cpu(ver.iv_build_num)); + return 0; + +not_ready_yet: + msleep(MXL862XX_READY_POLL_MS); + } while (time_before(jiffies, timeout)); + + dev_err(ds->dev, "switch not responding after reset\n"); + return -ETIMEDOUT; +} + +static int mxl862xx_setup(struct dsa_switch *ds) +{ + struct mxl862xx_bridge_port_config br_port_cfg = {}; + struct mxl862xx_priv *priv = ds->priv; + u16 bridge_port_map = 0; + struct dsa_port *dp; + int cpu_port = -1; + int ret; + + dsa_switch_for_each_cpu_port(dp, ds) { + /* Only a single CPU port is supported by now */ + if (cpu_port != -1) + return -EINVAL; + + cpu_port = dp->index; + } + + ret = mxl862xx_reset(priv); + if (ret) + return ret; + + ret = mxl862xx_wait_ready(ds); + if (ret) + return ret; + + /* CPU port bridge setup */ + br_port_cfg.mask = cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP | + MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING | + MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MAC_LEARNING); + + br_port_cfg.bridge_port_id = cpu_to_le16(DSA_MXL_PORT(cpu_port)); + br_port_cfg.src_mac_learning_disable = false; + br_port_cfg.vlan_src_mac_vid_enable = true; + br_port_cfg.vlan_dst_mac_vid_enable = true; + + /* include all non-CPU ports in the CPU portmap */ + dsa_switch_for_each_available_port(dp, ds) { + if (dsa_port_is_cpu(dp)) + continue; + + bridge_port_map |= BIT(DSA_MXL_PORT(dp->index)); + } + br_port_cfg.bridge_port_map[0] |= cpu_to_le16(bridge_port_map); + + ret = MXL862XX_API_WRITE(priv, MXL862XX_BRIDGEPORT_CONFIGSET, + br_port_cfg); + if (ret) { + dev_err(ds->dev, "failed to set the CPU portmap\n"); + return ret; + } + mxl862xx_port_fast_age(ds, cpu_port); + + ret = mxl862xx_setup_mdio(ds); + if (ret) + return ret; + + return 0; +} + +static int mxl862xx_port_setup(struct dsa_switch *ds, int port) +{ + bool is_cpu_port = dsa_is_cpu_port(ds, port); + int ret; + + ret = mxl862xx_configure_tag_proto(ds, port, is_cpu_port); + if (ret) + return ret; + + if (!is_cpu_port && !dsa_is_unused_port(ds, port)) { + ret = mxl862xx_isolate_port(ds, port); + if (ret) + return ret; + } + + mxl862xx_port_fast_age(ds, port); + + ret = mxl862xx_port_state(ds, port, is_cpu_port); + if (ret) + return ret; + + return 0; +} + +static void mxl862xx_phylink_get_caps(struct dsa_switch *ds, int port, + struct phylink_config *config) +{ + config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE | MAC_10 | + MAC_100 | MAC_1000 | MAC_2500FD; + + __set_bit(PHY_INTERFACE_MODE_INTERNAL, + config->supported_interfaces); +} + +static const struct dsa_switch_ops mxl862xx_switch_ops = { + .get_tag_protocol = mxl862xx_get_tag_protocol, + .setup = mxl862xx_setup, + .port_setup = mxl862xx_port_setup, + .phylink_get_caps = mxl862xx_phylink_get_caps, + .port_enable = mxl862xx_port_enable, + .port_disable = mxl862xx_port_disable, + .port_fast_age = mxl862xx_port_fast_age, +}; + +static void mxl862xx_phylink_mac_config(struct phylink_config *config, + unsigned int mode, + const struct phylink_link_state *state) +{ +} + +static void mxl862xx_phylink_mac_link_down(struct phylink_config *config, + unsigned int mode, + phy_interface_t interface) +{ +} + +static void mxl862xx_phylink_mac_link_up(struct phylink_config *config, + struct phy_device *phydev, + unsigned int mode, + phy_interface_t interface, + int speed, int duplex, + bool tx_pause, bool rx_pause) +{ +} + +static const struct phylink_mac_ops mxl862xx_phylink_mac_ops = { + .mac_config = mxl862xx_phylink_mac_config, + .mac_link_down = mxl862xx_phylink_mac_link_down, + .mac_link_up = mxl862xx_phylink_mac_link_up, +}; + +static int mxl862xx_probe(struct mdio_device *mdiodev) +{ + struct device *dev = &mdiodev->dev; + struct mxl862xx_priv *priv; + struct dsa_switch *ds; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->mdiodev = mdiodev; + + ds = devm_kzalloc(dev, sizeof(*ds), GFP_KERNEL); + if (!ds) + return -ENOMEM; + + priv->ds = ds; + ds->dev = dev; + ds->priv = priv; + ds->ops = &mxl862xx_switch_ops; + ds->phylink_mac_ops = &mxl862xx_phylink_mac_ops; + ds->num_ports = MXL862XX_MAX_PORT_NUM; + + dev_set_drvdata(dev, ds); + + ret = dsa_register_switch(ds); + if (ret) + return ret; + + return 0; +} + +static void mxl862xx_remove(struct mdio_device *mdiodev) +{ + struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev); + + if (!ds) + return; + + dsa_unregister_switch(ds); +} + +static void mxl862xx_shutdown(struct mdio_device *mdiodev) +{ + struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev); + + if (!ds) + return; + + dsa_switch_shutdown(ds); + + dev_set_drvdata(&mdiodev->dev, NULL); +} + +static const struct of_device_id mxl862xx_of_match[] = { + { .compatible = "maxlinear,mxl86282" }, + { .compatible = "maxlinear,mxl86252" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mxl862xx_of_match); + +static struct mdio_driver mxl862xx_driver = { + .probe = mxl862xx_probe, + .remove = mxl862xx_remove, + .shutdown = mxl862xx_shutdown, + .mdiodrv.driver = { + .name = "mxl862xx", + .of_match_table = mxl862xx_of_match, + }, +}; + +mdio_module_driver(mxl862xx_driver); + +MODULE_DESCRIPTION("Driver for MaxLinear MxL862xx switch family"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.h b/drivers/net/dsa/mxl862xx/mxl862xx.h new file mode 100644 index 000000000000..e6de494e834a --- /dev/null +++ b/drivers/net/dsa/mxl862xx/mxl862xx.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef __MXL862XX_H +#define __MXL862XX_H + +#include <linux/mdio.h> +#include <net/dsa.h> + +#define MXL862XX_MAX_PHY_PORT_NUM 8 +#define MXL862XX_MAX_EXT_PORT_NUM 7 +#define MXL862XX_MAX_PORT_NUM (MXL862XX_MAX_PHY_PORT_NUM + \ + MXL862XX_MAX_EXT_PORT_NUM) + +struct mxl862xx_priv { + struct dsa_switch *ds; + struct mdio_device *mdiodev; +}; + +#endif /* __MXL862XX_H */ -- 2.52.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH net-next v9 4/4] net: dsa: add basic initial driver for MxL862xx switches 2026-01-27 21:38 ` [PATCH net-next v9 4/4] net: dsa: add basic initial driver for MxL862xx switches Daniel Golle @ 2026-01-28 11:29 ` Vladimir Oltean 2026-01-28 16:45 ` Daniel Golle 0 siblings, 1 reply; 10+ messages in thread From: Vladimir Oltean @ 2026-01-28 11:29 UTC (permalink / raw) To: Daniel Golle Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiner Kallweit, Russell King, Simon Horman, netdev, devicetree, linux-kernel, Frank Wunderlich, Chad Monroe, Cezary Wilmanski, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin On Tue, Jan 27, 2026 at 09:38:25PM +0000, Daniel Golle wrote: > Add very basic DSA driver for MaxLinear's MxL862xx switches. > > In contrast to previous MaxLinear switches the MxL862xx has a built-in > processor that runs a sophisticated firmware based on Zephyr RTOS. > Interaction between the host and the switch hence is organized using a > software API of that firmware rather than accessing hardware registers > directly. > > Add descriptions of the most basic firmware API calls to access the > built-in MDIO bus hosting the 2.5GE PHYs, basic port control as well as > setting up the CPU port. > > Implement a very basic DSA driver using that API which is sufficient to > get packets flowing between the user ports and the CPU port. > > The firmware offers all features one would expect from a modern switch > hardware, they will be added one by one in follow-up patch series. > > Signed-off-by: Daniel Golle <daniel@makrotopia.org> > --- > v9: > * remove practically unused struct hw_info > * lots of kerneldoc improvements in mxl862xx-api.h > * drop .mac_select_pcs() stub > * better handling for firmware error return value > * apply reverse xmas tree in mxl862xx_api_wrap > * guard headers with #ifdef macro > * include net/dsa.h and linux/mdio.h in mxl862xx.h > * call mxl862xx_port_fast_age() only once in .port_setup > * don't create isolation bridges for unused ports > * replace errornous cast with correct range of values denoting firmware errors > > v8: > * use le32 endian in bridge_port_config API > * remove duplciate assignment of br_port_cfg.bridge_port_id when setting > up CPU port > > v7: > * fix kerneldoc style > > v6: > * include bridge and bridgeport API needed to isolate ports > * remove warning in .setup as ports are now isolated > * make ready-after-reset check more robust by adding delay > * sort structs in order of struct definitions > * best effort to sort functions without introducing additional prototypes > * always use enums with kerneldoc comments in mxl862xx-api.h > * remove bogus .phy_read and .phy_write DSA ops as the driver anyway registers > a user MDIO bus with Clause-22 and Clause-45 operations > * various small style fixes > > v5: > * output warning in .setup regarding unknown pre-configuration > * add comment explaining why CFGGET is used in reset function > > RFC v4: > * poll switch readiness after reset > * implement driver shutdown > * added port_fast_aging API call and driver op > * unified port setup in new .port_setup op > * improve comment explaining special handlign for unaligned API read > * various typos > > RFC v3: > * fix return value being uninitialized on error in mxl862xx_api_wrap() > * add missing descrition in kerneldoc comment of > struct mxl862xx_ss_sp_tag > > RFC v2: > * make use of struct mdio_device > * add phylink_mac_ops stubs > * drop leftover nonsense from mxl862xx_phylink_get_caps() > * use __le32 instead of enum types in over-the-wire structs > * use existing MDIO_* macros whenever possible > * simplify API constants to be more readable > * use readx_poll_timeout instead of open-coding poll timeout loop > * add mxl862xx_reg_read() and mxl862xx_reg_write() helpers > * demystify error codes returned by the firmware > * add #defines for mxl862xx_ss_sp_tag member values > * move reset to dedicated function, clarify magic number being the > reset command ID > --- > MAINTAINERS | 1 + > drivers/net/dsa/Kconfig | 2 + > drivers/net/dsa/Makefile | 1 + > drivers/net/dsa/mxl862xx/Kconfig | 12 + > drivers/net/dsa/mxl862xx/Makefile | 3 + > drivers/net/dsa/mxl862xx/mxl862xx-api.h | 675 +++++++++++++++++++++++ > drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 49 ++ > drivers/net/dsa/mxl862xx/mxl862xx-host.c | 245 ++++++++ > drivers/net/dsa/mxl862xx/mxl862xx-host.h | 12 + > drivers/net/dsa/mxl862xx/mxl862xx.c | 475 ++++++++++++++++ > drivers/net/dsa/mxl862xx/mxl862xx.h | 19 + > 11 files changed, 1494 insertions(+) > create mode 100644 drivers/net/dsa/mxl862xx/Kconfig > create mode 100644 drivers/net/dsa/mxl862xx/Makefile > create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-api.h > create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-cmd.h > create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-host.c > create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-host.h > create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx.c > create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx.h > > diff --git a/MAINTAINERS b/MAINTAINERS > index bd33de42604e..ca941ca5ddf1 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -15624,6 +15624,7 @@ M: Daniel Golle <daniel@makrotopia.org> > L: netdev@vger.kernel.org > S: Maintained > F: Documentation/devicetree/bindings/net/dsa/maxlinear,mxl862xx.yaml > +F: drivers/net/dsa/mxl862xx/ > F: net/dsa/tag_mxl862xx.c > > MCAN DEVICE DRIVER > diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig > index 7eb301fd987d..18f6e8b7f4cb 100644 > --- a/drivers/net/dsa/Kconfig > +++ b/drivers/net/dsa/Kconfig > @@ -74,6 +74,8 @@ source "drivers/net/dsa/microchip/Kconfig" > > source "drivers/net/dsa/mv88e6xxx/Kconfig" > > +source "drivers/net/dsa/mxl862xx/Kconfig" > + > source "drivers/net/dsa/ocelot/Kconfig" > > source "drivers/net/dsa/qca/Kconfig" > diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile > index 16de4ba3fa38..f5a463b87ec2 100644 > --- a/drivers/net/dsa/Makefile > +++ b/drivers/net/dsa/Makefile > @@ -20,6 +20,7 @@ obj-y += hirschmann/ > obj-y += lantiq/ > obj-y += microchip/ > obj-y += mv88e6xxx/ > +obj-y += mxl862xx/ > obj-y += ocelot/ > obj-y += qca/ > obj-y += realtek/ > diff --git a/drivers/net/dsa/mxl862xx/Kconfig b/drivers/net/dsa/mxl862xx/Kconfig > new file mode 100644 > index 000000000000..3722260da7d8 > --- /dev/null > +++ b/drivers/net/dsa/mxl862xx/Kconfig > @@ -0,0 +1,12 @@ > +# SPDX-License-Identifier: GPL-2.0-only > +config NET_DSA_MXL862 > + tristate "MaxLinear MxL862xx" > + depends on NET_DSA > + select MAXLINEAR_GPHY > + select NET_DSA_TAG_MXL_862XX > + help > + This enables support for the MaxLinear MxL862xx switch family. > + These switches have two 10GE SerDes interfaces, one typically > + used as CPU port. > + MxL86282 has eight 2.5 Gigabit PHYs > + MxL86252 has five 2.5 Gigabit PHYs > diff --git a/drivers/net/dsa/mxl862xx/Makefile b/drivers/net/dsa/mxl862xx/Makefile > new file mode 100644 > index 000000000000..d23dd3cd511d > --- /dev/null > +++ b/drivers/net/dsa/mxl862xx/Makefile > @@ -0,0 +1,3 @@ > +# SPDX-License-Identifier: GPL-2.0 > +obj-$(CONFIG_NET_DSA_MXL862) += mxl862xx_dsa.o > +mxl862xx_dsa-y := mxl862xx.o mxl862xx-host.o > diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-api.h b/drivers/net/dsa/mxl862xx/mxl862xx-api.h > new file mode 100644 > index 000000000000..84863717728e > --- /dev/null > +++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h > @@ -0,0 +1,675 @@ > +/* SPDX-License-Identifier: GPL-2.0-or-later */ > + > +#ifndef __MXL862XX_API_H > +#define __MXL862XX_API_H > + > +#include <linux/if_ether.h> > + > +/** > + * struct mdio_relay_data - relayed access to the switch internal MDIO bus > + * @data: data to be read or written > + * @phy: PHY index > + * @mmd: MMD device > + * @reg: register index > + */ > +struct mdio_relay_data { > + __le16 data; > + u8 phy; > + u8 mmd; > + __le16 reg; > +} __packed; > + > +/** > + * struct mxl862xx_register_mod - Register access parameter to directly > + * modify internal registers > + * @addr: Register address offset for modification > + * @data: Value to write to the register address > + * @mask: Mask of bits to be modified (1 to modify, 0 to ignore) > + * > + * Used for direct register modification operations. > + */ > +struct mxl862xx_register_mod { > + __le16 addr; > + __le16 data; > + __le16 mask; > +} __packed; > + > +/** > + * enum mxl862xx_mac_clear_type - MAC table clear type > + * @MXL862XX_MAC_CLEAR_PHY_PORT: clear dynamic entries based on port_id > + * @MXL862XX_MAC_CLEAR_DYNAMIC: clear all dynamic entries > + */ > +enum mxl862xx_mac_clear_type { > + MXL862XX_MAC_CLEAR_PHY_PORT = 0, > + MXL862XX_MAC_CLEAR_DYNAMIC, > +}; > + > +/** > + * struct mxl862xx_mac_table_clear - MAC table clear > + * @type: see &enum mxl862xx_mac_clear_type > + * @port_id: physical port id > + */ > +struct mxl862xx_mac_table_clear { > + u8 type; > + u8 port_id; > +} __packed; > + > +/** > + * enum mxl862xx_age_timer - Aging Timer Value. > + * @MXL862XX_AGETIMER_1_SEC: 1 second aging time > + * @MXL862XX_AGETIMER_10_SEC: 10 seconds aging time > + * @MXL862XX_AGETIMER_300_SEC: 300 seconds aging time > + * @MXL862XX_AGETIMER_1_HOUR: 1 hour aging time > + * @MXL862XX_AGETIMER_1_DAY: 24 hours aging time > + * @MXL862XX_AGETIMER_CUSTOM: Custom aging time in seconds > + */ > +enum mxl862xx_age_timer { > + MXL862XX_AGETIMER_1_SEC = 1, > + MXL862XX_AGETIMER_10_SEC, > + MXL862XX_AGETIMER_300_SEC, > + MXL862XX_AGETIMER_1_HOUR, > + MXL862XX_AGETIMER_1_DAY, > + MXL862XX_AGETIMER_CUSTOM, > +}; > + > +/** > + * struct mxl862xx_bridge_alloc - Bridge Allocation > + * @bridge_id: If the bridge allocation is successful, a valid ID will be > + * returned in this field. Otherwise, INVALID_HANDLE is > + * returned. For bridge free, this field should contain a > + * valid ID returned by the bridge allocation. > + * > + * Used by MXL862XX_BRIDGE_ALLOC and MXL862XX_BRIDGE_FREE. > + */ > +struct mxl862xx_bridge_alloc { > + __le16 bridge_id; > +} __packed; > + > +/** > + * enum mxl862xx_bridge_config_mask - Bridge configuration mask > + * @MXL862XX_BRIDGE_CONFIG_MASK_MAC_LEARNING_LIMIT: > + * Mask for mac_learning_limit_enable and mac_learning_limit. > + * @MXL862XX_BRIDGE_CONFIG_MASK_MAC_LEARNED_COUNT: > + * Mask for mac_learning_count > + * @MXL862XX_BRIDGE_CONFIG_MASK_MAC_DISCARD_COUNT: > + * Mask for learning_discard_event > + * @MXL862XX_BRIDGE_CONFIG_MASK_SUB_METER: > + * Mask for sub_metering_enable and traffic_sub_meter_id > + * @MXL862XX_BRIDGE_CONFIG_MASK_FORWARDING_MODE: > + * Mask for forward_broadcast, forward_unknown_multicast_ip, > + * forward_unknown_multicast_non_ip and forward_unknown_unicast. > + * @MXL862XX_BRIDGE_CONFIG_MASK_ALL: Enable all > + * @MXL862XX_BRIDGE_CONFIG_MASK_FORCE: Bypass any check for debug purpose > + */ > +enum mxl862xx_bridge_config_mask { > + MXL862XX_BRIDGE_CONFIG_MASK_MAC_LEARNING_LIMIT = BIT(0), > + MXL862XX_BRIDGE_CONFIG_MASK_MAC_LEARNED_COUNT = BIT(1), > + MXL862XX_BRIDGE_CONFIG_MASK_MAC_DISCARD_COUNT = BIT(2), > + MXL862XX_BRIDGE_CONFIG_MASK_SUB_METER = BIT(3), > + MXL862XX_BRIDGE_CONFIG_MASK_FORWARDING_MODE = BIT(4), > + MXL862XX_BRIDGE_CONFIG_MASK_ALL = 0x7FFFFFFF, > + MXL862XX_BRIDGE_CONFIG_MASK_FORCE = BIT(31) > +}; > + > +/** > + * enum mxl862xx_bridge_port_egress_meter - Meters for egress traffic type > + * @MXL862XX_BRIDGE_PORT_EGRESS_METER_BROADCAST: > + * Index of broadcast traffic meter > + * @MXL862XX_BRIDGE_PORT_EGRESS_METER_MULTICAST: > + * Index of known multicast traffic meter > + * @MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_IP: > + * Index of unknown multicast IP traffic meter > + * @MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_NON_IP: > + * Index of unknown multicast non-IP traffic meter > + * @MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_UC: > + * Index of unknown unicast traffic meter > + * @MXL862XX_BRIDGE_PORT_EGRESS_METER_OTHERS: > + * Index of traffic meter for other types > + * @MXL862XX_BRIDGE_PORT_EGRESS_METER_MAX: Number of index > + */ > +enum mxl862xx_bridge_port_egress_meter { > + MXL862XX_BRIDGE_PORT_EGRESS_METER_BROADCAST = 0, > + MXL862XX_BRIDGE_PORT_EGRESS_METER_MULTICAST, > + MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_IP, > + MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_NON_IP, > + MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_UC, > + MXL862XX_BRIDGE_PORT_EGRESS_METER_OTHERS, > + MXL862XX_BRIDGE_PORT_EGRESS_METER_MAX, > +}; > + > +/** > + * enum mxl862xx_bridge_forward_mode - Bridge forwarding type of packet > + * @MXL862XX_BRIDGE_FORWARD_FLOOD: Packet is flooded to port members of > + * ingress bridge port > + * @MXL862XX_BRIDGE_FORWARD_DISCARD: Packet is discarded > + * @MXL862XX_BRIDGE_FORWARD_CPU: Packet is forwarded to logical port 0 CTP > + * port 0 bridge port 0 nitpick: indentation not aligned > + */ > +enum mxl862xx_bridge_forward_mode { > + MXL862XX_BRIDGE_FORWARD_FLOOD = 0, > + MXL862XX_BRIDGE_FORWARD_DISCARD, > + MXL862XX_BRIDGE_FORWARD_CPU, > +}; > + > +/** > + * struct mxl862xx_bridge_config - Bridge Configuration > + * @bridge_id: Bridge ID (FID) > + * @mask: See &enum mxl862xx_bridge_config_mask > + * @mac_learning_limit_enable: Enable MAC learning limitation > + * @mac_learning_limit: Max number of MAC addresses that can be learned in > + * this bridge (all bridge ports) > + * @mac_learning_count: Number of MAC addresses learned from this bridge > + * @learning_discard_event: Number of learning discard events due to > + * hardware resource not available > + * @sub_metering_enable: Traffic metering on type of traffic (such as > + * broadcast, multicast, unknown unicast, etc) applies > + * @traffic_sub_meter_id: Meter for bridge process with specific type (such > + * as broadcast, multicast, unknown unicast, etc) > + * @forward_broadcast: Forwarding mode of broadcast traffic. See > + * &enum mxl862xx_bridge_forward_mode > + * @forward_unknown_multicast_ip: Forwarding mode of unknown multicast IP > + * traffic. See &enum mxl862xx_bridge_forward_mode > + * @forward_unknown_multicast_non_ip: Forwarding mode of unknown multicast > + * non-IP traffic. See &enum mxl862xx_bridge_forward_mode > + * @forward_unknown_unicast: Forwarding mode of unknown unicast traffic. See > + * &enum mxl862xx_bridge_forward_mode > + */ > +struct mxl862xx_bridge_config { > + __le16 bridge_id; > + __le32 mask; /* enum mxl862xx_bridge_config_mask */ > + u8 mac_learning_limit_enable; > + __le16 mac_learning_limit; > + __le16 mac_learning_count; > + __le32 learning_discard_event; > + u8 sub_metering_enable[MXL862XX_BRIDGE_PORT_EGRESS_METER_MAX]; > + __le16 traffic_sub_meter_id[MXL862XX_BRIDGE_PORT_EGRESS_METER_MAX]; > + __le32 forward_broadcast; /* enum mxl862xx_bridge_forward_mode */ > + __le32 forward_unknown_multicast_ip; /* enum mxl862xx_bridge_forward_mode */ > + __le32 forward_unknown_multicast_non_ip; /* enum mxl862xx_bridge_forward_mode */ > + __le32 forward_unknown_unicast; /* enum mxl862xx_bridge_forward_mode */ > +} __packed; > + > +/** > + * struct mxl862xx_bridge_port_alloc - Bridge Port Allocation > + * @bridge_port_id: If the bridge port allocation is successful, a valid ID > + * will be returned in this field. Otherwise, INVALID_HANDLE is returned. > + * For bridge port free, this field should contain a valid ID returned by > + * the bridge port allocation. ID 0 is special for the CPU port in > + * PRX300, mapping to CTP (Connectivity Termination Port) 0 (Logical Port > + * 0 with Sub-interface ID 0), and is pre-allocated during initialization. Just to be clear, "CPU port in PRX300" does not refer to the "DSA CPU port" that connects to the Linux system, no? Because mxl862xx_configure_tag_proto() assigns a different CTP ID for the DSA CPU port AFAICS, not 0. Without further clarifications, this comment is a bit confusing. I would at least add that CTP 0 is not used by the driver. > + * > + * Used by MXL862XX_BRIDGE_PORT_ALLOC and MXL862XX_BRIDGE_PORT_FREE. > + */ > +struct mxl862xx_bridge_port_alloc { > + __le16 bridge_port_id; > +}; > + > +/** > + * enum mxl862xx_bridge_port_config_mask - Bridge Port configuration mask > + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_ID: > + * Mask for bridge_id > + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_VLAN: > + * Mask for ingress_extended_vlan_enable, > + * ingress_extended_vlan_block_id and ingress_extended_vlan_block_size > + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_VLAN: > + * Mask for egress_extended_vlan_enable, egress_extended_vlan_block_id > + * and egress_extended_vlan_block_size > + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_MARKING: > + * Mask for ingress_marking_mode > + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_REMARKING: > + * Mask for egress_remarking_mode > + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_METER: > + * Mask for ingress_metering_enable and ingress_traffic_meter_id > + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_SUB_METER: > + * Mask for egress_sub_metering_enable and egress_traffic_sub_meter_id > + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_CTP_MAPPING: > + * Mask for dest_logical_port_id, pmapper_enable, dest_sub_if_id_group, > + * pmapper_mapping_mode, pmapper_id_valid and pmapper > + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP: > + * Mask for bridge_port_map > + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_DEST_IP_LOOKUP: > + * Mask for mc_dest_ip_lookup_disable > + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_IP_LOOKUP: > + * Mask for mc_src_ip_lookup_enable > + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_DEST_MAC_LOOKUP: > + * Mask for dest_mac_lookup_disable > + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING: > + * Mask for src_mac_learning_disable > + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_MAC_SPOOFING: > + * Mask for mac_spoofing_detect_enable > + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_PORT_LOCK: > + * Mask for port_lock_enable > + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_MAC_LEARNING_LIMIT: > + * Mask for mac_learning_limit_enable and mac_learning_limit > + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_MAC_LEARNED_COUNT: > + * Mask for mac_learning_count > + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_VLAN_FILTER: > + * Mask for ingress_vlan_filter_enable, ingress_vlan_filter_block_id > + * and ingress_vlan_filter_block_size > + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_VLAN_FILTER1: > + * Mask for bypass_egress_vlan_filter1, egress_vlan_filter1enable, > + * egress_vlan_filter1block_id and egress_vlan_filter1block_size > + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_VLAN_FILTER2: > + * Mask for egress_vlan_filter2enable, egress_vlan_filter2block_id and > + * egress_vlan_filter2block_size > + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MAC_LEARNING: > + * Mask for vlan_tag_selection, vlan_src_mac_priority_enable, > + * vlan_src_mac_dei_enable, vlan_src_mac_vid_enable, > + * vlan_dst_mac_priority_enable, vlan_dst_mac_dei_enable and > + * vlan_dst_mac_vid_enable > + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MULTICAST_LOOKUP: > + * Mask for vlan_multicast_priority_enable, > + * vlan_multicast_dei_enable and vlan_multicast_vid_enable > + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_LOOP_VIOLATION_COUNTER: > + * Mask for loop_violation_count > + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_ALL: Enable all > + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_FORCE: Bypass any check for debug purpose > + */ > +enum mxl862xx_bridge_port_config_mask { > + MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_ID = BIT(0), > + MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_VLAN = BIT(1), > + MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_VLAN = BIT(2), > + MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_MARKING = BIT(3), > + MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_REMARKING = BIT(4), > + MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_METER = BIT(5), > + MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_SUB_METER = BIT(6), > + MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_CTP_MAPPING = BIT(7), > + MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP = BIT(8), > + MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_DEST_IP_LOOKUP = BIT(9), > + MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_IP_LOOKUP = BIT(10), > + MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_DEST_MAC_LOOKUP = BIT(11), > + MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING = BIT(12), > + MXL862XX_BRIDGE_PORT_CONFIG_MASK_MAC_SPOOFING = BIT(13), > + MXL862XX_BRIDGE_PORT_CONFIG_MASK_PORT_LOCK = BIT(14), > + MXL862XX_BRIDGE_PORT_CONFIG_MASK_MAC_LEARNING_LIMIT = BIT(15), > + MXL862XX_BRIDGE_PORT_CONFIG_MASK_MAC_LEARNED_COUNT = BIT(16), > + MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_VLAN_FILTER = BIT(17), > + MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_VLAN_FILTER1 = BIT(18), > + MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_VLAN_FILTER2 = BIT(19), > + MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MAC_LEARNING = BIT(20), > + MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MULTICAST_LOOKUP = BIT(21), > + MXL862XX_BRIDGE_PORT_CONFIG_MASK_LOOP_VIOLATION_COUNTER = BIT(22), > + MXL862XX_BRIDGE_PORT_CONFIG_MASK_ALL = 0x7FFFFFFF, > + MXL862XX_BRIDGE_PORT_CONFIG_MASK_FORCE = BIT(31) > +}; > + > +/** > + * enum mxl862xx_color_marking_mode - Color Marking Mode > + * @MXL862XX_MARKING_ALL_GREEN: mark packets (except critical) to green > + * @MXL862XX_MARKING_INTERNAL_MARKING: do not change color and priority > + * @MXL862XX_MARKING_DEI: DEI mark mode > + * @MXL862XX_MARKING_PCP_8P0D: PCP 8P0D mark mode > + * @MXL862XX_MARKING_PCP_7P1D: PCP 7P1D mark mode > + * @MXL862XX_MARKING_PCP_6P2D: PCP 6P2D mark mode > + * @MXL862XX_MARKING_PCP_5P3D: PCP 5P3D mark mode > + * @MXL862XX_MARKING_DSCP_AF: DSCP AF class > + */ > +enum mxl862xx_color_marking_mode { > + MXL862XX_MARKING_ALL_GREEN = 0, > + MXL862XX_MARKING_INTERNAL_MARKING, > + MXL862XX_MARKING_DEI, > + MXL862XX_MARKING_PCP_8P0D, > + MXL862XX_MARKING_PCP_7P1D, > + MXL862XX_MARKING_PCP_6P2D, > + MXL862XX_MARKING_PCP_5P3D, > + MXL862XX_MARKING_DSCP_AF, > +}; > + > +/** > + * enum mxl862xx_color_remarking_mode - Color Remarking Mode > + * @MXL862XX_REMARKING_NONE: values from last process stage > + * @MXL862XX_REMARKING_DEI: DEI mark mode > + * @MXL862XX_REMARKING_PCP_8P0D: PCP 8P0D mark mode > + * @MXL862XX_REMARKING_PCP_7P1D: PCP 7P1D mark mode > + * @MXL862XX_REMARKING_PCP_6P2D: PCP 6P2D mark mode > + * @MXL862XX_REMARKING_PCP_5P3D: PCP 5P3D mark mode > + * @MXL862XX_REMARKING_DSCP_AF: DSCP AF class > + */ > +enum mxl862xx_color_remarking_mode { > + MXL862XX_REMARKING_NONE = 0, > + MXL862XX_REMARKING_DEI = 2, > + MXL862XX_REMARKING_PCP_8P0D, > + MXL862XX_REMARKING_PCP_7P1D, > + MXL862XX_REMARKING_PCP_6P2D, > + MXL862XX_REMARKING_PCP_5P3D, > + MXL862XX_REMARKING_DSCP_AF, > +}; > + > +/** > + * enum mxl862xx_pmapper_mapping_mode - P-mapper Mapping Mode > + * @MXL862XX_PMAPPER_MAPPING_PCP: Use PCP for VLAN tagged packets to derive > + * sub interface ID group > + * @MXL862XX_PMAPPER_MAPPING_LAG: Use LAG Index for Pmapper access > + * regardless of IP and VLAN packet > + * @MXL862XX_PMAPPER_MAPPING_DSCP: Use DSCP for VLAN tagged IP packets to > + * derive sub interface ID group > + */ > +enum mxl862xx_pmapper_mapping_mode { > + MXL862XX_PMAPPER_MAPPING_PCP = 0, > + MXL862XX_PMAPPER_MAPPING_LAG, > + MXL862XX_PMAPPER_MAPPING_DSCP, > +}; > + > +/** > + * struct mxl862xx_pmapper - P-mapper Configuration > + * @pmapper_id: Index of P-mapper (0-31) > + * @dest_sub_if_id_group: Sub interface ID group. Entry 0 is for non-IP and > + * non-VLAN tagged packets. Entries 1-8 are PCP mapping entries for VLAN > + * tagged packets. Entries 9-72 are DSCP or LAG mapping entries. nitpick: lines are a bit too long > + * > + * Used by CTP port config and bridge port config. In case of LAG, it is > + * user's responsibility to provide the mapped entries in given P-mapper > + * table. In other modes the entries are auto mapped from input packet. > + */ > +struct mxl862xx_pmapper { > + __le16 pmapper_id; > + u8 dest_sub_if_id_group[73]; > +} __packed; > + > +/** > + * struct mxl862xx_bridge_port_config - Bridge Port Configuration > + * @bridge_port_id: Bridge Port ID allocated by bridge port allocation > + * @mask: See &enum mxl862xx_bridge_port_config_mask > + * @bridge_id: Bridge ID (FID) to which this bridge port is associated > + * @ingress_extended_vlan_enable: Enable extended VLAN processing for > + * ingress traffic > + * @ingress_extended_vlan_block_id: Extended VLAN block allocated for > + * ingress traffic > + * @ingress_extended_vlan_block_size: Extended VLAN block size for ingress > + * traffic > + * @egress_extended_vlan_enable: Enable extended VLAN processing for egress > + * traffic > + * @egress_extended_vlan_block_id: Extended VLAN block allocated for egress > + * traffic > + * @egress_extended_vlan_block_size: Extended VLAN block size for egress > + * traffic > + * @ingress_marking_mode: Ingress color marking mode. See > + * &enum mxl862xx_color_marking_mode > + * @egress_remarking_mode: Color remarking for egress traffic. See > + * &enum mxl862xx_color_remarking_mode > + * @ingress_metering_enable: Traffic metering on ingress traffic applies > + * @ingress_traffic_meter_id: Meter for ingress Bridge Port process > + * @egress_sub_metering_enable: Traffic metering on various types of egress > + * traffic > + * @egress_traffic_sub_meter_id: Meter for egress Bridge Port process with > + * specific type > + * @dest_logical_port_id: Destination logical port > + * @pmapper_enable: Enable P-mapper > + * @dest_sub_if_id_group: Destination sub interface ID group when > + * pmapper_enable is false > + * @pmapper_mapping_mode: P-mapper mapping mode. See > + * &enum mxl862xx_pmapper_mapping_mode > + * @pmapper_id_valid: When true, P-mapper is re-used; when false, > + * allocation is handled by API > + * @pmapper: P-mapper configuration used when pmapper_enable is true > + * @bridge_port_map: Port map defining broadcast domain. Each bit > + * represents one bridge port. Bridge port ID is index * 16 + bit > + * offset. > + * @mc_dest_ip_lookup_disable: Disable multicast IP destination table > + * lookup > + * @mc_src_ip_lookup_enable: Enable multicast IP source table lookup > + * @dest_mac_lookup_disable: Disable destination MAC lookup; packet treated > + * as unknown > + * @src_mac_learning_disable: Disable source MAC address learning > + * @mac_spoofing_detect_enable: Enable MAC spoofing detection > + * @port_lock_enable: Enable port locking > + * @mac_learning_limit_enable: Enable MAC learning limitation > + * @mac_learning_limit: Maximum number of MAC addresses that can be learned > + * from this bridge port > + * @loop_violation_count: Number of loop violation events from this bridge > + * port > + * @mac_learning_count: Number of MAC addresses learned from this bridge > + * port > + * @ingress_vlan_filter_enable: Enable ingress VLAN filter > + * @ingress_vlan_filter_block_id: VLAN filter block of ingress traffic > + * @ingress_vlan_filter_block_size: VLAN filter block size for ingress > + * traffic > + * @bypass_egress_vlan_filter1: For ingress traffic, bypass VLAN filter 1 > + * at egress bridge port processing > + * @egress_vlan_filter1enable: Enable egress VLAN filter 1 > + * @egress_vlan_filter1block_id: VLAN filter block 1 of egress traffic > + * @egress_vlan_filter1block_size: VLAN filter block 1 size > + * @egress_vlan_filter2enable: Enable egress VLAN filter 2 > + * @egress_vlan_filter2block_id: VLAN filter block 2 of egress traffic > + * @egress_vlan_filter2block_size: VLAN filter block 2 size > + * @vlan_tag_selection: VLAN tag selection for MAC address/multicast > + * learning, lookup and filtering. 0 - Intermediate outer VLAN tag is > + * used. 1 - Original outer VLAN tag is used. > + * @vlan_src_mac_priority_enable: Enable VLAN Priority field for source MAC > + * learning and filtering nitpick: indentation not aligned > + * @vlan_src_mac_dei_enable: Enable VLAN DEI/CFI field for source MAC > + * learning and filtering > + * @vlan_src_mac_vid_enable: Enable VLAN ID field for source MAC learning > + * and filtering > + * @vlan_dst_mac_priority_enable: Enable VLAN Priority field for destination > + * MAC lookup and filtering nitpick: indentation not aligned > + * @vlan_dst_mac_dei_enable: Enable VLAN CFI/DEI field for destination MAC > + * lookup and filtering > + * @vlan_dst_mac_vid_enable: Enable VLAN ID field for destination MAC lookup > + * and filtering > + * @vlan_multicast_priority_enable: Enable VLAN Priority field for IP > + * multicast lookup > + * @vlan_multicast_dei_enable: Enable VLAN CFI/DEI field for IP multicast > + * lookup > + * @vlan_multicast_vid_enable: Enable VLAN ID field for IP multicast lookup > + */ > +struct mxl862xx_bridge_port_config { > + __le16 bridge_port_id; > + __le32 mask; /* enum mxl862xx_bridge_port_config_mask */ > + __le16 bridge_id; > + u8 ingress_extended_vlan_enable; > + __le16 ingress_extended_vlan_block_id; > + __le16 ingress_extended_vlan_block_size; > + u8 egress_extended_vlan_enable; > + __le16 egress_extended_vlan_block_id; > + __le16 egress_extended_vlan_block_size; > + __le32 ingress_marking_mode; /* enum mxl862xx_color_marking_mode */ > + __le32 egress_remarking_mode; /* enum mxl862xx_color_remarking_mode */ > + u8 ingress_metering_enable; > + __le16 ingress_traffic_meter_id; > + u8 egress_sub_metering_enable[MXL862XX_BRIDGE_PORT_EGRESS_METER_MAX]; > + __le16 egress_traffic_sub_meter_id[MXL862XX_BRIDGE_PORT_EGRESS_METER_MAX]; > + u8 dest_logical_port_id; > + u8 pmapper_enable; > + __le16 dest_sub_if_id_group; > + __le32 pmapper_mapping_mode; /* enum mxl862xx_pmapper_mapping_mode */ > + u8 pmapper_id_valid; > + struct mxl862xx_pmapper pmapper; > + __le16 bridge_port_map[8]; > + u8 mc_dest_ip_lookup_disable; > + u8 mc_src_ip_lookup_enable; > + u8 dest_mac_lookup_disable; > + u8 src_mac_learning_disable; > + u8 mac_spoofing_detect_enable; > + u8 port_lock_enable; > + u8 mac_learning_limit_enable; > + __le16 mac_learning_limit; > + __le16 loop_violation_count; > + __le16 mac_learning_count; > + u8 ingress_vlan_filter_enable; > + __le16 ingress_vlan_filter_block_id; > + __le16 ingress_vlan_filter_block_size; > + u8 bypass_egress_vlan_filter1; > + u8 egress_vlan_filter1enable; > + __le16 egress_vlan_filter1block_id; > + __le16 egress_vlan_filter1block_size; > + u8 egress_vlan_filter2enable; > + __le16 egress_vlan_filter2block_id; > + __le16 egress_vlan_filter2block_size; > + u8 vlan_tag_selection; > + u8 vlan_src_mac_priority_enable; > + u8 vlan_src_mac_dei_enable; > + u8 vlan_src_mac_vid_enable; > + u8 vlan_dst_mac_priority_enable; > + u8 vlan_dst_mac_dei_enable; > + u8 vlan_dst_mac_vid_enable; > + u8 vlan_multicast_priority_enable; > + u8 vlan_multicast_dei_enable; > + u8 vlan_multicast_vid_enable; > +} __packed; > + > +/** > + * struct mxl862xx_cfg - Global Switch configuration Attributes > + * @mac_table_age_timer: See &enum mxl862xx_age_timer > + * @age_timer: Custom MAC table aging timer in seconds > + * @max_packet_len: Maximum Ethernet packet length > + * @learning_limit_action: Automatic MAC address table learning limitation > + * consecutive action > + * @mac_locking_action: Accept or discard MAC port locking violation > + * packets > + * @mac_spoofing_action: Accept or discard MAC spoofing and port MAC locking > + * violation packets > + * @pause_mac_mode_src: Pause frame MAC source address mode > + * @pause_mac_src: Pause frame MAC source address > + */ > +struct mxl862xx_cfg { > + __le32 mac_table_age_timer; /* enum mxl862xx_age_timer */ > + __le32 age_timer; > + __le16 max_packet_len; > + u8 learning_limit_action; > + u8 mac_locking_action; > + u8 mac_spoofing_action; > + u8 pause_mac_mode_src; > + u8 pause_mac_src[ETH_ALEN]; > +} __packed; > + > +/** > + * enum mxl862xx_ss_sp_tag_mask - Special tag valid field indicator bits > + * @MXL862XX_SS_SP_TAG_MASK_RX: valid RX special tag mode > + * @MXL862XX_SS_SP_TAG_MASK_TX: valid TX special tag mode > + * @MXL862XX_SS_SP_TAG_MASK_RX_PEN: valid RX special tag info over preamble > + * @MXL862XX_SS_SP_TAG_MASK_TX_PEN: valid TX special tag info over preamble > + */ > +enum mxl862xx_ss_sp_tag_mask { > + MXL862XX_SS_SP_TAG_MASK_RX = BIT(0), > + MXL862XX_SS_SP_TAG_MASK_TX = BIT(1), > + MXL862XX_SS_SP_TAG_MASK_RX_PEN = BIT(2), > + MXL862XX_SS_SP_TAG_MASK_TX_PEN = BIT(3), > +}; > + > +/** > + * enum mxl862xx_ss_sp_tag_rx - RX special tag mode > + * @MXL862XX_SS_SP_TAG_RX_NO_TAG_NO_INSERT: packet does NOT have special > + * tag and special tag is NOT inserted > + * @MXL862XX_SS_SP_TAG_RX_NO_TAG_INSERT: packet does NOT have special tag > + * and special tag is inserted > + * @MXL862XX_SS_SP_TAG_RX_TAG_NO_INSERT: packet has special tag and special > + * tag is NOT inserted > + */ > +enum mxl862xx_ss_sp_tag_rx { > + MXL862XX_SS_SP_TAG_RX_NO_TAG_NO_INSERT = 0, > + MXL862XX_SS_SP_TAG_RX_NO_TAG_INSERT = 1, > + MXL862XX_SS_SP_TAG_RX_TAG_NO_INSERT = 2, > +}; > + > +/** > + * enum mxl862xx_ss_sp_tag_tx - TX special tag mode > + * @MXL862XX_SS_SP_TAG_TX_NO_TAG_NO_REMOVE: packet does NOT have special > + * tag and special tag is NOT removed > + * @MXL862XX_SS_SP_TAG_TX_TAG_REPLACE: packet has special tag and special > + * tag is replaced > + * @MXL862XX_SS_SP_TAG_TX_TAG_NO_REMOVE: packet has special tag and special > + * tag is NOT removed > + * @MXL862XX_SS_SP_TAG_TX_TAG_REMOVE: packet has special tag and special > + * tag is removed > + */ > +enum mxl862xx_ss_sp_tag_tx { > + MXL862XX_SS_SP_TAG_TX_NO_TAG_NO_REMOVE = 0, > + MXL862XX_SS_SP_TAG_TX_TAG_REPLACE = 1, > + MXL862XX_SS_SP_TAG_TX_TAG_NO_REMOVE = 2, > + MXL862XX_SS_SP_TAG_TX_TAG_REMOVE = 3, > +}; > + > +/** > + * enum mxl862xx_ss_sp_tag_rx_pen - RX special tag info over preamble > + * @MXL862XX_SS_SP_TAG_RX_PEN_ALL_0: special tag info inserted from byte 2 > + * to 7 are all 0 > + * @MXL862XX_SS_SP_TAG_RX_PEN_BYTE_5_IS_16: special tag byte 5 is 16, other > + * bytes from 2 to 7 are 0 > + * @MXL862XX_SS_SP_TAG_RX_PEN_BYTE_5_FROM_PREAMBLE: special tag byte 5 is > + * from preamble field, others are 0 > + * @MXL862XX_SS_SP_TAG_RX_PEN_BYTE_2_TO_7_FROM_PREAMBLE: special tag byte 2 > + * to 7 are from preamble field > + */ > +enum mxl862xx_ss_sp_tag_rx_pen { > + MXL862XX_SS_SP_TAG_RX_PEN_ALL_0 = 0, > + MXL862XX_SS_SP_TAG_RX_PEN_BYTE_5_IS_16 = 1, > + MXL862XX_SS_SP_TAG_RX_PEN_BYTE_5_FROM_PREAMBLE = 2, > + MXL862XX_SS_SP_TAG_RX_PEN_BYTE_2_TO_7_FROM_PREAMBLE = 3, > +}; > + > +/** > + * struct mxl862xx_ss_sp_tag - Special tag port settings > + * @pid: port ID (1~16) > + * @mask: See &enum mxl862xx_ss_sp_tag_mask > + * @rx: See &enum mxl862xx_ss_sp_tag_rx > + * @tx: See &enum mxl862xx_ss_sp_tag_tx > + * @rx_pen: See &enum mxl862xx_ss_sp_tag_rx_pen > + * @tx_pen: TX special tag info over preamble > + * 0 - disabled > + * 1 - enabled > + */ > +struct mxl862xx_ss_sp_tag { > + u8 pid; > + u8 mask; /* enum mxl862xx_ss_sp_tag_mask */ > + u8 rx; /* enum mxl862xx_ss_sp_tag_rx */ > + u8 tx; /* enum mxl862xx_ss_sp_tag_tx */ > + u8 rx_pen; /* enum mxl862xx_ss_sp_tag_rx_pen */ > + u8 tx_pen; /* boolean */ > +} __packed; > + > +/** > + * enum mxl862xx_logical_port_mode - Logical port mode > + * @MXL862XX_LOGICAL_PORT_8BIT_WLAN: WLAN with 8-bit station ID > + * @MXL862XX_LOGICAL_PORT_9BIT_WLAN: WLAN with 9-bit station ID > + * @MXL862XX_LOGICAL_PORT_ETHERNET: Ethernet port > + * @MXL862XX_LOGICAL_PORT_OTHER: Others > + */ > +enum mxl862xx_logical_port_mode { > + MXL862XX_LOGICAL_PORT_8BIT_WLAN = 0, > + MXL862XX_LOGICAL_PORT_9BIT_WLAN, > + MXL862XX_LOGICAL_PORT_ETHERNET, > + MXL862XX_LOGICAL_PORT_OTHER = 0xFF, > +}; > + > +/** > + * struct mxl862xx_ctp_port_assignment - CTP Port Assignment/association > + * with logical port > + * @logical_port_id: Logical Port Id. The valid range is hardware dependent > + * @first_ctp_port_id: First CTP (Connectivity Termination Port) ID mapped > + * to above logical port ID > + * @number_of_ctp_port: Total number of CTP Ports mapped above logical port > + * ID > + * @mode: Logical port mode to define sub interface ID format. See > + * &enum mxl862xx_logical_port_mode > + * @bridge_port_id: Bridge Port ID (not FID). For allocation, each CTP > + * allocated is mapped to the Bridge Port given by this > + * field. The Bridge Port will be configured to use first CTP > + * as egress CTP. > + * > + * A CTP (Connectivity Termination Port) is equivalent to > + * Logical Port + Sub-Interface-ID (capped at 31). "+" as in "arithmetical plus"? How do we know that CTP 5 is 3 + 2 and not 4 + 1? > + */ > +struct mxl862xx_ctp_port_assignment { > + u8 logical_port_id; > + __le16 first_ctp_port_id; > + __le16 number_of_ctp_port; > + __le32 mode; /* enum mxl862xx_logical_port_mode */ > + __le16 bridge_port_id; > +} __packed; > + > +/** > + * struct mxl862xx_sys_fw_image_version - Firmware version information > + * @iv_major: firmware major version > + * @iv_minor: firmware minor version > + * @iv_revision: firmware revision > + * @iv_build_num: firmware build number > + */ > +struct mxl862xx_sys_fw_image_version { > + u8 iv_major; > + u8 iv_minor; > + __le16 iv_revision; > + __le32 iv_build_num; > +} __packed; > + > +#endif /* __MXL862XX_API_H */ > diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h > new file mode 100644 > index 000000000000..f6852ade64e7 > --- /dev/null > +++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h > @@ -0,0 +1,49 @@ > +/* SPDX-License-Identifier: GPL-2.0-or-later */ > + > +#ifndef __MXL862XX_CMD_H > +#define __MXL862XX_CMD_H > + > +#define MXL862XX_MMD_DEV 30 > +#define MXL862XX_MMD_REG_CTRL 0 > +#define MXL862XX_MMD_REG_LEN_RET 1 > +#define MXL862XX_MMD_REG_DATA_FIRST 2 > +#define MXL862XX_MMD_REG_DATA_LAST 95 > +#define MXL862XX_MMD_REG_DATA_MAX_SIZE \ > + (MXL862XX_MMD_REG_DATA_LAST - MXL862XX_MMD_REG_DATA_FIRST + 1) > + > +#define MXL862XX_COMMON_MAGIC 0x100 > +#define MXL862XX_BRDG_MAGIC 0x300 > +#define MXL862XX_BRDGPORT_MAGIC 0x400 > +#define MXL862XX_CTP_MAGIC 0x500 > +#define MXL862XX_SWMAC_MAGIC 0xa00 > +#define MXL862XX_SS_MAGIC 0x1600 > +#define GPY_GPY2XX_MAGIC 0x1800 > +#define SYS_MISC_MAGIC 0x1900 > + > +#define MXL862XX_COMMON_CFGGET (MXL862XX_COMMON_MAGIC + 0x9) > +#define MXL862XX_COMMON_REGISTERMOD (MXL862XX_COMMON_MAGIC + 0x11) > + > +#define MXL862XX_BRIDGE_ALLOC (MXL862XX_BRDG_MAGIC + 0x1) > +#define MXL862XX_BRIDGE_CONFIGSET (MXL862XX_BRDG_MAGIC + 0x2) > +#define MXL862XX_BRIDGE_CONFIGGET (MXL862XX_BRDG_MAGIC + 0x3) > +#define MXL862XX_BRIDGE_FREE (MXL862XX_BRDG_MAGIC + 0x4) > + > +#define MXL862XX_BRIDGEPORT_ALLOC (MXL862XX_BRDGPORT_MAGIC + 0x1) > +#define MXL862XX_BRIDGEPORT_CONFIGSET (MXL862XX_BRDGPORT_MAGIC + 0x2) > +#define MXL862XX_BRIDGEPORT_CONFIGGET (MXL862XX_BRDGPORT_MAGIC + 0x3) > +#define MXL862XX_BRIDGEPORT_FREE (MXL862XX_BRDGPORT_MAGIC + 0x4) > + > +#define MXL862XX_CTP_PORTASSIGNMENTSET (MXL862XX_CTP_MAGIC + 0x3) > + > +#define MXL862XX_MAC_TABLECLEARCOND (MXL862XX_SWMAC_MAGIC + 0x8) > + > +#define MXL862XX_SS_SPTAG_SET (MXL862XX_SS_MAGIC + 0x02) > + > +#define INT_GPHY_READ (GPY_GPY2XX_MAGIC + 0x01) > +#define INT_GPHY_WRITE (GPY_GPY2XX_MAGIC + 0x02) > + > +#define SYS_MISC_FW_VERSION (SYS_MISC_MAGIC + 0x02) > + > +#define MMD_API_MAXIMUM_ID 0x7fff > + > +#endif /* __MXL862XX_CMD_H */ > diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-host.c b/drivers/net/dsa/mxl862xx/mxl862xx-host.c > new file mode 100644 > index 000000000000..f2b3c0b1dff1 > --- /dev/null > +++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c > @@ -0,0 +1,245 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* > + * Based upon the Maxlinear SDK driver > + * > + * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org> > + * Copyright (C) 2025 John Crispin <john@phrozen.org> > + * Copyright (C) 2024 MaxLinear Inc. > + */ > + > +#include <linux/bits.h> > +#include <linux/iopoll.h> > +#include <linux/limits.h> > +#include <net/dsa.h> > +#include "mxl862xx.h" > +#include "mxl862xx-host.h" > + > +#define CTRL_BUSY_MASK BIT(15) > + > +#define MXL862XX_MMD_REG_CTRL 0 > +#define MXL862XX_MMD_REG_LEN_RET 1 > +#define MXL862XX_MMD_REG_DATA_FIRST 2 > +#define MXL862XX_MMD_REG_DATA_LAST 95 > +#define MXL862XX_MMD_REG_DATA_MAX_SIZE \ > + (MXL862XX_MMD_REG_DATA_LAST - MXL862XX_MMD_REG_DATA_FIRST + 1) > + > +#define MMD_API_SET_DATA_0 2 > +#define MMD_API_GET_DATA_0 5 > +#define MMD_API_RST_DATA 8 > + > +#define MXL862XX_SWITCH_RESET 0x9907 > + > +static int mxl862xx_reg_read(struct mxl862xx_priv *priv, u32 addr) > +{ > + return __mdiodev_c45_read(priv->mdiodev, MDIO_MMD_VEND1, addr); > +} > + > +static int mxl862xx_reg_write(struct mxl862xx_priv *priv, u32 addr, u16 data) > +{ > + return __mdiodev_c45_write(priv->mdiodev, MDIO_MMD_VEND1, addr, data); > +} > + > +static int mxl862xx_ctrl_read(struct mxl862xx_priv *priv) > +{ > + return mxl862xx_reg_read(priv, MXL862XX_MMD_REG_CTRL); > +} > + > +static int mxl862xx_busy_wait(struct mxl862xx_priv *priv) > +{ > + int val; > + > + return readx_poll_timeout(mxl862xx_ctrl_read, priv, val, > + !(val & CTRL_BUSY_MASK), 15, 10000); > +} > + > +static int mxl862xx_set_data(struct mxl862xx_priv *priv, u16 words) > +{ > + int ret; > + u16 cmd; > + > + ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_LEN_RET, > + MXL862XX_MMD_REG_DATA_MAX_SIZE * sizeof(u16)); > + if (ret < 0) > + return ret; > + > + cmd = words / MXL862XX_MMD_REG_DATA_MAX_SIZE - 1; > + if (!(cmd < 2)) > + return -EINVAL; > + > + cmd += MMD_API_SET_DATA_0; > + ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_CTRL, > + cmd | CTRL_BUSY_MASK); > + if (ret < 0) > + return ret; > + > + return mxl862xx_busy_wait(priv); > +} > + > +static int mxl862xx_get_data(struct mxl862xx_priv *priv, u16 words) > +{ > + int ret; > + u16 cmd; > + > + ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_LEN_RET, > + MXL862XX_MMD_REG_DATA_MAX_SIZE * sizeof(u16)); > + if (ret < 0) > + return ret; > + > + cmd = words / MXL862XX_MMD_REG_DATA_MAX_SIZE; > + if (!(cmd > 0 && cmd < 3)) > + return -EINVAL; > + > + cmd += MMD_API_GET_DATA_0; > + ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_CTRL, > + cmd | CTRL_BUSY_MASK); > + if (ret < 0) > + return ret; > + > + return mxl862xx_busy_wait(priv); > +} > + > +static int mxl862xx_firmware_return(int ret) > +{ > + /* Only 16-bit values are valid. */ > + if (WARN_ON(ret & GENMASK(31, 16))) > + return -EINVAL; > + > + /* Interpret value as signed 16-bit integer. */ > + return (s16)ret; > +} > + > +static int mxl862xx_send_cmd(struct mxl862xx_priv *priv, u16 cmd, u16 size, > + bool quiet) > +{ > + int ret; > + > + ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_LEN_RET, size); > + if (ret) > + return ret; > + > + ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_CTRL, > + cmd | CTRL_BUSY_MASK); > + if (ret) > + return ret; > + > + ret = mxl862xx_busy_wait(priv); > + if (ret) > + return ret; > + > + ret = mxl862xx_reg_read(priv, MXL862XX_MMD_REG_LEN_RET); > + if (ret < 0) > + return ret; > + > + /* handle errors returned by the firmware as -EIO > + * The firmware is based on Zephyr OS and uses the errors as > + * defined in errno.h of Zephyr OS. See > + * https://github.com/zephyrproject-rtos/zephyr/blob/v3.7.0/lib/libc/minimal/include/errno.h > + */ > + ret = mxl862xx_firmware_return(ret); > + if (ret < 0) { > + if (!quiet) > + dev_err(&priv->mdiodev->dev, > + "CMD %04x returned error %d\n", cmd, ret); > + return -EIO; > + } > + > + return ret; > +} > + > +int mxl862xx_api_wrap(struct mxl862xx_priv *priv, u16 cmd, void *_data, > + u16 size, bool read, bool quiet) > +{ > + __le16 *data = _data; > + int ret, cmd_ret; > + u16 max, i; > + > + dev_dbg(&priv->mdiodev->dev, "CMD %04x DATA %*ph\n", cmd, size, data); > + > + mutex_lock_nested(&priv->mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED); > + > + max = (size + 1) / 2; > + > + ret = mxl862xx_busy_wait(priv); > + if (ret < 0) > + goto out; > + > + for (i = 0; i < max; i++) { > + u16 off = i % MXL862XX_MMD_REG_DATA_MAX_SIZE; > + > + if (i && off == 0) { > + /* Send command to set data when every > + * MXL862XX_MMD_REG_DATA_MAX_SIZE of WORDs are written. > + */ > + ret = mxl862xx_set_data(priv, i); > + if (ret < 0) > + goto out; > + } > + > + ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_DATA_FIRST + off, > + le16_to_cpu(data[i])); > + if (ret < 0) > + goto out; > + } > + > + ret = mxl862xx_send_cmd(priv, cmd, size, quiet); > + if (ret < 0 || !read) > + goto out; > + > + /* store result of mxl862xx_send_cmd() */ > + cmd_ret = ret; > + > + for (i = 0; i < max; i++) { > + u16 off = i % MXL862XX_MMD_REG_DATA_MAX_SIZE; > + > + if (i && off == 0) { > + /* Send command to fetch next batch of data when every > + * MXL862XX_MMD_REG_DATA_MAX_SIZE of WORDs are read. > + */ > + ret = mxl862xx_get_data(priv, i); > + if (ret < 0) > + goto out; > + } > + > + ret = mxl862xx_reg_read(priv, MXL862XX_MMD_REG_DATA_FIRST + off); > + if (ret < 0) > + goto out; > + > + if ((i * 2 + 1) == size) { > + /* Special handling for last BYTE if it's not WORD > + * aligned to avoid writing beyond the allocated data > + * structure. > + */ > + *(uint8_t *)&data[i] = ret & 0xff; > + } else { > + data[i] = cpu_to_le16((u16)ret); > + } > + } > + > + /* on success return the result of the mxl862xx_send_cmd() */ > + ret = cmd_ret; > + > + dev_dbg(&priv->mdiodev->dev, "RET %d DATA %*ph\n", ret, size, data); > + > +out: > + mutex_unlock(&priv->mdiodev->bus->mdio_lock); > + > + return ret; > +} > + > +int mxl862xx_reset(struct mxl862xx_priv *priv) > +{ > + int ret; > + > + mutex_lock_nested(&priv->mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED); > + > + /* Software reset */ > + ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_LEN_RET, 0); > + if (ret) > + goto out; > + > + ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_CTRL, MXL862XX_SWITCH_RESET); > +out: > + mutex_unlock(&priv->mdiodev->bus->mdio_lock); > + > + return ret; > +} > diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-host.h b/drivers/net/dsa/mxl862xx/mxl862xx-host.h > new file mode 100644 > index 000000000000..7cc496f6be5c > --- /dev/null > +++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.h > @@ -0,0 +1,12 @@ > +/* SPDX-License-Identifier: GPL-2.0-or-later */ > + > +#ifndef __MXL862XX_HOST_H > +#define __MXL862XX_HOST_H > + > +#include "mxl862xx.h" > + > +int mxl862xx_api_wrap(struct mxl862xx_priv *priv, u16 cmd, void *data, u16 size, > + bool read, bool quiet); > +int mxl862xx_reset(struct mxl862xx_priv *priv); > + > +#endif /* __MXL862XX_HOST_H */ > diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.c b/drivers/net/dsa/mxl862xx/mxl862xx.c > new file mode 100644 > index 000000000000..2ccfb53e6dcb > --- /dev/null > +++ b/drivers/net/dsa/mxl862xx/mxl862xx.c > @@ -0,0 +1,475 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* > + * Driver for MaxLinear MxL862xx switch family > + * > + * Copyright (C) 2024 MaxLinear Inc. > + * Copyright (C) 2025 John Crispin <john@phrozen.org> > + * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org> > + */ > + > +#include <linux/module.h> > +#include <linux/delay.h> > +#include <linux/of_device.h> > +#include <linux/of_mdio.h> > +#include <linux/phy.h> > +#include <linux/phylink.h> > +#include <net/dsa.h> > + > +#include "mxl862xx.h" > +#include "mxl862xx-api.h" > +#include "mxl862xx-cmd.h" > +#include "mxl862xx-host.h" > + > +#define MXL862XX_API_WRITE(dev, cmd, data) \ > + mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), false, false) > +#define MXL862XX_API_READ(dev, cmd, data) \ > + mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), true, false) > +#define MXL862XX_API_READ_QUIET(dev, cmd, data) \ > + mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), true, true) > + > +#define DSA_MXL_PORT(port) ((port) + 1) > +#define DSA_MXL_CPU_PORTS(ds) (dsa_cpu_ports(ds) << 1) > + > +#define MXL862XX_SDMA_PCTRLP(p) (0xbc0 + ((p) * 0x6)) > +#define MXL862XX_SDMA_PCTRL_EN BIT(0) > + > +#define MXL862XX_FDMA_PCTRLP(p) (0xa80 + ((p) * 0x6)) > +#define MXL862XX_FDMA_PCTRL_EN BIT(0) > + > +#define MXL862XX_READY_TIMEOUT_MS 10000 > +#define MXL862XX_READY_POLL_MS 100 > + > +static enum dsa_tag_protocol mxl862xx_get_tag_protocol(struct dsa_switch *ds, > + int port, > + enum dsa_tag_protocol m) > +{ > + return DSA_TAG_PROTO_MXL862; > +} > + > +/* PHY access via firmware relay */ > +static int mxl862xx_phy_read_mmd(struct mxl862xx_priv *priv, int port, > + int devadd, int reg) > +{ > + struct mdio_relay_data param = { > + .phy = port, > + .mmd = devadd, > + .reg = cpu_to_le16(reg), > + }; > + int ret; > + > + ret = MXL862XX_API_READ(priv, INT_GPHY_READ, param); > + if (ret) > + return ret; > + > + return le16_to_cpu(param.data); > +} > + > +static int mxl862xx_phy_write_mmd(struct mxl862xx_priv *priv, int port, > + int devadd, int reg, u16 data) > +{ > + struct mdio_relay_data param = { > + .phy = port, > + .mmd = devadd, > + .reg = cpu_to_le16(reg), > + .data = cpu_to_le16(data), > + }; > + > + return MXL862XX_API_WRITE(priv, INT_GPHY_WRITE, param); > +} > + > +static int mxl862xx_phy_read_mii_bus(struct mii_bus *bus, int port, int regnum) > +{ > + return mxl862xx_phy_read_mmd(bus->priv, port, 0, regnum); > +} > + > +static int mxl862xx_phy_write_mii_bus(struct mii_bus *bus, int port, > + int regnum, u16 val) > +{ > + return mxl862xx_phy_write_mmd(bus->priv, port, 0, regnum, val); > +} > + > +static int mxl862xx_phy_read_c45_mii_bus(struct mii_bus *bus, int port, > + int devadd, int regnum) > +{ > + return mxl862xx_phy_read_mmd(bus->priv, port, devadd, regnum); > +} > + > +static int mxl862xx_phy_write_c45_mii_bus(struct mii_bus *bus, int port, > + int devadd, int regnum, u16 val) > +{ > + return mxl862xx_phy_write_mmd(bus->priv, port, devadd, regnum, val); > +} > + > +static int mxl862xx_configure_tag_proto(struct dsa_switch *ds, int port, bool enable) > +{ > + struct mxl862xx_ctp_port_assignment assign = { > + .number_of_ctp_port = cpu_to_le16(enable ? (32 - DSA_MXL_PORT(port)) : 1), > + .logical_port_id = DSA_MXL_PORT(port), > + .first_ctp_port_id = cpu_to_le16(DSA_MXL_PORT(port)), > + .mode = cpu_to_le32(MXL862XX_LOGICAL_PORT_ETHERNET), I have a really hard time understanding the physical port to CTP port mapping here. Specifically, I see you allocate 1 CTP port (sub-interface) per user port, and the rest up to 32 CTP ports as sub-interfaces of the DSA CPU port. But I can only see this working if the index of the DSA CPU port is the last. Otherwise, won't the CTP port IDs of user ports subsequent to the DSA CPU port overlap with sub-interfaces of the latter? Example: mxl862xx_configure_tag_proto(ds, 3, true) // CPU port -> .number_of_ctp_port = cpu_to_le16(32 - DSA_MXL_PORT(3)), // 28 -> .logical_port_id = DSA_MXL_PORT(3), // 4 -> .first_ctp_port_id = cpu_to_le16(DSA_MXL_PORT(3)), // 4 mxl862xx_configure_tag_proto(ds, 4, false) // user port -> .number_of_ctp_port = cpu_to_le16(1), // 1 -> .logical_port_id = DSA_MXL_PORT(4), // 5 -> .first_ctp_port_id = cpu_to_le16(DSA_MXL_PORT(4)), // 5 Doesn't the CTP port ID of user port 4 (5) collide with sub-interface 2 of CPU port 3? > + }; > + struct mxl862xx_ss_sp_tag tag = { > + .pid = DSA_MXL_PORT(port), > + .mask = MXL862XX_SS_SP_TAG_MASK_RX | MXL862XX_SS_SP_TAG_MASK_TX, > + .rx = enable ? MXL862XX_SS_SP_TAG_RX_TAG_NO_INSERT : > + MXL862XX_SS_SP_TAG_RX_NO_TAG_INSERT, > + .tx = enable ? MXL862XX_SS_SP_TAG_TX_TAG_NO_REMOVE : > + MXL862XX_SS_SP_TAG_TX_TAG_REMOVE, > + }; > + int ret; > + > + ret = MXL862XX_API_WRITE(ds->priv, MXL862XX_SS_SPTAG_SET, tag); > + if (ret) > + return ret; > + > + return MXL862XX_API_WRITE(ds->priv, MXL862XX_CTP_PORTASSIGNMENTSET, assign); > +} > + > +static int mxl862xx_port_state(struct dsa_switch *ds, int port, bool enable) > +{ > + struct mxl862xx_register_mod sdma = { > + .addr = cpu_to_le16(MXL862XX_SDMA_PCTRLP(DSA_MXL_PORT(port))), > + .data = cpu_to_le16(enable ? MXL862XX_SDMA_PCTRL_EN : 0), > + .mask = cpu_to_le16(MXL862XX_SDMA_PCTRL_EN), > + }; > + struct mxl862xx_register_mod fdma = { > + .addr = cpu_to_le16(MXL862XX_FDMA_PCTRLP(DSA_MXL_PORT(port))), > + .data = cpu_to_le16(enable ? MXL862XX_FDMA_PCTRL_EN : 0), > + .mask = cpu_to_le16(MXL862XX_FDMA_PCTRL_EN), > + }; > + int ret; > + > + ret = MXL862XX_API_WRITE(ds->priv, MXL862XX_COMMON_REGISTERMOD, sdma); > + if (ret) > + return ret; > + > + return MXL862XX_API_WRITE(ds->priv, MXL862XX_COMMON_REGISTERMOD, fdma); > +} > + > +static int mxl862xx_port_enable(struct dsa_switch *ds, int port, > + struct phy_device *phydev) > +{ > + return mxl862xx_port_state(ds, port, true); > +} > + > +static void mxl862xx_port_disable(struct dsa_switch *ds, int port) > +{ > + mxl862xx_port_state(ds, port, false); > +} > + > +static void mxl862xx_port_fast_age(struct dsa_switch *ds, int port) > +{ > + struct mxl862xx_mac_table_clear param = { > + .type = MXL862XX_MAC_CLEAR_PHY_PORT, > + .port_id = DSA_MXL_PORT(port), > + }; > + > + if (MXL862XX_API_WRITE(ds->priv, MXL862XX_MAC_TABLECLEARCOND, param)) > + dev_err(ds->dev, "failed to clear fdb on port %d\n", port); > +} > + > +static int mxl862xx_isolate_port(struct dsa_switch *ds, int port) I would recommend against using the "isolate" term in this context, it will conflict at some point with the meaning of the BR_ISOLATED bridge port flag. From man bridge: isolated on or isolated off Controls whether a given port will be isolated, which means it will be able to communicate with non-isolated ports only. By default this flag is off. Something like gswip_add_single_port_br() is better maybe? > +{ > + struct mxl862xx_bridge_port_config br_port_cfg = {}; > + struct mxl862xx_bridge_alloc br_alloc = {}; > + int ret; > + > + ret = MXL862XX_API_READ(ds->priv, MXL862XX_BRIDGE_ALLOC, br_alloc); > + if (ret) { > + dev_err(ds->dev, "failed to allocate a bridge for port %d\n", port); > + return ret; > + } > + > + br_port_cfg.bridge_id = br_alloc.bridge_id; > + br_port_cfg.bridge_port_id = cpu_to_le16(DSA_MXL_PORT(port)); > + br_port_cfg.mask = cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_ID | > + MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP | > + MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING | > + MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MAC_LEARNING); > + br_port_cfg.src_mac_learning_disable = true; > + br_port_cfg.vlan_src_mac_vid_enable = false; > + br_port_cfg.vlan_dst_mac_vid_enable = false; > + br_port_cfg.bridge_port_map[0] = cpu_to_le16(DSA_MXL_CPU_PORTS(ds)); > + > + return MXL862XX_API_WRITE(ds->priv, MXL862XX_BRIDGEPORT_CONFIGSET, br_port_cfg); > +} > + > +static int mxl862xx_setup_mdio(struct dsa_switch *ds) > +{ > + struct mxl862xx_priv *priv = ds->priv; > + struct device *dev = ds->dev; > + struct device_node *mdio_np; > + struct mii_bus *bus; > + static int idx; > + int ret; > + > + bus = devm_mdiobus_alloc(dev); > + if (!bus) > + return -ENOMEM; > + > + bus->priv = priv; > + ds->user_mii_bus = bus; > + bus->name = KBUILD_MODNAME "-mii"; > + snprintf(bus->id, MII_BUS_ID_SIZE, KBUILD_MODNAME "-%d", idx++); > + bus->read_c45 = mxl862xx_phy_read_c45_mii_bus; > + bus->write_c45 = mxl862xx_phy_write_c45_mii_bus; > + bus->read = mxl862xx_phy_read_mii_bus; > + bus->write = mxl862xx_phy_write_mii_bus; > + bus->parent = dev; > + bus->phy_mask = ~ds->phys_mii_mask; > + > + mdio_np = of_get_child_by_name(dev->of_node, "mdio"); > + if (!mdio_np) > + return -ENODEV; > + > + ret = devm_of_mdiobus_register(dev, bus, mdio_np); > + of_node_put(mdio_np); > + > + return ret; > +} > + > +static int mxl862xx_wait_ready(struct dsa_switch *ds) > +{ > + struct mxl862xx_sys_fw_image_version ver = {}; > + unsigned long start = jiffies, timeout; > + struct mxl862xx_priv *priv = ds->priv; > + struct mxl862xx_cfg cfg = {}; > + int ret; > + > + timeout = start + msecs_to_jiffies(MXL862XX_READY_TIMEOUT_MS); > + msleep(2000); /* it always takes at least 2 seconds */ > + do { > + ret = MXL862XX_API_READ_QUIET(priv, SYS_MISC_FW_VERSION, ver); > + if (ret || !ver.iv_major) > + goto not_ready_yet; > + > + /* being able to perform CFGGET indicates that > + * the firmware is ready > + */ > + ret = MXL862XX_API_READ_QUIET(priv, > + MXL862XX_COMMON_CFGGET, > + cfg); > + if (ret) > + goto not_ready_yet; > + > + dev_info(ds->dev, "switch ready after %ums, firmware %u.%u.%u (build %u)\n", > + jiffies_to_msecs(jiffies - start), > + ver.iv_major, ver.iv_minor, > + le16_to_cpu(ver.iv_revision), > + le32_to_cpu(ver.iv_build_num)); > + return 0; > + > +not_ready_yet: > + msleep(MXL862XX_READY_POLL_MS); > + } while (time_before(jiffies, timeout)); > + > + dev_err(ds->dev, "switch not responding after reset\n"); > + return -ETIMEDOUT; > +} > + > +static int mxl862xx_setup(struct dsa_switch *ds) > +{ > + struct mxl862xx_bridge_port_config br_port_cfg = {}; > + struct mxl862xx_priv *priv = ds->priv; > + u16 bridge_port_map = 0; > + struct dsa_port *dp; > + int cpu_port = -1; > + int ret; > + > + dsa_switch_for_each_cpu_port(dp, ds) { > + /* Only a single CPU port is supported by now */ > + if (cpu_port != -1) > + return -EINVAL; If the hardware supports multiple CPU ports, then please describe all CPU ports as such in the device tree, and make an effort to handle that description gracefully even if you cannot make use of the second CPU port. See the rules by which dsa_tree_setup_cpu_ports() creates the initial user to CPU port mapping. This avoids a future breakage you'll cause with old kernels when you update the device tree to describe the second CPU port for what it is. > + > + cpu_port = dp->index; > + } > + > + ret = mxl862xx_reset(priv); > + if (ret) > + return ret; > + > + ret = mxl862xx_wait_ready(ds); > + if (ret) > + return ret; > + > + /* CPU port bridge setup */ > + br_port_cfg.mask = cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP | > + MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING | > + MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MAC_LEARNING); > + > + br_port_cfg.bridge_port_id = cpu_to_le16(DSA_MXL_PORT(cpu_port)); > + br_port_cfg.src_mac_learning_disable = false; > + br_port_cfg.vlan_src_mac_vid_enable = true; > + br_port_cfg.vlan_dst_mac_vid_enable = true; > + > + /* include all non-CPU ports in the CPU portmap */ > + dsa_switch_for_each_available_port(dp, ds) { > + if (dsa_port_is_cpu(dp)) > + continue; > + > + bridge_port_map |= BIT(DSA_MXL_PORT(dp->index)); > + } > + br_port_cfg.bridge_port_map[0] |= cpu_to_le16(bridge_port_map); > + > + ret = MXL862XX_API_WRITE(priv, MXL862XX_BRIDGEPORT_CONFIGSET, > + br_port_cfg); > + if (ret) { > + dev_err(ds->dev, "failed to set the CPU portmap\n"); > + return ret; > + } > + mxl862xx_port_fast_age(ds, cpu_port); Duplicate with the call from mxl862xx_port_setup()? > + > + ret = mxl862xx_setup_mdio(ds); > + if (ret) > + return ret; > + > + return 0; > +} > + > +static int mxl862xx_port_setup(struct dsa_switch *ds, int port) > +{ > + bool is_cpu_port = dsa_is_cpu_port(ds, port); > + int ret; > + > + ret = mxl862xx_configure_tag_proto(ds, port, is_cpu_port); > + if (ret) > + return ret; > + > + if (!is_cpu_port && !dsa_is_unused_port(ds, port)) { > + ret = mxl862xx_isolate_port(ds, port); > + if (ret) > + return ret; > + } > + > + mxl862xx_port_fast_age(ds, port); > + > + ret = mxl862xx_port_state(ds, port, is_cpu_port); What is the point of this? Do CPU ports need to be enabled prior to the mxl862xx_port_enable() call? > + if (ret) > + return ret; > + > + return 0; > +} > + > +static void mxl862xx_phylink_get_caps(struct dsa_switch *ds, int port, > + struct phylink_config *config) > +{ > + config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE | MAC_10 | > + MAC_100 | MAC_1000 | MAC_2500FD; > + > + __set_bit(PHY_INTERFACE_MODE_INTERNAL, > + config->supported_interfaces); > +} > + > +static const struct dsa_switch_ops mxl862xx_switch_ops = { > + .get_tag_protocol = mxl862xx_get_tag_protocol, > + .setup = mxl862xx_setup, > + .port_setup = mxl862xx_port_setup, > + .phylink_get_caps = mxl862xx_phylink_get_caps, > + .port_enable = mxl862xx_port_enable, > + .port_disable = mxl862xx_port_disable, > + .port_fast_age = mxl862xx_port_fast_age, > +}; > + > +static void mxl862xx_phylink_mac_config(struct phylink_config *config, > + unsigned int mode, > + const struct phylink_link_state *state) > +{ > +} > + > +static void mxl862xx_phylink_mac_link_down(struct phylink_config *config, > + unsigned int mode, > + phy_interface_t interface) > +{ > +} > + > +static void mxl862xx_phylink_mac_link_up(struct phylink_config *config, > + struct phy_device *phydev, > + unsigned int mode, > + phy_interface_t interface, > + int speed, int duplex, > + bool tx_pause, bool rx_pause) > +{ > +} > + > +static const struct phylink_mac_ops mxl862xx_phylink_mac_ops = { > + .mac_config = mxl862xx_phylink_mac_config, > + .mac_link_down = mxl862xx_phylink_mac_link_down, > + .mac_link_up = mxl862xx_phylink_mac_link_up, > +}; > + > +static int mxl862xx_probe(struct mdio_device *mdiodev) > +{ > + struct device *dev = &mdiodev->dev; > + struct mxl862xx_priv *priv; > + struct dsa_switch *ds; > + int ret; > + > + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); > + if (!priv) > + return -ENOMEM; > + > + priv->mdiodev = mdiodev; > + > + ds = devm_kzalloc(dev, sizeof(*ds), GFP_KERNEL); > + if (!ds) > + return -ENOMEM; > + > + priv->ds = ds; > + ds->dev = dev; > + ds->priv = priv; > + ds->ops = &mxl862xx_switch_ops; > + ds->phylink_mac_ops = &mxl862xx_phylink_mac_ops; > + ds->num_ports = MXL862XX_MAX_PORT_NUM; > + > + dev_set_drvdata(dev, ds); > + > + ret = dsa_register_switch(ds); > + if (ret) > + return ret; > + > + return 0; > +} > + > +static void mxl862xx_remove(struct mdio_device *mdiodev) > +{ > + struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev); > + > + if (!ds) > + return; > + > + dsa_unregister_switch(ds); > +} > + > +static void mxl862xx_shutdown(struct mdio_device *mdiodev) > +{ > + struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev); > + > + if (!ds) > + return; > + > + dsa_switch_shutdown(ds); > + > + dev_set_drvdata(&mdiodev->dev, NULL); > +} > + > +static const struct of_device_id mxl862xx_of_match[] = { > + { .compatible = "maxlinear,mxl86282" }, > + { .compatible = "maxlinear,mxl86252" }, > + { /* sentinel */ } > +}; > +MODULE_DEVICE_TABLE(of, mxl862xx_of_match); > + > +static struct mdio_driver mxl862xx_driver = { > + .probe = mxl862xx_probe, > + .remove = mxl862xx_remove, > + .shutdown = mxl862xx_shutdown, > + .mdiodrv.driver = { > + .name = "mxl862xx", > + .of_match_table = mxl862xx_of_match, > + }, > +}; > + > +mdio_module_driver(mxl862xx_driver); > + > +MODULE_DESCRIPTION("Driver for MaxLinear MxL862xx switch family"); > +MODULE_LICENSE("GPL"); > diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.h b/drivers/net/dsa/mxl862xx/mxl862xx.h > new file mode 100644 > index 000000000000..e6de494e834a > --- /dev/null > +++ b/drivers/net/dsa/mxl862xx/mxl862xx.h > @@ -0,0 +1,19 @@ > +/* SPDX-License-Identifier: GPL-2.0-or-later */ > + > +#ifndef __MXL862XX_H > +#define __MXL862XX_H > + > +#include <linux/mdio.h> > +#include <net/dsa.h> > + > +#define MXL862XX_MAX_PHY_PORT_NUM 8 > +#define MXL862XX_MAX_EXT_PORT_NUM 7 > +#define MXL862XX_MAX_PORT_NUM (MXL862XX_MAX_PHY_PORT_NUM + \ > + MXL862XX_MAX_EXT_PORT_NUM) 8 + 7 == 15. This is written into ds->num_ports. Could you please explain how this agrees with the Kconfig help text: These switches have two 10GE SerDes interfaces, one typically used as CPU port. MxL86282 has eight 2.5 Gigabit PHYs MxL86252 has five 2.5 Gigabit PHYs What are the extra port indices used for, and is it ok that MxL86282 and MxL86252 are registered with the same ds->num_ports value? This should be visible in the "devlink port" command - even unused ports have devlink instances. > + > +struct mxl862xx_priv { > + struct dsa_switch *ds; > + struct mdio_device *mdiodev; > +}; > + > +#endif /* __MXL862XX_H */ > -- > 2.52.0 ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH net-next v9 4/4] net: dsa: add basic initial driver for MxL862xx switches 2026-01-28 11:29 ` Vladimir Oltean @ 2026-01-28 16:45 ` Daniel Golle 2026-01-28 20:55 ` Vladimir Oltean 0 siblings, 1 reply; 10+ messages in thread From: Daniel Golle @ 2026-01-28 16:45 UTC (permalink / raw) To: Vladimir Oltean Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiner Kallweit, Russell King, Simon Horman, netdev, devicetree, linux-kernel, Frank Wunderlich, Chad Monroe, Cezary Wilmanski, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin Hi Vladimir, thank you for the elaborate and very helpful review (one could even say "inspiring" ;). See some questions and answers inline below. On Wed, Jan 28, 2026 at 01:29:50PM +0200, Vladimir Oltean wrote: > > [...] > > +/** > > + * struct mxl862xx_bridge_port_alloc - Bridge Port Allocation > > + * @bridge_port_id: If the bridge port allocation is successful, a valid ID > > + * will be returned in this field. Otherwise, INVALID_HANDLE is returned. > > + * For bridge port free, this field should contain a valid ID returned by > > + * the bridge port allocation. ID 0 is special for the CPU port in > > + * PRX300, mapping to CTP (Connectivity Termination Port) 0 (Logical Port > > + * 0 with Sub-interface ID 0), and is pre-allocated during initialization. > > Just to be clear, "CPU port in PRX300" does not refer to the "DSA CPU > port" that connects to the Linux system, no? Because > mxl862xx_configure_tag_proto() assigns a different CTP ID for the DSA > CPU port AFAICS, not 0. > > Without further clarifications, this comment is a bit confusing. I would > at least add that CTP 0 is not used by the driver. This documentation was probably meant for a whole series of different chips which are used with the (proprietary) switch driver which in case of the MxL862xx is running on the internal microcontroller rather than on the host OS... I'll remove the comment, PRX300 is a xPON SoC which contains the same switch IP. There are plans to upstream support also for this SoC, but of course without the proprietary switch driver which this API refers to. > [...] > > +/** > > + * struct mxl862xx_ctp_port_assignment - CTP Port Assignment/association > > + * with logical port > > + * @logical_port_id: Logical Port Id. The valid range is hardware dependent > > + * @first_ctp_port_id: First CTP (Connectivity Termination Port) ID mapped > > + * to above logical port ID > > + * @number_of_ctp_port: Total number of CTP Ports mapped above logical port > > + * ID > > + * @mode: Logical port mode to define sub interface ID format. See > > + * &enum mxl862xx_logical_port_mode > > + * @bridge_port_id: Bridge Port ID (not FID). For allocation, each CTP > > + * allocated is mapped to the Bridge Port given by this > > + * field. The Bridge Port will be configured to use first CTP > > + * as egress CTP. > > + * > > + * A CTP (Connectivity Termination Port) is equivalent to > > + * Logical Port + Sub-Interface-ID (capped at 31). > > "+" as in "arithmetical plus"? Yes. > How do we know that CTP 5 is 3 + 2 and not 4 + 1? We can't. This "rule" anyway seems to rather be just a software convention, see below. > > [...] > > +static int mxl862xx_configure_tag_proto(struct dsa_switch *ds, int port, bool enable) > > +{ > > + struct mxl862xx_ctp_port_assignment assign = { > > + .number_of_ctp_port = cpu_to_le16(enable ? (32 - DSA_MXL_PORT(port)) : 1), > > + .logical_port_id = DSA_MXL_PORT(port), > > + .first_ctp_port_id = cpu_to_le16(DSA_MXL_PORT(port)), > > + .mode = cpu_to_le32(MXL862XX_LOGICAL_PORT_ETHERNET), > > I have a really hard time understanding the physical port to CTP port > mapping here. Specifically, I see you allocate 1 CTP port (sub-interface) > per user port, and the rest up to 32 CTP ports as sub-interfaces of the > DSA CPU port. > > But I can only see this working if the index of the DSA CPU port is the > last. Otherwise, won't the CTP port IDs of user ports subsequent to the > DSA CPU port overlap with sub-interfaces of the latter? > > Example: > mxl862xx_configure_tag_proto(ds, 3, true) // CPU port > -> .number_of_ctp_port = cpu_to_le16(32 - DSA_MXL_PORT(3)), // 28 > -> .logical_port_id = DSA_MXL_PORT(3), // 4 > -> .first_ctp_port_id = cpu_to_le16(DSA_MXL_PORT(3)), // 4 > mxl862xx_configure_tag_proto(ds, 4, false) // user port > -> .number_of_ctp_port = cpu_to_le16(1), // 1 > -> .logical_port_id = DSA_MXL_PORT(4), // 5 > -> .first_ctp_port_id = cpu_to_le16(DSA_MXL_PORT(4)), // 5 > > Doesn't the CTP port ID of user port 4 (5) collide with sub-interface 2 > of CPU port 3? Absolutely true. It works coincidental because only SerDes port 0 (port_id 9) is allowed to be CPU port. When using SerDes port 1 (port_id 13) as a user port it already starts to be tricky and more care needs to be taken when setting up CTP port assignment... Now that I understand more of what it actually does I'll try to come up with something smarter. > > [...] > > +static int mxl862xx_isolate_port(struct dsa_switch *ds, int port) > > I would recommend against using the "isolate" term in this context, it > will conflict at some point with the meaning of the BR_ISOLATED bridge > port flag. From man bridge: > > isolated on or isolated off > Controls whether a given port will be isolated, which > means it will be able to communicate with non-isolated > ports only. By default this flag is off. > > Something like gswip_add_single_port_br() is better maybe? +1 > > [...] > > +static int mxl862xx_setup(struct dsa_switch *ds) > > +{ > > + struct mxl862xx_bridge_port_config br_port_cfg = {}; > > + struct mxl862xx_priv *priv = ds->priv; > > + u16 bridge_port_map = 0; > > + struct dsa_port *dp; > > + int cpu_port = -1; > > + int ret; > > + > > + dsa_switch_for_each_cpu_port(dp, ds) { > > + /* Only a single CPU port is supported by now */ > > + if (cpu_port != -1) > > + return -EINVAL; > > If the hardware supports multiple CPU ports, then please describe all > CPU ports as such in the device tree, and make an effort to handle that > description gracefully even if you cannot make use of the second CPU port. The number of available CTP (32) limits the possible setup for CPU ports. The reference driver only allows port_id 9 (SerDes 0) to be used as CPU port. SerDes 1 may also be used as CPU port in theory. > See the rules by which dsa_tree_setup_cpu_ports() creates the initial > user to CPU port mapping. This avoids a future breakage you'll cause > with old kernels when you update the device tree to describe the second > CPU port for what it is. Understood. On old kernel, lets assume with this basic driver just added but future DT with more than one CPU port described one should still end up with the first CPU port used and all ports assigned to that CPU port. Right? > > > + > > + cpu_port = dp->index; > > + } > > + > > + ret = mxl862xx_reset(priv); > > + if (ret) > > + return ret; > > + > > + ret = mxl862xx_wait_ready(ds); > > + if (ret) > > + return ret; > > + > > + /* CPU port bridge setup */ > > + br_port_cfg.mask = cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP | > > + MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING | > > + MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MAC_LEARNING); > > + > > + br_port_cfg.bridge_port_id = cpu_to_le16(DSA_MXL_PORT(cpu_port)); > > + br_port_cfg.src_mac_learning_disable = false; > > + br_port_cfg.vlan_src_mac_vid_enable = true; > > + br_port_cfg.vlan_dst_mac_vid_enable = true; > > + > > + /* include all non-CPU ports in the CPU portmap */ > > + dsa_switch_for_each_available_port(dp, ds) { > > + if (dsa_port_is_cpu(dp)) > > + continue; > > + > > + bridge_port_map |= BIT(DSA_MXL_PORT(dp->index)); > > + } > > + br_port_cfg.bridge_port_map[0] |= cpu_to_le16(bridge_port_map); > > + > > + ret = MXL862XX_API_WRITE(priv, MXL862XX_BRIDGEPORT_CONFIGSET, > > + br_port_cfg); > > + if (ret) { > > + dev_err(ds->dev, "failed to set the CPU portmap\n"); > > + return ret; > > + } > > + mxl862xx_port_fast_age(ds, cpu_port); > > Duplicate with the call from mxl862xx_port_setup()? +1 I'll remove that. > > [...] > > +static int mxl862xx_port_setup(struct dsa_switch *ds, int port) > > +{ > > + bool is_cpu_port = dsa_is_cpu_port(ds, port); > > + int ret; > > + > > + ret = mxl862xx_configure_tag_proto(ds, port, is_cpu_port); > > + if (ret) > > + return ret; > > + > > + if (!is_cpu_port && !dsa_is_unused_port(ds, port)) { > > + ret = mxl862xx_isolate_port(ds, port); > > + if (ret) > > + return ret; > > + } > > + > > + mxl862xx_port_fast_age(ds, port); > > + > > + ret = mxl862xx_port_state(ds, port, is_cpu_port); > > What is the point of this? Do CPU ports need to be enabled prior to the > mxl862xx_port_enable() call? Testing turned out it doesn't matter and it's totally fine to have the CPU port enabled/disabled by calling mxl862xx_port_enable() just like for any other port. > > > [...] > > +#define MXL862XX_MAX_PHY_PORT_NUM 8 > > +#define MXL862XX_MAX_EXT_PORT_NUM 7 > > +#define MXL862XX_MAX_PORT_NUM (MXL862XX_MAX_PHY_PORT_NUM + \ > > + MXL862XX_MAX_EXT_PORT_NUM) > > 8 + 7 == 15. This is written into ds->num_ports. Good you are asking this. It's another oddity which came from the vendor driver. It's wrong in many ways, see below. > > Could you please explain how this agrees with the Kconfig help text: > These switches have two 10GE SerDes interfaces, one typically > used as CPU port. > MxL86282 has eight 2.5 Gigabit PHYs > MxL86252 has five 2.5 Gigabit PHYs > > What are the extra port indices used for, and is it ok that MxL86282 and > MxL86252 are registered with the same ds->num_ports value? This should > be visible in the "devlink port" command - even unused ports have > devlink instances. This is probably because the SerDes ports also support 10G QXGMII PHYs with 4x 2.5G TP ports connected via a single pair of 10G SerDes lanes. In this case 4 port indexes are used for the 4 ports. My understanding of the internal port IDs by now: MxL86282 MxL86252 port 0: microcontroller microcontroller port 1: PHY port 0 PHY port 0 port 2: PHY port 1 PHY port 1 port 3: PHY port 2 PHY port 2 port 4: PHY port 3 PHY port 3 port 5: PHY port 4 PHY port 4 port 6: PHY port 5 n/c port 7: PHY port 6 n/c port 8: PHY port 7 n/c port 9: SerDes PCS 0 SerDes PCS 0 port 10: SerDes PCS 0 (QXGMII) SerDes PCS 0 (QXGMII) port 11: SerDes PCS 0 (QXGMII) SerDes PCS 0 (QXGMII) port 12: SerDes PCS 0 (QXGMII) SerDes PCS 0 (QXGMII) port 13: SerDes PCS 1 SerDes PCS 1 port 14: SerDes PCS 1 (QXGMII) SerDes PCS 1 (QXGMII) port 15: SerDes PCS 1 (QXGMII) SerDes PCS 1 (QXGMII) port 16: SerDes PCS 1 (QXGMII) SerDes PCS 1 (QXGMII) MaxLinear seems to tell board designers to always use port 9 as CPU port and port 13 for an SFP cage or for an additional single-port PHY. But this seem to be limitations of their current downstream driver implementation and the hardware would be capable of using either or both SerDes port as CPU port, and also support connecting a QXGMII PHY to end up with 12 TP ports in total. Port 0 indeed turned out to be the microcontroller, I can confirm that because it screams when poking it with IEEE1588v2 packets, which you get to see in the kernel logs: net eth1: Invalid source port, packet dropped, tag: 88 c3 0f 04 00 00 00 10 ^^ IGP/EGP is 0 here, Record-ID is 1 The datasheet says that Record-ID "[...] is used for logging information for PTP and OAM packets". And calling mxl862xx_port_disable() on it makes it shut up... Cheers Daniel ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH net-next v9 4/4] net: dsa: add basic initial driver for MxL862xx switches 2026-01-28 16:45 ` Daniel Golle @ 2026-01-28 20:55 ` Vladimir Oltean 0 siblings, 0 replies; 10+ messages in thread From: Vladimir Oltean @ 2026-01-28 20:55 UTC (permalink / raw) To: Daniel Golle Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiner Kallweit, Russell King, Simon Horman, netdev, devicetree, linux-kernel, Frank Wunderlich, Chad Monroe, Cezary Wilmanski, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin On Wed, Jan 28, 2026 at 04:45:38PM +0000, Daniel Golle wrote: > > > +static int mxl862xx_configure_tag_proto(struct dsa_switch *ds, int port, bool enable) > > > +{ > > > + struct mxl862xx_ctp_port_assignment assign = { > > > + .number_of_ctp_port = cpu_to_le16(enable ? (32 - DSA_MXL_PORT(port)) : 1), > > > + .logical_port_id = DSA_MXL_PORT(port), > > > + .first_ctp_port_id = cpu_to_le16(DSA_MXL_PORT(port)), > > > + .mode = cpu_to_le32(MXL862XX_LOGICAL_PORT_ETHERNET), > > > > I have a really hard time understanding the physical port to CTP port > > mapping here. Specifically, I see you allocate 1 CTP port (sub-interface) > > per user port, and the rest up to 32 CTP ports as sub-interfaces of the > > DSA CPU port. > > > > But I can only see this working if the index of the DSA CPU port is the > > last. Otherwise, won't the CTP port IDs of user ports subsequent to the > > DSA CPU port overlap with sub-interfaces of the latter? > > > > Example: > > mxl862xx_configure_tag_proto(ds, 3, true) // CPU port > > -> .number_of_ctp_port = cpu_to_le16(32 - DSA_MXL_PORT(3)), // 28 > > -> .logical_port_id = DSA_MXL_PORT(3), // 4 > > -> .first_ctp_port_id = cpu_to_le16(DSA_MXL_PORT(3)), // 4 > > mxl862xx_configure_tag_proto(ds, 4, false) // user port > > -> .number_of_ctp_port = cpu_to_le16(1), // 1 > > -> .logical_port_id = DSA_MXL_PORT(4), // 5 > > -> .first_ctp_port_id = cpu_to_le16(DSA_MXL_PORT(4)), // 5 > > > > Doesn't the CTP port ID of user port 4 (5) collide with sub-interface 2 > > of CPU port 3? > > Absolutely true. It works coincidental because only SerDes port 0 > (port_id 9) is allowed to be CPU port. When using SerDes port 1 > (port_id 13) as a user port it already starts to be tricky and more care > needs to be taken when setting up CTP port assignment... > > Now that I understand more of what it actually does I'll try to come up > with something smarter. I think the next step is to figure out how to best allocate the limited sub-interfaces for the CPU port depending on what is actually needed from them, because... > > If the hardware supports multiple CPU ports, then please describe all > > CPU ports as such in the device tree, and make an effort to handle that > > description gracefully even if you cannot make use of the second CPU port. > > The number of available CTP (32) limits the possible setup for CPU ports. > The reference driver only allows port_id 9 (SerDes 0) to be used as CPU > port. SerDes 1 may also be used as CPU port in theory. > > > See the rules by which dsa_tree_setup_cpu_ports() creates the initial > > user to CPU port mapping. This avoids a future breakage you'll cause > > with old kernels when you update the device tree to describe the second > > CPU port for what it is. > > Understood. On old kernel, lets assume with this basic driver just added > but future DT with more than one CPU port described one should still end > up with the first CPU port used and all ports assigned to that CPU port. > Right? ...with multiple CPU ports, calls such as dsa_switch_for_each_cpu_port() will enumerate them all. The second CPU port will exist and will be set up like all others (including mxl862xx_configure_tag_proto()), it's just that no user port will be mapped to it (dp->cpu_dp will all point to the first CPU port). > > > [...] > > > +#define MXL862XX_MAX_PHY_PORT_NUM 8 > > > +#define MXL862XX_MAX_EXT_PORT_NUM 7 > > > +#define MXL862XX_MAX_PORT_NUM (MXL862XX_MAX_PHY_PORT_NUM + \ > > > + MXL862XX_MAX_EXT_PORT_NUM) > > > > 8 + 7 == 15. This is written into ds->num_ports. > > Good you are asking this. It's another oddity which came from the vendor > driver. It's wrong in many ways, see below. Ok, so in the next revision you'll set it to 17 according to the table below, right? > > Could you please explain how this agrees with the Kconfig help text: > > These switches have two 10GE SerDes interfaces, one typically > > used as CPU port. > > MxL86282 has eight 2.5 Gigabit PHYs > > MxL86252 has five 2.5 Gigabit PHYs > > > > What are the extra port indices used for, and is it ok that MxL86282 and > > MxL86252 are registered with the same ds->num_ports value? This should > > be visible in the "devlink port" command - even unused ports have > > devlink instances. > > This is probably because the SerDes ports also support 10G QXGMII PHYs > with 4x 2.5G TP ports connected via a single pair of 10G SerDes lanes. > In this case 4 port indexes are used for the 4 ports. > > My understanding of the internal port IDs by now: > MxL86282 MxL86252 > port 0: microcontroller microcontroller > port 1: PHY port 0 PHY port 0 > port 2: PHY port 1 PHY port 1 > port 3: PHY port 2 PHY port 2 > port 4: PHY port 3 PHY port 3 > port 5: PHY port 4 PHY port 4 > port 6: PHY port 5 n/c > port 7: PHY port 6 n/c > port 8: PHY port 7 n/c > port 9: SerDes PCS 0 SerDes PCS 0 > port 10: SerDes PCS 0 (QXGMII) SerDes PCS 0 (QXGMII) > port 11: SerDes PCS 0 (QXGMII) SerDes PCS 0 (QXGMII) > port 12: SerDes PCS 0 (QXGMII) SerDes PCS 0 (QXGMII) For multi-port SerDes interfaces like 10G-QXGMII, technically each port has its own PCS (port 10 -> PCS 1, port 11 -> PCS 2, etc). But I think I understood the general idea. Maybe it would have been better to say "SerDes lane 0 PCS 0" etc. > port 13: SerDes PCS 1 SerDes PCS 1 > port 14: SerDes PCS 1 (QXGMII) SerDes PCS 1 (QXGMII) > port 15: SerDes PCS 1 (QXGMII) SerDes PCS 1 (QXGMII) > port 16: SerDes PCS 1 (QXGMII) SerDes PCS 1 (QXGMII) To be clear, what would be a proper name for the first column in this table? Physical ports, right? > MaxLinear seems to tell board designers to always use port 9 as CPU port > and port 13 for an SFP cage or for an additional single-port PHY. Ok, I assume that's what your board has as well. If I quickly run those numbers into mxl862_tag_xmit(), then I guess that if you wanted to ping each port (with cpu_port = 9), you'd set: MXL862_SUBIF_ID MXL862_IGP_EGP port 0: 7 9 port 1: 8 9 port 2: 9 9 port 3: 10 9 port 4: 11 9 port 5: 12 9 port 6: 13 9 port 7: 14 9 port 8: 15 9 port 9: 16 9 port 10: 17 9 port 11: 18 9 port 12: 19 9 port 13: 20 9 port 14: 21 9 port 15: 22 9 port 16: 23 9 I wonder how those MXL862_SUBIF_ID values map to the .number_of_ctp_port configured by mxl862xx_configure_tag_proto(), which is 32 - DSA_MXL_PORT(9) == 22. So to target port 16, you'd need a MXL862_SUBIF_ID which is out of range, and results in an unrepresentable CTP index of 32. And subif_id values 0..6 are unused. Something doesn't add up... Where does the 16 come from in the "usr_port + 16 - cpu_port" formula? > But this seem to be limitations of their current downstream driver > implementation and the hardware would be capable of using either > or both SerDes port as CPU port, and also support connecting a QXGMII PHY > to end up with 12 TP ports in total. Interesting. If your host SoC supported 10G-QXGMII, you could reconfigure both sides of that 10G SerDes interface on your existing board and test quad CPU ports just for fun and giggles. > Port 0 indeed turned out to be the microcontroller, I can confirm that > because it screams when poking it with IEEE1588v2 packets, which you > get to see in the kernel logs: > net eth1: Invalid source port, packet dropped, tag: 88 c3 0f 04 00 00 00 10 > ^^ > IGP/EGP is 0 here, Record-ID is 1 > > The datasheet says that Record-ID "[...] is used for logging information > for PTP and OAM packets". > > And calling mxl862xx_port_disable() on it makes it shut up... In SJA1110 I could also ping the microcontroller by putting an IP address on that user port. I am shutting it down during probe time, in sja1110_disable_microcontroller(), and not relying on the firmware at all. Maybe this is a stupid question, but I still don't quite grasp the purpose of the logical port + sub-interface scheme from its use. From what I could gather: - xmit from Linux targets the logical port ID of the CPU port, and a sub-interface somehow in an implicit 1:1 association with user ports, which seems under-dimensioned in the maximal scenario - the P-Mapper documentation says it can map one of {VLAN PCP, IP DSCP, LAG ID} to a sub-interface, but this feature is left to the firmware default state (MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_CTP_MAPPING is never set), so I cannot make any assumptions about its influence I don't quite understand how these two uses interact. ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH net-next v9 0/4] net: dsa: initial support for MaxLinear MxL862xx switches 2026-01-27 21:37 [PATCH net-next v9 0/4] net: dsa: initial support for MaxLinear MxL862xx switches Daniel Golle ` (3 preceding siblings ...) 2026-01-27 21:38 ` [PATCH net-next v9 4/4] net: dsa: add basic initial driver for MxL862xx switches Daniel Golle @ 2026-01-27 22:56 ` Vladimir Oltean 2026-01-28 1:38 ` Daniel Golle 4 siblings, 1 reply; 10+ messages in thread From: Vladimir Oltean @ 2026-01-27 22:56 UTC (permalink / raw) To: Daniel Golle Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiner Kallweit, Russell King, Simon Horman, netdev, devicetree, linux-kernel, Frank Wunderlich, Chad Monroe, Cezary Wilmanski, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin On Tue, Jan 27, 2026 at 09:37:40PM +0000, Daniel Golle wrote: > This series adds very basic DSA support for the MaxLinear MxL86252 > (5 PHY ports) and MxL86282 (8 PHY ports) switches. > > MxL862xx integrates a firmware running on an embedded processor (running > Zephyr RTOS). Host interaction uses a simple netlink-like API transported > over MDIO/MMD. > > This series includes only what's needed to pass traffic between user > ports and the CPU port: relayed MDIO to internal PHYs, basic port > enable/disable, and CPU-port special tagging. > Follow up series will bring bridge, VLAN, ... offloading, I'm surprised the Kconfig help text says: These switches have two 10GE SerDes interfaces, one typically used as CPU port. yet only PHY_INTERFACE_MODE_INTERNAL is set in phylink supported_interfaces. You're also not making any mention of future SerDes support. What's up with that, how do the SerDes ports currently work and how are they described? (as internal?!) > and support for using a 802.1Q-based special tag instead of the > proprietary 8-byte tag. Why is that? Another (related) question. You have this comment in tag_mxl862xx.c: /* switch firmware expects ports to be counted starting from 1 */ from which I don't completely understand how is the firmware involved (does it process the tags?). Would the expectation also apply to the 802.1Q based tagger? What's the real story behind port index 0? Does it really not exist, or is it some attempt to hide an internal port that's not supposed to be used? If the latter, I guess something like the snippet below (seen in arch/arm64/boot/dts/freescale/fsl-lx2160a-bluebox3.dts) would simplify the driver by a bit: ethernet-switch@0 { ... ethernet-ports { ... /* Microcontroller port */ port@0 { reg = <0>; status = "disabled"; }; ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH net-next v9 0/4] net: dsa: initial support for MaxLinear MxL862xx switches 2026-01-27 22:56 ` [PATCH net-next v9 0/4] net: dsa: initial support for MaxLinear " Vladimir Oltean @ 2026-01-28 1:38 ` Daniel Golle 0 siblings, 0 replies; 10+ messages in thread From: Daniel Golle @ 2026-01-28 1:38 UTC (permalink / raw) To: Vladimir Oltean Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiner Kallweit, Russell King, Simon Horman, netdev, devicetree, linux-kernel, Frank Wunderlich, Chad Monroe, Cezary Wilmanski, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin On Wed, Jan 28, 2026 at 12:56:43AM +0200, Vladimir Oltean wrote: > On Tue, Jan 27, 2026 at 09:37:40PM +0000, Daniel Golle wrote: > > This series adds very basic DSA support for the MaxLinear MxL86252 > > (5 PHY ports) and MxL86282 (8 PHY ports) switches. > > > > MxL862xx integrates a firmware running on an embedded processor (running > > Zephyr RTOS). Host interaction uses a simple netlink-like API transported > > over MDIO/MMD. > > > > This series includes only what's needed to pass traffic between user > > ports and the CPU port: relayed MDIO to internal PHYs, basic port > > enable/disable, and CPU-port special tagging. > > Follow up series will bring bridge, VLAN, ... offloading, > > I'm surprised the Kconfig help text says: > > These switches have two 10GE SerDes interfaces, one typically > used as CPU port. > > yet only PHY_INTERFACE_MODE_INTERNAL is set in phylink supported_interfaces. > You're also not making any mention of future SerDes support. What's up > with that, how do the SerDes ports currently work and how are they > described? (as internal?!) Oh, sure, I'm obviously going to add both SerDes interfaces which support various interface modes as phylink_pcs instances in a follow-up series. I was planning to do basic bridge offloading and FDB access first, then SerDes PCS, then LAG, then VLAN, and then last but not least using the 802.1Q-based special tag (LoC of just that feature is almost as much as all the rest together in the vendor driver). > > > and support for using a 802.1Q-based special tag instead of the > > proprietary 8-byte tag. > > Why is that? Using an 802.1Q-based special tag is advantageous in some situations, for example PPE offloading engines typically do support 802.1Q and 802.1ad VLANs, but do not support any special tag apart from those of the same vendor (eg. MediaTek's PPE supports MediaTek's 4-byte special tag, but includes "plain" 802.1Q or 802.1ad/Q-in-Q as part of the tuple identifying a flow). Hence by implementing an 802.1Q-based tag one can benefit from better performance, less CPU load and less energy consumption when combining these switches with router SoCs of other vendors if they implementing "picky" offloading fast-paths. > > Another (related) question. You have this comment in tag_mxl862xx.c: > > /* switch firmware expects ports to be counted starting from 1 */ > > from which I don't completely understand how is the firmware involved > (does it process the tags?). Would the expectation also apply to the > 802.1Q based tagger? No. The 802.1Q-based tagger is basically implemented entirely by setting up additional bridges, virtual bridge ports and special VLAN forwarding rules. It takes more than just "switching" the tag protocol... But hence it can be implemented in pretty much any way we want. > > What's the real story behind port index 0? Does it really not exist, or > is it some attempt to hide an internal port that's not supposed to be used? It's just an oddity of the management firmware which runs on the switch. Port ID 0 has special meaning, physical ports are counted started from 1. For example, when adding FDB entries, port_id == 0 means not specifying a port (eg. for filter rules). However, it *is* weird that BIT(0) of any portmap isn't ever used in the driver, and it looks like it could be like you describe below. I will ask. > > If the latter, I guess something like the snippet below (seen in > arch/arm64/boot/dts/freescale/fsl-lx2160a-bluebox3.dts) would simplify > the driver by a bit: > > ethernet-switch@0 { > ... > ethernet-ports { > ... > /* Microcontroller port */ > port@0 { > reg = <0>; > status = "disabled"; > }; This construct could never the less help to make things more easy and less annoying to deal with. I might just start counting from reg = <1> in DT and have a dummy/reserved port 0. Always having add/substract 1 is often confusing because then you have to remember if a port number or mask stored in some structure or variable denotes the DSA perspective or the switch firmware perspective. Unifying the two would definitely be beneficial. I can give that a short tomorrow. ^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-01-28 20:55 UTC | newest] Thread overview: 10+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-01-27 21:37 [PATCH net-next v9 0/4] net: dsa: initial support for MaxLinear MxL862xx switches Daniel Golle 2026-01-27 21:37 ` [PATCH net-next v9 1/4] dt-bindings: net: dsa: add MaxLinear MxL862xx Daniel Golle 2026-01-27 21:37 ` [PATCH net-next v9 2/4] net: dsa: add tag format for MxL862xx switches Daniel Golle 2026-01-27 21:38 ` [PATCH net-next v9 3/4] net: mdio: add unlocked mdiodev C45 bus accessors Daniel Golle 2026-01-27 21:38 ` [PATCH net-next v9 4/4] net: dsa: add basic initial driver for MxL862xx switches Daniel Golle 2026-01-28 11:29 ` Vladimir Oltean 2026-01-28 16:45 ` Daniel Golle 2026-01-28 20:55 ` Vladimir Oltean 2026-01-27 22:56 ` [PATCH net-next v9 0/4] net: dsa: initial support for MaxLinear " Vladimir Oltean 2026-01-28 1:38 ` Daniel Golle
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox