* [PATCH net-next v13 0/9] net: dsa: add DSA support for the LAN9645x switch chip family
@ 2026-09-29 7:48 Jens Emil Schulz Østergaard
2026-09-29 7:48 ` [PATCH net-next v13 1/9] net: dsa: add tag driver for LAN9645X Jens Emil Schulz Østergaard
` (8 more replies)
0 siblings, 9 replies; 18+ messages in thread
From: Jens Emil Schulz Østergaard @ 2026-09-29 7:48 UTC (permalink / raw)
To: UNGLinuxDriver, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Woojung Huh,
Russell King, Steen Hegelund, Daniel Machon, Geert Uytterhoeven,
Magnus Damm
Cc: linux-kernel, netdev, devicetree, linux-renesas-soc,
Jens Emil Schulz Østergaard
This series provides the Microchip LAN9645X Switch driver.
The LAN9645x is a family of chips with ethernet switch functionality and
multiple peripheral functions. The switch delivers up to 9 ethernet
ports and 12 Gbps switching bandwidth.
The switch chip has 5 integrated copper PHYs, support for 2x RGMII
interfaces, 2x SGMII and one QSGMII interface.
The switch chip is from the same design architecture family as ocelot
and lan966x, and the driver reflects this similarity. However, LAN9645x
does not have an internal CPU in any package, and must be driven
externally. For register IO it supports interfaces such as SPI, I2C and
MDIO.
The chip supports a variety of network features such as
* Mactable for MDB/FDB functionality
* Bridge forwarding offload
* VLAN-aware bridging
* IGMP/MLD snooping
* Link aggregation
* PTP timestamping
* FRER (802.1CB)
* Media Redundancy Protocol
* Parallel Redundancy and High-Availability Seamless Redundancy
(HSR/PRP) in DANH/DANP mode
* Per stream filtering and policing
* Shapers such as Credit Based Shaping and Time Aware Shaing
* Frame preemption
* A TCAM (VCAP) for line-rate frame processing
The LAN9645x family consists of the following SKUs:
LAN96455F
LAN96457F
LAN96459F
LAN96455S
LAN96457S
LAN96459S
The difference between the SKUs is the number of supported ports (5, 7
or 9) and features supported. The F subfamily supports HSR/PRP and TSN,
while the S subfamily does not.
The intended way to bind this driver is using a parent MFD driver,
responsible for the register IO protocol, and distributing regmaps to
child devices. The goal is to use the same approach as the MFD driver in
drivers/mfd/ocelot-spi.c, which also resets the chip before it
instantiates its children.
This driver expects to request named regmaps from a parent device. This
approach is similar to the DSA driver
drivers/net/dsa/ocelot/ocelot_ext.c
which supports being driven by an external CPU via SPI with parent
device drivers/mfd/ocelot-spi.c.
The MFD driver will come in a later series, because there are
requirements on the number of child devices before a driver qualifies as
a MFD device.
Development is done using the LAN966x as a host CPU, running the lan966x
swichdev driver, using the EVB-LAN9668 EDS2 board.
The datasheet is available here:
https://ww1.microchip.com/downloads/aemDocuments/documents/UNG/ProductDocuments/DataSheets/LAN9645xF-Data-Sheet-DS00006065.pdf
This series will deliver the following features:
* Standalone ports
* Bridge forwarding and FDB offloading
* Multicast forwarding and MDB offloading
* VLAN-aware bridge
* Stats integration
More support will be added at a later stage. Here is a tentative plan of
future patches for this DSA driver:
* Add LAG support.
* Add TC matchall mirror support.
* Add TC matchall police support.
* Add DCB/qos support.
* Add simple TC support: mqprio, cbs, tbf, ebf.
* Add TC flower filter support.
* Add HSR/PRP offloading support.
* Add PTP support.
* Add TC taprio support.
For completeness I include tentative plan of planned patches for
LAN9645x peripherals:
* Extend pinctrl-ocelot for LAN9645x:
https://lore.kernel.org/linux-gpio/20260119-pinctrl_ocelot_extend_support_for_lan9645x-v1-0-1228155ed0ee@microchip.com/
* Add driver for internal PHY:
https://lore.kernel.org/netdev/20260123-phy_micrel_add_support_for_lan9645x_internal_phy-v1-1-8484b1a5a7fd@microchip.com/
* MFD driver for managing register IO protocol and child device
initialization.
* Extend pinctrl-microchip-sgpio for LAN9645x support.
* Extend i2c_designware for LAN9645x support.
* Add driver for outbound interrupt controller.
* Add serdes driver for lan9645x.
Signed-off-by: Jens Emil Schulz Østergaard <jensemil.schulzostergaard@microchip.com>
---
Changes in v13:
- Individual patches mention specific v13 changes
- bindings: Drop the commit message paragraph citing
Documentation/networking/phy.rst.
- basic: only redirect the CPU extraction queues to the NPI port while
its link is up, and flush the CPU port module on NPI link up.
- Rebased on net-next, bumping DSA_TAG_PROTO_LAN9645X_VALUE to 37
- Link to v12: https://lore.kernel.org/r/20260908-dsa_lan9645x_switch_driver_base-v12-0-2d6aa59350cd@microchip.com
Changes in v12:
- reorder the series so bridge support lands after vlan, mac table and
mdb support. port_fast_age() now exists before the STP state handling
that needs it, and host address filtering is in place before bridging.
- include the CPU port module in the flood masks from the start, and
remove it again in the bridge patch where port_set_host_flood() takes
over on demand host flooding. Previously the host could not receive
unicast until the mac table patch.
- vlan: serialize the VLAN table with fwd_domain_lock, and describe the
untagged VLAN, TPID and C-component conformance limitations.
- tag driver: move an out-of-band VLAN tag into the payload before
reading it, instead of relying on the conduit never advertising a VLAN
TX offload.
- tag driver: restore the classified VLAN as a C-tag rather than following
the IFH tag type, so a terminated frame lands on the VID the hardware
forwarded it on.
- basic: drop the tail drop watermark setup, which programmed a
condition that could not trigger.
- mdb: handle PGID exhaustion on both the add and delete paths, rather
than leaving a departed port in the group.
- mdb: classify IPv4 multicast on the 01:00:5E prefix alone. Hardware does
not apply the 23-bit rule, so some groups were programmed as entries it
never matches.
- stats: give the hardware counters and the software shadow a common
baseline, fix some rtnl_link_stats64 fields, and stop reporting the
three eth-mac fields the hardware cannot measure.
- stats: stop the counter poller in .shutdown, which kept reading
registers after the parent had been shut down.
- Link to v11: https://lore.kernel.org/r/20260805-dsa_lan9645x_switch_driver_base-v11-0-007ebc983a0a@microchip.com
Changes in v11:
- Update comment about vlan tag hwaccel use in tag driver.
- Bump DSA_TAG_PROTO value in include/net/dsa.h.
- stats: add comment about calling contexts of sw_lock in
lan9645x_stats_get_stats64
- Link to v10: https://lore.kernel.org/r/20260713-dsa_lan9645x_switch_driver_base-v10-0-a4886a08fb15@microchip.com
Changes in v10:
- Individual patches mention specific v10 changes
- Update tag driver after skb ownership model change in DSA taggers.
- Link to v9: https://lore.kernel.org/r/20260708-dsa_lan9645x_switch_driver_base-v9-0-0d1512a326d7@microchip.com
Changes in v9:
- Individual patches mention specific v9 changes
- Drop RGMII muxing to port 4.
- Link to v8: https://lore.kernel.org/r/20260702-dsa_lan9645x_switch_driver_base-v8-0-90228d8bba58@microchip.com
Changes in v8:
- Individual patches mention specific v8 changes
- Do not use the name CPU_PORT for the chips internal CPU port module,
as it collides with the DSA CPU port (the NPI front port). Drop the
CPU_PORT macro and reference the module as lan9645x->num_phys_ports,
mirroring ocelot/felix.
- Reword the port map and related comments to distinguish the chip CPU
port modules (indices 9-10) from the DSA CPU port.
- Add a port_mux_lock mutex to serialize port mux arbitration in
the phylink mac_prepare path.
- Link to v7: https://lore.kernel.org/r/20260603-dsa_lan9645x_switch_driver_base-v7-0-b2f90e676707@microchip.com
Changes in v7:
- Individual patches mention specific v7 changes
- Do not mark the BPDU range 01:80:C2:00:00:0X as offloaded in the tag driver.
- Refactor cpu queue based frame classification to use categories default, trap
and copy, which mirrors usage instead of being based on frame types.
- Add registers ANA:COMMON:CPUQ_8021_CFG for bpdu cpu queue configuration.
- Use cpu queue LAN9645X_CPUQ_TRAP for bpdu frames.
- Refactor IGMP/MLD/IPMC_CTRL to use new LAN9645X_CPUQ_DEF,
LAN9645X_CPUQ_TRAP and LAN9645X_CPUQ_COPY queues.
- Add __aligned(2) to mac variable in lan9645x_mdb_update_dest.
- Link to v6: https://lore.kernel.org/r/20260527-dsa_lan9645x_switch_driver_base-v6-0-4d409ae64f3c@microchip.com
Changes in v6:
- Individual patches mention specific v6 changes
- Rebased on net-next, bumping DSA_TAG_PROTO_LAN9645X_VALUE to 34
- Link to v5: https://lore.kernel.org/r/20260518-dsa_lan9645x_switch_driver_base-v5-0-968fbf34ffa3@microchip.com
Changes in v5:
- Individual patches mention specific v5 changes
- Undo offset fix in postpull_rcsum. The original logic was correct for
CHECKSUM_COMPLETE host NICs
- Use __always_inline in lan9645x_ifh_{get,set}
- remove double space after = in set_merge_mask
- remove unused fields dd_dis and tsn_dis, and add SKU supported port validation
during setup
- phylink: remove MAC_2500FD
- phylink: add comment about empty supported_interfaces for port 5-6.
- phylink: fix 2:1 rgmii port muxing for port module 4 and 7 to be fully
dynamic and validate requested mux settings.
- phylink: add comment about 2:1 rgmii port muxing for port module 4
and 7.
- rx/tx-internal-delay-ps checked against supported 2ns value
- init lan9645x->npi = -1 at probe and check port < 0 in npi_deinit
- use ds->ageing_time_max
- use packed p->host_flood_req for atomic r/w
- fix typo in set_ageing_time comment
- include lan9645x->bridge deref under lock in brige_join
- switch -EBUSY to -EINVAL for vlan add/del in the reserved range.
- remove reserved HSR vlan
- lan9645x_mac_init returns error on table init timeout
- add comment about skipping LOCKED entries on fdb dump
- skip igmp/mld redir for npi port
- make lan9645x_stats_init void
- add SCNT_TX_BUFDROP to tx_dropped
- change rmon range {0,64} -> {64, 64}. Runt frames counted elsewhere.
- remove rx_crc, rx_symbol_err from rx_packets, as they are already
counted in SZ_* buckets.
- add defensive cancel_delayed_work_sync in stats_free
- Link to v4: https://lore.kernel.org/r/20260430-dsa_lan9645x_switch_driver_base-v4-0-f1b6005fa8b7@microchip.com
Changes in v4:
- v3 was deferred, but I made some changes based on the Sashiko review
- Individual patches mention specific v4 changes
- Fix offset in postpull_rcsum so prefix eth header is cleared, not
actual eth header, so tag driver works with CHECKSUM_COMPLETE host
NICs
- Fix untagged rx on vlan aware port with pvid
- Add comment to QSYS_RES_CFG configuration
- Phylink_mac_prepare: fix to make sure we can dynamically change rgmii
on port 4
- Move ports allocation to probe
- tag_npi_setup: reject cascaded setups
- Skip WARN_ON in lan9645x_to_port
- set_host_flood changed to per port work to coalesce values and skip
atomic allocations
- Fix clear HOST_PVID vlan membership when a port joins a bridge.
- Explicit default value write to tag type register for untagged frames
- Use lan_rmw for ANA_DROP_CFG
- Add comment for error path in lan9645x_vlan_hw_wr
- Remove mac_entries list and just do direct IO to mac table from
fdb_add/fdb_del.
- Clean up fresh mdb when hw mac table write fails.
- Remove rx_uc and tx_uc from ethtool stats list, as they are derivable
from the eth-mac group
- Split stats_init into stats_alloc and stats_init, use alloc in probe
and init in dsa_setup
- Link to v3: https://lore.kernel.org/r/20260410-dsa_lan9645x_switch_driver_base-v3-0-aadc8595306d@microchip.com
Changes in v3:
- Individual patches mention specific v3 changes.
- Add guard before vlan_remove_tag on xmit
- Add pskb_may_pull checks on rx
- Remove additionalProperties: true in bindings
- Remove unnecessary | from description in bindings
- Change top level $ref to dsa.yaml#/$defs/ethernet-ports
- Use ethernet-ports and ethernet-port
- Move ethernet-ports under properties instead of patternProperties
- Move unevaluatedProperties: false after $ref
- Update bindings example to use ethernet-ports and ethernet-port
- Move DEV_MAC_TAGS_CFG to port setup, instead of vlan config, so vlan
overhead is always included in port frame maxlen calculation.
- Remove code disabling ipv6 on conduit
- Use of_property_read_u32 for {rx,tx}-internal-delay-ps
- Use dsa_user_ports(ds) instead of
GENMASK(lan9645x->num_phys_ports - 1, 0) as base flood mask.
- Add comment explaining obey vlan
- Allow disabling aging with explicit zero parameters.
- Fix non-forwarding STP states in bridge fwd calculation.
- Restore host flood state on bridge leave.
- Avoid mac_entry dealloc when mac table writes fail.
- Avoid mdb_entry dealloc when mac table writes fail.
- Dealloc mac_entries on deinit.
- Dealloc mdb_entries on deinit.
- Link to v2: https://lore.kernel.org/r/20260324-dsa_lan9645x_switch_driver_base-v2-0-f7504e3b0681@microchip.com
Changes in v2:
- Individual patches have specific v2 changes.
- Ran DSA, and std counters, selftests, which prompted several changes.
The following selftests pass, except for some expected failures:
- bridge_vlan_aware.sh
- bridge_vlan_unaware.sh
- bridge_vlan_mcast.sh
- no_forwarding.sh
- bridge_mdb.sh
- bridge_mld.sh
- test_fdb_stress_test.sh
- .../drivers/net/hw/ethtool_rmon.sh
- .../drivers/net/hw/ethtool_std_stats.sh (from Ioana's series)
- Added new patch for MDB management, as this was required for selftests.
- Added port_set_host_flood to enable unknown traffic to standalone during
promisc/ALL_MULTI (selftests).
- Remove the dubugfs.
- Link to v1: https://lore.kernel.org/r/20260303-dsa_lan9645x_switch_driver_base-v1-0-bff8ca1396f5@microchip.com
---
Jens Emil Schulz Østergaard (9):
net: dsa: add tag driver for LAN9645X
dt-bindings: net: lan9645x: add LAN9645X switch bindings
net: dsa: lan9645x: add autogenerated register macros
net: dsa: lan9645x: add basic dsa driver for LAN9645X
net: dsa: lan9645x: add vlan support
net: dsa: lan9645x: add mac table integration
net: dsa: lan9645x: add mdb management
net: dsa: lan9645x: add bridge support
net: dsa: lan9645x: add port statistics
.../net/dsa/microchip,lan96455s-switch.yaml | 177 ++
MAINTAINERS | 10 +
drivers/net/dsa/Kconfig | 2 +
drivers/net/dsa/microchip/Makefile | 1 +
drivers/net/dsa/microchip/lan9645x/Kconfig | 12 +
drivers/net/dsa/microchip/lan9645x/Makefile | 12 +
drivers/net/dsa/microchip/lan9645x/lan9645x_mac.c | 268 +++
drivers/net/dsa/microchip/lan9645x/lan9645x_main.c | 1031 +++++++++++
drivers/net/dsa/microchip/lan9645x/lan9645x_main.h | 435 +++++
drivers/net/dsa/microchip/lan9645x/lan9645x_mdb.c | 569 ++++++
drivers/net/dsa/microchip/lan9645x/lan9645x_npi.c | 116 ++
.../net/dsa/microchip/lan9645x/lan9645x_phylink.c | 371 ++++
drivers/net/dsa/microchip/lan9645x/lan9645x_port.c | 180 ++
drivers/net/dsa/microchip/lan9645x/lan9645x_regs.h | 1932 ++++++++++++++++++++
.../net/dsa/microchip/lan9645x/lan9645x_stats.c | 856 +++++++++
.../net/dsa/microchip/lan9645x/lan9645x_stats.h | 229 +++
drivers/net/dsa/microchip/lan9645x/lan9645x_vlan.c | 411 +++++
include/linux/dsa/lan9645x.h | 54 +
include/net/dsa.h | 2 +
net/dsa/Kconfig | 11 +
net/dsa/Makefile | 1 +
net/dsa/tag_lan9645x.c | 474 +++++
22 files changed, 7154 insertions(+)
---
base-commit: 014d795c73837ea2339a4ea8e8f82c6e959b845d
change-id: 20260210-dsa_lan9645x_switch_driver_base-312bbfc37edb
Best regards,
--
Jens Emil Schulz Østergaard <jensemil.schulzostergaard@microchip.com>
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH net-next v13 1/9] net: dsa: add tag driver for LAN9645X
2026-09-29 7:48 [PATCH net-next v13 0/9] net: dsa: add DSA support for the LAN9645x switch chip family Jens Emil Schulz Østergaard
@ 2026-09-29 7:48 ` Jens Emil Schulz Østergaard
2026-10-02 21:14 ` netdev-bot+sashiko
2026-09-29 7:48 ` [PATCH net-next v13 2/9] dt-bindings: net: lan9645x: add LAN9645X switch bindings Jens Emil Schulz Østergaard
` (7 subsequent siblings)
8 siblings, 1 reply; 18+ messages in thread
From: Jens Emil Schulz Østergaard @ 2026-09-29 7:48 UTC (permalink / raw)
To: UNGLinuxDriver, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Woojung Huh,
Russell King, Steen Hegelund, Daniel Machon, Geert Uytterhoeven,
Magnus Damm
Cc: linux-kernel, netdev, devicetree, linux-renesas-soc,
Jens Emil Schulz Østergaard
Add tag driver for LAN9645x using a front port as CPU port. This mode
is called an NPI port in the datasheet.
Use long prefix on extraction (RX) and no prefix on injection (TX). A
long prefix on extraction helps get through the conduit port on host
side, since it will see a broadcast MAC.
The LAN9645x chip is in the same design architecture family as ocelot
and lan966x. The tagging protocol has the same structure as these chips,
but the particular fields are different or have different sizes.
Therefore, this tag driver is similar to tag_ocelot.c, but the
differences in fields makes it hard to reuse.
LAN9645x supports 3 different tag formats for extraction/injection of
frames from a CPU port: long prefix, short prefix and no prefix.
The tag is prepended to the frame. The critical data for the chip is
contained in an internal frame header (IFH) which is 28 bytes. The
prefix formats look like this:
Long prefix (16 bytes) + IFH:
- DMAC = 0xffffffffffff on extraction.
- SMAC = 0xfeffffffffff on extraction.
- ETYPE = 0x8880
- payload = 0x0011
- IFH
Short prefix (4 bytes) + IFH:
- 0x8880
- 0x0011
- IFH
No prefix:
- IFH
The format can be configured asymmetrically on RX and TX.
The IFH get/set functions are declared as inline. All the field
constants are compile-time known, so when these calls are inlined
efficient code is generated with branches pruned and loops unrolled.
During testing it was observed that without explicit inlining GCC would
have trouble inlining the functions, which hurt performance.
A frame extracted from a VLAN-aware port arrives without its VLAN tag.
REW_PORT_CFG.NO_REWRITE is clear on the NPI port, so the rewriter applies
the classified VLAN_POP_CNT, and the classified VID reaches the CPU only
through the IFH. The tagger restores it as a hwaccel tag, except when it
equals the port pvid, where a frame tagged with the pvid is
indistinguishable from an untagged one.
The restored tag is always a C-tag, not the TPID the IFH TAG_TYPE reports.
Both TPIDs are recognized as VLAN tags unconditionally, with no per port
control to treat one as untagged customer data, so an S-tag is already
consumed as the VLAN tag before extraction. Restoring 802.1AD would make
the bridge push the tag back into the payload and reclassify the frame to
the port pvid, placing it on a different VID than the one the hardware
forwarded it on. Normalizing to a C-tag keeps the terminated path
consistent with the forwarded path, at the cost of not preserving the
S-tag TPID towards the CPU. This assumes a bridge vlan_protocol of
802.1Q, which is the only protocol the driver offloads.
Reviewed-by: Steen Hegelund <Steen.Hegelund@microchip.com>
Signed-off-by: Jens Emil Schulz Østergaard <jensemil.schulzostergaard@microchip.com>
---
Changes in v12:
- Move an out-of-band VLAN tag into the payload with
__vlan_hwaccel_push_inside() before reading it, rather than relying on
the conduit never advertising NETIF_F_HW_VLAN_CTAG_TX or
NETIF_F_HW_VLAN_STAG_TX in vlan_features.
- Return the skb from lan9645x_xmit_get_vlan_info(), since
__vlan_hwaccel_push_inside() may reallocate and frees the skb on
failure.
- Annotate the IFH field defines with injection/extraction
applicability
- Drop IFH_TIMESTAMP because it is 38 bits wide. Rely on NS/SUB_NS
subfields.
- Document how HW derives the DMAC offset from pop_cnt and etype_ofs,
and drop frames whose encoding only a tag push could produce.
- Drop unncessary IFH_SRCPORT on injection.
- Drop the WARN_ON_ONCE() when the source port does not resolve to a
user port.
- Move the IFH field position and size defines from
include/linux/dsa/lan9645x.h into net/dsa/tag_lan9645x.c.
- Rename LAN9645X_IFH_LEN to LAN9645X_IFH_LEN_BYTES, so it does not read
as the position of the IFH LEN field.
- Use skb_vlan_eth_hdr() in lan9645x_xmit_get_vlan_info().
- Drop BTM_MSK() TOP_MSK() helpers for GENMASK().
- Rename set_merge_mask() to lan9645x_ifh_merge_byte().
- Name the vendor in the Kconfig prompt, spell the part LAN9645x, and
move the entry to its sorted position after NET_DSA_TAG_KSZ.
- Clamp the classified QoS class to the width of IFH_QOS_CLASS. A
skb->priority above 7 aliased down onto an unrelated class.
- Comment why needed_headroom covers the extraction prefix.
- Restore the classified VLAN as a C-tag rather than following the IFH
TAG_TYPE, so a terminated S-tagged frame lands on the same VID the
hardware forwards it on.
Changes in v11:
- Update comment about vlan tag hwaccel use in tag driver.
- Bump DSA_TAG_PROTO value in include/net/dsa.h.
Changes in v10:
- Update tag driver after skb ownership model change in DSA taggers.
Explicitly freeing the skb at rcv/xmit return points.
Changes in v8:
- Drop the cpu_port local and use ds->num_ports directly as the IFH
source port (the CPU port module index).
- Reword the reflection comment to refer to "the CPU".
Changes in v7:
- Introduce cpu queue based frame classification, and refactor to the
categories default, trap and copy, which mirrors usage instead of
being based on frame types.
Changes in v6:
- rebased on net-next, bumping DSA_TAG_PROTO_LAN9645X_VALUE to 34
Changes in v5:
- Undo offset fix in postpull_rcsum. The original logic was correct for
CHECKSUM_COMPLETE host NICs
- Use __always_inline in lan9645x_ifh_{get,set}
- remove double space after = in set_merge_mask
Changes in v4:
- Fix offset in postpull_rcsum so prefix eth header is cleared, not
actual eth header, so tag driver works with CHECKSUM_COMPLETE host
NICs
- Fix untagged rx on vlan aware port with pvid
Changes in v3:
- guard vlan_remove_tag behind skb_headlen(skb) >= VLAN_ETH_HLEN on xmit
- add pskb_may_pull checks in rx path
Changes in v2:
- sorting in net/dsa/Kconfig
- sorting in net/dsa/Makefile
- remove default zero promisc_on_conduit
- move functions to to .c file
- add justification for inline usage to commit message
- add __skb_put_padto on xmit path
- fix hwaccel_put_tag
---
MAINTAINERS | 8 +
include/linux/dsa/lan9645x.h | 54 +++++
include/net/dsa.h | 2 +
net/dsa/Kconfig | 11 +
net/dsa/Makefile | 1 +
net/dsa/tag_lan9645x.c | 474 +++++++++++++++++++++++++++++++++++++++++++
6 files changed, 550 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 6de1ff058db6..e2cbcf89d36e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17800,6 +17800,14 @@ L: netdev@vger.kernel.org
S: Maintained
F: drivers/net/phy/microchip_t1.c
+MICROCHIP LAN9645X ETHERNET SWITCH DRIVER
+M: Jens Emil Schulz Østergaard <jensemil.schulzostergaard@microchip.com>
+M: UNGLinuxDriver@microchip.com
+L: netdev@vger.kernel.org
+S: Maintained
+F: include/linux/dsa/lan9645x.h
+F: net/dsa/tag_lan9645x.c
+
MICROCHIP LAN966X ETHERNET DRIVER
M: Horatiu Vultur <horatiu.vultur@microchip.com>
M: UNGLinuxDriver@microchip.com
diff --git a/include/linux/dsa/lan9645x.h b/include/linux/dsa/lan9645x.h
new file mode 100644
index 000000000000..e774aa137092
--- /dev/null
+++ b/include/linux/dsa/lan9645x.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: GPL-2.0
+ * Copyright (C) 2026 Microchip Technology Inc.
+ */
+
+#ifndef _NET_DSA_TAG_LAN9645X_H_
+#define _NET_DSA_TAG_LAN9645X_H_
+
+#include <linux/bits.h>
+#include <linux/types.h>
+
+/* LAN9645x supports 3 different formats on an NPI port, long prefix, short
+ * prefix and no prefix. The format can be configured asymmetrically on RX and
+ * TX. We use long prefix on extraction (RX), and no prefix on injection.
+ * The long prefix on extraction helps get through the conduit port on host
+ * side, since it will see a broadcast MAC.
+ *
+ * The internal frame header (IFH) is 28 bytes.
+ *
+ * Long prefix, 16 bytes + IFH:
+ * - DMAC = 0xFFFFFFFFFFFF on extraction.
+ * - SMAC = 0xFEFFFFFFFFFF on extraction.
+ * - ETYPE = 0x8880
+ * - payload = 0x0011
+ * - IFH
+ *
+ * Short prefix, 4 bytes + IFH:
+ * - 0x8880
+ * - 0x0011
+ * - IFH
+ *
+ * No prefix:
+ * - IFH
+ *
+ */
+#define LAN9645X_IFH_TAG_TYPE_C 0
+#define LAN9645X_IFH_TAG_TYPE_S 1
+#define LAN9645X_IFH_LEN_U32 7
+#define LAN9645X_IFH_LEN_BYTES (LAN9645X_IFH_LEN_U32 * sizeof(u32))
+#define LAN9645X_IFH_BITS (LAN9645X_IFH_LEN_BYTES * BITS_PER_BYTE)
+#define LAN9645X_LONG_PREFIX_LEN 16
+#define LAN9645X_TOTAL_TAG_LEN \
+ (LAN9645X_LONG_PREFIX_LEN + LAN9645X_IFH_LEN_BYTES)
+
+/* Chip has 8 cpu queues. The cpu queues used by a frame are passed as a mask in
+ * the IFH on extraction. We use this to avoid classifying BPDU, IGMP and MLD
+ * frames in the tag driver.
+ */
+enum {
+ LAN9645X_CPUQ_DEF = 0,
+ LAN9645X_CPUQ_TRAP = 1,
+ LAN9645X_CPUQ_COPY = 2,
+};
+
+#endif /* _NET_DSA_TAG_LAN9645X_H_ */
diff --git a/include/net/dsa.h b/include/net/dsa.h
index 5d12191b6f6f..09f3043d57f0 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -62,6 +62,7 @@ struct tc_action;
#define DSA_TAG_PROTO_KSZ8463_VALUE 34
#define DSA_TAG_PROTO_MT7628_VALUE 35
#define DSA_TAG_PROTO_KS8995_VALUE 36
+#define DSA_TAG_PROTO_LAN9645X_VALUE 37
enum dsa_tag_protocol {
DSA_TAG_PROTO_NONE = DSA_TAG_PROTO_NONE_VALUE,
@@ -101,6 +102,7 @@ enum dsa_tag_protocol {
DSA_TAG_PROTO_KSZ8463 = DSA_TAG_PROTO_KSZ8463_VALUE,
DSA_TAG_PROTO_MT7628 = DSA_TAG_PROTO_MT7628_VALUE,
DSA_TAG_PROTO_KS8995 = DSA_TAG_PROTO_KS8995_VALUE,
+ DSA_TAG_PROTO_LAN9645X = DSA_TAG_PROTO_LAN9645X_VALUE,
};
struct dsa_switch;
diff --git a/net/dsa/Kconfig b/net/dsa/Kconfig
index 4f44bf3ede23..bdeb60af764b 100644
--- a/net/dsa/Kconfig
+++ b/net/dsa/Kconfig
@@ -137,6 +137,17 @@ config NET_DSA_TAG_KSZ
Say Y if you want to enable support for tagging frames for the
Microchip 8795/937x/9477/9893 families of switches.
+config NET_DSA_TAG_LAN9645X
+ tristate "Tag driver for Microchip LAN9645x switches"
+ help
+ Say Y or M if you want to enable NPI tagging for the Microchip
+ LAN9645x switches. In this mode, the frames over the Ethernet CPU
+ port are prepended with a hardware-defined injection/extraction frame
+ header. On injection a 28 byte internal frame header (IFH) is used.
+ On extraction a 16 byte prefix is prepended before the internal frame
+ header. This prefix starts with a broadcast MAC, to ease passage
+ through the host side RX filter.
+
config NET_DSA_TAG_NETC
tristate "Tag driver for NXP NETC switches"
help
diff --git a/net/dsa/Makefile b/net/dsa/Makefile
index 1f9cc30e9988..3cd53124448d 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_KS8995) += tag_ks8995.o
obj-$(CONFIG_NET_DSA_TAG_KSZ) += tag_ksz.o
obj-$(CONFIG_NET_DSA_TAG_LAN9303) += tag_lan9303.o
+obj-$(CONFIG_NET_DSA_TAG_LAN9645X) += tag_lan9645x.o
obj-$(CONFIG_NET_DSA_TAG_MT7628) += tag_mt7628.o
obj-$(CONFIG_NET_DSA_TAG_MTK) += tag_mtk.o
obj-$(CONFIG_NET_DSA_TAG_MXL_862XX) += tag_mxl862xx.o
diff --git a/net/dsa/tag_lan9645x.c b/net/dsa/tag_lan9645x.c
new file mode 100644
index 000000000000..f54646d4b394
--- /dev/null
+++ b/net/dsa/tag_lan9645x.c
@@ -0,0 +1,474 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2026 Microchip Technology Inc.
+ */
+
+#include <linux/dsa/lan9645x.h>
+
+#include "tag.h"
+
+#define LAN9645X_NAME "lan9645x"
+
+/* The internal frame header (IFH) is 28 bytes, and the fields are documented
+ * below. Some fields are only used on either injection or extraction.
+ *
+ * Injection header
+ */
+#define IFH_INJ_TIMESTAMP 192
+#define IFH_BYPASS 191
+#define IFH_MASQ 190
+/* Extraction header */
+#define IFH_TIMESTAMP_NS 194
+#define IFH_TIMESTAMP_SUBNS 186
+/* Injection header */
+#define IFH_MASQ_PORT 186
+#define IFH_RCT_INJ 185
+/* Extraction header */
+#define IFH_LEN 171
+#define IFH_WRDMODE 169
+/* Extraction/Injection header */
+#define IFH_RTAGD 167
+/* Extraction header */
+#define IFH_CUTTHRU 166
+/* Extraction/Injection header */
+#define IFH_REW_CMD 156
+#define IFH_REW_OAM 155
+#define IFH_PDU_TYPE 151
+#define IFH_FCS_UPD 150
+#define IFH_DP 149
+/* Reserved */
+#define IFH_RTE_INB_UPDATE 148
+/* Extraction/Injection header */
+#define IFH_POP_CNT 146
+#define IFH_ETYPE_OFS 144
+/* Extraction header */
+#define IFH_SRCPORT 140
+/* Extraction/Injection header */
+#define IFH_SEQ_NUM 120
+#define IFH_TAG_TYPE 119
+#define IFH_TCI 103
+#define IFH_DSCP 97
+#define IFH_QOS_CLASS 94
+#define IFH_CPUQ 86
+/* Extraction header */
+#define IFH_LEARN_FLAGS 84
+/* Extraction/Injection header */
+#define IFH_SFLOW_ID 80
+#define IFH_ACL_HIT 79
+#define IFH_ACL_IDX 73
+#define IFH_ISDX 65
+#define IFH_DSTS 55
+/* Extraction header */
+#define IFH_FLOOD 53
+/* Extraction/Injection header */
+#define IFH_SEQ_OP 51
+#define IFH_IPV 48
+/* Injection header */
+#define IFH_AFI 47
+/* Reserved */
+#define IFH_RTP_ID 37
+#define IFH_RTP_SUBID 36
+#define IFH_PN_DATA_STATUS 28
+#define IFH_PN_TRANSF_STATUS_ZERO 27
+#define IFH_PN_CC 11
+/* Extraction/Injection header */
+#define IFH_DUPL_DISC_ENA 10
+/* Extraction header */
+#define IFH_RCT_AVAIL 9
+
+#define IFH_INJ_TIMESTAMP_SZ 32
+#define IFH_BYPASS_SZ 1
+#define IFH_MASQ_SZ 1
+#define IFH_TIMESTAMP_NS_SZ 30
+#define IFH_TIMESTAMP_SUBNS_SZ 8
+#define IFH_MASQ_PORT_SZ 4
+#define IFH_RCT_INJ_SZ 1
+#define IFH_LEN_SZ 14
+#define IFH_WRDMODE_SZ 2
+#define IFH_RTAGD_SZ 2
+#define IFH_CUTTHRU_SZ 1
+#define IFH_REW_CMD_SZ 10
+#define IFH_REW_OAM_SZ 1
+#define IFH_PDU_TYPE_SZ 4
+#define IFH_FCS_UPD_SZ 1
+#define IFH_DP_SZ 1
+#define IFH_RTE_INB_UPDATE_SZ 1
+#define IFH_POP_CNT_SZ 2
+#define IFH_ETYPE_OFS_SZ 2
+#define IFH_SRCPORT_SZ 4
+#define IFH_SEQ_NUM_SZ 16
+#define IFH_TAG_TYPE_SZ 1
+#define IFH_TCI_SZ 16
+#define IFH_DSCP_SZ 6
+#define IFH_QOS_CLASS_SZ 3
+#define IFH_CPUQ_SZ 8
+#define IFH_LEARN_FLAGS_SZ 2
+#define IFH_SFLOW_ID_SZ 4
+#define IFH_ACL_HIT_SZ 1
+#define IFH_ACL_IDX_SZ 6
+#define IFH_ISDX_SZ 8
+#define IFH_DSTS_SZ 10
+#define IFH_FLOOD_SZ 2
+#define IFH_SEQ_OP_SZ 2
+#define IFH_IPV_SZ 3
+#define IFH_AFI_SZ 1
+#define IFH_RTP_ID_SZ 10
+#define IFH_RTP_SUBID_SZ 1
+#define IFH_PN_DATA_STATUS_SZ 8
+#define IFH_PN_TRANSF_STATUS_ZERO_SZ 1
+#define IFH_PN_CC_SZ 16
+#define IFH_DUPL_DISC_ENA_SZ 1
+#define IFH_RCT_AVAIL_SZ 1
+
+static __always_inline void lan9645x_ifh_merge_byte(u8 *dst, u8 src, u8 mask)
+{
+ *dst = *dst ^ ((*dst ^ src) & mask);
+}
+
+/* The internal frame header (IFH) is a big-endian 28 byte unpadded bit array.
+ * Frames can be prepended with an IFH on injection and extraction. There
+ * are two field layouts, one for extraction and one for injection.
+ *
+ * IFH bits go from high to low, for instance
+ * ifh[0] = [223:216]
+ * ifh[27] = [7:0]
+ *
+ * Here is an example of setting a value starting at bit 13 of bit length 17.
+ *
+ * val = 0x1ff
+ * pos = 13
+ * length = 17
+ *
+ *
+ * IFH[] 0 23 24 25 26 27
+ *
+ * end_u8 start_u8
+ * +--------+----------------+--------+--------+--------+--------+--------+
+ * | | | | | | | |
+ * IFH | | .... | | vvvvvvvvvvvvvvvvvvv | |
+ * | | | | | | | | | |
+ * +--------+----------------+--------+--+-----+--------+--+-----+--------+
+ * Bits 223 39 32 31| 24 23 16 15| 8 7 0
+ * | |
+ * | |
+ * | |
+ * v v
+ * end = 29 pos = 13
+ * end_rem = 5 pos_rem = 5
+ * end_u8 = 3 start_u8 = 1
+ * GENMASK(5, 0) = 0x3f GENMASK(7, 5) = 0xe0
+ *
+ *
+ * In end_u8 and start_u8 we must merge the existing IFH byte with the new
+ * value. In the 'middle' bytes of the value we can overwrite the corresponding
+ * IFH byte.
+ */
+static __always_inline void lan9645x_ifh_set(u8 *ifh, u32 val, size_t pos,
+ size_t length)
+{
+ size_t end = (pos + length) - 1;
+ size_t end_rem = end & 0x7;
+ size_t pos_rem = pos & 0x7;
+ size_t start_u8 = pos >> 3;
+ size_t end_u8 = end >> 3;
+ u8 end_mask, start_mask;
+ size_t vshift;
+ u8 *ptr;
+
+ BUILD_BUG_ON_MSG(length > 32, "IFH field size wider than 32.");
+ BUILD_BUG_ON_MSG(length == 0, "IFH field size of 0.");
+ BUILD_BUG_ON_MSG(pos + length > LAN9645X_IFH_BITS,
+ "IFH field overflows IFH");
+
+ end_mask = GENMASK(end_rem, 0);
+ start_mask = GENMASK(7, pos_rem);
+
+ ptr = &ifh[LAN9645X_IFH_LEN_BYTES - 1 - end_u8];
+
+ if (end_u8 == start_u8)
+ return lan9645x_ifh_merge_byte(ptr, val << pos_rem,
+ end_mask & start_mask);
+
+ vshift = length - end_rem - 1;
+ lan9645x_ifh_merge_byte(ptr++, val >> vshift, end_mask);
+
+ for (size_t j = 1; j < end_u8 - start_u8; j++) {
+ vshift -= 8;
+ *ptr++ = val >> vshift;
+ }
+
+ lan9645x_ifh_merge_byte(ptr, val << pos_rem, start_mask);
+}
+
+static __always_inline u32 lan9645x_ifh_get(const u8 *ifh, size_t pos,
+ size_t length)
+{
+ size_t end = (pos + length) - 1;
+ size_t end_rem = end & 0x7;
+ size_t pos_rem = pos & 0x7;
+ size_t start_u8 = pos >> 3;
+ size_t end_u8 = end >> 3;
+ u8 end_mask, start_mask;
+ const u8 *ptr;
+ u32 val;
+
+ BUILD_BUG_ON_MSG(length > 32, "IFH field size wider than 32.");
+ BUILD_BUG_ON_MSG(length == 0, "IFH field size of 0.");
+ BUILD_BUG_ON_MSG(pos + length > LAN9645X_IFH_BITS,
+ "IFH field overflows IFH");
+
+ end_mask = GENMASK(end_rem, 0);
+ start_mask = GENMASK(7, pos_rem);
+
+ ptr = &ifh[LAN9645X_IFH_LEN_BYTES - 1 - end_u8];
+
+ if (end_u8 == start_u8)
+ return (*ptr & end_mask & start_mask) >> pos_rem;
+
+ val = *ptr++ & end_mask;
+
+ for (size_t j = 1; j < end_u8 - start_u8; j++)
+ val = val << 8 | *ptr++;
+
+ return val << (8 - pos_rem) | (*ptr & start_mask) >> pos_rem;
+}
+
+static struct sk_buff *lan9645x_xmit_get_vlan_info(struct sk_buff *skb,
+ struct net_device *br,
+ u32 *vlan_tci,
+ u32 *tag_type)
+{
+ struct vlan_ethhdr *hdr;
+ u16 proto, tci;
+
+ /* If the VLAN tag is in the hwaccel area, move it to the payload so
+ * that both cases are handled uniformly below, and so that the conduit
+ * cannot insert it into the middle of the IFH we are about to prepend.
+ */
+ if (unlikely(skb_vlan_tag_present(skb))) {
+ skb = __vlan_hwaccel_push_inside(skb);
+ if (!skb)
+ return NULL;
+ }
+
+ if (!br || !br_vlan_enabled(br)) {
+ *vlan_tci = 0;
+ *tag_type = LAN9645X_IFH_TAG_TYPE_C;
+ return skb;
+ }
+
+ hdr = skb_vlan_eth_hdr(skb);
+ br_vlan_get_proto(br, &proto);
+
+ if (skb_headlen(skb) >= VLAN_ETH_HLEN &&
+ ntohs(hdr->h_vlan_proto) == proto) {
+ vlan_remove_tag(skb, &tci);
+ *vlan_tci = tci;
+ } else {
+ rcu_read_lock();
+ br_vlan_get_pvid_rcu(br, &tci);
+ rcu_read_unlock();
+ *vlan_tci = tci;
+ }
+
+ *tag_type = (proto != ETH_P_8021Q) ? LAN9645X_IFH_TAG_TYPE_S :
+ LAN9645X_IFH_TAG_TYPE_C;
+
+ return skb;
+}
+
+static void lan9645x_offload_fwd_mark(struct sk_buff *skb, u32 cpuq)
+{
+ /* Trapped frames must be forwarded by the stack. */
+ if (cpuq & BIT(LAN9645X_CPUQ_TRAP)) {
+ skb->offload_fwd_mark = 0;
+ return;
+ }
+
+ dsa_default_offload_fwd_mark(skb);
+}
+
+static struct sk_buff *lan9645x_xmit(struct sk_buff *skb,
+ struct net_device *ndev)
+{
+ struct dsa_port *dp = dsa_user_to_port(ndev);
+ u32 vlan_tci, tag_type;
+ u32 qos_class;
+ void *ifh;
+
+ skb = lan9645x_xmit_get_vlan_info(skb, dsa_port_bridge_dev_get(dp),
+ &vlan_tci, &tag_type);
+ if (!skb)
+ return NULL;
+
+ /* We need to make sure frame has the proper size after IFH is stripped
+ * by hw.
+ */
+ if (skb_put_padto(skb, ETH_ZLEN))
+ return NULL;
+
+ qos_class = netdev_get_num_tc(ndev) ?
+ netdev_get_prio_tc_map(ndev, skb->priority) :
+ skb->priority;
+ qos_class = min_t(u32, qos_class, GENMASK(IFH_QOS_CLASS_SZ - 1, 0));
+
+ /* Make room for IFH */
+ ifh = skb_push(skb, LAN9645X_IFH_LEN_BYTES);
+ memset(ifh, 0, LAN9645X_IFH_LEN_BYTES);
+
+ lan9645x_ifh_set(ifh, 1, IFH_BYPASS, IFH_BYPASS_SZ);
+ lan9645x_ifh_set(ifh, tag_type, IFH_TAG_TYPE, IFH_TAG_TYPE_SZ);
+ lan9645x_ifh_set(ifh, vlan_tci, IFH_TCI, IFH_TCI_SZ);
+ lan9645x_ifh_set(ifh, qos_class, IFH_QOS_CLASS, IFH_QOS_CLASS_SZ);
+ lan9645x_ifh_set(ifh, BIT(dp->index), IFH_DSTS, IFH_DSTS_SZ);
+
+ return skb;
+}
+
+static struct sk_buff *lan9645x_rcv(struct sk_buff *skb,
+ struct net_device *ndev)
+{
+ u32 src_port, qos_class, vlan_tci, popcnt, etype_ofs, cpuq;
+ struct dsa_port *dp;
+ u32 ifh_gap_len = 0;
+ u8 *ifh;
+
+ /* Conduit already consumed DMAC,SMAC,ETYPE from long prefix. Go back
+ * to beginning of frame.
+ */
+ skb_push(skb, ETH_HLEN);
+
+ if (unlikely(!pskb_may_pull(skb, LAN9645X_TOTAL_TAG_LEN))) {
+ kfree_skb(skb);
+ return NULL;
+ }
+
+ /* IFH starts after our long prefix */
+ ifh = skb_pull(skb, LAN9645X_LONG_PREFIX_LEN);
+
+ popcnt = lan9645x_ifh_get(ifh, IFH_POP_CNT, IFH_POP_CNT_SZ);
+ etype_ofs = lan9645x_ifh_get(ifh, IFH_ETYPE_OFS, IFH_ETYPE_OFS_SZ);
+ src_port = lan9645x_ifh_get(ifh, IFH_SRCPORT, IFH_SRCPORT_SZ);
+ vlan_tci = lan9645x_ifh_get(ifh, IFH_TCI, IFH_TCI_SZ);
+ qos_class = lan9645x_ifh_get(ifh, IFH_QOS_CLASS, IFH_QOS_CLASS_SZ);
+ cpuq = lan9645x_ifh_get(ifh, IFH_CPUQ, IFH_CPUQ_SZ);
+
+ /* Tag pushing is disabled on the NPI port via REW_TAG_CFG, so if this
+ * fires REW_TAG_CFG is misconfigured.
+ */
+ if (popcnt == 1 ||
+ (popcnt == 0 && etype_ofs > 0)) {
+ kfree_skb(skb);
+ return NULL;
+ }
+
+ /* Since REW_PORT_CFG_NO_REWRITE=0 is required on the NPI port, we need
+ * to account for any tags popped by the hardware, as that will leave a
+ * gap between the IFH and DMAC. Tag pushing is disabled.
+ *
+ * The IFH fields do not have intuitive values. This is how HW does the
+ * calculation:
+ *
+ * DMAC_DT = (ifh.pop_cnt == 0 && ifh.etype_ofs == 0) ? 4 : ifh.pop_cnt
+ * DMAC_OFFSET = TAG_SIZE + 4*(DMAC_DT - 2)
+ *
+ * With tag pushing disabled we have either
+ *
+ * popcnt=0 and etype_ofs=0 => 2x pop
+ * popcnt=3 and etype_ofs=* => 1x pop
+ * popcnt=2 and etype_ofs=* => no pop
+ *
+ * The remaining combinations indicate a push and will not occur.
+ */
+ if (popcnt == 0 && etype_ofs == 0)
+ ifh_gap_len = 2 * VLAN_HLEN;
+ else if (popcnt == 3)
+ ifh_gap_len = VLAN_HLEN;
+
+ /* Set skb->data at start of real header */
+ skb_pull(skb, LAN9645X_IFH_LEN_BYTES);
+
+ if (unlikely(!pskb_may_pull(skb, ifh_gap_len + ETH_HLEN))) {
+ kfree_skb(skb);
+ return NULL;
+ }
+
+ skb_pull(skb, ifh_gap_len);
+ skb_reset_mac_header(skb);
+ skb_set_network_header(skb, ETH_HLEN);
+ skb_reset_mac_len(skb);
+
+ /* Reset skb->data past the actual ethernet header. */
+ skb_pull(skb, ETH_HLEN);
+
+ /* We must deliver the skb so skb->csum only covers the data beyond the
+ * real ethernet header. The fake ethernet header in the prefix is
+ * not part of skb->csum already. We must subtract what remains of the
+ * prefix, the ifh and the gap. The start is derived from the current
+ * skb->data rather than saved on entry, because the pskb_may_pull()
+ * calls above may have reallocated skb->head.
+ */
+ skb_postpull_rcsum(skb,
+ skb->data - LAN9645X_TOTAL_TAG_LEN - ifh_gap_len,
+ LAN9645X_TOTAL_TAG_LEN + ifh_gap_len);
+
+ skb->dev = dsa_conduit_find_user(ndev, 0, src_port);
+ if (!skb->dev) {
+ /* Reflection is disabled for frames from the tag driver itself,
+ * however it is possible that a frame sent directly on the
+ * conduit gets reflected, so we drop it here.
+ */
+ kfree_skb(skb);
+ return NULL;
+ }
+
+ lan9645x_offload_fwd_mark(skb, cpuq);
+
+ skb->priority = qos_class;
+
+ /* While we have REW_PORT_CFG_NO_REWRITE=0 on the NPI port, we still
+ * disable port VLAN tag pushing with REW_TAG_CFG. A frame ingressing
+ * on a vlan aware port, which is forwarded to the CPU, will not carry
+ * vlan info in the frame data, because the tag is popped. The
+ * classified VID is only communicated via the IFH, never in the
+ * payload. We therefore restore it via hwaccel and must not pop an
+ * in-band tag here.
+ */
+ dp = dsa_user_to_port(skb->dev);
+
+ if (dsa_port_is_vlan_filtering(dp) && vlan_tci) {
+ u16 port_pvid = 0;
+
+ br_vlan_get_pvid_rcu(skb->dev, &port_pvid);
+
+ /* The tag is restored as a C-tag, not as the TAG_TYPE the IFH
+ * reports. The classifier recognizes both TPIDs as VLAN tags,
+ * so an S-tag has already been used for classification by the
+ * time we get here. Restoring it as 802.1AD would make the
+ * bridge push it back into the payload and reclassify the frame
+ * to the port pvid, on a different VID than the one the
+ * hardware forwarded it on.
+ */
+ if ((vlan_tci & VLAN_VID_MASK) != port_pvid)
+ __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q),
+ vlan_tci);
+ }
+
+ return skb;
+}
+
+static const struct dsa_device_ops lan9645x_netdev_ops = {
+ .name = LAN9645X_NAME,
+ .proto = DSA_TAG_PROTO_LAN9645X,
+ .xmit = lan9645x_xmit,
+ .rcv = lan9645x_rcv,
+ /* Covers the extraction prefix too, since dsa_tag_protocol_overhead()
+ * sizes the conduit MTU from this.
+ */
+ .needed_headroom = LAN9645X_TOTAL_TAG_LEN,
+};
+
+MODULE_DESCRIPTION("DSA tag driver for LAN9645x family of switches, using NPI port");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_LAN9645X, LAN9645X_NAME);
+
+module_dsa_tag_driver(lan9645x_netdev_ops);
--
2.52.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH net-next v13 2/9] dt-bindings: net: lan9645x: add LAN9645X switch bindings
2026-09-29 7:48 [PATCH net-next v13 0/9] net: dsa: add DSA support for the LAN9645x switch chip family Jens Emil Schulz Østergaard
2026-09-29 7:48 ` [PATCH net-next v13 1/9] net: dsa: add tag driver for LAN9645X Jens Emil Schulz Østergaard
@ 2026-09-29 7:48 ` Jens Emil Schulz Østergaard
2026-10-02 21:14 ` netdev-bot+sashiko
2026-09-29 7:48 ` [PATCH net-next v13 3/9] net: dsa: lan9645x: add autogenerated register macros Jens Emil Schulz Østergaard
` (6 subsequent siblings)
8 siblings, 1 reply; 18+ messages in thread
From: Jens Emil Schulz Østergaard @ 2026-09-29 7:48 UTC (permalink / raw)
To: UNGLinuxDriver, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Woojung Huh,
Russell King, Steen Hegelund, Daniel Machon, Geert Uytterhoeven,
Magnus Damm
Cc: linux-kernel, netdev, devicetree, linux-renesas-soc,
Jens Emil Schulz Østergaard
Add bindings for LAN9645X switch. We use a fallback compatible for the
smallest SKU microchip,lan96455s-switch. The last digit of the part number
is the number of usable front ports and the letter is the feature set.
The switch is a child of an externally controlled parent that hands out
one regmap per register target, so reg and reg-names describe the target
layout the way mscc,vsc7514-switch.yaml does for the equivalent
mscc,vsc7512-switch node. That binding, and the mfd parent it hangs off
in mscc,ocelot.yaml, is the model this driver and its parent follow.
Reviewed-by: Steen Hegelund <Steen.Hegelund@microchip.com>
Signed-off-by: Jens Emil Schulz Østergaard <jensemil.schulzostergaard@microchip.com>
---
Changes in v13:
- Remove section quoting Documentation/networking/phy.rst from commit
message.
- Remove rx/tx-internal-delay-ps from example.
Changes in v12:
- Remove Rob's Reviewed-by
- Match both the port@ and ethernet-port@ spellings, so the per port
constraints apply. dsa.yaml accepts either, and the local pattern only
matched the long form.
- Allow 0 as well as 2000 for rx/tx-internal-delay-ps, matching what the
driver accepts, and restrict them to those two values on RGMII ports.
- Constrain the ethernet-ports node the way renesas,rzn1-a5psw.yaml does,
with additionalProperties instead of a local dsa-port.yaml $ref.
- Put the example properties in canonical order, so dt-check-style
--mode=strict is clean.
- Describe the switch register targets in reg, with a matching reg-names,
following mscc,vsc7514-switch.yaml.
- Document the default of rx/tx-internal-delay-ps, as
microchip,lan937x.yaml does.
- Describe the SKU naming in the binding description.
Changes in v5:
- No changes.
Changes in v4:
- No changes.
Changes in v3:
- remove additionalProperties: true
- remove unnecessary | from description
- change top level $ref to dsa.yaml#/$defs/ethernet-ports
- use ethernet-ports and ethernet-port
- move ethernet-ports under properties instead of patternProperties
- move unevaluatedProperties: false after $ref
- update example to use ethernet-ports and ethernet-port
Changes in v2:
- rename file to microchip,lan96455s-switch.yaml
- remove led vendor property
- add {rx,tx}-internal-delay-ps for rgmii delay
- remove labels from example
- remove container node from example
---
.../net/dsa/microchip,lan96455s-switch.yaml | 177 +++++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 178 insertions(+)
diff --git a/Documentation/devicetree/bindings/net/dsa/microchip,lan96455s-switch.yaml b/Documentation/devicetree/bindings/net/dsa/microchip,lan96455s-switch.yaml
new file mode 100644
index 000000000000..9deb7a427804
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/dsa/microchip,lan96455s-switch.yaml
@@ -0,0 +1,177 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/dsa/microchip,lan96455s-switch.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Microchip LAN9645x Ethernet switch
+
+maintainers:
+ - Jens Emil Schulz Østergaard <jensemil.schulzostergaard@microchip.com>
+
+description: |
+ The LAN9645x switch is a multi-port Gigabit AVB/TSN Ethernet switch with
+ five integrated 10/100/1000Base-T PHYs. In addition to the integrated PHYs,
+ it supports up to 2 RGMII/RMII, up to 2 BASE-X/SERDES/2.5GBASE-X and one
+ Quad-SGMII interfaces.
+
+ The part number encodes the SKU. The last digit is the number of usable
+ front ports, 5, 7 or 9. The letter is the feature set, f for full and s for
+ standard, where full adds HSR, PRP, TAS, frame preemption and PSFP.
+
+properties:
+ compatible:
+ oneOf:
+ - enum:
+ - microchip,lan96455s-switch
+ - items:
+ - enum:
+ - microchip,lan96455f-switch
+ - microchip,lan96457f-switch
+ - microchip,lan96459f-switch
+ - microchip,lan96457s-switch
+ - microchip,lan96459s-switch
+ - const: microchip,lan96455s-switch
+
+ reg:
+ items:
+ - description: General configuration block target
+ - description:
+ CPU device queue system target, the register based frame injection
+ and extraction interface
+ - description: Chip top level target
+ - description: Rewriter target
+ - description: Switching engine target
+ - description: HSIO target
+ - description: Port 0 device target
+ - description: Port 1 device target
+ - description: Port 2 device target
+ - description: Port 3 device target
+ - description: Port 4 device target
+ - description: Port 5 device target
+ - description: Port 6 device target
+ - description: Port 7 device target
+ - description: Port 8 device target
+ - description: Queue system target
+ - description: Analyzer target
+
+ reg-names:
+ items:
+ - const: gcb
+ - const: qs
+ - const: chip_top
+ - const: rew
+ - const: sys
+ - const: hsio
+ - const: dev0
+ - const: dev1
+ - const: dev2
+ - const: dev3
+ - const: dev4
+ - const: dev5
+ - const: dev6
+ - const: dev7
+ - const: dev8
+ - const: qsys
+ - const: ana
+
+ ethernet-ports:
+ type: object
+ additionalProperties: true
+ patternProperties:
+ "^(ethernet-)?port@[0-8]$":
+ type: object
+ description: Ethernet switch ports
+ additionalProperties: true
+
+ allOf:
+ - if:
+ properties:
+ phy-mode:
+ contains:
+ enum:
+ - rgmii
+ - rgmii-rxid
+ - rgmii-txid
+ - rgmii-id
+ then:
+ properties:
+ rx-internal-delay-ps:
+ $ref: "#/$defs/internal-delay-ps"
+ tx-internal-delay-ps:
+ $ref: "#/$defs/internal-delay-ps"
+
+$ref: dsa.yaml#/$defs/ethernet-ports
+
+required:
+ - compatible
+ - reg
+ - reg-names
+ - ethernet-ports
+
+unevaluatedProperties: false
+
+$defs:
+ internal-delay-ps:
+ description:
+ Disable the delay line using 0 ps, or enable the 2000 ps delay. The
+ delay line is not tunable, so no other phase can be selected.
+ enum: [0, 2000]
+ default: 0
+
+examples:
+ - |
+ ethernet-switch@4000 {
+ compatible = "microchip,lan96459f-switch", "microchip,lan96455s-switch";
+ reg = <0x4000 0x244>, <0x8000 0x4c>, <0x10000 0x134>,
+ <0x18000 0x1910>, <0x2c000 0x12d4>, <0x30000 0xe4>,
+ <0x38000 0xb8>, <0x3c000 0xb8>, <0x40000 0xb8>,
+ <0x44000 0xb8>, <0x48000 0xb8>, <0x4c000 0xb8>,
+ <0x50000 0xb8>, <0x54000 0xb8>, <0x58000 0xb8>,
+ <0x100000 0xa000>, <0x140000 0x7468>;
+ reg-names = "gcb", "qs", "chip_top", "rew", "sys", "hsio", "dev0",
+ "dev1", "dev2", "dev3", "dev4", "dev5", "dev6", "dev7",
+ "dev8", "qsys", "ana";
+
+ ethernet-ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ethernet-port@0 {
+ reg = <0>;
+ phy-handle = <&cuphy0>;
+ phy-mode = "gmii";
+ };
+
+ ethernet-port@1 {
+ reg = <1>;
+ phy-handle = <&cuphy1>;
+ phy-mode = "gmii";
+ };
+
+ ethernet-port@2 {
+ reg = <2>;
+ phy-handle = <&cuphy2>;
+ phy-mode = "gmii";
+ };
+
+ ethernet-port@3 {
+ reg = <3>;
+ phy-handle = <&cuphy3>;
+ phy-mode = "gmii";
+ };
+
+ ethernet-port@7 {
+ reg = <7>;
+ ethernet = <&cpu_host_port>;
+ phy-mode = "rgmii";
+
+ fixed-link {
+ full-duplex;
+ pause;
+ speed = <1000>;
+ };
+ };
+ };
+ };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index e2cbcf89d36e..d9632ba3419a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17805,6 +17805,7 @@ M: Jens Emil Schulz Østergaard <jensemil.schulzostergaard@microchip.com>
M: UNGLinuxDriver@microchip.com
L: netdev@vger.kernel.org
S: Maintained
+F: Documentation/devicetree/bindings/net/dsa/microchip,lan96455s-switch.yaml
F: include/linux/dsa/lan9645x.h
F: net/dsa/tag_lan9645x.c
--
2.52.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH net-next v13 3/9] net: dsa: lan9645x: add autogenerated register macros
2026-09-29 7:48 [PATCH net-next v13 0/9] net: dsa: add DSA support for the LAN9645x switch chip family Jens Emil Schulz Østergaard
2026-09-29 7:48 ` [PATCH net-next v13 1/9] net: dsa: add tag driver for LAN9645X Jens Emil Schulz Østergaard
2026-09-29 7:48 ` [PATCH net-next v13 2/9] dt-bindings: net: lan9645x: add LAN9645X switch bindings Jens Emil Schulz Østergaard
@ 2026-09-29 7:48 ` Jens Emil Schulz Østergaard
2026-09-29 7:48 ` [PATCH net-next v13 4/9] net: dsa: lan9645x: add basic dsa driver for LAN9645X Jens Emil Schulz Østergaard
` (5 subsequent siblings)
8 siblings, 0 replies; 18+ messages in thread
From: Jens Emil Schulz Østergaard @ 2026-09-29 7:48 UTC (permalink / raw)
To: UNGLinuxDriver, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Woojung Huh,
Russell King, Steen Hegelund, Daniel Machon, Geert Uytterhoeven,
Magnus Damm
Cc: linux-kernel, netdev, devicetree, linux-renesas-soc,
Jens Emil Schulz Østergaard
Add autogenerated register macros and update MAINTAINERS file. The
register macros are generated using the same tool we use for lan966x,
sparx5 and lan969x.
Reviewed-by: Steen Hegelund <Steen.Hegelund@microchip.com>
Signed-off-by: Jens Emil Schulz Østergaard <jensemil.schulzostergaard@microchip.com>
---
Changes in v13:
- Add QS:XTR:XTR_GRP_CFG for extraction mode configuration.
Changes in v12:
- Regenerate without the unused AFI target.
Changes in v7:
- Add ANA:COMMON:CPUQ_8021_CFG for bpdu cpu queue configuration.
Changes in v3:
- No changes
Changes in v2:
- Add cpuq registers
---
MAINTAINERS | 1 +
drivers/net/dsa/microchip/lan9645x/lan9645x_regs.h | 1932 ++++++++++++++++++++
2 files changed, 1933 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index d9632ba3419a..75cabf00f0d8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17806,6 +17806,7 @@ M: UNGLinuxDriver@microchip.com
L: netdev@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/net/dsa/microchip,lan96455s-switch.yaml
+F: drivers/net/dsa/microchip/lan9645x/*
F: include/linux/dsa/lan9645x.h
F: net/dsa/tag_lan9645x.c
diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_regs.h b/drivers/net/dsa/microchip/lan9645x/lan9645x_regs.h
new file mode 100644
index 000000000000..0414c7239103
--- /dev/null
+++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_regs.h
@@ -0,0 +1,1932 @@
+/* SPDX-License-Identifier: GPL-2.0+
+ *
+ * Copyright (c) 2026 Microchip Technology Inc.
+ */
+
+/* This file is autogenerated by cml-utils 2026-09-28 15:07:47 +0200.
+ * Commit ID: 725d258adf97fd69178153a51b7fc60f2a4b5988
+ */
+
+#ifndef _LAN9645X_REGS_H_
+#define _LAN9645X_REGS_H_
+
+#include <linux/bitfield.h>
+#include <linux/types.h>
+#include <linux/bug.h>
+
+enum lan9645x_target {
+ TARGET_ANA = 1,
+ TARGET_CHIP_TOP = 2,
+ TARGET_DEV = 5,
+ TARGET_GCB = 16,
+ TARGET_HSIO = 17,
+ TARGET_QS = 26,
+ TARGET_QSYS = 27,
+ TARGET_REW = 28,
+ TARGET_SYS = 29,
+ NUM_TARGETS = 38
+};
+
+#define __REG(...) __VA_ARGS__
+
+/* ANA:ANA:ADVLEARN */
+#define ANA_ADVLEARN __REG(TARGET_ANA,\
+ 0, 1, 27136, 0, 1, 284, 0, 0, 1, 4)
+
+#define ANA_ADVLEARN_VLAN_CHK BIT(0)
+#define ANA_ADVLEARN_VLAN_CHK_SET(x)\
+ FIELD_PREP(ANA_ADVLEARN_VLAN_CHK, x)
+#define ANA_ADVLEARN_VLAN_CHK_GET(x)\
+ FIELD_GET(ANA_ADVLEARN_VLAN_CHK, x)
+
+/* ANA:ANA:VLANMASK */
+#define ANA_VLANMASK __REG(TARGET_ANA,\
+ 0, 1, 27136, 0, 1, 284, 8, 0, 1, 4)
+
+#define ANA_VLANMASK_VLANMASK GENMASK(9, 0)
+#define ANA_VLANMASK_VLANMASK_SET(x)\
+ FIELD_PREP(ANA_VLANMASK_VLANMASK, x)
+#define ANA_VLANMASK_VLANMASK_GET(x)\
+ FIELD_GET(ANA_VLANMASK_VLANMASK, x)
+
+/* ANA:ANA:ANAGEFIL */
+#define ANA_ANAGEFIL __REG(TARGET_ANA,\
+ 0, 1, 27136, 0, 1, 284, 12, 0, 1, 4)
+
+#define ANA_ANAGEFIL_AGE_LOCKED BIT(20)
+#define ANA_ANAGEFIL_AGE_LOCKED_SET(x)\
+ FIELD_PREP(ANA_ANAGEFIL_AGE_LOCKED, x)
+#define ANA_ANAGEFIL_AGE_LOCKED_GET(x)\
+ FIELD_GET(ANA_ANAGEFIL_AGE_LOCKED, x)
+
+#define ANA_ANAGEFIL_PID_EN BIT(19)
+#define ANA_ANAGEFIL_PID_EN_SET(x)\
+ FIELD_PREP(ANA_ANAGEFIL_PID_EN, x)
+#define ANA_ANAGEFIL_PID_EN_GET(x)\
+ FIELD_GET(ANA_ANAGEFIL_PID_EN, x)
+
+#define ANA_ANAGEFIL_PID_VAL GENMASK(18, 14)
+#define ANA_ANAGEFIL_PID_VAL_SET(x)\
+ FIELD_PREP(ANA_ANAGEFIL_PID_VAL, x)
+#define ANA_ANAGEFIL_PID_VAL_GET(x)\
+ FIELD_GET(ANA_ANAGEFIL_PID_VAL, x)
+
+#define ANA_ANAGEFIL_VID_EN BIT(13)
+#define ANA_ANAGEFIL_VID_EN_SET(x)\
+ FIELD_PREP(ANA_ANAGEFIL_VID_EN, x)
+#define ANA_ANAGEFIL_VID_EN_GET(x)\
+ FIELD_GET(ANA_ANAGEFIL_VID_EN, x)
+
+#define ANA_ANAGEFIL_VID_VAL GENMASK(12, 0)
+#define ANA_ANAGEFIL_VID_VAL_SET(x)\
+ FIELD_PREP(ANA_ANAGEFIL_VID_VAL, x)
+#define ANA_ANAGEFIL_VID_VAL_GET(x)\
+ FIELD_GET(ANA_ANAGEFIL_VID_VAL, x)
+
+/* ANA:ANA:AUTOAGE */
+#define ANA_AUTOAGE __REG(TARGET_ANA,\
+ 0, 1, 27136, 0, 1, 284, 44, 0, 1, 4)
+
+#define ANA_AUTOAGE_AGE_FAST BIT(21)
+#define ANA_AUTOAGE_AGE_FAST_SET(x)\
+ FIELD_PREP(ANA_AUTOAGE_AGE_FAST, x)
+#define ANA_AUTOAGE_AGE_FAST_GET(x)\
+ FIELD_GET(ANA_AUTOAGE_AGE_FAST, x)
+
+#define ANA_AUTOAGE_AGE_PERIOD GENMASK(20, 1)
+#define ANA_AUTOAGE_AGE_PERIOD_SET(x)\
+ FIELD_PREP(ANA_AUTOAGE_AGE_PERIOD, x)
+#define ANA_AUTOAGE_AGE_PERIOD_GET(x)\
+ FIELD_GET(ANA_AUTOAGE_AGE_PERIOD, x)
+
+#define ANA_AUTOAGE_AUTOAGE_LOCKED BIT(0)
+#define ANA_AUTOAGE_AUTOAGE_LOCKED_SET(x)\
+ FIELD_PREP(ANA_AUTOAGE_AUTOAGE_LOCKED, x)
+#define ANA_AUTOAGE_AUTOAGE_LOCKED_GET(x)\
+ FIELD_GET(ANA_AUTOAGE_AUTOAGE_LOCKED, x)
+
+/* ANA:ANA:FLOODING */
+#define ANA_FLOODING(r) __REG(TARGET_ANA,\
+ 0, 1, 27136, 0, 1, 284, 68, r, 8, 4)
+
+#define ANA_FLOODING_FLD_UNICAST GENMASK(17, 12)
+#define ANA_FLOODING_FLD_UNICAST_SET(x)\
+ FIELD_PREP(ANA_FLOODING_FLD_UNICAST, x)
+#define ANA_FLOODING_FLD_UNICAST_GET(x)\
+ FIELD_GET(ANA_FLOODING_FLD_UNICAST, x)
+
+#define ANA_FLOODING_FLD_BROADCAST GENMASK(11, 6)
+#define ANA_FLOODING_FLD_BROADCAST_SET(x)\
+ FIELD_PREP(ANA_FLOODING_FLD_BROADCAST, x)
+#define ANA_FLOODING_FLD_BROADCAST_GET(x)\
+ FIELD_GET(ANA_FLOODING_FLD_BROADCAST, x)
+
+#define ANA_FLOODING_FLD_MULTICAST GENMASK(5, 0)
+#define ANA_FLOODING_FLD_MULTICAST_SET(x)\
+ FIELD_PREP(ANA_FLOODING_FLD_MULTICAST, x)
+#define ANA_FLOODING_FLD_MULTICAST_GET(x)\
+ FIELD_GET(ANA_FLOODING_FLD_MULTICAST, x)
+
+/* ANA:ANA:FLOODING_IPMC */
+#define ANA_FLOODING_IPMC __REG(TARGET_ANA,\
+ 0, 1, 27136, 0, 1, 284, 100, 0, 1, 4)
+
+#define ANA_FLOODING_IPMC_FLD_MC4_CTRL GENMASK(23, 18)
+#define ANA_FLOODING_IPMC_FLD_MC4_CTRL_SET(x)\
+ FIELD_PREP(ANA_FLOODING_IPMC_FLD_MC4_CTRL, x)
+#define ANA_FLOODING_IPMC_FLD_MC4_CTRL_GET(x)\
+ FIELD_GET(ANA_FLOODING_IPMC_FLD_MC4_CTRL, x)
+
+#define ANA_FLOODING_IPMC_FLD_MC4_DATA GENMASK(17, 12)
+#define ANA_FLOODING_IPMC_FLD_MC4_DATA_SET(x)\
+ FIELD_PREP(ANA_FLOODING_IPMC_FLD_MC4_DATA, x)
+#define ANA_FLOODING_IPMC_FLD_MC4_DATA_GET(x)\
+ FIELD_GET(ANA_FLOODING_IPMC_FLD_MC4_DATA, x)
+
+#define ANA_FLOODING_IPMC_FLD_MC6_CTRL GENMASK(11, 6)
+#define ANA_FLOODING_IPMC_FLD_MC6_CTRL_SET(x)\
+ FIELD_PREP(ANA_FLOODING_IPMC_FLD_MC6_CTRL, x)
+#define ANA_FLOODING_IPMC_FLD_MC6_CTRL_GET(x)\
+ FIELD_GET(ANA_FLOODING_IPMC_FLD_MC6_CTRL, x)
+
+#define ANA_FLOODING_IPMC_FLD_MC6_DATA GENMASK(5, 0)
+#define ANA_FLOODING_IPMC_FLD_MC6_DATA_SET(x)\
+ FIELD_PREP(ANA_FLOODING_IPMC_FLD_MC6_DATA, x)
+#define ANA_FLOODING_IPMC_FLD_MC6_DATA_GET(x)\
+ FIELD_GET(ANA_FLOODING_IPMC_FLD_MC6_DATA, x)
+
+/* ANA:PGID:PGID */
+#define ANA_PGID(g) __REG(TARGET_ANA,\
+ 0, 1, 27648, g, 90, 8, 0, 0, 1, 4)
+
+#define ANA_PGID_PGID GENMASK(9, 0)
+#define ANA_PGID_PGID_SET(x)\
+ FIELD_PREP(ANA_PGID_PGID, x)
+#define ANA_PGID_PGID_GET(x)\
+ FIELD_GET(ANA_PGID_PGID, x)
+
+/* ANA:PGID:PGID_CFG */
+#define ANA_PGID_CFG(g) __REG(TARGET_ANA,\
+ 0, 1, 27648, g, 90, 8, 4, 0, 1, 4)
+
+#define ANA_PGID_CFG_SAN_ENA BIT(4)
+#define ANA_PGID_CFG_SAN_ENA_SET(x)\
+ FIELD_PREP(ANA_PGID_CFG_SAN_ENA, x)
+#define ANA_PGID_CFG_SAN_ENA_GET(x)\
+ FIELD_GET(ANA_PGID_CFG_SAN_ENA, x)
+
+#define ANA_PGID_CFG_CPUQ_DST_PGID GENMASK(3, 1)
+#define ANA_PGID_CFG_CPUQ_DST_PGID_SET(x)\
+ FIELD_PREP(ANA_PGID_CFG_CPUQ_DST_PGID, x)
+#define ANA_PGID_CFG_CPUQ_DST_PGID_GET(x)\
+ FIELD_GET(ANA_PGID_CFG_CPUQ_DST_PGID, x)
+
+#define ANA_PGID_CFG_OBEY_VLAN BIT(0)
+#define ANA_PGID_CFG_OBEY_VLAN_SET(x)\
+ FIELD_PREP(ANA_PGID_CFG_OBEY_VLAN, x)
+#define ANA_PGID_CFG_OBEY_VLAN_GET(x)\
+ FIELD_GET(ANA_PGID_CFG_OBEY_VLAN, x)
+
+/* ANA:ANA_TABLES:MACHDATA */
+#define ANA_MACHDATA __REG(TARGET_ANA,\
+ 0, 1, 23680, 0, 1, 128, 44, 0, 1, 4)
+
+#define ANA_MACHDATA_VID GENMASK(28, 16)
+#define ANA_MACHDATA_VID_SET(x)\
+ FIELD_PREP(ANA_MACHDATA_VID, x)
+#define ANA_MACHDATA_VID_GET(x)\
+ FIELD_GET(ANA_MACHDATA_VID, x)
+
+#define ANA_MACHDATA_MACHDATA GENMASK(15, 0)
+#define ANA_MACHDATA_MACHDATA_SET(x)\
+ FIELD_PREP(ANA_MACHDATA_MACHDATA, x)
+#define ANA_MACHDATA_MACHDATA_GET(x)\
+ FIELD_GET(ANA_MACHDATA_MACHDATA, x)
+
+/* ANA:ANA_TABLES:MACLDATA */
+#define ANA_MACLDATA __REG(TARGET_ANA,\
+ 0, 1, 23680, 0, 1, 128, 48, 0, 1, 4)
+
+/* ANA:ANA_TABLES:MACACCESS */
+#define ANA_MACACCESS __REG(TARGET_ANA,\
+ 0, 1, 23680, 0, 1, 128, 52, 0, 1, 4)
+
+#define ANA_MACACCESS_CHANGE2SW BIT(17)
+#define ANA_MACACCESS_CHANGE2SW_SET(x)\
+ FIELD_PREP(ANA_MACACCESS_CHANGE2SW, x)
+#define ANA_MACACCESS_CHANGE2SW_GET(x)\
+ FIELD_GET(ANA_MACACCESS_CHANGE2SW, x)
+
+#define ANA_MACACCESS_MAC_CPU_COPY BIT(16)
+#define ANA_MACACCESS_MAC_CPU_COPY_SET(x)\
+ FIELD_PREP(ANA_MACACCESS_MAC_CPU_COPY, x)
+#define ANA_MACACCESS_MAC_CPU_COPY_GET(x)\
+ FIELD_GET(ANA_MACACCESS_MAC_CPU_COPY, x)
+
+#define ANA_MACACCESS_SRC_KILL BIT(15)
+#define ANA_MACACCESS_SRC_KILL_SET(x)\
+ FIELD_PREP(ANA_MACACCESS_SRC_KILL, x)
+#define ANA_MACACCESS_SRC_KILL_GET(x)\
+ FIELD_GET(ANA_MACACCESS_SRC_KILL, x)
+
+#define ANA_MACACCESS_IGNORE_VLAN BIT(14)
+#define ANA_MACACCESS_IGNORE_VLAN_SET(x)\
+ FIELD_PREP(ANA_MACACCESS_IGNORE_VLAN, x)
+#define ANA_MACACCESS_IGNORE_VLAN_GET(x)\
+ FIELD_GET(ANA_MACACCESS_IGNORE_VLAN, x)
+
+#define ANA_MACACCESS_AGED_FLAG BIT(13)
+#define ANA_MACACCESS_AGED_FLAG_SET(x)\
+ FIELD_PREP(ANA_MACACCESS_AGED_FLAG, x)
+#define ANA_MACACCESS_AGED_FLAG_GET(x)\
+ FIELD_GET(ANA_MACACCESS_AGED_FLAG, x)
+
+#define ANA_MACACCESS_VALID BIT(12)
+#define ANA_MACACCESS_VALID_SET(x)\
+ FIELD_PREP(ANA_MACACCESS_VALID, x)
+#define ANA_MACACCESS_VALID_GET(x)\
+ FIELD_GET(ANA_MACACCESS_VALID, x)
+
+#define ANA_MACACCESS_ENTRYTYPE GENMASK(11, 10)
+#define ANA_MACACCESS_ENTRYTYPE_SET(x)\
+ FIELD_PREP(ANA_MACACCESS_ENTRYTYPE, x)
+#define ANA_MACACCESS_ENTRYTYPE_GET(x)\
+ FIELD_GET(ANA_MACACCESS_ENTRYTYPE, x)
+
+#define ANA_MACACCESS_DEST_IDX GENMASK(9, 4)
+#define ANA_MACACCESS_DEST_IDX_SET(x)\
+ FIELD_PREP(ANA_MACACCESS_DEST_IDX, x)
+#define ANA_MACACCESS_DEST_IDX_GET(x)\
+ FIELD_GET(ANA_MACACCESS_DEST_IDX, x)
+
+#define ANA_MACACCESS_MAC_TABLE_CMD GENMASK(3, 0)
+#define ANA_MACACCESS_MAC_TABLE_CMD_SET(x)\
+ FIELD_PREP(ANA_MACACCESS_MAC_TABLE_CMD, x)
+#define ANA_MACACCESS_MAC_TABLE_CMD_GET(x)\
+ FIELD_GET(ANA_MACACCESS_MAC_TABLE_CMD, x)
+
+/* ANA:ANA_TABLES:MACTINDX */
+#define ANA_MACTINDX __REG(TARGET_ANA,\
+ 0, 1, 23680, 0, 1, 128, 56, 0, 1, 4)
+
+#define ANA_MACTINDX_BUCKET GENMASK(12, 11)
+#define ANA_MACTINDX_BUCKET_SET(x)\
+ FIELD_PREP(ANA_MACTINDX_BUCKET, x)
+#define ANA_MACTINDX_BUCKET_GET(x)\
+ FIELD_GET(ANA_MACTINDX_BUCKET, x)
+
+#define ANA_MACTINDX_M_INDEX GENMASK(10, 0)
+#define ANA_MACTINDX_M_INDEX_SET(x)\
+ FIELD_PREP(ANA_MACTINDX_M_INDEX, x)
+#define ANA_MACTINDX_M_INDEX_GET(x)\
+ FIELD_GET(ANA_MACTINDX_M_INDEX, x)
+
+/* ANA:ANA_TABLES:VLAN_PORT_MASK */
+#define ANA_VLAN_PORT_MASK __REG(TARGET_ANA,\
+ 0, 1, 23680, 0, 1, 128, 60, 0, 1, 4)
+
+#define ANA_VLAN_PORT_MASK_VLAN_PORT_MASK GENMASK(9, 0)
+#define ANA_VLAN_PORT_MASK_VLAN_PORT_MASK_SET(x)\
+ FIELD_PREP(ANA_VLAN_PORT_MASK_VLAN_PORT_MASK, x)
+#define ANA_VLAN_PORT_MASK_VLAN_PORT_MASK_GET(x)\
+ FIELD_GET(ANA_VLAN_PORT_MASK_VLAN_PORT_MASK, x)
+
+/* ANA:ANA_TABLES:VLANACCESS */
+#define ANA_VLANACCESS __REG(TARGET_ANA,\
+ 0, 1, 23680, 0, 1, 128, 64, 0, 1, 4)
+
+#define ANA_VLANACCESS_VLAN_TBL_CMD GENMASK(1, 0)
+#define ANA_VLANACCESS_VLAN_TBL_CMD_SET(x)\
+ FIELD_PREP(ANA_VLANACCESS_VLAN_TBL_CMD, x)
+#define ANA_VLANACCESS_VLAN_TBL_CMD_GET(x)\
+ FIELD_GET(ANA_VLANACCESS_VLAN_TBL_CMD, x)
+
+/* ANA:ANA_TABLES:VLANTIDX */
+#define ANA_VLANTIDX __REG(TARGET_ANA,\
+ 0, 1, 23680, 0, 1, 128, 68, 0, 1, 4)
+
+#define ANA_VLANTIDX_VLAN_PGID_CPU_DIS BIT(18)
+#define ANA_VLANTIDX_VLAN_PGID_CPU_DIS_SET(x)\
+ FIELD_PREP(ANA_VLANTIDX_VLAN_PGID_CPU_DIS, x)
+#define ANA_VLANTIDX_VLAN_PGID_CPU_DIS_GET(x)\
+ FIELD_GET(ANA_VLANTIDX_VLAN_PGID_CPU_DIS, x)
+
+#define ANA_VLANTIDX_VLAN_SEC_FWD_ENA BIT(17)
+#define ANA_VLANTIDX_VLAN_SEC_FWD_ENA_SET(x)\
+ FIELD_PREP(ANA_VLANTIDX_VLAN_SEC_FWD_ENA, x)
+#define ANA_VLANTIDX_VLAN_SEC_FWD_ENA_GET(x)\
+ FIELD_GET(ANA_VLANTIDX_VLAN_SEC_FWD_ENA, x)
+
+#define ANA_VLANTIDX_VLAN_FLOOD_DIS BIT(16)
+#define ANA_VLANTIDX_VLAN_FLOOD_DIS_SET(x)\
+ FIELD_PREP(ANA_VLANTIDX_VLAN_FLOOD_DIS, x)
+#define ANA_VLANTIDX_VLAN_FLOOD_DIS_GET(x)\
+ FIELD_GET(ANA_VLANTIDX_VLAN_FLOOD_DIS, x)
+
+#define ANA_VLANTIDX_VLAN_PRIV_VLAN BIT(15)
+#define ANA_VLANTIDX_VLAN_PRIV_VLAN_SET(x)\
+ FIELD_PREP(ANA_VLANTIDX_VLAN_PRIV_VLAN, x)
+#define ANA_VLANTIDX_VLAN_PRIV_VLAN_GET(x)\
+ FIELD_GET(ANA_VLANTIDX_VLAN_PRIV_VLAN, x)
+
+#define ANA_VLANTIDX_VLAN_LEARN_DISABLED BIT(14)
+#define ANA_VLANTIDX_VLAN_LEARN_DISABLED_SET(x)\
+ FIELD_PREP(ANA_VLANTIDX_VLAN_LEARN_DISABLED, x)
+#define ANA_VLANTIDX_VLAN_LEARN_DISABLED_GET(x)\
+ FIELD_GET(ANA_VLANTIDX_VLAN_LEARN_DISABLED, x)
+
+#define ANA_VLANTIDX_VLAN_MIRROR BIT(13)
+#define ANA_VLANTIDX_VLAN_MIRROR_SET(x)\
+ FIELD_PREP(ANA_VLANTIDX_VLAN_MIRROR, x)
+#define ANA_VLANTIDX_VLAN_MIRROR_GET(x)\
+ FIELD_GET(ANA_VLANTIDX_VLAN_MIRROR, x)
+
+#define ANA_VLANTIDX_VLAN_SRC_CHK BIT(12)
+#define ANA_VLANTIDX_VLAN_SRC_CHK_SET(x)\
+ FIELD_PREP(ANA_VLANTIDX_VLAN_SRC_CHK, x)
+#define ANA_VLANTIDX_VLAN_SRC_CHK_GET(x)\
+ FIELD_GET(ANA_VLANTIDX_VLAN_SRC_CHK, x)
+
+#define ANA_VLANTIDX_V_INDEX GENMASK(11, 0)
+#define ANA_VLANTIDX_V_INDEX_SET(x)\
+ FIELD_PREP(ANA_VLANTIDX_V_INDEX, x)
+#define ANA_VLANTIDX_V_INDEX_GET(x)\
+ FIELD_GET(ANA_VLANTIDX_V_INDEX, x)
+
+/* ANA:PORT:VLAN_CFG */
+#define ANA_VLAN_CFG(g) __REG(TARGET_ANA,\
+ 0, 1, 24576, g, 10, 256, 0, 0, 1, 4)
+
+#define ANA_VLAN_CFG_VLAN_PFC_ENA BIT(21)
+#define ANA_VLAN_CFG_VLAN_PFC_ENA_SET(x)\
+ FIELD_PREP(ANA_VLAN_CFG_VLAN_PFC_ENA, x)
+#define ANA_VLAN_CFG_VLAN_PFC_ENA_GET(x)\
+ FIELD_GET(ANA_VLAN_CFG_VLAN_PFC_ENA, x)
+
+#define ANA_VLAN_CFG_VLAN_AWARE_ENA BIT(20)
+#define ANA_VLAN_CFG_VLAN_AWARE_ENA_SET(x)\
+ FIELD_PREP(ANA_VLAN_CFG_VLAN_AWARE_ENA, x)
+#define ANA_VLAN_CFG_VLAN_AWARE_ENA_GET(x)\
+ FIELD_GET(ANA_VLAN_CFG_VLAN_AWARE_ENA, x)
+
+#define ANA_VLAN_CFG_VLAN_POP_CNT GENMASK(19, 18)
+#define ANA_VLAN_CFG_VLAN_POP_CNT_SET(x)\
+ FIELD_PREP(ANA_VLAN_CFG_VLAN_POP_CNT, x)
+#define ANA_VLAN_CFG_VLAN_POP_CNT_GET(x)\
+ FIELD_GET(ANA_VLAN_CFG_VLAN_POP_CNT, x)
+
+#define ANA_VLAN_CFG_VLAN_INNER_TAG_ENA BIT(17)
+#define ANA_VLAN_CFG_VLAN_INNER_TAG_ENA_SET(x)\
+ FIELD_PREP(ANA_VLAN_CFG_VLAN_INNER_TAG_ENA, x)
+#define ANA_VLAN_CFG_VLAN_INNER_TAG_ENA_GET(x)\
+ FIELD_GET(ANA_VLAN_CFG_VLAN_INNER_TAG_ENA, x)
+
+#define ANA_VLAN_CFG_VLAN_TAG_TYPE BIT(16)
+#define ANA_VLAN_CFG_VLAN_TAG_TYPE_SET(x)\
+ FIELD_PREP(ANA_VLAN_CFG_VLAN_TAG_TYPE, x)
+#define ANA_VLAN_CFG_VLAN_TAG_TYPE_GET(x)\
+ FIELD_GET(ANA_VLAN_CFG_VLAN_TAG_TYPE, x)
+
+#define ANA_VLAN_CFG_VLAN_PCP GENMASK(15, 13)
+#define ANA_VLAN_CFG_VLAN_PCP_SET(x)\
+ FIELD_PREP(ANA_VLAN_CFG_VLAN_PCP, x)
+#define ANA_VLAN_CFG_VLAN_PCP_GET(x)\
+ FIELD_GET(ANA_VLAN_CFG_VLAN_PCP, x)
+
+#define ANA_VLAN_CFG_VLAN_DEI BIT(12)
+#define ANA_VLAN_CFG_VLAN_DEI_SET(x)\
+ FIELD_PREP(ANA_VLAN_CFG_VLAN_DEI, x)
+#define ANA_VLAN_CFG_VLAN_DEI_GET(x)\
+ FIELD_GET(ANA_VLAN_CFG_VLAN_DEI, x)
+
+#define ANA_VLAN_CFG_VLAN_VID GENMASK(11, 0)
+#define ANA_VLAN_CFG_VLAN_VID_SET(x)\
+ FIELD_PREP(ANA_VLAN_CFG_VLAN_VID, x)
+#define ANA_VLAN_CFG_VLAN_VID_GET(x)\
+ FIELD_GET(ANA_VLAN_CFG_VLAN_VID, x)
+
+/* ANA:PORT:DROP_CFG */
+#define ANA_DROP_CFG(g) __REG(TARGET_ANA,\
+ 0, 1, 24576, g, 10, 256, 4, 0, 1, 4)
+
+#define ANA_DROP_CFG_DROP_UNTAGGED_ENA BIT(6)
+#define ANA_DROP_CFG_DROP_UNTAGGED_ENA_SET(x)\
+ FIELD_PREP(ANA_DROP_CFG_DROP_UNTAGGED_ENA, x)
+#define ANA_DROP_CFG_DROP_UNTAGGED_ENA_GET(x)\
+ FIELD_GET(ANA_DROP_CFG_DROP_UNTAGGED_ENA, x)
+
+#define ANA_DROP_CFG_DROP_S_TAGGED_ENA BIT(5)
+#define ANA_DROP_CFG_DROP_S_TAGGED_ENA_SET(x)\
+ FIELD_PREP(ANA_DROP_CFG_DROP_S_TAGGED_ENA, x)
+#define ANA_DROP_CFG_DROP_S_TAGGED_ENA_GET(x)\
+ FIELD_GET(ANA_DROP_CFG_DROP_S_TAGGED_ENA, x)
+
+#define ANA_DROP_CFG_DROP_C_TAGGED_ENA BIT(4)
+#define ANA_DROP_CFG_DROP_C_TAGGED_ENA_SET(x)\
+ FIELD_PREP(ANA_DROP_CFG_DROP_C_TAGGED_ENA, x)
+#define ANA_DROP_CFG_DROP_C_TAGGED_ENA_GET(x)\
+ FIELD_GET(ANA_DROP_CFG_DROP_C_TAGGED_ENA, x)
+
+#define ANA_DROP_CFG_DROP_PRIO_S_TAGGED_ENA BIT(3)
+#define ANA_DROP_CFG_DROP_PRIO_S_TAGGED_ENA_SET(x)\
+ FIELD_PREP(ANA_DROP_CFG_DROP_PRIO_S_TAGGED_ENA, x)
+#define ANA_DROP_CFG_DROP_PRIO_S_TAGGED_ENA_GET(x)\
+ FIELD_GET(ANA_DROP_CFG_DROP_PRIO_S_TAGGED_ENA, x)
+
+#define ANA_DROP_CFG_DROP_PRIO_C_TAGGED_ENA BIT(2)
+#define ANA_DROP_CFG_DROP_PRIO_C_TAGGED_ENA_SET(x)\
+ FIELD_PREP(ANA_DROP_CFG_DROP_PRIO_C_TAGGED_ENA, x)
+#define ANA_DROP_CFG_DROP_PRIO_C_TAGGED_ENA_GET(x)\
+ FIELD_GET(ANA_DROP_CFG_DROP_PRIO_C_TAGGED_ENA, x)
+
+#define ANA_DROP_CFG_DROP_NULL_MAC_ENA BIT(1)
+#define ANA_DROP_CFG_DROP_NULL_MAC_ENA_SET(x)\
+ FIELD_PREP(ANA_DROP_CFG_DROP_NULL_MAC_ENA, x)
+#define ANA_DROP_CFG_DROP_NULL_MAC_ENA_GET(x)\
+ FIELD_GET(ANA_DROP_CFG_DROP_NULL_MAC_ENA, x)
+
+#define ANA_DROP_CFG_DROP_MC_SMAC_ENA BIT(0)
+#define ANA_DROP_CFG_DROP_MC_SMAC_ENA_SET(x)\
+ FIELD_PREP(ANA_DROP_CFG_DROP_MC_SMAC_ENA, x)
+#define ANA_DROP_CFG_DROP_MC_SMAC_ENA_GET(x)\
+ FIELD_GET(ANA_DROP_CFG_DROP_MC_SMAC_ENA, x)
+
+/* ANA:PORT:CPU_FWD_CFG */
+#define ANA_CPU_FWD_CFG(g) __REG(TARGET_ANA,\
+ 0, 1, 24576, g, 10, 256, 96, 0, 1, 4)
+
+#define ANA_CPU_FWD_CFG_NO_HSR_REDIR_ENA BIT(9)
+#define ANA_CPU_FWD_CFG_NO_HSR_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_CPU_FWD_CFG_NO_HSR_REDIR_ENA, x)
+#define ANA_CPU_FWD_CFG_NO_HSR_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_CPU_FWD_CFG_NO_HSR_REDIR_ENA, x)
+
+#define ANA_CPU_FWD_CFG_SPV_COPY_ENA BIT(8)
+#define ANA_CPU_FWD_CFG_SPV_COPY_ENA_SET(x)\
+ FIELD_PREP(ANA_CPU_FWD_CFG_SPV_COPY_ENA, x)
+#define ANA_CPU_FWD_CFG_SPV_COPY_ENA_GET(x)\
+ FIELD_GET(ANA_CPU_FWD_CFG_SPV_COPY_ENA, x)
+
+#define ANA_CPU_FWD_CFG_VRAP_REDIR_ENA BIT(7)
+#define ANA_CPU_FWD_CFG_VRAP_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_CPU_FWD_CFG_VRAP_REDIR_ENA, x)
+#define ANA_CPU_FWD_CFG_VRAP_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_CPU_FWD_CFG_VRAP_REDIR_ENA, x)
+
+#define ANA_CPU_FWD_CFG_MLD_REDIR_ENA BIT(6)
+#define ANA_CPU_FWD_CFG_MLD_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_CPU_FWD_CFG_MLD_REDIR_ENA, x)
+#define ANA_CPU_FWD_CFG_MLD_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_CPU_FWD_CFG_MLD_REDIR_ENA, x)
+
+#define ANA_CPU_FWD_CFG_IGMP_REDIR_ENA BIT(5)
+#define ANA_CPU_FWD_CFG_IGMP_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_CPU_FWD_CFG_IGMP_REDIR_ENA, x)
+#define ANA_CPU_FWD_CFG_IGMP_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_CPU_FWD_CFG_IGMP_REDIR_ENA, x)
+
+#define ANA_CPU_FWD_CFG_IPMC_CTRL_COPY_ENA BIT(4)
+#define ANA_CPU_FWD_CFG_IPMC_CTRL_COPY_ENA_SET(x)\
+ FIELD_PREP(ANA_CPU_FWD_CFG_IPMC_CTRL_COPY_ENA, x)
+#define ANA_CPU_FWD_CFG_IPMC_CTRL_COPY_ENA_GET(x)\
+ FIELD_GET(ANA_CPU_FWD_CFG_IPMC_CTRL_COPY_ENA, x)
+
+#define ANA_CPU_FWD_CFG_SRC_COPY_ENA BIT(3)
+#define ANA_CPU_FWD_CFG_SRC_COPY_ENA_SET(x)\
+ FIELD_PREP(ANA_CPU_FWD_CFG_SRC_COPY_ENA, x)
+#define ANA_CPU_FWD_CFG_SRC_COPY_ENA_GET(x)\
+ FIELD_GET(ANA_CPU_FWD_CFG_SRC_COPY_ENA, x)
+
+#define ANA_CPU_FWD_CFG_ALLBRIDGE_DROP_ENA BIT(2)
+#define ANA_CPU_FWD_CFG_ALLBRIDGE_DROP_ENA_SET(x)\
+ FIELD_PREP(ANA_CPU_FWD_CFG_ALLBRIDGE_DROP_ENA, x)
+#define ANA_CPU_FWD_CFG_ALLBRIDGE_DROP_ENA_GET(x)\
+ FIELD_GET(ANA_CPU_FWD_CFG_ALLBRIDGE_DROP_ENA, x)
+
+#define ANA_CPU_FWD_CFG_ALLBRIDGE_REDIR_ENA BIT(1)
+#define ANA_CPU_FWD_CFG_ALLBRIDGE_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_CPU_FWD_CFG_ALLBRIDGE_REDIR_ENA, x)
+#define ANA_CPU_FWD_CFG_ALLBRIDGE_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_CPU_FWD_CFG_ALLBRIDGE_REDIR_ENA, x)
+
+#define ANA_CPU_FWD_CFG_OAM_ENA BIT(0)
+#define ANA_CPU_FWD_CFG_OAM_ENA_SET(x)\
+ FIELD_PREP(ANA_CPU_FWD_CFG_OAM_ENA, x)
+#define ANA_CPU_FWD_CFG_OAM_ENA_GET(x)\
+ FIELD_GET(ANA_CPU_FWD_CFG_OAM_ENA, x)
+
+/* ANA:PORT:CPU_FWD_BPDU_CFG */
+#define ANA_CPU_FWD_BPDU_CFG(g) __REG(TARGET_ANA,\
+ 0, 1, 24576, g, 10, 256, 100, 0, 1, 4)
+
+#define ANA_CPU_FWD_BPDU_CFG_BPDU_DROP_ENA GENMASK(31, 16)
+#define ANA_CPU_FWD_BPDU_CFG_BPDU_DROP_ENA_SET(x)\
+ FIELD_PREP(ANA_CPU_FWD_BPDU_CFG_BPDU_DROP_ENA, x)
+#define ANA_CPU_FWD_BPDU_CFG_BPDU_DROP_ENA_GET(x)\
+ FIELD_GET(ANA_CPU_FWD_BPDU_CFG_BPDU_DROP_ENA, x)
+
+#define ANA_CPU_FWD_BPDU_CFG_BPDU_REDIR_ENA GENMASK(15, 0)
+#define ANA_CPU_FWD_BPDU_CFG_BPDU_REDIR_ENA_SET(x)\
+ FIELD_PREP(ANA_CPU_FWD_BPDU_CFG_BPDU_REDIR_ENA, x)
+#define ANA_CPU_FWD_BPDU_CFG_BPDU_REDIR_ENA_GET(x)\
+ FIELD_GET(ANA_CPU_FWD_BPDU_CFG_BPDU_REDIR_ENA, x)
+
+/* ANA:PORT:PORT_CFG */
+#define ANA_PORT_CFG(g) __REG(TARGET_ANA,\
+ 0, 1, 24576, g, 10, 256, 112, 0, 1, 4)
+
+#define ANA_PORT_CFG_SRC_MIRROR_ENA BIT(13)
+#define ANA_PORT_CFG_SRC_MIRROR_ENA_SET(x)\
+ FIELD_PREP(ANA_PORT_CFG_SRC_MIRROR_ENA, x)
+#define ANA_PORT_CFG_SRC_MIRROR_ENA_GET(x)\
+ FIELD_GET(ANA_PORT_CFG_SRC_MIRROR_ENA, x)
+
+#define ANA_PORT_CFG_LIMIT_DROP BIT(12)
+#define ANA_PORT_CFG_LIMIT_DROP_SET(x)\
+ FIELD_PREP(ANA_PORT_CFG_LIMIT_DROP, x)
+#define ANA_PORT_CFG_LIMIT_DROP_GET(x)\
+ FIELD_GET(ANA_PORT_CFG_LIMIT_DROP, x)
+
+#define ANA_PORT_CFG_LIMIT_CPU BIT(11)
+#define ANA_PORT_CFG_LIMIT_CPU_SET(x)\
+ FIELD_PREP(ANA_PORT_CFG_LIMIT_CPU, x)
+#define ANA_PORT_CFG_LIMIT_CPU_GET(x)\
+ FIELD_GET(ANA_PORT_CFG_LIMIT_CPU, x)
+
+#define ANA_PORT_CFG_LOCKED_PORTMOVE_DROP BIT(10)
+#define ANA_PORT_CFG_LOCKED_PORTMOVE_DROP_SET(x)\
+ FIELD_PREP(ANA_PORT_CFG_LOCKED_PORTMOVE_DROP, x)
+#define ANA_PORT_CFG_LOCKED_PORTMOVE_DROP_GET(x)\
+ FIELD_GET(ANA_PORT_CFG_LOCKED_PORTMOVE_DROP, x)
+
+#define ANA_PORT_CFG_LOCKED_PORTMOVE_CPU BIT(9)
+#define ANA_PORT_CFG_LOCKED_PORTMOVE_CPU_SET(x)\
+ FIELD_PREP(ANA_PORT_CFG_LOCKED_PORTMOVE_CPU, x)
+#define ANA_PORT_CFG_LOCKED_PORTMOVE_CPU_GET(x)\
+ FIELD_GET(ANA_PORT_CFG_LOCKED_PORTMOVE_CPU, x)
+
+#define ANA_PORT_CFG_LEARNDROP BIT(8)
+#define ANA_PORT_CFG_LEARNDROP_SET(x)\
+ FIELD_PREP(ANA_PORT_CFG_LEARNDROP, x)
+#define ANA_PORT_CFG_LEARNDROP_GET(x)\
+ FIELD_GET(ANA_PORT_CFG_LEARNDROP, x)
+
+#define ANA_PORT_CFG_LEARNCPU BIT(7)
+#define ANA_PORT_CFG_LEARNCPU_SET(x)\
+ FIELD_PREP(ANA_PORT_CFG_LEARNCPU, x)
+#define ANA_PORT_CFG_LEARNCPU_GET(x)\
+ FIELD_GET(ANA_PORT_CFG_LEARNCPU, x)
+
+#define ANA_PORT_CFG_LEARNAUTO BIT(6)
+#define ANA_PORT_CFG_LEARNAUTO_SET(x)\
+ FIELD_PREP(ANA_PORT_CFG_LEARNAUTO, x)
+#define ANA_PORT_CFG_LEARNAUTO_GET(x)\
+ FIELD_GET(ANA_PORT_CFG_LEARNAUTO, x)
+
+#define ANA_PORT_CFG_LEARN_ENA BIT(5)
+#define ANA_PORT_CFG_LEARN_ENA_SET(x)\
+ FIELD_PREP(ANA_PORT_CFG_LEARN_ENA, x)
+#define ANA_PORT_CFG_LEARN_ENA_GET(x)\
+ FIELD_GET(ANA_PORT_CFG_LEARN_ENA, x)
+
+#define ANA_PORT_CFG_RECV_ENA BIT(4)
+#define ANA_PORT_CFG_RECV_ENA_SET(x)\
+ FIELD_PREP(ANA_PORT_CFG_RECV_ENA, x)
+#define ANA_PORT_CFG_RECV_ENA_GET(x)\
+ FIELD_GET(ANA_PORT_CFG_RECV_ENA, x)
+
+#define ANA_PORT_CFG_PORTID_VAL GENMASK(3, 0)
+#define ANA_PORT_CFG_PORTID_VAL_SET(x)\
+ FIELD_PREP(ANA_PORT_CFG_PORTID_VAL, x)
+#define ANA_PORT_CFG_PORTID_VAL_GET(x)\
+ FIELD_GET(ANA_PORT_CFG_PORTID_VAL, x)
+
+/* ANA:PFC:PFC_CFG */
+#define ANA_PFC_CFG(g) __REG(TARGET_ANA,\
+ 0, 1, 28672, g, 9, 64, 0, 0, 1, 4)
+
+#define ANA_PFC_CFG_RX_PFC_ENA GENMASK(9, 2)
+#define ANA_PFC_CFG_RX_PFC_ENA_SET(x)\
+ FIELD_PREP(ANA_PFC_CFG_RX_PFC_ENA, x)
+#define ANA_PFC_CFG_RX_PFC_ENA_GET(x)\
+ FIELD_GET(ANA_PFC_CFG_RX_PFC_ENA, x)
+
+#define ANA_PFC_CFG_FC_LINK_SPEED GENMASK(1, 0)
+#define ANA_PFC_CFG_FC_LINK_SPEED_SET(x)\
+ FIELD_PREP(ANA_PFC_CFG_FC_LINK_SPEED, x)
+#define ANA_PFC_CFG_FC_LINK_SPEED_GET(x)\
+ FIELD_GET(ANA_PFC_CFG_FC_LINK_SPEED, x)
+
+/* ANA:COMMON:AGGR_CFG */
+#define ANA_AGGR_CFG __REG(TARGET_ANA,\
+ 0, 1, 29248, 0, 1, 552, 0, 0, 1, 4)
+
+#define ANA_AGGR_CFG_AC_RND_ENA BIT(6)
+#define ANA_AGGR_CFG_AC_RND_ENA_SET(x)\
+ FIELD_PREP(ANA_AGGR_CFG_AC_RND_ENA, x)
+#define ANA_AGGR_CFG_AC_RND_ENA_GET(x)\
+ FIELD_GET(ANA_AGGR_CFG_AC_RND_ENA, x)
+
+#define ANA_AGGR_CFG_AC_DMAC_ENA BIT(5)
+#define ANA_AGGR_CFG_AC_DMAC_ENA_SET(x)\
+ FIELD_PREP(ANA_AGGR_CFG_AC_DMAC_ENA, x)
+#define ANA_AGGR_CFG_AC_DMAC_ENA_GET(x)\
+ FIELD_GET(ANA_AGGR_CFG_AC_DMAC_ENA, x)
+
+#define ANA_AGGR_CFG_AC_SMAC_ENA BIT(4)
+#define ANA_AGGR_CFG_AC_SMAC_ENA_SET(x)\
+ FIELD_PREP(ANA_AGGR_CFG_AC_SMAC_ENA, x)
+#define ANA_AGGR_CFG_AC_SMAC_ENA_GET(x)\
+ FIELD_GET(ANA_AGGR_CFG_AC_SMAC_ENA, x)
+
+#define ANA_AGGR_CFG_AC_IP6_FLOW_LBL_ENA BIT(3)
+#define ANA_AGGR_CFG_AC_IP6_FLOW_LBL_ENA_SET(x)\
+ FIELD_PREP(ANA_AGGR_CFG_AC_IP6_FLOW_LBL_ENA, x)
+#define ANA_AGGR_CFG_AC_IP6_FLOW_LBL_ENA_GET(x)\
+ FIELD_GET(ANA_AGGR_CFG_AC_IP6_FLOW_LBL_ENA, x)
+
+#define ANA_AGGR_CFG_AC_IP6_TCPUDP_ENA BIT(2)
+#define ANA_AGGR_CFG_AC_IP6_TCPUDP_ENA_SET(x)\
+ FIELD_PREP(ANA_AGGR_CFG_AC_IP6_TCPUDP_ENA, x)
+#define ANA_AGGR_CFG_AC_IP6_TCPUDP_ENA_GET(x)\
+ FIELD_GET(ANA_AGGR_CFG_AC_IP6_TCPUDP_ENA, x)
+
+#define ANA_AGGR_CFG_AC_IP4_SIPDIP_ENA BIT(1)
+#define ANA_AGGR_CFG_AC_IP4_SIPDIP_ENA_SET(x)\
+ FIELD_PREP(ANA_AGGR_CFG_AC_IP4_SIPDIP_ENA, x)
+#define ANA_AGGR_CFG_AC_IP4_SIPDIP_ENA_GET(x)\
+ FIELD_GET(ANA_AGGR_CFG_AC_IP4_SIPDIP_ENA, x)
+
+#define ANA_AGGR_CFG_AC_IP4_TCPUDP_ENA BIT(0)
+#define ANA_AGGR_CFG_AC_IP4_TCPUDP_ENA_SET(x)\
+ FIELD_PREP(ANA_AGGR_CFG_AC_IP4_TCPUDP_ENA, x)
+#define ANA_AGGR_CFG_AC_IP4_TCPUDP_ENA_GET(x)\
+ FIELD_GET(ANA_AGGR_CFG_AC_IP4_TCPUDP_ENA, x)
+
+/* ANA:COMMON:CPUQ_CFG */
+#define ANA_CPUQ_CFG __REG(TARGET_ANA,\
+ 0, 1, 29248, 0, 1, 552, 4, 0, 1, 4)
+
+#define ANA_CPUQ_CFG_CPUQ_MLD GENMASK(29, 27)
+#define ANA_CPUQ_CFG_CPUQ_MLD_SET(x)\
+ FIELD_PREP(ANA_CPUQ_CFG_CPUQ_MLD, x)
+#define ANA_CPUQ_CFG_CPUQ_MLD_GET(x)\
+ FIELD_GET(ANA_CPUQ_CFG_CPUQ_MLD, x)
+
+#define ANA_CPUQ_CFG_CPUQ_IGMP GENMASK(26, 24)
+#define ANA_CPUQ_CFG_CPUQ_IGMP_SET(x)\
+ FIELD_PREP(ANA_CPUQ_CFG_CPUQ_IGMP, x)
+#define ANA_CPUQ_CFG_CPUQ_IGMP_GET(x)\
+ FIELD_GET(ANA_CPUQ_CFG_CPUQ_IGMP, x)
+
+#define ANA_CPUQ_CFG_CPUQ_IPMC_CTRL GENMASK(23, 21)
+#define ANA_CPUQ_CFG_CPUQ_IPMC_CTRL_SET(x)\
+ FIELD_PREP(ANA_CPUQ_CFG_CPUQ_IPMC_CTRL, x)
+#define ANA_CPUQ_CFG_CPUQ_IPMC_CTRL_GET(x)\
+ FIELD_GET(ANA_CPUQ_CFG_CPUQ_IPMC_CTRL, x)
+
+#define ANA_CPUQ_CFG_CPUQ_ALLBRIDGE GENMASK(20, 18)
+#define ANA_CPUQ_CFG_CPUQ_ALLBRIDGE_SET(x)\
+ FIELD_PREP(ANA_CPUQ_CFG_CPUQ_ALLBRIDGE, x)
+#define ANA_CPUQ_CFG_CPUQ_ALLBRIDGE_GET(x)\
+ FIELD_GET(ANA_CPUQ_CFG_CPUQ_ALLBRIDGE, x)
+
+#define ANA_CPUQ_CFG_CPUQ_LOCKED_PORTMOVE GENMASK(17, 15)
+#define ANA_CPUQ_CFG_CPUQ_LOCKED_PORTMOVE_SET(x)\
+ FIELD_PREP(ANA_CPUQ_CFG_CPUQ_LOCKED_PORTMOVE, x)
+#define ANA_CPUQ_CFG_CPUQ_LOCKED_PORTMOVE_GET(x)\
+ FIELD_GET(ANA_CPUQ_CFG_CPUQ_LOCKED_PORTMOVE, x)
+
+#define ANA_CPUQ_CFG_CPUQ_SRC_COPY GENMASK(14, 12)
+#define ANA_CPUQ_CFG_CPUQ_SRC_COPY_SET(x)\
+ FIELD_PREP(ANA_CPUQ_CFG_CPUQ_SRC_COPY, x)
+#define ANA_CPUQ_CFG_CPUQ_SRC_COPY_GET(x)\
+ FIELD_GET(ANA_CPUQ_CFG_CPUQ_SRC_COPY, x)
+
+#define ANA_CPUQ_CFG_CPUQ_MAC_COPY GENMASK(11, 9)
+#define ANA_CPUQ_CFG_CPUQ_MAC_COPY_SET(x)\
+ FIELD_PREP(ANA_CPUQ_CFG_CPUQ_MAC_COPY, x)
+#define ANA_CPUQ_CFG_CPUQ_MAC_COPY_GET(x)\
+ FIELD_GET(ANA_CPUQ_CFG_CPUQ_MAC_COPY, x)
+
+#define ANA_CPUQ_CFG_CPUQ_LRN GENMASK(8, 6)
+#define ANA_CPUQ_CFG_CPUQ_LRN_SET(x)\
+ FIELD_PREP(ANA_CPUQ_CFG_CPUQ_LRN, x)
+#define ANA_CPUQ_CFG_CPUQ_LRN_GET(x)\
+ FIELD_GET(ANA_CPUQ_CFG_CPUQ_LRN, x)
+
+#define ANA_CPUQ_CFG_CPUQ_MIRROR GENMASK(5, 3)
+#define ANA_CPUQ_CFG_CPUQ_MIRROR_SET(x)\
+ FIELD_PREP(ANA_CPUQ_CFG_CPUQ_MIRROR, x)
+#define ANA_CPUQ_CFG_CPUQ_MIRROR_GET(x)\
+ FIELD_GET(ANA_CPUQ_CFG_CPUQ_MIRROR, x)
+
+#define ANA_CPUQ_CFG_CPUQ_SFLOW GENMASK(2, 0)
+#define ANA_CPUQ_CFG_CPUQ_SFLOW_SET(x)\
+ FIELD_PREP(ANA_CPUQ_CFG_CPUQ_SFLOW, x)
+#define ANA_CPUQ_CFG_CPUQ_SFLOW_GET(x)\
+ FIELD_GET(ANA_CPUQ_CFG_CPUQ_SFLOW, x)
+
+/* ANA:COMMON:CPUQ_8021_CFG */
+#define ANA_CPUQ_8021_CFG(r) __REG(TARGET_ANA,\
+ 0, 1, 29248, 0, 1, 552, 12, r, 16, 4)
+
+#define ANA_CPUQ_8021_CFG_CPUQ_BPDU_VAL GENMASK(8, 6)
+#define ANA_CPUQ_8021_CFG_CPUQ_BPDU_VAL_SET(x)\
+ FIELD_PREP(ANA_CPUQ_8021_CFG_CPUQ_BPDU_VAL, x)
+#define ANA_CPUQ_8021_CFG_CPUQ_BPDU_VAL_GET(x)\
+ FIELD_GET(ANA_CPUQ_8021_CFG_CPUQ_BPDU_VAL, x)
+
+#define ANA_CPUQ_8021_CFG_CPUQ_GARP_VAL GENMASK(5, 3)
+#define ANA_CPUQ_8021_CFG_CPUQ_GARP_VAL_SET(x)\
+ FIELD_PREP(ANA_CPUQ_8021_CFG_CPUQ_GARP_VAL, x)
+#define ANA_CPUQ_8021_CFG_CPUQ_GARP_VAL_GET(x)\
+ FIELD_GET(ANA_CPUQ_8021_CFG_CPUQ_GARP_VAL, x)
+
+#define ANA_CPUQ_8021_CFG_CPUQ_CCM_VAL GENMASK(2, 0)
+#define ANA_CPUQ_8021_CFG_CPUQ_CCM_VAL_SET(x)\
+ FIELD_PREP(ANA_CPUQ_8021_CFG_CPUQ_CCM_VAL, x)
+#define ANA_CPUQ_8021_CFG_CPUQ_CCM_VAL_GET(x)\
+ FIELD_GET(ANA_CPUQ_8021_CFG_CPUQ_CCM_VAL, x)
+
+/* CHIP_TOP:CUPHY_CFG:CUPHY_PORT_CFG */
+#define CHIP_TOP_CUPHY_PORT_CFG(r) __REG(TARGET_CHIP_TOP,\
+ 0, 1, 12, 0, 1, 64, 20, r, 5, 4)
+
+#define CHIP_TOP_CUPHY_PORT_CFG_AUTO_SQUELCH_ENA BIT(7)
+#define CHIP_TOP_CUPHY_PORT_CFG_AUTO_SQUELCH_ENA_SET(x)\
+ FIELD_PREP(CHIP_TOP_CUPHY_PORT_CFG_AUTO_SQUELCH_ENA, x)
+#define CHIP_TOP_CUPHY_PORT_CFG_AUTO_SQUELCH_ENA_GET(x)\
+ FIELD_GET(CHIP_TOP_CUPHY_PORT_CFG_AUTO_SQUELCH_ENA, x)
+
+#define CHIP_TOP_CUPHY_PORT_CFG_COMA_MODE BIT(6)
+#define CHIP_TOP_CUPHY_PORT_CFG_COMA_MODE_SET(x)\
+ FIELD_PREP(CHIP_TOP_CUPHY_PORT_CFG_COMA_MODE, x)
+#define CHIP_TOP_CUPHY_PORT_CFG_COMA_MODE_GET(x)\
+ FIELD_GET(CHIP_TOP_CUPHY_PORT_CFG_COMA_MODE, x)
+
+#define CHIP_TOP_CUPHY_PORT_CFG_MODE GENMASK(5, 1)
+#define CHIP_TOP_CUPHY_PORT_CFG_MODE_SET(x)\
+ FIELD_PREP(CHIP_TOP_CUPHY_PORT_CFG_MODE, x)
+#define CHIP_TOP_CUPHY_PORT_CFG_MODE_GET(x)\
+ FIELD_GET(CHIP_TOP_CUPHY_PORT_CFG_MODE, x)
+
+#define CHIP_TOP_CUPHY_PORT_CFG_GTX_CLK_ENA BIT(0)
+#define CHIP_TOP_CUPHY_PORT_CFG_GTX_CLK_ENA_SET(x)\
+ FIELD_PREP(CHIP_TOP_CUPHY_PORT_CFG_GTX_CLK_ENA, x)
+#define CHIP_TOP_CUPHY_PORT_CFG_GTX_CLK_ENA_GET(x)\
+ FIELD_GET(CHIP_TOP_CUPHY_PORT_CFG_GTX_CLK_ENA, x)
+
+/* CHIP_TOP:CUPHY_CFG:CUPHY_LED_CFG */
+#define CHIP_TOP_CUPHY_LED_CFG(r) __REG(TARGET_CHIP_TOP,\
+ 0, 1, 12, 0, 1, 64, 40, r, 5, 4)
+
+#define CHIP_TOP_CUPHY_LED_CFG_LED_ECO_DIS BIT(11)
+#define CHIP_TOP_CUPHY_LED_CFG_LED_ECO_DIS_SET(x)\
+ FIELD_PREP(CHIP_TOP_CUPHY_LED_CFG_LED_ECO_DIS, x)
+#define CHIP_TOP_CUPHY_LED_CFG_LED_ECO_DIS_GET(x)\
+ FIELD_GET(CHIP_TOP_CUPHY_LED_CFG_LED_ECO_DIS, x)
+
+#define CHIP_TOP_CUPHY_LED_CFG_LED_EEE_MODE BIT(10)
+#define CHIP_TOP_CUPHY_LED_CFG_LED_EEE_MODE_SET(x)\
+ FIELD_PREP(CHIP_TOP_CUPHY_LED_CFG_LED_EEE_MODE, x)
+#define CHIP_TOP_CUPHY_LED_CFG_LED_EEE_MODE_GET(x)\
+ FIELD_GET(CHIP_TOP_CUPHY_LED_CFG_LED_EEE_MODE, x)
+
+#define CHIP_TOP_CUPHY_LED_CFG_LED_TEST_MODE GENMASK(9, 8)
+#define CHIP_TOP_CUPHY_LED_CFG_LED_TEST_MODE_SET(x)\
+ FIELD_PREP(CHIP_TOP_CUPHY_LED_CFG_LED_TEST_MODE, x)
+#define CHIP_TOP_CUPHY_LED_CFG_LED_TEST_MODE_GET(x)\
+ FIELD_GET(CHIP_TOP_CUPHY_LED_CFG_LED_TEST_MODE, x)
+
+#define CHIP_TOP_CUPHY_LED_CFG_LED_TEST_VAL GENMASK(7, 6)
+#define CHIP_TOP_CUPHY_LED_CFG_LED_TEST_VAL_SET(x)\
+ FIELD_PREP(CHIP_TOP_CUPHY_LED_CFG_LED_TEST_VAL, x)
+#define CHIP_TOP_CUPHY_LED_CFG_LED_TEST_VAL_GET(x)\
+ FIELD_GET(CHIP_TOP_CUPHY_LED_CFG_LED_TEST_VAL, x)
+
+#define CHIP_TOP_CUPHY_LED_CFG_LED_POLARITY GENMASK(5, 4)
+#define CHIP_TOP_CUPHY_LED_CFG_LED_POLARITY_SET(x)\
+ FIELD_PREP(CHIP_TOP_CUPHY_LED_CFG_LED_POLARITY, x)
+#define CHIP_TOP_CUPHY_LED_CFG_LED_POLARITY_GET(x)\
+ FIELD_GET(CHIP_TOP_CUPHY_LED_CFG_LED_POLARITY, x)
+
+#define CHIP_TOP_CUPHY_LED_CFG_LED_DRIVE_MODE GENMASK(3, 2)
+#define CHIP_TOP_CUPHY_LED_CFG_LED_DRIVE_MODE_SET(x)\
+ FIELD_PREP(CHIP_TOP_CUPHY_LED_CFG_LED_DRIVE_MODE, x)
+#define CHIP_TOP_CUPHY_LED_CFG_LED_DRIVE_MODE_GET(x)\
+ FIELD_GET(CHIP_TOP_CUPHY_LED_CFG_LED_DRIVE_MODE, x)
+
+#define CHIP_TOP_CUPHY_LED_CFG_LED_BLINK_MODE GENMASK(1, 0)
+#define CHIP_TOP_CUPHY_LED_CFG_LED_BLINK_MODE_SET(x)\
+ FIELD_PREP(CHIP_TOP_CUPHY_LED_CFG_LED_BLINK_MODE, x)
+#define CHIP_TOP_CUPHY_LED_CFG_LED_BLINK_MODE_GET(x)\
+ FIELD_GET(CHIP_TOP_CUPHY_LED_CFG_LED_BLINK_MODE, x)
+
+/* DEV:PORT_MODE:CLOCK_CFG */
+#define DEV_CLOCK_CFG(t) __REG(TARGET_DEV,\
+ t, 9, 0, 0, 1, 20, 0, 0, 1, 4)
+
+#define DEV_CLOCK_CFG_MAC_TX_RST BIT(7)
+#define DEV_CLOCK_CFG_MAC_TX_RST_SET(x)\
+ FIELD_PREP(DEV_CLOCK_CFG_MAC_TX_RST, x)
+#define DEV_CLOCK_CFG_MAC_TX_RST_GET(x)\
+ FIELD_GET(DEV_CLOCK_CFG_MAC_TX_RST, x)
+
+#define DEV_CLOCK_CFG_MAC_RX_RST BIT(6)
+#define DEV_CLOCK_CFG_MAC_RX_RST_SET(x)\
+ FIELD_PREP(DEV_CLOCK_CFG_MAC_RX_RST, x)
+#define DEV_CLOCK_CFG_MAC_RX_RST_GET(x)\
+ FIELD_GET(DEV_CLOCK_CFG_MAC_RX_RST, x)
+
+#define DEV_CLOCK_CFG_PCS_TX_RST BIT(5)
+#define DEV_CLOCK_CFG_PCS_TX_RST_SET(x)\
+ FIELD_PREP(DEV_CLOCK_CFG_PCS_TX_RST, x)
+#define DEV_CLOCK_CFG_PCS_TX_RST_GET(x)\
+ FIELD_GET(DEV_CLOCK_CFG_PCS_TX_RST, x)
+
+#define DEV_CLOCK_CFG_PCS_RX_RST BIT(4)
+#define DEV_CLOCK_CFG_PCS_RX_RST_SET(x)\
+ FIELD_PREP(DEV_CLOCK_CFG_PCS_RX_RST, x)
+#define DEV_CLOCK_CFG_PCS_RX_RST_GET(x)\
+ FIELD_GET(DEV_CLOCK_CFG_PCS_RX_RST, x)
+
+#define DEV_CLOCK_CFG_PORT_RST BIT(3)
+#define DEV_CLOCK_CFG_PORT_RST_SET(x)\
+ FIELD_PREP(DEV_CLOCK_CFG_PORT_RST, x)
+#define DEV_CLOCK_CFG_PORT_RST_GET(x)\
+ FIELD_GET(DEV_CLOCK_CFG_PORT_RST, x)
+
+#define DEV_CLOCK_CFG_PHY_RST BIT(2)
+#define DEV_CLOCK_CFG_PHY_RST_SET(x)\
+ FIELD_PREP(DEV_CLOCK_CFG_PHY_RST, x)
+#define DEV_CLOCK_CFG_PHY_RST_GET(x)\
+ FIELD_GET(DEV_CLOCK_CFG_PHY_RST, x)
+
+#define DEV_CLOCK_CFG_LINK_SPEED GENMASK(1, 0)
+#define DEV_CLOCK_CFG_LINK_SPEED_SET(x)\
+ FIELD_PREP(DEV_CLOCK_CFG_LINK_SPEED, x)
+#define DEV_CLOCK_CFG_LINK_SPEED_GET(x)\
+ FIELD_GET(DEV_CLOCK_CFG_LINK_SPEED, x)
+
+/* DEV:MAC_CFG_STATUS:MAC_ENA_CFG */
+#define DEV_MAC_ENA_CFG(t) __REG(TARGET_DEV,\
+ t, 9, 20, 0, 1, 44, 0, 0, 1, 4)
+
+#define DEV_MAC_ENA_CFG_RX_ENA BIT(4)
+#define DEV_MAC_ENA_CFG_RX_ENA_SET(x)\
+ FIELD_PREP(DEV_MAC_ENA_CFG_RX_ENA, x)
+#define DEV_MAC_ENA_CFG_RX_ENA_GET(x)\
+ FIELD_GET(DEV_MAC_ENA_CFG_RX_ENA, x)
+
+#define DEV_MAC_ENA_CFG_TX_ENA BIT(0)
+#define DEV_MAC_ENA_CFG_TX_ENA_SET(x)\
+ FIELD_PREP(DEV_MAC_ENA_CFG_TX_ENA, x)
+#define DEV_MAC_ENA_CFG_TX_ENA_GET(x)\
+ FIELD_GET(DEV_MAC_ENA_CFG_TX_ENA, x)
+
+/* DEV:MAC_CFG_STATUS:MAC_MODE_CFG */
+#define DEV_MAC_MODE_CFG(t) __REG(TARGET_DEV,\
+ t, 9, 20, 0, 1, 44, 4, 0, 1, 4)
+
+#define DEV_MAC_MODE_CFG_FC_WORD_SYNC_ENA BIT(8)
+#define DEV_MAC_MODE_CFG_FC_WORD_SYNC_ENA_SET(x)\
+ FIELD_PREP(DEV_MAC_MODE_CFG_FC_WORD_SYNC_ENA, x)
+#define DEV_MAC_MODE_CFG_FC_WORD_SYNC_ENA_GET(x)\
+ FIELD_GET(DEV_MAC_MODE_CFG_FC_WORD_SYNC_ENA, x)
+
+#define DEV_MAC_MODE_CFG_GIGA_MODE_ENA BIT(4)
+#define DEV_MAC_MODE_CFG_GIGA_MODE_ENA_SET(x)\
+ FIELD_PREP(DEV_MAC_MODE_CFG_GIGA_MODE_ENA, x)
+#define DEV_MAC_MODE_CFG_GIGA_MODE_ENA_GET(x)\
+ FIELD_GET(DEV_MAC_MODE_CFG_GIGA_MODE_ENA, x)
+
+#define DEV_MAC_MODE_CFG_FDX_ENA BIT(0)
+#define DEV_MAC_MODE_CFG_FDX_ENA_SET(x)\
+ FIELD_PREP(DEV_MAC_MODE_CFG_FDX_ENA, x)
+#define DEV_MAC_MODE_CFG_FDX_ENA_GET(x)\
+ FIELD_GET(DEV_MAC_MODE_CFG_FDX_ENA, x)
+
+/* DEV:MAC_CFG_STATUS:MAC_MAXLEN_CFG */
+#define DEV_MAC_MAXLEN_CFG(t) __REG(TARGET_DEV,\
+ t, 9, 20, 0, 1, 44, 8, 0, 1, 4)
+
+#define DEV_MAC_MAXLEN_CFG_MAX_LEN GENMASK(15, 0)
+#define DEV_MAC_MAXLEN_CFG_MAX_LEN_SET(x)\
+ FIELD_PREP(DEV_MAC_MAXLEN_CFG_MAX_LEN, x)
+#define DEV_MAC_MAXLEN_CFG_MAX_LEN_GET(x)\
+ FIELD_GET(DEV_MAC_MAXLEN_CFG_MAX_LEN, x)
+
+/* DEV:MAC_CFG_STATUS:MAC_TAGS_CFG */
+#define DEV_MAC_TAGS_CFG(t) __REG(TARGET_DEV,\
+ t, 9, 20, 0, 1, 44, 12, 0, 1, 4)
+
+#define DEV_MAC_TAGS_CFG_TAG_ID GENMASK(31, 16)
+#define DEV_MAC_TAGS_CFG_TAG_ID_SET(x)\
+ FIELD_PREP(DEV_MAC_TAGS_CFG_TAG_ID, x)
+#define DEV_MAC_TAGS_CFG_TAG_ID_GET(x)\
+ FIELD_GET(DEV_MAC_TAGS_CFG_TAG_ID, x)
+
+#define DEV_MAC_TAGS_CFG_PB_ENA BIT(1)
+#define DEV_MAC_TAGS_CFG_PB_ENA_SET(x)\
+ FIELD_PREP(DEV_MAC_TAGS_CFG_PB_ENA, x)
+#define DEV_MAC_TAGS_CFG_PB_ENA_GET(x)\
+ FIELD_GET(DEV_MAC_TAGS_CFG_PB_ENA, x)
+
+#define DEV_MAC_TAGS_CFG_VLAN_AWR_ENA BIT(0)
+#define DEV_MAC_TAGS_CFG_VLAN_AWR_ENA_SET(x)\
+ FIELD_PREP(DEV_MAC_TAGS_CFG_VLAN_AWR_ENA, x)
+#define DEV_MAC_TAGS_CFG_VLAN_AWR_ENA_GET(x)\
+ FIELD_GET(DEV_MAC_TAGS_CFG_VLAN_AWR_ENA, x)
+
+#define DEV_MAC_TAGS_CFG_VLAN_LEN_AWR_ENA BIT(2)
+#define DEV_MAC_TAGS_CFG_VLAN_LEN_AWR_ENA_SET(x)\
+ FIELD_PREP(DEV_MAC_TAGS_CFG_VLAN_LEN_AWR_ENA, x)
+#define DEV_MAC_TAGS_CFG_VLAN_LEN_AWR_ENA_GET(x)\
+ FIELD_GET(DEV_MAC_TAGS_CFG_VLAN_LEN_AWR_ENA, x)
+
+/* DEV:MAC_CFG_STATUS:MAC_IFG_CFG */
+#define DEV_MAC_IFG_CFG(t) __REG(TARGET_DEV,\
+ t, 9, 20, 0, 1, 44, 20, 0, 1, 4)
+
+#define DEV_MAC_IFG_CFG_OLD_IPG_CHECK BIT(17)
+#define DEV_MAC_IFG_CFG_OLD_IPG_CHECK_SET(x)\
+ FIELD_PREP(DEV_MAC_IFG_CFG_OLD_IPG_CHECK, x)
+#define DEV_MAC_IFG_CFG_OLD_IPG_CHECK_GET(x)\
+ FIELD_GET(DEV_MAC_IFG_CFG_OLD_IPG_CHECK, x)
+
+#define DEV_MAC_IFG_CFG_REDUCED_TX_IFG BIT(16)
+#define DEV_MAC_IFG_CFG_REDUCED_TX_IFG_SET(x)\
+ FIELD_PREP(DEV_MAC_IFG_CFG_REDUCED_TX_IFG, x)
+#define DEV_MAC_IFG_CFG_REDUCED_TX_IFG_GET(x)\
+ FIELD_GET(DEV_MAC_IFG_CFG_REDUCED_TX_IFG, x)
+
+#define DEV_MAC_IFG_CFG_TX_IFG GENMASK(12, 8)
+#define DEV_MAC_IFG_CFG_TX_IFG_SET(x)\
+ FIELD_PREP(DEV_MAC_IFG_CFG_TX_IFG, x)
+#define DEV_MAC_IFG_CFG_TX_IFG_GET(x)\
+ FIELD_GET(DEV_MAC_IFG_CFG_TX_IFG, x)
+
+#define DEV_MAC_IFG_CFG_RX_IFG2 GENMASK(7, 4)
+#define DEV_MAC_IFG_CFG_RX_IFG2_SET(x)\
+ FIELD_PREP(DEV_MAC_IFG_CFG_RX_IFG2, x)
+#define DEV_MAC_IFG_CFG_RX_IFG2_GET(x)\
+ FIELD_GET(DEV_MAC_IFG_CFG_RX_IFG2, x)
+
+#define DEV_MAC_IFG_CFG_RX_IFG1 GENMASK(3, 0)
+#define DEV_MAC_IFG_CFG_RX_IFG1_SET(x)\
+ FIELD_PREP(DEV_MAC_IFG_CFG_RX_IFG1, x)
+#define DEV_MAC_IFG_CFG_RX_IFG1_GET(x)\
+ FIELD_GET(DEV_MAC_IFG_CFG_RX_IFG1, x)
+
+/* DEV:MAC_CFG_STATUS:MAC_HDX_CFG */
+#define DEV_MAC_HDX_CFG(t) __REG(TARGET_DEV,\
+ t, 9, 20, 0, 1, 44, 24, 0, 1, 4)
+
+#define DEV_MAC_HDX_CFG_BYPASS_COL_SYNC BIT(26)
+#define DEV_MAC_HDX_CFG_BYPASS_COL_SYNC_SET(x)\
+ FIELD_PREP(DEV_MAC_HDX_CFG_BYPASS_COL_SYNC, x)
+#define DEV_MAC_HDX_CFG_BYPASS_COL_SYNC_GET(x)\
+ FIELD_GET(DEV_MAC_HDX_CFG_BYPASS_COL_SYNC, x)
+
+#define DEV_MAC_HDX_CFG_OB_ENA BIT(25)
+#define DEV_MAC_HDX_CFG_OB_ENA_SET(x)\
+ FIELD_PREP(DEV_MAC_HDX_CFG_OB_ENA, x)
+#define DEV_MAC_HDX_CFG_OB_ENA_GET(x)\
+ FIELD_GET(DEV_MAC_HDX_CFG_OB_ENA, x)
+
+#define DEV_MAC_HDX_CFG_WEXC_DIS BIT(24)
+#define DEV_MAC_HDX_CFG_WEXC_DIS_SET(x)\
+ FIELD_PREP(DEV_MAC_HDX_CFG_WEXC_DIS, x)
+#define DEV_MAC_HDX_CFG_WEXC_DIS_GET(x)\
+ FIELD_GET(DEV_MAC_HDX_CFG_WEXC_DIS, x)
+
+#define DEV_MAC_HDX_CFG_SEED GENMASK(23, 16)
+#define DEV_MAC_HDX_CFG_SEED_SET(x)\
+ FIELD_PREP(DEV_MAC_HDX_CFG_SEED, x)
+#define DEV_MAC_HDX_CFG_SEED_GET(x)\
+ FIELD_GET(DEV_MAC_HDX_CFG_SEED, x)
+
+#define DEV_MAC_HDX_CFG_SEED_LOAD BIT(12)
+#define DEV_MAC_HDX_CFG_SEED_LOAD_SET(x)\
+ FIELD_PREP(DEV_MAC_HDX_CFG_SEED_LOAD, x)
+#define DEV_MAC_HDX_CFG_SEED_LOAD_GET(x)\
+ FIELD_GET(DEV_MAC_HDX_CFG_SEED_LOAD, x)
+
+#define DEV_MAC_HDX_CFG_RETRY_EXC_COL_ENA BIT(8)
+#define DEV_MAC_HDX_CFG_RETRY_EXC_COL_ENA_SET(x)\
+ FIELD_PREP(DEV_MAC_HDX_CFG_RETRY_EXC_COL_ENA, x)
+#define DEV_MAC_HDX_CFG_RETRY_EXC_COL_ENA_GET(x)\
+ FIELD_GET(DEV_MAC_HDX_CFG_RETRY_EXC_COL_ENA, x)
+
+#define DEV_MAC_HDX_CFG_LATE_COL_POS GENMASK(6, 0)
+#define DEV_MAC_HDX_CFG_LATE_COL_POS_SET(x)\
+ FIELD_PREP(DEV_MAC_HDX_CFG_LATE_COL_POS, x)
+#define DEV_MAC_HDX_CFG_LATE_COL_POS_GET(x)\
+ FIELD_GET(DEV_MAC_HDX_CFG_LATE_COL_POS, x)
+
+/* DEV:MAC_CFG_STATUS:MAC_FC_MAC_LOW_CFG */
+#define DEV_FC_MAC_LOW_CFG(t) __REG(TARGET_DEV,\
+ t, 9, 20, 0, 1, 44, 32, 0, 1, 4)
+
+#define DEV_FC_MAC_LOW_CFG_MAC_LOW GENMASK(23, 0)
+#define DEV_FC_MAC_LOW_CFG_MAC_LOW_SET(x)\
+ FIELD_PREP(DEV_FC_MAC_LOW_CFG_MAC_LOW, x)
+#define DEV_FC_MAC_LOW_CFG_MAC_LOW_GET(x)\
+ FIELD_GET(DEV_FC_MAC_LOW_CFG_MAC_LOW, x)
+
+/* DEV:MAC_CFG_STATUS:MAC_FC_MAC_HIGH_CFG */
+#define DEV_FC_MAC_HIGH_CFG(t) __REG(TARGET_DEV,\
+ t, 9, 20, 0, 1, 44, 36, 0, 1, 4)
+
+#define DEV_FC_MAC_HIGH_CFG_MAC_HIGH GENMASK(23, 0)
+#define DEV_FC_MAC_HIGH_CFG_MAC_HIGH_SET(x)\
+ FIELD_PREP(DEV_FC_MAC_HIGH_CFG_MAC_HIGH, x)
+#define DEV_FC_MAC_HIGH_CFG_MAC_HIGH_GET(x)\
+ FIELD_GET(DEV_FC_MAC_HIGH_CFG_MAC_HIGH, x)
+
+/* DEV:PCS1G_CFG_STATUS:PCS1G_CFG */
+#define DEV_PCS1G_CFG(t) __REG(TARGET_DEV,\
+ t, 9, 64, 0, 1, 68, 0, 0, 1, 4)
+
+#define DEV_PCS1G_CFG_LINK_STATUS_TYPE BIT(4)
+#define DEV_PCS1G_CFG_LINK_STATUS_TYPE_SET(x)\
+ FIELD_PREP(DEV_PCS1G_CFG_LINK_STATUS_TYPE, x)
+#define DEV_PCS1G_CFG_LINK_STATUS_TYPE_GET(x)\
+ FIELD_GET(DEV_PCS1G_CFG_LINK_STATUS_TYPE, x)
+
+#define DEV_PCS1G_CFG_AN_LINK_CTRL_ENA BIT(1)
+#define DEV_PCS1G_CFG_AN_LINK_CTRL_ENA_SET(x)\
+ FIELD_PREP(DEV_PCS1G_CFG_AN_LINK_CTRL_ENA, x)
+#define DEV_PCS1G_CFG_AN_LINK_CTRL_ENA_GET(x)\
+ FIELD_GET(DEV_PCS1G_CFG_AN_LINK_CTRL_ENA, x)
+
+#define DEV_PCS1G_CFG_PCS_ENA BIT(0)
+#define DEV_PCS1G_CFG_PCS_ENA_SET(x)\
+ FIELD_PREP(DEV_PCS1G_CFG_PCS_ENA, x)
+#define DEV_PCS1G_CFG_PCS_ENA_GET(x)\
+ FIELD_GET(DEV_PCS1G_CFG_PCS_ENA, x)
+
+/* DEV:PCS1G_CFG_STATUS:PCS1G_SD_CFG */
+#define DEV_PCS1G_SD_CFG(t) __REG(TARGET_DEV,\
+ t, 9, 64, 0, 1, 68, 8, 0, 1, 4)
+
+#define DEV_PCS1G_SD_CFG_SD_SEL BIT(8)
+#define DEV_PCS1G_SD_CFG_SD_SEL_SET(x)\
+ FIELD_PREP(DEV_PCS1G_SD_CFG_SD_SEL, x)
+#define DEV_PCS1G_SD_CFG_SD_SEL_GET(x)\
+ FIELD_GET(DEV_PCS1G_SD_CFG_SD_SEL, x)
+
+#define DEV_PCS1G_SD_CFG_SD_POL BIT(4)
+#define DEV_PCS1G_SD_CFG_SD_POL_SET(x)\
+ FIELD_PREP(DEV_PCS1G_SD_CFG_SD_POL, x)
+#define DEV_PCS1G_SD_CFG_SD_POL_GET(x)\
+ FIELD_GET(DEV_PCS1G_SD_CFG_SD_POL, x)
+
+#define DEV_PCS1G_SD_CFG_SD_ENA BIT(0)
+#define DEV_PCS1G_SD_CFG_SD_ENA_SET(x)\
+ FIELD_PREP(DEV_PCS1G_SD_CFG_SD_ENA, x)
+#define DEV_PCS1G_SD_CFG_SD_ENA_GET(x)\
+ FIELD_GET(DEV_PCS1G_SD_CFG_SD_ENA, x)
+
+/* DEVCPU_GCB:CHIP_REGS:FEAT_DISABLE */
+#define GCB_FEAT_DISABLE __REG(TARGET_GCB,\
+ 0, 1, 0, 0, 1, 28, 20, 0, 1, 4)
+
+#define GCB_FEAT_DISABLE_FEAT_EEPROM_CFG_DIS BIT(0)
+#define GCB_FEAT_DISABLE_FEAT_EEPROM_CFG_DIS_SET(x)\
+ FIELD_PREP(GCB_FEAT_DISABLE_FEAT_EEPROM_CFG_DIS, x)
+#define GCB_FEAT_DISABLE_FEAT_EEPROM_CFG_DIS_GET(x)\
+ FIELD_GET(GCB_FEAT_DISABLE_FEAT_EEPROM_CFG_DIS, x)
+
+#define GCB_FEAT_DISABLE_FEAT_EEPROM_BOOT_DIS BIT(1)
+#define GCB_FEAT_DISABLE_FEAT_EEPROM_BOOT_DIS_SET(x)\
+ FIELD_PREP(GCB_FEAT_DISABLE_FEAT_EEPROM_BOOT_DIS, x)
+#define GCB_FEAT_DISABLE_FEAT_EEPROM_BOOT_DIS_GET(x)\
+ FIELD_GET(GCB_FEAT_DISABLE_FEAT_EEPROM_BOOT_DIS, x)
+
+#define GCB_FEAT_DISABLE_FEAT_EEPROM_FW_PATCH_DIS BIT(2)
+#define GCB_FEAT_DISABLE_FEAT_EEPROM_FW_PATCH_DIS_SET(x)\
+ FIELD_PREP(GCB_FEAT_DISABLE_FEAT_EEPROM_FW_PATCH_DIS, x)
+#define GCB_FEAT_DISABLE_FEAT_EEPROM_FW_PATCH_DIS_GET(x)\
+ FIELD_GET(GCB_FEAT_DISABLE_FEAT_EEPROM_FW_PATCH_DIS, x)
+
+#define GCB_FEAT_DISABLE_FEAT_MMU_BACKDOOR_WR_DIS BIT(3)
+#define GCB_FEAT_DISABLE_FEAT_MMU_BACKDOOR_WR_DIS_SET(x)\
+ FIELD_PREP(GCB_FEAT_DISABLE_FEAT_MMU_BACKDOOR_WR_DIS, x)
+#define GCB_FEAT_DISABLE_FEAT_MMU_BACKDOOR_WR_DIS_GET(x)\
+ FIELD_GET(GCB_FEAT_DISABLE_FEAT_MMU_BACKDOOR_WR_DIS, x)
+
+#define GCB_FEAT_DISABLE_FEAT_MGMT_IF_WR_DIS BIT(4)
+#define GCB_FEAT_DISABLE_FEAT_MGMT_IF_WR_DIS_SET(x)\
+ FIELD_PREP(GCB_FEAT_DISABLE_FEAT_MGMT_IF_WR_DIS, x)
+#define GCB_FEAT_DISABLE_FEAT_MGMT_IF_WR_DIS_GET(x)\
+ FIELD_GET(GCB_FEAT_DISABLE_FEAT_MGMT_IF_WR_DIS, x)
+
+#define GCB_FEAT_DISABLE_FEAT_DD_DIS BIT(5)
+#define GCB_FEAT_DISABLE_FEAT_DD_DIS_SET(x)\
+ FIELD_PREP(GCB_FEAT_DISABLE_FEAT_DD_DIS, x)
+#define GCB_FEAT_DISABLE_FEAT_DD_DIS_GET(x)\
+ FIELD_GET(GCB_FEAT_DISABLE_FEAT_DD_DIS, x)
+
+#define GCB_FEAT_DISABLE_FEAT_TSN_DIS BIT(6)
+#define GCB_FEAT_DISABLE_FEAT_TSN_DIS_SET(x)\
+ FIELD_PREP(GCB_FEAT_DISABLE_FEAT_TSN_DIS, x)
+#define GCB_FEAT_DISABLE_FEAT_TSN_DIS_GET(x)\
+ FIELD_GET(GCB_FEAT_DISABLE_FEAT_TSN_DIS, x)
+
+#define GCB_FEAT_DISABLE_FEAT_PTP_DIS BIT(7)
+#define GCB_FEAT_DISABLE_FEAT_PTP_DIS_SET(x)\
+ FIELD_PREP(GCB_FEAT_DISABLE_FEAT_PTP_DIS, x)
+#define GCB_FEAT_DISABLE_FEAT_PTP_DIS_GET(x)\
+ FIELD_GET(GCB_FEAT_DISABLE_FEAT_PTP_DIS, x)
+
+#define GCB_FEAT_DISABLE_FEAT_FRER_DIS BIT(8)
+#define GCB_FEAT_DISABLE_FEAT_FRER_DIS_SET(x)\
+ FIELD_PREP(GCB_FEAT_DISABLE_FEAT_FRER_DIS, x)
+#define GCB_FEAT_DISABLE_FEAT_FRER_DIS_GET(x)\
+ FIELD_GET(GCB_FEAT_DISABLE_FEAT_FRER_DIS, x)
+
+#define GCB_FEAT_DISABLE_FEAT_NUM_PORTS_DIS GENMASK(14, 12)
+#define GCB_FEAT_DISABLE_FEAT_NUM_PORTS_DIS_SET(x)\
+ FIELD_PREP(GCB_FEAT_DISABLE_FEAT_NUM_PORTS_DIS, x)
+#define GCB_FEAT_DISABLE_FEAT_NUM_PORTS_DIS_GET(x)\
+ FIELD_GET(GCB_FEAT_DISABLE_FEAT_NUM_PORTS_DIS, x)
+
+#define GCB_FEAT_DISABLE_FEAT_NUM_CU_DIS GENMASK(16, 15)
+#define GCB_FEAT_DISABLE_FEAT_NUM_CU_DIS_SET(x)\
+ FIELD_PREP(GCB_FEAT_DISABLE_FEAT_NUM_CU_DIS, x)
+#define GCB_FEAT_DISABLE_FEAT_NUM_CU_DIS_GET(x)\
+ FIELD_GET(GCB_FEAT_DISABLE_FEAT_NUM_CU_DIS, x)
+
+#define GCB_FEAT_DISABLE_FEAT_RGMII_DIS GENMASK(18, 17)
+#define GCB_FEAT_DISABLE_FEAT_RGMII_DIS_SET(x)\
+ FIELD_PREP(GCB_FEAT_DISABLE_FEAT_RGMII_DIS, x)
+#define GCB_FEAT_DISABLE_FEAT_RGMII_DIS_GET(x)\
+ FIELD_GET(GCB_FEAT_DISABLE_FEAT_RGMII_DIS, x)
+
+#define GCB_FEAT_DISABLE_FEAT_SGMII_DIS GENMASK(20, 19)
+#define GCB_FEAT_DISABLE_FEAT_SGMII_DIS_SET(x)\
+ FIELD_PREP(GCB_FEAT_DISABLE_FEAT_SGMII_DIS, x)
+#define GCB_FEAT_DISABLE_FEAT_SGMII_DIS_GET(x)\
+ FIELD_GET(GCB_FEAT_DISABLE_FEAT_SGMII_DIS, x)
+
+#define GCB_FEAT_DISABLE_FEAT_QSGMII_DIS BIT(21)
+#define GCB_FEAT_DISABLE_FEAT_QSGMII_DIS_SET(x)\
+ FIELD_PREP(GCB_FEAT_DISABLE_FEAT_QSGMII_DIS, x)
+#define GCB_FEAT_DISABLE_FEAT_QSGMII_DIS_GET(x)\
+ FIELD_GET(GCB_FEAT_DISABLE_FEAT_QSGMII_DIS, x)
+
+#define GCB_FEAT_DISABLE_FEAT_NUM_CU_FIXED BIT(22)
+#define GCB_FEAT_DISABLE_FEAT_NUM_CU_FIXED_SET(x)\
+ FIELD_PREP(GCB_FEAT_DISABLE_FEAT_NUM_CU_FIXED, x)
+#define GCB_FEAT_DISABLE_FEAT_NUM_CU_FIXED_GET(x)\
+ FIELD_GET(GCB_FEAT_DISABLE_FEAT_NUM_CU_FIXED, x)
+
+#define GCB_FEAT_DISABLE_FEAT_WDT_ENABLED BIT(25)
+#define GCB_FEAT_DISABLE_FEAT_WDT_ENABLED_SET(x)\
+ FIELD_PREP(GCB_FEAT_DISABLE_FEAT_WDT_ENABLED, x)
+#define GCB_FEAT_DISABLE_FEAT_WDT_ENABLED_GET(x)\
+ FIELD_GET(GCB_FEAT_DISABLE_FEAT_WDT_ENABLED, x)
+
+/* HSIO:HW_CFGSTAT:HW_CFG */
+#define HSIO_HW_CFG __REG(TARGET_HSIO,\
+ 0, 1, 72, 0, 1, 44, 0, 0, 1, 4)
+
+#define HSIO_HW_CFG_RGMII_0_CFG BIT(10)
+#define HSIO_HW_CFG_RGMII_0_CFG_SET(x)\
+ FIELD_PREP(HSIO_HW_CFG_RGMII_0_CFG, x)
+#define HSIO_HW_CFG_RGMII_0_CFG_GET(x)\
+ FIELD_GET(HSIO_HW_CFG_RGMII_0_CFG, x)
+
+#define HSIO_HW_CFG_GMII_ENA GENMASK(9, 1)
+#define HSIO_HW_CFG_GMII_ENA_SET(x)\
+ FIELD_PREP(HSIO_HW_CFG_GMII_ENA, x)
+#define HSIO_HW_CFG_GMII_ENA_GET(x)\
+ FIELD_GET(HSIO_HW_CFG_GMII_ENA, x)
+
+#define HSIO_HW_CFG_QSGMII_ENA BIT(0)
+#define HSIO_HW_CFG_QSGMII_ENA_SET(x)\
+ FIELD_PREP(HSIO_HW_CFG_QSGMII_ENA, x)
+#define HSIO_HW_CFG_QSGMII_ENA_GET(x)\
+ FIELD_GET(HSIO_HW_CFG_QSGMII_ENA, x)
+
+/* HSIO:HW_CFGSTAT:RGMII_CFG */
+#define HSIO_RGMII_CFG(r) __REG(TARGET_HSIO,\
+ 0, 1, 72, 0, 1, 44, 12, r, 2, 4)
+
+#define HSIO_RGMII_CFG_IB_RX_LINK_STATUS BIT(15)
+#define HSIO_RGMII_CFG_IB_RX_LINK_STATUS_SET(x)\
+ FIELD_PREP(HSIO_RGMII_CFG_IB_RX_LINK_STATUS, x)
+#define HSIO_RGMII_CFG_IB_RX_LINK_STATUS_GET(x)\
+ FIELD_GET(HSIO_RGMII_CFG_IB_RX_LINK_STATUS, x)
+
+#define HSIO_RGMII_CFG_IB_RX_DUPLEX BIT(14)
+#define HSIO_RGMII_CFG_IB_RX_DUPLEX_SET(x)\
+ FIELD_PREP(HSIO_RGMII_CFG_IB_RX_DUPLEX, x)
+#define HSIO_RGMII_CFG_IB_RX_DUPLEX_GET(x)\
+ FIELD_GET(HSIO_RGMII_CFG_IB_RX_DUPLEX, x)
+
+#define HSIO_RGMII_CFG_IB_RX_SPEED GENMASK(13, 12)
+#define HSIO_RGMII_CFG_IB_RX_SPEED_SET(x)\
+ FIELD_PREP(HSIO_RGMII_CFG_IB_RX_SPEED, x)
+#define HSIO_RGMII_CFG_IB_RX_SPEED_GET(x)\
+ FIELD_GET(HSIO_RGMII_CFG_IB_RX_SPEED, x)
+
+#define HSIO_RGMII_CFG_IB_TX_LINK_STATUS BIT(11)
+#define HSIO_RGMII_CFG_IB_TX_LINK_STATUS_SET(x)\
+ FIELD_PREP(HSIO_RGMII_CFG_IB_TX_LINK_STATUS, x)
+#define HSIO_RGMII_CFG_IB_TX_LINK_STATUS_GET(x)\
+ FIELD_GET(HSIO_RGMII_CFG_IB_TX_LINK_STATUS, x)
+
+#define HSIO_RGMII_CFG_IB_TX_FDX BIT(10)
+#define HSIO_RGMII_CFG_IB_TX_FDX_SET(x)\
+ FIELD_PREP(HSIO_RGMII_CFG_IB_TX_FDX, x)
+#define HSIO_RGMII_CFG_IB_TX_FDX_GET(x)\
+ FIELD_GET(HSIO_RGMII_CFG_IB_TX_FDX, x)
+
+#define HSIO_RGMII_CFG_IB_TX_MII_SPD BIT(9)
+#define HSIO_RGMII_CFG_IB_TX_MII_SPD_SET(x)\
+ FIELD_PREP(HSIO_RGMII_CFG_IB_TX_MII_SPD, x)
+#define HSIO_RGMII_CFG_IB_TX_MII_SPD_GET(x)\
+ FIELD_GET(HSIO_RGMII_CFG_IB_TX_MII_SPD, x)
+
+#define HSIO_RGMII_CFG_IB_TX_SPD_1G BIT(8)
+#define HSIO_RGMII_CFG_IB_TX_SPD_1G_SET(x)\
+ FIELD_PREP(HSIO_RGMII_CFG_IB_TX_SPD_1G, x)
+#define HSIO_RGMII_CFG_IB_TX_SPD_1G_GET(x)\
+ FIELD_GET(HSIO_RGMII_CFG_IB_TX_SPD_1G, x)
+
+#define HSIO_RGMII_CFG_IB_TX_ENA BIT(7)
+#define HSIO_RGMII_CFG_IB_TX_ENA_SET(x)\
+ FIELD_PREP(HSIO_RGMII_CFG_IB_TX_ENA, x)
+#define HSIO_RGMII_CFG_IB_TX_ENA_GET(x)\
+ FIELD_GET(HSIO_RGMII_CFG_IB_TX_ENA, x)
+
+#define HSIO_RGMII_CFG_IB_RX_ENA BIT(6)
+#define HSIO_RGMII_CFG_IB_RX_ENA_SET(x)\
+ FIELD_PREP(HSIO_RGMII_CFG_IB_RX_ENA, x)
+#define HSIO_RGMII_CFG_IB_RX_ENA_GET(x)\
+ FIELD_GET(HSIO_RGMII_CFG_IB_RX_ENA, x)
+
+#define HSIO_RGMII_CFG_IB_ENA BIT(5)
+#define HSIO_RGMII_CFG_IB_ENA_SET(x)\
+ FIELD_PREP(HSIO_RGMII_CFG_IB_ENA, x)
+#define HSIO_RGMII_CFG_IB_ENA_GET(x)\
+ FIELD_GET(HSIO_RGMII_CFG_IB_ENA, x)
+
+#define HSIO_RGMII_CFG_TX_CLK_CFG GENMASK(4, 2)
+#define HSIO_RGMII_CFG_TX_CLK_CFG_SET(x)\
+ FIELD_PREP(HSIO_RGMII_CFG_TX_CLK_CFG, x)
+#define HSIO_RGMII_CFG_TX_CLK_CFG_GET(x)\
+ FIELD_GET(HSIO_RGMII_CFG_TX_CLK_CFG, x)
+
+#define HSIO_RGMII_CFG_RGMII_TX_RST BIT(1)
+#define HSIO_RGMII_CFG_RGMII_TX_RST_SET(x)\
+ FIELD_PREP(HSIO_RGMII_CFG_RGMII_TX_RST, x)
+#define HSIO_RGMII_CFG_RGMII_TX_RST_GET(x)\
+ FIELD_GET(HSIO_RGMII_CFG_RGMII_TX_RST, x)
+
+#define HSIO_RGMII_CFG_RGMII_RX_RST BIT(0)
+#define HSIO_RGMII_CFG_RGMII_RX_RST_SET(x)\
+ FIELD_PREP(HSIO_RGMII_CFG_RGMII_RX_RST, x)
+#define HSIO_RGMII_CFG_RGMII_RX_RST_GET(x)\
+ FIELD_GET(HSIO_RGMII_CFG_RGMII_RX_RST, x)
+
+/* HSIO:HW_CFGSTAT:DLL_CFG */
+#define HSIO_DLL_CFG(r) __REG(TARGET_HSIO,\
+ 0, 1, 72, 0, 1, 44, 28, r, 4, 4)
+
+#define HSIO_DLL_CFG_DLL_CLK_ENA BIT(20)
+#define HSIO_DLL_CFG_DLL_CLK_ENA_SET(x)\
+ FIELD_PREP(HSIO_DLL_CFG_DLL_CLK_ENA, x)
+#define HSIO_DLL_CFG_DLL_CLK_ENA_GET(x)\
+ FIELD_GET(HSIO_DLL_CFG_DLL_CLK_ENA, x)
+
+#define HSIO_DLL_CFG_BIST_PASS BIT(19)
+#define HSIO_DLL_CFG_BIST_PASS_SET(x)\
+ FIELD_PREP(HSIO_DLL_CFG_BIST_PASS, x)
+#define HSIO_DLL_CFG_BIST_PASS_GET(x)\
+ FIELD_GET(HSIO_DLL_CFG_BIST_PASS, x)
+
+#define HSIO_DLL_CFG_BIST_END BIT(18)
+#define HSIO_DLL_CFG_BIST_END_SET(x)\
+ FIELD_PREP(HSIO_DLL_CFG_BIST_END, x)
+#define HSIO_DLL_CFG_BIST_END_GET(x)\
+ FIELD_GET(HSIO_DLL_CFG_BIST_END, x)
+
+#define HSIO_DLL_CFG_BIST_START BIT(17)
+#define HSIO_DLL_CFG_BIST_START_SET(x)\
+ FIELD_PREP(HSIO_DLL_CFG_BIST_START, x)
+#define HSIO_DLL_CFG_BIST_START_GET(x)\
+ FIELD_GET(HSIO_DLL_CFG_BIST_START, x)
+
+#define HSIO_DLL_CFG_TAP_SEL GENMASK(16, 10)
+#define HSIO_DLL_CFG_TAP_SEL_SET(x)\
+ FIELD_PREP(HSIO_DLL_CFG_TAP_SEL, x)
+#define HSIO_DLL_CFG_TAP_SEL_GET(x)\
+ FIELD_GET(HSIO_DLL_CFG_TAP_SEL, x)
+
+#define HSIO_DLL_CFG_TAP_ADJ GENMASK(9, 3)
+#define HSIO_DLL_CFG_TAP_ADJ_SET(x)\
+ FIELD_PREP(HSIO_DLL_CFG_TAP_ADJ, x)
+#define HSIO_DLL_CFG_TAP_ADJ_GET(x)\
+ FIELD_GET(HSIO_DLL_CFG_TAP_ADJ, x)
+
+#define HSIO_DLL_CFG_DELAY_ENA BIT(2)
+#define HSIO_DLL_CFG_DELAY_ENA_SET(x)\
+ FIELD_PREP(HSIO_DLL_CFG_DELAY_ENA, x)
+#define HSIO_DLL_CFG_DELAY_ENA_GET(x)\
+ FIELD_GET(HSIO_DLL_CFG_DELAY_ENA, x)
+
+#define HSIO_DLL_CFG_DLL_ENA BIT(1)
+#define HSIO_DLL_CFG_DLL_ENA_SET(x)\
+ FIELD_PREP(HSIO_DLL_CFG_DLL_ENA, x)
+#define HSIO_DLL_CFG_DLL_ENA_GET(x)\
+ FIELD_GET(HSIO_DLL_CFG_DLL_ENA, x)
+
+#define HSIO_DLL_CFG_DLL_RST BIT(0)
+#define HSIO_DLL_CFG_DLL_RST_SET(x)\
+ FIELD_PREP(HSIO_DLL_CFG_DLL_RST, x)
+#define HSIO_DLL_CFG_DLL_RST_GET(x)\
+ FIELD_GET(HSIO_DLL_CFG_DLL_RST, x)
+
+/* DEVCPU_QS:XTR:XTR_GRP_CFG */
+#define QS_XTR_GRP_CFG(r) __REG(TARGET_QS,\
+ 0, 1, 0, 0, 1, 36, 0, r, 2, 4)
+
+#define QS_XTR_GRP_CFG_MODE GENMASK(3, 2)
+#define QS_XTR_GRP_CFG_MODE_SET(x)\
+ FIELD_PREP(QS_XTR_GRP_CFG_MODE, x)
+#define QS_XTR_GRP_CFG_MODE_GET(x)\
+ FIELD_GET(QS_XTR_GRP_CFG_MODE, x)
+
+#define QS_XTR_GRP_CFG_STATUS_WORD_POS BIT(1)
+#define QS_XTR_GRP_CFG_STATUS_WORD_POS_SET(x)\
+ FIELD_PREP(QS_XTR_GRP_CFG_STATUS_WORD_POS, x)
+#define QS_XTR_GRP_CFG_STATUS_WORD_POS_GET(x)\
+ FIELD_GET(QS_XTR_GRP_CFG_STATUS_WORD_POS, x)
+
+#define QS_XTR_GRP_CFG_BYTE_SWAP BIT(0)
+#define QS_XTR_GRP_CFG_BYTE_SWAP_SET(x)\
+ FIELD_PREP(QS_XTR_GRP_CFG_BYTE_SWAP, x)
+#define QS_XTR_GRP_CFG_BYTE_SWAP_GET(x)\
+ FIELD_GET(QS_XTR_GRP_CFG_BYTE_SWAP, x)
+
+/* DEVCPU_QS:XTR:XTR_FLUSH */
+#define QS_XTR_FLUSH __REG(TARGET_QS,\
+ 0, 1, 0, 0, 1, 36, 24, 0, 1, 4)
+
+#define QS_XTR_FLUSH_FLUSH GENMASK(1, 0)
+#define QS_XTR_FLUSH_FLUSH_SET(x)\
+ FIELD_PREP(QS_XTR_FLUSH_FLUSH, x)
+#define QS_XTR_FLUSH_FLUSH_GET(x)\
+ FIELD_GET(QS_XTR_FLUSH_FLUSH, x)
+
+/* DEVCPU_QS:INJ:INJ_GRP_CFG */
+#define QS_INJ_GRP_CFG(r) __REG(TARGET_QS,\
+ 0, 1, 36, 0, 1, 40, 0, r, 2, 4)
+
+#define QS_INJ_GRP_CFG_MODE GENMASK(3, 2)
+#define QS_INJ_GRP_CFG_MODE_SET(x)\
+ FIELD_PREP(QS_INJ_GRP_CFG_MODE, x)
+#define QS_INJ_GRP_CFG_MODE_GET(x)\
+ FIELD_GET(QS_INJ_GRP_CFG_MODE, x)
+
+#define QS_INJ_GRP_CFG_BYTE_SWAP BIT(0)
+#define QS_INJ_GRP_CFG_BYTE_SWAP_SET(x)\
+ FIELD_PREP(QS_INJ_GRP_CFG_BYTE_SWAP, x)
+#define QS_INJ_GRP_CFG_BYTE_SWAP_GET(x)\
+ FIELD_GET(QS_INJ_GRP_CFG_BYTE_SWAP, x)
+
+/* DEVCPU_QS:INJ:INJ_CTRL */
+#define QS_INJ_CTRL(r) __REG(TARGET_QS,\
+ 0, 1, 36, 0, 1, 40, 16, r, 2, 4)
+
+#define QS_INJ_CTRL_GAP_SIZE GENMASK(24, 21)
+#define QS_INJ_CTRL_GAP_SIZE_SET(x)\
+ FIELD_PREP(QS_INJ_CTRL_GAP_SIZE, x)
+#define QS_INJ_CTRL_GAP_SIZE_GET(x)\
+ FIELD_GET(QS_INJ_CTRL_GAP_SIZE, x)
+
+#define QS_INJ_CTRL_ABORT BIT(20)
+#define QS_INJ_CTRL_ABORT_SET(x)\
+ FIELD_PREP(QS_INJ_CTRL_ABORT, x)
+#define QS_INJ_CTRL_ABORT_GET(x)\
+ FIELD_GET(QS_INJ_CTRL_ABORT, x)
+
+#define QS_INJ_CTRL_EOF BIT(19)
+#define QS_INJ_CTRL_EOF_SET(x)\
+ FIELD_PREP(QS_INJ_CTRL_EOF, x)
+#define QS_INJ_CTRL_EOF_GET(x)\
+ FIELD_GET(QS_INJ_CTRL_EOF, x)
+
+#define QS_INJ_CTRL_SOF BIT(18)
+#define QS_INJ_CTRL_SOF_SET(x)\
+ FIELD_PREP(QS_INJ_CTRL_SOF, x)
+#define QS_INJ_CTRL_SOF_GET(x)\
+ FIELD_GET(QS_INJ_CTRL_SOF, x)
+
+#define QS_INJ_CTRL_VLD_BYTES GENMASK(17, 16)
+#define QS_INJ_CTRL_VLD_BYTES_SET(x)\
+ FIELD_PREP(QS_INJ_CTRL_VLD_BYTES, x)
+#define QS_INJ_CTRL_VLD_BYTES_GET(x)\
+ FIELD_GET(QS_INJ_CTRL_VLD_BYTES, x)
+
+/* QSYS:SYSTEM:PORT_MODE */
+#define QSYS_PORT_MODE(r) __REG(TARGET_QSYS,\
+ 0, 1, 14336, 0, 1, 240, 0, r, 11, 4)
+
+#define QSYS_PORT_MODE_DEQUEUE_DIS BIT(1)
+#define QSYS_PORT_MODE_DEQUEUE_DIS_SET(x)\
+ FIELD_PREP(QSYS_PORT_MODE_DEQUEUE_DIS, x)
+#define QSYS_PORT_MODE_DEQUEUE_DIS_GET(x)\
+ FIELD_GET(QSYS_PORT_MODE_DEQUEUE_DIS, x)
+
+#define QSYS_PORT_MODE_DEQUEUE_LATE BIT(0)
+#define QSYS_PORT_MODE_DEQUEUE_LATE_SET(x)\
+ FIELD_PREP(QSYS_PORT_MODE_DEQUEUE_LATE, x)
+#define QSYS_PORT_MODE_DEQUEUE_LATE_GET(x)\
+ FIELD_GET(QSYS_PORT_MODE_DEQUEUE_LATE, x)
+
+/* QSYS:SYSTEM:SWITCH_PORT_MODE */
+#define QSYS_SW_PORT_MODE(r) __REG(TARGET_QSYS,\
+ 0, 1, 14336, 0, 1, 240, 88, r, 10, 4)
+
+#define QSYS_SW_PORT_MODE_PORT_ENA BIT(19)
+#define QSYS_SW_PORT_MODE_PORT_ENA_SET(x)\
+ FIELD_PREP(QSYS_SW_PORT_MODE_PORT_ENA, x)
+#define QSYS_SW_PORT_MODE_PORT_ENA_GET(x)\
+ FIELD_GET(QSYS_SW_PORT_MODE_PORT_ENA, x)
+
+#define QSYS_SW_PORT_MODE_IDEQ_DIS BIT(18)
+#define QSYS_SW_PORT_MODE_IDEQ_DIS_SET(x)\
+ FIELD_PREP(QSYS_SW_PORT_MODE_IDEQ_DIS, x)
+#define QSYS_SW_PORT_MODE_IDEQ_DIS_GET(x)\
+ FIELD_GET(QSYS_SW_PORT_MODE_IDEQ_DIS, x)
+
+#define QSYS_SW_PORT_MODE_SCH_NEXT_CFG GENMASK(17, 15)
+#define QSYS_SW_PORT_MODE_SCH_NEXT_CFG_SET(x)\
+ FIELD_PREP(QSYS_SW_PORT_MODE_SCH_NEXT_CFG, x)
+#define QSYS_SW_PORT_MODE_SCH_NEXT_CFG_GET(x)\
+ FIELD_GET(QSYS_SW_PORT_MODE_SCH_NEXT_CFG, x)
+
+#define QSYS_SW_PORT_MODE_YEL_RSRVD BIT(14)
+#define QSYS_SW_PORT_MODE_YEL_RSRVD_SET(x)\
+ FIELD_PREP(QSYS_SW_PORT_MODE_YEL_RSRVD, x)
+#define QSYS_SW_PORT_MODE_YEL_RSRVD_GET(x)\
+ FIELD_GET(QSYS_SW_PORT_MODE_YEL_RSRVD, x)
+
+#define QSYS_SW_PORT_MODE_INGRESS_DROP_MODE BIT(13)
+#define QSYS_SW_PORT_MODE_INGRESS_DROP_MODE_SET(x)\
+ FIELD_PREP(QSYS_SW_PORT_MODE_INGRESS_DROP_MODE, x)
+#define QSYS_SW_PORT_MODE_INGRESS_DROP_MODE_GET(x)\
+ FIELD_GET(QSYS_SW_PORT_MODE_INGRESS_DROP_MODE, x)
+
+#define QSYS_SW_PORT_MODE_TX_PFC_ENA GENMASK(12, 5)
+#define QSYS_SW_PORT_MODE_TX_PFC_ENA_SET(x)\
+ FIELD_PREP(QSYS_SW_PORT_MODE_TX_PFC_ENA, x)
+#define QSYS_SW_PORT_MODE_TX_PFC_ENA_GET(x)\
+ FIELD_GET(QSYS_SW_PORT_MODE_TX_PFC_ENA, x)
+
+#define QSYS_SW_PORT_MODE_TX_PFC_MODE BIT(4)
+#define QSYS_SW_PORT_MODE_TX_PFC_MODE_SET(x)\
+ FIELD_PREP(QSYS_SW_PORT_MODE_TX_PFC_MODE, x)
+#define QSYS_SW_PORT_MODE_TX_PFC_MODE_GET(x)\
+ FIELD_GET(QSYS_SW_PORT_MODE_TX_PFC_MODE, x)
+
+#define QSYS_SW_PORT_MODE_FWD_TWOCYCLE_MODE BIT(3)
+#define QSYS_SW_PORT_MODE_FWD_TWOCYCLE_MODE_SET(x)\
+ FIELD_PREP(QSYS_SW_PORT_MODE_FWD_TWOCYCLE_MODE, x)
+#define QSYS_SW_PORT_MODE_FWD_TWOCYCLE_MODE_GET(x)\
+ FIELD_GET(QSYS_SW_PORT_MODE_FWD_TWOCYCLE_MODE, x)
+
+#define QSYS_SW_PORT_MODE_AGING_MODE GENMASK(2, 1)
+#define QSYS_SW_PORT_MODE_AGING_MODE_SET(x)\
+ FIELD_PREP(QSYS_SW_PORT_MODE_AGING_MODE, x)
+#define QSYS_SW_PORT_MODE_AGING_MODE_GET(x)\
+ FIELD_GET(QSYS_SW_PORT_MODE_AGING_MODE, x)
+
+#define QSYS_SW_PORT_MODE_HOL_PROTECTED BIT(0)
+#define QSYS_SW_PORT_MODE_HOL_PROTECTED_SET(x)\
+ FIELD_PREP(QSYS_SW_PORT_MODE_HOL_PROTECTED, x)
+#define QSYS_SW_PORT_MODE_HOL_PROTECTED_GET(x)\
+ FIELD_GET(QSYS_SW_PORT_MODE_HOL_PROTECTED, x)
+
+/* QSYS:SYSTEM:EGR_NO_SHARING */
+#define QSYS_EGR_NO_SHARING __REG(TARGET_QSYS,\
+ 0, 1, 14336, 0, 1, 240, 176, 0, 1, 4)
+
+#define QSYS_EGR_NO_SHARING_EGR_NO_SHARING GENMASK(9, 0)
+#define QSYS_EGR_NO_SHARING_EGR_NO_SHARING_SET(x)\
+ FIELD_PREP(QSYS_EGR_NO_SHARING_EGR_NO_SHARING, x)
+#define QSYS_EGR_NO_SHARING_EGR_NO_SHARING_GET(x)\
+ FIELD_GET(QSYS_EGR_NO_SHARING_EGR_NO_SHARING, x)
+
+/* QSYS:SYSTEM:SW_STATUS */
+#define QSYS_SW_STATUS(r) __REG(TARGET_QSYS,\
+ 0, 1, 14336, 0, 1, 240, 180, r, 10, 4)
+
+#define QSYS_SW_STATUS_EQ_AVAIL GENMASK(7, 0)
+#define QSYS_SW_STATUS_EQ_AVAIL_SET(x)\
+ FIELD_PREP(QSYS_SW_STATUS_EQ_AVAIL, x)
+#define QSYS_SW_STATUS_EQ_AVAIL_GET(x)\
+ FIELD_GET(QSYS_SW_STATUS_EQ_AVAIL, x)
+
+/* QSYS:SYSTEM:EXT_CPU_CFG */
+#define QSYS_EXT_CPU_CFG __REG(TARGET_QSYS,\
+ 0, 1, 14336, 0, 1, 240, 220, 0, 1, 4)
+
+#define QSYS_EXT_CPU_CFG_EXT_CPU_KILL_ENA BIT(14)
+#define QSYS_EXT_CPU_CFG_EXT_CPU_KILL_ENA_SET(x)\
+ FIELD_PREP(QSYS_EXT_CPU_CFG_EXT_CPU_KILL_ENA, x)
+#define QSYS_EXT_CPU_CFG_EXT_CPU_KILL_ENA_GET(x)\
+ FIELD_GET(QSYS_EXT_CPU_CFG_EXT_CPU_KILL_ENA, x)
+
+#define QSYS_EXT_CPU_CFG_INT_CPU_KILL_ENA BIT(13)
+#define QSYS_EXT_CPU_CFG_INT_CPU_KILL_ENA_SET(x)\
+ FIELD_PREP(QSYS_EXT_CPU_CFG_INT_CPU_KILL_ENA, x)
+#define QSYS_EXT_CPU_CFG_INT_CPU_KILL_ENA_GET(x)\
+ FIELD_GET(QSYS_EXT_CPU_CFG_INT_CPU_KILL_ENA, x)
+
+#define QSYS_EXT_CPU_CFG_EXT_CPU_PORT GENMASK(12, 8)
+#define QSYS_EXT_CPU_CFG_EXT_CPU_PORT_SET(x)\
+ FIELD_PREP(QSYS_EXT_CPU_CFG_EXT_CPU_PORT, x)
+#define QSYS_EXT_CPU_CFG_EXT_CPU_PORT_GET(x)\
+ FIELD_GET(QSYS_EXT_CPU_CFG_EXT_CPU_PORT, x)
+
+#define QSYS_EXT_CPU_CFG_EXT_CPUQ_MSK GENMASK(7, 0)
+#define QSYS_EXT_CPU_CFG_EXT_CPUQ_MSK_SET(x)\
+ FIELD_PREP(QSYS_EXT_CPU_CFG_EXT_CPUQ_MSK, x)
+#define QSYS_EXT_CPU_CFG_EXT_CPUQ_MSK_GET(x)\
+ FIELD_GET(QSYS_EXT_CPU_CFG_EXT_CPUQ_MSK, x)
+
+/* QSYS:SYSTEM:CPU_GROUP_MAP */
+#define QSYS_CPU_GROUP_MAP __REG(TARGET_QSYS,\
+ 0, 1, 14336, 0, 1, 240, 224, 0, 1, 4)
+
+#define QSYS_CPU_GROUP_MAP_CPU_GROUP_MAP GENMASK(7, 0)
+#define QSYS_CPU_GROUP_MAP_CPU_GROUP_MAP_SET(x)\
+ FIELD_PREP(QSYS_CPU_GROUP_MAP_CPU_GROUP_MAP, x)
+#define QSYS_CPU_GROUP_MAP_CPU_GROUP_MAP_GET(x)\
+ FIELD_GET(QSYS_CPU_GROUP_MAP_CPU_GROUP_MAP, x)
+
+/* QSYS:RES_CTRL:RES_CFG */
+#define QSYS_RES_CFG(g) __REG(TARGET_QSYS,\
+ 0, 1, 16384, g, 1024, 8, 0, 0, 1, 4)
+
+#define QSYS_RES_CFG_WM_HIGH GENMASK(8, 0)
+#define QSYS_RES_CFG_WM_HIGH_SET(x)\
+ FIELD_PREP(QSYS_RES_CFG_WM_HIGH, x)
+#define QSYS_RES_CFG_WM_HIGH_GET(x)\
+ FIELD_GET(QSYS_RES_CFG_WM_HIGH, x)
+
+/* QSYS:DROP_CFG:EGR_DROP_MODE */
+#define QSYS_EGR_DROP_MODE __REG(TARGET_QSYS,\
+ 0, 1, 12736, 0, 1, 8, 0, 0, 1, 4)
+
+#define QSYS_EGR_DROP_MODE_EGRESS_DROP_MODE GENMASK(9, 0)
+#define QSYS_EGR_DROP_MODE_EGRESS_DROP_MODE_SET(x)\
+ FIELD_PREP(QSYS_EGR_DROP_MODE_EGRESS_DROP_MODE, x)
+#define QSYS_EGR_DROP_MODE_EGRESS_DROP_MODE_GET(x)\
+ FIELD_GET(QSYS_EGR_DROP_MODE_EGRESS_DROP_MODE, x)
+
+/* REW:PORT:PORT_VLAN_CFG */
+#define REW_PORT_VLAN_CFG(g) __REG(TARGET_REW,\
+ 0, 1, 4096, g, 11, 128, 0, 0, 1, 4)
+
+#define REW_PORT_VLAN_CFG_PORT_TPID GENMASK(31, 16)
+#define REW_PORT_VLAN_CFG_PORT_TPID_SET(x)\
+ FIELD_PREP(REW_PORT_VLAN_CFG_PORT_TPID, x)
+#define REW_PORT_VLAN_CFG_PORT_TPID_GET(x)\
+ FIELD_GET(REW_PORT_VLAN_CFG_PORT_TPID, x)
+
+#define REW_PORT_VLAN_CFG_PORT_DEI BIT(15)
+#define REW_PORT_VLAN_CFG_PORT_DEI_SET(x)\
+ FIELD_PREP(REW_PORT_VLAN_CFG_PORT_DEI, x)
+#define REW_PORT_VLAN_CFG_PORT_DEI_GET(x)\
+ FIELD_GET(REW_PORT_VLAN_CFG_PORT_DEI, x)
+
+#define REW_PORT_VLAN_CFG_PORT_PCP GENMASK(14, 12)
+#define REW_PORT_VLAN_CFG_PORT_PCP_SET(x)\
+ FIELD_PREP(REW_PORT_VLAN_CFG_PORT_PCP, x)
+#define REW_PORT_VLAN_CFG_PORT_PCP_GET(x)\
+ FIELD_GET(REW_PORT_VLAN_CFG_PORT_PCP, x)
+
+#define REW_PORT_VLAN_CFG_PORT_VID GENMASK(11, 0)
+#define REW_PORT_VLAN_CFG_PORT_VID_SET(x)\
+ FIELD_PREP(REW_PORT_VLAN_CFG_PORT_VID, x)
+#define REW_PORT_VLAN_CFG_PORT_VID_GET(x)\
+ FIELD_GET(REW_PORT_VLAN_CFG_PORT_VID, x)
+
+/* REW:PORT:TAG_CFG */
+#define REW_TAG_CFG(g) __REG(TARGET_REW,\
+ 0, 1, 4096, g, 11, 128, 4, 0, 1, 4)
+
+#define REW_TAG_CFG_TAG_CFG GENMASK(8, 7)
+#define REW_TAG_CFG_TAG_CFG_SET(x)\
+ FIELD_PREP(REW_TAG_CFG_TAG_CFG, x)
+#define REW_TAG_CFG_TAG_CFG_GET(x)\
+ FIELD_GET(REW_TAG_CFG_TAG_CFG, x)
+
+#define REW_TAG_CFG_TAG_TPID_CFG GENMASK(6, 5)
+#define REW_TAG_CFG_TAG_TPID_CFG_SET(x)\
+ FIELD_PREP(REW_TAG_CFG_TAG_TPID_CFG, x)
+#define REW_TAG_CFG_TAG_TPID_CFG_GET(x)\
+ FIELD_GET(REW_TAG_CFG_TAG_TPID_CFG, x)
+
+#define REW_TAG_CFG_TAG_VID_CFG BIT(4)
+#define REW_TAG_CFG_TAG_VID_CFG_SET(x)\
+ FIELD_PREP(REW_TAG_CFG_TAG_VID_CFG, x)
+#define REW_TAG_CFG_TAG_VID_CFG_GET(x)\
+ FIELD_GET(REW_TAG_CFG_TAG_VID_CFG, x)
+
+#define REW_TAG_CFG_TAG_PCP_CFG GENMASK(3, 2)
+#define REW_TAG_CFG_TAG_PCP_CFG_SET(x)\
+ FIELD_PREP(REW_TAG_CFG_TAG_PCP_CFG, x)
+#define REW_TAG_CFG_TAG_PCP_CFG_GET(x)\
+ FIELD_GET(REW_TAG_CFG_TAG_PCP_CFG, x)
+
+#define REW_TAG_CFG_TAG_DEI_CFG GENMASK(1, 0)
+#define REW_TAG_CFG_TAG_DEI_CFG_SET(x)\
+ FIELD_PREP(REW_TAG_CFG_TAG_DEI_CFG, x)
+#define REW_TAG_CFG_TAG_DEI_CFG_GET(x)\
+ FIELD_GET(REW_TAG_CFG_TAG_DEI_CFG, x)
+
+/* REW:PORT:PORT_CFG */
+#define REW_PORT_CFG(g) __REG(TARGET_REW,\
+ 0, 1, 4096, g, 11, 128, 8, 0, 1, 4)
+
+#define REW_PORT_CFG_ES0_EN BIT(4)
+#define REW_PORT_CFG_ES0_EN_SET(x)\
+ FIELD_PREP(REW_PORT_CFG_ES0_EN, x)
+#define REW_PORT_CFG_ES0_EN_GET(x)\
+ FIELD_GET(REW_PORT_CFG_ES0_EN, x)
+
+#define REW_PORT_CFG_FCS_UPDATE_NONCPU_CFG GENMASK(3, 2)
+#define REW_PORT_CFG_FCS_UPDATE_NONCPU_CFG_SET(x)\
+ FIELD_PREP(REW_PORT_CFG_FCS_UPDATE_NONCPU_CFG, x)
+#define REW_PORT_CFG_FCS_UPDATE_NONCPU_CFG_GET(x)\
+ FIELD_GET(REW_PORT_CFG_FCS_UPDATE_NONCPU_CFG, x)
+
+#define REW_PORT_CFG_FCS_UPDATE_CPU_ENA BIT(1)
+#define REW_PORT_CFG_FCS_UPDATE_CPU_ENA_SET(x)\
+ FIELD_PREP(REW_PORT_CFG_FCS_UPDATE_CPU_ENA, x)
+#define REW_PORT_CFG_FCS_UPDATE_CPU_ENA_GET(x)\
+ FIELD_GET(REW_PORT_CFG_FCS_UPDATE_CPU_ENA, x)
+
+#define REW_PORT_CFG_NO_REWRITE BIT(0)
+#define REW_PORT_CFG_NO_REWRITE_SET(x)\
+ FIELD_PREP(REW_PORT_CFG_NO_REWRITE, x)
+#define REW_PORT_CFG_NO_REWRITE_GET(x)\
+ FIELD_GET(REW_PORT_CFG_NO_REWRITE, x)
+
+/* SYS:SYSTEM:RESET_CFG */
+#define SYS_RESET_CFG __REG(TARGET_SYS,\
+ 0, 1, 4160, 0, 1, 184, 0, 0, 1, 4)
+
+#define SYS_RESET_CFG_CORE_ENA BIT(0)
+#define SYS_RESET_CFG_CORE_ENA_SET(x)\
+ FIELD_PREP(SYS_RESET_CFG_CORE_ENA, x)
+#define SYS_RESET_CFG_CORE_ENA_GET(x)\
+ FIELD_GET(SYS_RESET_CFG_CORE_ENA, x)
+
+/* SYS:SYSTEM:PORT_MODE */
+#define SYS_PORT_MODE(r) __REG(TARGET_SYS,\
+ 0, 1, 4160, 0, 1, 184, 48, r, 11, 4)
+
+#define SYS_PORT_MODE_PRP_LANID BIT(8)
+#define SYS_PORT_MODE_PRP_LANID_SET(x)\
+ FIELD_PREP(SYS_PORT_MODE_PRP_LANID, x)
+#define SYS_PORT_MODE_PRP_LANID_GET(x)\
+ FIELD_GET(SYS_PORT_MODE_PRP_LANID, x)
+
+#define SYS_PORT_MODE_PRP_ENA BIT(7)
+#define SYS_PORT_MODE_PRP_ENA_SET(x)\
+ FIELD_PREP(SYS_PORT_MODE_PRP_ENA, x)
+#define SYS_PORT_MODE_PRP_ENA_GET(x)\
+ FIELD_GET(SYS_PORT_MODE_PRP_ENA, x)
+
+#define SYS_PORT_MODE_INCL_INJ_HDR GENMASK(6, 5)
+#define SYS_PORT_MODE_INCL_INJ_HDR_SET(x)\
+ FIELD_PREP(SYS_PORT_MODE_INCL_INJ_HDR, x)
+#define SYS_PORT_MODE_INCL_INJ_HDR_GET(x)\
+ FIELD_GET(SYS_PORT_MODE_INCL_INJ_HDR, x)
+
+#define SYS_PORT_MODE_INCL_XTR_HDR GENMASK(4, 3)
+#define SYS_PORT_MODE_INCL_XTR_HDR_SET(x)\
+ FIELD_PREP(SYS_PORT_MODE_INCL_XTR_HDR, x)
+#define SYS_PORT_MODE_INCL_XTR_HDR_GET(x)\
+ FIELD_GET(SYS_PORT_MODE_INCL_XTR_HDR, x)
+
+#define SYS_PORT_MODE_INJ_HDR_ERR BIT(2)
+#define SYS_PORT_MODE_INJ_HDR_ERR_SET(x)\
+ FIELD_PREP(SYS_PORT_MODE_INJ_HDR_ERR, x)
+#define SYS_PORT_MODE_INJ_HDR_ERR_GET(x)\
+ FIELD_GET(SYS_PORT_MODE_INJ_HDR_ERR, x)
+
+#define SYS_PORT_MODE_PAD_DIS BIT(1)
+#define SYS_PORT_MODE_PAD_DIS_SET(x)\
+ FIELD_PREP(SYS_PORT_MODE_PAD_DIS, x)
+#define SYS_PORT_MODE_PAD_DIS_GET(x)\
+ FIELD_GET(SYS_PORT_MODE_PAD_DIS, x)
+
+#define SYS_PORT_MODE_RTAG_CLEAR BIT(0)
+#define SYS_PORT_MODE_RTAG_CLEAR_SET(x)\
+ FIELD_PREP(SYS_PORT_MODE_RTAG_CLEAR, x)
+#define SYS_PORT_MODE_RTAG_CLEAR_GET(x)\
+ FIELD_GET(SYS_PORT_MODE_RTAG_CLEAR, x)
+
+/* SYS:SYSTEM:FRONT_PORT_MODE */
+#define SYS_FRONT_PORT_MODE(r) __REG(TARGET_SYS,\
+ 0, 1, 4160, 0, 1, 184, 92, r, 9, 4)
+
+#define SYS_FRONT_PORT_MODE_HDX_MODE BIT(1)
+#define SYS_FRONT_PORT_MODE_HDX_MODE_SET(x)\
+ FIELD_PREP(SYS_FRONT_PORT_MODE_HDX_MODE, x)
+#define SYS_FRONT_PORT_MODE_HDX_MODE_GET(x)\
+ FIELD_GET(SYS_FRONT_PORT_MODE_HDX_MODE, x)
+
+#define SYS_FRONT_PORT_MODE_ADD_FRAG_SIZE GENMASK(9, 8)
+#define SYS_FRONT_PORT_MODE_ADD_FRAG_SIZE_SET(x)\
+ FIELD_PREP(SYS_FRONT_PORT_MODE_ADD_FRAG_SIZE, x)
+#define SYS_FRONT_PORT_MODE_ADD_FRAG_SIZE_GET(x)\
+ FIELD_GET(SYS_FRONT_PORT_MODE_ADD_FRAG_SIZE, x)
+
+#define SYS_FRONT_PORT_MODE_DONT_WAIT_FOR_TS BIT(0)
+#define SYS_FRONT_PORT_MODE_DONT_WAIT_FOR_TS_SET(x)\
+ FIELD_PREP(SYS_FRONT_PORT_MODE_DONT_WAIT_FOR_TS, x)
+#define SYS_FRONT_PORT_MODE_DONT_WAIT_FOR_TS_GET(x)\
+ FIELD_GET(SYS_FRONT_PORT_MODE_DONT_WAIT_FOR_TS, x)
+
+/* SYS:SYSTEM:FRM_AGING */
+#define SYS_FRM_AGING __REG(TARGET_SYS,\
+ 0, 1, 4160, 0, 1, 184, 128, 0, 1, 4)
+
+#define SYS_FRM_AGING_AGE_TX_ENA BIT(20)
+#define SYS_FRM_AGING_AGE_TX_ENA_SET(x)\
+ FIELD_PREP(SYS_FRM_AGING_AGE_TX_ENA, x)
+#define SYS_FRM_AGING_AGE_TX_ENA_GET(x)\
+ FIELD_GET(SYS_FRM_AGING_AGE_TX_ENA, x)
+
+#define SYS_FRM_AGING_MAX_AGE GENMASK(19, 0)
+#define SYS_FRM_AGING_MAX_AGE_SET(x)\
+ FIELD_PREP(SYS_FRM_AGING_MAX_AGE, x)
+#define SYS_FRM_AGING_MAX_AGE_GET(x)\
+ FIELD_GET(SYS_FRM_AGING_MAX_AGE, x)
+
+/* SYS:SYSTEM:STAT_CFG */
+#define SYS_STAT_CFG __REG(TARGET_SYS,\
+ 0, 1, 4160, 0, 1, 184, 132, 0, 1, 4)
+
+#define SYS_STAT_CFG_STAT_CLEAR_SHOT GENMASK(16, 10)
+#define SYS_STAT_CFG_STAT_CLEAR_SHOT_SET(x)\
+ FIELD_PREP(SYS_STAT_CFG_STAT_CLEAR_SHOT, x)
+#define SYS_STAT_CFG_STAT_CLEAR_SHOT_GET(x)\
+ FIELD_GET(SYS_STAT_CFG_STAT_CLEAR_SHOT, x)
+
+#define SYS_STAT_CFG_STAT_VIEW GENMASK(9, 0)
+#define SYS_STAT_CFG_STAT_VIEW_SET(x)\
+ FIELD_PREP(SYS_STAT_CFG_STAT_VIEW, x)
+#define SYS_STAT_CFG_STAT_VIEW_GET(x)\
+ FIELD_GET(SYS_STAT_CFG_STAT_VIEW, x)
+
+/* SYS:SYSTEM:SW_STATUS */
+#define SYS_SW_STATUS(r) __REG(TARGET_SYS,\
+ 0, 1, 4160, 0, 1, 184, 136, r, 10, 4)
+
+#define SYS_SW_STATUS_PORT_RX_PAUSED BIT(0)
+#define SYS_SW_STATUS_PORT_RX_PAUSED_SET(x)\
+ FIELD_PREP(SYS_SW_STATUS_PORT_RX_PAUSED, x)
+#define SYS_SW_STATUS_PORT_RX_PAUSED_GET(x)\
+ FIELD_GET(SYS_SW_STATUS_PORT_RX_PAUSED, x)
+
+/* SYS:PAUSE_CFG:PAUSE_CFG */
+#define SYS_PAUSE_CFG(r) __REG(TARGET_SYS,\
+ 0, 1, 4344, 0, 1, 124, 0, r, 10, 4)
+
+#define SYS_PAUSE_CFG_PAUSE_START GENMASK(18, 10)
+#define SYS_PAUSE_CFG_PAUSE_START_SET(x)\
+ FIELD_PREP(SYS_PAUSE_CFG_PAUSE_START, x)
+#define SYS_PAUSE_CFG_PAUSE_START_GET(x)\
+ FIELD_GET(SYS_PAUSE_CFG_PAUSE_START, x)
+
+#define SYS_PAUSE_CFG_PAUSE_STOP GENMASK(9, 1)
+#define SYS_PAUSE_CFG_PAUSE_STOP_SET(x)\
+ FIELD_PREP(SYS_PAUSE_CFG_PAUSE_STOP, x)
+#define SYS_PAUSE_CFG_PAUSE_STOP_GET(x)\
+ FIELD_GET(SYS_PAUSE_CFG_PAUSE_STOP, x)
+
+#define SYS_PAUSE_CFG_PAUSE_ENA BIT(0)
+#define SYS_PAUSE_CFG_PAUSE_ENA_SET(x)\
+ FIELD_PREP(SYS_PAUSE_CFG_PAUSE_ENA, x)
+#define SYS_PAUSE_CFG_PAUSE_ENA_GET(x)\
+ FIELD_GET(SYS_PAUSE_CFG_PAUSE_ENA, x)
+
+/* SYS:PAUSE_CFG:ATOP */
+#define SYS_ATOP(r) __REG(TARGET_SYS,\
+ 0, 1, 4344, 0, 1, 124, 44, r, 10, 4)
+
+#define SYS_ATOP_ATOP GENMASK(8, 0)
+#define SYS_ATOP_ATOP_SET(x)\
+ FIELD_PREP(SYS_ATOP_ATOP, x)
+#define SYS_ATOP_ATOP_GET(x)\
+ FIELD_GET(SYS_ATOP_ATOP, x)
+
+/* SYS:PAUSE_CFG:ATOP_TOT_CFG */
+#define SYS_ATOP_TOT_CFG __REG(TARGET_SYS,\
+ 0, 1, 4344, 0, 1, 124, 84, 0, 1, 4)
+
+#define SYS_ATOP_TOT_CFG_ATOP_TOT GENMASK(8, 0)
+#define SYS_ATOP_TOT_CFG_ATOP_TOT_SET(x)\
+ FIELD_PREP(SYS_ATOP_TOT_CFG_ATOP_TOT, x)
+#define SYS_ATOP_TOT_CFG_ATOP_TOT_GET(x)\
+ FIELD_GET(SYS_ATOP_TOT_CFG_ATOP_TOT, x)
+
+/* SYS:PAUSE_CFG:MAC_FC_CFG */
+#define SYS_MAC_FC_CFG(r) __REG(TARGET_SYS,\
+ 0, 1, 4344, 0, 1, 124, 88, r, 9, 4)
+
+#define SYS_MAC_FC_CFG_FC_LINK_SPEED GENMASK(27, 26)
+#define SYS_MAC_FC_CFG_FC_LINK_SPEED_SET(x)\
+ FIELD_PREP(SYS_MAC_FC_CFG_FC_LINK_SPEED, x)
+#define SYS_MAC_FC_CFG_FC_LINK_SPEED_GET(x)\
+ FIELD_GET(SYS_MAC_FC_CFG_FC_LINK_SPEED, x)
+
+#define SYS_MAC_FC_CFG_FC_LATENCY_CFG GENMASK(25, 20)
+#define SYS_MAC_FC_CFG_FC_LATENCY_CFG_SET(x)\
+ FIELD_PREP(SYS_MAC_FC_CFG_FC_LATENCY_CFG, x)
+#define SYS_MAC_FC_CFG_FC_LATENCY_CFG_GET(x)\
+ FIELD_GET(SYS_MAC_FC_CFG_FC_LATENCY_CFG, x)
+
+#define SYS_MAC_FC_CFG_ZERO_PAUSE_ENA BIT(18)
+#define SYS_MAC_FC_CFG_ZERO_PAUSE_ENA_SET(x)\
+ FIELD_PREP(SYS_MAC_FC_CFG_ZERO_PAUSE_ENA, x)
+#define SYS_MAC_FC_CFG_ZERO_PAUSE_ENA_GET(x)\
+ FIELD_GET(SYS_MAC_FC_CFG_ZERO_PAUSE_ENA, x)
+
+#define SYS_MAC_FC_CFG_TX_FC_ENA BIT(17)
+#define SYS_MAC_FC_CFG_TX_FC_ENA_SET(x)\
+ FIELD_PREP(SYS_MAC_FC_CFG_TX_FC_ENA, x)
+#define SYS_MAC_FC_CFG_TX_FC_ENA_GET(x)\
+ FIELD_GET(SYS_MAC_FC_CFG_TX_FC_ENA, x)
+
+#define SYS_MAC_FC_CFG_RX_FC_ENA BIT(16)
+#define SYS_MAC_FC_CFG_RX_FC_ENA_SET(x)\
+ FIELD_PREP(SYS_MAC_FC_CFG_RX_FC_ENA, x)
+#define SYS_MAC_FC_CFG_RX_FC_ENA_GET(x)\
+ FIELD_GET(SYS_MAC_FC_CFG_RX_FC_ENA, x)
+
+#define SYS_MAC_FC_CFG_PAUSE_VAL_CFG GENMASK(15, 0)
+#define SYS_MAC_FC_CFG_PAUSE_VAL_CFG_SET(x)\
+ FIELD_PREP(SYS_MAC_FC_CFG_PAUSE_VAL_CFG, x)
+#define SYS_MAC_FC_CFG_PAUSE_VAL_CFG_GET(x)\
+ FIELD_GET(SYS_MAC_FC_CFG_PAUSE_VAL_CFG, x)
+
+/* SYS:STAT:CNT */
+#define SYS_CNT(g) __REG(TARGET_SYS,\
+ 0, 1, 0, g, 896, 4, 0, 0, 1, 4)
+
+/* SYS:RAM_CTRL:RAM_INIT */
+#define SYS_RAM_INIT __REG(TARGET_SYS,\
+ 0, 1, 4492, 0, 1, 4, 0, 0, 1, 4)
+
+#define SYS_RAM_INIT_RAM_TEST_OPT GENMASK(4, 2)
+#define SYS_RAM_INIT_RAM_TEST_OPT_SET(x)\
+ FIELD_PREP(SYS_RAM_INIT_RAM_TEST_OPT, x)
+#define SYS_RAM_INIT_RAM_TEST_OPT_GET(x)\
+ FIELD_GET(SYS_RAM_INIT_RAM_TEST_OPT, x)
+
+#define SYS_RAM_INIT_RAM_INIT BIT(1)
+#define SYS_RAM_INIT_RAM_INIT_SET(x)\
+ FIELD_PREP(SYS_RAM_INIT_RAM_INIT, x)
+#define SYS_RAM_INIT_RAM_INIT_GET(x)\
+ FIELD_GET(SYS_RAM_INIT_RAM_INIT, x)
+
+#define SYS_RAM_INIT_RAM_CFG_HOOK BIT(0)
+#define SYS_RAM_INIT_RAM_CFG_HOOK_SET(x)\
+ FIELD_PREP(SYS_RAM_INIT_RAM_CFG_HOOK, x)
+#define SYS_RAM_INIT_RAM_CFG_HOOK_GET(x)\
+ FIELD_GET(SYS_RAM_INIT_RAM_CFG_HOOK, x)
+
+/* SYS:PTPPORT:PTP_RXDLY_CFG */
+#define SYS_PTP_RXDLY_CFG(g) __REG(TARGET_SYS,\
+ 0, 1, 4512, g, 11, 28, 8, 0, 1, 4)
+
+#define SYS_PTP_RXDLY_CFG_PTP_RX_IO_DLY GENMASK(23, 0)
+#define SYS_PTP_RXDLY_CFG_PTP_RX_IO_DLY_SET(x)\
+ FIELD_PREP(SYS_PTP_RXDLY_CFG_PTP_RX_IO_DLY, x)
+#define SYS_PTP_RXDLY_CFG_PTP_RX_IO_DLY_GET(x)\
+ FIELD_GET(SYS_PTP_RXDLY_CFG_PTP_RX_IO_DLY, x)
+
+/* SYS:PTPPORT:PTP_TXDLY_CFG */
+#define SYS_PTP_TXDLY_CFG(g) __REG(TARGET_SYS,\
+ 0, 1, 4512, g, 11, 28, 12, 0, 1, 4)
+
+#define SYS_PTP_TXDLY_CFG_PTP_TX_IO_DLY GENMASK(23, 0)
+#define SYS_PTP_TXDLY_CFG_PTP_TX_IO_DLY_SET(x)\
+ FIELD_PREP(SYS_PTP_TXDLY_CFG_PTP_TX_IO_DLY, x)
+#define SYS_PTP_TXDLY_CFG_PTP_TX_IO_DLY_GET(x)\
+ FIELD_GET(SYS_PTP_TXDLY_CFG_PTP_TX_IO_DLY, x)
+
+#endif /* _LAN9645X_REGS_H_ */
--
2.52.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH net-next v13 4/9] net: dsa: lan9645x: add basic dsa driver for LAN9645X
2026-09-29 7:48 [PATCH net-next v13 0/9] net: dsa: add DSA support for the LAN9645x switch chip family Jens Emil Schulz Østergaard
` (2 preceding siblings ...)
2026-09-29 7:48 ` [PATCH net-next v13 3/9] net: dsa: lan9645x: add autogenerated register macros Jens Emil Schulz Østergaard
@ 2026-09-29 7:48 ` Jens Emil Schulz Østergaard
2026-10-02 21:14 ` netdev-bot+sashiko
2026-09-29 7:48 ` [PATCH net-next v13 5/9] net: dsa: lan9645x: add vlan support Jens Emil Schulz Østergaard
` (4 subsequent siblings)
8 siblings, 1 reply; 18+ messages in thread
From: Jens Emil Schulz Østergaard @ 2026-09-29 7:48 UTC (permalink / raw)
To: UNGLinuxDriver, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Woojung Huh,
Russell King, Steen Hegelund, Daniel Machon, Geert Uytterhoeven,
Magnus Damm
Cc: linux-kernel, netdev, devicetree, linux-renesas-soc,
Jens Emil Schulz Østergaard
Add the LAN9645X basic DSA driver with initialization, parent regmap
requests, port module initialization for NPI, CPU ports and front ports,
and phylink integration for MAC side configuration.
This patch only wires up the internal CuPHYs (ports 0-4) and the RGMII
paths for ports 7 and 8. The supported SerDes-based modes: SGMII,
QSGMII, 1000BASE-X and 2500BASE-X on ports 5-8 are deferred, and support
will be added in follow-up patches. Port 5-6 have no usable interface
mode in this patch, and should not be declared in DT until SerDes
support lands.
If port 5 or 6 is declared in DT as a user port, phylink_create()
rejects the empty supported_interfaces and dsa_tree_setup_ports() falls
back to setting the port up as unused, so the switch still probes.
DSA logs an error including the port number. Declared as a CPU port
there is no such fallback, and the probe fails, and our driver logs an
error including the port number.
The intended way to bind this driver is using a parent MFD driver,
responsible for the register IO protocol, and distributing regmaps to
child devices. The goal is to use the same approach as the MFD driver in
drivers/mfd/ocelot-spi.c, which also resets the chip before it
instantiates its children.
This driver expects to request named regmaps from a parent device. This
approach is similar to the DSA driver
drivers/net/dsa/ocelot/ocelot_ext.c
which supports being driven by an external CPU via SPI with parent
device drivers/mfd/ocelot-spi.c.
The MFD driver will come in a later series, because there are
requirements on the number of child devices before a driver qualifies as
a MFD device.
Reviewed-by: Steen Hegelund <Steen.Hegelund@microchip.com>
Signed-off-by: Jens Emil Schulz Østergaard <jensemil.schulzostergaard@microchip.com>
---
Changes in v13:
- Only redirect the CPU extraction queues to the NPI port while the
NPI port link is up. This avoids queuing CPU frames to the NPI port
while its MAC is held in reset.
- Configure CPU extraction group 0 for manual extraction, instead of
the default where the VRAP engine owns it, so QSYS_SW_STATUS.EQ_AVAIL
of the CPU port module can be polled when flushing it on NPI link up.
Changes in v12:
- Drop lan9645x_port_set_tail_drop_wm(). This tail dropping config
is only active under cut through forwarding, which we never enable.
- lan9645x_reset_switch(): skip the memory init when the switch core is
already enabled. We expect parent device to reset before probe, but if
it does not, we must skip memory init if switch core is already
enabled.
- Drop unused shared_queue_sz field and LAN9645X_BUFFER_MEMORY define.
- Set ds->max_num_bridges = 1 next to ds->fdb_isolation. We support a
single offloaded bridge, so let the core enforce it.
- Reject an NPI port with no usable interface mode.
- Kconfig: select REGMAP.
- Include the CPU port module in the unicast and multicast flood masks.
Host address filtering needs the fdb and mdb operations, and unknown
traffic needs port_set_host_flood(), so until bridge support lands the
host depends on flooding to receive anything.
- Describe in the commit what happens if port 5 or 6 is declared in DT.
- Drop unused register writes from lan9645x_port_cpu_init().
- Update PGID table comment.
- Drop the unused PGID_AGGR_NUM, LAN9645X_ISDX_MAX, LAN9645X_ESDX_MAX and
LAN9645X_SFID_MAX defines, along with PGID_ENTRIES.
- Follow the LAN9645X_IFH_LEN rename in the tag driver.
- Drop ds->dscp_prio_mapping_is_global. It is only consulted after
port_add_dscp_prio() or port_del_dscp_prio() succeeds, neither of which
this series implements.
- Program QSYS_RES_CFG through lan9645x_wm_enc().
- Return -ENOMEM directly, not via dev_err_probe(), and add the missing
newlines to the remaining probe messages.
- lan9645x_npi_port_deinit(): restore the NPI bit in the CPU port module
source PGID that lan9645x_npi_port_init() cleared.
- Spell the part LAN9645x in the Kconfig prompt, the help text and
MODULE_DESCRIPTION.
- Drop the unread NULL sentinel from lan9645x_resource_names[].
- Resolve the regmap and the register address once in
lan9645x_rd_poll_timeout() and lan9645x_rd_poll_slow().
- lan9645x_get_max_mtu(): drop the NPI case. DSA only asks for the user
port limit and adds the tag overhead back itself when it sizes the
conduit MTU.
- lan9645x_phylink_mac_prepare(): drop the port range check. Ports 5 and 6
get no phylink instance, and ds->num_ports bounds the rest.
- lan9645x_phylink_mac_link_up(): drop the unreachable SPEED_2500 case,
and the DEV_CLOCK_CFG.LINK_SPEED fixup it forced.
lan9645x_phylink_get_caps() does not advertise MAC_2500FD.
- Fix DEV_MAC_IFG_CFG.TX_IFG, which had its full and half duplex values
swapped.
- Drop the unused afi target regmap request.
- Name the first per port regmap dev0 rather than dev, so the nine read
uniformly.
- Bound the ANA_PGID_CFG.OBEY_VLAN loop at PGID_AGGR.
- lan9645x_phylink_mac_prepare(): drop the HSIO_HW_CFG.RGMII_0_CFG write.
The field resets to DEV7 and nothing else writes it.
- lan9645x_phylink_port_down(): restore QSYS_SW_PORT_MODE.AGING_MODE to 2
after the flush, which is the reset value, rather than 1. Mode 1 exempts
frames from the CPU interface from the transmit ageing that
lan9645x_setup() enables, and every port sat in it from probe.
Changes in v9:
- Drop the DEV4 RGMII muxing support.
Changes in v8:
- Remove the CPU_PORT macro, which named the chips internal CPU port
module and collided with the DSA CPU port terminology. Reference the
module as lan9645x->num_phys_ports.
- Rework the port map comment to distinguish the chip CPU port modules
(indices 9-10) from the DSA CPU port (the NPI front port).
- Derive PGID_GP_START from NUM_PHYS_PORTS instead of CPU_PORT.
- Add a port_mux_lock mutex to serialize port mux arbitration in
lan9645x_phylink_mac_prepare().
Changes in v7:
- use cpu queue LAN9645X_CPUQ_TRAP for bpdu frames
Changes in v5:
- remove dd_dis and tsn_dis, and add SKU supported port validation
during setup
- phylink: remove MAC_2500FD
- phylink: add comment about empty supported_interfaces for port 5-6.
- phylink: fix 2:1 rgmii port muxing for port module 4 and 7 to be fully
dynamic and validate requested mux settings.
- phylink: add comment about 2:1 rgmii port muxing for port module 4
and 7.
- rx/tx-internal-delay-ps checked against support 2ns value
- update commit message
- init lan9645x->npi = -1 at probe and check port < 0 in npi_deinit
Changes in v4:
- add comment to QSYS_RES_CFG configuration
- phylink_mac_prepare: fix to make sure we can dynamically change rgmii
on port 4
- move ports allocation to probe
- tag_npi_setup: reject cascaded setups
- skip WARN_ON in lan9645x_to_port
Changes in v3:
- move DEV_MAC_TAGS_CFG config to port setup, for vlan overhead in port
frame maxlen
- remove code disabling ipv6 on conduit
- use of_property_read_u32 for {rx,tx}-internal-delay-ps
- use dsa_user_ports(ds) instead of
GENMASK(lan9645x->num_phys_ports - 1, 0) as base flood mask.
- update obey vlan comment
Changes in v2:
- source Kconfig from drivers/net/dsa/Kconfig
- sorting in Kconfig and Makefiles
- remove unused struct fields
- remote path delays
- use port_setup and dp->dn instead of DTS parsing
- phylink: split rgmii setup into dll and speed config
- phylink: remove pcs/sgmii/qsgmii related code
- phylink: simplify mac_prepare
- phylink: remove phylink_ops wrappers
- phylink: remove unrelated config from link up
- phylink: reorder functions according to phylink call order
---
drivers/net/dsa/Kconfig | 2 +
drivers/net/dsa/microchip/Makefile | 1 +
drivers/net/dsa/microchip/lan9645x/Kconfig | 12 +
drivers/net/dsa/microchip/lan9645x/Makefile | 8 +
drivers/net/dsa/microchip/lan9645x/lan9645x_main.c | 456 +++++++++++++++++++++
drivers/net/dsa/microchip/lan9645x/lan9645x_main.h | 348 ++++++++++++++++
drivers/net/dsa/microchip/lan9645x/lan9645x_npi.c | 116 ++++++
.../net/dsa/microchip/lan9645x/lan9645x_phylink.c | 371 +++++++++++++++++
drivers/net/dsa/microchip/lan9645x/lan9645x_port.c | 166 ++++++++
9 files changed, 1480 insertions(+)
diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig
index fe8cd5338fda..d62f0cccf13d 100644
--- a/drivers/net/dsa/Kconfig
+++ b/drivers/net/dsa/Kconfig
@@ -82,6 +82,8 @@ config NET_DSA_MV88E6060
source "drivers/net/dsa/microchip/Kconfig"
+source "drivers/net/dsa/microchip/lan9645x/Kconfig"
+
source "drivers/net/dsa/motorcomm/Kconfig"
source "drivers/net/dsa/mv88e6xxx/Kconfig"
diff --git a/drivers/net/dsa/microchip/Makefile b/drivers/net/dsa/microchip/Makefile
index 9347cfb3d0b5..e75f17888f75 100644
--- a/drivers/net/dsa/microchip/Makefile
+++ b/drivers/net/dsa/microchip/Makefile
@@ -12,3 +12,4 @@ endif
obj-$(CONFIG_NET_DSA_MICROCHIP_KSZ9477_I2C) += ksz9477_i2c.o
obj-$(CONFIG_NET_DSA_MICROCHIP_KSZ_SPI) += ksz_spi.o
obj-$(CONFIG_NET_DSA_MICROCHIP_KSZ8863_SMI) += ksz8863_smi.o
+obj-$(CONFIG_NET_DSA_MICROCHIP_LAN9645X) += lan9645x/
diff --git a/drivers/net/dsa/microchip/lan9645x/Kconfig b/drivers/net/dsa/microchip/lan9645x/Kconfig
new file mode 100644
index 000000000000..4e3e0d2f5846
--- /dev/null
+++ b/drivers/net/dsa/microchip/lan9645x/Kconfig
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config NET_DSA_MICROCHIP_LAN9645X
+ tristate "Microchip LAN9645x switch support"
+ depends on NET_DSA
+ select NET_DSA_TAG_LAN9645X
+ select REGMAP
+ help
+ This driver adds DSA support for Microchip LAN9645x switch chips.
+ The lan9645x switch is a multi-port Gigabit AVB/TSN Ethernet Switch
+ with five integrated 10/100/1000Base-T PHYs. In addition to the
+ integrated PHYs, it supports up to 2 RGMII/RMII, up to 2
+ BASE-X/SERDES/2.5GBASE-X and one Quad-SGMII/Quad-USGMII interfaces.
diff --git a/drivers/net/dsa/microchip/lan9645x/Makefile b/drivers/net/dsa/microchip/lan9645x/Makefile
new file mode 100644
index 000000000000..7cc0ae0ada40
--- /dev/null
+++ b/drivers/net/dsa/microchip/lan9645x/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_NET_DSA_MICROCHIP_LAN9645X) += mchp-lan9645x.o
+
+mchp-lan9645x-objs := \
+ lan9645x_main.o \
+ lan9645x_npi.o \
+ lan9645x_phylink.o \
+ lan9645x_port.o \
diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
new file mode 100644
index 000000000000..c7de3836e696
--- /dev/null
+++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
@@ -0,0 +1,456 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright (C) 2026 Microchip Technology Inc.
+ */
+
+#include <linux/platform_device.h>
+
+#include "lan9645x_main.h"
+
+static const char *lan9645x_resource_names[NUM_TARGETS] = {
+ [TARGET_GCB] = "gcb",
+ [TARGET_QS] = "qs",
+ [TARGET_CHIP_TOP] = "chip_top",
+ [TARGET_REW] = "rew",
+ [TARGET_SYS] = "sys",
+ [TARGET_HSIO] = "hsio",
+ [TARGET_DEV + 0] = "dev0",
+ [TARGET_DEV + 1] = "dev1",
+ [TARGET_DEV + 2] = "dev2",
+ [TARGET_DEV + 3] = "dev3",
+ [TARGET_DEV + 4] = "dev4",
+ [TARGET_DEV + 5] = "dev5",
+ [TARGET_DEV + 6] = "dev6",
+ [TARGET_DEV + 7] = "dev7",
+ [TARGET_DEV + 8] = "dev8",
+ [TARGET_QSYS] = "qsys",
+ [TARGET_ANA] = "ana",
+};
+
+static int lan9645x_tag_npi_setup(struct dsa_switch *ds)
+{
+ struct dsa_port *dp, *first_cpu_dp = NULL;
+ struct lan9645x *lan9645x = ds->priv;
+ struct phylink_config c = {};
+
+ dsa_switch_for_each_user_port(dp, ds) {
+ if (dp->cpu_dp->ds != ds) {
+ dev_err(ds->dev,
+ "NPI port on a remote switch is not supported\n");
+ return -EINVAL;
+ }
+
+ if (first_cpu_dp && dp->cpu_dp != first_cpu_dp) {
+ dev_err(ds->dev, "Multiple NPI ports not supported\n");
+ return -EINVAL;
+ }
+
+ first_cpu_dp = dp->cpu_dp;
+ }
+
+ if (!first_cpu_dp)
+ return -EINVAL;
+
+ lan9645x_phylink_get_caps(lan9645x, first_cpu_dp->index, &c);
+ if (phy_interface_empty(c.supported_interfaces)) {
+ dev_err(ds->dev,
+ "Port %d has no usable interface mode, cannot be NPI port\n",
+ first_cpu_dp->index);
+ return -EINVAL;
+ }
+
+ lan9645x_npi_port_init(lan9645x, first_cpu_dp);
+
+ return 0;
+}
+
+static enum dsa_tag_protocol lan9645x_get_tag_protocol(struct dsa_switch *ds,
+ int port,
+ enum dsa_tag_protocol tp)
+{
+ return DSA_TAG_PROTO_LAN9645X;
+}
+
+static void lan9645x_teardown(struct dsa_switch *ds)
+{
+ struct lan9645x *lan9645x = ds->priv;
+
+ lan9645x_npi_port_deinit(lan9645x, lan9645x->npi);
+}
+
+static int lan9645x_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
+{
+ return lan9645x_port_set_maxlen(ds->priv, port, new_mtu);
+}
+
+static int lan9645x_get_max_mtu(struct dsa_switch *ds, int port)
+{
+ /* Actual MAC max MTU is around 16KB. We set 10000 - overhead which
+ * should be sufficient for all jumbo frames. Larger frames can cause
+ * problems especially with flow control, since we only have 160K queue
+ * buffer.
+ *
+ * DSA only asks for the user port limit, and adds the tag overhead
+ * back when it sizes the conduit MTU, so the CPU port is not a case
+ * here.
+ */
+ return 10000 - ETH_HLEN - ETH_FCS_LEN;
+}
+
+static int lan9645x_reset_switch(struct lan9645x *lan9645x)
+{
+ u32 val = 0;
+ int err;
+
+ /* The parent must reset the whole chip before instantiating its
+ * children, so the switch core is disabled when we get here. The
+ * sequence below must not run more than once: initializing the
+ * memories on a running core corrupts pointers inside the queue
+ * system, which eventually fills up and stops passing traffic. If the
+ * core is already enabled the chip was not reset, so skip it.
+ */
+ if (SYS_RESET_CFG_CORE_ENA_GET(lan_rd(lan9645x, SYS_RESET_CFG)))
+ return 0;
+
+ lan_wr(SYS_RESET_CFG_CORE_ENA_SET(0), lan9645x, SYS_RESET_CFG);
+ lan_wr(SYS_RAM_INIT_RAM_INIT_SET(1), lan9645x, SYS_RAM_INIT);
+ err = lan9645x_rd_poll_timeout(lan9645x, SYS_RAM_INIT, val,
+ SYS_RAM_INIT_RAM_INIT_GET(val) == 0);
+ if (err) {
+ dev_err(lan9645x->dev, "Failed to init chip RAM.\n");
+ return err;
+ }
+ lan_wr(SYS_RESET_CFG_CORE_ENA_SET(1), lan9645x, SYS_RESET_CFG);
+
+ return 0;
+}
+
+static int lan9645x_setup(struct dsa_switch *ds)
+{
+ struct lan9645x *lan9645x = ds->priv;
+ struct device *dev = lan9645x->dev;
+ u32 front_ports, res_wm;
+ int supported, used = 0;
+ struct dsa_port *dp;
+ int err;
+
+ lan9645x->num_phys_ports = ds->num_ports;
+ front_ports = dsa_user_ports(ds);
+ supported = lan9645x->num_phys_ports - lan9645x->num_port_dis;
+
+ dsa_switch_for_each_available_port(dp, ds)
+ used++;
+
+ if (used > supported) {
+ dev_err(ds->dev,
+ "%d physical ports in use in DT, SKU supports at most %d\n",
+ used, supported);
+ return -EINVAL;
+ }
+
+ err = lan9645x_reset_switch(lan9645x);
+ if (err)
+ return err;
+
+ err = lan9645x_tag_npi_setup(ds);
+ if (err) {
+ dev_err(dev, "Failed to setup NPI port.\n");
+ return err;
+ }
+
+ /* Link Aggregation Mode: NETDEV_LAG_HASH_L2 */
+ lan_wr(ANA_AGGR_CFG_AC_SMAC_ENA |
+ ANA_AGGR_CFG_AC_DMAC_ENA,
+ lan9645x, ANA_AGGR_CFG);
+
+ /* Flush queues */
+ lan_wr(GENMASK(1, 0), lan9645x, QS_XTR_FLUSH);
+
+ /* Allow to drain */
+ usleep_range(1000, 2000);
+
+ /* All Queues normal */
+ lan_wr(0x0, lan9645x, QS_XTR_FLUSH);
+
+ /* Set MAC age time to default value, the entry is aged after
+ * 2 * AGE_PERIOD
+ */
+ lan_wr(ANA_AUTOAGE_AGE_PERIOD_SET(BR_DEFAULT_AGEING_TIME / 2 / HZ),
+ lan9645x, ANA_AUTOAGE);
+
+ /* Disable learning for frames discarded by VLAN ingress filtering */
+ lan_rmw(ANA_ADVLEARN_VLAN_CHK_SET(1),
+ ANA_ADVLEARN_VLAN_CHK,
+ lan9645x, ANA_ADVLEARN);
+
+ /* Queue system frame ageing. We target 2s ageing.
+ *
+ * Register unit is 1024 cycles.
+ *
+ * ASIC: 165.625 Mhz ~ 6.0377 ns period
+ *
+ * 1024 * 6.0377 ns =~ 6182 ns
+ * val = 2000000000ns / 6182ns
+ */
+ lan_wr(SYS_FRM_AGING_AGE_TX_ENA_SET(1) |
+ SYS_FRM_AGING_MAX_AGE_SET((2000000000 / 6182)),
+ lan9645x, SYS_FRM_AGING);
+
+ /* Setup flooding PGIDs for IPv4/IPv6 multicast. Control and dataplane
+ * use the same masks. Control frames are redirected to CPU, and
+ * the network stack is responsible for forwarding these.
+ * The dataplane is forwarding according to the offloaded MDB entries.
+ */
+ lan_wr(ANA_FLOODING_IPMC_FLD_MC4_DATA_SET(PGID_MCIPV4) |
+ ANA_FLOODING_IPMC_FLD_MC4_CTRL_SET(PGID_MC) |
+ ANA_FLOODING_IPMC_FLD_MC6_DATA_SET(PGID_MCIPV6) |
+ ANA_FLOODING_IPMC_FLD_MC6_CTRL_SET(PGID_MC),
+ lan9645x, ANA_FLOODING_IPMC);
+
+ for (int prio = 0; prio < NUM_PRIO_QUEUES; ++prio)
+ lan_wr(ANA_FLOODING_FLD_MULTICAST_SET(PGID_MC) |
+ ANA_FLOODING_FLD_UNICAST_SET(PGID_UC) |
+ ANA_FLOODING_FLD_BROADCAST_SET(PGID_BC),
+ lan9645x, ANA_FLOODING(prio));
+
+ /* Allow VLAN table to control whether cpu copy from the pgid table is
+ * enabled. Only the destination analysis resolves a CPU copy, so the
+ * aggregation and source regions are left at their reset value.
+ */
+ for (int i = 0; i < PGID_AGGR; ++i)
+ lan_wr(ANA_PGID_CFG_OBEY_VLAN_SET(1),
+ lan9645x, ANA_PGID_CFG(i));
+
+ /* Disable bridging by default */
+ for (int p = 0; p < lan9645x->num_phys_ports; p++) {
+ lan_wr(0, lan9645x, ANA_PGID(PGID_SRC + p));
+
+ /* Do not forward BPDU frames to the front ports and trap them
+ * to CPU
+ */
+ lan_wr(ANA_CPU_FWD_BPDU_CFG_BPDU_REDIR_ENA,
+ lan9645x, ANA_CPU_FWD_BPDU_CFG(p));
+ }
+
+ for (int i = 0; i < 16; i++) {
+ /* The register instance number corresponds to the
+ * address of the extracted frame. For instance:
+ * CPUQ_8021_CFG[4].CPUQ_BPDU_VAL
+ * is the cpu extraction queue used for BPDU frames
+ * with address 01-80-C2-00-00-04
+ */
+ lan_rmw(ANA_CPUQ_8021_CFG_CPUQ_BPDU_VAL_SET(LAN9645X_CPUQ_TRAP),
+ ANA_CPUQ_8021_CFG_CPUQ_BPDU_VAL,
+ lan9645x, ANA_CPUQ_8021_CFG(i));
+ }
+
+ /* Reserve 1664 bytes, 26 buffer cells, per (port, prio) for source
+ * tracking (resource 0, indices 0..95) and destination tracking
+ * (resource 2, indices 512..607). These are access watermarks, not
+ * pre-allocations: a flow draws from its reservation first, then
+ * from the shared pool. Keeping the reservation above a max-size
+ * Ethernet frame prevents a single frame from spilling into the
+ * shared pool, and cause pause frames to be emitted without actual
+ * congestion.
+ */
+ res_wm = QSYS_RES_CFG_WM_HIGH_SET(lan9645x_wm_enc(QS_SRC_BUF_RSV));
+ for (int i = 0; i <= QSYS_Q_RSRV; ++i) {
+ lan_wr(res_wm, lan9645x, QSYS_RES_CFG(i));
+ lan_wr(res_wm, lan9645x, QSYS_RES_CFG(512 + i));
+ }
+
+ lan9645x_port_cpu_init(lan9645x);
+
+ /* Multicast to cpu and all front ports */
+ lan_wr(BIT(lan9645x->num_phys_ports) | front_ports, lan9645x,
+ ANA_PGID(PGID_MC));
+
+ /* IP multicast to cpu and all front ports */
+ lan_wr(BIT(lan9645x->num_phys_ports) | front_ports, lan9645x,
+ ANA_PGID(PGID_MCIPV4));
+ lan_wr(BIT(lan9645x->num_phys_ports) | front_ports, lan9645x,
+ ANA_PGID(PGID_MCIPV6));
+
+ /* Unicast to cpu and all front ports */
+ lan_wr(BIT(lan9645x->num_phys_ports) | front_ports, lan9645x,
+ ANA_PGID(PGID_UC));
+
+ /* Broadcast to cpu and all front ports */
+ lan_wr(BIT(lan9645x->num_phys_ports) | front_ports, lan9645x,
+ ANA_PGID(PGID_BC));
+
+ ds->mtu_enforcement_ingress = true;
+ ds->assisted_learning_on_cpu_port = true;
+ ds->fdb_isolation = true;
+ ds->max_num_bridges = 1;
+
+ dev_info(lan9645x->dev,
+ "SKU features: max_ports=%d\n",
+ lan9645x->num_phys_ports - lan9645x->num_port_dis);
+
+ return 0;
+}
+
+static void lan9645x_port_phylink_get_caps(struct dsa_switch *ds, int port,
+ struct phylink_config *config)
+{
+ lan9645x_phylink_get_caps(ds->priv, port, config);
+}
+
+static const struct dsa_switch_ops lan9645x_switch_ops = {
+ .get_tag_protocol = lan9645x_get_tag_protocol,
+
+ .setup = lan9645x_setup,
+ .teardown = lan9645x_teardown,
+ .port_setup = lan9645x_port_setup,
+
+ /* Phylink integration */
+ .phylink_get_caps = lan9645x_port_phylink_get_caps,
+
+ /* MTU */
+ .port_change_mtu = lan9645x_change_mtu,
+ .port_max_mtu = lan9645x_get_max_mtu,
+};
+
+static int lan9645x_request_target_regmaps(struct lan9645x *lan9645x)
+{
+ const char *resource_name;
+ struct regmap *tgt_map;
+
+ for (int i = 0; i < NUM_TARGETS; i++) {
+ resource_name = lan9645x_resource_names[i];
+ if (!resource_name)
+ continue;
+
+ tgt_map = dev_get_regmap(lan9645x->dev->parent, resource_name);
+ if (IS_ERR_OR_NULL(tgt_map)) {
+ dev_err(lan9645x->dev, "Failed to get regmap=%s\n",
+ resource_name);
+ return -ENODEV;
+ }
+
+ lan9645x->rmap[i] = tgt_map;
+ }
+
+ return 0;
+}
+
+static void lan9645x_set_feat_dis(struct lan9645x *lan9645x)
+{
+ u32 feat_dis;
+
+ /* The features which can be physically disabled on some SKUs are:
+ * 1) Number of ports can be 5, 7 or 9. Any ports can be used, the chip
+ * tracks how many are active.
+ * 2) HSR/PRP. The duplicate discard table can be disabled.
+ * 3) TAS, frame preemption and PSFP can be disabled.
+ */
+ feat_dis = lan_rd(lan9645x, GCB_FEAT_DISABLE);
+
+ lan9645x->num_port_dis =
+ GCB_FEAT_DISABLE_FEAT_NUM_PORTS_DIS_GET(feat_dis);
+}
+
+static int lan9645x_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct lan9645x *lan9645x;
+ struct dsa_switch *ds;
+ int err = 0;
+
+ lan9645x = devm_kzalloc(dev, sizeof(*lan9645x), GFP_KERNEL);
+ if (!lan9645x)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, lan9645x);
+ lan9645x->dev = dev;
+
+ err = lan9645x_request_target_regmaps(lan9645x);
+ if (err)
+ return dev_err_probe(dev, err, "Failed to request regmaps\n");
+
+ ds = devm_kzalloc(dev, sizeof(*ds), GFP_KERNEL);
+ if (!ds)
+ return -ENOMEM;
+
+ lan9645x->ports = devm_kcalloc(lan9645x->dev, NUM_PHYS_PORTS,
+ sizeof(struct lan9645x_port *),
+ GFP_KERNEL);
+ if (!lan9645x->ports)
+ return -ENOMEM;
+
+ for (int port = 0; port < NUM_PHYS_PORTS; port++) {
+ struct lan9645x_port *p;
+
+ p = devm_kzalloc(lan9645x->dev, sizeof(*p), GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ p->lan9645x = lan9645x;
+ p->chip_port = port;
+ lan9645x->ports[port] = p;
+ }
+
+ ds->dev = dev;
+ ds->num_ports = NUM_PHYS_PORTS;
+ ds->num_tx_queues = NUM_PRIO_QUEUES;
+
+ ds->ops = &lan9645x_switch_ops;
+ ds->phylink_mac_ops = &lan9645x_phylink_mac_ops;
+ ds->priv = lan9645x;
+
+ lan9645x->ds = ds;
+ lan9645x->npi = -1;
+
+ lan9645x_set_feat_dis(lan9645x);
+
+ err = dsa_register_switch(ds);
+ if (err)
+ return dev_err_probe(dev, err, "Failed to register DSA switch\n");
+
+ return 0;
+}
+
+static void lan9645x_remove(struct platform_device *pdev)
+{
+ struct lan9645x *lan9645x = dev_get_drvdata(&pdev->dev);
+
+ if (!lan9645x)
+ return;
+
+ /* Calls lan9645x DSA .teardown */
+ dsa_unregister_switch(lan9645x->ds);
+ dev_set_drvdata(&pdev->dev, NULL);
+}
+
+static void lan9645x_shutdown(struct platform_device *pdev)
+{
+ struct lan9645x *lan9645x = dev_get_drvdata(&pdev->dev);
+
+ if (!lan9645x)
+ return;
+
+ dsa_switch_shutdown(lan9645x->ds);
+
+ dev_set_drvdata(&pdev->dev, NULL);
+}
+
+static const struct of_device_id lan9645x_switch_of_match[] = {
+ { .compatible = "microchip,lan96455s-switch" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, lan9645x_switch_of_match);
+
+static struct platform_driver lan9645x_switch_driver = {
+ .driver = {
+ .name = "lan96455s-switch",
+ .of_match_table = lan9645x_switch_of_match,
+ },
+ .probe = lan9645x_probe,
+ .remove = lan9645x_remove,
+ .shutdown = lan9645x_shutdown,
+};
+module_platform_driver(lan9645x_switch_driver);
+
+MODULE_DESCRIPTION("LAN9645x Switch Driver");
+MODULE_AUTHOR("Jens Emil Schulz Østergaard <jensemil.schulzostergaard@microchip.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_main.h b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.h
new file mode 100644
index 000000000000..3a4abedafa2c
--- /dev/null
+++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.h
@@ -0,0 +1,348 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (C) 2026 Microchip Technology Inc.
+ */
+
+#ifndef __LAN9645X_MAIN_H__
+#define __LAN9645X_MAIN_H__
+
+#include <linux/dsa/lan9645x.h>
+#include <linux/if_bridge.h>
+#include <linux/regmap.h>
+#include <net/dsa.h>
+
+#include "lan9645x_regs.h"
+
+/* Port modules 0-8 are front (user) ports. The chip additionally has two
+ * logical CPU port modules at indices 9 and 10. These are not the DSA CPU port.
+ * The CPU port modules are logical ports in the chip intended for management.
+ *
+ * The frame delivery mechanism can vary: direct register injection/extraction,
+ * or a front port can be used as the management port, called a Node Processor
+ * Interface (NPI) in the datasheet.
+ *
+ * LAN9645X uses the NPI approach, so the DSA CPU port is a front port
+ * (see lan9645x->npi) configured as NPI port.
+ *
+ * Therefore the CPU datapath has two port module indices of interest,
+ * lan9645x->npi and the cpu port module at index 9.
+ */
+#define NUM_PHYS_PORTS 9
+
+#define NUM_PRIO_QUEUES 8
+
+#define QS_SRC_BUF_RSV 1664
+
+/* Reserved amount for (SRC, PRIO) at index 8*SRC + PRIO
+ * See QSYS:RES_CTRL[*]:RES_CFG description
+ */
+#define QSYS_Q_RSRV 95
+
+/* Reserved VLAN IDs. */
+#define UNAWARE_PVID 0
+#define HOST_PVID 4095
+#define VLAN_MAX (HOST_PVID - 1)
+
+/* Port Group Identifiers (PGID) are port-masks applied to all frames.
+ *
+ * The forwarding engine outputs two masks for the forwarding decision, DEST and
+ * CPUQ. DEST determines which front ports will receive a frame, and CPUQ
+ * whether the frame is forwarded to the CPU.
+ *
+ * The replicated registers are organized like so in HW:
+ *
+ * 0-63: Destination analysis.
+ * 64-79: Aggregation analysis
+ * 80-(80+10-1): Source port analysis
+ *
+ * Destination: Destination PGIDs are the result of mac table lookups or
+ * flooding decisions. The resolved destination PGID will feed into DEST and set
+ * the bit for ANA_PGID_CFG.CPUQ_DST_PGID in CPUQ, if the CPU port module bit is
+ * set in the PGID. This is how PGID_CPU and host flooding work.
+ *
+ * The first NUM_PHYS_PORTS destination PGIDs can not be used freely, because
+ * dynamic learning writes mac table entries with DEST_IDX taken from the
+ * ingress ports ANA_PORT_CFG.PORTID_VAL. They come out of reset as BIT(i)
+ * for that reason.
+ *
+ * PGID NUM_PHYS_PORTS..63 come out of reset as zero and have no reserved
+ * role. We use them for L2 Multicast and flooding (see FLOODING and
+ * FLOODING_IPMC) and reserve a few at the end of the range. The CPU port module
+ * has no reserved destination PGID. It does have a reserved source PGID at
+ * PGID_SRC + NUM_PHYS_PORTS.
+ *
+ * Aggregation: Each frame receives a link aggregation code from the analyzer,
+ * based on a configurable algorithm. This code picks out one of the 16
+ * aggregation PGIDs to mask out ports in DEST. If no aggregation is configured,
+ * these are all-ones. The CPU port module bit is inert here.
+ *
+ * Source: Control which ports a given source port can forward to. A frame that
+ * is received on port n, uses entry PGID_SRC + n to mask out DEST.
+ * The default values are all bits set except for the index itself
+ * (no loopback). The CPU port module bit is inert here.
+ */
+
+#define PGID_AGGR 64
+#define PGID_SRC 80
+
+/* General purpose PGIDs. */
+#define PGID_GP_START NUM_PHYS_PORTS
+#define PGID_GP_END PGID_MRP
+
+/* Reserved PGIDs.
+ * PGID_MRP is a blackhole PGID
+ */
+#define PGID_MRP (PGID_AGGR - 7)
+#define PGID_CPU (PGID_AGGR - 6)
+#define PGID_UC (PGID_AGGR - 5)
+#define PGID_BC (PGID_AGGR - 4)
+#define PGID_MC (PGID_AGGR - 3)
+#define PGID_MCIPV4 (PGID_AGGR - 2)
+#define PGID_MCIPV6 (PGID_AGGR - 1)
+
+/* Flooding PGIDS:
+ * PGID_UC
+ * PGID_MC*
+ * PGID_BC
+ */
+
+#define GWM_MULTIPLIER_BIT BIT(8)
+#define LAN9645X_BUFFER_CELL_SZ 64
+
+#define RD_SLEEP_US 3
+#define RD_SLEEPTIMEOUT_US 100000
+#define SLOW_RD_SLEEP_US 1000
+#define SLOW_RD_SLEEPTIMEOUT_US 4000000
+
+/* regmap_read_poll_timeout() re-evaluates its map and address arguments on
+ * every iteration, so resolve both once up front.
+ */
+#define lan9645x_rd_poll_timeout(_lan9645x, _reg_macro, _val, _cond) \
+({ \
+ struct regmap *__map = lan_rmap((_lan9645x), _reg_macro); \
+ u32 __addr = lan_rel_addr(_reg_macro); \
+ \
+ regmap_read_poll_timeout(__map, __addr, (_val), (_cond), \
+ RD_SLEEP_US, RD_SLEEPTIMEOUT_US); \
+})
+
+#define lan9645x_rd_poll_slow(_lan9645x, _reg_macro, _val, _cond) \
+({ \
+ struct regmap *__map = lan_rmap((_lan9645x), _reg_macro); \
+ u32 __addr = lan_rel_addr(_reg_macro); \
+ \
+ regmap_read_poll_timeout(__map, __addr, (_val), (_cond), \
+ SLOW_RD_SLEEP_US, \
+ SLOW_RD_SLEEPTIMEOUT_US); \
+})
+
+/* NPI port prefix config encoding
+ *
+ * 0: No CPU extraction header (normal frames)
+ * 1: CPU extraction header without prefix
+ * 2: CPU extraction header with short prefix
+ * 3: CPU extraction header with long prefix
+ */
+enum lan9645x_tag_prefix {
+ LAN9645X_TAG_PREFIX_DISABLED = 0,
+ LAN9645X_TAG_PREFIX_NONE = 1,
+ LAN9645X_TAG_PREFIX_SHORT = 2,
+ LAN9645X_TAG_PREFIX_LONG = 3,
+};
+
+enum {
+ LAN9645X_SPEED_DISABLED = 0,
+ LAN9645X_SPEED_10 = 1,
+ LAN9645X_SPEED_100 = 2,
+ LAN9645X_SPEED_1000 = 3,
+ LAN9645X_SPEED_2500 = 4,
+};
+
+/* Rewriter VLAN port tagging encoding for REW:PORT[0-10]:TAG_CFG.TAG_CFG
+ *
+ * 0: Port tagging disabled.
+ * 1: Tag all frames, except when VID=PORT_VLAN_CFG.PORT_VID or VID=0.
+ * 2: Tag all frames, except when VID=0.
+ * 3: Tag all frames.
+ */
+enum lan9645x_vlan_port_tag {
+ LAN9645X_TAG_DISABLED = 0,
+ LAN9645X_TAG_NO_PVID_NO_UNAWARE = 1,
+ LAN9645X_TAG_NO_UNAWARE = 2,
+ LAN9645X_TAG_ALL = 3,
+};
+
+struct lan9645x {
+ struct device *dev;
+ struct dsa_switch *ds;
+ struct regmap *rmap[NUM_TARGETS];
+
+ /* NPI chip_port */
+ int npi;
+
+ u8 num_phys_ports;
+ struct lan9645x_port **ports;
+
+ int num_port_dis;
+};
+
+struct lan9645x_port {
+ struct lan9645x *lan9645x;
+
+ u8 chip_port;
+
+ bool rx_internal_delay;
+ bool tx_internal_delay;
+};
+
+extern const struct phylink_mac_ops lan9645x_phylink_mac_ops;
+
+/* PFC_CFG.FC_LINK_SPEED encoding */
+static inline int lan9645x_speed_fc_enc(int speed)
+{
+ switch (speed) {
+ case LAN9645X_SPEED_10:
+ return 3;
+ case LAN9645X_SPEED_100:
+ return 2;
+ case LAN9645X_SPEED_1000:
+ return 1;
+ case LAN9645X_SPEED_2500:
+ return 0;
+ default:
+ WARN_ON_ONCE(1);
+ return 1;
+ }
+}
+
+/* Watermark encode. See QSYS:RES_CTRL[*]:RES_CFG.WM_HIGH for details.
+ * Returns lowest encoded number which will fit request/ is larger than request.
+ * Or the maximum representable value, if request is too large.
+ */
+static inline u32 lan9645x_wm_enc(u32 value)
+{
+ value = DIV_ROUND_UP(value, LAN9645X_BUFFER_CELL_SZ);
+
+ if (value >= GWM_MULTIPLIER_BIT) {
+ value = DIV_ROUND_UP(value, 16);
+ if (value >= GWM_MULTIPLIER_BIT)
+ value = (GWM_MULTIPLIER_BIT - 1);
+ value |= GWM_MULTIPLIER_BIT;
+ }
+
+ return value;
+}
+
+static inline struct lan9645x_port *lan9645x_to_port(struct lan9645x *lan9645x,
+ int port)
+{
+ return lan9645x->ports[port];
+}
+
+static inline struct regmap *lan_tgt2rmap(struct lan9645x *lan9645x,
+ enum lan9645x_target t, int tinst)
+{
+ return lan9645x->rmap[t + tinst];
+}
+
+static inline u32 __lan_rel_addr(int gbase, int ginst, int gcnt,
+ int gwidth, int raddr, int rinst,
+ int rcnt, int rwidth)
+{
+ WARN_ON(ginst >= gcnt);
+ WARN_ON(rinst >= rcnt);
+ return gbase + ginst * gwidth + raddr + rinst * rwidth;
+}
+
+/* Get register address relative to target instance */
+static inline u32 lan_rel_addr(enum lan9645x_target t, int tinst, int tcnt,
+ int gbase, int ginst, int gcnt, int gwidth,
+ int raddr, int rinst, int rcnt, int rwidth)
+{
+ WARN_ON(tinst >= tcnt);
+ return __lan_rel_addr(gbase, ginst, gcnt, gwidth, raddr, rinst,
+ rcnt, rwidth);
+}
+
+static inline u32 lan_rd(struct lan9645x *lan9645x, enum lan9645x_target t,
+ int tinst, int tcnt, int gbase, int ginst,
+ int gcnt, int gwidth, int raddr, int rinst,
+ int rcnt, int rwidth)
+{
+ u32 addr, val = 0;
+
+ addr = lan_rel_addr(t, tinst, tcnt, gbase, ginst, gcnt, gwidth,
+ raddr, rinst, rcnt, rwidth);
+
+ WARN_ON_ONCE(regmap_read(lan_tgt2rmap(lan9645x, t, tinst), addr, &val));
+
+ return val;
+}
+
+static inline int lan_bulk_rd(void *val, size_t val_count,
+ struct lan9645x *lan9645x,
+ enum lan9645x_target t, int tinst, int tcnt,
+ int gbase, int ginst, int gcnt, int gwidth,
+ int raddr, int rinst, int rcnt, int rwidth)
+{
+ u32 addr;
+
+ addr = lan_rel_addr(t, tinst, tcnt, gbase, ginst, gcnt, gwidth,
+ raddr, rinst, rcnt, rwidth);
+
+ return regmap_bulk_read(lan_tgt2rmap(lan9645x, t, tinst), addr, val,
+ val_count);
+}
+
+static inline struct regmap *lan_rmap(struct lan9645x *lan9645x,
+ enum lan9645x_target t, int tinst,
+ int tcnt, int gbase, int ginst,
+ int gcnt, int gwidth, int raddr,
+ int rinst, int rcnt, int rwidth)
+{
+ return lan_tgt2rmap(lan9645x, t, tinst);
+}
+
+static inline void lan_wr(u32 val, struct lan9645x *lan9645x,
+ enum lan9645x_target t, int tinst, int tcnt,
+ int gbase, int ginst, int gcnt, int gwidth,
+ int raddr, int rinst, int rcnt, int rwidth)
+{
+ u32 addr;
+
+ addr = lan_rel_addr(t, tinst, tcnt, gbase, ginst, gcnt, gwidth,
+ raddr, rinst, rcnt, rwidth);
+
+ WARN_ON_ONCE(regmap_write(lan_tgt2rmap(lan9645x, t, tinst), addr, val));
+}
+
+static inline void lan_rmw(u32 val, u32 mask, struct lan9645x *lan9645x,
+ enum lan9645x_target t, int tinst, int tcnt,
+ int gbase, int ginst, int gcnt, int gwidth,
+ int raddr, int rinst, int rcnt, int rwidth)
+{
+ u32 addr;
+
+ addr = lan_rel_addr(t, tinst, tcnt, gbase, ginst, gcnt, gwidth,
+ raddr, rinst, rcnt, rwidth);
+
+ WARN_ON_ONCE(regmap_update_bits(lan_tgt2rmap(lan9645x, t, tinst),
+ addr, mask, val));
+}
+
+/* lan9645x_npi.c */
+void lan9645x_npi_port_init(struct lan9645x *lan9645x,
+ struct dsa_port *cpu_port);
+void lan9645x_npi_port_deinit(struct lan9645x *lan9645x, int port);
+void lan9645x_npi_cpuq_redirect(struct lan9645x *lan9645x, bool enable);
+
+/* lan9645x_port.c */
+int lan9645x_port_setup(struct dsa_switch *ds, int port);
+int lan9645x_port_set_maxlen(struct lan9645x *lan9645x, int port, size_t sdu);
+void lan9645x_port_cpu_init(struct lan9645x *lan9645x);
+
+/* lan9645x_phylink.c */
+void lan9645x_phylink_get_caps(struct lan9645x *lan9645x, int port,
+ struct phylink_config *c);
+void lan9645x_phylink_port_down(struct lan9645x *lan9645x, int port);
+
+#endif /* __LAN9645X_MAIN_H__ */
diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_npi.c b/drivers/net/dsa/microchip/lan9645x/lan9645x_npi.c
new file mode 100644
index 000000000000..945fc026b3ca
--- /dev/null
+++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_npi.c
@@ -0,0 +1,116 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright (C) 2026 Microchip Technology Inc.
+ */
+
+#include "lan9645x_main.h"
+
+void lan9645x_npi_port_init(struct lan9645x *lan9645x,
+ struct dsa_port *cpu_port)
+{
+ int port = cpu_port->index;
+ struct lan9645x_port *p;
+
+ p = lan9645x_to_port(lan9645x, port);
+ lan9645x->npi = port;
+
+ dev_dbg(lan9645x->dev, "NPI port=%d\n", port);
+
+ /* Never send injected frames back to the CPU. CPU queues are redirected
+ * to the NPI port on link up, see lan9645x_npi_cpuq_redirect().
+ */
+ lan_wr(QSYS_EXT_CPU_CFG_EXT_CPUQ_MSK_SET(0) |
+ QSYS_EXT_CPU_CFG_EXT_CPU_PORT_SET(p->chip_port) |
+ QSYS_EXT_CPU_CFG_EXT_CPU_KILL_ENA_SET(1) |
+ QSYS_EXT_CPU_CFG_INT_CPU_KILL_ENA_SET(1),
+ lan9645x, QSYS_EXT_CPU_CFG);
+
+ /* Configure IFH prefix mode for NPI port. We can not use an injection
+ * prefix, because it requires all frames sent on the port to contain
+ * the prefix. Frames without the prefix would get stuck in the queue
+ * system rendering the port unusable. Since we do not control
+ * what is sent to the NPI port, no prefix is our only option.
+ */
+ lan_rmw(SYS_PORT_MODE_INCL_XTR_HDR_SET(LAN9645X_TAG_PREFIX_LONG) |
+ SYS_PORT_MODE_INCL_INJ_HDR_SET(LAN9645X_TAG_PREFIX_NONE),
+ SYS_PORT_MODE_INCL_XTR_HDR |
+ SYS_PORT_MODE_INCL_INJ_HDR,
+ lan9645x,
+ SYS_PORT_MODE(p->chip_port));
+
+ /* Rewriting and extraction with IFH does not play nice together. A VLAN
+ * tag pushed into the frame by REW will cause 4 bytes at the end of the
+ * extraction header to be overwritten with the top 4 bytes of the DMAC.
+ *
+ * We can not use REW_PORT_CFG_NO_REWRITE=1 as that disabled RTAGD
+ * setting in the IFH
+ */
+ lan_rmw(REW_TAG_CFG_TAG_CFG_SET(LAN9645X_TAG_DISABLED),
+ REW_TAG_CFG_TAG_CFG, lan9645x, REW_TAG_CFG(p->chip_port));
+
+ /* Clear rewriter port vid */
+ lan_wr(0, lan9645x, REW_PORT_VLAN_CFG(p->chip_port));
+
+ /* Make sure frames with src_port=<CPU port module> are not reflected
+ * back via the NPI port. This could happen if a frame is flooded for
+ * instance. The *_CPU_KILL_ENA flags above only have an effect when a
+ * frame is output due to a CPU forwarding decision such as trapping or
+ * cpu copy.
+ */
+ lan_rmw(0, BIT(port), lan9645x,
+ ANA_PGID(PGID_SRC + lan9645x->num_phys_ports));
+}
+
+/* Toggle CPU queue redirect to the NPI port, flushing the CPU port module on
+ * enable
+ */
+void lan9645x_npi_cpuq_redirect(struct lan9645x *lan9645x, bool enable)
+{
+ int cpu_port = lan9645x->num_phys_ports;
+ u32 val;
+ int err;
+
+ lan_rmw(QSYS_EXT_CPU_CFG_EXT_CPUQ_MSK_SET(enable ? GENMASK(7, 0) : 0),
+ QSYS_EXT_CPU_CFG_EXT_CPUQ_MSK,
+ lan9645x, QSYS_EXT_CPU_CFG);
+
+ if (!enable)
+ return;
+
+ lan_wr(QS_XTR_FLUSH_FLUSH_SET(BIT(0)), lan9645x, QS_XTR_FLUSH);
+
+ err = lan9645x_rd_poll_slow(lan9645x, QSYS_SW_STATUS(cpu_port), val,
+ !QSYS_SW_STATUS_EQ_AVAIL_GET(val));
+ if (err)
+ dev_err(lan9645x->dev, "CPU port %d flush timeout\n", cpu_port);
+
+ lan_wr(QS_XTR_FLUSH_FLUSH_SET(0), lan9645x, QS_XTR_FLUSH);
+}
+
+void lan9645x_npi_port_deinit(struct lan9645x *lan9645x, int port)
+{
+ u32 no_cpu_port = FIELD_MAX(QSYS_EXT_CPU_CFG_EXT_CPU_PORT);
+ struct lan9645x_port *p;
+
+ if (port < 0)
+ return;
+
+ lan9645x->npi = -1;
+ p = lan9645x_to_port(lan9645x, port);
+
+ lan_wr(QSYS_EXT_CPU_CFG_EXT_CPUQ_MSK_SET(0) |
+ QSYS_EXT_CPU_CFG_EXT_CPU_PORT_SET(no_cpu_port) |
+ QSYS_EXT_CPU_CFG_EXT_CPU_KILL_ENA_SET(1) |
+ QSYS_EXT_CPU_CFG_INT_CPU_KILL_ENA_SET(1),
+ lan9645x, QSYS_EXT_CPU_CFG);
+
+ lan_rmw(SYS_PORT_MODE_INCL_XTR_HDR_SET(LAN9645X_TAG_PREFIX_DISABLED) |
+ SYS_PORT_MODE_INCL_INJ_HDR_SET(LAN9645X_TAG_PREFIX_DISABLED),
+ SYS_PORT_MODE_INCL_XTR_HDR |
+ SYS_PORT_MODE_INCL_INJ_HDR,
+ lan9645x,
+ SYS_PORT_MODE(p->chip_port));
+
+ lan_rmw(BIT(p->chip_port),
+ BIT(p->chip_port), lan9645x,
+ ANA_PGID(PGID_SRC + lan9645x->num_phys_ports));
+}
diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_phylink.c b/drivers/net/dsa/microchip/lan9645x/lan9645x_phylink.c
new file mode 100644
index 000000000000..b57beee8329f
--- /dev/null
+++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_phylink.c
@@ -0,0 +1,371 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright (C) 2026 Microchip Technology Inc.
+ */
+
+#include <linux/phy.h>
+
+#include "lan9645x_main.h"
+
+/* Port 7 is RGMII_0 and port 8 is RGMII_1 */
+#define RGMII_IDX(port) ((port) == 8 ? 1 : 0)
+
+void lan9645x_phylink_get_caps(struct lan9645x *lan9645x, int port,
+ struct phylink_config *c)
+{
+ c->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE | MAC_10 |
+ MAC_100 | MAC_1000FD;
+
+ switch (port) {
+ case 0 ... 4:
+ __set_bit(PHY_INTERFACE_MODE_GMII, c->supported_interfaces);
+ break;
+ case 5 ... 6:
+ /* SerDes ports: QSGMII/SGMII/1000BASEX/2500BASEX modes
+ * require PCS support which is not yet implemented. With an
+ * empty supported_interfaces, phylink_create() fails and
+ * dsa_tree_setup_ports() retries the port as unused, so a
+ * user port declared in DT is only disabled. A CPU port has
+ * no such fallback and fails the probe, which
+ * lan9645x_tag_npi_setup() rejects explicitly.
+ */
+ break;
+ case 7 ... 8:
+ /* QSGMII mode on ports 7-8 requires SerDes PCS support,
+ * which is not yet implemented.
+ */
+ phy_interface_set_rgmii(c->supported_interfaces);
+ break;
+ default:
+ break;
+ }
+}
+
+static void lan9645x_rgmii_set_speed(struct lan9645x *lan9645x, int port,
+ int speed)
+{
+ u8 tx_clk;
+
+ tx_clk = speed == SPEED_1000 ? 1 :
+ speed == SPEED_100 ? 2 :
+ speed == SPEED_10 ? 3 : 0;
+
+ lan_rmw(HSIO_RGMII_CFG_RGMII_RX_RST_SET(0) |
+ HSIO_RGMII_CFG_RGMII_TX_RST_SET(0) |
+ HSIO_RGMII_CFG_TX_CLK_CFG_SET(tx_clk),
+ HSIO_RGMII_CFG_RGMII_RX_RST |
+ HSIO_RGMII_CFG_RGMII_TX_RST |
+ HSIO_RGMII_CFG_TX_CLK_CFG,
+ lan9645x, HSIO_RGMII_CFG(RGMII_IDX(port)));
+}
+
+static void lan9645x_rgmii_dll_config(struct lan9645x_port *p)
+{
+ u32 rx_idx, tx_idx;
+
+ /* DLL register layout:
+ * (N*2): RGMII_N_RX
+ * (N*2)+1: RGMII_N_TX
+ */
+ rx_idx = RGMII_IDX(p->chip_port) * 2;
+ tx_idx = RGMII_IDX(p->chip_port) * 2 + 1;
+
+ /* Enable DLL in RGMII clock paths, deassert DLL reset, and start the
+ * delay tune FSM.
+ */
+ lan_rmw(HSIO_DLL_CFG_DLL_CLK_ENA_SET(1) |
+ HSIO_DLL_CFG_DLL_RST_SET(0) |
+ HSIO_DLL_CFG_DLL_ENA_SET(p->rx_internal_delay) |
+ HSIO_DLL_CFG_DELAY_ENA_SET(p->rx_internal_delay),
+ HSIO_DLL_CFG_DLL_CLK_ENA |
+ HSIO_DLL_CFG_DLL_RST |
+ HSIO_DLL_CFG_DLL_ENA |
+ HSIO_DLL_CFG_DELAY_ENA,
+ p->lan9645x, HSIO_DLL_CFG(rx_idx));
+
+ lan_rmw(HSIO_DLL_CFG_DLL_CLK_ENA_SET(1) |
+ HSIO_DLL_CFG_DLL_RST_SET(0) |
+ HSIO_DLL_CFG_DLL_ENA_SET(p->tx_internal_delay) |
+ HSIO_DLL_CFG_DELAY_ENA_SET(p->tx_internal_delay),
+ HSIO_DLL_CFG_DLL_CLK_ENA |
+ HSIO_DLL_CFG_DLL_RST |
+ HSIO_DLL_CFG_DLL_ENA |
+ HSIO_DLL_CFG_DELAY_ENA,
+ p->lan9645x, HSIO_DLL_CFG(tx_idx));
+}
+
+static struct lan9645x_port *
+lan9645x_phylink_config_to_port(struct phylink_config *config)
+{
+ struct dsa_port *dp = dsa_phylink_to_port(config);
+
+ return lan9645x_to_port(dp->ds->priv, dp->index);
+}
+
+static int lan9645x_phylink_mac_prepare(struct phylink_config *config,
+ unsigned int mode,
+ phy_interface_t iface)
+{
+ struct lan9645x_port *p = lan9645x_phylink_config_to_port(config);
+ struct lan9645x *lan9645x = p->lan9645x;
+ int port = p->chip_port;
+
+ lan_rmw(HSIO_HW_CFG_GMII_ENA_SET(BIT(port)),
+ HSIO_HW_CFG_GMII_ENA_SET(BIT(port)), lan9645x, HSIO_HW_CFG);
+
+ return 0;
+}
+
+static void lan9645x_phylink_mac_config(struct phylink_config *config,
+ unsigned int mode,
+ const struct phylink_link_state *state)
+{
+ struct lan9645x_port *p = lan9645x_phylink_config_to_port(config);
+
+ if (phy_interface_mode_is_rgmii(state->interface))
+ lan9645x_rgmii_dll_config(p);
+}
+
+static bool lan9645x_port_is_cuphy(struct lan9645x *lan9645x, int port,
+ phy_interface_t interface)
+{
+ return port >= 0 && port <= 4 && interface == PHY_INTERFACE_MODE_GMII;
+}
+
+void lan9645x_phylink_port_down(struct lan9645x *lan9645x, int port)
+{
+ struct lan9645x_port *p = lan9645x_to_port(lan9645x, port);
+ u32 val;
+
+ /* Disable MAC frame reception */
+ lan_rmw(DEV_MAC_ENA_CFG_RX_ENA_SET(0),
+ DEV_MAC_ENA_CFG_RX_ENA,
+ lan9645x, DEV_MAC_ENA_CFG(p->chip_port));
+
+ /* Disable traffic being sent to or from switch port */
+ lan_rmw(QSYS_SW_PORT_MODE_PORT_ENA_SET(0),
+ QSYS_SW_PORT_MODE_PORT_ENA,
+ lan9645x, QSYS_SW_PORT_MODE(p->chip_port));
+
+ /* Disable dequeuing from the egress queues */
+ lan_rmw(QSYS_PORT_MODE_DEQUEUE_DIS_SET(1),
+ QSYS_PORT_MODE_DEQUEUE_DIS,
+ lan9645x, QSYS_PORT_MODE(p->chip_port));
+
+ /* Disable Flowcontrol */
+ lan_rmw(SYS_PAUSE_CFG_PAUSE_ENA_SET(0),
+ SYS_PAUSE_CFG_PAUSE_ENA,
+ lan9645x, SYS_PAUSE_CFG(p->chip_port));
+
+ /* Wait a worst case time 8ms (10K jumbo/10Mbit) */
+ usleep_range(8 * USEC_PER_MSEC, 9 * USEC_PER_MSEC);
+
+ /* Disable HDX backpressure. */
+ lan_rmw(SYS_FRONT_PORT_MODE_HDX_MODE_SET(0),
+ SYS_FRONT_PORT_MODE_HDX_MODE,
+ lan9645x, SYS_FRONT_PORT_MODE(p->chip_port));
+
+ /* Flush the queues associated with the port */
+ lan_rmw(QSYS_SW_PORT_MODE_AGING_MODE_SET(3),
+ QSYS_SW_PORT_MODE_AGING_MODE,
+ lan9645x, QSYS_SW_PORT_MODE(p->chip_port));
+
+ /* Enable dequeuing from the egress queues */
+ lan_rmw(QSYS_PORT_MODE_DEQUEUE_DIS_SET(0),
+ QSYS_PORT_MODE_DEQUEUE_DIS,
+ lan9645x, QSYS_PORT_MODE(p->chip_port));
+
+ /* Wait until flushing is complete */
+ if (lan9645x_rd_poll_slow(lan9645x, QSYS_SW_STATUS(p->chip_port),
+ val, !QSYS_SW_STATUS_EQ_AVAIL_GET(val)))
+ dev_err(lan9645x->dev, "Flush timeout chip port %u\n",
+ p->chip_port);
+
+ /* Disable MAC tx */
+ lan_rmw(DEV_MAC_ENA_CFG_TX_ENA_SET(0),
+ DEV_MAC_ENA_CFG_TX_ENA,
+ lan9645x, DEV_MAC_ENA_CFG(p->chip_port));
+
+ /* Reset the Port and MAC clock domains */
+ lan_rmw(DEV_CLOCK_CFG_PORT_RST_SET(1),
+ DEV_CLOCK_CFG_PORT_RST,
+ lan9645x, DEV_CLOCK_CFG(p->chip_port));
+
+ /* Wait before resetting MAC clock domains. */
+ usleep_range(USEC_PER_MSEC, 2 * USEC_PER_MSEC);
+
+ lan_rmw(DEV_CLOCK_CFG_MAC_TX_RST_SET(1) |
+ DEV_CLOCK_CFG_MAC_RX_RST_SET(1) |
+ DEV_CLOCK_CFG_PORT_RST_SET(1),
+ DEV_CLOCK_CFG_MAC_TX_RST |
+ DEV_CLOCK_CFG_MAC_RX_RST |
+ DEV_CLOCK_CFG_PORT_RST,
+ lan9645x, DEV_CLOCK_CFG(p->chip_port));
+
+ /* Clear flushing */
+ lan_rmw(QSYS_SW_PORT_MODE_AGING_MODE_SET(2),
+ QSYS_SW_PORT_MODE_AGING_MODE,
+ lan9645x, QSYS_SW_PORT_MODE(p->chip_port));
+}
+
+static void lan9645x_phylink_mac_link_down(struct phylink_config *config,
+ unsigned int link_an_mode,
+ phy_interface_t interface)
+{
+ struct lan9645x_port *p = lan9645x_phylink_config_to_port(config);
+ struct lan9645x *lan9645x = p->lan9645x;
+
+ /* Stop forwarding all CPU queues to the NPI port before the port is put
+ * in reset
+ */
+ if (p->chip_port == lan9645x->npi)
+ lan9645x_npi_cpuq_redirect(lan9645x, false);
+
+ lan9645x_phylink_port_down(lan9645x, p->chip_port);
+}
+
+static void lan9645x_phylink_mac_link_up(struct phylink_config *config,
+ struct phy_device *phydev,
+ unsigned int link_an_mode,
+ phy_interface_t interface, int speed,
+ int duplex, bool tx_pause,
+ bool rx_pause)
+{
+ struct lan9645x_port *p = lan9645x_phylink_config_to_port(config);
+ int rx_ifg1, rx_ifg2, tx_ifg, gtx_clk = 0;
+ struct lan9645x *lan9645x = p->lan9645x;
+ int gspeed = LAN9645X_SPEED_DISABLED;
+ int port = p->chip_port;
+ int mode = 0;
+ int fc_spd;
+ bool fdx;
+
+ /* Configure RGMII TX clock for the negotiated speed */
+ if (phy_interface_mode_is_rgmii(interface))
+ lan9645x_rgmii_set_speed(lan9645x, port, speed);
+
+ fdx = duplex == DUPLEX_FULL || speed == SPEED_1000;
+
+ if (fdx)
+ mode |= DEV_MAC_MODE_CFG_FDX_ENA_SET(1);
+
+ tx_ifg = fdx ? 0x6 : 0x5;
+ rx_ifg2 = 0x2;
+
+ switch (speed) {
+ case SPEED_10:
+ rx_ifg1 = 0x2;
+ gspeed = LAN9645X_SPEED_10;
+ break;
+ case SPEED_100:
+ rx_ifg1 = 0x1;
+ gspeed = LAN9645X_SPEED_100;
+ break;
+ case SPEED_1000:
+ gspeed = LAN9645X_SPEED_1000;
+ mode |= DEV_MAC_MODE_CFG_GIGA_MODE_ENA_SET(1);
+ rx_ifg1 = 0x1;
+ gtx_clk = 1;
+ break;
+ default:
+ dev_err(lan9645x->dev, "Unsupported speed on port %d: %d\n",
+ p->chip_port, speed);
+ return;
+ }
+
+ fc_spd = lan9645x_speed_fc_enc(gspeed);
+
+ lan_rmw(mode,
+ DEV_MAC_MODE_CFG_FDX_ENA |
+ DEV_MAC_MODE_CFG_GIGA_MODE_ENA,
+ lan9645x, DEV_MAC_MODE_CFG(p->chip_port));
+
+ lan_rmw(DEV_MAC_IFG_CFG_TX_IFG_SET(tx_ifg) |
+ DEV_MAC_IFG_CFG_RX_IFG1_SET(rx_ifg1) |
+ DEV_MAC_IFG_CFG_RX_IFG2_SET(rx_ifg2),
+ DEV_MAC_IFG_CFG_TX_IFG |
+ DEV_MAC_IFG_CFG_RX_IFG1 |
+ DEV_MAC_IFG_CFG_RX_IFG2,
+ lan9645x, DEV_MAC_IFG_CFG(p->chip_port));
+
+ if (lan9645x_port_is_cuphy(lan9645x, port, interface)) {
+ lan_rmw(CHIP_TOP_CUPHY_PORT_CFG_GTX_CLK_ENA_SET(gtx_clk),
+ CHIP_TOP_CUPHY_PORT_CFG_GTX_CLK_ENA, lan9645x,
+ CHIP_TOP_CUPHY_PORT_CFG(p->chip_port));
+ }
+
+ lan_rmw(SYS_PAUSE_CFG_PAUSE_ENA_SET(1),
+ SYS_PAUSE_CFG_PAUSE_ENA,
+ lan9645x, SYS_PAUSE_CFG(p->chip_port));
+
+ /* Flow control */
+ lan_rmw(SYS_MAC_FC_CFG_FC_LINK_SPEED_SET(fc_spd) |
+ SYS_MAC_FC_CFG_FC_LATENCY_CFG_SET(0x7) |
+ SYS_MAC_FC_CFG_ZERO_PAUSE_ENA_SET(1) |
+ SYS_MAC_FC_CFG_PAUSE_VAL_CFG_SET(0xffff) |
+ SYS_MAC_FC_CFG_RX_FC_ENA_SET(rx_pause ? 1 : 0) |
+ SYS_MAC_FC_CFG_TX_FC_ENA_SET(tx_pause ? 1 : 0),
+ SYS_MAC_FC_CFG_FC_LINK_SPEED |
+ SYS_MAC_FC_CFG_FC_LATENCY_CFG |
+ SYS_MAC_FC_CFG_ZERO_PAUSE_ENA |
+ SYS_MAC_FC_CFG_PAUSE_VAL_CFG |
+ SYS_MAC_FC_CFG_RX_FC_ENA |
+ SYS_MAC_FC_CFG_TX_FC_ENA,
+ lan9645x, SYS_MAC_FC_CFG(p->chip_port));
+
+ /* Enable MAC module */
+ lan_wr(DEV_MAC_ENA_CFG_RX_ENA_SET(1) |
+ DEV_MAC_ENA_CFG_TX_ENA_SET(1),
+ lan9645x, DEV_MAC_ENA_CFG(p->chip_port));
+
+ /* port _must_ be taken out of reset before MAC. */
+ lan_rmw(DEV_CLOCK_CFG_PORT_RST_SET(0),
+ DEV_CLOCK_CFG_PORT_RST,
+ lan9645x, DEV_CLOCK_CFG(p->chip_port));
+
+ /* Take out the clock from reset. Note this write will set all these
+ * fields to zero:
+ *
+ * DEV_CLOCK_CFG[*].MAC_TX_RST
+ * DEV_CLOCK_CFG[*].MAC_RX_RST
+ * DEV_CLOCK_CFG[*].PCS_TX_RST
+ * DEV_CLOCK_CFG[*].PCS_RX_RST
+ * DEV_CLOCK_CFG[*].PORT_RST
+ * DEV_CLOCK_CFG[*].PHY_RST
+ *
+ * Note link_down will assert PORT_RST, MAC_RX_RST and MAC_TX_RST, so
+ * we are effectively taking the mac tx/rx clocks out of reset.
+ *
+ * This linkspeed field has a slightly different encoding from others:
+ *
+ * - 0 is no-link
+ * - 1 is both 2500/1000
+ * - 2 is 100mbit
+ * - 3 is 10mbit
+ *
+ */
+ lan_wr(DEV_CLOCK_CFG_LINK_SPEED_SET(fc_spd),
+ lan9645x,
+ DEV_CLOCK_CFG(p->chip_port));
+
+ /* Core: Enable port for frame transfer */
+ lan_rmw(QSYS_SW_PORT_MODE_PORT_ENA_SET(1) |
+ QSYS_SW_PORT_MODE_SCH_NEXT_CFG_SET(1) |
+ QSYS_SW_PORT_MODE_INGRESS_DROP_MODE_SET(1) |
+ QSYS_SW_PORT_MODE_TX_PFC_ENA_SET(0),
+ QSYS_SW_PORT_MODE_PORT_ENA |
+ QSYS_SW_PORT_MODE_SCH_NEXT_CFG |
+ QSYS_SW_PORT_MODE_INGRESS_DROP_MODE |
+ QSYS_SW_PORT_MODE_TX_PFC_ENA,
+ lan9645x, QSYS_SW_PORT_MODE(p->chip_port));
+
+ /* Forward all CPU queues to the NPI port */
+ if (port == lan9645x->npi)
+ lan9645x_npi_cpuq_redirect(lan9645x, true);
+}
+
+const struct phylink_mac_ops lan9645x_phylink_mac_ops = {
+ .mac_prepare = lan9645x_phylink_mac_prepare,
+ .mac_config = lan9645x_phylink_mac_config,
+ .mac_link_down = lan9645x_phylink_mac_link_down,
+ .mac_link_up = lan9645x_phylink_mac_link_up,
+};
diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_port.c b/drivers/net/dsa/microchip/lan9645x/lan9645x_port.c
new file mode 100644
index 000000000000..4d2d922bacba
--- /dev/null
+++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_port.c
@@ -0,0 +1,166 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright (C) 2026 Microchip Technology Inc.
+ */
+
+#include <linux/of_net.h>
+
+#include "lan9645x_main.h"
+
+void lan9645x_port_cpu_init(struct lan9645x *lan9645x)
+{
+ u32 cpu_module_mask = BIT(lan9645x->num_phys_ports);
+
+ /* Map the 8 CPU extraction queues to the CPU port module (datasheet is
+ * wrong)
+ */
+ lan_wr(0, lan9645x, QSYS_CPU_GROUP_MAP);
+
+ /* Configure first cpu port for manual extraction. */
+ lan_rmw(QS_XTR_GRP_CFG_MODE_SET(1),
+ QS_XTR_GRP_CFG_MODE,
+ lan9645x, QS_XTR_GRP_CFG(0));
+
+ /* The CPU will only use its reserved buffer in the shared queue system
+ * and none of the shared buffer space, therefore we disable resource
+ * sharing in egress direction. We must not disable resource sharing in
+ * the ingress direction, because some traffic test scenarios require
+ * loads of buffer memory for frames initiated by the CPU.
+ */
+ lan_rmw(QSYS_EGR_NO_SHARING_EGR_NO_SHARING_SET(cpu_module_mask),
+ QSYS_EGR_NO_SHARING_EGR_NO_SHARING_SET(cpu_module_mask),
+ lan9645x, QSYS_EGR_NO_SHARING);
+
+ /* The CPU should also discard frames forwarded to it if it has run
+ * out of the reserved buffer space. Otherwise they will be held back
+ * in the ingress queues with potential head-of-line blocking effects.
+ */
+ lan_rmw(QSYS_EGR_DROP_MODE_EGRESS_DROP_MODE_SET(cpu_module_mask),
+ QSYS_EGR_DROP_MODE_EGRESS_DROP_MODE_SET(cpu_module_mask),
+ lan9645x, QSYS_EGR_DROP_MODE);
+
+ lan_wr(cpu_module_mask, lan9645x, ANA_PGID(PGID_CPU));
+
+ /* Enable switching to/from cpu port. Keep default aging-mode. */
+ lan_rmw(QSYS_SW_PORT_MODE_PORT_ENA_SET(1) |
+ QSYS_SW_PORT_MODE_SCH_NEXT_CFG_SET(1) |
+ QSYS_SW_PORT_MODE_INGRESS_DROP_MODE_SET(1),
+ QSYS_SW_PORT_MODE_PORT_ENA |
+ QSYS_SW_PORT_MODE_SCH_NEXT_CFG |
+ QSYS_SW_PORT_MODE_INGRESS_DROP_MODE,
+ lan9645x, QSYS_SW_PORT_MODE(lan9645x->num_phys_ports));
+}
+
+/* VLAN tag overhead is handled by DEV_MAC_TAGS_CFG */
+int lan9645x_port_set_maxlen(struct lan9645x *lan9645x, int port, size_t sdu)
+{
+ struct lan9645x_port *p = lan9645x_to_port(lan9645x, port);
+ int maxlen = sdu + ETH_HLEN + ETH_FCS_LEN;
+
+ if (port == lan9645x->npi) {
+ maxlen += LAN9645X_IFH_LEN_BYTES;
+ maxlen += LAN9645X_LONG_PREFIX_LEN;
+ }
+
+ lan_wr(DEV_MAC_MAXLEN_CFG_MAX_LEN_SET(maxlen), lan9645x,
+ DEV_MAC_MAXLEN_CFG(p->chip_port));
+
+ /* Set Pause WM hysteresis */
+ lan_rmw(SYS_PAUSE_CFG_PAUSE_STOP_SET(lan9645x_wm_enc(4 * maxlen)) |
+ SYS_PAUSE_CFG_PAUSE_START_SET(lan9645x_wm_enc(6 * maxlen)),
+ SYS_PAUSE_CFG_PAUSE_START |
+ SYS_PAUSE_CFG_PAUSE_STOP,
+ lan9645x,
+ SYS_PAUSE_CFG(p->chip_port));
+
+ return 0;
+}
+
+static void lan9645x_parse_mac_delay(struct lan9645x *lan9645x, int port,
+ struct device_node *dn, const char *name,
+ bool *out)
+{
+ u32 val;
+
+ if (of_property_read_u32(dn, name, &val))
+ return;
+
+ if (val != 0 && val != 2000) {
+ dev_warn(lan9645x->dev,
+ "port %d: %s only supports a 2ns delay (on/off), got %u ps\n",
+ port, name, val);
+ return;
+ }
+
+ *out = val == 2000;
+}
+
+int lan9645x_port_setup(struct dsa_switch *ds, int port)
+{
+ struct dsa_port *dp = dsa_to_port(ds, port);
+ struct lan9645x *lan9645x = ds->priv;
+ struct lan9645x_port *p;
+
+ p = lan9645x_to_port(lan9645x, port);
+
+ if (dp->dn) {
+ lan9645x_parse_mac_delay(lan9645x, port, dp->dn,
+ "rx-internal-delay-ps",
+ &p->rx_internal_delay);
+
+ lan9645x_parse_mac_delay(lan9645x, port, dp->dn,
+ "tx-internal-delay-ps",
+ &p->tx_internal_delay);
+ }
+
+ /* Disable learning on port */
+ lan_rmw(ANA_PORT_CFG_LEARN_ENA_SET(0),
+ ANA_PORT_CFG_LEARN_ENA,
+ lan9645x, ANA_PORT_CFG(p->chip_port));
+
+ lan9645x_port_set_maxlen(lan9645x, port, ETH_DATA_LEN);
+
+ /* Load HDX backoff seed (fixed per-port, one-shot strobe) */
+ lan_rmw(DEV_MAC_HDX_CFG_SEED_SET(p->chip_port) |
+ DEV_MAC_HDX_CFG_SEED_LOAD_SET(1),
+ DEV_MAC_HDX_CFG_SEED |
+ DEV_MAC_HDX_CFG_SEED_LOAD, lan9645x,
+ DEV_MAC_HDX_CFG(p->chip_port));
+
+ lan_rmw(DEV_MAC_HDX_CFG_SEED_LOAD_SET(0),
+ DEV_MAC_HDX_CFG_SEED_LOAD, lan9645x,
+ DEV_MAC_HDX_CFG(p->chip_port));
+
+ /* Set SMAC of Pause frame (00:00:00:00:00:00) */
+ lan_wr(0, lan9645x, DEV_FC_MAC_LOW_CFG(p->chip_port));
+ lan_wr(0, lan9645x, DEV_FC_MAC_HIGH_CFG(p->chip_port));
+
+ lan9645x_phylink_port_down(lan9645x, port);
+
+ /* Drop frames with multicast source address */
+ lan_rmw(ANA_DROP_CFG_DROP_MC_SMAC_ENA_SET(1),
+ ANA_DROP_CFG_DROP_MC_SMAC_ENA, lan9645x,
+ ANA_DROP_CFG(p->chip_port));
+
+ lan_rmw(DEV_MAC_TAGS_CFG_VLAN_AWR_ENA_SET(1) |
+ DEV_MAC_TAGS_CFG_PB_ENA_SET(1) |
+ DEV_MAC_TAGS_CFG_VLAN_LEN_AWR_ENA_SET(1) |
+ DEV_MAC_TAGS_CFG_TAG_ID_SET(ETH_P_8021AD),
+ DEV_MAC_TAGS_CFG_VLAN_AWR_ENA |
+ DEV_MAC_TAGS_CFG_PB_ENA |
+ DEV_MAC_TAGS_CFG_VLAN_LEN_AWR_ENA |
+ DEV_MAC_TAGS_CFG_TAG_ID,
+ lan9645x, DEV_MAC_TAGS_CFG(p->chip_port));
+
+ /* Enable receiving frames on the port, and activate auto-learning of
+ * MAC addresses. LEARNAUTO is ignored when LEARN_ENA=0.
+ */
+ lan_rmw(ANA_PORT_CFG_LEARNAUTO_SET(1) |
+ ANA_PORT_CFG_RECV_ENA_SET(1) |
+ ANA_PORT_CFG_PORTID_VAL_SET(p->chip_port),
+ ANA_PORT_CFG_LEARNAUTO |
+ ANA_PORT_CFG_RECV_ENA |
+ ANA_PORT_CFG_PORTID_VAL,
+ lan9645x, ANA_PORT_CFG(p->chip_port));
+
+ return 0;
+}
--
2.52.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH net-next v13 5/9] net: dsa: lan9645x: add vlan support
2026-09-29 7:48 [PATCH net-next v13 0/9] net: dsa: add DSA support for the LAN9645x switch chip family Jens Emil Schulz Østergaard
` (3 preceding siblings ...)
2026-09-29 7:48 ` [PATCH net-next v13 4/9] net: dsa: lan9645x: add basic dsa driver for LAN9645X Jens Emil Schulz Østergaard
@ 2026-09-29 7:48 ` Jens Emil Schulz Østergaard
2026-10-02 21:14 ` netdev-bot+sashiko
2026-09-29 7:48 ` [PATCH net-next v13 6/9] net: dsa: lan9645x: add mac table integration Jens Emil Schulz Østergaard
` (3 subsequent siblings)
8 siblings, 1 reply; 18+ messages in thread
From: Jens Emil Schulz Østergaard @ 2026-09-29 7:48 UTC (permalink / raw)
To: UNGLinuxDriver, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Woojung Huh,
Russell King, Steen Hegelund, Daniel Machon, Geert Uytterhoeven,
Magnus Damm
Cc: linux-kernel, netdev, devicetree, linux-renesas-soc,
Jens Emil Schulz Østergaard
Add VLAN support for VLAN-aware and VLAN-unaware bridges.
VID 4095 (HOST_PVID) is reserved and used internally as the port
VLAN for standalone (non-bridged) ports so their FDB domain is
isolated from bridged traffic.
VID 0 is used internally as the port VLAN for VLAN-unaware bridge ports
(UNAWARE_PVID).
The VLAN table and the per port VLAN configuration are serialized with
fwd_domain_lock, since .port_setup runs without rtnl held.
There are two chip limitations. Only one egress-untagged VLAN is
supported on a port that also has tagged VLANs. The rewriter can exempt
a single PORT_VID from tagging, so adding a second untagged VLAN
alongside tagged ones is rejected with -EBUSY.
Both TPIDs are recognized as VLAN tags unconditionally. There is no per
port control to classify one TPID as tagged while treating the other as
untagged customer data.
Only bridge vlan_protocol 802.1Q is offloaded. DSA does not pass
SWITCHDEV_ATTR_ID_BRIDGE_VLAN_PROTOCOL to drivers, so an 802.1ad bridge
cannot be refused, and the classifier does not honour it.
S-tag and C-tag are always treated as interchangeable. Full C-component
conformance would need S-tagged frames treated as untagged by a VLAN
aware bridge, dropped on a port without a pvid and classified to the
pvid on a port with one.
Of those, only the drop is expressible, through
ANA_DROP_CFG.DROP_S_TAGGED_ENA. The reclassification needs the VCAP to
match on tag type and overwrite the classified VID, see
ocelot_update_vlan_reclassify_rule().
We program neither, since dropping without reclassifying is less
consistent than treating both tag types alike.
Reviewed-by: Steen Hegelund <Steen.Hegelund@microchip.com>
Signed-off-by: Jens Emil Schulz Østergaard <jensemil.schulzostergaard@microchip.com>
---
Changes in v12:
- Serialize the VLAN table with fwd_domain_lock, and assert it in the
lan9645x_vlan.c entry points. .port_setup runs outside rtnl, so a
registered user port can be bridged while a later port is still being
set up.
- Move this patch before bridge support. It now carries bridge_mask,
fwd_domain_lock and lan9645x_port_is_bridged().
- Move lan9645x_vlan_clear_hostmode() to the bridge patch, together with
its only caller.
- Describe the untagged VLAN limit, and why S-tag and C-tag are treated
as interchangeable.
- Say that only bridge vlan_protocol 802.1Q is offloaded.
- lan9645x_teardown(): destroy fwd_domain_lock before the NPI port deinit,
so teardown unwinds in reverse order of setup.
- Drop the six unused lan9645x_vlan bitfields.
- Spell VLAN in upper case in the two dev_err() strings.
- Split the extack in lan9645x_vlan_port_add_vlan(). The condition also
fires when a tagged VLAN is added to a port with several untagged ones,
where there is no native VLAN to report.
Changes in v8:
- Use lan9645x->num_phys_ports instead of CPU_PORT for the CPU port
module, and reword the portmask comment.
Changes in v5:
- switch -EBUSY to -EINVAL for vlan add/del in the reserved range.
- remove reserved HSR vlan
- update commit message
Changes in v4:
- fix clear HOST_PVID vlan membership when a port joins a bridge
- explicit default value write to tag type register for untagged frames
- use lan_rmw for ANA_DROP_CFG
- add comment for error path in lan9645x_vlan_hw_wr
- use dsa_switch_for_each_user_port to iterate ports
Changes in v3:
- use SET register macros in vlan_hw_wr
- add vlan id bounds check to vlan_del
- return vlan_hw_wr timeout err on init
- move cpu vlan action after bounds check
Changes in v2:
- redesign based on selftests which rely on changing vlan_default_pvid.
Our HW limitations were too forward. Following Vladimirs changes to
ocelot VLAN implementation, we now dynamically change egress tag
configuration, allowing more states.
- selftests are passing, except an expected failure w.r.t ctag/stag
conformance, which is a hw limitation.
---
drivers/net/dsa/microchip/lan9645x/Makefile | 1 +
drivers/net/dsa/microchip/lan9645x/lan9645x_main.c | 69 ++++
drivers/net/dsa/microchip/lan9645x/lan9645x_main.h | 32 ++
drivers/net/dsa/microchip/lan9645x/lan9645x_port.c | 6 +
drivers/net/dsa/microchip/lan9645x/lan9645x_vlan.c | 402 +++++++++++++++++++++
5 files changed, 510 insertions(+)
diff --git a/drivers/net/dsa/microchip/lan9645x/Makefile b/drivers/net/dsa/microchip/lan9645x/Makefile
index 7cc0ae0ada40..e049114b3563 100644
--- a/drivers/net/dsa/microchip/lan9645x/Makefile
+++ b/drivers/net/dsa/microchip/lan9645x/Makefile
@@ -6,3 +6,4 @@ mchp-lan9645x-objs := \
lan9645x_npi.o \
lan9645x_phylink.o \
lan9645x_port.o \
+ lan9645x_vlan.o \
diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
index c7de3836e696..6818b91f4bed 100644
--- a/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
+++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
@@ -74,6 +74,7 @@ static void lan9645x_teardown(struct dsa_switch *ds)
{
struct lan9645x *lan9645x = ds->priv;
+ mutex_destroy(&lan9645x->fwd_domain_lock);
lan9645x_npi_port_deinit(lan9645x, lan9645x->npi);
}
@@ -157,6 +158,11 @@ static int lan9645x_setup(struct dsa_switch *ds)
return err;
}
+ mutex_init(&lan9645x->fwd_domain_lock);
+ err = lan9645x_vlan_init(lan9645x);
+ if (err)
+ goto err_mutex;
+
/* Link Aggregation Mode: NETDEV_LAG_HASH_L2 */
lan_wr(ANA_AGGR_CFG_AC_SMAC_ENA |
ANA_AGGR_CFG_AC_DMAC_ENA,
@@ -288,6 +294,11 @@ static int lan9645x_setup(struct dsa_switch *ds)
lan9645x->num_phys_ports - lan9645x->num_port_dis);
return 0;
+
+err_mutex:
+ mutex_destroy(&lan9645x->fwd_domain_lock);
+ lan9645x_npi_port_deinit(lan9645x, lan9645x->npi);
+ return err;
}
static void lan9645x_port_phylink_get_caps(struct dsa_switch *ds, int port,
@@ -296,6 +307,59 @@ static void lan9645x_port_phylink_get_caps(struct dsa_switch *ds, int port,
lan9645x_phylink_get_caps(ds->priv, port, config);
}
+static int lan9645x_port_vlan_filtering(struct dsa_switch *ds, int port,
+ bool enabled,
+ struct netlink_ext_ack *extack)
+{
+ struct lan9645x *lan9645x = ds->priv;
+ struct lan9645x_port *p;
+
+ /* DSA core does not call this for the CPU port */
+ p = lan9645x_to_port(lan9645x, port);
+ mutex_lock(&lan9645x->fwd_domain_lock);
+ p->vlan_aware = enabled;
+ lan9645x_vlan_port_apply(p);
+ mutex_unlock(&lan9645x->fwd_domain_lock);
+
+ return 0;
+}
+
+static int lan9645x_port_vlan_add(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_vlan *vlan,
+ struct netlink_ext_ack *extack)
+{
+ struct lan9645x *lan9645x = ds->priv;
+ struct lan9645x_port *p;
+ bool pvid, untagged;
+ int err;
+
+ p = lan9645x_to_port(lan9645x, port);
+ pvid = !!(vlan->flags & BRIDGE_VLAN_INFO_PVID);
+ untagged = !!(vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED);
+
+ mutex_lock(&lan9645x->fwd_domain_lock);
+ err = lan9645x_vlan_port_add_vlan(p, vlan->vid, pvid, untagged, extack);
+ mutex_unlock(&lan9645x->fwd_domain_lock);
+
+ return err;
+}
+
+static int lan9645x_port_vlan_del(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_vlan *vlan)
+{
+ struct lan9645x *lan9645x = ds->priv;
+ struct lan9645x_port *p;
+ int err;
+
+ p = lan9645x_to_port(lan9645x, port);
+
+ mutex_lock(&lan9645x->fwd_domain_lock);
+ err = lan9645x_vlan_port_del_vlan(p, vlan->vid);
+ mutex_unlock(&lan9645x->fwd_domain_lock);
+
+ return err;
+}
+
static const struct dsa_switch_ops lan9645x_switch_ops = {
.get_tag_protocol = lan9645x_get_tag_protocol,
@@ -309,6 +373,11 @@ static const struct dsa_switch_ops lan9645x_switch_ops = {
/* MTU */
.port_change_mtu = lan9645x_change_mtu,
.port_max_mtu = lan9645x_get_max_mtu,
+
+ /* VLAN integration */
+ .port_vlan_filtering = lan9645x_port_vlan_filtering,
+ .port_vlan_add = lan9645x_port_vlan_add,
+ .port_vlan_del = lan9645x_port_vlan_del,
};
static int lan9645x_request_target_regmaps(struct lan9645x *lan9645x)
diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_main.h b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.h
index 3a4abedafa2c..c61efd115aaa 100644
--- a/drivers/net/dsa/microchip/lan9645x/lan9645x_main.h
+++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.h
@@ -7,6 +7,7 @@
#include <linux/dsa/lan9645x.h>
#include <linux/if_bridge.h>
+#include <linux/if_vlan.h>
#include <linux/regmap.h>
#include <net/dsa.h>
@@ -171,6 +172,11 @@ enum lan9645x_vlan_port_tag {
LAN9645X_TAG_ALL = 3,
};
+struct lan9645x_vlan {
+ u32 portmask: 10, /* ports 0-8 + CPU port module */
+ untagged: 9; /* ports 0-8 */
+};
+
struct lan9645x {
struct device *dev;
struct dsa_switch *ds;
@@ -182,7 +188,15 @@ struct lan9645x {
u8 num_phys_ports;
struct lan9645x_port **ports;
+ /* Forwarding Database */
+ u16 bridge_mask; /* Mask for bridged ports */
+ /* lock forwarding configuration and vlan table */
+ struct mutex fwd_domain_lock;
+
int num_port_dis;
+
+ /* VLAN entries */
+ struct lan9645x_vlan vlans[VLAN_N_VID];
};
struct lan9645x_port {
@@ -190,6 +204,9 @@ struct lan9645x_port {
u8 chip_port;
+ bool vlan_aware;
+ u16 pvid;
+
bool rx_internal_delay;
bool tx_internal_delay;
};
@@ -238,6 +255,11 @@ static inline struct lan9645x_port *lan9645x_to_port(struct lan9645x *lan9645x,
return lan9645x->ports[port];
}
+static inline bool lan9645x_port_is_bridged(struct lan9645x_port *p)
+{
+ return p->lan9645x->bridge_mask & BIT(p->chip_port);
+}
+
static inline struct regmap *lan_tgt2rmap(struct lan9645x *lan9645x,
enum lan9645x_target t, int tinst)
{
@@ -345,4 +367,14 @@ void lan9645x_phylink_get_caps(struct lan9645x *lan9645x, int port,
struct phylink_config *c);
void lan9645x_phylink_port_down(struct lan9645x *lan9645x, int port);
+/* VLAN lan9645x_vlan.c */
+int lan9645x_vlan_init(struct lan9645x *lan9645x);
+u16 lan9645x_vlan_unaware_pvid(bool is_bridged);
+void lan9645x_vlan_port_apply(struct lan9645x_port *p);
+int lan9645x_vlan_port_add_vlan(struct lan9645x_port *p, u16 vid, bool pvid,
+ bool untagged,
+ struct netlink_ext_ack *extack);
+int lan9645x_vlan_port_del_vlan(struct lan9645x_port *p, u16 vid);
+void lan9645x_vlan_set_hostmode(struct lan9645x_port *p);
+
#endif /* __LAN9645X_MAIN_H__ */
diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_port.c b/drivers/net/dsa/microchip/lan9645x/lan9645x_port.c
index 4d2d922bacba..2571380f2c02 100644
--- a/drivers/net/dsa/microchip/lan9645x/lan9645x_port.c
+++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_port.c
@@ -162,5 +162,11 @@ int lan9645x_port_setup(struct dsa_switch *ds, int port)
ANA_PORT_CFG_PORTID_VAL,
lan9645x, ANA_PORT_CFG(p->chip_port));
+ if (p->chip_port != lan9645x->npi) {
+ mutex_lock(&lan9645x->fwd_domain_lock);
+ lan9645x_vlan_set_hostmode(p);
+ mutex_unlock(&lan9645x->fwd_domain_lock);
+ }
+
return 0;
}
diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_vlan.c b/drivers/net/dsa/microchip/lan9645x/lan9645x_vlan.c
new file mode 100644
index 000000000000..6d28a7cf1442
--- /dev/null
+++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_vlan.c
@@ -0,0 +1,402 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright (C) 2026 Microchip Technology Inc.
+ */
+
+#include "lan9645x_main.h"
+
+#define VLANACCESS_CMD_IDLE 0
+#define VLANACCESS_CMD_READ 1
+#define VLANACCESS_CMD_WRITE 2
+#define VLANACCESS_CMD_INIT 3
+
+struct lan9645x_vlan_port_info {
+ int untagged;
+ int tagged;
+ u16 untagged_vid;
+};
+
+/* Calculate VLAN state of a port, across all VLANS. */
+static void lan9645x_vlan_port_get_info(struct lan9645x *lan9645x, int port,
+ struct lan9645x_vlan_port_info *info)
+{
+ u16 vid;
+
+ info->untagged = 0;
+ info->tagged = 0;
+ info->untagged_vid = 0;
+
+ for (vid = 1; vid <= VLAN_MAX; vid++) {
+ struct lan9645x_vlan *v = &lan9645x->vlans[vid];
+
+ if (!(v->portmask & BIT(port)))
+ continue;
+
+ if (v->untagged & BIT(port)) {
+ info->untagged++;
+ info->untagged_vid = vid;
+ } else {
+ info->tagged++;
+ }
+
+ /* VLAN composition is invalid, so break early. */
+ if (info->untagged > 1 && info->tagged)
+ break;
+ }
+}
+
+static int lan9645x_vlan_wait_for_completion(struct lan9645x *lan9645x)
+{
+ u32 val;
+
+ return lan9645x_rd_poll_timeout(lan9645x, ANA_VLANACCESS, val,
+ ANA_VLANACCESS_VLAN_TBL_CMD_GET(val) ==
+ VLANACCESS_CMD_IDLE);
+}
+
+static int lan9645x_vlan_hw_wr(struct lan9645x *lan9645x, u16 vid)
+{
+ struct lan9645x_vlan *v = &lan9645x->vlans[vid];
+ bool cpu_dis = !(v->portmask & BIT(lan9645x->num_phys_ports));
+ u32 val;
+ int err;
+
+ val = ANA_VLANTIDX_VLAN_PGID_CPU_DIS_SET(cpu_dis) |
+ ANA_VLANTIDX_V_INDEX_SET(vid);
+
+ lan_wr(val, lan9645x, ANA_VLANTIDX);
+ lan_wr(ANA_VLAN_PORT_MASK_VLAN_PORT_MASK_SET(v->portmask),
+ lan9645x, ANA_VLAN_PORT_MASK);
+ lan_wr(ANA_VLANACCESS_VLAN_TBL_CMD_SET(VLANACCESS_CMD_WRITE),
+ lan9645x, ANA_VLANACCESS);
+
+ /* The VLAN access engine completes in a fixed ~1us vs the polling
+ * timeout of 100_000 us. A timeout here therefore likely means the
+ * register bus itself is dead, not that the VLAN op failed. There is no
+ * meaningful recovery at runtime, so this function logs via dev_err()
+ * and runtime callers discard the return value. Only
+ * lan9645x_vlan_init() treats this as fatal so that probe fails early
+ * on a broken bus.
+ */
+ err = lan9645x_vlan_wait_for_completion(lan9645x);
+ if (err)
+ dev_err(lan9645x->dev, "VLAN set mask failed\n");
+
+ return err;
+}
+
+u16 lan9645x_vlan_unaware_pvid(bool is_bridged)
+{
+ return is_bridged ? UNAWARE_PVID : HOST_PVID;
+}
+
+static u16 lan9645x_vlan_port_get_pvid(struct lan9645x_port *port)
+{
+ bool is_bridged = lan9645x_port_is_bridged(port);
+
+ if (is_bridged && port->vlan_aware)
+ return port->pvid;
+ else
+ return lan9645x_vlan_unaware_pvid(is_bridged);
+}
+
+/* Dynamically choose the egress tagging mode based on the port vlan state:
+ *
+ * Standalone:
+ * TAG_NO_PVID_NO_UNAWARE with PORT_VID=HOST_PVID. This avoids leaking the
+ * internal HOST_PVID tag on ingress mirrored frames while leaving normal
+ * egress frames untagged.
+ *
+ * Bridged, VLAN-aware:
+ * - N untagged, 0 tagged: TAG_DISABLED
+ * - 1 untagged, N tagged: TAG_NO_PVID_NO_UNAWARE
+ * - 0 untagged, N tagged: TAG_ALL
+ *
+ * Bridged, VLAN-unaware:
+ * TAG_DISABLED
+ */
+static void
+lan9645x_vlan_port_apply_egress(struct lan9645x_port *p,
+ struct lan9645x_vlan_port_info *info)
+{
+ struct lan9645x *lan9645x = p->lan9645x;
+ enum lan9645x_vlan_port_tag tag_cfg;
+ u16 port_vid = UNAWARE_PVID;
+
+ if (!lan9645x_port_is_bridged(p)) {
+ tag_cfg = LAN9645X_TAG_NO_PVID_NO_UNAWARE;
+ port_vid = HOST_PVID;
+ } else if (p->vlan_aware) {
+ struct lan9645x_vlan_port_info _info;
+
+ if (!info) {
+ lan9645x_vlan_port_get_info(lan9645x, p->chip_port,
+ &_info);
+ info = &_info;
+ }
+
+ if (info->untagged == 1 && info->tagged) {
+ tag_cfg = LAN9645X_TAG_NO_PVID_NO_UNAWARE;
+ port_vid = info->untagged_vid;
+ } else if (info->untagged) {
+ tag_cfg = LAN9645X_TAG_DISABLED;
+ } else {
+ tag_cfg = LAN9645X_TAG_ALL;
+ }
+ } else {
+ tag_cfg = LAN9645X_TAG_DISABLED;
+ }
+
+ /* TAG_TPID_CFG encoding:
+ *
+ * 0: Use 0x8100.
+ * 1: Use 0x88A8.
+ * 2: Use custom value from PORT_VLAN_CFG.PORT_TPID.
+ * 3: Use PORT_VLAN_CFG.PORT_TPID, unless ingress tag was a C-tag
+ * (EtherType = 0x8100)
+ *
+ * Use 3 and PORT_VLAN_CFG.PORT_TPID=0x88a8 to ensure stags are not
+ * rewritten to ctags on egress.
+ */
+ lan_rmw(REW_TAG_CFG_TAG_TPID_CFG_SET(3) |
+ REW_TAG_CFG_TAG_CFG_SET(tag_cfg),
+ REW_TAG_CFG_TAG_TPID_CFG |
+ REW_TAG_CFG_TAG_CFG,
+ lan9645x, REW_TAG_CFG(p->chip_port));
+
+ lan_rmw(REW_PORT_VLAN_CFG_PORT_TPID_SET(ETH_P_8021AD) |
+ REW_PORT_VLAN_CFG_PORT_VID_SET(port_vid),
+ REW_PORT_VLAN_CFG_PORT_TPID |
+ REW_PORT_VLAN_CFG_PORT_VID,
+ lan9645x, REW_PORT_VLAN_CFG(p->chip_port));
+}
+
+static void lan9645x_vlan_port_apply_ingress(struct lan9645x_port *p)
+{
+ struct lan9645x *lan9645x = p->lan9645x;
+ u16 pvid;
+ u32 val;
+
+ pvid = lan9645x_vlan_port_get_pvid(p);
+
+ /* Default vlan to classify for untagged frames (may be zero), and set
+ * their tag type to C-tag.
+ */
+ val = ANA_VLAN_CFG_VLAN_VID_SET(pvid) |
+ ANA_VLAN_CFG_VLAN_TAG_TYPE_SET(0);
+ if (p->vlan_aware)
+ val |= ANA_VLAN_CFG_VLAN_AWARE_ENA_SET(1) |
+ ANA_VLAN_CFG_VLAN_POP_CNT_SET(1);
+
+ lan_rmw(val,
+ ANA_VLAN_CFG_VLAN_VID |
+ ANA_VLAN_CFG_VLAN_AWARE_ENA |
+ ANA_VLAN_CFG_VLAN_POP_CNT |
+ ANA_VLAN_CFG_VLAN_TAG_TYPE,
+ lan9645x, ANA_VLAN_CFG(p->chip_port));
+
+ val = 0;
+ if (p->vlan_aware && !pvid)
+ /* If port is vlan-aware and tagged, drop untagged and priority
+ * tagged frames.
+ */
+ val = ANA_DROP_CFG_DROP_UNTAGGED_ENA_SET(1) |
+ ANA_DROP_CFG_DROP_PRIO_S_TAGGED_ENA_SET(1) |
+ ANA_DROP_CFG_DROP_PRIO_C_TAGGED_ENA_SET(1);
+
+ lan_rmw(val,
+ ANA_DROP_CFG_DROP_UNTAGGED_ENA |
+ ANA_DROP_CFG_DROP_PRIO_S_TAGGED_ENA |
+ ANA_DROP_CFG_DROP_PRIO_C_TAGGED_ENA,
+ lan9645x, ANA_DROP_CFG(p->chip_port));
+}
+
+void lan9645x_vlan_port_apply(struct lan9645x_port *p)
+{
+ lockdep_assert_held(&p->lan9645x->fwd_domain_lock);
+
+ lan9645x_vlan_port_apply_ingress(p);
+ lan9645x_vlan_port_apply_egress(p, NULL);
+}
+
+static struct lan9645x_vlan *lan9645x_vlan_port_modify(struct lan9645x_port *p,
+ u16 vid, bool pvid,
+ bool untagged)
+{
+ struct lan9645x_vlan *v = &p->lan9645x->vlans[vid];
+
+ if (untagged)
+ v->untagged |= BIT(p->chip_port);
+ else
+ v->untagged &= ~BIT(p->chip_port);
+
+ if (pvid)
+ p->pvid = vid;
+ else if (p->pvid == vid)
+ p->pvid = 0;
+
+ return v;
+}
+
+static int lan9645x_vlan_cpu_add(struct lan9645x_port *p, u16 vid)
+{
+ struct lan9645x_vlan *v = &p->lan9645x->vlans[vid];
+
+ v->portmask |= BIT(p->lan9645x->num_phys_ports) | BIT(p->chip_port);
+ lan9645x_vlan_hw_wr(p->lan9645x, vid);
+
+ return 0;
+}
+
+int lan9645x_vlan_port_add_vlan(struct lan9645x_port *p, u16 vid, bool pvid,
+ bool untagged, struct netlink_ext_ack *extack)
+{
+ struct lan9645x *lan9645x = p->lan9645x;
+ struct lan9645x_vlan_port_info info;
+ struct lan9645x_vlan old_vlan;
+ struct lan9645x_vlan *v;
+ u16 old_pvid;
+
+ lockdep_assert_held(&lan9645x->fwd_domain_lock);
+
+ /* Kernel VLAN core adds vid 0, which collides with our UNAWARE_PVID.
+ * Hardware needs no entry for it: basic VLAN classification replaces a
+ * VID of 0 with VLAN_CFG.VLAN_VID, keeping the frame's PCP and DEI, so
+ * a priority tagged frame is classified to the port pvid. Ports with no
+ * pvid instead drop it, see lan9645x_vlan_port_apply_ingress().
+ */
+ if (!vid)
+ return 0;
+
+ if (vid > VLAN_MAX) {
+ NL_SET_ERR_MSG_MOD(extack, "VLAN 4095 reserved");
+ return -EINVAL;
+ }
+
+ if (p->chip_port == lan9645x->npi)
+ return lan9645x_vlan_cpu_add(p, vid);
+
+ old_vlan = lan9645x->vlans[vid];
+ old_pvid = p->pvid;
+
+ v = lan9645x_vlan_port_modify(p, vid, pvid, untagged);
+ v->portmask |= BIT(p->chip_port);
+
+ lan9645x_vlan_port_get_info(lan9645x, p->chip_port, &info);
+
+ if (info.untagged > 1 && info.tagged) {
+ *v = old_vlan;
+ p->pvid = old_pvid;
+ if (untagged)
+ NL_SET_ERR_MSG_MOD(extack,
+ "Port with egress-tagged VLANs cannot have more than one egress-untagged VLAN");
+ else
+ NL_SET_ERR_MSG_MOD(extack,
+ "Port with more than one egress-untagged VLAN cannot have egress-tagged VLANs");
+ return -EBUSY;
+ }
+
+ lan9645x_vlan_hw_wr(lan9645x, vid);
+ lan9645x_vlan_port_apply_ingress(p);
+ lan9645x_vlan_port_apply_egress(p, &info);
+
+ return 0;
+}
+
+static int lan9645x_vlan_cpu_del(struct lan9645x_port *p, u16 vid)
+{
+ struct lan9645x_vlan *v = &p->lan9645x->vlans[vid];
+
+ v->portmask &= ~(BIT(p->lan9645x->num_phys_ports) |
+ BIT(p->chip_port));
+ lan9645x_vlan_hw_wr(p->lan9645x, vid);
+
+ return 0;
+}
+
+int lan9645x_vlan_port_del_vlan(struct lan9645x_port *p, u16 vid)
+{
+ struct lan9645x *lan9645x = p->lan9645x;
+ struct lan9645x_vlan *v;
+
+ lockdep_assert_held(&lan9645x->fwd_domain_lock);
+
+ if (!vid)
+ return 0;
+
+ if (vid > VLAN_MAX)
+ return -EINVAL;
+
+ if (p->chip_port == lan9645x->npi)
+ return lan9645x_vlan_cpu_del(p, vid);
+
+ v = lan9645x_vlan_port_modify(p, vid, false, false);
+ v->portmask &= ~BIT(p->chip_port);
+ lan9645x_vlan_hw_wr(lan9645x, vid);
+ lan9645x_vlan_port_apply(p);
+
+ return 0;
+}
+
+void lan9645x_vlan_set_hostmode(struct lan9645x_port *p)
+{
+ lockdep_assert_held(&p->lan9645x->fwd_domain_lock);
+
+ p->vlan_aware = false;
+ p->lan9645x->vlans[HOST_PVID].portmask |= BIT(p->chip_port);
+ lan9645x_vlan_hw_wr(p->lan9645x, HOST_PVID);
+ lan9645x_vlan_port_apply(p);
+}
+
+int lan9645x_vlan_init(struct lan9645x *lan9645x)
+{
+ u32 all_phys_ports, all_ports;
+ struct dsa_port *dp;
+ u16 vid;
+ int err;
+
+ all_phys_ports = GENMASK(lan9645x->num_phys_ports - 1, 0);
+ all_ports = all_phys_ports | BIT(lan9645x->num_phys_ports);
+
+ /* Clear VLAN table, by default all ports are members of all VLANS */
+ lan_wr(ANA_VLANACCESS_VLAN_TBL_CMD_SET(VLANACCESS_CMD_INIT),
+ lan9645x, ANA_VLANACCESS);
+
+ err = lan9645x_vlan_wait_for_completion(lan9645x);
+ if (err) {
+ dev_err(lan9645x->dev, "VLAN clear table failed\n");
+ return err;
+ }
+
+ for (vid = 1; vid < VLAN_N_VID; vid++) {
+ err = lan9645x_vlan_hw_wr(lan9645x, vid);
+ if (err)
+ return err;
+ }
+
+ /* Set all the ports + cpu to be part of HOST_PVID and UNAWARE_PVID */
+ lan9645x->vlans[HOST_PVID].portmask = all_ports;
+ err = lan9645x_vlan_hw_wr(lan9645x, HOST_PVID);
+ if (err)
+ return err;
+
+ lan9645x->vlans[UNAWARE_PVID].portmask = all_ports;
+ err = lan9645x_vlan_hw_wr(lan9645x, UNAWARE_PVID);
+ if (err)
+ return err;
+
+ /* Configure the CPU port module to be vlan aware */
+ lan_wr(ANA_VLAN_CFG_VLAN_VID_SET(UNAWARE_PVID) |
+ ANA_VLAN_CFG_VLAN_AWARE_ENA_SET(1) |
+ ANA_VLAN_CFG_VLAN_POP_CNT_SET(1),
+ lan9645x, ANA_VLAN_CFG(lan9645x->num_phys_ports));
+
+ /* Set vlan ingress filter mask to all ports */
+ lan_wr(all_ports, lan9645x, ANA_VLANMASK);
+
+ dsa_switch_for_each_user_port(dp, lan9645x->ds) {
+ lan_wr(0, lan9645x, REW_PORT_VLAN_CFG(dp->index));
+ lan_wr(0, lan9645x, REW_TAG_CFG(dp->index));
+ }
+
+ return 0;
+}
--
2.52.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH net-next v13 6/9] net: dsa: lan9645x: add mac table integration
2026-09-29 7:48 [PATCH net-next v13 0/9] net: dsa: add DSA support for the LAN9645x switch chip family Jens Emil Schulz Østergaard
` (4 preceding siblings ...)
2026-09-29 7:48 ` [PATCH net-next v13 5/9] net: dsa: lan9645x: add vlan support Jens Emil Schulz Østergaard
@ 2026-09-29 7:48 ` Jens Emil Schulz Østergaard
2026-10-02 21:14 ` netdev-bot+sashiko
2026-09-29 7:48 ` [PATCH net-next v13 7/9] net: dsa: lan9645x: add mdb management Jens Emil Schulz Østergaard
` (2 subsequent siblings)
8 siblings, 1 reply; 18+ messages in thread
From: Jens Emil Schulz Østergaard @ 2026-09-29 7:48 UTC (permalink / raw)
To: UNGLinuxDriver, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Woojung Huh,
Russell King, Steen Hegelund, Daniel Machon, Geert Uytterhoeven,
Magnus Damm
Cc: linux-kernel, netdev, devicetree, linux-renesas-soc,
Jens Emil Schulz Østergaard
Add MAC table support, and dsa fdb callback integration. The mactable is
keyed on (vid,mac) and each row has 4 buckets. A mac table entry
typically points to a PGID index, the first 9 of which represent a front
port.
Mac table entries for L2 multicast will use a PGID containing a group
port mask. For IP multicast entries in the mac table a trick is used,
where the group port mask is packed into the MAC data, exploiting the
fact that the top bits are fixed, and that the number of switch ports is
small enough to fit in the redundant bits.
Therefore, we can avoid using sparse PGID resources for IP multicast
entries in the mac table.
Reviewed-by: Steen Hegelund <Steen.Hegelund@microchip.com>
Signed-off-by: Jens Emil Schulz Østergaard <jensemil.schulzostergaard@microchip.com>
---
Changes in v12:
- Log a failure from lan9645x_mact_flush() in port_fast_age(), which
returns void and cannot propagate it.
- Fix a comment to name PGID_CPU rather than the CPU port module.
- Move this patch before bridge support, so .port_fast_age exists when
port_stp_state_set() is introduced.
- Give lan9645x_mact_learn() a cpu_copy argument, and drop
lan9645x_mac_ports_use_cpu() and the wrapper that used it to derive
the flag from the encoded mac. The helper could only return false,
since its one caller passes ENTRYTYPE_LOCKED, and the mdb path needs
the flag passed in rather than derived.
- Bound the CMD_GET_NEXT walk in lan9645x_mact_dsa_dump() at the mac table
size plus one, derived from ANA_MACTINDX. Log and return -EIO on overrun.
- lan9645x_mact_dsa_dump(): leave automatic ageing enabled during the
GET_NEXT walk, as it does not disturb the walk, and toggling ageing
resets the timer.
- lan9645x_teardown(): deinit the mac table before fwd_domain_lock.
- Spell the mac argument as mac[ETH_ALEN] and the vid as unsigned int.
Changes in v8:
- Pass lan9645x into lan9645x_mac_ports_use_cpu() and reference the CPU
port module as lan9645x->num_phys_ports instead of CPU_PORT.
Changes in v5:
- lan9645x_mac_init returns error on table init timeout
- add comment about skipping LOCKED entries on fdb dump
Changes in v4:
- remove mac_entries list and just do direct IO to mac table from
fdb_add/fdb_del
Changes in v3:
- avoid mac add/del dealloc when mac table writes fail
- add mact_lock to change ageing time
- dealloc all mac_entries on deinit
- dsa_dump returns mac table timeout error
Changes in v2:
- use a single lock for hw and sw
- remove unused row struct field and define
- remove list element INIT_LIST_HEAD
- consistent use of err vs ret
- remove mutex_lock in init
- use empty initializer { 0 } -> {}
- do not move fwd_domain_lock init to this unit
- add newline to dev_* log statements
---
drivers/net/dsa/microchip/lan9645x/Makefile | 1 +
drivers/net/dsa/microchip/lan9645x/lan9645x_mac.c | 268 +++++++++++++++++++++
drivers/net/dsa/microchip/lan9645x/lan9645x_main.c | 85 +++++++
drivers/net/dsa/microchip/lan9645x/lan9645x_main.h | 27 +++
4 files changed, 381 insertions(+)
diff --git a/drivers/net/dsa/microchip/lan9645x/Makefile b/drivers/net/dsa/microchip/lan9645x/Makefile
index e049114b3563..70815edca5b9 100644
--- a/drivers/net/dsa/microchip/lan9645x/Makefile
+++ b/drivers/net/dsa/microchip/lan9645x/Makefile
@@ -2,6 +2,7 @@
obj-$(CONFIG_NET_DSA_MICROCHIP_LAN9645X) += mchp-lan9645x.o
mchp-lan9645x-objs := \
+ lan9645x_mac.o \
lan9645x_main.o \
lan9645x_npi.o \
lan9645x_phylink.o \
diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_mac.c b/drivers/net/dsa/microchip/lan9645x/lan9645x_mac.c
new file mode 100644
index 000000000000..50ce1378b9da
--- /dev/null
+++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_mac.c
@@ -0,0 +1,268 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright (C) 2026 Microchip Technology Inc.
+ */
+
+#include "lan9645x_main.h"
+
+#define CMD_IDLE 0
+#define CMD_LEARN 1
+#define CMD_FORGET 2
+#define CMD_AGE 3
+#define CMD_GET_NEXT 4
+#define CMD_INIT 5
+#define CMD_READ 6
+#define CMD_WRITE 7
+#define CMD_SYNC_GET_NEXT 8
+
+/* The chip has 2048 * 4 = 8192 total entries in the mac table */
+#define MACT_ROWS (FIELD_MAX(ANA_MACTINDX_M_INDEX) + 1)
+#define MACT_BUCKETS (FIELD_MAX(ANA_MACTINDX_BUCKET) + 1)
+#define MACT_ENTRIES (MACT_ROWS * MACT_BUCKETS)
+#define MACT_GET_NEXT_MAX (MACT_ENTRIES + 1)
+
+static int lan9645x_mac_wait_for_completion(struct lan9645x *lan9645x,
+ u32 *maca)
+{
+ u32 val = 0;
+ int err;
+
+ lockdep_assert_held(&lan9645x->mact_lock);
+
+ err = lan9645x_rd_poll_timeout(lan9645x, ANA_MACACCESS, val,
+ ANA_MACACCESS_MAC_TABLE_CMD_GET(val) ==
+ CMD_IDLE);
+ if (err)
+ return err;
+
+ if (maca)
+ *maca = val;
+
+ return 0;
+}
+
+static void lan9645x_mac_select(struct lan9645x *lan9645x,
+ const unsigned char mac[ETH_ALEN],
+ unsigned int vid)
+{
+ u64 maddr = ether_addr_to_u64(mac);
+
+ lockdep_assert_held(&lan9645x->mact_lock);
+
+ lan_wr(ANA_MACHDATA_VID_SET(vid) |
+ ANA_MACHDATA_MACHDATA_SET(upper_32_bits(maddr)),
+ lan9645x,
+ ANA_MACHDATA);
+
+ lan_wr(lower_32_bits(maddr),
+ lan9645x,
+ ANA_MACLDATA);
+}
+
+static int __lan9645x_mact_forget(struct lan9645x *lan9645x,
+ const unsigned char mac[ETH_ALEN],
+ unsigned int vid,
+ enum macaccess_entry_type type)
+{
+ lockdep_assert_held(&lan9645x->mact_lock);
+
+ lan9645x_mac_select(lan9645x, mac, vid);
+
+ lan_wr(ANA_MACACCESS_ENTRYTYPE_SET(type) |
+ ANA_MACACCESS_MAC_TABLE_CMD_SET(CMD_FORGET),
+ lan9645x,
+ ANA_MACACCESS);
+
+ return lan9645x_mac_wait_for_completion(lan9645x, NULL);
+}
+
+int lan9645x_mact_forget(struct lan9645x *lan9645x,
+ const unsigned char mac[ETH_ALEN], unsigned int vid,
+ enum macaccess_entry_type type)
+{
+ int err;
+
+ mutex_lock(&lan9645x->mact_lock);
+ err = __lan9645x_mact_forget(lan9645x, mac, vid, type);
+ mutex_unlock(&lan9645x->mact_lock);
+
+ return err;
+}
+
+static int __lan9645x_mact_learn(struct lan9645x *lan9645x, int port,
+ const unsigned char mac[ETH_ALEN],
+ unsigned int vid,
+ enum macaccess_entry_type type,
+ bool cpu_copy)
+{
+ lockdep_assert_held(&lan9645x->mact_lock);
+
+ lan9645x_mac_select(lan9645x, mac, vid);
+
+ lan_wr(ANA_MACACCESS_VALID_SET(1) |
+ ANA_MACACCESS_DEST_IDX_SET(port) |
+ ANA_MACACCESS_MAC_CPU_COPY_SET(cpu_copy) |
+ ANA_MACACCESS_ENTRYTYPE_SET(type) |
+ ANA_MACACCESS_MAC_TABLE_CMD_SET(CMD_LEARN),
+ lan9645x, ANA_MACACCESS);
+
+ return lan9645x_mac_wait_for_completion(lan9645x, NULL);
+}
+
+int lan9645x_mact_learn(struct lan9645x *lan9645x, int port,
+ const unsigned char mac[ETH_ALEN], unsigned int vid,
+ enum macaccess_entry_type type, bool cpu_copy)
+{
+ int err;
+
+ mutex_lock(&lan9645x->mact_lock);
+ err = __lan9645x_mact_learn(lan9645x, port, mac, vid, type, cpu_copy);
+ mutex_unlock(&lan9645x->mact_lock);
+
+ return err;
+}
+
+int lan9645x_mact_flush(struct lan9645x *lan9645x, int port)
+{
+ int err;
+
+ mutex_lock(&lan9645x->mact_lock);
+ /* MAC table entries with dst index matching port are aged on scan. */
+ lan_wr(ANA_ANAGEFIL_PID_EN_SET(1) |
+ ANA_ANAGEFIL_PID_VAL_SET(port),
+ lan9645x, ANA_ANAGEFIL);
+
+ /* Flushing requires two scans. First sets AGE_FLAG=1, second removes
+ * entries with AGE_FLAG=1.
+ */
+ lan_wr(ANA_MACACCESS_MAC_TABLE_CMD_SET(CMD_AGE),
+ lan9645x,
+ ANA_MACACCESS);
+
+ err = lan9645x_mac_wait_for_completion(lan9645x, NULL);
+ if (err)
+ goto mact_unlock;
+
+ lan_wr(ANA_MACACCESS_MAC_TABLE_CMD_SET(CMD_AGE),
+ lan9645x,
+ ANA_MACACCESS);
+
+ err = lan9645x_mac_wait_for_completion(lan9645x, NULL);
+
+mact_unlock:
+ lan_wr(0, lan9645x, ANA_ANAGEFIL);
+ mutex_unlock(&lan9645x->mact_lock);
+ return err;
+}
+
+int lan9645x_mac_init(struct lan9645x *lan9645x)
+{
+ u32 val;
+ int err;
+
+ /* Clear the MAC table */
+ lan_wr(ANA_MACACCESS_MAC_TABLE_CMD_SET(CMD_INIT), lan9645x,
+ ANA_MACACCESS);
+
+ err = lan9645x_rd_poll_timeout(lan9645x, ANA_MACACCESS, val,
+ ANA_MACACCESS_MAC_TABLE_CMD_GET(val) ==
+ CMD_IDLE);
+ if (err) {
+ dev_err(lan9645x->dev, "MAC table clear timeout\n");
+ return err;
+ }
+
+ mutex_init(&lan9645x->mact_lock);
+ return 0;
+}
+
+void lan9645x_mac_deinit(struct lan9645x *lan9645x)
+{
+ mutex_destroy(&lan9645x->mact_lock);
+}
+
+int lan9645x_mact_dsa_dump(struct lan9645x *lan9645x, int port,
+ dsa_fdb_dump_cb_t *cb, void *data)
+{
+ u8 mac[ETH_ALEN] __aligned(2);
+ u32 mach, macl, maca;
+ int err = 0;
+ u64 addr;
+ int iter;
+ u16 vid;
+ u8 type;
+
+ mutex_lock(&lan9645x->mact_lock);
+
+ /* The aging filter works both for aging scans and GET_NEXT table scans.
+ * With it, the HW table iteration only stops at entries matching our
+ * filter. Since DSA calls us for each port on a table dump, this helps
+ * avoid unnecessary work.
+ *
+ * Setup agefilter on our port
+ */
+ lan_wr(ANA_ANAGEFIL_PID_EN_SET(1) |
+ ANA_ANAGEFIL_PID_VAL_SET(port),
+ lan9645x, ANA_ANAGEFIL);
+
+ lan_wr(0, lan9645x, ANA_MACHDATA);
+ lan_wr(0, lan9645x, ANA_MACLDATA);
+
+ type = ENTRYTYPE_NORMAL;
+
+ for (iter = 0; iter < MACT_GET_NEXT_MAX; iter++) {
+ /* CMD_GET_NEXT returns the smallest entry ordered above the one
+ * left in the registers.
+ * We rely on mach, macl and type being set correctly in
+ * the registers from previous round, vis a vis the GET_NEXT
+ * semantics, so locking entire loop is important.
+ */
+ lan_wr(ANA_MACACCESS_MAC_TABLE_CMD_SET(CMD_GET_NEXT) |
+ ANA_MACACCESS_ENTRYTYPE_SET(type),
+ lan9645x, ANA_MACACCESS);
+
+ err = lan9645x_mac_wait_for_completion(lan9645x, &maca);
+ if (err)
+ break;
+
+ if (ANA_MACACCESS_VALID_GET(maca) == 0)
+ break;
+
+ type = ANA_MACACCESS_ENTRYTYPE_GET(maca);
+ mach = lan_rd(lan9645x, ANA_MACHDATA);
+ macl = lan_rd(lan9645x, ANA_MACLDATA);
+
+ /* Only dynamic entries are surfaced through the user port dump.
+ * ENTRYTYPE_LOCKED entries are already reported by the bridge
+ * master's ndo_fdb_dump as NTF_MASTER, so we avoid duplicating
+ * them as NTF_SELF.
+ * Entries toward the host (NTF_SELF) have DEST_IDX == PGID_CPU
+ * and are filtered out by the DEST_IDX check.
+ */
+ if (ANA_MACACCESS_DEST_IDX_GET(maca) == port &&
+ type == ENTRYTYPE_NORMAL) {
+ addr = (u64)ANA_MACHDATA_MACHDATA_GET(mach) << 32 |
+ macl;
+ u64_to_ether_addr(addr, mac);
+ vid = ANA_MACHDATA_VID_GET(mach);
+ if (vid > VLAN_MAX)
+ vid = 0;
+
+ err = cb(mac, vid, false, data);
+ if (err)
+ break;
+ }
+ }
+
+ if (iter == MACT_GET_NEXT_MAX) {
+ dev_err(lan9645x->dev,
+ "MAC table walk on port %d did not terminate\n", port);
+ err = -EIO;
+ }
+
+ /* Remove aging filters */
+ lan_wr(0, lan9645x, ANA_ANAGEFIL);
+
+ mutex_unlock(&lan9645x->mact_lock);
+
+ return err;
+}
diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
index 6818b91f4bed..5cb4f5fe1374 100644
--- a/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
+++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
@@ -74,6 +74,7 @@ static void lan9645x_teardown(struct dsa_switch *ds)
{
struct lan9645x *lan9645x = ds->priv;
+ lan9645x_mac_deinit(lan9645x);
mutex_destroy(&lan9645x->fwd_domain_lock);
lan9645x_npi_port_deinit(lan9645x, lan9645x->npi);
}
@@ -160,6 +161,9 @@ static int lan9645x_setup(struct dsa_switch *ds)
mutex_init(&lan9645x->fwd_domain_lock);
err = lan9645x_vlan_init(lan9645x);
+ if (err)
+ goto err_mutex;
+ err = lan9645x_mac_init(lan9645x);
if (err)
goto err_mutex;
@@ -360,6 +364,81 @@ static int lan9645x_port_vlan_del(struct dsa_switch *ds, int port,
return err;
}
+static void lan9645x_port_fast_age(struct dsa_switch *ds, int port)
+{
+ int err;
+
+ err = lan9645x_mact_flush(ds->priv, port);
+ if (err)
+ dev_err(ds->dev, "Flushing MAC table on port %d returned %pe\n",
+ port, ERR_PTR(err));
+}
+
+static int lan9645x_fdb_dump(struct dsa_switch *ds, int port,
+ dsa_fdb_dump_cb_t *cb, void *data)
+{
+ return lan9645x_mact_dsa_dump(ds->priv, port, cb, data);
+}
+
+static struct net_device *lan9645x_db2bridge(struct dsa_db db)
+{
+ switch (db.type) {
+ case DSA_DB_PORT:
+ case DSA_DB_LAG:
+ return NULL;
+ case DSA_DB_BRIDGE:
+ return db.bridge.dev;
+ default:
+ return ERR_PTR(-EOPNOTSUPP);
+ }
+}
+
+static int lan9645x_fdb_add(struct dsa_switch *ds, int port,
+ const unsigned char *addr, u16 vid,
+ struct dsa_db db)
+{
+ struct net_device *br = lan9645x_db2bridge(db);
+ struct dsa_port *dp = dsa_to_port(ds, port);
+ struct lan9645x *lan9645x = ds->priv;
+ int dest;
+
+ if (IS_ERR(br))
+ return PTR_ERR(br);
+
+ if (dsa_port_is_cpu(dp) && !br &&
+ dsa_fdb_present_in_other_db(ds, port, addr, vid, db))
+ return 0;
+
+ if (!vid)
+ vid = lan9645x_vlan_unaware_pvid(!!br);
+
+ dest = dsa_port_is_cpu(dp) ? PGID_CPU : port;
+
+ return lan9645x_mact_learn(lan9645x, dest, addr, vid, ENTRYTYPE_LOCKED,
+ false);
+}
+
+static int lan9645x_fdb_del(struct dsa_switch *ds, int port,
+ const unsigned char *addr, u16 vid,
+ struct dsa_db db)
+{
+ struct net_device *br = lan9645x_db2bridge(db);
+ struct dsa_port *dp = dsa_to_port(ds, port);
+ struct lan9645x *lan9645x = ds->priv;
+
+ if (IS_ERR(br))
+ return PTR_ERR(br);
+
+ if (dsa_port_is_cpu(dp) && !br &&
+ dsa_fdb_present_in_other_db(ds, port, addr, vid, db))
+ return 0;
+
+ if (!vid)
+ vid = lan9645x_vlan_unaware_pvid(!!br);
+
+ return lan9645x_mact_forget(lan9645x, addr, vid, ENTRYTYPE_LOCKED);
+}
+
static const struct dsa_switch_ops lan9645x_switch_ops = {
.get_tag_protocol = lan9645x_get_tag_protocol,
@@ -378,6 +457,12 @@ static const struct dsa_switch_ops lan9645x_switch_ops = {
.port_vlan_filtering = lan9645x_port_vlan_filtering,
.port_vlan_add = lan9645x_port_vlan_add,
.port_vlan_del = lan9645x_port_vlan_del,
+
+ /* MAC table integration */
+ .port_fast_age = lan9645x_port_fast_age,
+ .port_fdb_dump = lan9645x_fdb_dump,
+ .port_fdb_add = lan9645x_fdb_add,
+ .port_fdb_del = lan9645x_fdb_del,
};
static int lan9645x_request_target_regmaps(struct lan9645x *lan9645x)
diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_main.h b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.h
index c61efd115aaa..9721a77276e5 100644
--- a/drivers/net/dsa/microchip/lan9645x/lan9645x_main.h
+++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.h
@@ -177,6 +177,19 @@ struct lan9645x_vlan {
untagged: 9; /* ports 0-8 */
};
+/* MAC table entry types.
+ * ENTRYTYPE_NORMAL is subject to aging.
+ * ENTRYTYPE_LOCKED is not subject to aging.
+ * ENTRYTYPE_MACV4 is not subject to aging. For IPv4 multicast.
+ * ENTRYTYPE_MACV6 is not subject to aging. For IPv6 multicast.
+ */
+enum macaccess_entry_type {
+ ENTRYTYPE_NORMAL = 0,
+ ENTRYTYPE_LOCKED,
+ ENTRYTYPE_MACV4,
+ ENTRYTYPE_MACV6,
+};
+
struct lan9645x {
struct device *dev;
struct dsa_switch *ds;
@@ -192,6 +205,7 @@ struct lan9645x {
u16 bridge_mask; /* Mask for bridged ports */
/* lock forwarding configuration and vlan table */
struct mutex fwd_domain_lock;
+ struct mutex mact_lock; /* serialize mac table register access */
int num_port_dis;
@@ -377,4 +391,17 @@ int lan9645x_vlan_port_add_vlan(struct lan9645x_port *p, u16 vid, bool pvid,
int lan9645x_vlan_port_del_vlan(struct lan9645x_port *p, u16 vid);
void lan9645x_vlan_set_hostmode(struct lan9645x_port *p);
+/* MAC table: lan9645x_mac.c */
+int lan9645x_mact_flush(struct lan9645x *lan9645x, int port);
+int lan9645x_mact_learn(struct lan9645x *lan9645x, int port,
+ const unsigned char mac[ETH_ALEN], unsigned int vid,
+ enum macaccess_entry_type type, bool cpu_copy);
+int lan9645x_mact_forget(struct lan9645x *lan9645x,
+ const unsigned char mac[ETH_ALEN], unsigned int vid,
+ enum macaccess_entry_type type);
+int lan9645x_mac_init(struct lan9645x *lan9645x);
+void lan9645x_mac_deinit(struct lan9645x *lan9645x);
+int lan9645x_mact_dsa_dump(struct lan9645x *lan9645x, int port,
+ dsa_fdb_dump_cb_t *cb, void *data);
+
#endif /* __LAN9645X_MAIN_H__ */
--
2.52.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH net-next v13 7/9] net: dsa: lan9645x: add mdb management
2026-09-29 7:48 [PATCH net-next v13 0/9] net: dsa: add DSA support for the LAN9645x switch chip family Jens Emil Schulz Østergaard
` (5 preceding siblings ...)
2026-09-29 7:48 ` [PATCH net-next v13 6/9] net: dsa: lan9645x: add mac table integration Jens Emil Schulz Østergaard
@ 2026-09-29 7:48 ` Jens Emil Schulz Østergaard
2026-10-02 21:14 ` netdev-bot+sashiko
2026-09-29 7:48 ` [PATCH net-next v13 8/9] net: dsa: lan9645x: add bridge support Jens Emil Schulz Østergaard
2026-09-29 7:48 ` [PATCH net-next v13 9/9] net: dsa: lan9645x: add port statistics Jens Emil Schulz Østergaard
8 siblings, 1 reply; 18+ messages in thread
From: Jens Emil Schulz Østergaard @ 2026-09-29 7:48 UTC (permalink / raw)
To: UNGLinuxDriver, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Woojung Huh,
Russell King, Steen Hegelund, Daniel Machon, Geert Uytterhoeven,
Magnus Damm
Cc: linux-kernel, netdev, devicetree, linux-renesas-soc,
Jens Emil Schulz Østergaard
Add support for dsa mdb callbacks. L2 multicast and IP multicast is
handled differently. IP multicast stores the port group mask inline in
the mac table. L2 multicast points at a PGID, whose destination mask is
the port group.
CPU port module membership is carried by the mac table entry's
MAC_CPU_COPY bit rather than by the PGID destination mask. This differs
from mscc/ocelot, which keeps the CPU port module bit inside the PGID
mask, and it lets groups that differ only in host membership share a
PGID.
The PGID table is finite, so it is possible to exhaust it and be unable to
service an L2 MC mdb request. The two directions then behave differently on
purpose.
On add we report the error and leave the group forwarding to the port group
it already had, so the caller learns the port was not joined and the
existing members are undisturbed.
On delete we always report success. Reporting a failure has negative
effects. The switchdev_port_obj_del_deferred() path only logs the error,
while br_switchdev_mdb_replay() abandons the remaining entries after the
first failure, so failing one delete during a bridge leave would leave
the other groups programmed. The group falls back to forwarding
according to the flood masks and host membership instead.
IGMP and MLD frames are redirected to the CPU, and IPv4 multicast
control frames are copied to it, so that the bridge can snoop them.
Reviewed-by: Steen Hegelund <Steen.Hegelund@microchip.com>
Signed-off-by: Jens Emil Schulz Østergaard <jensemil.schulzostergaard@microchip.com>
---
Changes in v12:
- Move this patch before bridge support.
- lan9645x_teardown(): deinit the mdb before the mac table, so teardown
unwinds in reverse order of init.
- Move the IGMP, MLD and IPMC CPU queue configuration from
lan9645x_mdb_init() to lan9645x_setup(), next to the other CPUQ setup.
- __lan9645x_mdb_del(): do not bail out when no PGID is free. If the
entry owns its PGID outright, narrow the port mask in place.
Otherwise fall back to the L2 MC flood mask.
- Split lan9645x_mdb_update_dest() into lan9645x_mdb_widen_dest() for the
add direction and lan9645x_mdb_narrow_dest() for the delete direction.
The bridge reads the return value of a .port_mdb_add but not of a
.port_mdb_del, so the two need opposite error handling.
- __lan9645x_mdb_add(): let a group already on the flood mask absorb
membership changes, so the host can still join it.
- Note that IGMP and MLD are redirected to the CPU and IPv4 multicast
control frames copied, so the bridge can snoop them.
- __lan9645x_mdb_del(): log a failing CMD_FORGET but drop the software
entry anyway. The bridge never repeats a delete, so a kept entry
would be wedged.
- Issue a CMD_FORGET before discarding a freshly allocated mdb entry whose
CMD_LEARN reported an error, so a bus failure between the command and
the status read cannot leave an unreachable entry behind in hardware.
- Use GPL-2.0+ in the SPDX identifier, matching the rest of the driver.
- Assert mdb_lock in every helper that walks the mdb or pgid lists.
- Replace the PGID_INDEX() macro with lan9645x_pgid_idx().
- Spell the mac argument as mac[ETH_ALEN] and the vid as unsigned int.
- Keep the PGID reference when a mac table write fails, both in
lan9645x_mdb_narrow_dest() and when the CMD_FORGET fails for the last
port leaving a group, so the index cannot be handed to an unrelated
group while hardware may still point at it.
- lan9645x_mdb_deinit(): drain pgid_entries, which can now outlive every
mdb entry.
- Note why the delete side reports success. One switchdev path ignores
the error and br_switchdev_mdb_replay() truncates on it.
- Classify IPv4 multicast on the 01:00:5E prefix alone rather than with
ether_addr_is_ipv4_mcast().
- Name the DSA ops lan9645x_port_mdb_add()/_del() after the
.port_mdb_add hooks, rename the lan9645x_mdb.c entry points to
lan9645x_mdb_add()/_del(), and fold the mdb_lock wrappers into them.
The two files previously defined the same names at different levels
of the same call chain.
- lan9645x_mdb_pgid_entry_get(): drop the unreachable !ports test, so
a NULL return means an IP entry and nothing else.
Changes in v8:
- Use lan9645x->num_phys_ports instead of CPU_PORT for the CPU port
module, and reword the related comments.
Changes in v7:
- refactor IGMP/MLD/IPMC_CTRL to use new LAN9645X_CPUQ_DEF,
LAN9645X_CPUQ_TRAP and LAN9645X_CPUQ_COPY queues
- add __aligned(2) to mac variable in lan9645x_mdb_update_dest
Changes in v5:
- skip igmp/mld redir for npi port
Changes in v4:
- clean up fresh mdb when hw mac table write fails.
- avoid void return in lan9645x_offload_fwd_mark
Changes in v3:
- avoid mdb add/del dealloc when mac table writes fail
- dealloc mdb entries on deinit
Changes in v2:
- New file: selftests required implementation of the mdb callbacks.
---
drivers/net/dsa/microchip/lan9645x/Makefile | 1 +
drivers/net/dsa/microchip/lan9645x/lan9645x_main.c | 72 +++
drivers/net/dsa/microchip/lan9645x/lan9645x_main.h | 18 +
drivers/net/dsa/microchip/lan9645x/lan9645x_mdb.c | 569 +++++++++++++++++++++
drivers/net/dsa/microchip/lan9645x/lan9645x_port.c | 8 +
5 files changed, 668 insertions(+)
diff --git a/drivers/net/dsa/microchip/lan9645x/Makefile b/drivers/net/dsa/microchip/lan9645x/Makefile
index 70815edca5b9..2413d11fe849 100644
--- a/drivers/net/dsa/microchip/lan9645x/Makefile
+++ b/drivers/net/dsa/microchip/lan9645x/Makefile
@@ -4,6 +4,7 @@ obj-$(CONFIG_NET_DSA_MICROCHIP_LAN9645X) += mchp-lan9645x.o
mchp-lan9645x-objs := \
lan9645x_mac.o \
lan9645x_main.o \
+ lan9645x_mdb.o \
lan9645x_npi.o \
lan9645x_phylink.o \
lan9645x_port.o \
diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
index 5cb4f5fe1374..cc85e18c5a3b 100644
--- a/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
+++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
@@ -74,6 +74,7 @@ static void lan9645x_teardown(struct dsa_switch *ds)
{
struct lan9645x *lan9645x = ds->priv;
+ lan9645x_mdb_deinit(lan9645x);
lan9645x_mac_deinit(lan9645x);
mutex_destroy(&lan9645x->fwd_domain_lock);
lan9645x_npi_port_deinit(lan9645x, lan9645x->npi);
@@ -166,6 +167,7 @@ static int lan9645x_setup(struct dsa_switch *ds)
err = lan9645x_mac_init(lan9645x);
if (err)
goto err_mutex;
+ lan9645x_mdb_init(lan9645x);
/* Link Aggregation Mode: NETDEV_LAG_HASH_L2 */
lan_wr(ANA_AGGR_CFG_AC_SMAC_ENA |
@@ -253,6 +255,17 @@ static int lan9645x_setup(struct dsa_switch *ds)
lan9645x, ANA_CPUQ_8021_CFG(i));
}
+ /* Use CPU queues to communicate frame classification to the CPU */
+ lan_rmw(ANA_CPUQ_CFG_CPUQ_MAC_COPY_SET(LAN9645X_CPUQ_DEF) |
+ ANA_CPUQ_CFG_CPUQ_IGMP_SET(LAN9645X_CPUQ_TRAP) |
+ ANA_CPUQ_CFG_CPUQ_MLD_SET(LAN9645X_CPUQ_TRAP) |
+ ANA_CPUQ_CFG_CPUQ_IPMC_CTRL_SET(LAN9645X_CPUQ_COPY),
+ ANA_CPUQ_CFG_CPUQ_MAC_COPY |
+ ANA_CPUQ_CFG_CPUQ_IGMP |
+ ANA_CPUQ_CFG_CPUQ_MLD |
+ ANA_CPUQ_CFG_CPUQ_IPMC_CTRL,
+ lan9645x, ANA_CPUQ_CFG);
+
/* Reserve 1664 bytes, 26 buffer cells, per (port, prio) for source
* tracking (resource 0, indices 0..95) and destination tracking
* (resource 2, indices 512..607). These are access watermarks, not
@@ -439,6 +452,61 @@ static int lan9645x_fdb_del(struct dsa_switch *ds, int port,
return lan9645x_mact_forget(lan9645x, addr, vid, ENTRYTYPE_LOCKED);
}
+static int lan9645x_port_mdb_add(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb,
+ struct dsa_db db)
+{
+ struct net_device *bridge_dev = lan9645x_db2bridge(db);
+ struct lan9645x *lan9645x = ds->priv;
+
+ dev_dbg(lan9645x->dev, "port=%d addr=%pM vid=%u\n", port, mdb->addr,
+ mdb->vid);
+
+ if (IS_ERR(bridge_dev))
+ return PTR_ERR(bridge_dev);
+
+ if (dsa_is_cpu_port(ds, port) && !bridge_dev &&
+ dsa_mdb_present_in_other_db(ds, port, mdb, db))
+ return 0;
+
+ if (port == lan9645x->npi)
+ port = lan9645x->num_phys_ports;
+
+ return lan9645x_mdb_add(lan9645x, port, mdb, bridge_dev);
+}
+
+static int lan9645x_port_mdb_del(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb,
+ struct dsa_db db)
+{
+ struct net_device *bridge_dev = lan9645x_db2bridge(db);
+ struct lan9645x *lan9645x = ds->priv;
+ int err;
+
+ dev_dbg(lan9645x->dev, "port=%d addr=%pM vid=%u\n", port, mdb->addr,
+ mdb->vid);
+
+ if (IS_ERR(bridge_dev))
+ return PTR_ERR(bridge_dev);
+
+ if (dsa_is_cpu_port(ds, port) && !bridge_dev &&
+ dsa_mdb_present_in_other_db(ds, port, mdb, db))
+ return 0;
+
+ if (port == lan9645x->npi)
+ port = lan9645x->num_phys_ports;
+
+ err = lan9645x_mdb_del(lan9645x, port, mdb, bridge_dev);
+ if (err == -ENOENT) {
+ dev_dbg(lan9645x->dev,
+ "mdb not found port=%d addr=%pM vid=%u\n", port,
+ mdb->addr, mdb->vid);
+ return 0;
+ }
+
+ return err;
+}
+
static const struct dsa_switch_ops lan9645x_switch_ops = {
.get_tag_protocol = lan9645x_get_tag_protocol,
@@ -463,6 +531,10 @@ static const struct dsa_switch_ops lan9645x_switch_ops = {
.port_fdb_dump = lan9645x_fdb_dump,
.port_fdb_add = lan9645x_fdb_add,
.port_fdb_del = lan9645x_fdb_del,
+
+ /* Multicast database */
+ .port_mdb_add = lan9645x_port_mdb_add,
+ .port_mdb_del = lan9645x_port_mdb_del,
};
static int lan9645x_request_target_regmaps(struct lan9645x *lan9645x)
diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_main.h b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.h
index 9721a77276e5..82b2e469ff60 100644
--- a/drivers/net/dsa/microchip/lan9645x/lan9645x_main.h
+++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.h
@@ -207,6 +207,14 @@ struct lan9645x {
struct mutex fwd_domain_lock;
struct mutex mact_lock; /* serialize mac table register access */
+ /* Multicast Forwarding Database */
+ struct list_head mdb_entries;
+ struct list_head pgid_entries;
+ /* lock for mdb_entries and pgid_entries. Must be taken before mact_lock
+ * if both are taken.
+ */
+ struct mutex mdb_lock;
+
int num_port_dis;
/* VLAN entries */
@@ -404,4 +412,14 @@ void lan9645x_mac_deinit(struct lan9645x *lan9645x);
int lan9645x_mact_dsa_dump(struct lan9645x *lan9645x, int port,
dsa_fdb_dump_cb_t *cb, void *data);
+/* Multicast Database lan9645x_mdb.c */
+int lan9645x_mdb_add(struct lan9645x *lan9645x, int port,
+ const struct switchdev_obj_port_mdb *mdb,
+ struct net_device *bridge);
+int lan9645x_mdb_del(struct lan9645x *lan9645x, int port,
+ const struct switchdev_obj_port_mdb *mdb,
+ struct net_device *bridge);
+void lan9645x_mdb_init(struct lan9645x *lan9645x);
+void lan9645x_mdb_deinit(struct lan9645x *lan9645x);
+
#endif /* __LAN9645X_MAIN_H__ */
diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_mdb.c b/drivers/net/dsa/microchip/lan9645x/lan9645x_mdb.c
new file mode 100644
index 000000000000..92ab5f55f791
--- /dev/null
+++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_mdb.c
@@ -0,0 +1,569 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright (C) 2026 Microchip Technology Inc.
+ */
+
+#include "lan9645x_main.h"
+
+/* IPv4/IPv6 types the mac entry dest_idx is not used for forwarding. The
+ * datasheet recommends using 0 as the dummy index.
+ */
+#define IP_ENTRY_PGID 0
+
+struct lan9645x_pgid_entry {
+ struct list_head list;
+ int index;
+ refcount_t refcount;
+ u16 ports;
+};
+
+struct lan9645x_mdb_entry {
+ struct list_head list;
+ unsigned char mac[ETH_ALEN];
+ u16 vid;
+ u16 ports;
+ struct lan9645x_pgid_entry *pgid;
+};
+
+static int lan9645x_pgid_idx(const struct lan9645x_pgid_entry *pgid)
+{
+ return pgid ? pgid->index : IP_ENTRY_PGID;
+}
+
+void lan9645x_mdb_init(struct lan9645x *lan9645x)
+{
+ INIT_LIST_HEAD(&lan9645x->mdb_entries);
+ INIT_LIST_HEAD(&lan9645x->pgid_entries);
+ mutex_init(&lan9645x->mdb_lock);
+}
+
+static enum macaccess_entry_type lan9645x_mdb_classify(const unsigned char *mac)
+{
+ if (mac[0] == 0x01 && mac[1] == 0x00 && mac[2] == 0x5e)
+ return ENTRYTYPE_MACV4;
+ if (mac[0] == 0x33 && mac[1] == 0x33)
+ return ENTRYTYPE_MACV6;
+ return ENTRYTYPE_LOCKED;
+}
+
+static struct lan9645x_mdb_entry *
+lan9645x_mdb_entry_lookup(struct lan9645x *lan9645x, const unsigned char *mac,
+ u16 vid)
+{
+ struct lan9645x_mdb_entry *mdb;
+
+ lockdep_assert_held(&lan9645x->mdb_lock);
+
+ list_for_each_entry(mdb, &lan9645x->mdb_entries, list) {
+ if (ether_addr_equal(mdb->mac, mac) && mdb->vid == vid)
+ return mdb;
+ }
+
+ return NULL;
+}
+
+static struct lan9645x_mdb_entry *
+lan9645x_mdb_entry_alloc(struct lan9645x *lan9645x,
+ const unsigned char addr[ETH_ALEN], u16 vid)
+{
+ struct lan9645x_mdb_entry *mdb_entry;
+
+ lockdep_assert_held(&lan9645x->mdb_lock);
+
+ mdb_entry = kzalloc_obj(*mdb_entry);
+ if (!mdb_entry)
+ return ERR_PTR(-ENOMEM);
+
+ ether_addr_copy(mdb_entry->mac, addr);
+ mdb_entry->vid = vid;
+
+ list_add_tail(&mdb_entry->list, &lan9645x->mdb_entries);
+
+ dev_dbg(lan9645x->dev, "vid=%u addr=%pM\n", mdb_entry->vid,
+ mdb_entry->mac);
+
+ return mdb_entry;
+}
+
+static void lan9645x_mdb_encode_mac(unsigned char *dst,
+ const unsigned char *mac, u16 ports,
+ enum macaccess_entry_type type)
+{
+ ether_addr_copy(dst, mac);
+
+ /* The HW encodes the portmask in the high bits of the mac for ip
+ * multicast entries, to save on the limited PGID resources.
+ *
+ * IPv4 Multicast DMAC: 0x01005Exxxxxx
+ * IPv6 Multicast DMAC: 0x3333xxxxxxxx
+ *
+ * which gives us 24 or 16 bits to encode the portmask.
+ */
+ if (type == ENTRYTYPE_MACV4) {
+ dst[0] = 0;
+ dst[1] = ports >> 8;
+ dst[2] = ports & 0xff;
+ } else if (type == ENTRYTYPE_MACV6) {
+ dst[0] = ports >> 8;
+ dst[1] = ports & 0xff;
+ }
+}
+
+static void lan9645x_pgid_entry_put(struct lan9645x *lan9645x,
+ struct lan9645x_pgid_entry *pgid_entry)
+{
+ lockdep_assert_held(&lan9645x->mdb_lock);
+
+ if (!pgid_entry)
+ return;
+
+ if (!refcount_dec_and_test(&pgid_entry->refcount))
+ return;
+
+ dev_dbg(lan9645x->dev, "pgid=%d ports=0x%x\n", pgid_entry->index,
+ pgid_entry->ports);
+ /* Leave the destination mask programmed in HW. Reusing the index
+ * rewrites the mask in lan9645x_pgid_entry_alloc() before anything
+ * points at it.
+ */
+ list_del(&pgid_entry->list);
+ kfree(pgid_entry);
+}
+
+static void lan9645x_mdb_entry_dealloc(struct lan9645x *lan9645x,
+ struct lan9645x_mdb_entry *mdb_entry)
+{
+ lockdep_assert_held(&lan9645x->mdb_lock);
+
+ dev_dbg(lan9645x->dev, "vid=%u addr=%pM\n", mdb_entry->vid,
+ mdb_entry->mac);
+ list_del(&mdb_entry->list);
+ lan9645x_pgid_entry_put(lan9645x, mdb_entry->pgid);
+ kfree(mdb_entry);
+}
+
+static struct lan9645x_pgid_entry *
+lan9645x_mdb_pgid_entry_lookup(struct lan9645x *lan9645x, u16 ports)
+{
+ struct lan9645x_pgid_entry *pgid_entry;
+
+ lockdep_assert_held(&lan9645x->mdb_lock);
+
+ list_for_each_entry(pgid_entry, &lan9645x->pgid_entries, list) {
+ if (pgid_entry->ports == ports) {
+ refcount_inc(&pgid_entry->refcount);
+ return pgid_entry;
+ }
+ }
+
+ return NULL;
+}
+
+static struct lan9645x_pgid_entry *
+lan9645x_pgid_entry_alloc(struct lan9645x *lan9645x, int index, u16 ports)
+{
+ struct lan9645x_pgid_entry *pgid_entry;
+
+ lockdep_assert_held(&lan9645x->mdb_lock);
+
+ pgid_entry = kzalloc_obj(*pgid_entry);
+ if (!pgid_entry)
+ return ERR_PTR(-ENOMEM);
+
+ pgid_entry->ports = ports;
+ pgid_entry->index = index;
+ refcount_set(&pgid_entry->refcount, 1);
+
+ list_add_tail(&pgid_entry->list, &lan9645x->pgid_entries);
+
+ dev_dbg(lan9645x->dev, "index=%d ports=0x%x\n", pgid_entry->index,
+ pgid_entry->ports);
+
+ lan_rmw(ANA_PGID_PGID_SET(pgid_entry->ports),
+ ANA_PGID_PGID, lan9645x,
+ ANA_PGID(pgid_entry->index));
+
+ return pgid_entry;
+}
+
+static struct lan9645x_pgid_entry *
+lan9645x_mdb_pgid_entry_create(struct lan9645x *lan9645x, u16 ports)
+{
+ struct lan9645x_pgid_entry *pgid_entry;
+ int index;
+
+ lockdep_assert_held(&lan9645x->mdb_lock);
+
+ for (index = PGID_GP_START; index < PGID_GP_END; index++) {
+ bool used = false;
+
+ list_for_each_entry(pgid_entry, &lan9645x->pgid_entries, list) {
+ if (pgid_entry->index == index) {
+ used = true;
+ break;
+ }
+ }
+
+ if (!used)
+ return lan9645x_pgid_entry_alloc(lan9645x, index,
+ ports);
+ }
+
+ return ERR_PTR(-ENOSPC);
+}
+
+static struct lan9645x_pgid_entry *
+lan9645x_mdb_pgid_entry_get(struct lan9645x *lan9645x, u16 ports,
+ enum macaccess_entry_type type)
+{
+ struct lan9645x_pgid_entry *pgid_entry;
+ u16 pgid_ports;
+
+ lockdep_assert_held(&lan9645x->mdb_lock);
+
+ if (type == ENTRYTYPE_MACV4 || type == ENTRYTYPE_MACV6)
+ return NULL;
+
+ /* CPU port module forwarding is handled by cpu_copy flag on mac table
+ * entry. So we can strip the CPU port module here to allow better PGID
+ * sharing.
+ */
+ pgid_ports = ports & ~BIT(lan9645x->num_phys_ports);
+
+ pgid_entry = lan9645x_mdb_pgid_entry_lookup(lan9645x, pgid_ports);
+ if (!pgid_entry)
+ return lan9645x_mdb_pgid_entry_create(lan9645x, pgid_ports);
+
+ return pgid_entry;
+}
+
+/* An L2 MC group without a pgid is one that fell back to the flood mask in
+ * __lan9645x_mdb_del().
+ */
+static bool
+lan9645x_mdb_on_flood_mask(const struct lan9645x_mdb_entry *mdb_entry,
+ enum macaccess_entry_type type)
+{
+ return type == ENTRYTYPE_LOCKED && mdb_entry->ports && !mdb_entry->pgid;
+}
+
+/* Point the hardware mac table entry at pgid_index with the port mask
+ * new_ports. Does not touch mdb_entry, the caller owns the software state.
+ */
+static int lan9645x_mdb_write_dest(struct lan9645x *lan9645x,
+ struct lan9645x_mdb_entry *mdb_entry,
+ enum macaccess_entry_type type,
+ int pgid_index, u16 new_ports)
+{
+ unsigned char mac[ETH_ALEN] __aligned(2);
+ bool cpu_copy;
+
+ lockdep_assert_held(&lan9645x->mdb_lock);
+
+ lan9645x_mdb_encode_mac(mac, mdb_entry->mac, new_ports, type);
+ cpu_copy = !!(new_ports & BIT(lan9645x->num_phys_ports));
+
+ /* For IP multicast, the hardware lookup uses the DMAC
+ * (01:00:5E:.. / 33:33:..) as the (mac, vid) key, not the encoded mac.
+ * Therefore, this CMD_LEARN will atomically rewrite the existing
+ * hardware entry. We intentionally do not do a forget before learn
+ * sequence, as that would not be atomic, and leave a forwarding gap.
+ */
+ return lan9645x_mact_learn(lan9645x, pgid_index, mac, mdb_entry->vid,
+ type, cpu_copy);
+}
+
+/* Grow the group. A port can be refused membership, and the bridge reads the
+ * return value of a SWITCHDEV_PORT_OBJ_ADD, so a failure must leave the
+ * software entry exactly as it was and drop the reference taken on new_pgid.
+ */
+static int lan9645x_mdb_widen_dest(struct lan9645x *lan9645x,
+ struct lan9645x_mdb_entry *mdb_entry,
+ enum macaccess_entry_type type,
+ struct lan9645x_pgid_entry *new_pgid,
+ int pgid_index, u16 new_ports)
+{
+ struct lan9645x_pgid_entry *old_pgid = mdb_entry->pgid;
+ int err;
+
+ lockdep_assert_held(&lan9645x->mdb_lock);
+
+ err = lan9645x_mdb_write_dest(lan9645x, mdb_entry, type, pgid_index,
+ new_ports);
+ if (err) {
+ lan9645x_pgid_entry_put(lan9645x, new_pgid);
+ return err;
+ }
+ mdb_entry->pgid = new_pgid;
+ mdb_entry->ports = new_ports;
+ lan9645x_pgid_entry_put(lan9645x, old_pgid);
+ return 0;
+}
+
+/* Shrink the group. A port cannot be refused leaving, and reporting a failure
+ * is worse than useless. One del path ignores the error:
+ * switchdev_port_obj_del_deferred() only logs it, and unlike RTM_NEWMDB the
+ * RTM_DELMDB path installs no obj.complete. The other truncates on it:
+ * br_switchdev_mdb_replay() abandons the remaining entries after the first
+ * failure, so failing one delete during a bridge leave would leave the other
+ * groups programmed. The bridge has already dropped the group by the time we
+ * run, so it will never ask again.
+ *
+ * Commit the software state first, then program hardware and only log a
+ * failure. That keeps both invariants the rest of this file relies on:
+ *
+ * mdb_entry->ports stays a subset of the bridge's view of the group, so a
+ * later delete can still drive it to zero and reclaim the entry, and
+ *
+ * mdb_entry->pgid->ports stays equal to
+ * mdb_entry->ports & ~BIT(num_phys_ports), which is what lets
+ * __lan9645x_mdb_del() conclude that a departing port reaching the
+ * overwrite in place branch cannot be the CPU port module.
+ */
+static void lan9645x_mdb_narrow_dest(struct lan9645x *lan9645x,
+ struct lan9645x_mdb_entry *mdb_entry,
+ enum macaccess_entry_type type,
+ struct lan9645x_pgid_entry *new_pgid,
+ int pgid_index, u16 new_ports)
+{
+ struct lan9645x_pgid_entry *old_pgid = mdb_entry->pgid;
+ int err;
+
+ lockdep_assert_held(&lan9645x->mdb_lock);
+
+ mdb_entry->pgid = new_pgid;
+ mdb_entry->ports = new_ports;
+
+ err = lan9645x_mdb_write_dest(lan9645x, mdb_entry, type, pgid_index,
+ new_ports);
+ if (err) {
+ dev_err(lan9645x->dev,
+ "Narrowing %pM vid %u to mask 0x%x returned %pe\n",
+ mdb_entry->mac, mdb_entry->vid, new_ports,
+ ERR_PTR(err));
+ return;
+ }
+
+ lan9645x_pgid_entry_put(lan9645x, old_pgid);
+}
+
+static int __lan9645x_mdb_add(struct lan9645x *lan9645x, int chip_port,
+ const unsigned char addr[ETH_ALEN], u16 vid,
+ enum macaccess_entry_type type)
+{
+ struct lan9645x_pgid_entry *new_pgid;
+ struct lan9645x_mdb_entry *mdb_entry;
+ u16 new_ports;
+ int err;
+
+ lockdep_assert_held(&lan9645x->mdb_lock);
+
+ mdb_entry = lan9645x_mdb_entry_lookup(lan9645x, addr, vid);
+ if (!mdb_entry) {
+ mdb_entry = lan9645x_mdb_entry_alloc(lan9645x, addr, vid);
+ if (IS_ERR(mdb_entry))
+ return PTR_ERR(mdb_entry);
+ }
+
+ if (mdb_entry->ports & BIT(chip_port))
+ return 0;
+
+ new_ports = mdb_entry->ports | BIT(chip_port);
+
+ /* Update PGID ptr for non-IP entries (L2 multicast) */
+ new_pgid = lan9645x_mdb_pgid_entry_get(lan9645x, new_ports, type);
+ if (IS_ERR(new_pgid)) {
+ /* Out of PGIDs or mem. Remove a fresh mdb_entry again. */
+ if (!mdb_entry->ports) {
+ lan9645x_mdb_entry_dealloc(lan9645x, mdb_entry);
+ return PTR_ERR(new_pgid);
+ }
+
+ /* For a L2 MC group already on the flood mask, we keep it there
+ * so that the host can still join/leave a group we had to give
+ * up offloading.
+ */
+ if (lan9645x_mdb_on_flood_mask(mdb_entry, type))
+ return lan9645x_mdb_widen_dest(lan9645x, mdb_entry,
+ type, NULL, PGID_MC,
+ new_ports);
+
+ /* Continue forwarding to old port group. */
+ return PTR_ERR(new_pgid);
+ }
+
+ err = lan9645x_mdb_widen_dest(lan9645x, mdb_entry, type, new_pgid,
+ lan9645x_pgid_idx(new_pgid), new_ports);
+ if (err && !mdb_entry->ports) {
+ /* We are about to drop a fresh entry, so make sure the hardware
+ * does not keep one we can no longer reach. The mac commands
+ * complete in a few microseconds, so the only way to get here
+ * with an entry actually written is for the register bus to
+ * fail between the command and the status read. That is close
+ * to impossible, but the forget costs nothing: it is a noop if
+ * the entry was never written, and it fails harmlessly if the
+ * bus is still down.
+ */
+ lan9645x_mact_forget(lan9645x, mdb_entry->mac,
+ mdb_entry->vid, type);
+ lan9645x_mdb_entry_dealloc(lan9645x, mdb_entry);
+ }
+
+ return err;
+}
+
+static int __lan9645x_mdb_del(struct lan9645x *lan9645x, int chip_port,
+ const unsigned char addr[ETH_ALEN], u16 vid,
+ enum macaccess_entry_type type)
+{
+ struct lan9645x_pgid_entry *new_pgid;
+ struct lan9645x_mdb_entry *mdb_entry;
+ u16 new_ports;
+ int err;
+
+ lockdep_assert_held(&lan9645x->mdb_lock);
+
+ mdb_entry = lan9645x_mdb_entry_lookup(lan9645x, addr, vid);
+ if (!mdb_entry)
+ return -ENOENT;
+
+ if (!(mdb_entry->ports & BIT(chip_port)))
+ return 0;
+
+ new_ports = mdb_entry->ports & ~BIT(chip_port);
+
+ if (!new_ports) {
+ /* The encoded bytes are not part of the (mac, vid) key used for
+ * lookups in the mactable, for entries of type 2
+ * (IPv4 Multicast) and type 3 (IPv6 Multicast). For these types
+ * the mac used in the key is the 'real' mac e.g.
+ *
+ * Type 2 (IPv4 multicast):
+ * KEY_MAC = 0x01005E000000 | MACLDATA[23:0]
+ *
+ * Type 3 (IPv6 multicast):
+ * KEY_MAC = 0x333300000000 | MACLDATA[31:0]
+ *
+ * This holds for both the datapath and the CPU access path.
+ * Therefore, it is not necessary to encode the mac before a
+ * CMD_FORGET, because the bytes it changes are unused for the
+ * lookup.
+ */
+ err = lan9645x_mact_forget(lan9645x, mdb_entry->mac,
+ mdb_entry->vid, type);
+ if (err) {
+ dev_err(lan9645x->dev,
+ "Forgetting %pM vid %u on port %d returned %pe\n",
+ mdb_entry->mac, mdb_entry->vid, chip_port,
+ ERR_PTR(err));
+ mdb_entry->pgid = NULL;
+ }
+ lan9645x_mdb_entry_dealloc(lan9645x, mdb_entry);
+ return 0;
+ }
+
+ /* Update PGID ptr for non-IP entries (L2 multicast) */
+ new_pgid = lan9645x_mdb_pgid_entry_get(lan9645x, new_ports, type);
+ if (!IS_ERR(new_pgid)) {
+ lan9645x_mdb_narrow_dest(lan9645x, mdb_entry, type, new_pgid,
+ lan9645x_pgid_idx(new_pgid),
+ new_ports);
+ return 0;
+ }
+
+ /* Out of pgids for L2 MC.
+ *
+ * PGID is not shared, so we overwrite in place.
+ *
+ * We know the deleted port is not the CPU. Getting here means
+ * lan9645x_mdb_pgid_entry_get() found no PGID holding the narrowed
+ * mask, and for a delete of only the CPU port module bit the mask it
+ * looks up is unchanged, since lan9645x_mdb_pgid_entry_get() strips
+ * that bit. lan9645x_mdb_pgid_entry_lookup() would have returned the
+ * PGID this entry already points at. So the departing port is a front
+ * port, and the mac table entry, which only carries the PGID index and
+ * MAC_CPU_COPY, does not need rewriting.
+ *
+ * refcount_read is safe because mdb_lock serializes every get and put
+ * of a PGID entry.
+ */
+ if (mdb_entry->pgid && refcount_read(&mdb_entry->pgid->refcount) == 1) {
+ mdb_entry->pgid->ports = new_ports &
+ ~BIT(lan9645x->num_phys_ports);
+ mdb_entry->ports = new_ports;
+
+ lan_rmw(ANA_PGID_PGID_SET(mdb_entry->pgid->ports),
+ ANA_PGID_PGID,
+ lan9645x, ANA_PGID(mdb_entry->pgid->index));
+
+ return 0;
+ }
+
+ /* Shared PGID or on flood mask. Point the entry at the L2 MC flood mask
+ * instead. This way our mac table entry survives and MAC_CPU_COPY
+ * keeps host membership working.
+ */
+ if (mdb_entry->pgid)
+ dev_warn_ratelimited(lan9645x->dev,
+ "No PGID, flooding group %pM vid %u: %pe\n",
+ mdb_entry->mac, mdb_entry->vid, new_pgid);
+
+ lan9645x_mdb_narrow_dest(lan9645x, mdb_entry, type, NULL, PGID_MC,
+ new_ports);
+ return 0;
+}
+
+int lan9645x_mdb_add(struct lan9645x *lan9645x, int port,
+ const struct switchdev_obj_port_mdb *mdb,
+ struct net_device *bridge)
+{
+ enum macaccess_entry_type type;
+ u16 vid = mdb->vid;
+ int err;
+
+ type = lan9645x_mdb_classify(mdb->addr);
+
+ if (!vid)
+ vid = lan9645x_vlan_unaware_pvid(!!bridge);
+
+ mutex_lock(&lan9645x->mdb_lock);
+ err = __lan9645x_mdb_add(lan9645x, port, mdb->addr, vid, type);
+ mutex_unlock(&lan9645x->mdb_lock);
+ return err;
+}
+
+int lan9645x_mdb_del(struct lan9645x *lan9645x, int port,
+ const struct switchdev_obj_port_mdb *mdb,
+ struct net_device *bridge)
+{
+ enum macaccess_entry_type type;
+ u16 vid = mdb->vid;
+ int err;
+
+ type = lan9645x_mdb_classify(mdb->addr);
+
+ if (!vid)
+ vid = lan9645x_vlan_unaware_pvid(!!bridge);
+
+ mutex_lock(&lan9645x->mdb_lock);
+ err = __lan9645x_mdb_del(lan9645x, port, mdb->addr, vid, type);
+ mutex_unlock(&lan9645x->mdb_lock);
+ return err;
+}
+
+void lan9645x_mdb_deinit(struct lan9645x *lan9645x)
+{
+ struct lan9645x_pgid_entry *pgid, *pgid_tmp;
+ struct lan9645x_mdb_entry *mdb, *tmp;
+
+ mutex_lock(&lan9645x->mdb_lock);
+ list_for_each_entry_safe(mdb, tmp, &lan9645x->mdb_entries, list)
+ lan9645x_mdb_entry_dealloc(lan9645x, mdb);
+
+ list_for_each_entry_safe(pgid, pgid_tmp, &lan9645x->pgid_entries, list) {
+ list_del(&pgid->list);
+ kfree(pgid);
+ }
+ mutex_unlock(&lan9645x->mdb_lock);
+
+ mutex_destroy(&lan9645x->mdb_lock);
+}
diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_port.c b/drivers/net/dsa/microchip/lan9645x/lan9645x_port.c
index 2571380f2c02..edcedd7723c9 100644
--- a/drivers/net/dsa/microchip/lan9645x/lan9645x_port.c
+++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_port.c
@@ -166,6 +166,14 @@ int lan9645x_port_setup(struct dsa_switch *ds, int port)
mutex_lock(&lan9645x->fwd_domain_lock);
lan9645x_vlan_set_hostmode(p);
mutex_unlock(&lan9645x->fwd_domain_lock);
+
+ lan_rmw(ANA_CPU_FWD_CFG_IGMP_REDIR_ENA_SET(1) |
+ ANA_CPU_FWD_CFG_MLD_REDIR_ENA_SET(1) |
+ ANA_CPU_FWD_CFG_IPMC_CTRL_COPY_ENA_SET(1),
+ ANA_CPU_FWD_CFG_IGMP_REDIR_ENA |
+ ANA_CPU_FWD_CFG_MLD_REDIR_ENA |
+ ANA_CPU_FWD_CFG_IPMC_CTRL_COPY_ENA,
+ lan9645x, ANA_CPU_FWD_CFG(p->chip_port));
}
return 0;
--
2.52.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH net-next v13 8/9] net: dsa: lan9645x: add bridge support
2026-09-29 7:48 [PATCH net-next v13 0/9] net: dsa: add DSA support for the LAN9645x switch chip family Jens Emil Schulz Østergaard
` (6 preceding siblings ...)
2026-09-29 7:48 ` [PATCH net-next v13 7/9] net: dsa: lan9645x: add mdb management Jens Emil Schulz Østergaard
@ 2026-09-29 7:48 ` Jens Emil Schulz Østergaard
2026-10-02 21:14 ` netdev-bot+sashiko
2026-09-29 7:48 ` [PATCH net-next v13 9/9] net: dsa: lan9645x: add port statistics Jens Emil Schulz Østergaard
8 siblings, 1 reply; 18+ messages in thread
From: Jens Emil Schulz Østergaard @ 2026-09-29 7:48 UTC (permalink / raw)
To: UNGLinuxDriver, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Woojung Huh,
Russell King, Steen Hegelund, Daniel Machon, Geert Uytterhoeven,
Magnus Damm
Cc: linux-kernel, netdev, devicetree, linux-renesas-soc,
Jens Emil Schulz Østergaard
Add support for hardware offloading of the bridge. We support a single
bridge device.
Mac entry ageing can be configured with a 2s granularity, and a
requested time is rounded down. An ANA_AUTOAGE.AGE_PERIOD of 0 stops the
analyzer from ageing at all, which is the opposite of what a bridge
ageing time of 0 asks for, so ds->ageing_time_min and
ds->ageing_time_max let the DSA core reject what the hardware cannot
express.
The flood masks are global and not per ingress port, so a host flood
request from one port causes unknown unicast or multicast from every
port to be forwarded to the CPU.
The bridge applies promiscuous mode to enslaved ports under most
conditions even when VLAN filtering is enabled. To protect a weak
external CPU, port_set_host_flood requests for offloaded bridged ports
are recorded, but not programmed until the port leaves the bridge.
Standalone ports get programmed, so software bridging works.
Reviewed-by: Steen Hegelund <Steen.Hegelund@microchip.com>
Signed-off-by: Jens Emil Schulz Østergaard <jensemil.schulzostergaard@microchip.com>
---
Changes in v12:
- Move this patch after vlan, mac table and mdb support, so
.port_fast_age exists for the STP state transitions and host address
filtering is already advertised.
- Drop the open coded single bridge check now that ds->max_num_bridges
makes the core reject a second bridge, and the now unused bridge field.
- Set ds->ageing_time_min and drop the clamp and the msecs == 0 case
from lan9645x_set_ageing_time(). An ANA_AUTOAGE.AGE_PERIOD of 0 stops
the analyzer from ageing, which is the opposite of what a bridge
ageing_time of 0 asks for, so the DSA core now rejects out of range
values instead.
- Drop mact_lock from lan9645x_set_ageing_time() after the fdb dump no
longer toggles ageing.
- Remove the CPU port module from the unicast and multicast flood masks.
Host address filtering covers the known addresses and
port_set_host_flood() covers promiscuous and allmulti.
- Move lan9645x_vlan_clear_hostmode() here, with its caller.
- Describe the host flood compromise for offloaded bridged ports.
- lan9645x_port_set_learning() now takes fwd_domain_lock.
- port_set_host_flood(): drop the per port work and ordered workqueue
and program the masks inline, since the ndo no longer runs in atomic
context.
- Remove unreachable WARN_ON()s and the equally unreachable NPI port
check in lan9645x_port_bridge_flags(). DSA does not call the port
operations for the CPU port.
Changes in v9:
- Drop the port_mux_lock teardown from the setup error path, as the
mutex has been removed.
Changes in v8:
- Use lan9645x->num_phys_ports instead of CPU_PORT for the CPU port
module in the port pgid and host flood helpers.
Changes in v5:
- use ds->ageing_time_max
- use packed p->host_flood_req for atomic r/w
- fix typo in set_ageing_time comment
- include lan9645x->bridge deref under lock in brige_join
Changes in v4:
- set_host_flood changed to per port work to coalesce values and skip
atomic allocations
Changes in v3:
- allow disabling aging with explicit zero parameters.
- fix non-forwarding stp states
- fix restore host_flood requests on bridge leave
- destroy fwd_domain_lock mutex on setup err path
Changes in v2:
- variable name consistency
- port_set_learning use stp_state before writing to hw
- add set_host_flood for selftests, which need promic/all_multi on
standalone interfaces
---
drivers/net/dsa/microchip/lan9645x/lan9645x_main.c | 291 ++++++++++++++++++++-
drivers/net/dsa/microchip/lan9645x/lan9645x_main.h | 7 +
drivers/net/dsa/microchip/lan9645x/lan9645x_vlan.c | 9 +
3 files changed, 296 insertions(+), 11 deletions(-)
diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
index cc85e18c5a3b..e41a73c20606 100644
--- a/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
+++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
@@ -283,19 +283,15 @@ static int lan9645x_setup(struct dsa_switch *ds)
lan9645x_port_cpu_init(lan9645x);
- /* Multicast to cpu and all front ports */
- lan_wr(BIT(lan9645x->num_phys_ports) | front_ports, lan9645x,
- ANA_PGID(PGID_MC));
+ /* Multicast to all front ports */
+ lan_wr(front_ports, lan9645x, ANA_PGID(PGID_MC));
- /* IP multicast to cpu and all front ports */
- lan_wr(BIT(lan9645x->num_phys_ports) | front_ports, lan9645x,
- ANA_PGID(PGID_MCIPV4));
- lan_wr(BIT(lan9645x->num_phys_ports) | front_ports, lan9645x,
- ANA_PGID(PGID_MCIPV6));
+ /* IP multicast to all front ports */
+ lan_wr(front_ports, lan9645x, ANA_PGID(PGID_MCIPV4));
+ lan_wr(front_ports, lan9645x, ANA_PGID(PGID_MCIPV6));
- /* Unicast to cpu and all front ports */
- lan_wr(BIT(lan9645x->num_phys_ports) | front_ports, lan9645x,
- ANA_PGID(PGID_UC));
+ /* Unicast to all front ports */
+ lan_wr(front_ports, lan9645x, ANA_PGID(PGID_UC));
/* Broadcast to cpu and all front ports */
lan_wr(BIT(lan9645x->num_phys_ports) | front_ports, lan9645x,
@@ -306,6 +302,19 @@ static int lan9645x_setup(struct dsa_switch *ds)
ds->fdb_isolation = true;
ds->max_num_bridges = 1;
+ /* ANA_AUTOAGE_AGE_PERIOD is a seconds-based field and entries are
+ * aged after 2 * AGE_PERIOD, giving (2 * FIELD_MAX) seconds of
+ * maximum aging and 2 seconds of minimum aging.
+ *
+ * An AGE_PERIOD of 0 stops the analyzer from aging at all, which is
+ * the opposite of what a bridge ageing_time of 0 asks for. The bridge
+ * expires every dynamic entry immediately in that case and floods, so
+ * reject it rather than keep learned addresses forever.
+ */
+ ds->ageing_time_min = 2U * MSEC_PER_SEC;
+ ds->ageing_time_max = 2U * MSEC_PER_SEC *
+ FIELD_MAX(ANA_AUTOAGE_AGE_PERIOD);
+
dev_info(lan9645x->dev,
"SKU features: max_ports=%d\n",
lan9645x->num_phys_ports - lan9645x->num_port_dis);
@@ -324,6 +333,257 @@ static void lan9645x_port_phylink_get_caps(struct dsa_switch *ds, int port,
lan9645x_phylink_get_caps(ds->priv, port, config);
}
+static int lan9645x_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
+{
+ struct lan9645x *lan9645x = ds->priv;
+
+ /* Entry must suffer two aging scans before it is removed, so it is
+ * aged after 2*AGE_PERIOD, and the unit is in seconds.
+ */
+ lan_rmw(ANA_AUTOAGE_AGE_PERIOD_SET(msecs / (2 * MSEC_PER_SEC)),
+ ANA_AUTOAGE_AGE_PERIOD,
+ lan9645x, ANA_AUTOAGE);
+
+ return 0;
+}
+
+static int lan9645x_port_pre_bridge_flags(struct dsa_switch *ds, int port,
+ struct switchdev_brport_flags flags,
+ struct netlink_ext_ack *extack)
+{
+ if (flags.mask &
+ ~(BR_LEARNING | BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD))
+ return -EINVAL;
+
+ return 0;
+}
+
+static void lan9645x_port_pgid_set(struct lan9645x *lan9645x, u16 pgid,
+ int chip_port, bool enabled)
+{
+ u32 reg_msk, port_msk;
+
+ port_msk = ANA_PGID_PGID_SET(enabled ? BIT(chip_port) : 0);
+ reg_msk = ANA_PGID_PGID_SET(BIT(chip_port));
+
+ lan_rmw(port_msk, reg_msk, lan9645x, ANA_PGID(pgid));
+}
+
+static void lan9645x_port_set_learning(struct lan9645x *lan9645x, int port,
+ bool enabled)
+{
+ struct lan9645x_port *p = lan9645x_to_port(lan9645x, port);
+
+ mutex_lock(&lan9645x->fwd_domain_lock);
+ p->learn_ena = enabled;
+
+ enabled = enabled && (p->stp_state == BR_STATE_LEARNING ||
+ p->stp_state == BR_STATE_FORWARDING);
+
+ lan_rmw(ANA_PORT_CFG_LEARN_ENA_SET(enabled), ANA_PORT_CFG_LEARN_ENA,
+ lan9645x, ANA_PORT_CFG(p->chip_port));
+ mutex_unlock(&lan9645x->fwd_domain_lock);
+}
+
+static int lan9645x_port_bridge_flags(struct dsa_switch *ds, int port,
+ struct switchdev_brport_flags f,
+ struct netlink_ext_ack *extack)
+{
+ struct lan9645x *lan9645x = ds->priv;
+
+ /* DSA core does not call this for the CPU port */
+ if (f.mask & BR_LEARNING)
+ lan9645x_port_set_learning(lan9645x, port,
+ !!(f.val & BR_LEARNING));
+
+ if (f.mask & BR_FLOOD)
+ lan9645x_port_pgid_set(lan9645x, PGID_UC, port,
+ !!(f.val & BR_FLOOD));
+
+ if (f.mask & BR_MCAST_FLOOD) {
+ bool ena = !!(f.val & BR_MCAST_FLOOD);
+
+ lan9645x_port_pgid_set(lan9645x, PGID_MC, port, ena);
+ lan9645x_port_pgid_set(lan9645x, PGID_MCIPV4, port, ena);
+ lan9645x_port_pgid_set(lan9645x, PGID_MCIPV6, port, ena);
+ }
+
+ if (f.mask & BR_BCAST_FLOOD)
+ lan9645x_port_pgid_set(lan9645x, PGID_BC, port,
+ !!(f.val & BR_BCAST_FLOOD));
+
+ return 0;
+}
+
+static void lan9645x_update_fwd_mask(struct lan9645x *lan9645x)
+{
+ struct lan9645x_port *p;
+ struct dsa_port *dp;
+
+ lockdep_assert_held(&lan9645x->fwd_domain_lock);
+
+ /* Updates the source port PGIDs, making sure frames from p
+ * are only forwarded to ports q != p, where q is relevant to forward
+ */
+ dsa_switch_for_each_available_port(dp, lan9645x->ds) {
+ u32 mask = 0;
+
+ p = lan9645x_to_port(lan9645x, dp->index);
+
+ if (lan9645x_port_is_bridged(p) &&
+ (lan9645x->bridge_fwd_mask & BIT(dp->index))) {
+ mask = lan9645x->bridge_mask &
+ lan9645x->bridge_fwd_mask & ~BIT(dp->index);
+ }
+
+ lan_wr(ANA_PGID_PGID_SET(mask), lan9645x,
+ ANA_PGID(PGID_SRC + dp->index));
+ }
+}
+
+static void __lan9645x_port_mark_host_flood(struct lan9645x *lan9645x, int port,
+ bool uc, bool mc)
+{
+ lockdep_assert_held(&lan9645x->fwd_domain_lock);
+
+ if (uc)
+ lan9645x->host_flood_uc_mask |= BIT(port);
+ else
+ lan9645x->host_flood_uc_mask &= ~BIT(port);
+
+ if (mc)
+ lan9645x->host_flood_mc_mask |= BIT(port);
+ else
+ lan9645x->host_flood_mc_mask &= ~BIT(port);
+}
+
+static void __lan9645x_port_set_host_flood(struct lan9645x *lan9645x)
+{
+ bool mc_ena, uc_ena;
+ u16 unbridged;
+
+ lockdep_assert_held(&lan9645x->fwd_domain_lock);
+
+ /* We want promiscuous and all_multi to affect standalone ports, for
+ * software bridging, debug and test purposes.
+ *
+ * However, the linux bridge is incredibly eager to put bridged ports in
+ * promiscuous mode.
+ *
+ * This is unfortunate since lan9645x flood masks are global and not per
+ * ingress port. When some port triggers unknown uc/mc to the CPU, the
+ * traffic from any port is forwarded to the CPU.
+ *
+ * If the host CPU is weak, this can cause tremendous stress. Therefore,
+ * we compromise by ignoring this host flood request for offloaded
+ * bridged ports.
+ */
+ unbridged = ~lan9645x->bridge_mask &
+ GENMASK(lan9645x->num_phys_ports - 1, 0);
+
+ uc_ena = !!(lan9645x->host_flood_uc_mask & unbridged);
+ lan9645x_port_pgid_set(lan9645x, PGID_UC, lan9645x->num_phys_ports,
+ uc_ena);
+
+ mc_ena = !!(lan9645x->host_flood_mc_mask & unbridged);
+ lan9645x_port_pgid_set(lan9645x, PGID_MC, lan9645x->num_phys_ports,
+ mc_ena);
+ lan9645x_port_pgid_set(lan9645x, PGID_MCIPV4, lan9645x->num_phys_ports,
+ mc_ena);
+ lan9645x_port_pgid_set(lan9645x, PGID_MCIPV6, lan9645x->num_phys_ports,
+ mc_ena);
+}
+
+static void lan9645x_port_set_host_flood(struct dsa_switch *ds, int port,
+ bool uc, bool mc)
+{
+ struct lan9645x *lan9645x = ds->priv;
+ struct lan9645x_port *p;
+
+ p = lan9645x_to_port(lan9645x, port);
+
+ mutex_lock(&lan9645x->fwd_domain_lock);
+ __lan9645x_port_mark_host_flood(lan9645x, p->chip_port, uc, mc);
+ __lan9645x_port_set_host_flood(lan9645x);
+ mutex_unlock(&lan9645x->fwd_domain_lock);
+}
+
+static int lan9645x_port_bridge_join(struct dsa_switch *ds, int port,
+ struct dsa_bridge bridge,
+ bool *tx_fwd_offload,
+ struct netlink_ext_ack *extack)
+{
+ struct lan9645x *lan9645x = ds->priv;
+ struct lan9645x_port *p;
+
+ p = lan9645x_to_port(lan9645x, port);
+
+ mutex_lock(&lan9645x->fwd_domain_lock);
+
+ lan9645x->bridge_mask |= BIT(p->chip_port);
+ __lan9645x_port_set_host_flood(lan9645x);
+ lan9645x_vlan_clear_hostmode(p);
+
+ mutex_unlock(&lan9645x->fwd_domain_lock);
+
+ /* Forwarding is updated by .port_stp_state_set(), which the core
+ * invokes after this returns:
+ * dsa_port_bridge_join() -> dsa_port_switchdev_sync_attrs() ->
+ * dsa_port_set_state()
+ */
+
+ return 0;
+}
+
+static void lan9645x_port_bridge_stp_state_set(struct dsa_switch *ds, int port,
+ u8 state)
+{
+ struct lan9645x *lan9645x;
+ struct lan9645x_port *p;
+ bool learn_ena;
+
+ lan9645x = ds->priv;
+ p = lan9645x_to_port(lan9645x, port);
+
+ mutex_lock(&lan9645x->fwd_domain_lock);
+
+ p->stp_state = state;
+
+ if (state == BR_STATE_FORWARDING)
+ lan9645x->bridge_fwd_mask |= BIT(p->chip_port);
+ else
+ lan9645x->bridge_fwd_mask &= ~BIT(p->chip_port);
+
+ learn_ena = (state == BR_STATE_LEARNING ||
+ state == BR_STATE_FORWARDING) && p->learn_ena;
+
+ lan_rmw(ANA_PORT_CFG_LEARN_ENA_SET(learn_ena),
+ ANA_PORT_CFG_LEARN_ENA, lan9645x,
+ ANA_PORT_CFG(p->chip_port));
+
+ lan9645x_update_fwd_mask(lan9645x);
+ mutex_unlock(&lan9645x->fwd_domain_lock);
+}
+
+static void lan9645x_port_bridge_leave(struct dsa_switch *ds, int port,
+ struct dsa_bridge bridge)
+{
+ struct lan9645x *lan9645x = ds->priv;
+ struct lan9645x_port *p;
+
+ p = lan9645x_to_port(lan9645x, port);
+
+ mutex_lock(&lan9645x->fwd_domain_lock);
+
+ lan9645x->bridge_mask &= ~BIT(p->chip_port);
+
+ __lan9645x_port_set_host_flood(lan9645x);
+ lan9645x_vlan_set_hostmode(p);
+ lan9645x_update_fwd_mask(lan9645x);
+
+ mutex_unlock(&lan9645x->fwd_domain_lock);
+}
+
static int lan9645x_port_vlan_filtering(struct dsa_switch *ds, int port,
bool enabled,
struct netlink_ext_ack *extack)
@@ -521,6 +781,15 @@ static const struct dsa_switch_ops lan9645x_switch_ops = {
.port_change_mtu = lan9645x_change_mtu,
.port_max_mtu = lan9645x_get_max_mtu,
+ /* Bridge integration */
+ .set_ageing_time = lan9645x_set_ageing_time,
+ .port_pre_bridge_flags = lan9645x_port_pre_bridge_flags,
+ .port_bridge_flags = lan9645x_port_bridge_flags,
+ .port_bridge_join = lan9645x_port_bridge_join,
+ .port_bridge_leave = lan9645x_port_bridge_leave,
+ .port_stp_state_set = lan9645x_port_bridge_stp_state_set,
+ .port_set_host_flood = lan9645x_port_set_host_flood,
+
/* VLAN integration */
.port_vlan_filtering = lan9645x_port_vlan_filtering,
.port_vlan_add = lan9645x_port_vlan_add,
diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_main.h b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.h
index 82b2e469ff60..0b284eb4e9eb 100644
--- a/drivers/net/dsa/microchip/lan9645x/lan9645x_main.h
+++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.h
@@ -195,6 +195,9 @@ struct lan9645x {
struct dsa_switch *ds;
struct regmap *rmap[NUM_TARGETS];
+ u16 host_flood_uc_mask;
+ u16 host_flood_mc_mask;
+
/* NPI chip_port */
int npi;
@@ -203,6 +206,7 @@ struct lan9645x {
/* Forwarding Database */
u16 bridge_mask; /* Mask for bridged ports */
+ u16 bridge_fwd_mask; /* Mask for forwarding bridged ports */
/* lock forwarding configuration and vlan table */
struct mutex fwd_domain_lock;
struct mutex mact_lock; /* serialize mac table register access */
@@ -225,6 +229,8 @@ struct lan9645x_port {
struct lan9645x *lan9645x;
u8 chip_port;
+ u8 stp_state;
+ bool learn_ena;
bool vlan_aware;
u16 pvid;
@@ -398,6 +404,7 @@ int lan9645x_vlan_port_add_vlan(struct lan9645x_port *p, u16 vid, bool pvid,
struct netlink_ext_ack *extack);
int lan9645x_vlan_port_del_vlan(struct lan9645x_port *p, u16 vid);
void lan9645x_vlan_set_hostmode(struct lan9645x_port *p);
+void lan9645x_vlan_clear_hostmode(struct lan9645x_port *p);
/* MAC table: lan9645x_mac.c */
int lan9645x_mact_flush(struct lan9645x *lan9645x, int port);
diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_vlan.c b/drivers/net/dsa/microchip/lan9645x/lan9645x_vlan.c
index 6d28a7cf1442..29d679c853dc 100644
--- a/drivers/net/dsa/microchip/lan9645x/lan9645x_vlan.c
+++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_vlan.c
@@ -347,6 +347,15 @@ void lan9645x_vlan_set_hostmode(struct lan9645x_port *p)
lan9645x_vlan_port_apply(p);
}
+void lan9645x_vlan_clear_hostmode(struct lan9645x_port *p)
+{
+ lockdep_assert_held(&p->lan9645x->fwd_domain_lock);
+
+ p->lan9645x->vlans[HOST_PVID].portmask &= ~BIT(p->chip_port);
+ lan9645x_vlan_hw_wr(p->lan9645x, HOST_PVID);
+ lan9645x_vlan_port_apply(p);
+}
+
int lan9645x_vlan_init(struct lan9645x *lan9645x)
{
u32 all_phys_ports, all_ports;
--
2.52.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH net-next v13 9/9] net: dsa: lan9645x: add port statistics
2026-09-29 7:48 [PATCH net-next v13 0/9] net: dsa: add DSA support for the LAN9645x switch chip family Jens Emil Schulz Østergaard
` (7 preceding siblings ...)
2026-09-29 7:48 ` [PATCH net-next v13 8/9] net: dsa: lan9645x: add bridge support Jens Emil Schulz Østergaard
@ 2026-09-29 7:48 ` Jens Emil Schulz Østergaard
2026-10-02 21:14 ` netdev-bot+sashiko
8 siblings, 1 reply; 18+ messages in thread
From: Jens Emil Schulz Østergaard @ 2026-09-29 7:48 UTC (permalink / raw)
To: UNGLinuxDriver, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Woojung Huh,
Russell King, Steen Hegelund, Daniel Machon, Geert Uytterhoeven,
Magnus Damm
Cc: linux-kernel, netdev, devicetree, linux-renesas-soc,
Jens Emil Schulz Østergaard
Add statistics support for the port counters. Chip registers are 32 bit,
so this unit is responsible maintaining a 64bit software cache, and
updating it frequently to handle overflows in hardware.
The eMAC and pMAC counter sources are decoded, but only the aggregate
source is reachable for now, until MAC merge support is added.
Reviewed-by: Steen Hegelund <Steen.Hegelund@microchip.com>
Signed-off-by: Jens Emil Schulz Østergaard <jensemil.schulzostergaard@microchip.com>
---
Changes in v12:
- Clear hardware and software counters lan9645x_stats_init().
- get_stats64: add the missing SCNT_RX_PMAC_LONG to rx_packets.
- get_stats64: move SCNT_DR_TAIL from rx_dropped to rx_missed_errors.
- Drop the unimplemented ISDX, ESDX and SFID counter views, the MAC
merge statistics, and the now unused view back pointer, type and name
fields.
- Rename lan9645x_stats_add_cnt() to lan9645x_stats_update_counter().
- Move lan9645x_get_vstats() out of lan9645x_stats.h and into
lan9645x_stats.c, and replace the STATS_INDEX and STAT_COUNTERS macros
with static functions there.
- Drop the NULL return from lan9645x_get_vstats(), and the three guards it
forced on its callers.
- Drop the unreachable mac merge related code.
- get_eth_mac_stats: leave InRangeLengthErrors and OutOfRangeLengthField
unset. Both describe the 802.3 length/type field, which the MAC does not
validate.
- get_eth_mac_stats: leave SingleCollisionFrames unset. SCNT_TX_COL
counts collision events, charging 16 to a frame that hits the excessive
collision limit, so it cannot express the attribute. It is still
reported as rtnl_link_stats64.collisions.
- get_eth_mac_stats: add SCNT_RX_JABBER to FrameTooLongErrors, the
too-long-with-bad-CRC half of the event.
- get_stats64: drop SCNT_RX_SYMBOL_ERR from rx_errors, it is a subset of
SCNT_RX_CRC which rx_crc_errors already covers. Still reported as
SymbolErrorDuringCarrier.
- get_stats64: report rx_length_errors and rx_crc_errors.
rx_length_errors carries the FrameTooLongErrors counters, and runts
are left out of it since they have no 802.3 length attribute.
SCNT_RX_SHORT is reported in rx_errors and rmon undersize_pkts
instead.
- Size the bulk read scratch buffer for a single index rather than all of
them. hw_lock is held across the read and the transfer into the 64 bit
counters, so only one index is ever live.
- Stop the statistics poller in .shutdown. dsa_switch_shutdown() does not
call .teardown, so the delayed work stays armed and keeps reading
registers after the parent holding the regmaps has been shut down.
- Drop dr_tail from the ethtool -S list. It is reported as
rtnl_link_stats64.rx_missed_errors.
- lan9645x_teardown(): stop the counter poller first, so nothing reads
registers while the rest unwinds.
- Stop using SCNT_RX_FRAG. It counts frames received after the port is
paused rather than undersize frames, so it is dropped from rx_errors,
rx_packets and the rmon fragment count.
- get_stats64: drop SCNT_RX_CAT_DROP and SCNT_RX_LONG from rx_dropped.
Classifier drops cover ordinary ingress filtering, and an oversized
frame is an error rather than a drop, so it is reported in rx_errors
and rx_length_errors.
- lan9645x_stats.h: include what it uses, and spell the ethtool statistics
argument u64, matching the definition.
- Rate limit the bulk read error. It sits under the three second poller and
runs once per port.
Changes in v11:
- add comment about calling contexts of sw_lock in
lan9645x_stats_get_stats64
Changes in v5:
- make lan9645x_stats_init void
- add SCNT_TX_BUFDROP to tx_dropped
- change rmon range {0,64} -> {64, 64}. Runt frames counted elsewhere.
- remove rx_crc, rx_symbol_err from rx_packets, as they are already
counted in SZ_* buckets.
- add defensive cancel_delayed_work_sync in stats_free
Changes in v4:
- remove rx_uc and tx_uc from ethtool stats list, as they are derivable
from the eth-mac group
- split stats_init into stats_alloc and stats_init, use alloc in probe
and init in dsa_setup
Changes in v3:
- No changes
Changes in v2:
- introduce spinlock sw_lock protecting software counters and region
buffer.
- ran Ioana's selftest for standard counters
drivers/net/hw/ethtool_std_stats.sh
along with
selftests/drivers/net/hw/ethtool_rmon.sh
they pass except for software injected pause frames.
- remove strings/counters covered by standard counters from
get_strings/get_ethtool_stats.
- fix proper use of 'src' in standard counters
- remove static region table, and use stats_prepare_regions for dynamic
region calculation inspired by ocelot.
- fix queue leak in error path.
---
drivers/net/dsa/microchip/lan9645x/Makefile | 1 +
drivers/net/dsa/microchip/lan9645x/lan9645x_main.c | 82 +-
drivers/net/dsa/microchip/lan9645x/lan9645x_main.h | 3 +
.../net/dsa/microchip/lan9645x/lan9645x_stats.c | 856 +++++++++++++++++++++
.../net/dsa/microchip/lan9645x/lan9645x_stats.h | 229 ++++++
5 files changed, 1170 insertions(+), 1 deletion(-)
diff --git a/drivers/net/dsa/microchip/lan9645x/Makefile b/drivers/net/dsa/microchip/lan9645x/Makefile
index 2413d11fe849..cd994943c1c0 100644
--- a/drivers/net/dsa/microchip/lan9645x/Makefile
+++ b/drivers/net/dsa/microchip/lan9645x/Makefile
@@ -8,4 +8,5 @@ mchp-lan9645x-objs := \
lan9645x_npi.o \
lan9645x_phylink.o \
lan9645x_port.o \
+ lan9645x_stats.o \
lan9645x_vlan.o \
diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
index e41a73c20606..599099c72968 100644
--- a/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
+++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
@@ -5,6 +5,7 @@
#include <linux/platform_device.h>
#include "lan9645x_main.h"
+#include "lan9645x_stats.h"
static const char *lan9645x_resource_names[NUM_TARGETS] = {
[TARGET_GCB] = "gcb",
@@ -74,6 +75,7 @@ static void lan9645x_teardown(struct dsa_switch *ds)
{
struct lan9645x *lan9645x = ds->priv;
+ lan9645x_stats_deinit(lan9645x);
lan9645x_mdb_deinit(lan9645x);
lan9645x_mac_deinit(lan9645x);
mutex_destroy(&lan9645x->fwd_domain_lock);
@@ -297,6 +299,8 @@ static int lan9645x_setup(struct dsa_switch *ds)
lan_wr(BIT(lan9645x->num_phys_ports) | front_ports, lan9645x,
ANA_PGID(PGID_BC));
+ lan9645x_stats_init(lan9645x);
+
ds->mtu_enforcement_ingress = true;
ds->assisted_learning_on_cpu_port = true;
ds->fdb_isolation = true;
@@ -767,6 +771,62 @@ static int lan9645x_port_mdb_del(struct dsa_switch *ds, int port,
return err;
}
+static void lan9645x_get_strings(struct dsa_switch *ds, int port, u32 stringset,
+ uint8_t *data)
+{
+ lan9645x_stats_get_strings(ds->priv, port, stringset, data);
+}
+
+static void lan9645x_get_ethtool_stats(struct dsa_switch *ds, int port,
+ uint64_t *data)
+{
+ lan9645x_stats_get_ethtool_stats(ds->priv, port, data);
+}
+
+static int lan9645x_get_sset_count(struct dsa_switch *ds, int port, int sset)
+{
+ return lan9645x_stats_get_sset_count(ds->priv, port, sset);
+}
+
+static void lan9645x_get_eth_mac_stats(struct dsa_switch *ds, int port,
+ struct ethtool_eth_mac_stats *mac_stats)
+{
+ lan9645x_stats_get_eth_mac_stats(ds->priv, port, mac_stats);
+}
+
+static void
+lan9645x_get_rmon_stats(struct dsa_switch *ds, int port,
+ struct ethtool_rmon_stats *rmon_stats,
+ const struct ethtool_rmon_hist_range **ranges)
+{
+ lan9645x_stats_get_rmon_stats(ds->priv, port, rmon_stats, ranges);
+}
+
+static void lan9645x_get_stats64(struct dsa_switch *ds, int port,
+ struct rtnl_link_stats64 *s)
+{
+ lan9645x_stats_get_stats64(ds->priv, port, s);
+}
+
+static void lan9645x_get_pause_stats(struct dsa_switch *ds, int port,
+ struct ethtool_pause_stats *pause_stats)
+{
+ lan9645x_stats_get_pause_stats(ds->priv, port, pause_stats);
+}
+
+static void lan9645x_get_eth_phy_stats(struct dsa_switch *ds, int port,
+ struct ethtool_eth_phy_stats *phy_stats)
+{
+ lan9645x_stats_get_eth_phy_stats(ds->priv, port, phy_stats);
+}
+
+static void
+lan9645x_get_eth_ctrl_stats(struct dsa_switch *ds, int port,
+ struct ethtool_eth_ctrl_stats *ctrl_stats)
+{
+ lan9645x_stats_get_eth_ctrl_stats(ds->priv, port, ctrl_stats);
+}
+
static const struct dsa_switch_ops lan9645x_switch_ops = {
.get_tag_protocol = lan9645x_get_tag_protocol,
@@ -804,6 +864,17 @@ static const struct dsa_switch_ops lan9645x_switch_ops = {
/* Multicast database */
.port_mdb_add = lan9645x_port_mdb_add,
.port_mdb_del = lan9645x_port_mdb_del,
+
+ /* Port statistics counters. */
+ .get_strings = lan9645x_get_strings,
+ .get_ethtool_stats = lan9645x_get_ethtool_stats,
+ .get_sset_count = lan9645x_get_sset_count,
+ .get_eth_mac_stats = lan9645x_get_eth_mac_stats,
+ .get_rmon_stats = lan9645x_get_rmon_stats,
+ .get_stats64 = lan9645x_get_stats64,
+ .get_pause_stats = lan9645x_get_pause_stats,
+ .get_eth_phy_stats = lan9645x_get_eth_phy_stats,
+ .get_eth_ctrl_stats = lan9645x_get_eth_ctrl_stats,
};
static int lan9645x_request_target_regmaps(struct lan9645x *lan9645x)
@@ -898,9 +969,15 @@ static int lan9645x_probe(struct platform_device *pdev)
lan9645x_set_feat_dis(lan9645x);
- err = dsa_register_switch(ds);
+ err = lan9645x_stats_alloc(lan9645x);
if (err)
+ return dev_err_probe(dev, err, "Failed to allocate stats\n");
+
+ err = dsa_register_switch(ds);
+ if (err) {
+ lan9645x_stats_free(lan9645x);
return dev_err_probe(dev, err, "Failed to register DSA switch\n");
+ }
return 0;
}
@@ -914,6 +991,7 @@ static void lan9645x_remove(struct platform_device *pdev)
/* Calls lan9645x DSA .teardown */
dsa_unregister_switch(lan9645x->ds);
+ lan9645x_stats_free(lan9645x);
dev_set_drvdata(&pdev->dev, NULL);
}
@@ -924,6 +1002,8 @@ static void lan9645x_shutdown(struct platform_device *pdev)
if (!lan9645x)
return;
+ lan9645x_stats_deinit(lan9645x);
+
dsa_switch_shutdown(lan9645x->ds);
dev_set_drvdata(&pdev->dev, NULL);
diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_main.h b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.h
index 0b284eb4e9eb..26ec18cfce15 100644
--- a/drivers/net/dsa/microchip/lan9645x/lan9645x_main.h
+++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.h
@@ -219,6 +219,9 @@ struct lan9645x {
*/
struct mutex mdb_lock;
+ /* Statistics */
+ struct lan9645x_stats *stats;
+
int num_port_dis;
/* VLAN entries */
diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_stats.c b/drivers/net/dsa/microchip/lan9645x/lan9645x_stats.c
new file mode 100644
index 000000000000..1253f7748993
--- /dev/null
+++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_stats.c
@@ -0,0 +1,856 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright (C) 2026 Microchip Technology Inc.
+ */
+
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+
+#include "lan9645x_main.h"
+#include "lan9645x_stats.h"
+
+#define LAN9645X_STATS_CHECK_DELAY (3 * HZ)
+
+/* SYS_STAT_CFG.STAT_CLEAR_SHOT group bits selecting the counter groups that
+ * are indexed by port: 0 (Rx), 1 (Tx) and 2 (Drop).
+ * Bit 0: RX
+ * Bit 1: TX
+ * Bit 2: Drop
+ */
+#define LAN9645X_STAT_PORT_GROUPS GENMASK(2, 0)
+
+static const u32 lan9645x_port_stats_layout[] = {
+ [SCNT_RX_OCT] = 0x0,
+ [SCNT_RX_UC] = 0x1,
+ [SCNT_RX_MC] = 0x2,
+ [SCNT_RX_BC] = 0x3,
+ [SCNT_RX_SHORT] = 0x4,
+ [SCNT_RX_FRAG] = 0x5,
+ [SCNT_RX_JABBER] = 0x6,
+ [SCNT_RX_CRC] = 0x7,
+ [SCNT_RX_SYMBOL_ERR] = 0x8,
+ [SCNT_RX_SZ_64] = 0x9,
+ [SCNT_RX_SZ_65_127] = 0xa,
+ [SCNT_RX_SZ_128_255] = 0xb,
+ [SCNT_RX_SZ_256_511] = 0xc,
+ [SCNT_RX_SZ_512_1023] = 0xd,
+ [SCNT_RX_SZ_1024_1526] = 0xe,
+ [SCNT_RX_SZ_JUMBO] = 0xf,
+ [SCNT_RX_PAUSE] = 0x10,
+ [SCNT_RX_CONTROL] = 0x11,
+ [SCNT_RX_LONG] = 0x12,
+ [SCNT_RX_CAT_DROP] = 0x13,
+ [SCNT_RX_RED_PRIO_0] = 0x14,
+ [SCNT_RX_RED_PRIO_1] = 0x15,
+ [SCNT_RX_RED_PRIO_2] = 0x16,
+ [SCNT_RX_RED_PRIO_3] = 0x17,
+ [SCNT_RX_RED_PRIO_4] = 0x18,
+ [SCNT_RX_RED_PRIO_5] = 0x19,
+ [SCNT_RX_RED_PRIO_6] = 0x1a,
+ [SCNT_RX_RED_PRIO_7] = 0x1b,
+ [SCNT_RX_YELLOW_PRIO_0] = 0x1c,
+ [SCNT_RX_YELLOW_PRIO_1] = 0x1d,
+ [SCNT_RX_YELLOW_PRIO_2] = 0x1e,
+ [SCNT_RX_YELLOW_PRIO_3] = 0x1f,
+ [SCNT_RX_YELLOW_PRIO_4] = 0x20,
+ [SCNT_RX_YELLOW_PRIO_5] = 0x21,
+ [SCNT_RX_YELLOW_PRIO_6] = 0x22,
+ [SCNT_RX_YELLOW_PRIO_7] = 0x23,
+ [SCNT_RX_GREEN_PRIO_0] = 0x24,
+ [SCNT_RX_GREEN_PRIO_1] = 0x25,
+ [SCNT_RX_GREEN_PRIO_2] = 0x26,
+ [SCNT_RX_GREEN_PRIO_3] = 0x27,
+ [SCNT_RX_GREEN_PRIO_4] = 0x28,
+ [SCNT_RX_GREEN_PRIO_5] = 0x29,
+ [SCNT_RX_GREEN_PRIO_6] = 0x2a,
+ [SCNT_RX_GREEN_PRIO_7] = 0x2b,
+ [SCNT_RX_ASSEMBLY_ERR] = 0x2c,
+ [SCNT_RX_SMD_ERR] = 0x2d,
+ [SCNT_RX_ASSEMBLY_OK] = 0x2e,
+ [SCNT_RX_MERGE_FRAG] = 0x2f,
+ [SCNT_RX_PMAC_OCT] = 0x30,
+ [SCNT_RX_PMAC_UC] = 0x31,
+ [SCNT_RX_PMAC_MC] = 0x32,
+ [SCNT_RX_PMAC_BC] = 0x33,
+ [SCNT_RX_PMAC_SHORT] = 0x34,
+ [SCNT_RX_PMAC_FRAG] = 0x35,
+ [SCNT_RX_PMAC_JABBER] = 0x36,
+ [SCNT_RX_PMAC_CRC] = 0x37,
+ [SCNT_RX_PMAC_SYMBOL_ERR] = 0x38,
+ [SCNT_RX_PMAC_SZ_64] = 0x39,
+ [SCNT_RX_PMAC_SZ_65_127] = 0x3a,
+ [SCNT_RX_PMAC_SZ_128_255] = 0x3b,
+ [SCNT_RX_PMAC_SZ_256_511] = 0x3c,
+ [SCNT_RX_PMAC_SZ_512_1023] = 0x3d,
+ [SCNT_RX_PMAC_SZ_1024_1526] = 0x3e,
+ [SCNT_RX_PMAC_SZ_JUMBO] = 0x3f,
+ [SCNT_RX_PMAC_PAUSE] = 0x40,
+ [SCNT_RX_PMAC_CONTROL] = 0x41,
+ [SCNT_RX_PMAC_LONG] = 0x42,
+ [SCNT_TX_OCT] = 0x80,
+ [SCNT_TX_UC] = 0x81,
+ [SCNT_TX_MC] = 0x82,
+ [SCNT_TX_BC] = 0x83,
+ [SCNT_TX_COL] = 0x84,
+ [SCNT_TX_DROP] = 0x85,
+ [SCNT_TX_PAUSE] = 0x86,
+ [SCNT_TX_SZ_64] = 0x87,
+ [SCNT_TX_SZ_65_127] = 0x88,
+ [SCNT_TX_SZ_128_255] = 0x89,
+ [SCNT_TX_SZ_256_511] = 0x8a,
+ [SCNT_TX_SZ_512_1023] = 0x8b,
+ [SCNT_TX_SZ_1024_1526] = 0x8c,
+ [SCNT_TX_SZ_JUMBO] = 0x8d,
+ [SCNT_TX_YELLOW_PRIO_0] = 0x8e,
+ [SCNT_TX_YELLOW_PRIO_1] = 0x8f,
+ [SCNT_TX_YELLOW_PRIO_2] = 0x90,
+ [SCNT_TX_YELLOW_PRIO_3] = 0x91,
+ [SCNT_TX_YELLOW_PRIO_4] = 0x92,
+ [SCNT_TX_YELLOW_PRIO_5] = 0x93,
+ [SCNT_TX_YELLOW_PRIO_6] = 0x94,
+ [SCNT_TX_YELLOW_PRIO_7] = 0x95,
+ [SCNT_TX_GREEN_PRIO_0] = 0x96,
+ [SCNT_TX_GREEN_PRIO_1] = 0x97,
+ [SCNT_TX_GREEN_PRIO_2] = 0x98,
+ [SCNT_TX_GREEN_PRIO_3] = 0x99,
+ [SCNT_TX_GREEN_PRIO_4] = 0x9a,
+ [SCNT_TX_GREEN_PRIO_5] = 0x9b,
+ [SCNT_TX_GREEN_PRIO_6] = 0x9c,
+ [SCNT_TX_GREEN_PRIO_7] = 0x9d,
+ [SCNT_TX_AGED] = 0x9e,
+ [SCNT_TX_LLCT] = 0x9f,
+ [SCNT_TX_CT] = 0xa0,
+ [SCNT_TX_BUFDROP] = 0xa1,
+ [SCNT_TX_MM_HOLD] = 0xa2,
+ [SCNT_TX_MERGE_FRAG] = 0xa3,
+ [SCNT_TX_PMAC_OCT] = 0xa4,
+ [SCNT_TX_PMAC_UC] = 0xa5,
+ [SCNT_TX_PMAC_MC] = 0xa6,
+ [SCNT_TX_PMAC_BC] = 0xa7,
+ [SCNT_TX_PMAC_PAUSE] = 0xa8,
+ [SCNT_TX_PMAC_SZ_64] = 0xa9,
+ [SCNT_TX_PMAC_SZ_65_127] = 0xaa,
+ [SCNT_TX_PMAC_SZ_128_255] = 0xab,
+ [SCNT_TX_PMAC_SZ_256_511] = 0xac,
+ [SCNT_TX_PMAC_SZ_512_1023] = 0xad,
+ [SCNT_TX_PMAC_SZ_1024_1526] = 0xae,
+ [SCNT_TX_PMAC_SZ_JUMBO] = 0xaf,
+ [SCNT_DR_LOCAL] = 0x100,
+ [SCNT_DR_TAIL] = 0x101,
+ [SCNT_DR_YELLOW_PRIO_0] = 0x102,
+ [SCNT_DR_YELLOW_PRIO_1] = 0x103,
+ [SCNT_DR_YELLOW_PRIO_2] = 0x104,
+ [SCNT_DR_YELLOW_PRIO_3] = 0x105,
+ [SCNT_DR_YELLOW_PRIO_4] = 0x106,
+ [SCNT_DR_YELLOW_PRIO_5] = 0x107,
+ [SCNT_DR_YELLOW_PRIO_6] = 0x108,
+ [SCNT_DR_YELLOW_PRIO_7] = 0x109,
+ [SCNT_DR_GREEN_PRIO_0] = 0x10a,
+ [SCNT_DR_GREEN_PRIO_1] = 0x10b,
+ [SCNT_DR_GREEN_PRIO_2] = 0x10c,
+ [SCNT_DR_GREEN_PRIO_3] = 0x10d,
+ [SCNT_DR_GREEN_PRIO_4] = 0x10e,
+ [SCNT_DR_GREEN_PRIO_5] = 0x10f,
+ [SCNT_DR_GREEN_PRIO_6] = 0x110,
+ [SCNT_DR_GREEN_PRIO_7] = 0x111,
+};
+
+struct lan9645x_ethtool_stat {
+ char name[ETH_GSTRING_LEN];
+ u16 idx;
+};
+
+static const struct lan9645x_ethtool_stat lan9645x_port_ethtool_stats[] = {
+ { "rx_cat_drop", SCNT_RX_CAT_DROP },
+ { "rx_red_prio_0", SCNT_RX_RED_PRIO_0 },
+ { "rx_red_prio_1", SCNT_RX_RED_PRIO_1 },
+ { "rx_red_prio_2", SCNT_RX_RED_PRIO_2 },
+ { "rx_red_prio_3", SCNT_RX_RED_PRIO_3 },
+ { "rx_red_prio_4", SCNT_RX_RED_PRIO_4 },
+ { "rx_red_prio_5", SCNT_RX_RED_PRIO_5 },
+ { "rx_red_prio_6", SCNT_RX_RED_PRIO_6 },
+ { "rx_red_prio_7", SCNT_RX_RED_PRIO_7 },
+ { "rx_yellow_prio_0", SCNT_RX_YELLOW_PRIO_0 },
+ { "rx_yellow_prio_1", SCNT_RX_YELLOW_PRIO_1 },
+ { "rx_yellow_prio_2", SCNT_RX_YELLOW_PRIO_2 },
+ { "rx_yellow_prio_3", SCNT_RX_YELLOW_PRIO_3 },
+ { "rx_yellow_prio_4", SCNT_RX_YELLOW_PRIO_4 },
+ { "rx_yellow_prio_5", SCNT_RX_YELLOW_PRIO_5 },
+ { "rx_yellow_prio_6", SCNT_RX_YELLOW_PRIO_6 },
+ { "rx_yellow_prio_7", SCNT_RX_YELLOW_PRIO_7 },
+ { "rx_green_prio_0", SCNT_RX_GREEN_PRIO_0 },
+ { "rx_green_prio_1", SCNT_RX_GREEN_PRIO_1 },
+ { "rx_green_prio_2", SCNT_RX_GREEN_PRIO_2 },
+ { "rx_green_prio_3", SCNT_RX_GREEN_PRIO_3 },
+ { "rx_green_prio_4", SCNT_RX_GREEN_PRIO_4 },
+ { "rx_green_prio_5", SCNT_RX_GREEN_PRIO_5 },
+ { "rx_green_prio_6", SCNT_RX_GREEN_PRIO_6 },
+ { "rx_green_prio_7", SCNT_RX_GREEN_PRIO_7 },
+ { "tx_drop", SCNT_TX_DROP },
+ { "tx_yellow_prio_0", SCNT_TX_YELLOW_PRIO_0 },
+ { "tx_yellow_prio_1", SCNT_TX_YELLOW_PRIO_1 },
+ { "tx_yellow_prio_2", SCNT_TX_YELLOW_PRIO_2 },
+ { "tx_yellow_prio_3", SCNT_TX_YELLOW_PRIO_3 },
+ { "tx_yellow_prio_4", SCNT_TX_YELLOW_PRIO_4 },
+ { "tx_yellow_prio_5", SCNT_TX_YELLOW_PRIO_5 },
+ { "tx_yellow_prio_6", SCNT_TX_YELLOW_PRIO_6 },
+ { "tx_yellow_prio_7", SCNT_TX_YELLOW_PRIO_7 },
+ { "tx_green_prio_0", SCNT_TX_GREEN_PRIO_0 },
+ { "tx_green_prio_1", SCNT_TX_GREEN_PRIO_1 },
+ { "tx_green_prio_2", SCNT_TX_GREEN_PRIO_2 },
+ { "tx_green_prio_3", SCNT_TX_GREEN_PRIO_3 },
+ { "tx_green_prio_4", SCNT_TX_GREEN_PRIO_4 },
+ { "tx_green_prio_5", SCNT_TX_GREEN_PRIO_5 },
+ { "tx_green_prio_6", SCNT_TX_GREEN_PRIO_6 },
+ { "tx_green_prio_7", SCNT_TX_GREEN_PRIO_7 },
+ { "tx_aged", SCNT_TX_AGED },
+ { "tx_bufdrop", SCNT_TX_BUFDROP },
+ { "dr_local", SCNT_DR_LOCAL },
+ { "dr_yellow_prio_0", SCNT_DR_YELLOW_PRIO_0 },
+ { "dr_yellow_prio_1", SCNT_DR_YELLOW_PRIO_1 },
+ { "dr_yellow_prio_2", SCNT_DR_YELLOW_PRIO_2 },
+ { "dr_yellow_prio_3", SCNT_DR_YELLOW_PRIO_3 },
+ { "dr_yellow_prio_4", SCNT_DR_YELLOW_PRIO_4 },
+ { "dr_yellow_prio_5", SCNT_DR_YELLOW_PRIO_5 },
+ { "dr_yellow_prio_6", SCNT_DR_YELLOW_PRIO_6 },
+ { "dr_yellow_prio_7", SCNT_DR_YELLOW_PRIO_7 },
+ { "dr_green_prio_0", SCNT_DR_GREEN_PRIO_0 },
+ { "dr_green_prio_1", SCNT_DR_GREEN_PRIO_1 },
+ { "dr_green_prio_2", SCNT_DR_GREEN_PRIO_2 },
+ { "dr_green_prio_3", SCNT_DR_GREEN_PRIO_3 },
+ { "dr_green_prio_4", SCNT_DR_GREEN_PRIO_4 },
+ { "dr_green_prio_5", SCNT_DR_GREEN_PRIO_5 },
+ { "dr_green_prio_6", SCNT_DR_GREEN_PRIO_6 },
+ { "dr_green_prio_7", SCNT_DR_GREEN_PRIO_7 },
+};
+
+static const struct lan9645x_view_stats lan9645x_view_stat_cfgs[] = {
+ [LAN9645X_STAT_PORTS] = {
+ .layout = lan9645x_port_stats_layout,
+ .num_cnts = ARRAY_SIZE(lan9645x_port_stats_layout),
+ .num_indexes = NUM_PHYS_PORTS,
+ },
+};
+
+static_assert(ARRAY_SIZE(lan9645x_view_stat_cfgs) == LAN9645X_STAT_NUM);
+
+static struct lan9645x_view_stats *
+lan9645x_get_vstats(struct lan9645x *lan9645x,
+ enum lan9645x_view_stat_type type)
+{
+ return &lan9645x->stats->view[type];
+}
+
+static u64 *lan9645x_stats_index(struct lan9645x_view_stats *vstats, int idx)
+{
+ return &vstats->cnts[vstats->num_cnts * idx];
+}
+
+static u64 *lan9645x_stat_counters(struct lan9645x *lan9645x,
+ enum lan9645x_view_stat_type type, int idx)
+{
+ return lan9645x_stats_index(lan9645x_get_vstats(lan9645x, type), idx);
+}
+
+/* Update a 64 bit software counter from a wrapping 32 bit hardware counter.
+ * The low half is replaced, not accumulated, so both must start from the same
+ * baseline. lan9645x_stats_init() clears hardware and software together.
+ */
+static void lan9645x_stats_update_counter(u64 *sw, u32 hw)
+{
+ if (hw < (*sw & U32_MAX))
+ *sw += (u64)1 << 32; /* value has wrapped */
+
+ *sw = (*sw & ~(u64)U32_MAX) + hw;
+}
+
+static int __lan9645x_stats_view_idx_hw_read(struct lan9645x *lan9645x,
+ enum lan9645x_view_stat_type vtype,
+ int idx)
+{
+ struct lan9645x_stat_region region;
+ struct lan9645x_view_stats *vstats;
+ int err;
+
+ lockdep_assert_held(&lan9645x->stats->hw_lock);
+
+ vstats = lan9645x_get_vstats(lan9645x, vtype);
+ if (idx < 0 || idx >= vstats->num_indexes)
+ return -EINVAL;
+
+ lan_wr(SYS_STAT_CFG_STAT_VIEW_SET(idx), lan9645x, SYS_STAT_CFG);
+
+ /* Each region for this index contains counters which are at sequential
+ * addresses, so we can use bulk reads to ease lock pressure a bit.
+ */
+ for (int r = 0; r < vstats->num_regions; r++) {
+ region = vstats->regions[r];
+ err = lan_bulk_rd(&vstats->buf[region.cnts_base_idx],
+ region.cnt, lan9645x,
+ SYS_CNT(region.base_offset));
+ if (err) {
+ dev_err_ratelimited(lan9645x->dev,
+ "stats bulk read err vtype=%d idx=%d err=%d\n",
+ vtype, idx, err);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static void
+__lan9645x_stats_view_idx_transfer(struct lan9645x *lan9645x,
+ enum lan9645x_view_stat_type vtype, int idx)
+{
+ struct lan9645x_view_stats *vstats;
+ u64 *idx_counters;
+ int cntr;
+
+ lockdep_assert_held(&lan9645x->stats->sw_lock);
+
+ vstats = lan9645x_get_vstats(lan9645x, vtype);
+ if (idx < 0 || idx >= vstats->num_indexes)
+ return;
+
+ idx_counters = lan9645x_stats_index(vstats, idx);
+
+ for (cntr = 0; cntr < vstats->num_cnts; cntr++)
+ lan9645x_stats_update_counter(&idx_counters[cntr],
+ vstats->buf[cntr]);
+}
+
+static void __lan9645x_stats_view_idx_update(struct lan9645x *lan9645x,
+ enum lan9645x_view_stat_type vtype,
+ int idx)
+{
+ struct lan9645x_stats *s = lan9645x->stats;
+
+ lockdep_assert_held(&s->hw_lock);
+
+ if (!__lan9645x_stats_view_idx_hw_read(lan9645x, vtype, idx)) {
+ spin_lock(&s->sw_lock);
+ __lan9645x_stats_view_idx_transfer(lan9645x, vtype, idx);
+ spin_unlock(&s->sw_lock);
+ }
+}
+
+static u64 *lan9645x_stats_view_idx_update(struct lan9645x *lan9645x,
+ enum lan9645x_view_stat_type vtype,
+ int idx)
+{
+ struct lan9645x_stats *s = lan9645x->stats;
+
+ mutex_lock(&s->hw_lock);
+ __lan9645x_stats_view_idx_update(lan9645x, vtype, idx);
+ mutex_unlock(&s->hw_lock);
+
+ return lan9645x_stat_counters(lan9645x, vtype, idx);
+}
+
+static void lan9645x_stats_view_update(struct lan9645x *lan9645x,
+ enum lan9645x_view_stat_type vtype)
+{
+ struct lan9645x_stats *s = lan9645x->stats;
+ struct lan9645x_view_stats *vstats;
+ int idx;
+
+ vstats = lan9645x_get_vstats(lan9645x, vtype);
+
+ switch (vtype) {
+ case LAN9645X_STAT_PORTS:
+ mutex_lock(&s->hw_lock);
+ for (idx = 0; idx < vstats->num_indexes; idx++) {
+ if (dsa_is_unused_port(lan9645x->ds, idx))
+ continue;
+ __lan9645x_stats_view_idx_update(lan9645x, vtype, idx);
+ }
+ mutex_unlock(&s->hw_lock);
+ return;
+ default:
+ return;
+ }
+}
+
+static void lan9645x_stats_update(struct lan9645x *lan9645x)
+{
+ for (int vtype = 0; vtype < LAN9645X_STAT_NUM; vtype++)
+ lan9645x_stats_view_update(lan9645x, vtype);
+}
+
+void lan9645x_stats_get_strings(struct lan9645x *lan9645x, int port,
+ u32 stringset, u8 *data)
+{
+ int i;
+
+ if (stringset != ETH_SS_STATS)
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(lan9645x_port_ethtool_stats); i++)
+ ethtool_puts(&data, lan9645x_port_ethtool_stats[i].name);
+}
+
+int lan9645x_stats_get_sset_count(struct lan9645x *lan9645x, int port, int sset)
+{
+ if (sset != ETH_SS_STATS)
+ return -EOPNOTSUPP;
+
+ return ARRAY_SIZE(lan9645x_port_ethtool_stats);
+}
+
+void lan9645x_stats_get_ethtool_stats(struct lan9645x *lan9645x, int port,
+ u64 *data)
+{
+ struct lan9645x_stats *stats = lan9645x->stats;
+ u64 *c;
+ int i;
+
+ c = lan9645x_stats_view_idx_update(lan9645x, LAN9645X_STAT_PORTS, port);
+
+ spin_lock(&stats->sw_lock);
+ for (i = 0; i < ARRAY_SIZE(lan9645x_port_ethtool_stats); i++)
+ *data++ = c[lan9645x_port_ethtool_stats[i].idx];
+ spin_unlock(&stats->sw_lock);
+}
+
+static u64 *lan9645x_stats_port_update(struct lan9645x *lan9645x, int port)
+{
+ return lan9645x_stats_view_idx_update(lan9645x, LAN9645X_STAT_PORTS,
+ port);
+}
+
+void lan9645x_stats_get_eth_mac_stats(struct lan9645x *lan9645x, int port,
+ struct ethtool_eth_mac_stats *m)
+{
+ struct lan9645x_stats *s = lan9645x->stats;
+ u64 *c;
+
+ c = lan9645x_stats_port_update(lan9645x, port);
+
+ spin_lock(&s->sw_lock);
+
+ m->FramesTransmittedOK = c[SCNT_TX_UC] +
+ c[SCNT_TX_MC] +
+ c[SCNT_TX_BC] +
+ c[SCNT_TX_PMAC_UC] +
+ c[SCNT_TX_PMAC_MC] +
+ c[SCNT_TX_PMAC_BC];
+ m->FramesReceivedOK = c[SCNT_RX_UC] +
+ c[SCNT_RX_MC] +
+ c[SCNT_RX_BC] +
+ c[SCNT_RX_PMAC_UC] +
+ c[SCNT_RX_PMAC_MC] +
+ c[SCNT_RX_PMAC_BC];
+ m->FrameCheckSequenceErrors = c[SCNT_RX_CRC] +
+ c[SCNT_RX_PMAC_CRC];
+ m->OctetsTransmittedOK = c[SCNT_TX_OCT] +
+ c[SCNT_TX_PMAC_OCT];
+ m->OctetsReceivedOK = c[SCNT_RX_OCT] +
+ c[SCNT_RX_PMAC_OCT];
+ m->MulticastFramesXmittedOK = c[SCNT_TX_MC] +
+ c[SCNT_TX_PMAC_MC];
+ m->BroadcastFramesXmittedOK = c[SCNT_TX_BC] +
+ c[SCNT_TX_PMAC_BC];
+ m->MulticastFramesReceivedOK = c[SCNT_RX_MC] +
+ c[SCNT_RX_PMAC_MC];
+ m->BroadcastFramesReceivedOK = c[SCNT_RX_BC] +
+ c[SCNT_RX_PMAC_BC];
+ m->FrameTooLongErrors = c[SCNT_RX_JABBER] +
+ c[SCNT_RX_LONG] +
+ c[SCNT_RX_PMAC_JABBER] +
+ c[SCNT_RX_PMAC_LONG];
+
+ spin_unlock(&s->sw_lock);
+}
+
+static const struct ethtool_rmon_hist_range lan9645x_rmon_ranges[] = {
+ { 64, 64 },
+ { 65, 127 },
+ { 128, 255 },
+ { 256, 511 },
+ { 512, 1023 },
+ { 1024, 1526 },
+ { 1527, 0xffff },
+ {}
+};
+
+void
+lan9645x_stats_get_rmon_stats(struct lan9645x *lan9645x, int port,
+ struct ethtool_rmon_stats *r,
+ const struct ethtool_rmon_hist_range **ranges)
+{
+ struct lan9645x_stats *s = lan9645x->stats;
+ u64 *c;
+
+ c = lan9645x_stats_port_update(lan9645x, port);
+
+ spin_lock(&s->sw_lock);
+
+ r->undersize_pkts = c[SCNT_RX_SHORT] +
+ c[SCNT_RX_PMAC_SHORT];
+ r->oversize_pkts = c[SCNT_RX_LONG] +
+ c[SCNT_RX_PMAC_LONG];
+ /* SCNT_RX_FRAG counts frames received after the port is paused, and
+ * increments when pause frames arrive from the link partner.
+ * It counts neither undersize frames nor errors, so it is left out here
+ * and out of rx_packets, unlike lan966x which adds it to rx_errors.
+ * SCNT_RX_PMAC_FRAG does count number of runt frames with invalid CRC.
+ */
+ r->fragments = c[SCNT_RX_PMAC_FRAG];
+ r->jabbers = c[SCNT_RX_JABBER] +
+ c[SCNT_RX_PMAC_JABBER];
+ r->hist[0] = c[SCNT_RX_SZ_64] +
+ c[SCNT_RX_PMAC_SZ_64];
+ r->hist[1] = c[SCNT_RX_SZ_65_127] +
+ c[SCNT_RX_PMAC_SZ_65_127];
+ r->hist[2] = c[SCNT_RX_SZ_128_255] +
+ c[SCNT_RX_PMAC_SZ_128_255];
+ r->hist[3] = c[SCNT_RX_SZ_256_511] +
+ c[SCNT_RX_PMAC_SZ_256_511];
+ r->hist[4] = c[SCNT_RX_SZ_512_1023] +
+ c[SCNT_RX_PMAC_SZ_512_1023];
+ r->hist[5] = c[SCNT_RX_SZ_1024_1526] +
+ c[SCNT_RX_PMAC_SZ_1024_1526];
+ r->hist[6] = c[SCNT_RX_SZ_JUMBO] +
+ c[SCNT_RX_PMAC_SZ_JUMBO];
+ r->hist_tx[0] = c[SCNT_TX_SZ_64] +
+ c[SCNT_TX_PMAC_SZ_64];
+ r->hist_tx[1] = c[SCNT_TX_SZ_65_127] +
+ c[SCNT_TX_PMAC_SZ_65_127];
+ r->hist_tx[2] = c[SCNT_TX_SZ_128_255] +
+ c[SCNT_TX_PMAC_SZ_128_255];
+ r->hist_tx[3] = c[SCNT_TX_SZ_256_511] +
+ c[SCNT_TX_PMAC_SZ_256_511];
+ r->hist_tx[4] = c[SCNT_TX_SZ_512_1023] +
+ c[SCNT_TX_PMAC_SZ_512_1023];
+ r->hist_tx[5] = c[SCNT_TX_SZ_1024_1526] +
+ c[SCNT_TX_PMAC_SZ_1024_1526];
+ r->hist_tx[6] = c[SCNT_TX_SZ_JUMBO] +
+ c[SCNT_TX_PMAC_SZ_JUMBO];
+
+ spin_unlock(&s->sw_lock);
+
+ *ranges = lan9645x_rmon_ranges;
+}
+
+void lan9645x_stats_get_stats64(struct lan9645x *lan9645x, int port,
+ struct rtnl_link_stats64 *stats)
+{
+ struct lan9645x_stats *s = lan9645x->stats;
+ u64 *c;
+
+ c = lan9645x_stat_counters(lan9645x, LAN9645X_STAT_PORTS, port);
+
+ /* ndo_get_stats64 may run in non-sleepable context (under
+ * rcu_read_lock, or with a callers spinlock held as in bonding), so
+ * unlike the ethtool paths we must not take the hw_lock mutex or touch
+ * hardware here.
+ * sw_lock is never taken from softirq/IRQ context by any path, so a
+ * plain spin_lock is sufficient, as in ocelot and ksz.
+ */
+ spin_lock(&s->sw_lock);
+
+ stats->rx_bytes = c[SCNT_RX_OCT] + c[SCNT_RX_PMAC_OCT];
+
+ stats->rx_packets = c[SCNT_RX_SHORT] +
+ c[SCNT_RX_JABBER] +
+ c[SCNT_RX_SZ_64] +
+ c[SCNT_RX_SZ_65_127] +
+ c[SCNT_RX_SZ_128_255] +
+ c[SCNT_RX_SZ_256_511] +
+ c[SCNT_RX_SZ_512_1023] +
+ c[SCNT_RX_SZ_1024_1526] +
+ c[SCNT_RX_SZ_JUMBO] +
+ c[SCNT_RX_LONG] +
+ c[SCNT_RX_PMAC_SHORT] +
+ c[SCNT_RX_PMAC_FRAG] +
+ c[SCNT_RX_PMAC_JABBER] +
+ c[SCNT_RX_PMAC_SZ_64] +
+ c[SCNT_RX_PMAC_SZ_65_127] +
+ c[SCNT_RX_PMAC_SZ_128_255] +
+ c[SCNT_RX_PMAC_SZ_256_511] +
+ c[SCNT_RX_PMAC_SZ_512_1023] +
+ c[SCNT_RX_PMAC_SZ_1024_1526] +
+ c[SCNT_RX_PMAC_SZ_JUMBO] +
+ c[SCNT_RX_PMAC_LONG];
+
+ stats->multicast = c[SCNT_RX_MC] + c[SCNT_RX_PMAC_MC];
+
+ stats->rx_errors = c[SCNT_RX_SHORT] +
+ c[SCNT_RX_JABBER] +
+ c[SCNT_RX_CRC] +
+ c[SCNT_RX_LONG] +
+ c[SCNT_RX_PMAC_SHORT] +
+ c[SCNT_RX_PMAC_JABBER] +
+ c[SCNT_RX_PMAC_CRC] +
+ c[SCNT_RX_PMAC_LONG];
+
+ stats->rx_length_errors = c[SCNT_RX_JABBER] +
+ c[SCNT_RX_LONG] +
+ c[SCNT_RX_PMAC_JABBER] +
+ c[SCNT_RX_PMAC_LONG];
+
+ /* SCNT_RX_CRC counts CRC errors, alignment errors and RX_ER events at
+ * any frame size, so a short frame with a bad CRC is counted here.
+ */
+ stats->rx_crc_errors = c[SCNT_RX_CRC] + c[SCNT_RX_PMAC_CRC];
+
+ stats->rx_missed_errors = c[SCNT_DR_TAIL];
+
+ stats->rx_dropped = c[SCNT_DR_LOCAL];
+
+ for (int i = 0; i < NUM_PRIO_QUEUES; i++) {
+ stats->rx_dropped += c[SCNT_RX_RED_PRIO_0 + i] +
+ c[SCNT_DR_YELLOW_PRIO_0 + i] +
+ c[SCNT_DR_GREEN_PRIO_0 + i];
+ }
+
+ stats->tx_bytes = c[SCNT_TX_OCT] + c[SCNT_TX_PMAC_OCT];
+
+ stats->tx_packets = c[SCNT_TX_SZ_64] +
+ c[SCNT_TX_SZ_65_127] +
+ c[SCNT_TX_SZ_128_255] +
+ c[SCNT_TX_SZ_256_511] +
+ c[SCNT_TX_SZ_512_1023] +
+ c[SCNT_TX_SZ_1024_1526] +
+ c[SCNT_TX_SZ_JUMBO] +
+ c[SCNT_TX_PMAC_SZ_64] +
+ c[SCNT_TX_PMAC_SZ_65_127] +
+ c[SCNT_TX_PMAC_SZ_128_255] +
+ c[SCNT_TX_PMAC_SZ_256_511] +
+ c[SCNT_TX_PMAC_SZ_512_1023] +
+ c[SCNT_TX_PMAC_SZ_1024_1526] +
+ c[SCNT_TX_PMAC_SZ_JUMBO];
+
+ stats->tx_dropped = c[SCNT_TX_DROP] +
+ c[SCNT_TX_AGED] +
+ c[SCNT_TX_BUFDROP];
+
+ stats->collisions = c[SCNT_TX_COL];
+
+ spin_unlock(&s->sw_lock);
+}
+
+void lan9645x_stats_get_eth_phy_stats(struct lan9645x *lan9645x, int port,
+ struct ethtool_eth_phy_stats *p)
+{
+ struct lan9645x_stats *s = lan9645x->stats;
+ u64 *c;
+
+ c = lan9645x_stats_port_update(lan9645x, port);
+
+ spin_lock(&s->sw_lock);
+
+ p->SymbolErrorDuringCarrier = c[SCNT_RX_SYMBOL_ERR] +
+ c[SCNT_RX_PMAC_SYMBOL_ERR];
+
+ spin_unlock(&s->sw_lock);
+}
+
+void
+lan9645x_stats_get_eth_ctrl_stats(struct lan9645x *lan9645x, int port,
+ struct ethtool_eth_ctrl_stats *ctrl)
+{
+ struct lan9645x_stats *s = lan9645x->stats;
+ u64 *c;
+
+ c = lan9645x_stats_port_update(lan9645x, port);
+
+ spin_lock(&s->sw_lock);
+
+ ctrl->MACControlFramesReceived = c[SCNT_RX_CONTROL] +
+ c[SCNT_RX_PMAC_CONTROL];
+
+ spin_unlock(&s->sw_lock);
+}
+
+void lan9645x_stats_get_pause_stats(struct lan9645x *lan9645x, int port,
+ struct ethtool_pause_stats *ps)
+{
+ struct lan9645x_stats *s = lan9645x->stats;
+ u64 *c;
+
+ c = lan9645x_stats_port_update(lan9645x, port);
+
+ spin_lock(&s->sw_lock);
+
+ ps->tx_pause_frames = c[SCNT_TX_PAUSE] + c[SCNT_TX_PMAC_PAUSE];
+ ps->rx_pause_frames = c[SCNT_RX_PAUSE] + c[SCNT_RX_PMAC_PAUSE];
+
+ spin_unlock(&s->sw_lock);
+}
+
+static void lan9645x_check_stats_work(struct work_struct *work)
+{
+ struct delayed_work *del_work = to_delayed_work(work);
+ struct lan9645x_stats *stats;
+
+ stats = container_of(del_work, struct lan9645x_stats, work);
+
+ lan9645x_stats_update(stats->lan9645x);
+
+ queue_delayed_work(stats->queue, &stats->work,
+ LAN9645X_STATS_CHECK_DELAY);
+}
+
+static int lan9645x_stats_prepare_regions(struct lan9645x *lan9645x,
+ struct lan9645x_view_stats *vstat)
+{
+ struct lan9645x_stat_region *regions;
+ const u32 *layout = vstat->layout;
+ size_t num_regions = 1;
+ size_t r;
+ int i;
+
+ for (i = 1; i < vstat->num_cnts; i++)
+ if (layout[i] != layout[i - 1] + 1)
+ num_regions++;
+
+ regions = devm_kcalloc(lan9645x->dev, num_regions, sizeof(*regions),
+ GFP_KERNEL);
+ if (!regions)
+ return -ENOMEM;
+
+ vstat->num_regions = num_regions;
+ vstat->regions = regions;
+
+ regions[0].base_offset = layout[0];
+ regions[0].cnts_base_idx = 0;
+ regions[0].cnt = 1;
+
+ for (i = 1, r = 0; i < vstat->num_cnts; i++) {
+ if (layout[i] != layout[i - 1] + 1) {
+ r++;
+ regions[r].base_offset = layout[i];
+ regions[r].cnts_base_idx = i;
+ regions[r].cnt = 1;
+ } else {
+ regions[r].cnt++;
+ }
+ }
+
+ return 0;
+}
+
+static int lan9645x_view_stat_init(struct lan9645x *lan9645x,
+ struct lan9645x_view_stats *vstat,
+ const struct lan9645x_view_stats *cfg)
+{
+ size_t total = cfg->num_cnts * cfg->num_indexes;
+ int err;
+
+ memcpy(vstat, cfg, sizeof(*cfg));
+
+ vstat->cnts = devm_kcalloc(lan9645x->dev, total, sizeof(u64),
+ GFP_KERNEL);
+ if (!vstat->cnts)
+ return -ENOMEM;
+
+ /* Scratch for one index at a time. hw_lock is held across the bulk
+ * read and the transfer into cnts, so no slot outlives an iteration.
+ */
+ vstat->buf = devm_kcalloc(lan9645x->dev, cfg->num_cnts, sizeof(u32),
+ GFP_KERNEL);
+ if (!vstat->buf)
+ return -ENOMEM;
+
+ err = lan9645x_stats_prepare_regions(lan9645x, vstat);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+int lan9645x_stats_alloc(struct lan9645x *lan9645x)
+{
+ struct lan9645x_stats *stats;
+ int err;
+
+ lan9645x->stats = devm_kzalloc(lan9645x->dev, sizeof(*stats),
+ GFP_KERNEL);
+ if (!lan9645x->stats)
+ return -ENOMEM;
+
+ stats = lan9645x->stats;
+ stats->lan9645x = lan9645x;
+
+ for (int t = 0; t < LAN9645X_STAT_NUM; t++) {
+ err = lan9645x_view_stat_init(lan9645x, &stats->view[t],
+ &lan9645x_view_stat_cfgs[t]);
+ if (err)
+ return err;
+ }
+
+ stats->queue = alloc_ordered_workqueue("%s-stats", 0,
+ dev_name(lan9645x->dev));
+ if (!stats->queue)
+ return -ENOMEM;
+
+ mutex_init(&stats->hw_lock);
+ spin_lock_init(&stats->sw_lock);
+ INIT_DELAYED_WORK(&stats->work, lan9645x_check_stats_work);
+
+ return 0;
+}
+
+void lan9645x_stats_free(struct lan9645x *lan9645x)
+{
+ cancel_delayed_work_sync(&lan9645x->stats->work);
+ destroy_workqueue(lan9645x->stats->queue);
+ mutex_destroy(&lan9645x->stats->hw_lock);
+}
+
+static int lan9645x_stats_clear_view(struct lan9645x *lan9645x, u32 idx,
+ u32 groups)
+{
+ u32 val;
+
+ lockdep_assert_held(&lan9645x->stats->hw_lock);
+
+ lan_wr(SYS_STAT_CFG_STAT_VIEW_SET(idx) |
+ SYS_STAT_CFG_STAT_CLEAR_SHOT_SET(groups),
+ lan9645x, SYS_STAT_CFG);
+
+ /* STAT_CLEAR_SHOT self-clears when the clear completes, in about 1us.
+ * Wait for it, so the next STAT_VIEW write cannot land while a
+ * clear is still in flight.
+ */
+ return lan9645x_rd_poll_timeout(lan9645x, SYS_STAT_CFG, val,
+ !SYS_STAT_CFG_STAT_CLEAR_SHOT_GET(val));
+}
+
+void lan9645x_stats_init(struct lan9645x *lan9645x)
+{
+ struct lan9645x_stats *stats = lan9645x->stats;
+ struct lan9645x_view_stats *vstat;
+
+ /* Make sure software and hardware is zeroed on init. */
+ vstat = &stats->view[LAN9645X_STAT_PORTS];
+
+ mutex_lock(&stats->hw_lock);
+
+ /* Clear hardware before software. A failed clear leaves hardware
+ * non-zero against a zeroed shadow, which the first poll in
+ * lan9645x_stats_update_counter() simply adopts.
+ */
+ for (u32 idx = 0; idx < vstat->num_indexes; idx++)
+ if (lan9645x_stats_clear_view(lan9645x, idx,
+ LAN9645X_STAT_PORT_GROUPS))
+ dev_err(lan9645x->dev,
+ "Timeout clearing counters for port %u\n", idx);
+
+ spin_lock(&stats->sw_lock);
+ memset(vstat->cnts, 0,
+ array3_size(vstat->num_indexes, vstat->num_cnts,
+ sizeof(*vstat->cnts)));
+ spin_unlock(&stats->sw_lock);
+
+ mutex_unlock(&stats->hw_lock);
+
+ queue_delayed_work(stats->queue, &stats->work,
+ LAN9645X_STATS_CHECK_DELAY);
+}
+
+void lan9645x_stats_deinit(struct lan9645x *lan9645x)
+{
+ cancel_delayed_work_sync(&lan9645x->stats->work);
+}
diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_stats.h b/drivers/net/dsa/microchip/lan9645x/lan9645x_stats.h
new file mode 100644
index 000000000000..915e20af6521
--- /dev/null
+++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_stats.h
@@ -0,0 +1,229 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (C) 2026 Microchip Technology Inc.
+ */
+
+#ifndef _LAN9645X_STATS_H_
+#define _LAN9645X_STATS_H_
+
+#include <linux/ethtool.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+
+#include "lan9645x_main.h"
+
+/* Counter indices into stat layout structs */
+#define SCNT_RX_OCT 0
+#define SCNT_RX_UC 1
+#define SCNT_RX_MC 2
+#define SCNT_RX_BC 3
+#define SCNT_RX_SHORT 4
+#define SCNT_RX_FRAG 5
+#define SCNT_RX_JABBER 6
+#define SCNT_RX_CRC 7
+#define SCNT_RX_SYMBOL_ERR 8
+#define SCNT_RX_SZ_64 9
+#define SCNT_RX_SZ_65_127 10
+#define SCNT_RX_SZ_128_255 11
+#define SCNT_RX_SZ_256_511 12
+#define SCNT_RX_SZ_512_1023 13
+#define SCNT_RX_SZ_1024_1526 14
+#define SCNT_RX_SZ_JUMBO 15
+#define SCNT_RX_PAUSE 16
+#define SCNT_RX_CONTROL 17
+#define SCNT_RX_LONG 18
+#define SCNT_RX_CAT_DROP 19
+#define SCNT_RX_RED_PRIO_0 20
+#define SCNT_RX_RED_PRIO_1 21
+#define SCNT_RX_RED_PRIO_2 22
+#define SCNT_RX_RED_PRIO_3 23
+#define SCNT_RX_RED_PRIO_4 24
+#define SCNT_RX_RED_PRIO_5 25
+#define SCNT_RX_RED_PRIO_6 26
+#define SCNT_RX_RED_PRIO_7 27
+#define SCNT_RX_YELLOW_PRIO_0 28
+#define SCNT_RX_YELLOW_PRIO_1 29
+#define SCNT_RX_YELLOW_PRIO_2 30
+#define SCNT_RX_YELLOW_PRIO_3 31
+#define SCNT_RX_YELLOW_PRIO_4 32
+#define SCNT_RX_YELLOW_PRIO_5 33
+#define SCNT_RX_YELLOW_PRIO_6 34
+#define SCNT_RX_YELLOW_PRIO_7 35
+#define SCNT_RX_GREEN_PRIO_0 36
+#define SCNT_RX_GREEN_PRIO_1 37
+#define SCNT_RX_GREEN_PRIO_2 38
+#define SCNT_RX_GREEN_PRIO_3 39
+#define SCNT_RX_GREEN_PRIO_4 40
+#define SCNT_RX_GREEN_PRIO_5 41
+#define SCNT_RX_GREEN_PRIO_6 42
+#define SCNT_RX_GREEN_PRIO_7 43
+#define SCNT_RX_ASSEMBLY_ERR 44
+#define SCNT_RX_SMD_ERR 45
+#define SCNT_RX_ASSEMBLY_OK 46
+#define SCNT_RX_MERGE_FRAG 47
+#define SCNT_RX_PMAC_OCT 48
+#define SCNT_RX_PMAC_UC 49
+#define SCNT_RX_PMAC_MC 50
+#define SCNT_RX_PMAC_BC 51
+#define SCNT_RX_PMAC_SHORT 52
+#define SCNT_RX_PMAC_FRAG 53
+#define SCNT_RX_PMAC_JABBER 54
+#define SCNT_RX_PMAC_CRC 55
+#define SCNT_RX_PMAC_SYMBOL_ERR 56
+#define SCNT_RX_PMAC_SZ_64 57
+#define SCNT_RX_PMAC_SZ_65_127 58
+#define SCNT_RX_PMAC_SZ_128_255 59
+#define SCNT_RX_PMAC_SZ_256_511 60
+#define SCNT_RX_PMAC_SZ_512_1023 61
+#define SCNT_RX_PMAC_SZ_1024_1526 62
+#define SCNT_RX_PMAC_SZ_JUMBO 63
+#define SCNT_RX_PMAC_PAUSE 64
+#define SCNT_RX_PMAC_CONTROL 65
+#define SCNT_RX_PMAC_LONG 66
+#define SCNT_TX_OCT 67
+#define SCNT_TX_UC 68
+#define SCNT_TX_MC 69
+#define SCNT_TX_BC 70
+#define SCNT_TX_COL 71
+#define SCNT_TX_DROP 72
+#define SCNT_TX_PAUSE 73
+#define SCNT_TX_SZ_64 74
+#define SCNT_TX_SZ_65_127 75
+#define SCNT_TX_SZ_128_255 76
+#define SCNT_TX_SZ_256_511 77
+#define SCNT_TX_SZ_512_1023 78
+#define SCNT_TX_SZ_1024_1526 79
+#define SCNT_TX_SZ_JUMBO 80
+#define SCNT_TX_YELLOW_PRIO_0 81
+#define SCNT_TX_YELLOW_PRIO_1 82
+#define SCNT_TX_YELLOW_PRIO_2 83
+#define SCNT_TX_YELLOW_PRIO_3 84
+#define SCNT_TX_YELLOW_PRIO_4 85
+#define SCNT_TX_YELLOW_PRIO_5 86
+#define SCNT_TX_YELLOW_PRIO_6 87
+#define SCNT_TX_YELLOW_PRIO_7 88
+#define SCNT_TX_GREEN_PRIO_0 89
+#define SCNT_TX_GREEN_PRIO_1 90
+#define SCNT_TX_GREEN_PRIO_2 91
+#define SCNT_TX_GREEN_PRIO_3 92
+#define SCNT_TX_GREEN_PRIO_4 93
+#define SCNT_TX_GREEN_PRIO_5 94
+#define SCNT_TX_GREEN_PRIO_6 95
+#define SCNT_TX_GREEN_PRIO_7 96
+#define SCNT_TX_AGED 97
+#define SCNT_TX_LLCT 98
+#define SCNT_TX_CT 99
+#define SCNT_TX_BUFDROP 100
+#define SCNT_TX_MM_HOLD 101
+#define SCNT_TX_MERGE_FRAG 102
+#define SCNT_TX_PMAC_OCT 103
+#define SCNT_TX_PMAC_UC 104
+#define SCNT_TX_PMAC_MC 105
+#define SCNT_TX_PMAC_BC 106
+#define SCNT_TX_PMAC_PAUSE 107
+#define SCNT_TX_PMAC_SZ_64 108
+#define SCNT_TX_PMAC_SZ_65_127 109
+#define SCNT_TX_PMAC_SZ_128_255 110
+#define SCNT_TX_PMAC_SZ_256_511 111
+#define SCNT_TX_PMAC_SZ_512_1023 112
+#define SCNT_TX_PMAC_SZ_1024_1526 113
+#define SCNT_TX_PMAC_SZ_JUMBO 114
+#define SCNT_DR_LOCAL 115
+#define SCNT_DR_TAIL 116
+#define SCNT_DR_YELLOW_PRIO_0 117
+#define SCNT_DR_YELLOW_PRIO_1 118
+#define SCNT_DR_YELLOW_PRIO_2 119
+#define SCNT_DR_YELLOW_PRIO_3 120
+#define SCNT_DR_YELLOW_PRIO_4 121
+#define SCNT_DR_YELLOW_PRIO_5 122
+#define SCNT_DR_YELLOW_PRIO_6 123
+#define SCNT_DR_YELLOW_PRIO_7 124
+#define SCNT_DR_GREEN_PRIO_0 125
+#define SCNT_DR_GREEN_PRIO_1 126
+#define SCNT_DR_GREEN_PRIO_2 127
+#define SCNT_DR_GREEN_PRIO_3 128
+#define SCNT_DR_GREEN_PRIO_4 129
+#define SCNT_DR_GREEN_PRIO_5 130
+#define SCNT_DR_GREEN_PRIO_6 131
+#define SCNT_DR_GREEN_PRIO_7 132
+
+enum lan9645x_view_stat_type {
+ LAN9645X_STAT_PORTS = 0,
+ /* Unimplemented views: ISDX, ESDX, SFID, FRER */
+
+ LAN9645X_STAT_NUM,
+};
+
+struct lan9645x_stat_region {
+ u32 base_offset;
+ u32 cnt;
+ u32 cnts_base_idx;
+};
+
+/* Counters are organized by indices/views such as
+ *
+ * - physical ports
+ * - isdx
+ * - esdx
+ * - frer
+ * - sfid
+ *
+ * Each view contains regions, which is a linear address range of related
+ * stats. I.e. the ports index has RX, TX and Drop regions, and you have a
+ * given counter replicated per index.
+ */
+struct lan9645x_view_stats {
+ /* HW register offsets indexed by SCNT_*, used for bulk reading */
+ const u32 *layout;
+ /* Region description for this view, used for bulk reading */
+ struct lan9645x_stat_region *regions;
+ /* 64bit software counters with the same addr layout hw */
+ u64 *cnts;
+ /* Buffer for bulk reading counter regions from hw */
+ u32 *buf;
+ /* Number of counters per index in view */
+ u32 num_cnts;
+ /* Number of indexes in view */
+ u32 num_indexes;
+ /* Number of counter regions with counters at sequential addresses */
+ size_t num_regions;
+};
+
+struct lan9645x_stats {
+ struct lan9645x *lan9645x;
+ struct mutex hw_lock; /* lock r/w to stat registers and u32 buf */
+ spinlock_t sw_lock; /* lock access to u64 software counters */
+ struct delayed_work work;
+ struct workqueue_struct *queue;
+
+ struct lan9645x_view_stats view[LAN9645X_STAT_NUM];
+};
+
+int lan9645x_stats_alloc(struct lan9645x *lan9645x);
+void lan9645x_stats_free(struct lan9645x *lan9645x);
+void lan9645x_stats_init(struct lan9645x *lan9645x);
+void lan9645x_stats_deinit(struct lan9645x *lan9645x);
+void lan9645x_stats_get_strings(struct lan9645x *lan9645x, int port,
+ u32 stringset, u8 *data);
+int lan9645x_stats_get_sset_count(struct lan9645x *lan9645x, int port,
+ int sset);
+void lan9645x_stats_get_ethtool_stats(struct lan9645x *lan9645x, int port,
+ u64 *data);
+void lan9645x_stats_get_eth_mac_stats(struct lan9645x *lan9645x, int port,
+ struct ethtool_eth_mac_stats *mac_stats);
+void
+lan9645x_stats_get_rmon_stats(struct lan9645x *lan9645x, int port,
+ struct ethtool_rmon_stats *rmon_stats,
+ const struct ethtool_rmon_hist_range **ranges);
+void lan9645x_stats_get_stats64(struct lan9645x *lan9645x, int port,
+ struct rtnl_link_stats64 *s);
+void lan9645x_stats_get_pause_stats(struct lan9645x *lan9645x, int port,
+ struct ethtool_pause_stats *ps);
+void
+lan9645x_stats_get_eth_ctrl_stats(struct lan9645x *lan9645x, int port,
+ struct ethtool_eth_ctrl_stats *ctrl_stats);
+void lan9645x_stats_get_eth_phy_stats(struct lan9645x *lan9645x, int port,
+ struct ethtool_eth_phy_stats *phy_stats);
+
+#endif
--
2.52.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH net-next v13 1/9] net: dsa: add tag driver for LAN9645X
2026-09-29 7:48 ` [PATCH net-next v13 1/9] net: dsa: add tag driver for LAN9645X Jens Emil Schulz Østergaard
@ 2026-10-02 21:14 ` netdev-bot+sashiko
0 siblings, 0 replies; 18+ messages in thread
From: netdev-bot+sashiko @ 2026-10-02 21:14 UTC (permalink / raw)
To: jensemil.schulzostergaard
Cc: UNGLinuxDriver, andrew, olteanv, davem, edumazet, kuba, pabeni,
horms, robh, krzk+dt, conor+dt, woojung.huh, linux,
Steen.Hegelund, daniel.machon, geert+renesas, magnus.damm,
linux-kernel, netdev, devicetree, linux-renesas-soc
Thank you for your contribution! Sashiko AI review found 6 potential
issue(s) to consider.
Critical: 0 · High: 1 (1) · Medium: 2 · Low: 3
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] In lan9645x_xmit_get_vlan_info() (net/dsa/tag_lan9645x.c), any
frame with no in-band tag matching the bridge protocol gets IFH_TCI…
- [Medium] lan9645x_netdev_ops sets no .flow_dissect, so
__skb_flow_dissect() (net/core/flow_dissector.c:1083-1098) falls back…
- [Medium] When the IFH-classified VID equals the port pvid,
lan9645x_rcv() skips restoring the tag (tag_lan9645x.c:451-453).
- [Low] In lan9645x_xmit_get_vlan_info(), the condition `skb_headlen(skb)
>= VLAN_ETH_HLEN && ntohs(hdr->h_vlan_proto) == proto` treats a VLAN…
- [Low] In lan9645x_xmit_get_vlan_info(), the guard `skb_headlen(skb) >=
VLAN_ETH_HLEN` in front of vlan_remove_tag() does not cover every byte…
- [Low] lan9645x_rcv() always restores the classified VLAN as
ETH_P_8021Q.
Pre-existing issues:
- [High] lan9645x_xmit() calls dsa_port_bridge_dev_get(dp)
(include/net/dsa.h:774-778, `dp->bridge ?
--
net: dsa: add tag driver for LAN9645X
This adds a DSA tag driver for the Microchip LAN9645x switch, which uses a
front port as the NPI CPU port. On extraction, frames carry a 16 byte long
prefix plus a 28 byte IFH. On injection, frames carry only the IFH. On
receive, the classified VLAN is restored as a hwaccel C-tag unless it
equals the port pvid.
> diff --git a/net/dsa/tag_lan9645x.c b/net/dsa/tag_lan9645x.c
> new file mode 100644
> index 0000000000000..f54646d4b3947
> --- /dev/null
> +++ b/net/dsa/tag_lan9645x.c
> @@ -0,0 +1,474 @@
[ ... ]
> +static struct sk_buff *lan9645x_xmit_get_vlan_info(struct sk_buff *skb,
> + struct net_device *br,
> + u32 *vlan_tci,
> + u32 *tag_type)
> +{
[ ... ]
> + hdr = skb_vlan_eth_hdr(skb);
> + br_vlan_get_proto(br, &proto);
> +
> + if (skb_headlen(skb) >= VLAN_ETH_HLEN &&
> + ntohs(hdr->h_vlan_proto) == proto) {
> + vlan_remove_tag(skb, &tci);
[Severity: Low]
What happens when the VLAN header is not fully in the linear area?
Because of the skb_headlen() check, a tagged frame with a short linear head
goes to the else branch. The original 802.1Q tag then stays in the payload,
and IFH_TCI is set to br0's pvid.
dsa_user_xmit() does not linearize the skb here. The user netdev inherits
NETIF_F_SG from conduit->vlan_features in dsa_user_setup_tagger(). The
AF_PACKET PACKET_TX_RING path, tpacket_snd()->tpacket_fill_skb(), copies
only hard_header_len (14) bytes into the linear area.
Could the rewriter then push a second tag, so the frame goes out
double-tagged or in the wrong VLAN? Would a pskb_may_pull() of
VLAN_ETH_HLEN before this check avoid that?
[Severity: Low]
Is the VLAN_ETH_HLEN check enough for vlan_remove_tag()?
When h_vlan_encapsulated_proto is an 802.3 length, vlan_set_encap_proto()
also reads the two bytes after the VLAN header:
include/linux/if_vlan.h:vlan_set_encap_proto() {
...
rawp = (unsigned short *)(vhdr + 1);
if (*rawp == 0xFFFF)
...
}
lan9645x has no needed_tailroom, so dsa_user_xmit() does not pad the frame.
skb_put_padto() in lan9645x_xmit() only runs after this function returns.
Take an 18 or 19 byte frame such as DA SA 8100 TCI <len>. Can this read
uninitialized tailroom past skb_tail_pointer()? The only effect is whether
skb->protocol becomes ETH_P_802_3 or ETH_P_802_2, but KMSAN would likely
report an uninit-value.
> + *vlan_tci = tci;
> + } else {
> + rcu_read_lock();
> + br_vlan_get_pvid_rcu(br, &tci);
> + rcu_read_unlock();
[Severity: High]
Is br_vlan_get_pvid_rcu() being passed the right device here?
br comes from dsa_port_bridge_dev_get(), so it is the bridge master. This
call returns the pvid of br0's own VLAN group. That VID is unrelated to the
egress port and to the VLAN the bridge forwarded the frame in, because
br_handle_vlan() already cleared the tag for an egress-untagged VLAN.
lan9645x_xmit() then writes that VID into IFH_TCI with IFH_BYPASS set, so
the rewriter uses it as the classified VID:
lan9645x_ifh_set(ifh, 1, IFH_BYPASS, IFH_BYPASS_SZ);
...
lan9645x_ifh_set(ifh, vlan_tci, IFH_TCI, IFH_TCI_SZ);
The later "net: dsa: lan9645x: add vlan support" patch changes
lan9645x_vlan_port_apply_egress(). It programs a hybrid port (one untagged
VLAN plus tagged VLANs) as LAN9645X_TAG_NO_PVID_NO_UNAWARE, with PORT_VID
set to the untagged VID. In that mode every frame is tagged unless
VID == PORT_VID or VID == 0.
For example, say br0 has pvid 1 (the default), and swp1 has VLAN 10 as
pvid/untagged plus VLAN 20 tagged. A frame sent by the host in VLAN 10 (from
br0.10, or ARP flooded by the bridge) reaches this code untagged and gets
VID 1. So does a frame forwarded in software in VLAN 10.
Would the switch then send it on the wire tagged with VLAN 1 instead of
untagged in VLAN 10?
> + *vlan_tci = tci;
> + }
[ ... ]
> +static struct sk_buff *lan9645x_xmit(struct sk_buff *skb,
> + struct net_device *ndev)
> +{
> + struct dsa_port *dp = dsa_user_to_port(ndev);
> + u32 vlan_tci, tag_type;
> + u32 qos_class;
> + void *ifh;
> +
> + skb = lan9645x_xmit_get_vlan_info(skb, dsa_port_bridge_dev_get(dp),
> + &vlan_tci, &tag_type);
[Severity: High]
This isn't a bug introduced by this patch. The racy pattern is in the DSA
core and is already used by tag_ocelot, tag_sja1105, tag_dsa and others;
this tagger adds another user of it.
dsa_port_bridge_dev_get() reads dp->bridge twice on the TX path. It holds
no lock and uses neither READ_ONCE() nor RCU:
return dp->bridge ? dp->bridge->dev : NULL;
The following path runs under rtnl:
del_nbp()
netdev_upper_dev_unlink()
dsa_port_bridge_leave()
dsa_port_bridge_destroy()
It clears dp->bridge and, on the last reference, frees the dsa_bridge
without waiting for a grace period:
net/dsa/port.c:dsa_port_bridge_destroy() {
...
dp->bridge = NULL;
if (!refcount_dec_and_test(&bridge->refcount))
return;
...
kfree(bridge);
}
TX on the user port (AF_PACKET or locally generated traffic) is not
quiesced at that point.
Can a concurrent lan9645x_xmit() read ->dev from freed memory, or reload
dp->bridge after it became NULL? The result is then passed to
br_vlan_enabled(), br_vlan_get_proto() and br_vlan_get_pvid_rcu().
[ ... ]
> +static struct sk_buff *lan9645x_rcv(struct sk_buff *skb,
> + struct net_device *ndev)
> +{
[ ... ]
> + dp = dsa_user_to_port(skb->dev);
> +
> + if (dsa_port_is_vlan_filtering(dp) && vlan_tci) {
> + u16 port_pvid = 0;
> +
> + br_vlan_get_pvid_rcu(skb->dev, &port_pvid);
> +
> + /* The tag is restored as a C-tag, not as the TAG_TYPE the IFH
> + * reports. The classifier recognizes both TPIDs as VLAN tags,
> + * so an S-tag has already been used for classification by the
> + * time we get here. Restoring it as 802.1AD would make the
> + * bridge push it back into the payload and reclassify the frame
> + * to the port pvid, on a different VID than the one the
> + * hardware forwarded it on.
> + */
> + if ((vlan_tci & VLAN_VID_MASK) != port_pvid)
> + __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q),
> + vlan_tci);
[Severity: Medium]
What happens to a double-tagged frame whose outer VID equals the pvid?
The later "net: dsa: lan9645x: add vlan support" patch sets
ANA_VLAN_CFG_VLAN_POP_CNT=1 without VLAN_INNER_TAG_ENA, so VLAN-aware ports
pop exactly one tag.
Take outer VID 100 (the pvid) with an inner C-tag 200. The frame is
extracted with popcnt == 3, the inner tag is still in the payload, and
IFH_TCI reports VID 100.
No tag is restored here in that case. eth_type_trans() in dsa_switch_rcv()
then sets skb->protocol to 0x8100, and __netif_receive_skb_core() turns the
inner tag into hwaccel VID 200 via skb_vlan_untag().
Would the software bridge then classify the frame to VLAN 200, while the
hardware forwarded it in VLAN 100?
[Severity: Low]
The commit message says this assumes an 802.1Q bridge:
This assumes a bridge vlan_protocol of 802.1Q, which is the only
protocol the driver offloads.
The later "net: dsa: lan9645x: add vlan support" patch documents the same
limitation. Is anything enforcing it?
DSA does not handle SWITCHDEV_ATTR_ID_BRIDGE_VLAN_PROTOCOL, and
__br_vlan_set_proto() sends it with SWITCHDEV_F_SKIP_EOPNOTSUPP. So an
802.1ad bridge cannot be refused that way.
On such a bridge, __allowed_ingress() sees skb->vlan_proto !=
br->vlan_proto. It pushes the tag back into the payload and classifies the
frame to the pvid. Terminated frames on non-pvid VLANs would then land on
the pvid, or be dropped if the port has no pvid.
lan9645x_xmit_get_vlan_info(), on the other hand, handles 802.1ad
explicitly:
*tag_type = (proto != ETH_P_8021Q) ? LAN9645X_IFH_TAG_TYPE_S :
LAN9645X_IFH_TAG_TYPE_C;
Should 802.1ad bridges be refused in port_bridge_join? Should xmit and rcv
also handle an 802.1ad bridge the same way?
> + }
> +
> + return skb;
> +}
> +
> +static const struct dsa_device_ops lan9645x_netdev_ops = {
> + .name = LAN9645X_NAME,
> + .proto = DSA_TAG_PROTO_LAN9645X,
> + .xmit = lan9645x_xmit,
> + .rcv = lan9645x_rcv,
> + /* Covers the extraction prefix too, since dsa_tag_protocol_overhead()
> + * sizes the conduit MTU from this.
> + */
> + .needed_headroom = LAN9645X_TOTAL_TAG_LEN,
> +};
[Severity: Medium]
There is no .flow_dissect callback here, so __skb_flow_dissect() falls back
to dsa_tag_generic_flow_dissect(). That helper assumes the tag overhead is
exactly needed_headroom:
include/net/dsa.h:dsa_tag_generic_flow_dissect() {
...
*offset = tag_len;
*proto = ((__be16 *)skb->data)[(tag_len / 2) - 1];
}
Does that hold for this tagger? Whenever the rewriter popped tags,
lan9645x_rcv() skips a 4 or 8 byte ifh_gap_len between the IFH and the
real DMAC.
The later "net: dsa: lan9645x: add vlan support" patch sets
ANA_VLAN_CFG_VLAN_POP_CNT=1 on VLAN-aware ports. So every tagged frame
received on those ports has a 4 byte gap.
For those frames, would the generic dissector take SMAC or gap bytes as the
EtherType and use the wrong network header offset? That would misdirect RPS
and skb_get_hash() on the conduit.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260929-dsa_lan9645x_switch_driver_base-v13-0-827c2d3617f2%40microchip.com
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH net-next v13 2/9] dt-bindings: net: lan9645x: add LAN9645X switch bindings
2026-09-29 7:48 ` [PATCH net-next v13 2/9] dt-bindings: net: lan9645x: add LAN9645X switch bindings Jens Emil Schulz Østergaard
@ 2026-10-02 21:14 ` netdev-bot+sashiko
0 siblings, 0 replies; 18+ messages in thread
From: netdev-bot+sashiko @ 2026-10-02 21:14 UTC (permalink / raw)
To: jensemil.schulzostergaard
Cc: UNGLinuxDriver, andrew, olteanv, davem, edumazet, kuba, pabeni,
horms, robh, krzk+dt, conor+dt, woojung.huh, linux,
Steen.Hegelund, daniel.machon, geert+renesas, magnus.damm,
linux-kernel, netdev, devicetree, linux-renesas-soc
Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 2
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] The binding makes rx/tx-internal-delay-ps on the MAC-side
switch port nodes an on/off switch for the full 2 ns RGMII delay…
- [Low] The per-port pattern "^(ethernet-)?port@[0-8]$" in
properties/ethernet-ports does not actually limit ports to the 9…
- [Low] The subject "dt-bindings: net: lan9645x: add LAN9645X switch
bindings" does not follow…
--
dt-bindings: net: lan9645x: add LAN9645X switch bindings
This commit adds a DT binding for the Microchip LAN9645x DSA switch. The
smallest SKU, microchip,lan96455s-switch, is the fallback compatible. The
switch register targets are described through reg and reg-names, following
mscc,vsc7514-switch.yaml.
> dt-bindings: net: lan9645x: add LAN9645X switch bindings
[Severity: Low]
This isn't a bug, but the subject doesn't match what
Documentation/devicetree/bindings/submitting-patches.rst asks for. That file
says "Repeating "binding" again should also be avoided". Its example form is
"dt-bindings: iio: adc: Add ROHM BD79100G".
The file lives under net/dsa/, so the usual prefix is "dt-bindings: net: dsa:".
Something like "dt-bindings: net: dsa: Add Microchip LAN9645x switch" would
fit better.
> diff --git a/Documentation/devicetree/bindings/net/dsa/microchip,lan96455s-switch.yaml b/Documentation/devicetree/bindings/net/dsa/microchip,lan96455s-switch.yaml
> new file mode 100644
> index 0000000000000..9deb7a427804f
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/net/dsa/microchip,lan96455s-switch.yaml
[ ... ]
> + ethernet-ports:
> + type: object
> + additionalProperties: true
> + patternProperties:
> + "^(ethernet-)?port@[0-8]$":
[Severity: Low]
Does this pattern actually restrict port nodes to the 9 hardware ports
(dev0..dev8)?
ethernet-ports has additionalProperties: true, so nodes that don't match
"^(ethernet-)?port@[0-8]$" are not rejected here. The inherited
dsa.yaml#/$defs/ethernet-ports and ethernet-switch.yaml both accept any
"^(ethernet-)?port@[0-9a-f]+$" node through dsa-port.yaml.
So a node like ethernet-port@9 { reg = <9>; ... } or ethernet-port@a passes
dt_binding_check and dtbs_check. It also skips the local RGMII delay
constraints. The problem only shows up at probe time:
net/dsa/dsa.c:dsa_switch_parse_ports_of() {
...
if (reg >= ds->num_ports) {
dev_err(ds->dev, "port %pOF index %u exceeds num_ports (%u)\n",
port, reg, ds->num_ports);
...
}
Should the schema reject port nodes outside 0-8?
> + type: object
> + description: Ethernet switch ports
> + additionalProperties: true
> +
> + allOf:
> + - if:
> + properties:
> + phy-mode:
> + contains:
> + enum:
> + - rgmii
> + - rgmii-rxid
> + - rgmii-txid
> + - rgmii-id
> + then:
> + properties:
> + rx-internal-delay-ps:
> + $ref: "#/$defs/internal-delay-ps"
> + tx-internal-delay-ps:
> + $ref: "#/$defs/internal-delay-ps"
[ ... ]
> +$defs:
> + internal-delay-ps:
> + description:
> + Disable the delay line using 0 ps, or enable the 2000 ps delay. The
> + delay line is not tunable, so no other phase can be selected.
> + enum: [0, 2000]
> + default: 0
[Severity: Medium]
Is it intended that rx/tx-internal-delay-ps on the MAC side port nodes turns
the full 2 ns RGMII delay on and off, for all four rgmii phy-modes?
Documentation/devicetree/bindings/net/ethernet-controller.yaml says these
MAC node properties "should be used to indicate fine tuning performed by the
MAC. The values expected here are small. A value of 2000ps, i.e 2ns, and a
phy-mode of 'rgmii' will not be accepted by Reviewers."
The same document says that when the MAC adds the delay, it "must modify the
phy-mode it passes to the PHY, removing the delay it has added. Failure to
remove the delay will result in a non-functioning link."
This schema accepts phy-mode = "rgmii-id" together with
rx-internal-delay-ps = <2000>. In the driver later in this series,
lan9645x_parse_mac_delay() in lan9645x_port.c only checks for 0 or 2000.
lan9645x_rgmii_dll_config() in lan9645x_phylink.c then enables the delay line
directly from the parsed value:
drivers/net/dsa/microchip/lan9645x/lan9645x_phylink.c:lan9645x_rgmii_dll_config() {
...
HSIO_DLL_CFG_DLL_ENA_SET(p->rx_internal_delay) |
HSIO_DLL_CFG_DELAY_ENA_SET(p->rx_internal_delay),
...
}
Nothing there checks phy-mode or removes the delay from the interface that
phylink passes to the PHY.
Take port 7 or 8 with a phy-handle, phy-mode "rgmii-id" and
rx-internal-delay-ps = <2000>. Wouldn't the MAC and the PHY each add 2 ns,
leaving a DT that passes validation with a link that doesn't work?
The example also uses phy-mode = "rgmii" on the CPU port. For any RGMII mode
other than 'rgmii-id', ethernet-controller.yaml says it "is probably wrong"
unless justified.
> +
> +examples:
[ ... ]
> + ethernet-port@7 {
> + reg = <7>;
> + ethernet = <&cpu_host_port>;
> + phy-mode = "rgmii";
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260929-dsa_lan9645x_switch_driver_base-v13-0-827c2d3617f2%40microchip.com
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH net-next v13 4/9] net: dsa: lan9645x: add basic dsa driver for LAN9645X
2026-09-29 7:48 ` [PATCH net-next v13 4/9] net: dsa: lan9645x: add basic dsa driver for LAN9645X Jens Emil Schulz Østergaard
@ 2026-10-02 21:14 ` netdev-bot+sashiko
0 siblings, 0 replies; 18+ messages in thread
From: netdev-bot+sashiko @ 2026-10-02 21:14 UTC (permalink / raw)
To: jensemil.schulzostergaard
Cc: UNGLinuxDriver, andrew, olteanv, davem, edumazet, kuba, pabeni,
horms, robh, krzk+dt, conor+dt, woojung.huh, linux,
Steen.Hegelund, daniel.machon, geert+renesas, magnus.damm,
linux-kernel, netdev, devicetree, linux-renesas-soc
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] lan9645x_probe() gets every register target (gcb, qs, chip_top,
rew, sys, hsio, dev0..dev8, qsys, ana) through…
- [Low] The binding microchip,lan96455s-switch.yaml requires reg and reg-
names (17 windows) on the switch node, but the driver never reads its…
- [Low] The v4 changelog says lan9645x_tag_npi_setup() rejects cascaded
setups, but it only catches a LAN9645x that sits downstream.
- [Low] In lan9645x_reset_switch(), the decision to skip memory init
depends on lan_rd(lan9645x, SYS_RESET_CFG).
--
net: dsa: lan9645x: add basic dsa driver for LAN9645X
This adds a basic DSA driver for the Microchip LAN9645x switch. It covers
switch init, NPI and CPU port module setup, front port setup, and phylink
MAC configuration for the internal CuPHYs and the RGMII ports 7-8. Register
targets come from a parent device as named regmaps, and the parent MFD
driver is planned for a later series.
> diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
> new file mode 100644
> index 0000000000000..c7de3836e6967
> --- /dev/null
> +++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
> @@ -0,0 +1,456 @@
[ ... ]
> +static int lan9645x_tag_npi_setup(struct dsa_switch *ds)
> +{
> + struct dsa_port *dp, *first_cpu_dp = NULL;
> + struct lan9645x *lan9645x = ds->priv;
> + struct phylink_config c = {};
> +
> + dsa_switch_for_each_user_port(dp, ds) {
> + if (dp->cpu_dp->ds != ds) {
> + dev_err(ds->dev,
> + "NPI port on a remote switch is not supported\n");
> + return -EINVAL;
> + }
[Severity: Low]
Does this check catch every cascaded setup? The v4 changelog says
"tag_npi_setup: reject cascaded setups". The loop only looks at this
switch's user ports and only rejects when their cpu_dp is on another
switch, so it only covers a LAN9645x that sits downstream.
Take a LAN9645x at the top of a dsa,member cluster that owns the NPI port
and has a link port to a switch from another family. Every local user port
then has a local cpu_dp, so the check passes. The DSA link port and the
remote user ports behind it are never checked.
The DSA core doesn't seem to reject the mixed tree either.
dsa_switch_setup_tag_protocol() in net/dsa/dsa.c goes straight to connect
when the protocol matches the tree default:
if (tag_ops->proto == dst->default_proto)
goto connect;
lan9645x_port_setup() then sets up the link port like a normal front
port. lan9645x_setup() zeroes the source mask for every port:
for (int p = 0; p < lan9645x->num_phys_ports; p++) {
lan_wr(0, lan9645x, ANA_PGID(PGID_SRC + p));
so frames arriving on the link port are not forwarded. On transmit,
lan9645x_xmit() sets IFH_DSTS = BIT(dp->index) for a remote user port,
which selects the local port with the same index.
The tree would probe successfully, but cascaded traffic would be dropped
or misdirected with nothing logged. Could setup fail instead when a
dsa_switch_for_each_port() loop finds a port where dsa_port_is_dsa(dp) is
true?
[ ... ]
> +static int lan9645x_reset_switch(struct lan9645x *lan9645x)
> +{
> + u32 val = 0;
> + int err;
> +
> + /* The parent must reset the whole chip before instantiating its
> + * children, so the switch core is disabled when we get here. The
> + * sequence below must not run more than once: initializing the
> + * memories on a running core corrupts pointers inside the queue
> + * system, which eventually fills up and stops passing traffic. If the
> + * core is already enabled the chip was not reset, so skip it.
> + */
> + if (SYS_RESET_CFG_CORE_ENA_GET(lan_rd(lan9645x, SYS_RESET_CFG)))
> + return 0;
> +
> + lan_wr(SYS_RESET_CFG_CORE_ENA_SET(0), lan9645x, SYS_RESET_CFG);
> + lan_wr(SYS_RAM_INIT_RAM_INIT_SET(1), lan9645x, SYS_RAM_INIT);
[Severity: Low]
What happens here if the SYS_RESET_CFG read fails? After the WARN_ON_ONCE,
lan_rd() in lan9645x_main.h returns 0:
u32 addr, val = 0;
...
WARN_ON_ONCE(regmap_read(lan_tgt2rmap(lan9645x, t, tinst), addr, &val));
return val;
That reads the same as CORE_ENA=0, so the RAM_INIT sequence could run on a
core that is already running. The comment above says that corrupts the
queue system.
In the same way, lan_wr() returns void, so a failed SYS_RAM_INIT write is
ignored. CORE_ENA could then be set on memories that were never
initialized.
This needs both a chip the parent did not reset and a bus error on exactly
this access. There is no in-tree regmap provider yet, so it is unclear
whether these regmap calls can fail in practice.
Should the regmap_read() result be checked before deciding whether to skip
or run the memory init?
[ ... ]
> +static int lan9645x_request_target_regmaps(struct lan9645x *lan9645x)
> +{
> + const char *resource_name;
> + struct regmap *tgt_map;
> +
> + for (int i = 0; i < NUM_TARGETS; i++) {
> + resource_name = lan9645x_resource_names[i];
> + if (!resource_name)
> + continue;
> +
> + tgt_map = dev_get_regmap(lan9645x->dev->parent, resource_name);
> + if (IS_ERR_OR_NULL(tgt_map)) {
> + dev_err(lan9645x->dev, "Failed to get regmap=%s\n",
> + resource_name);
> + return -ENODEV;
> + }
[Severity: Low]
Is there any in-tree configuration where this lookup can succeed? Nothing
under drivers/mfd registers regmaps with these names (gcb, qs, chip_top,
rew, sys, hsio, dev0..dev8, qsys, ana) for a lan9645x or lan96455 parent.
That means lan9645x_probe() returns -ENODEV every time, including for a DT
node placed under simple-bus. None of the setup, NPI, phylink or port code
in this patch can run in-tree until the MFD parent lands.
The commit message does say "The MFD driver will come in a later series",
so this is mostly a question for the maintainers: should the driver be
merged before anything in-tree can bind it?
[Severity: Low]
Should the driver also be able to use the switch node's own register
windows?
Documentation/devicetree/bindings/net/dsa/microchip,lan96455s-switch.yaml
requires reg and reg-names on the switch node, with 17 windows. The driver
never reads those resources itself, and only asks dev->parent for named
regmaps.
For comparison, ocelot_regmap_from_resource() tries the device's own
IORESOURCE_MEM first and only then falls back to the parent.
If the plan is for the future MFD parent to build the named regmaps from
the child's reg and reg-names, as with VSC7512, then this matches, since
the binding's reg-names match lan9645x_resource_names[]. Otherwise, would a
fallback to the node's own resources be useful here?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260929-dsa_lan9645x_switch_driver_base-v13-0-827c2d3617f2%40microchip.com
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH net-next v13 5/9] net: dsa: lan9645x: add vlan support
2026-09-29 7:48 ` [PATCH net-next v13 5/9] net: dsa: lan9645x: add vlan support Jens Emil Schulz Østergaard
@ 2026-10-02 21:14 ` netdev-bot+sashiko
0 siblings, 0 replies; 18+ messages in thread
From: netdev-bot+sashiko @ 2026-10-02 21:14 UTC (permalink / raw)
To: jensemil.schulzostergaard
Cc: UNGLinuxDriver, andrew, olteanv, davem, edumazet, kuba, pabeni,
horms, robh, krzk+dt, conor+dt, woojung.huh, linux,
Steen.Hegelund, daniel.machon, geert+renesas, magnus.damm,
linux-kernel, netdev, devicetree, linux-renesas-soc
Thank you for your contribution! Sashiko AI review found 4 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 2 · Low: 2
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] On a VLAN-aware bridged port, frames the host sends without a
tag can leave the switch tagged, sometimes with the wrong VID.
- [Medium] On VLAN-aware ports, lan9645x_vlan_port_apply_ingress() sets
ANA_VLAN_CFG_VLAN_POP_CNT=1, so the hardware removes the outer VLAN…
- [Low] The commit message says the patch adds "VLAN support for VLAN-
aware and VLAN-unaware bridges", but none of the bridged code paths…
- [Low] Errors from VLAN table programming are not passed back to
callers.
--
net: dsa: lan9645x: add vlan support
This commit adds VLAN table management to the lan9645x DSA driver. It
configures ports for VLAN-aware and VLAN-unaware bridges, and reserves
HOST_PVID (4095) for standalone ports. It also registers the
port_vlan_filtering, port_vlan_add and port_vlan_del ops, all serialized
by a new fwd_domain_lock.
> Add VLAN support for VLAN-aware and VLAN-unaware bridges.
[Severity: Low]
Does this description match what the commit does on its own?
At this point in the series nothing writes lan9645x->bridge_mask, and
lan9645x_switch_ops has no .port_bridge_join. That means
lan9645x_port_is_bridged() always returns false:
static inline bool lan9645x_port_is_bridged(struct lan9645x_port *p)
{
return p->lan9645x->bridge_mask & BIT(p->chip_port);
}
It also means DSA never offloads a bridge, so .port_vlan_filtering,
.port_vlan_add and .port_vlan_del are never called for user ports. The
only thing that takes effect here is the standalone HOST_PVID setup done
from .port_setup.
Bridge offload, and clearing HOST_PVID membership on bridge join, arrive
with the later "net: dsa: lan9645x: add bridge support" patch. That
ordering is only explained in the v12 changelog below the "---", and git
am drops that part.
Could the commit message say that the bridged paths only become
reachable once the bridge support patch is applied?
> diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_vlan.c b/drivers/net/dsa/microchip/lan9645x/lan9645x_vlan.c
> new file mode 100644
> index 0000000000000..6d28a7cf1442b
> --- /dev/null
> +++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_vlan.c
> @@ -0,0 +1,402 @@
[ ... ]
> +static void
> +lan9645x_vlan_port_apply_egress(struct lan9645x_port *p,
> + struct lan9645x_vlan_port_info *info)
> +{
> + struct lan9645x *lan9645x = p->lan9645x;
> + enum lan9645x_vlan_port_tag tag_cfg;
> + u16 port_vid = UNAWARE_PVID;
> +
> + if (!lan9645x_port_is_bridged(p)) {
> + tag_cfg = LAN9645X_TAG_NO_PVID_NO_UNAWARE;
> + port_vid = HOST_PVID;
> + } else if (p->vlan_aware) {
[ ... ]
> + if (info->untagged == 1 && info->tagged) {
> + tag_cfg = LAN9645X_TAG_NO_PVID_NO_UNAWARE;
> + port_vid = info->untagged_vid;
> + } else if (info->untagged) {
> + tag_cfg = LAN9645X_TAG_DISABLED;
> + } else {
> + tag_cfg = LAN9645X_TAG_ALL;
> + }
[Severity: Medium]
Can untagged frames from the host leave a VLAN-aware bridged port tagged,
and with the wrong VID?
For an untagged host frame on a VLAN-filtering bridge,
lan9645x_xmit_get_vlan_info() in net/dsa/tag_lan9645x.c puts the bridge
device's pvid into the IFH:
} else {
rcu_read_lock();
br_vlan_get_pvid_rcu(br, &tci);
rcu_read_unlock();
*vlan_tci = tci;
}
The rewriter mode here, however, is chosen from the port's own untagged
VID:
- With one untagged VLAN X plus tagged VLANs, TAG_NO_PVID_NO_UNAWARE with
PORT_VID=X leaves only VID X or VID 0 untagged.
- With no untagged VLANs, TAG_ALL tags every frame, including VID 0.
The two sides only agree when br0's pvid equals the port's untagged VID.
Two examples where they don't:
(a) swp0 has "vid 10 pvid untagged" plus tagged vid 20, and br0 self
pvid is 1. br_handle_vlan() strips host traffic on br0.10 for egress on
swp0, and the same happens to VLAN 10 traffic forwarded in software from
a foreign bridge port. The tagger then puts VID 1 in the IFH, so the
frame goes out tagged with VID 1 instead of untagged in VLAN 10.
(b) On a trunk port with no untagged VLANs, untagged frames go out tagged
with br0's pvid. If br0 has no pvid, they go out priority-tagged with
VID 0. STP BPDUs from br_send_bpdu() and LLDP sent on swp0 are examples.
This depends on the rewriter using IFH.TCI as the classified VID when
BYPASS=1, and on the TAG_CFG semantics described in the
lan9645x_vlan_port_tag comment. I could not check either against the
datasheet. ocelot and tag_ocelot use a similar pairing, so this may be a
known limitation.
The code becomes reachable once the bridge support patch lands, and it
is unchanged at the end of the series. Also, LAN9645X_TAG_NO_UNAWARE is
defined but never used.
> + } else {
> + tag_cfg = LAN9645X_TAG_DISABLED;
> + }
[ ... ]
> +static void lan9645x_vlan_port_apply_ingress(struct lan9645x_port *p)
> +{
[ ... ]
> + val = ANA_VLAN_CFG_VLAN_VID_SET(pvid) |
> + ANA_VLAN_CFG_VLAN_TAG_TYPE_SET(0);
> + if (p->vlan_aware)
> + val |= ANA_VLAN_CFG_VLAN_AWARE_ENA_SET(1) |
> + ANA_VLAN_CFG_VLAN_POP_CNT_SET(1);
[Severity: Medium]
Does popping the outer tag here conflict with the pvid check in
lan9645x_rcv()?
With VLAN_POP_CNT=1 on VLAN-aware ports, the classified VID survives
only in the IFH. lan9645x_rcv() in net/dsa/tag_lan9645x.c skips
restoring it when it equals the port pvid:
if ((vlan_tci & VLAN_VID_MASK) != port_pvid)
__vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q),
vlan_tci);
That is fine for untagged frames. Consider a double-tagged frame
[0x8100 VID 100][0x8100 VID 200] that arrives on a VLAN-aware port with
pvid 100 and is sent to the CPU (broadcast, host flood or trap). The
hardware classifies it to VID 100 and pops the outer tag. IFH TCI is
100, which equals the pvid, so no hwaccel tag is set.
The software bridge then finds the inner tag 200 in the payload and puts
the frame in VLAN 200, while the hardware forwarded it in VLAN 100.
Could this cause local delivery (for example to br0.200), or software
forwarding in a different VLAN than the hardware used?
br_allowed_ingress() still restricts this to VIDs the port is a member
of.
[ ... ]
> +int lan9645x_vlan_port_add_vlan(struct lan9645x_port *p, u16 vid, bool pvid,
> + bool untagged, struct netlink_ext_ack *extack)
> +{
[ ... ]
> + lan9645x_vlan_hw_wr(lan9645x, vid);
> + lan9645x_vlan_port_apply_ingress(p);
> + lan9645x_vlan_port_apply_egress(p, &info);
> +
> + return 0;
> +}
[Severity: Low]
Should the return value of lan9645x_vlan_hw_wr() be checked here, before
the software state is committed?
This path updates lan9645x->vlans[] and p->pvid, reprograms the port
ingress and egress configuration, and returns 0 even when the table
write failed. lan9645x_vlan_port_del_vlan(), lan9645x_vlan_cpu_add(),
lan9645x_vlan_cpu_del() and lan9645x_vlan_set_hostmode() do the same.
Inside lan9645x_vlan_hw_wr(), the ANA_VLANTIDX, ANA_VLAN_PORT_MASK and
ANA_VLANACCESS writes go through lan_wr(), which only does WARN_ON_ONCE()
on a regmap error. The engine is also not checked for IDLE before a new
command is issued.
When this fails, the shadow table and the hardware can diverge while
switchdev believes the change succeeded. For the CPU port, DSA caches the
VID as installed, so later adds of the same VID only bump a refcount and
never restore the missing membership.
The comment in lan9645x_vlan_hw_wr() says a timeout most likely means
the register bus is dead, which limits the practical impact. lan_wr()
swallowing errors also predates this patch.
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260929-dsa_lan9645x_switch_driver_base-v13-0-827c2d3617f2%40microchip.com
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH net-next v13 6/9] net: dsa: lan9645x: add mac table integration
2026-09-29 7:48 ` [PATCH net-next v13 6/9] net: dsa: lan9645x: add mac table integration Jens Emil Schulz Østergaard
@ 2026-10-02 21:14 ` netdev-bot+sashiko
0 siblings, 0 replies; 18+ messages in thread
From: netdev-bot+sashiko @ 2026-10-02 21:14 UTC (permalink / raw)
To: jensemil.schulzostergaard
Cc: UNGLinuxDriver, andrew, olteanv, davem, edumazet, kuba, pabeni,
horms, robh, krzk+dt, conor+dt, woojung.huh, linux,
Steen.Hegelund, daniel.machon, geert+renesas, magnus.damm,
linux-kernel, netdev, devicetree, linux-renesas-soc
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 1
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] For a non-zero VID, lan9645x_fdb_add() and lan9645x_fdb_del()
map DSA_DB_PORT and DSA_DB_BRIDGE host entries to the same hardware…
- [Low] Each new MAC table sequence writes the key, the filter and the
command through lan_wr().
--
net: dsa: lan9645x: add mac table integration
This commit adds MAC table support to the lan9645x DSA driver. It adds
learn, forget, flush and GET_NEXT based dump helpers keyed on (vid, mac).
These are wired into the port_fast_age, port_fdb_dump, port_fdb_add and
port_fdb_del DSA callbacks.
> diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_mac.c b/drivers/net/dsa/microchip/lan9645x/lan9645x_mac.c
> new file mode 100644
> index 0000000000000..50ce1378b9dae
> --- /dev/null
> +++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_mac.c
> @@ -0,0 +1,268 @@
[ ... ]
> +static void lan9645x_mac_select(struct lan9645x *lan9645x,
> + const unsigned char mac[ETH_ALEN],
> + unsigned int vid)
> +{
> + u64 maddr = ether_addr_to_u64(mac);
> +
> + lockdep_assert_held(&lan9645x->mact_lock);
> +
> + lan_wr(ANA_MACHDATA_VID_SET(vid) |
> + ANA_MACHDATA_MACHDATA_SET(upper_32_bits(maddr)),
> + lan9645x,
> + ANA_MACHDATA);
> +
> + lan_wr(lower_32_bits(maddr),
> + lan9645x,
> + ANA_MACLDATA);
> +}
[ ... ]
> +static int __lan9645x_mact_learn(struct lan9645x *lan9645x, int port,
> + const unsigned char mac[ETH_ALEN],
> + unsigned int vid,
> + enum macaccess_entry_type type,
> + bool cpu_copy)
> +{
> + lockdep_assert_held(&lan9645x->mact_lock);
> +
> + lan9645x_mac_select(lan9645x, mac, vid);
> +
> + lan_wr(ANA_MACACCESS_VALID_SET(1) |
> + ANA_MACACCESS_DEST_IDX_SET(port) |
> + ANA_MACACCESS_MAC_CPU_COPY_SET(cpu_copy) |
> + ANA_MACACCESS_ENTRYTYPE_SET(type) |
> + ANA_MACACCESS_MAC_TABLE_CMD_SET(CMD_LEARN),
> + lan9645x, ANA_MACACCESS);
> +
> + return lan9645x_mac_wait_for_completion(lan9645x, NULL);
> +}
[Severity: Low]
Can a failed register write here be reported to DSA as a successful learn?
lan_wr() returns void and only calls WARN_ON_ONCE() when regmap_write()
fails. Suppose the ANA_MACACCESS command write is lost but the reads that
follow still work. Then lan9645x_mac_wait_for_completion() sees CMD_IDLE
immediately and returns 0:
err = lan9645x_rd_poll_timeout(lan9645x, ANA_MACACCESS, val,
ANA_MACACCESS_MAC_TABLE_CMD_GET(val) ==
CMD_IDLE);
In that case lan9645x_fdb_add() returns success and the bridge marks the
entry as offloaded, but the table was never changed. The same applies to
CMD_FORGET in __lan9645x_mact_forget().
What happens if only the ANA_MACHDATA or ANA_MACLDATA write in
lan9645x_mac_select() fails? Would CMD_LEARN or CMD_FORGET then act on a
stale key? That could be the previous operation's key, or whatever the
last GET_NEXT walk in lan9645x_mact_dsa_dump() left in the registers.
lan9645x_mact_flush() has the same problem with its ANA_ANAGEFIL write:
lan_wr(ANA_ANAGEFIL_PID_EN_SET(1) |
ANA_ANAGEFIL_PID_VAL_SET(port),
lan9645x, ANA_ANAGEFIL);
If that write fails, both CMD_AGE scans would run without the port
filter. Would that flush dynamic entries from every port instead of only
the requested one?
This only happens if the parent MFD bus can fail a write while later
reads succeed. The parent driver is not in the tree, so that could not be
confirmed. The ocelot-family drivers follow the same lan_wr() convention.
[ ... ]
> diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
> index 6818b91f4bed2..5cb4f5fe13740 100644
> --- a/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
> +++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
[ ... ]
> @@ -360,6 +364,81 @@ static int lan9645x_port_vlan_del(struct dsa_switch *ds, int port,
[ ... ]
> +static int lan9645x_fdb_add(struct dsa_switch *ds, int port,
> + const unsigned char *addr, u16 vid,
> + struct dsa_db db)
> +{
[ ... ]
> + if (dsa_port_is_cpu(dp) && !br &&
> + dsa_fdb_present_in_other_db(ds, port, addr, vid, db))
> + return 0;
> +
> + if (!vid)
> + vid = lan9645x_vlan_unaware_pvid(!!br);
> +
> + dest = dsa_port_is_cpu(dp) ? PGID_CPU : port;
> +
> + return lan9645x_mact_learn(lan9645x, dest, addr, vid, ENTRYTYPE_LOCKED,
> + false);
> +}
> +
> +static int lan9645x_fdb_del(struct dsa_switch *ds, int port,
> + const unsigned char *addr, u16 vid,
> + struct dsa_db db)
> +{
> + struct net_device *br = lan9645x_db2bridge(db);
> + struct dsa_port *dp = dsa_to_port(ds, port);
> + struct lan9645x *lan9645x = ds->priv;
> +
> + if (IS_ERR(br))
> + return PTR_ERR(br);
> +
> + if (dsa_port_is_cpu(dp) && !br &&
> + dsa_fdb_present_in_other_db(ds, port, addr, vid, db))
> + return 0;
> +
> + if (!vid)
> + vid = lan9645x_vlan_unaware_pvid(!!br);
> +
> + return lan9645x_mact_forget(lan9645x, addr, vid, ENTRYTYPE_LOCKED);
> +}
[Severity: Medium]
Could this forget a CPU port entry that another database still needs?
For a non-zero vid, lan9645x_fdb_add() and lan9645x_fdb_del() map two
host entries with the same (vid, mac) to one hardware key pointing at
PGID_CPU: the DSA_DB_PORT one and the DSA_DB_BRIDGE one. Only vid 0 is
split, into HOST_PVID and UNAWARE_PVID by lan9645x_vlan_unaware_pvid().
DSA refcounts the two entries separately on the CPU port, because
dsa_mac_addr_find() includes the db in its key. The shared-key guard only
matches entries of the same db type:
net/dsa/dsa.c:dsa_fdb_present_in_other_db() {
...
if (a->db.type == db.type && !dsa_db_equal(&a->db, &db))
return true;
...
}
So when a DSA_DB_PORT entry is deleted, the guard never sees a
DSA_DB_BRIDGE entry with the same key. For DSA_DB_BRIDGE deletions, the
!br check skips the guard entirely.
One way to hit this:
- swp1 is in a VLAN-aware bridge.
- swp1.100 is a VLAN upper whose MAC M differs from swp1's.
- dsa_user_vlan_rx_add_vid()->dsa_user_schedule_standalone_work(DSA_UC_ADD)
installs a DSA_DB_PORT host entry (M, 100).
- M is also a bridge host address in VID 100, which adds a DSA_DB_BRIDGE
host entry (M, 100). This happens, for example, with br0 using vid 100
self and the same MAC, or with a local entry of another port in VID 100.
Removing either entry, for example by taking swp1.100 down or deleting
the bridge VLAN, goes through:
dsa_switch_host_fdb_del()
dsa_port_do_fdb_del()
lan9645x_fdb_del()
dsa_fdb_present_in_other_db() /* false, db types differ */
lan9645x_mact_forget() /* CMD_FORGET on (M, 100) */
Wouldn't the hardware then lose the entry for the host address that is
still wanted?
__lan9645x_port_set_host_flood() leaves the CPU out of PGID_UC when only
bridged ports request host flooding. Unicast to M in VID 100 could then
be dropped instead of reaching the host. The felix driver uses the same
pattern.
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260929-dsa_lan9645x_switch_driver_base-v13-0-827c2d3617f2%40microchip.com
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH net-next v13 7/9] net: dsa: lan9645x: add mdb management
2026-09-29 7:48 ` [PATCH net-next v13 7/9] net: dsa: lan9645x: add mdb management Jens Emil Schulz Østergaard
@ 2026-10-02 21:14 ` netdev-bot+sashiko
0 siblings, 0 replies; 18+ messages in thread
From: netdev-bot+sashiko @ 2026-10-02 21:14 UTC (permalink / raw)
To: jensemil.schulzostergaard
Cc: UNGLinuxDriver, andrew, olteanv, davem, edumazet, kuba, pabeni,
horms, robh, krzk+dt, conor+dt, woojung.huh, linux,
Steen.Hegelund, daniel.machon, geert+renesas, magnus.damm,
linux-kernel, netdev, devicetree, linux-renesas-soc
Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 2
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] On a failed MAC table write, lan9645x_mdb_widen_dest() drops
the reference on new_pgid right away…
- [Low] lan9645x_mdb_narrow_dest() commits mdb_entry->pgid = new_pgid and
mdb_entry->ports = new_ports before calling lan9645x_mdb_write_dest().
- [Low] A failed CPU-only membership delete leaks a PGID reference
because old_pgid and new_pgid are the same object.
--
net: dsa: lan9645x: add mdb management
This adds the DSA mdb callbacks to the lan9645x driver. IP multicast
stores the port group mask inline in the mac table entry. L2 multicast
points at a refcounted PGID whose destination mask is the port group.
CPU membership is carried by the MAC_CPU_COPY bit, and when the PGIDs
run out, add and delete handle it differently.
> diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_mdb.c b/drivers/net/dsa/microchip/lan9645x/lan9645x_mdb.c
> new file mode 100644
> index 0000000000000..92ab5f55f7913
> --- /dev/null
> +++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_mdb.c
> @@ -0,0 +1,569 @@
[ ... ]
> +static int lan9645x_mdb_widen_dest(struct lan9645x *lan9645x,
> + struct lan9645x_mdb_entry *mdb_entry,
> + enum macaccess_entry_type type,
> + struct lan9645x_pgid_entry *new_pgid,
> + int pgid_index, u16 new_ports)
> +{
> + struct lan9645x_pgid_entry *old_pgid = mdb_entry->pgid;
> + int err;
> +
> + lockdep_assert_held(&lan9645x->mdb_lock);
> +
> + err = lan9645x_mdb_write_dest(lan9645x, mdb_entry, type, pgid_index,
> + new_ports);
> + if (err) {
> + lan9645x_pgid_entry_put(lan9645x, new_pgid);
> + return err;
> + }
[Severity: Medium]
Can this hand a PGID index to an unrelated group while hardware still
points at it?
__lan9645x_mact_learn() issues the command before it polls for
completion:
lan_wr(ANA_MACACCESS_VALID_SET(1) |
ANA_MACACCESS_DEST_IDX_SET(port) |
...
ANA_MACACCESS_MAC_TABLE_CMD_SET(CMD_LEARN),
lan9645x, ANA_MACACCESS);
return lan9645x_mac_wait_for_completion(lan9645x, NULL);
That means an error from lan9645x_mdb_write_dest() only tells us the
outcome is unknown, for example after a failed poll read or a timeout.
The entry may already point at pgid_index.
If new_pgid was just created, this put frees its index. A later add
with a different port mask can get the same index back from
lan9645x_mdb_pgid_entry_create(). lan9645x_pgid_entry_alloc() then
rewrites ANA_PGID for that group, so an entry that may have been
written would forward this group to the other group's ports.
When the entry already existed (ports != 0), __lan9645x_mdb_add() does
no further cleanup. For a fresh entry it issues the forget only after
the PGID has been released, and it ignores the result:
lan9645x_mact_forget(lan9645x, mdb_entry->mac,
mdb_entry->vid, type);
lan9645x_mdb_entry_dealloc(lan9645x, mdb_entry);
The comment there says the forget "fails harmlessly if the bus is still
down". Isn't that the case where an entry pointing at a freed PGID is
left in hardware?
The delete side handles the same uncertainty by keeping the reference.
lan9645x_mdb_narrow_dest() keeps old_pgid on failure, and
__lan9645x_mdb_del() sets mdb_entry->pgid = NULL when CMD_FORGET fails.
The v12 changelog gives the reason: "so the index cannot be handed to an
unrelated group while hardware may still point at it".
Should the add path also keep the new_pgid reference when the write
fails?
> + mdb_entry->pgid = new_pgid;
> + mdb_entry->ports = new_ports;
> + lan9645x_pgid_entry_put(lan9645x, old_pgid);
> + return 0;
> +}
[ ... ]
> +static void lan9645x_mdb_narrow_dest(struct lan9645x *lan9645x,
> + struct lan9645x_mdb_entry *mdb_entry,
> + enum macaccess_entry_type type,
> + struct lan9645x_pgid_entry *new_pgid,
> + int pgid_index, u16 new_ports)
> +{
> + struct lan9645x_pgid_entry *old_pgid = mdb_entry->pgid;
> + int err;
> +
> + lockdep_assert_held(&lan9645x->mdb_lock);
> +
> + mdb_entry->pgid = new_pgid;
> + mdb_entry->ports = new_ports;
[Severity: Low]
mdb_entry->pgid is switched to new_pgid before the hardware write. If
that write fails, which PGID does the entry use afterwards? Hardware
would still use old_pgid, while software records new_pgid.
The in-place branch in __lan9645x_mdb_del() assumes hardware points at
mdb_entry->pgid, so it only rewrites ANA_PGID:
if (mdb_entry->pgid && refcount_read(&mdb_entry->pgid->refcount) == 1) {
mdb_entry->pgid->ports = new_ports &
~BIT(lan9645x->num_phys_ports);
...
lan_rmw(ANA_PGID_PGID_SET(mdb_entry->pgid->ports), ...);
return 0;
}
For example, take a group {A,B,C} on PGID P:
1. Deleting C creates Q={A,B} with the last free index, and the MAC
write fails.
2. Deleting B finds no PGID for {A}, so creation returns -ENOSPC.
3. Q has refcount 1, so it is narrowed in place.
Hardware still forwards through P, so B and C keep receiving the group,
and nothing further is logged.
Should the in-place branch, or a later narrowing, rewrite the MAC entry
after an earlier failed write?
> +
> + err = lan9645x_mdb_write_dest(lan9645x, mdb_entry, type, pgid_index,
> + new_ports);
> + if (err) {
> + dev_err(lan9645x->dev,
> + "Narrowing %pM vid %u to mask 0x%x returned %pe\n",
> + mdb_entry->mac, mdb_entry->vid, new_ports,
> + ERR_PTR(err));
> + return;
> + }
[Severity: Low]
Does this early return leak a PGID reference when old_pgid and new_pgid
are the same object?
Take a group {A, CPU} on PGID P, whose ports are {A}. When only the CPU
port module leaves, lan9645x_mdb_pgid_entry_get() strips the CPU bit.
lan9645x_mdb_pgid_entry_lookup() then returns P after refcount_inc().
Now new_pgid == old_pgid == P, and P holds two references for a single
mdb entry.
If lan9645x_mdb_write_dest() fails here, the put below is skipped. When
the group is removed later, lan9645x_mdb_entry_dealloc() drops only one
reference. P and its index then stay reserved until
lan9645x_mdb_deinit().
Keeping old_pgid makes sense when it differs from new_pgid. Here,
though, mdb_entry->pgid already holds P. Should the put still run when
old_pgid == new_pgid?
> +
> + lan9645x_pgid_entry_put(lan9645x, old_pgid);
> +}
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260929-dsa_lan9645x_switch_driver_base-v13-0-827c2d3617f2%40microchip.com
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH net-next v13 8/9] net: dsa: lan9645x: add bridge support
2026-09-29 7:48 ` [PATCH net-next v13 8/9] net: dsa: lan9645x: add bridge support Jens Emil Schulz Østergaard
@ 2026-10-02 21:14 ` netdev-bot+sashiko
0 siblings, 0 replies; 18+ messages in thread
From: netdev-bot+sashiko @ 2026-10-02 21:14 UTC (permalink / raw)
To: jensemil.schulzostergaard
Cc: UNGLinuxDriver, andrew, olteanv, davem, edumazet, kuba, pabeni,
horms, robh, krzk+dt, conor+dt, woojung.huh, linux,
Steen.Hegelund, daniel.machon, geert+renesas, magnus.damm,
linux-kernel, netdev, devicetree, linux-renesas-soc
Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 3 · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] __lan9645x_port_set_host_flood() computes uc_ena/mc_ena from
host_flood_*_mask & ~bridge_mask, so it drops every host flood request…
- [Medium] The MDB code falls back to the L2 multicast flood PGID
(PGID_MC) when it runs out of general-purpose PGIDs.
- [Medium] The patch sets ds->ageing_time_min = 2000 ms, so enslaving a
lan9645x port to a bridge with ageing_time below 2 s (for example 0,…
--
net: dsa: lan9645x: add bridge support
This adds hardware bridge offload to the lan9645x DSA driver for a single
bridge. It covers ageing time, bridge port flags, STP state and host flood.
The CPU port module is taken out of the default UC/MC flood PGIDs. Host
flood requests from offloaded bridged ports are recorded, but they are not
programmed until the port leaves the bridge.
> diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
> index cc85e18c5a3b3..e41a73c20606b 100644
> --- a/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
> +++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_main.c
[ ... ]
> @@ -324,6 +333,257 @@ static void lan9645x_port_phylink_get_caps(struct dsa_switch *ds, int port,
[ ... ]
> +static int lan9645x_port_bridge_flags(struct dsa_switch *ds, int port,
> + struct switchdev_brport_flags f,
> + struct netlink_ext_ack *extack)
> +{
[ ... ]
> + if (f.mask & BR_MCAST_FLOOD) {
> + bool ena = !!(f.val & BR_MCAST_FLOOD);
> +
> + lan9645x_port_pgid_set(lan9645x, PGID_MC, port, ena);
[Severity: Medium]
Does this work with the MDB fallback to PGID_MC?
When the general-purpose PGIDs run out, __lan9645x_mdb_add() and
__lan9645x_mdb_del() in lan9645x_mdb.c point a group at PGID_MC:
drivers/net/dsa/microchip/lan9645x/lan9645x_mdb.c:__lan9645x_mdb_add() {
...
if (lan9645x_mdb_on_flood_mask(mdb_entry, type))
return lan9645x_mdb_widen_dest(lan9645x, mdb_entry,
type, NULL, PGID_MC,
new_ports);
...
}
drivers/net/dsa/microchip/lan9645x/lan9645x_mdb.c:__lan9645x_mdb_del() {
...
lan9645x_mdb_narrow_dest(lan9645x, mdb_entry, type, NULL, PGID_MC,
new_ports);
...
}
After the fallback, the MAC table entry forwards to whatever PGID_MC
holds, not to the member mask that the MDB code computed. That worked
when PGID_MC was fixed to all front ports.
With this change, "bridge link set dev swpN mcast_flood off" clears swpN
from PGID_MC. In the software bridge, mcast_flood only affects
unregistered multicast. Here, though, it looks like a registered member
port with mcast_flood off would stop getting any group that has fallen
back to PGID_MC.
One way to reach this is more than 48 distinct front port subsets among
non-IP L2 MDB groups, for example static "bridge mdb add" entries on a
switch with 6 or more user ports. That would use up PGID_GP_START to
PGID_GP_END. IP multicast should not be affected because the MAC entry
encodes the port mask. CPU membership comes from MAC_CPU_COPY.
> + lan9645x_port_pgid_set(lan9645x, PGID_MCIPV4, port, ena);
> + lan9645x_port_pgid_set(lan9645x, PGID_MCIPV6, port, ena);
> + }
[ ... ]
> +static void __lan9645x_port_set_host_flood(struct lan9645x *lan9645x)
> +{
> + bool mc_ena, uc_ena;
> + u16 unbridged;
> +
> + lockdep_assert_held(&lan9645x->fwd_domain_lock);
[ ... ]
> + unbridged = ~lan9645x->bridge_mask &
> + GENMASK(lan9645x->num_phys_ports - 1, 0);
> +
> + uc_ena = !!(lan9645x->host_flood_uc_mask & unbridged);
[Severity: Medium]
lan9645x_setup() no longer puts the CPU port module in PGID_UC, PGID_MC,
PGID_MCIPV4 or PGID_MCIPV6. That leaves the host flood bit as the only
path for unknown unicast and unregistered multicast to reach the CPU.
With the unbridged filter here, can the CPU ever get that traffic when
only bridged ports ask for host flooding?
The bridge calls dev_set_allmulti(dev, 1) on every port in br_add_if().
br_manage_promisc() sets promisc when vlan_filtering=0, and also when
there is more than one auto port. dsa_user_manage_host_flood() passes
the flags through unchanged:
net/dsa/user.c:dsa_user_manage_host_flood() {
bool mc = dev->flags & (IFF_PROMISC | IFF_ALLMULTI);
...
bool uc = dev->flags & IFF_PROMISC;
dsa_port_set_host_flood(dp, uc, mc);
}
Here, however, ~bridge_mask filters those requests out.
A few cases seem to be affected:
- Take a bridge with a lan9645x port and a foreign port such as wlan,
veth or tap. The software bridge can't flood FDB misses or
unregistered multicast to the foreign port. Assisted learning only
covers addresses it has already learned. DSA returns -EOPNOTSUPP for
MDB entries whose orig_dev is a foreign port, so the hardware would
drop a multicast stream going to a wifi client.
- With multicast snooping disabled, br0 receives no non-link-local
multicast.
- Setting promisc or allmulti explicitly on br0 or on a bridged port
does nothing.
Documentation/networking/switchdev.rst asks for unknown unicast to be
flooded to every port in the domain. It also asks for flooding to the CPU
when IFF_PROMISC or IFF_ALLMULTI is set. I'd also expect the promisc and
allmulti cases of test_bridge in
tools/testing/selftests/net/forwarding/local_termination.sh to fail.
felix has the same global flood mask limitation, but it honours host
flood for bridged ports. The commit message only presents this as
protection for a weak CPU and doesn't mention these consequences.
> + lan9645x_port_pgid_set(lan9645x, PGID_UC, lan9645x->num_phys_ports,
> + uc_ena);
[ ... ]
> +static void lan9645x_port_bridge_leave(struct dsa_switch *ds, int port,
> + struct dsa_bridge bridge)
> +{
> + struct lan9645x *lan9645x = ds->priv;
> + struct lan9645x_port *p;
> +
> + p = lan9645x_to_port(lan9645x, port);
> +
> + mutex_lock(&lan9645x->fwd_domain_lock);
> +
> + lan9645x->bridge_mask &= ~BIT(p->chip_port);
> +
> + __lan9645x_port_set_host_flood(lan9645x);
> + lan9645x_vlan_set_hostmode(p);
[Severity: Medium]
Can the new ds->ageing_time_min = 2U * MSEC_PER_SEC in lan9645x_setup()
leave the driver and the DSA core disagreeing when a bridge join fails
partway through?
Suppose a port joins a bridge whose ageing_time is below 2 s, for
example 0. dsa_port_switchdev_sync_attrs() applies the brport flags, STP
state and VLAN filtering first. Only after that does
dsa_port_ageing_time() fail with -ERANGE:
dsa_port_bridge_join()
DSA_NOTIFIER_BRIDGE_JOIN -> lan9645x_port_bridge_join()
dsa_port_switchdev_sync_attrs()
dsa_port_inherit_brport_flags() p->learn_ena = true, dp->learning = true
dsa_port_set_state()
dsa_port_vlan_filtering(true) p->vlan_aware = true, dp->vlan_filtering = true
dsa_port_ageing_time() -ERANGE
out_rollback_unoffload / out_rollback_unbridge
DSA_NOTIFIER_BRIDGE_LEAVE -> lan9645x_port_bridge_leave()
The rollback never calls dsa_port_switchdev_unsync_attrs(). Here,
lan9645x_vlan_set_hostmode() sets p->vlan_aware = false while
dp->vlan_filtering stays true. p->learn_ena stays true as well.
If the port later joins a vlan_filtering=1 bridge, dsa_port_vlan_filtering()
returns early:
if (dsa_port_is_vlan_filtering(dp) == vlan_filtering)
return 0;
As a result, lan9645x_port_vlan_filtering() is never called. Would the
port then run VLAN-unaware (UNAWARE_PVID, no VLAN_AWARE_ENA) inside a
VLAN-aware bridge?
If the port stays standalone and is brought down and up again, this path
runs:
dsa_port_enable_rt()
dsa_port_set_state_now(BR_STATE_FORWARDING)
lan9645x_port_bridge_stp_state_set()
learn_ena is computed from the stale p->learn_ena, and
ANA_PORT_CFG_LEARN_ENA gets set on a standalone port in HOST_PVID. Is
that intended?
The missing unsync in the DSA core rollback is older than this patch.
However, ageing_time_min is what makes it reachable for lan9645x. On top
of that, bridge_leave resets vlan_aware without DSA knowing.
> + lan9645x_update_fwd_mask(lan9645x);
> +
> + mutex_unlock(&lan9645x->fwd_domain_lock);
> +}
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260929-dsa_lan9645x_switch_driver_base-v13-0-827c2d3617f2%40microchip.com
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH net-next v13 9/9] net: dsa: lan9645x: add port statistics
2026-09-29 7:48 ` [PATCH net-next v13 9/9] net: dsa: lan9645x: add port statistics Jens Emil Schulz Østergaard
@ 2026-10-02 21:14 ` netdev-bot+sashiko
0 siblings, 0 replies; 18+ messages in thread
From: netdev-bot+sashiko @ 2026-10-02 21:14 UTC (permalink / raw)
To: jensemil.schulzostergaard
Cc: UNGLinuxDriver, andrew, olteanv, davem, edumazet, kuba, pabeni,
horms, robh, krzk+dt, conor+dt, woojung.huh, linux,
Steen.Hegelund, daniel.machon, geert+renesas, magnus.damm,
linux-kernel, netdev, devicetree, linux-renesas-soc
Thank you for your contribution! Sashiko AI review found 4 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 3 · Low: 1
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] lan9645x_stats_get_sset_count() returns -EOPNOTSUPP (-95) for
every string set other than ETH_SS_STATS.
- [Medium] lan9645x_stats_get_stats64() counts bad frames in rx_packets.
- [Medium] The eMAC and pMAC FRAG counters are handled inconsistently.
- [Low] The commit message says 'The eMAC and pMAC counter sources are
decoded, but only the aggregate source is reachable for now'.
--
net: dsa: lan9645x: add port statistics
This adds port counter support to the lan9645x DSA driver. The 32 bit
hardware counters are copied into 64 bit software counters by a poller
that runs every three seconds. They are reported through ethtool -S, the
standard ethtool statistics groups and ndo_get_stats64.
> The eMAC and pMAC counter sources are decoded, but only the aggregate
> source is reachable for now, until MAC merge support is added.
[Severity: Low]
Is this sentence still accurate? None of these read the ->src field of the
structure they fill in:
lan9645x_stats_get_eth_mac_stats()
lan9645x_stats_get_rmon_stats()
lan9645x_stats_get_pause_stats()
lan9645x_stats_get_eth_phy_stats()
lan9645x_stats_get_eth_ctrl_stats()
All of them always return the eMAC plus pMAC sum.
The v12 changelog says "Drop the unreachable mac merge related code", so
the per-source decoding seems to have been removed. This has no runtime
effect today, because without .get_mm ethtool only allows the aggregate
source.
Could the message say instead that the pMAC counters are summed into the
aggregate, and that per-source reporting is not implemented yet?
> diff --git a/drivers/net/dsa/microchip/lan9645x/lan9645x_stats.c b/drivers/net/dsa/microchip/lan9645x/lan9645x_stats.c
> new file mode 100644
> index 0000000000000..1253f77489931
> --- /dev/null
> +++ b/drivers/net/dsa/microchip/lan9645x/lan9645x_stats.c
> @@ -0,0 +1,856 @@
[ ... ]
> +int lan9645x_stats_get_sset_count(struct lan9645x *lan9645x, int port, int sset)
> +{
> + if (sset != ETH_SS_STATS)
> + return -EOPNOTSUPP;
[Severity: Medium]
Can this negative return value corrupt the sset counts that the conduit
netdev reports? DSA replaces the conduit's get_sset_count with
dsa_conduit_get_sset_count(). For every sset, that function calls:
net/dsa/conduit.c:dsa_conduit_append_port_sset_count() {
if (ds->ops->get_sset_count)
*count += ds->ops->get_sset_count(ds, port, sset);
}
Nothing checks for a negative value. For ETH_SS_TEST, ETH_SS_PRIV_FLAGS
and ETH_SS_PHY_STATS, the conduit therefore reports its own count minus
95.
If the conduit has fewer than 95 entries, the count is negative. Then
ethtool -t, the netlink priv-flags path and ETHTOOL_GSTRINGS on the
conduit fail with a bogus errno. The conduit's self-tests and private
flags also drop out of ethtool -i and the netlink strset reply.
If the conduit has 95 or more entries, the result is zero or a smaller
positive value. ethtool_self_test(), ethtool_get_phy_stats_ethtool() and
strset_prepare_set() size their buffers from that value, but the
conduit's own callbacks still write their full number of entries. No real
NIC has that many entries, so this part is theoretical.
Most other DSA drivers (hellcreek, lan9303, gswip, mt7530, qca8k, a5psw,
vsc73xx, and others) return 0 for unsupported ssets. Should this return 0
as well?
> +
> + return ARRAY_SIZE(lan9645x_port_ethtool_stats);
> +}
[ ... ]
> + r->undersize_pkts = c[SCNT_RX_SHORT] +
> + c[SCNT_RX_PMAC_SHORT];
> + r->oversize_pkts = c[SCNT_RX_LONG] +
> + c[SCNT_RX_PMAC_LONG];
> + /* SCNT_RX_FRAG counts frames received after the port is paused, and
> + * increments when pause frames arrive from the link partner.
> + * It counts neither undersize frames nor errors, so it is left out here
> + * and out of rx_packets, unlike lan966x which adds it to rx_errors.
> + * SCNT_RX_PMAC_FRAG does count number of runt frames with invalid CRC.
> + */
> + r->fragments = c[SCNT_RX_PMAC_FRAG];
[Severity: Medium]
Can fragments be non-zero without MAC merge support? The pMAC receives no
traffic until MAC merge is supported, so RMON fragments always reads 0 in
lan9645x_stats_get_rmon_stats(). That includes the case where runts with
a bad CRC arrive on the eMAC.
Documentation/networking/statistics.rst says drivers should leave fields
they do not report at ETHTOOL_STAT_NOT_SET, not overwrite them with 0. If
no accurate eMAC fragment count exists, would it be better to leave
fragments unset?
The two MACs are also treated differently in lan9645x_stats_get_stats64().
rx_packets includes c[SCNT_RX_PMAC_FRAG] but not c[SCNT_RX_FRAG]. As a
result, a bad-CRC runt is counted in rx_packets only when the pMAC
receives it.
Does the hardware really use different meanings for the counters at
offset 0x5 and offset 0x35, which is the same position in the pMAC block?
If not, could both MACs be handled the same way?
[ ... ]
> +void lan9645x_stats_get_stats64(struct lan9645x *lan9645x, int port,
> + struct rtnl_link_stats64 *stats)
> +{
[ ... ]
> + stats->rx_packets = c[SCNT_RX_SHORT] +
> + c[SCNT_RX_JABBER] +
> + c[SCNT_RX_SZ_64] +
> + c[SCNT_RX_SZ_65_127] +
> + c[SCNT_RX_SZ_128_255] +
> + c[SCNT_RX_SZ_256_511] +
> + c[SCNT_RX_SZ_512_1023] +
> + c[SCNT_RX_SZ_1024_1526] +
> + c[SCNT_RX_SZ_JUMBO] +
> + c[SCNT_RX_LONG] +
[Severity: Medium]
Should rx_packets include SCNT_RX_SHORT, SCNT_RX_JABBER and SCNT_RX_LONG,
and their pMAC equivalents? The same function counts all of them in
rx_errors below, and counts JABBER and LONG in rx_length_errors too.
include/uapi/linux/if_link.h describes rx_packets as "Number of good
packets received by the interface".
The SZ_* buckets also include frames with a bad FCS. The v5 changelog
says: "remove rx_crc, rx_symbol_err from rx_packets, as they are already
counted in SZ_* buckets".
So when a link partner sends a runt, jabber, oversized or bad-FCS frame,
it shows up in both rx_packets and rx_errors. ocelot and lan966x use a
similar formula.
Would the RX_UC, RX_MC and RX_BC good frame counters, which already feed
FramesReceivedOK, be a better source here?
> + c[SCNT_RX_PMAC_SHORT] +
> + c[SCNT_RX_PMAC_FRAG] +
> + c[SCNT_RX_PMAC_JABBER] +
> + c[SCNT_RX_PMAC_SZ_64] +
> + c[SCNT_RX_PMAC_SZ_65_127] +
> + c[SCNT_RX_PMAC_SZ_128_255] +
> + c[SCNT_RX_PMAC_SZ_256_511] +
> + c[SCNT_RX_PMAC_SZ_512_1023] +
> + c[SCNT_RX_PMAC_SZ_1024_1526] +
> + c[SCNT_RX_PMAC_SZ_JUMBO] +
> + c[SCNT_RX_PMAC_LONG];
> +
> + stats->multicast = c[SCNT_RX_MC] + c[SCNT_RX_PMAC_MC];
> +
> + stats->rx_errors = c[SCNT_RX_SHORT] +
> + c[SCNT_RX_JABBER] +
> + c[SCNT_RX_CRC] +
> + c[SCNT_RX_LONG] +
> + c[SCNT_RX_PMAC_SHORT] +
> + c[SCNT_RX_PMAC_JABBER] +
> + c[SCNT_RX_PMAC_CRC] +
> + c[SCNT_RX_PMAC_LONG];
> +
> + stats->rx_length_errors = c[SCNT_RX_JABBER] +
> + c[SCNT_RX_LONG] +
> + c[SCNT_RX_PMAC_JABBER] +
> + c[SCNT_RX_PMAC_LONG];
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260929-dsa_lan9645x_switch_driver_base-v13-0-827c2d3617f2%40microchip.com
^ permalink raw reply [flat|nested] 18+ messages in thread
end of thread, other threads:[~2026-10-02 21:14 UTC | newest]
Thread overview: 18+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-29 7:48 [PATCH net-next v13 0/9] net: dsa: add DSA support for the LAN9645x switch chip family Jens Emil Schulz Østergaard
2026-09-29 7:48 ` [PATCH net-next v13 1/9] net: dsa: add tag driver for LAN9645X Jens Emil Schulz Østergaard
2026-10-02 21:14 ` netdev-bot+sashiko
2026-09-29 7:48 ` [PATCH net-next v13 2/9] dt-bindings: net: lan9645x: add LAN9645X switch bindings Jens Emil Schulz Østergaard
2026-10-02 21:14 ` netdev-bot+sashiko
2026-09-29 7:48 ` [PATCH net-next v13 3/9] net: dsa: lan9645x: add autogenerated register macros Jens Emil Schulz Østergaard
2026-09-29 7:48 ` [PATCH net-next v13 4/9] net: dsa: lan9645x: add basic dsa driver for LAN9645X Jens Emil Schulz Østergaard
2026-10-02 21:14 ` netdev-bot+sashiko
2026-09-29 7:48 ` [PATCH net-next v13 5/9] net: dsa: lan9645x: add vlan support Jens Emil Schulz Østergaard
2026-10-02 21:14 ` netdev-bot+sashiko
2026-09-29 7:48 ` [PATCH net-next v13 6/9] net: dsa: lan9645x: add mac table integration Jens Emil Schulz Østergaard
2026-10-02 21:14 ` netdev-bot+sashiko
2026-09-29 7:48 ` [PATCH net-next v13 7/9] net: dsa: lan9645x: add mdb management Jens Emil Schulz Østergaard
2026-10-02 21:14 ` netdev-bot+sashiko
2026-09-29 7:48 ` [PATCH net-next v13 8/9] net: dsa: lan9645x: add bridge support Jens Emil Schulz Østergaard
2026-10-02 21:14 ` netdev-bot+sashiko
2026-09-29 7:48 ` [PATCH net-next v13 9/9] net: dsa: lan9645x: add port statistics Jens Emil Schulz Østergaard
2026-10-02 21:14 ` netdev-bot+sashiko
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®